* @git Joomla Component Builder * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // No direct access to this file defined('_JEXEC') or die('Restricted access'); use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Plugin\CMSPlugin; JLoader::register('ComponentbuilderHelper', JPATH_ADMINISTRATOR . '/components/com_componentbuilder/helpers/componentbuilder.php'); /** * Extension - Componentbuilder Language Packaging plugin. * * @package ComponentbuilderLanguagePackaging * @since 1.0.1 */ class PlgExtensionComponentbuilderLanguagePackaging extends CMSPlugin { /** * Affects constructor behavior. If true, language files will be loaded automatically. * * @var boolean * @since 1.0.0 */ protected $autoloadLanguage = true; /** * Database object * * @var DatabaseDriver * @since 1.0.0 */ protected $db; /** * Application object * * @var CMSApplication * @since 1.0.0 */ protected $app; /** * The percentage before a language can be added * * @var int * @since 1.0.0 */ protected $percentageLanguageAdd; /** * The percentage before a language can be added * * @var int * @since 1.0.0 */ protected $percentageLanguageAddOveride = 200; /** * The languages names * * @var array * @since 1.0.0 */ protected $languageNames = array(); /** * The language building tracker * * @var array * @since 1.0.0 */ protected $languageTracker = array(); /** * The languages not added that should be added * * @var array * @since 1.0.0 */ protected $langNot; /** * The languages added * * @var array * @since 1.0.0 */ protected $langSet; /** * The should the site folder be removed * * @var bool * @since 1.0.0 */ protected $removeSiteFolder; /** * The should the site folder be removed * * @var bool * @since 1.0.0 */ protected $removeSiteEditFolder; /** * The component path * * @var string * @since 1.0.0 */ protected $componentPath; /** * The compiler path * * @var string * @since 1.0.0 */ protected $compilerPath; /** * The temporal path * * @var string * @since 1.0.0 */ protected $tempPath; /** * The joomla version * * @var string * @since 1.0.0 */ protected $joomlaVersion; /** * The component version * * @var string * @since 1.0.0 */ protected $component_version; /** * The component name * * @var string * @since 1.0.0 */ protected $componentCodeName; /** * The number of files * * @var int * @since 1.0.0 */ protected $fileCount; /** * The number of lines * * @var bool * @since 1.0.0 */ protected $lineCount; /** * The hash placeholder * * @var string * @since 1.0.0 */ protected $hhh = '#' . '#' . '#'; /** * The file content static values * * @var array * @since 1.0.0 */ protected $fileContentStatic; /* * The line numbers Switch * * @var boolean * @since 1.0.0 */ protected $debugLinenr = false; /** * The Active Components * * @var array * @since 1.0.0 */ protected $activeComponents = array(); /** * The Active Components Names * * @var array * @since 1.0.0 */ protected $activeComponentsNames = array(); /** * The Languages * * @var array * @since 1.0.0 */ protected $languages = array(); /** * The Language build details * * @var array * @since 1.0.0 */ protected $buildDetails = array(); /** * The Excluded Languages * * @var array * @since 1.0.0 */ protected $excludedLang = array(); /** * The Active Language * * @var string * @since 1.0.0 */ protected $langTag; /** * Event Triggered in the compiler [on Before Model Component Data] * * @return void * * @since 1.0 */ public function jcb_ce_onBeforeModelComponentData(&$context, &$component) { // add the privacy $component->params = (isset($component->params) && ComponentbuilderHelper::checkJson($component->params)) ? json_decode($component->params, true) : $component->params; if (ComponentbuilderHelper::checkArray($component->params) && isset($component->params['language_options']) && isset($component->params['language_options']['activate']) && $component->params['language_options']['activate'] == 1) { // load the admin component details $this->activeComponents[$component->id] = $context; $this->activeComponentsNames[$component->id] = ComponentbuilderHelper::safeString($component->name_code); $this->activeComponentsRealNames[$component->id] = $component->name; // add excluded list of languages if (isset($component->params['language_options']['languages'])) { $this->excludedLang[$component->id] = $component->params['language_options']['languages']; } else { $this->excludedLang[$component->id] = array(); } // now set the component add languages if we should use local (2) if (isset($component->params['language_options']['use_percentagelanguageadd']) && $component->params['language_options']['use_percentagelanguageadd'] == 2) { $this->percentageLanguageAddOveride = $component->params['language_options']['percentagelanguageadd']; } } } /** * Event Triggered in the compiler [on After Get] * * @return void * * @since 1.0 */ public function jcb_ce_onAfterGet(&$context, $compiler) { // get component id $id = (int) filter_var($context, FILTER_SANITIZE_NUMBER_INT); // check if there is active if (ComponentbuilderHelper::checkArray($this->activeComponents) && isset($this->activeComponents[$id]) && $this->percentageLanguageAddOveride != 200) { $compiler->percentageLanguageAdd = $this->percentageLanguageAddOveride; } } /** * Event Triggered in the compiler [on Before Set Lang File Data] * * @return void * * @since 1.0 */ public function jcb_ce_onBeforeSetLangFileData(&$context, $compiler) { // lets map some arrays to the plugin for later use $this->compilerPath = $compiler->compilerPath; $this->tempPath = $compiler->tempPath; $this->langTag = $compiler->langTag; $this->debugLinenr = $compiler->debugLinenr; $this->component_version = $compiler->componentData->component_version; $this->joomlaVersion = $compiler->joomlaVersion; $this->percentageLanguageAdd = $compiler->percentageLanguageAdd; $this->langNot = &$compiler->langNot; $this->langSet = &$compiler->langSet; $this->removeSiteFolder = $compiler->removeSiteFolder; $this->removeSiteEditFolder = $compiler->removeSiteEditFolder; $this->componentPath = $compiler->componentPath; $this->componentCodeName = $compiler->componentCodeName; $this->fileCount = &$compiler->fileCount; $this->lineCount = &$compiler->lineCount; $this->fileContentStatic = &$compiler->fileContentStatic; $this->hhh = $compiler->hhh; } /** * Event Triggered in the compiler [on Before Build Plugin Lang Files] * * @return void * * @since 1.0 */ public function jcb_ce_onBeforeBuildPluginLangFiles(&$context, &$plugin, &$languages, &$langTag) { // get component id $id = (int) filter_var($context, FILTER_SANITIZE_NUMBER_INT); // check if there is active if (ComponentbuilderHelper::checkArray($this->activeComponents) && isset($this->activeComponents[$id])) { // set file name $file_name = 'plg_' . strtolower($plugin->group) . '_' . strtolower($plugin->code_name); // extrude the languages that should not remain in the plugin $this->extrudeLanguages($id, $languages, $langTag, $file_name, 'admin'); } } /** * Event Triggered in the compiler [on Before Build Module Lang Files] * * @return void * * @since 1.0 */ public function jcb_ce_onBeforeBuildModuleLangFiles(&$context, &$module, &$languages, &$langTag) { // get component id $id = (int) filter_var($context, FILTER_SANITIZE_NUMBER_INT); // check if there is active if (ComponentbuilderHelper::checkArray($this->activeComponents) && isset($this->activeComponents[$id])) { // extrude the languages that should not remain in the module $this->extrudeLanguages($id, $languages, $langTag, $module->file_name, $module->target_client); } } /** * Event Triggered in the compiler [on Before Build All Lang Files] * * @return void * * @since 1.0 */ public function jcb_ce_onBeforeBuildAllLangFiles(&$context, &$languages, &$langTag) { // get component id $id = (int) filter_var($context, FILTER_SANITIZE_NUMBER_INT); // check if there is active if (ComponentbuilderHelper::checkArray($this->activeComponents) && isset($this->activeComponents[$id])) { // set file name $file_name = 'com_' . $this->activeComponentsNames[$id]; // extrude the languages that should not remain in the module $this->extrudeLanguages($id, $languages, $langTag, $file_name); } // build the language packages $this->buildLanguages($id, $langTag); } /** * Extruder of the languages * * @return void * * @since 1.0 */ protected function extrudeLanguages(&$id, &$languages, &$langTag, &$file_name, $target_client = 'both') { $mainLangLoader = array(); // check if this id was set before if (!isset($this->languages[$id])) { $this->languages[$id] = array(); $this->buildDetails[$id] = array(); } // check if this file name was set before if (!isset($this->languages[$id][$file_name])) { $this->languages[$id][$file_name] = array(); } // set all the extra languages not excluded foreach ($languages as $key => $language) { if ($key !== $langTag && ComponentbuilderHelper::checkArray($language) && (!isset($this->excludedLang[$id]) || !in_array($key, $this->excludedLang[$id]))) { // add to our bucket $this->languages[$id][$file_name][$key] = $language; // remove from the JCB build unset($languages[$key]); } // count the area strings if ($langTag === $key) { foreach ($language as $area => $languageStrings) { $mainLangLoader[$area] = count($languageStrings); } } } // store details for build $this->buildDetails[$id][$file_name] = array($langTag => $mainLangLoader, 'target_client' => $target_client); } /** * Start the building of the languages packages * * @return void * */ protected function buildLanguages(&$id, &$langTag) { if (isset($this->languages[$id]) && ComponentbuilderHelper::checkArray($this->languages[$id])) { // rest xml array $langXML = array(); $langNames = array(); $langPackages = array(); $langZIPNames = array(); $langXMLNames = array(); $versionName = $this->activeComponentsNames[$id] . '_v' . str_replace('.', '_', $this->component_version . '__J' . $this->joomlaVersion); foreach ($this->languages[$id] as $file_name => $languages) { if (ComponentbuilderHelper::checkArray($languages) && isset($this->buildDetails[$id][$file_name][$langTag])) { // get the main lang loader $mainLangLoader = $this->buildDetails[$id][$file_name][$langTag]; // get the target client $target_client = $this->buildDetails[$id][$file_name]['target_client']; foreach ($languages as $tag => $areas) { // trim the tag $tag = trim($tag); // get language name $langName = $this->getLanguageName($tag); $langCodeName = ComponentbuilderHelper::safeString($langName, 'F'); // set the file folder name $langFolderFileName = $langCodeName . '_' . $versionName; // set the main folder path $main_path = $this->compilerPath . '/' . $langFolderFileName . '/'; // set the language name for later $langNames[$main_path] = $langName; // set the lang zip name for later $langZIPNames[$main_path] = $langFolderFileName; // set the lang xml name for later $langXMLNames[$main_path] = $langCodeName . '_' . $this->activeComponentsNames[$id] ; // we must check if old folder is found and remove it if (!isset($this->languageTracker[$main_path]) && JFolder::exists($main_path)) { // remove the main folder ComponentbuilderHelper::removeFolder($main_path); // do not remove it again $this->languageTracker[$main_path] = true; } // check if exist and create if not if (!JFolder::exists($main_path)) { JFolder::create($main_path); // count the folder created $this->folderCount++; } foreach ($areas as $area => $languageStrings) { // get the file name $fileName = $this->getLanguageFileName($file_name, $tag, $area); // check if language should be added if ($this->shouldLanguageBeAdded($tag, $languageStrings, $mainLangLoader[$area], $fileName) && ($actions = $this->getLangActions($file_name, $tag, $area, $target_client)) !== false) { // set the language data $lang = array_map( function ($langstring, $placeholder) { return $placeholder . '="' . $langstring . '"'; }, array_values($languageStrings), array_keys($languageStrings) ); // set the line counter $this->lineCount = $this->lineCount + count( (array) $lang ); // check that the main folder exist foreach ($actions as $act) { $client_path = $main_path . $act['target_client'] . '/'; // check if exist and create if not if (!JFolder::exists($client_path)) { JFolder::create($client_path); // count the folder created $this->folderCount++; } // write the language data to a file ComponentbuilderHelper::writeFile( $client_path . $act['file_name'], implode(PHP_EOL, $lang) ); // count the file created $this->fileCount++; // build xml strings if (!isset($langXML[$main_path])) { $langXML[$main_path] = array(); $langPackages[$main_path] = array(); } if (!isset($langXML[$main_path][$act['target_client']])) { $langXML[$main_path][$act['target_client']] = array(); } // set the package targets $langPackages[$main_path][$act['target_client']] = $act['target']; $langXML[$main_path][$act['target_client']][] = $act['file_name']; } // clear memory unset($lang); } } } } } // load the lang xml if (ComponentbuilderHelper::checkArray($langXML)) { foreach ($langXML as $main_path => $target_clients) { // get the XML $xml = str_replace( array_keys($this->fileContentStatic), array_values($this->fileContentStatic), $this->getLanguageXML($target_clients, $langPackages[$main_path], $langNames[$main_path]) ); // get the XML File Name $xmlFileName = $langXMLNames[$main_path] . '.xml'; // write the language data to a file ComponentbuilderHelper::writeFile( $main_path . $xmlFileName, $xml ); // set the zip full path $zipPath = $this->tempPath . '/' . $langZIPNames[$main_path] . '.zip'; // now zip the package if (ComponentbuilderHelper::zip( $main_path, $zipPath )) { // now remove the package ComponentbuilderHelper::removeFolder($main_path); } } } } } /** * get the language xml * * @return string * */ protected function getLanguageXML(&$target_clients, &$targets, &$language) { $xml = ''; $xml .= PHP_EOL . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'Component' . $this->hhh . ' - ' . $language . ' Language Pack'; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'BUILDDATE' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'AUTHOR' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'AUTHOREMAIL' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'AUTHORWEBSITE' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'COPYRIGHT' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'LICENSE' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $this->hhh . 'ACTUALVERSION' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . '' . $language . ' Language Pack - ' . $this->hhh . 'SHORT_DESCRIPTION' . $this->hhh . ''; $xml .= PHP_EOL . $this->_t(1) . ''; foreach ($target_clients as $target_client => $files) { $xml .= PHP_EOL . $this->_t(2) . ''; foreach ($files as $file) { $xml .= PHP_EOL . $this->_t(3) . '' . $file . ''; } $xml .= PHP_EOL . $this->_t(2) . ''; } $xml .= PHP_EOL . $this->_t(1) . ''; $xml .= PHP_EOL . ''; return $xml; } /** * get the language name * * @return string * */ protected function getLanguageName(&$tag) { if (!isset($this->languageNames[$tag])) { if (($name = ComponentbuilderHelper::getVar('language', $tag, 'langtag', 'name')) !== false) { $this->languageNames[$tag] = $name; } else { $this->languageNames[$tag] = $tag; } } return $this->languageNames[$tag]; } /** * get the language actions * * @return array * */ protected function getLangActions(&$file_name, &$tag, &$area, &$target_client) { // component extention type if (strpos($file_name, 'com_') !== false) { $target_client = 'admin'; $target = 'administrator/language/'; if (strpos($area, 'site') !== false) { $target_client = 'site'; $target = 'language/'; } return array( array( 'target_client' => $target_client, 'target' => $target . $tag, 'file_name' => $this->getLanguageFileName($file_name, $tag, $area) ) ); } elseif ('admin' === $target_client) { $target = 'administrator/language/'; } else { $target = 'language/'; } // module/plugin extension type (TODO we return both for now) return array( array( 'target_client' => $target_client, 'target' => $target . $tag, 'file_name' => $this->getLanguageFileName($file_name, $tag, $area) ), array( 'target_client' => $target_client, 'target' => $target . $tag, 'file_name' => $this->getLanguageFileName($file_name, $tag, $area, '.sys') ) ); } /** * get the language file name * * @return string * */ protected function getLanguageFileName(&$file_name, &$tag, &$area, $type = '') { // component extension type if (strpos($file_name, 'com_') !== false) { if (strpos($area, 'sys') !== false) { $type = '.sys'; } } // file name return $tag . '.' . $file_name . $type . '.ini'; } /** * check if a translation should be added * * @return bool * */ protected function shouldLanguageBeAdded(&$tag, &$languageStrings, &$total, &$file_name ) { // only log messages for none $this->langTag translations if ($this->langTag !== $tag) { $langStringNr = count($languageStrings); $langStringSum = ComponentbuilderHelper::bcmath( 'mul', $langStringNr, 100 ); $percentage = ComponentbuilderHelper::bcmath( 'div', $langStringSum, $total ); $stringNAme = ($langStringNr == 1) ? '(string ' . $tag . ' translated)' : '(strings ' . $tag . ' translated)'; // force load if debug lines are added if (!$this->debugLinenr) { // check if we should install this translation if ($percentage < $this->percentageLanguageAdd) { // dont add $this->langNot[$file_name] = '' . $total . '(total ' . $this->langTag . ' strings) only ' . $langStringNr . '' . $stringNAme . ' = ' . $percentage; return false; } } // show if it was added as well $this->langSet[$file_name] = '' . $total . '(total ' . $this->langTag . ' strings) and ' . $langStringNr . '' . $stringNAme . ' = ' . $percentage; } return true; } /** * Set the line number in comments * * @param int $nr The line number * * @return void * */ protected function setLine($nr) { if ($this->debugLinenr) { return ' [Plugin-Language-Packaging ' . $nr . ']'; } return ''; } /** * Set the tab/space * * @param int $nr The number of tag/space * * @return string * */ protected function _t($nr) { // use global method for conformity return ComponentbuilderHelper::_t($nr); } }