diff --git a/_admin.php b/_admin.php index 2a1a0f9..3aa095b 100644 --- a/_admin.php +++ b/_admin.php @@ -1,16 +1,15 @@ addItem( $core->adminurl->get('translater'), dcPage::getPF('translater/icon.png'), preg_match( - '/' . preg_quote($core->adminurl->get('translater')) . '(&.*)?$/', + '/' . preg_quote($core->adminurl->get('translater')) . '(&.*)?$/', $_SERVER['REQUEST_URI'] ), $core->auth->isSuperAdmin() @@ -37,7 +36,7 @@ class translaterAdminBehaviors /** * Create instance of dcTranslater once - * + * * @param dCore $core dcCore instance * @return dctranslater dcTranslater instance */ @@ -46,12 +45,13 @@ class translaterAdminBehaviors if (!(self::$translater instanceof dcTranslater)) { self::$translater = new dcTranslater($core, false); } + return self::$translater; } /** * Add button to go to module translation - * + * * @param object $list adminModulesList instance * @param string $id Module id * @param arrray $prop Module properties @@ -59,27 +59,27 @@ class translaterAdminBehaviors */ public static function adminModulesGetActions(adminModulesList $list, string $id, array $prop): ?string { - if ($list->getList() != $prop['type'] . '-activate' + if ($list->getList() != $prop['type'] . '-activate' || !self::translater($list->core)->getSetting($prop['type'] . '_menu') || !$list->core->auth->isSuperAdmin() ) { return null; } - if (self::translater($list->core)->getSetting('hide_default') + if (self::translater($list->core)->getSetting('hide_default') && in_array($id, dctranslater::$default_distrib_modules[$prop['type']]) ) { return null; } - return - ' '; } /** * Redirect to module translation - * + * * @param adminModulesList $list adminModulesList instance * @param array $modules Selected modules ids * @param string $type List type (plugin|theme) @@ -91,7 +91,7 @@ class translaterAdminBehaviors } $list->core->adminurl->redirect( - 'translater', + 'translater', ['part' => 'module', 'type' => $type, 'module' => key($_POST['translater'])], '#module-lang' ); @@ -99,7 +99,7 @@ class translaterAdminBehaviors /** * Add dashboard favorites icon - * + * * @param dcCore $core dcCore instance * @param dcFavorites $favs dcFavorites instance */ @@ -113,4 +113,4 @@ class translaterAdminBehaviors 'permissions' => $core->auth->isSuperAdmin() ]); } -} \ No newline at end of file +} diff --git a/_config.php b/_config.php index 183850c..b3b5fdb 100644 --- a/_config.php +++ b/_config.php @@ -1,16 +1,15 @@ getDefaultSettings() as $k => $v) { - $translater->setSetting($k, (isset($_POST[$k]) ? $_POST[$k] : '')); + foreach ($translater->getDefaultSettings() as $k => $v) { + $translater->setSetting($k, ($_POST[$k] ?? '')); } dcPage::addSuccessNotice( __('Configuration successfully updated.') ); $core->adminurl->redirect( - 'admin.plugins', + 'admin.plugins', ['module' => 'translater', 'conf' => 1, 'redir' => $list->getRedir()] ); } catch (Exception $e) { @@ -42,7 +41,7 @@ __('Write .lang.php files') . '

-

form::checkbox('parse_comment', '1', $translater->parse_comment) . __('Write comments in files') . '

' . form::field('parse_userinfo', 65, 255, $translater->parse_userinfo) . '

' . sprintf( - __('Following informations can be used: %s'), implode(', ', $translater::$allowed_user_informations)) . ' + __('Following informations can be used: %s'), + implode(', ', $translater::$allowed_user_informations) +) . '

' . __('Import/Export') . '

-

' . form::field('export_filename', 65, 255, $translater->export_filename) . '

@@ -73,25 +74,27 @@ form::field('export_filename', 65, 255, $translater->export_filename) . '

-

+

' . form::combo('backup_folder', $translater::$allowed_backup_folders, $translater->backup_folder) . '

' . __('Behaviors') . '

-

' . -form::combo('start_page',[ - __('Plugins') => 'plugin', - __('Themes') => 'theme', +

' . +form::combo('start_page', [ + __('Plugins') => 'plugin', + __('Themes') => 'theme', __('Home') => '-' ], $translater->start_page) . '

-

-

'; -dcPage::helpBlock('translater.config'); \ No newline at end of file +dcPage::helpBlock('translater.config'); diff --git a/_define.php b/_define.php index cad13f6..8fc3536 100644 --- a/_define.php +++ b/_define.php @@ -1,16 +1,15 @@ registerModule( 'Jean-Christian Denis & contributors', '2021.09.28', [ - 'requires' => [['core', '2.19']], + 'requires' => [['core', '2.19']], 'permissions' => null, - 'type' => 'plugin', - 'support' => 'http://forum.dotclear.org/viewtopic.php?id=39220', - 'details' => 'https://plugins.dotaddict.org/dc2/details/translater', - 'repository' => 'https://raw.githubusercontent.com/JcDenis/translater/master/dcstore.xml' + 'type' => 'plugin', + 'support' => 'http://forum.dotclear.org/viewtopic.php?id=39220', + 'details' => 'https://plugins.dotaddict.org/dc2/details/translater', + 'repository' => 'https://raw.githubusercontent.com/JcDenis/translater/master/dcstore.xml' ] -); \ No newline at end of file +); diff --git a/_install.php b/_install.php index 22b8111..fe81932 100644 --- a/_install.php +++ b/_install.php @@ -1,27 +1,27 @@ getVersion($id), $core->plugins->moduleInfo($id, 'version'), '>=')) { return null; } $translater = new dcTranslater($core, false); - $settings = $translater->getDefaultSettings(); - foreach($settings as $key => $setting) { + $settings = $translater->getDefaultSettings(); + foreach ($settings as $key => $setting) { $translater->setSetting($key, $setting['value'], false); } $core->setVersion($id, $core->plugins->moduleInfo($id, 'version')); @@ -30,4 +30,5 @@ try { } catch (Exception $e) { $core->error->add($e->getMessage()); } -return false; \ No newline at end of file + +return false; diff --git a/_prepend.php b/_prepend.php index d900ae7..5b96c01 100644 --- a/_prepend.php +++ b/_prepend.php @@ -1,16 +1,15 @@ adminurl)) { $core->adminurl->register('translater', 'plugin.php', ['p' => 'translater']); -} \ No newline at end of file +} diff --git a/_uninstall.php b/_uninstall.php index 1f098a2..7a354cc 100644 --- a/_uninstall.php +++ b/_uninstall.php @@ -1,58 +1,81 @@ addUserAction( - /* type */ 'settings', - /* action */ 'delete_all', - /* ns */ 'translater', - /* description */ __('delete all settings') + /* type */ + 'settings', + /* action */ + 'delete_all', + /* ns */ + 'translater', + /* description */ + __('delete all settings') ); $this->addUserAction( - /* type */ 'plugins', - /* action */ 'delete', - /* ns */ 'translater', - /* description */ __('delete plugin files') + /* type */ + 'plugins', + /* action */ + 'delete', + /* ns */ + 'translater', + /* description */ + __('delete plugin files') ); $this->addUserAction( - /* type */ 'versions', - /* action */ 'delete', - /* ns */ 'translater', - /* description */ __('delete the version number') + /* type */ + 'versions', + /* action */ + 'delete', + /* ns */ + 'translater', + /* description */ + __('delete the version number') ); $this->addDirectAction( - /* type */ 'settings', - /* action */ 'delete_all', - /* ns */ 'translater', - /* description */ sprintf(__('delete all %s settings'), 'translater') + /* type */ + 'settings', + /* action */ + 'delete_all', + /* ns */ + 'translater', + /* description */ + sprintf(__('delete all %s settings'), 'translater') ); $this->addDirectAction( - /* type */ 'plugins', - /* action */ 'delete', - /* ns */ 'translater', - /* description */ sprintf(__('delete %s plugin files'), 'translater') + /* type */ + 'plugins', + /* action */ + 'delete', + /* ns */ + 'translater', + /* description */ + sprintf(__('delete %s plugin files'), 'translater') ); $this->addDirectAction( - /* type */ 'versions', - /* action */ 'delete', - /* ns */ 'translater', - /* description */ sprintf(__('delete %s version number'), 'translater') -); \ No newline at end of file + /* type */ + 'versions', + /* action */ + 'delete', + /* ns */ + 'translater', + /* description */ + sprintf(__('delete %s version number'), 'translater') +); diff --git a/inc/class.dc.translater.lang.php b/inc/class.dc.translater.lang.php index 0b52ac6..17a238a 100644 --- a/inc/class.dc.translater.lang.php +++ b/inc/class.dc.translater.lang.php @@ -1,16 +1,15 @@ core = $module->core; + $this->core = $module->core; $this->translater = $module->translater; - $this->module = $module; + $this->module = $module; - $this->prop['code'] = $lang; - $this->prop['name'] = l10n::getLanguageName($lang); + $this->prop['code'] = $lang; + $this->prop['name'] = l10n::getLanguageName($lang); $this->prop['plural'] = explode(':', l10n::getLanguagePluralExpression($lang)); } /** * Get a lang property - * + * * @param string $key The lang property key * @return mixed The lang property value or null */ public function get(string $key) - { + { return array_key_exists($key, $this->prop) ? $this->prop[$key] : null; } @@ -55,7 +54,7 @@ class dcTranslaterLang /** * Get a lang messages - * + * * @return array The messages ids and translations */ public function getMessages(): array @@ -64,18 +63,18 @@ class dcTranslaterLang $m_msgids = $this->getMsgIds(); $m_msgstrs = $this->getMsgStrs(); - foreach($this->translater->getModules() as $module) { + foreach ($this->translater->getModules() as $module) { if ($module->id != $this->module->id) { $m_o_msgstrs[$module->id] = $this->translater->getlang($module, $this->code)->getMsgStrs(); } } - $dc_module = new dcTranslaterModule($this->translater, ['id' => 'dotclear', 'root' => DC_ROOT]); - $dc_lang = new dctranslaterLang($dc_module, $this->code); + $dc_module = new dcTranslaterModule($this->translater, ['id' => 'dotclear', 'root' => DC_ROOT]); + $dc_lang = new dctranslaterLang($dc_module, $this->code); $m_o_msgstrs['dotclear'] = $dc_lang->getMsgStrs(); # From id list - foreach($m_msgids as $rs) { - $res[$rs['msgid']]['files'][] = [trim($rs['file'],'/'), $rs['line']]; + foreach ($m_msgids as $rs) { + $res[$rs['msgid']]['files'][] = [trim($rs['file'], '/'), $rs['line']]; $res[$rs['msgid']]['group'] = 'main'; $res[$rs['msgid']]['plural'] = $rs['msgid_plural']; $res[$rs['msgid']]['msgstr'] = ['']; @@ -84,8 +83,7 @@ class dcTranslaterLang } # From str list - foreach($m_msgstrs as $rs) { - + foreach ($m_msgstrs as $rs) { if (!isset($res[$rs['msgid']])) { $res[$rs['msgid']]['files'][] = []; $res[$rs['msgid']]['in_dc'] = false; @@ -98,8 +96,8 @@ class dcTranslaterLang } # From others str list - foreach($m_o_msgstrs as $o_module => $o_msgstrs) { - foreach($o_msgstrs as $rs) { + foreach ($m_o_msgstrs as $o_module => $o_msgstrs) { + foreach ($o_msgstrs as $rs) { if (!isset($res[$rs['msgid']])) { continue; } @@ -114,24 +112,25 @@ class dcTranslaterLang } } } + return $res; } /** * Get messages ids - * + * * @return array The messages ids */ public function getMsgIds(): array { - $res = []; + $res = []; $scan_ext = ['php']; if ($this->translater->scan_tpl) { $scan_ext[] = 'html'; } $files = dcTranslater::scandir($this->module->root); - foreach($files as $file) { + foreach ($files as $file) { $extension = files::getExtension($file); if (is_dir($this->module->root . '/' . $file) || !in_array($extension, $scan_ext)) { continue; @@ -145,7 +144,7 @@ class dcTranslaterLang } elseif ($extension == 'html') { $msgs = dcTranslater::extractTplMsgs($contents); } - foreach($msgs as $msg) { + foreach ($msgs as $msg) { $res[] = [ 'msgid' => dcTranslater::encodeMsg($msg[0][0]), 'msgid_plural' => empty($msg[0][1]) ? '' : dcTranslater::encodeMsg($msg[0][1]), @@ -156,12 +155,13 @@ class dcTranslaterLang unset($contents); } + return $res; } /** * Get messages translations - * + * * @return array The messages translations */ public function getMsgStrs(): array @@ -173,12 +173,12 @@ class dcTranslaterLang return $res; } - foreach($langs[$this->code] as $file) { + foreach ($langs[$this->code] as $file) { if (in_array($file, $scanned)) { continue; } $scanned[] = $file; - $path = path::clean($this->module->locales . '/' . $file); + $path = path::clean($this->module->locales . '/' . $file); if (dcTranslater::isPoFile($file)) { $po = l10n::parsePoFile($path); @@ -186,22 +186,22 @@ class dcTranslaterLang continue; } $entries = $po[1]; - foreach($entries as $entry) { + foreach ($entries as $entry) { $res[] = [ - 'msgid' => $entry['msgid'], + 'msgid' => $entry['msgid'], 'msgid_plural' => $entry['msgid_plural'] ?? '', - 'msgstr' => is_array($entry['msgstr']) ? $entry['msgstr'] : [$entry['msgstr']], - 'lang' => $this->code, - 'type' => 'po', - 'path' => $path, - 'file' => basename($file), - 'group'=> str_replace('.po', '', basename($file)) + 'msgstr' => is_array($entry['msgstr']) ? $entry['msgstr'] : [$entry['msgstr']], + 'lang' => $this->code, + 'type' => 'po', + 'path' => $path, + 'file' => basename($file), + 'group' => str_replace('.po', '', basename($file)) ]; $exists[] = $entry['msgid']; } - } } + return $res; } -} \ No newline at end of file +} diff --git a/inc/class.dc.translater.module.php b/inc/class.dc.translater.module.php index f76bf25..c597e30 100644 --- a/inc/class.dc.translater.module.php +++ b/inc/class.dc.translater.module.php @@ -1,16 +1,15 @@ translater = $translater; $this->prop = $module; - $this->prop['root'] = path::real($this->prop['root']); - $i = path::info($this->prop['root']); + $this->prop['root'] = path::real($this->prop['root']); + $i = path::info($this->prop['root']); $this->prop['basename'] = $i['basename']; - $this->prop['locales'] = $this->prop['root'] . '/locales'; + $this->prop['locales'] = $this->prop['root'] . '/locales'; } /** * Get a module property - * + * * @param string $key The module property key * @return mixed The module property value or null */ public function get(string $key) - { + { return array_key_exists($key, $this->prop) ? $this->prop[$key] : null; } @@ -69,18 +68,19 @@ class dcTranslaterModule //@{ /** * Find backup folder of a module - * + * * @param boolean $throw Silently failed * @return mixed The backup folder directory or false */ public function getBackupRoot(bool $throw = false) { $dir = false; - switch($this->translater->backup_folder) { + switch ($this->translater->backup_folder) { case 'module': if ($this->prop['root_writable']) { $dir = $this->prop['locales']; } + break; case 'plugin': @@ -88,6 +88,7 @@ class dcTranslaterModule if ($tmp && is_writable($tmp)) { $dir = $tmp; } + break; case 'public': @@ -95,6 +96,7 @@ class dcTranslaterModule if ($tmp && is_writable($tmp)) { $dir = $tmp; } + break; case 'cache': @@ -103,6 +105,7 @@ class dcTranslaterModule @mkDir($tmp . '/l10n'); $dir = $tmp . '/l10n'; } + break; case 'translater': @@ -111,11 +114,13 @@ class dcTranslaterModule @mkDir($tmp . '/locales'); $dir = $tmp . '/locales'; } + break; } if (!$dir && $throw) { throw new Exception(sprintf( - __('Failed to find backups folder for module %s'), $id + __('Failed to find backups folder for module %s'), + $id )); } @@ -124,7 +129,7 @@ class dcTranslaterModule /** * Get a list of available backups - * + * * @param boolean $return_filename Return only filenames * @return array The module backups info */ @@ -135,13 +140,13 @@ class dcTranslaterModule return []; } - $res = []; + $res = []; $files = dcTranslater::scandir($backup); - foreach($files AS $file) { + foreach ($files as $file) { $is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m); - if (is_dir($backup . '/' . $file) - || !$is_backup + if (is_dir($backup . '/' . $file) + || !$is_backup || !l10n::isCode($m[1]) ) { continue; @@ -158,12 +163,13 @@ class dcTranslaterModule $res[$m[1]][$file]['module'] = $this->prop['id']; } } + return $res; } /** * Create a backup - * + * * @param string $lang The backup lang * @return boolean True on success */ @@ -173,18 +179,18 @@ class dcTranslaterModule if (!is_dir($this->prop['locales'] . '/' . $lang)) { throw new Exception(sprintf( - __('Failed to find language %s'), $lang + __('Failed to find language %s'), + $lang )); } - $res = []; + $res = []; $files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang); - foreach($files as $file) { - if (!is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) + foreach ($files as $file) { + if (!is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) && (dcTranslater::isLangphpFile($file) || dcTranslater::isPoFile($file)) ) { - $res[$this->prop['locales'] . '/' . $lang . '/' .$file] = - $this->prop['id'] . '/locales/' . $lang . '/' . $file; + $res[$this->prop['locales'] . '/' . $lang . '/' . $file] = $this->prop['id'] . '/locales/' . $lang . '/' . $file; } } @@ -192,9 +198,9 @@ class dcTranslaterModule dcTranslater::isBackupLimit($backup, $this->translater->backup_limit, true); @set_time_limit(300); - $fp = fopen($backup . '/l10n-' . $this->prop['id'] . '-' . $lang . '-' . time() . '.bck.zip', 'wb'); + $fp = fopen($backup . '/l10n-' . $this->prop['id'] . '-' . $lang . '-' . time() . '.bck.zip', 'wb'); $zip = new fileZip($fp); - foreach($res AS $from => $to) { + foreach ($res as $from => $to) { $zip->addFile($from, $to); } $zip->write(); @@ -207,7 +213,7 @@ class dcTranslaterModule /** * Retore a backup - * + * * @param string $file The backup filename * @return boolean True on success */ @@ -217,14 +223,15 @@ class dcTranslaterModule if (!file_exists($backup . '/' . $file)) { throw new Exception(sprintf( - __('Failed to find file %s'), $file + __('Failed to find file %s'), + $file )); } - $zip = new fileUnzip($backup . '/' . $file); + $zip = new fileUnzip($backup . '/' . $file); $zip_files = $zip->getFilesList(); - foreach($zip_files AS $zip_file) { + foreach ($zip_files as $zip_file) { $f = $this->parseZipFilename($zip_file, true); $zip->unzip($zip_file, $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']); $done = true; @@ -237,7 +244,7 @@ class dcTranslaterModule /** * Delete a module backup - * + * * @param string $file The backup filename * @return boolean True on success */ @@ -247,8 +254,8 @@ class dcTranslaterModule $is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m); - if (!file_exists($backup . '/' . $file) - || !$is_backup + if (!file_exists($backup . '/' . $file) + || !$is_backup || !l10n::isCode($m[1]) ) { return false; @@ -256,7 +263,8 @@ class dcTranslaterModule if (!files::isDeletable($backup . '/' . $file)) { throw new Exception(sprintf( - __('Failed to delete file %s'), $file + __('Failed to delete file %s'), + $file )); } @@ -267,7 +275,7 @@ class dcTranslaterModule /** * Import a language pack - * + * * @param array $zip_file The uploaded file info * @return boolean True on success */ @@ -275,27 +283,28 @@ class dcTranslaterModule { files::uploadStatus($zip_file); - $imported = false; + $imported = false; $not_overwrited = []; - $res = []; + $res = []; # Load Unzip object - $zip = new fileUnzip($zip_file['tmp_name']); + $zip = new fileUnzip($zip_file['tmp_name']); $files = $zip->getFilesList(); - foreach($files as $file) { + foreach ($files as $file) { $f = $this->parseZipFilename($file, true); - if (!$this->translater->import_overwrite + if (!$this->translater->import_overwrite && file_exists($this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']) ) { $not_overwrited[] = implode('-', [$f['lang'], $f['group'], $f['ext']]); + continue; } $res[] = [ - 'from' => $file, - 'root' => $this->prop['locales'] . '/' . $f['lang'], + 'from' => $file, + 'root' => $this->prop['locales'] . '/' . $f['lang'], 'to' => $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext'] ]; } @@ -313,19 +322,22 @@ class dcTranslaterModule if (!empty($not_overwrited)) { throw new Exception(sprintf( - __('Some languages has not been overwrited %s'), implode(', ', $not_overwrited) + __('Some languages has not been overwrited %s'), + implode(', ', $not_overwrited) )); } elseif (!$done) { throw new Exception(sprintf( - __('Nothing to import from %s'), $zip_file['name'] + __('Nothing to import from %s'), + $zip_file['name'] )); } + return true; } /** * Export (to output) language pack - * + * * @param array $langs Langs to export */ public function exportPack(array $langs) @@ -344,23 +356,21 @@ class dcTranslaterModule } $res = []; - foreach($langs AS $lang) { + foreach ($langs as $lang) { if (!is_dir($this->prop['locales'] . '/' . $lang)) { continue; } $files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang); - foreach($files as $file) { - - if (is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) - || !dcTranslater::isLangphpFile($file) + foreach ($files as $file) { + if (is_dir($this->prop['locales'] . '/' . $lang . '/' . $file) + || !dcTranslater::isLangphpFile($file) && !dcTranslater::isPoFile($file) ) { continue; } - $res[$this->prop['locales'] . '/' . $lang . '/' . $file] = - $this->prop['id'] . '/locales/' . $lang . '/' . $file; + $res[$this->prop['locales'] . '/' . $lang . '/' . $file] = $this->prop['id'] . '/locales/' . $lang . '/' . $file; } } @@ -371,9 +381,9 @@ class dcTranslaterModule } @set_time_limit(300); - $fp = fopen('php://output', 'wb'); + $fp = fopen('php://output', 'wb'); $zip = new fileZip($fp); - foreach($res as $from => $to) { + foreach ($res as $from => $to) { $zip->addFile($from, $to); } @@ -392,7 +402,7 @@ class dcTranslaterModule /** * Parse zip filename to module, lang info - * + * * @param string $file The zip filename * @param boolean $throw Silently failed * @return mixed Array of file info @@ -403,19 +413,22 @@ class dcTranslaterModule if ($is_file) { $module = $f[1] == $this->prop['id'] ?$f[1] : false; - $lang = l10n::isCode($f[2]) ? $f[2] : false; - $group = in_array($f[3], dctranslater::$allowed_l10n_groups) ? $f[3] : false; - $ext = dctranslater::isLangphpFile($f[4]) || dctranslater::isPoFile($f[4]) ? $f[4] : false; + $lang = l10n::isCode($f[2]) ? $f[2] : false; + $group = in_array($f[3], dctranslater::$allowed_l10n_groups) ? $f[3] : false; + $ext = dctranslater::isLangphpFile($f[4]) || dctranslater::isPoFile($f[4]) ? $f[4] : false; } if (!$is_file || !$module || !$lang || !$group || !$ext) { if ($throw) { throw new Exception(sprintf( - __('Zip file %s is not in translater format'), $file + __('Zip file %s is not in translater format'), + $file )); } + return []; } + return [ 'module' => $module, 'lang' => $lang, @@ -429,7 +442,7 @@ class dcTranslaterModule //@{ /** * List available langs of a module - * + * * @param boolean $return_path Return path or name * @return array The lang list */ @@ -440,7 +453,7 @@ class dcTranslaterModule $prefix = preg_match('/(locales(.*))$/', $this->prop['locales']) ? 'locales' : ''; $files = dcTranslater::scandir($this->prop['locales']); - foreach($files as $file) { + foreach ($files as $file) { if (!preg_match('/.*?locales\/([^\/]*?)\/([^\/]*?)(.lang.php|.po)$/', $prefix . $file, $m)) { continue; } @@ -455,12 +468,13 @@ class dcTranslaterModule $res[$m[1]] = l10n::getLanguageName($m[1]); // Lang name } } + return $res; } /** * List of used langs of a module - * + * * @return array The list of iso names and codes */ public function getUsedLangs() @@ -470,7 +484,7 @@ class dcTranslaterModule /** * List of unsused langs of a module - * + * * @return array The list of iso names and codes */ public function getUnusedLangs() @@ -480,7 +494,7 @@ class dcTranslaterModule /** * Add a lang to a module - * + * * @param string $lang The lang id * @param string $from_lang The lang to copy from * @return boolean True on success @@ -489,14 +503,16 @@ class dcTranslaterModule { if (!l10n::isCode($lang)) { throw new Exception(sprintf( - __('Unknow language %s'), $lang + __('Unknow language %s'), + $lang )); } $langs = $this->getLangs(); if (isset($langs[$lang])) { throw new Exception(sprintf( - __('Language %s already exists'), $lang + __('Language %s already exists'), + $lang )); } @@ -504,21 +520,23 @@ class dcTranslaterModule if (!empty($from_lang) && !isset($langs[$from_lang])) { throw new Exception(sprintf( - __('Failed to copy file from language %s'), $from_lang + __('Failed to copy file from language %s'), + $from_lang )); } if (!empty($from_lang) && isset($langs[$from_lang])) { $files = dcTranslater::scandir($this->prop['locales'] . '/' . $from_lang); - foreach($files as $file) { - if (is_dir($this->prop['locales'] . '/' . $from_lang . '/' . $file) - || !dcTranslater::isLangphpFile($file) + foreach ($files as $file) { + if (is_dir($this->prop['locales'] . '/' . $from_lang . '/' . $file) + || !dcTranslater::isLangphpFile($file) && !dcTranslater::isPoFile($file) ) { continue; } - files::putContent($this->prop['locales'] . '/' . $lang . '/' . $file, + files::putContent( + $this->prop['locales'] . '/' . $lang . '/' . $file, file_get_contents($this->prop['locales'] . '/' . $from_lang . '/' . $file) ); } @@ -530,7 +548,7 @@ class dcTranslaterModule /** * Update an existing lang - * + * * @param string $lang The lang * @param array $msgs The messages */ @@ -538,14 +556,16 @@ class dcTranslaterModule { if (!l10n::isCode($lang)) { throw new Exception(sprintf( - __('Unknow language %s'), $lang + __('Unknow language %s'), + $lang )); } $langs = $this->getLangs(); if (!isset($langs[$lang])) { throw new Exception(sprintf( - __('Failed to find language %s'), $lang + __('Failed to find language %s'), + $lang )); } @@ -554,19 +574,19 @@ class dcTranslaterModule } $rs = []; - foreach($msgs as $msg) { + foreach ($msgs as $msg) { if (empty($msg['msgstr'][0])) { continue; } $rs[$msg['group']][] = $msg; } - foreach(dcTranslater::$allowed_l10n_groups as $group) { + foreach (dcTranslater::$allowed_l10n_groups as $group) { if (isset($rs[$group])) { continue; } - $po_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.po'; + $po_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.po'; $langphp_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.lang.php'; if (file_exists($po_file)) { @@ -579,11 +599,12 @@ class dcTranslaterModule if (empty($rs)) { throw new Exception(sprintf( - __('No string to write, language %s deleted'), $lang + __('No string to write, language %s deleted'), + $lang )); } - foreach($rs as $group => $msgs) { + foreach ($rs as $group => $msgs) { $this->setPoContent($lang, $group, $msgs); $this->setLangphpContent($lang, $group, $msgs); } @@ -591,7 +612,7 @@ class dcTranslaterModule /** * Delete a lang - * + * * @param string $lang The lang code * @param boolean $del_empty_dir Also remove empty locales dir * @return boolean True on success @@ -601,18 +622,20 @@ class dcTranslaterModule # Path is right formed if (!l10n::isCode($lang)) { throw new Exception(sprintf( - __('Unknow language %s'), $lang + __('Unknow language %s'), + $lang )); } $files = $this->getLangs(true); if (!isset($files[$lang])) { throw new Exception(sprintf( - __('Failed to find language %s'), $lang + __('Failed to find language %s'), + $lang )); } - foreach($files[$lang] as $file) { + foreach ($files[$lang] as $file) { unlink($this->prop['locales'] . '/' . $file); } @@ -625,12 +648,13 @@ class dcTranslaterModule if (empty($loc)) { rmdir($this->prop['locales']); } + return true; } /** * Construct and parse a .po file - * + * * @param string $lang The lang code * @param string $group The lang group * @param array $msgs The strings @@ -641,14 +665,13 @@ class dcTranslaterModule $content = ''; if ($this->translater->parse_comment) { - $content .= - '# Language: ' . $lang->name . "\n" . - '# Module: ' . $this->id . " - " . $this->version . "\n" . + $content .= '# Language: ' . $lang->name . "\n" . + '# Module: ' . $this->id . ' - ' . $this->version . "\n" . '# Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n"; if ($this->translater->parse_user && $this->translater->parse_userinfo != '') { $search = dctranslater::$allowed_user_informations; - foreach($search AS $n) { + foreach ($search as $n) { $replace[] = $this->core->auth->getInfo('user_' . $n); } $info = trim(str_replace($search, $replace, $this->translater->parse_userinfo)); @@ -656,11 +679,9 @@ class dcTranslaterModule $content .= '# Author: ' . html::escapeHTML($info) . "\n"; } } - $content .= - '# Translated with translater ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n\n"; + $content .= '# Translated with translater ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n\n"; } - $content .= - "msgid \"\"\n" . + $content .= "msgid \"\"\n" . "msgstr \"\"\n" . '"Content-Type: text/plain; charset=UTF-8\n"' . "\n" . '"Project-Id-Version: ' . $this->id . ' ' . $this->version . '\n"' . "\n" . @@ -675,14 +696,13 @@ class dcTranslaterModule $comments = []; if ($this->translater->parse_comment) { $msgids = $lang->getMsgids(); - foreach($msgids as $msg) { - $comments[$msg['msgid']] = (isset($comments[$msg['msgid']]) ? - $comments[$msg['msgid']] : '') . - '#: '.trim($msg['file'],'/') . ':' . $msg['line'] . "\n"; + foreach ($msgids as $msg) { + $comments[$msg['msgid']] = ($comments[$msg['msgid']] ?? '') . + '#: ' . trim($msg['file'], '/') . ':' . $msg['line'] . "\n"; } } - foreach($msgs as $msg) { + foreach ($msgs as $msg) { if (empty($msg['msgstr'][0])) { continue; } @@ -694,7 +714,7 @@ class dcTranslaterModule $content .= 'msgstr "' . dcTranslater::poString($msg['msgstr'][0], true) . '"' . "\n"; } else { $content .= 'msgid_plural "' . dcTranslater::poString($msg['msgid_plural'], true) . '"' . "\n"; - foreach($msg['msgstr'] as $i => $plural) { + foreach ($msg['msgstr'] as $i => $plural) { $content .= 'msgstr[' . $i . '] "' . dcTranslater::poString(($msg['msgstr'][$i] ?: ''), true) . '"' . "\n"; } } @@ -703,23 +723,25 @@ class dcTranslaterModule $file = $this->locales . '/' . $lang->code . '/' . $group . '.po'; $path = path::info($file); - if (is_dir($path['dirname']) && !is_writable($path['dirname']) + if (is_dir($path['dirname']) && !is_writable($path['dirname']) || file_exists($file) && !is_writable($file)) { throw new Exception(sprintf( - __('Failed to grant write acces on file %s'), $file + __('Failed to grant write acces on file %s'), + $file )); } if (!($f = @files::putContent($file, $content))) { throw new Exception(sprintf( - __('Failed to write file %s'), $file + __('Failed to write file %s'), + $file )); } } /** * Construct and write a .lang.php file - * + * * @param string $lang The lang code * @param string $group The lang group * @param array $msgs The strings @@ -734,26 +756,24 @@ class dcTranslaterModule $content = ''; if ($this->translater->parse_comment) { - $content .= - '// Language: ' . $lang->name . "\n" . - '// Module: ' . $this->id . " - " . $this->verison . "\n" . + $content .= '// Language: ' . $lang->name . "\n" . + '// Module: ' . $this->id . ' - ' . $this->verison . "\n" . '// Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n"; if ($this->translater->parse_user && !empty($this->translater->parse_userinfo)) { $search = dcTranslater::$allowed_user_informations; - foreach($search as $n) { + foreach ($search as $n) { $replace[] = $this->core->auth->getInfo('user_' . $n); - } - $info = trim(str_replace($search, $replace,$this->translater->parse_userinfo)); + } + $info = trim(str_replace($search, $replace, $this->translater->parse_userinfo)); if (!empty($info)) { $content .= '// Author: ' . html::escapeHTML($info) . "\n"; } } - $content .= - '// Translated with dcTranslater - ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n\n"; + $content .= '// Translated with dcTranslater - ' . $this->core->plugins->moduleInfo('translater', 'version') . "\n\n"; } l10n::generatePhpFileFromPo($this->locales . '/' . $lang->code . '/' . $group, $content); } //@} -} \ No newline at end of file +} diff --git a/inc/class.dc.translater.php b/inc/class.dc.translater.php index 40280e7..f4f2fc8 100644 --- a/inc/class.dc.translater.php +++ b/inc/class.dc.translater.php @@ -1,16 +1,15 @@ [ - 'id' => 'translater_plugin_menu', + 'id' => 'translater_plugin_menu', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Put an link in plugins page' ], 'theme_menu' => [ - 'id' => 'translater_theme_menu', + 'id' => 'translater_theme_menu', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Put a link in themes page' ], 'backup_auto' => [ - 'id' => 'translater_backup_auto', + 'id' => 'translater_backup_auto', 'value' => 1, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Make a backup of languages old files when there are modified' ], 'backup_limit' => [ - 'id' => 'translater_backup_limit', + 'id' => 'translater_backup_limit', 'value' => 20, - 'type' => 'string', + 'type' => 'string', 'label' => 'Maximum backups per module' ], 'backup_folder' => [ - 'id' => 'translater_backup_folder', + 'id' => 'translater_backup_folder', 'value' => 'module', - 'type' => 'string', + 'type' => 'string', 'label' => 'In which folder to store backups' ], 'start_page' => [ - 'id' => 'translater_start_page', + 'id' => 'translater_start_page', 'value' => '-', - 'type' => 'string', + 'type' => 'string', 'label' => 'Page to start on' ], 'write_langphp' => [ - 'id' => 'translater_write_langphp', + 'id' => 'translater_write_langphp', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Write .lang.php languages files' ], 'scan_tpl' => [ - 'id' => 'translater_scan_tpl', + 'id' => 'translater_scan_tpl', 'value' => 1, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Translate strings of templates files' ], 'parse_nodc' => [ - 'id' => 'translater_parse_nodc', + 'id' => 'translater_parse_nodc', 'value' => 1, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Translate only untranslated strings of Dotclear', ], 'hide_default' => [ - 'id' => 'translater_hide_default', + 'id' => 'translater_hide_default', 'value' => 1, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Hide default modules of Dotclear', ], 'parse_comment' => [ - 'id' => 'translater_parse_comment', + 'id' => 'translater_parse_comment', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Write comments and strings informations in lang files' ], 'parse_user' => [ - 'id' => 'translater_parse_user', + 'id' => 'translater_parse_user', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Write inforamtions about author in lang files' ], 'parse_userinfo' => [ - 'id' => 'translater_parse_userinfo', + 'id' => 'translater_parse_userinfo', 'value' => 'displayname, email', - 'type' => 'string', + 'type' => 'string', 'label' => 'Type of informations about user to write' ], 'import_overwrite' => [ - 'id' => 'translater_import_overwrite', + 'id' => 'translater_import_overwrite', 'value' => 0, - 'type' => 'boolean', + 'type' => 'boolean', 'label' => 'Overwrite existing languages when import packages' ], 'export_filename' => [ - 'id' => 'translater_export_filename', + 'id' => 'translater_export_filename', 'value' => 'type-module-l10n-timestamp', - 'type' => 'string', + 'type' => 'string', 'label' => 'Name of files of exported package' ], 'proposal_tool' => [ - 'id' => 'translater_proposal_tool', + 'id' => 'translater_proposal_tool', 'value' => 'google', - 'type' => 'string', + 'type' => 'string', 'label' => 'Id of default tool for proposed translation' ], 'proposal_lang' => [ - 'id' => 'translater_proposal_lang', + 'id' => 'translater_proposal_lang', 'value' => 'en', - 'type' => 'string', + 'type' => 'string', 'label' => 'Default source language for proposed translation' ] ]; @@ -150,7 +149,7 @@ class dcTranslater /** * translater instance - * + * * @param dcCore $core dcCore instance * @param boolean $core Also load modules */ @@ -171,7 +170,7 @@ class dcTranslater __('locales folder of translater') => 'translater' ]; self::$default_distrib_modules = [ - 'plugin' => explode(',', DC_DISTRIB_PLUGINS), + 'plugin' => explode(',', DC_DISTRIB_PLUGINS), 'theme' => explode(',', DC_DISTRIB_THEMES) ]; } @@ -180,7 +179,7 @@ class dcTranslater //@{ /** * Get array of default settings - * + * * @return array All default settings */ public function getDefaultSettings(): array @@ -190,13 +189,13 @@ class dcTranslater /** * Get a setting according to default settings list - * + * * @param string $id The settings short id * @return mixed The setting value if exists or null */ public function getSetting(string $id) - { - return array_key_exists($id, self::$default_settings) ? + { + return array_key_exists($id, self::$default_settings) ? $this->core->blog->settings->translater->get(self::$default_settings[$id]['id']) : ''; } @@ -210,7 +209,7 @@ class dcTranslater /** * Set a setting according to default settings list - * + * * @param string $id The setting short id * @param mixed $value The setting value * @param mixed $overwrite Overwrite settings if exists @@ -224,6 +223,7 @@ class dcTranslater $s = self::$default_settings[$id]; $this->core->blog->settings->translater->drop($s['id']); $this->core->blog->settings->translater->put($s['id'], $value, $s['type'], $s['label'], $overwrite, true); + return true; } @@ -238,7 +238,7 @@ class dcTranslater /// @name modules methods //@{ - /** + /** * Load array of modules infos by type of modules */ private function loadModules() @@ -252,34 +252,34 @@ class dcTranslater 'theme' => $themes->getModules(), 'plugin' => $this->core->plugins->getModules() ]; - foreach($list as $type => $modules) { - foreach($modules as $id => $info) { + foreach ($list as $type => $modules) { + foreach ($modules as $id => $info) { if (!$info['root_writable']) { // continue; } - $info['id'] = $id; - $info['type'] = $type; + $info['id'] = $id; + $info['type'] = $type; $this->modules[$type][$id] = new dcTranslaterModule($this, $info); } } } - /** + /** * Return array of modules infos by type of modules - * + * * @param string $type The modules type * @return array The list of modules infos */ public function getModules(string $type = ''): array { - return in_array($type, ['plugin', 'theme']) ? - $this->modules[$type] : + return in_array($type, ['plugin', 'theme']) ? + $this->modules[$type] : array_merge($this->modules['theme'], $this->modules['plugin']); } - /** + /** * Return module class of a particular module for a given type of module - * + * * @param string $type The module type * @param string $id The module id * @return dcTranslaterModule The dcTranslaterModule instance @@ -290,14 +290,16 @@ class dcTranslater throw new Exception( sprintf(__('Failed to find module %s'), $id) ); + return false; } + return $this->modules[$type][$id]; } - /** + /** * Return module class of a particular module for a given type of module - * + * * @param string $module dcTranslaterModule instance * @param string $lang The lang iso code * @return dcTranslaterLang dcTranslaterLang instance or false @@ -308,17 +310,19 @@ class dcTranslater throw new Exception( sprintf(__('Failed find language %s'), $lang) ); + return false; } + return new dcTranslaterLang($module, $lang); } //@} /// @name helper methods //@{ - /** + /** * Scan recursively a folder and return files and folders names - * + * * @param string $path The path to scan * @param string $dir Internal recursion * @param array $res Internal recursion @@ -332,24 +336,25 @@ class dcTranslater } $files = files::scandir($path); - foreach($files AS $file) { + foreach ($files as $file) { if (in_array($file, ['.', '..'])) { continue; } if (is_dir($path . '/' . $file)) { $res[] = $file; - $res = self::scanDir($path . '/' . $file, $dir . '/' . $file, $res); + $res = self::scanDir($path . '/' . $file, $dir . '/' . $file, $res); } else { $res[] = empty($dir) ? $file : $dir . '/' . $file; } } + return $res; } /** * Encode a string - * + * * @param string $str The string to encode * @return string The encoded string */ @@ -360,7 +365,7 @@ class dcTranslater /** * Clean a po string - * + * * @param string $string The string to clean * @param boolean $reverse Un/escape string * @return string The cleaned string @@ -368,19 +373,20 @@ class dcTranslater public static function poString(string $string, bool $reverse = false): string { if ($reverse) { - $smap = array('"', "\n", "\t", "\r"); - $rmap = array('\\"', '\\n"' . "\n" . '"', '\\t', '\\r'); + $smap = ['"', "\n", "\t", "\r"]; + $rmap = ['\\"', '\\n"' . "\n" . '"', '\\t', '\\r']; + return trim((string) str_replace($smap, $rmap, $string)); - } else { - $smap = array('/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\"/'); - $rmap = array('', "\n", "\r", "\t", '"'); - return trim((string) preg_replace($smap, $rmap, $string)); } + $smap = ['/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\"/']; + $rmap = ['', "\n", "\r", "\t", '"']; + + return trim((string) preg_replace($smap, $rmap, $string)); } /** * Try if a file is a .po file - * + * * @param string $file The path to test * @return boolean Success */ @@ -391,7 +397,7 @@ class dcTranslater /** * Try if a file is a .lang.php file - * + * * @param string $file The path to test * @return boolean Success */ @@ -402,7 +408,7 @@ class dcTranslater /** * Check limit number of backup for a module - * + * * @param string $root The backups root * @param string $limit The backups limit * @param boolean $throw Silently failed @@ -411,9 +417,9 @@ class dcTranslater public static function isBackupLimit(string $root, int $limit = 10, bool $throw = false): bool { $count = 0; - foreach(self::scandir($root) AS $file) { + foreach (self::scandir($root) as $file) { if (!is_dir($root . '/' . $file) - && preg_match('/^(l10n-'. $id . '(.*?).bck.zip)$/', $root) + && preg_match('/^(l10n-' . $id . '(.*?).bck.zip)$/', $root) ) { $count++; } @@ -426,8 +432,10 @@ class dcTranslater sprintf(__('Limit of %s backups for module %s exceed'), $this->backup_limit, $id) ); } + return true; } + return false; } @@ -435,7 +443,7 @@ class dcTranslater * Extract messages from a php contents * * support plurals - * + * * @param string $content The contents * @param string $func The function name * @return array The messages @@ -445,12 +453,12 @@ class dcTranslater $duplicate = $final_strings = $lines = []; // split content by line to combine match/line on the end $content = str_replace("\r\n", "\n", $content); - $o = 0; - $parts = explode("\n", $content); - foreach($parts as $li => $part) { + $o = 0; + $parts = explode("\n", $content); + foreach ($parts as $li => $part) { $m = explode($func . '(', $part); - for($i = 1; $i < count($m); $i++) { - $lines[$o] = $li+1; + for ($i = 1; $i < count($m); $i++) { + $lines[$o] = $li + 1; $o++; } } @@ -460,14 +468,15 @@ class dcTranslater array_shift($parts); // walk through parts $p = 0; - foreach($parts as $part) { + foreach ($parts as $part) { // should start with quote - if (!in_array(substr($part,0,1), ['"', "'"])) { + if (!in_array(substr($part, 0, 1), ['"', "'"])) { $p++; + continue; } // put back first parenthesis - $part = '('.$part; + $part = '(' . $part; // find pairs of parenthesis preg_match_all("/\((?:[^\)\(]+|(?R))*+\)/s", $part, $subparts); // find quoted strings (single or double) @@ -475,16 +484,17 @@ class dcTranslater // strings exist if (!empty($strings[0])) { // remove quotes - $strings[0] = array_map(function($v){ return substr($v, 1, -1);}, $strings[0]); + $strings[0] = array_map(function ($v) { return substr($v, 1, -1);}, $strings[0]); // filter duplicate strings (only check first string for plurals form) if (!in_array($strings[0][0], $duplicate)) { // fill final array $final_strings[] = [$strings[0], $lines[$p]]; - $duplicate[] = $strings[0][0]; + $duplicate[] = $strings[0][0]; } } $p++; } + return $final_strings; } @@ -500,12 +510,12 @@ class dcTranslater $duplicate = $final_strings = $lines = []; // split content by line to combine match/line on the end $content = str_replace("\r\n", "\n", $content); - $o = 0; - $parts = explode("\n", $content); - foreach($parts as $li => $part) { + $o = 0; + $parts = explode("\n", $content); + foreach ($parts as $li => $part) { $m = explode('{{' . $func . ' ', $part); - for($i = 1; $i < count($m); $i++) { - $lines[$o] = $li+1; + for ($i = 1; $i < count($m); $i++) { + $lines[$o] = $li + 1; $o++; } } @@ -515,19 +525,20 @@ class dcTranslater } // walk through parts $p = 0; - foreach($parts[1] as $part) { + foreach ($parts[1] as $part) { // strings exist if (!empty($part)) { // filter duplicate strings if (!in_array($part, $duplicate)) { // fill final array $final_strings[] = [[$part], $lines[$p]]; - $duplicate[] = $part; + $duplicate[] = $part; } } $p++; } + return $final_strings; } //@} -} \ No newline at end of file +} diff --git a/inc/class.translater.proposals.php b/inc/class.translater.proposals.php index 4e38b17..43f86b2 100644 --- a/inc/class.translater.proposals.php +++ b/inc/class.translater.proposals.php @@ -1,12 +1,12 @@ stack); } -} \ No newline at end of file +} diff --git a/inc/class.translater.rest.php b/inc/class.translater.rest.php index 35f90d5..c97ad4f 100644 --- a/inc/class.translater.rest.php +++ b/inc/class.translater.rest.php @@ -1,16 +1,15 @@ proposal->getTool($tool)->translate($str_in ,$from, $to); + $str_out = (string) $translater->proposal->getTool($tool)->translate($str_in, $from, $to); } - $x = new xmlTag('proposal'); + $x = new xmlTag('proposal'); $x->lang_from = $from; - $x->lang_to = $to; - $x->tool = $tool; - $x->str_from = $str_in; - $x->str_to = text::toUTF8(html::decodeEntities($str_out)); + $x->lang_to = $to; + $x->tool = $tool; + $x->str_from = $str_in; + $x->str_to = text::toUTF8(html::decodeEntities($str_out)); $rsp->insertNode($x); } catch (Exception $e) { $core->error->add($e->getMessage()); } + return $rsp; } -} \ No newline at end of file +} diff --git a/inc/lib.translater.google.php b/inc/lib.translater.google.php index 5cd63b9..157780a 100644 --- a/inc/lib.translater.google.php +++ b/inc/lib.translater.google.php @@ -1,16 +1,15 @@

'. + '

' . '

' . __('You must have on Google API console:') . '

' . '