Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 253
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
acp_language
0.00% covered (danger)
0.00%
0 / 251
0.00% covered (danger)
0.00%
0 / 3
2450
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 main
0.00% covered (danger)
0.00%
0 / 226
0.00% covered (danger)
0.00%
0 / 1
2162
 compare_language_files
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14/**
15* @ignore
16*/
17if (!defined('IN_PHPBB'))
18{
19    exit;
20}
21
22use phpbb\config\config;
23use phpbb\db\driver\driver_interface;
24use phpbb\event\dispatcher;
25use phpbb\language\language;
26use phpbb\language\language_file_helper;
27use phpbb\log\log_interface;
28use phpbb\request\request_interface;
29use phpbb\template\template;
30use phpbb\user;
31
32class acp_language
33{
34    var $u_action;
35
36    var $language_file = '';
37    var $language_directory = '';
38
39    /** @var config Config class */
40    protected $config;
41
42    /** @var driver_interface DBAL driver */
43    protected $db;
44
45    /** @var dispatcher Event dispatcher */
46    protected $dispatcher;
47
48    /** @var language Language class */
49    protected $language;
50
51    /** @var language_file_helper Language file helper */
52    protected $language_helper;
53
54    /** @var log_interface Logging class */
55    protected $log;
56
57    /** @var request_interface */
58    protected $request;
59
60    /** @var template Template class */
61    protected $template;
62
63    /** @var user User class */
64    protected $user;
65
66    /** @var string phpBB root path */
67    protected $phpbb_root_path;
68
69    /** @var string PHP file extension */
70    protected $php_ext;
71
72    /** @var string Page title */
73    public $page_title = 'ACP_LANGUAGE_PACKS';
74
75    /** @var string Template name */
76    public $tpl_name = 'acp_language';
77
78    /**
79     * acp_language constructor
80     */
81    public function __construct()
82    {
83        global $config, $db, $user, $template, $phpbb_log, $phpbb_container;
84        global $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher;
85
86        $this->config = $config;
87        $this->db = $db;
88        $this->dispatcher = $phpbb_dispatcher;
89        $this->language = $phpbb_container->get('language');
90        $this->language_helper = $phpbb_container->get('language.helper.language_file');
91        $this->log = $phpbb_log;
92        $this->request = $request;
93        $this->template = $template;
94        $this->user = $user;
95        $this->phpbb_root_path = $phpbb_root_path;
96        $this->php_ext = $phpEx;
97    }
98
99    /**
100     * Main handler for acp_language
101     *
102     * @param string $id Module ID
103     * @param string $mode Module mode
104     */
105    public function main($id, $mode)
106    {
107        if (!function_exists('validate_language_iso_name'))
108        {
109            include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
110        }
111
112        // Check and set some common vars
113        $action        = $this->request->is_set_post('update_details') ? 'update_details' : '';
114        $action        = $this->request->is_set_post('remove_store') ? 'details' : $action;
115
116        $submit = (empty($action) && !$this->request->is_set_post('update') && !$this->request->is_set_post('test_connection')) ? false : true;
117        $action = (empty($action)) ? $this->request->variable('action', '') : $action;
118
119        $form_name = 'acp_lang';
120        add_form_key('acp_lang');
121
122        $lang_id = $this->request->variable('id', 0);
123
124        $selected_lang_file = $this->request->variable('language_file', '|common.' . $this->php_ext);
125
126        list($this->language_directory, $this->language_file) = explode('|', $selected_lang_file);
127
128        $this->language_directory = basename($this->language_directory);
129        $this->language_file = basename($this->language_file);
130
131        $this->language->add_lang('acp/language');
132
133        switch ($action)
134        {
135            case 'update_details':
136
137                if (!$submit || !check_form_key($form_name))
138                {
139                    trigger_error($this->language->lang('FORM_INVALID'). adm_back_link($this->u_action), E_USER_WARNING);
140                }
141
142                if (!$lang_id)
143                {
144                    trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING);
145                }
146
147                $sql = 'SELECT *
148                    FROM ' . LANG_TABLE . "
149                    WHERE lang_id = $lang_id";
150                $result = $this->db->sql_query($sql);
151                $row = $this->db->sql_fetchrow($result);
152                $this->db->sql_freeresult($result);
153
154                $sql_ary    = array(
155                    'lang_english_name'        => $this->request->variable('lang_english_name', $row['lang_english_name']),
156                    'lang_local_name'        => $this->request->variable('lang_local_name', $row['lang_local_name'], true),
157                    'lang_author'            => $this->request->variable('lang_author', $row['lang_author'], true),
158                );
159
160                $this->db->sql_query('UPDATE ' . LANG_TABLE . '
161                    SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
162                    WHERE lang_id = ' . $lang_id);
163
164                $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_UPDATED', false, array($sql_ary['lang_english_name']));
165
166                trigger_error($this->language->lang('LANGUAGE_DETAILS_UPDATED') . adm_back_link($this->u_action));
167            break;
168
169            case 'details':
170
171                if (!$lang_id)
172                {
173                    trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING);
174                }
175
176                $this->page_title = 'LANGUAGE_PACK_DETAILS';
177
178                $sql = 'SELECT *
179                    FROM ' . LANG_TABLE . '
180                    WHERE lang_id = ' . $lang_id;
181                $result = $this->db->sql_query($sql);
182                $lang_entries = $this->db->sql_fetchrow($result);
183                $this->db->sql_freeresult($result);
184
185                if (!$lang_entries)
186                {
187                    trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING);
188                }
189
190                $lang_iso = $lang_entries['lang_iso'];
191
192                try
193                {
194                    $lang_cfg = $this->language_helper->get_language_data_from_composer_file($this->phpbb_root_path . 'language/' . $lang_iso . '/composer.json');
195                }
196                catch (\DomainException $e)
197                {
198                    trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING);
199                }
200
201                $this->language->add_lang('acp/extensions');
202
203                $this->template->assign_vars(array(
204                    'S_DETAILS'                    => true,
205                    'U_ACTION'                    => $this->u_action . "&amp;action=details&amp;id=$lang_id",
206                    'U_BACK'                    => $this->u_action,
207
208                    'LANG_LOCAL_NAME'            => $lang_entries['lang_local_name'],
209                    'LANG_ENGLISH_NAME'            => $lang_entries['lang_english_name'],
210                    'LANG_ISO'                    => $lang_iso,
211                    'LANG_VERSION'                => $lang_cfg['version'],
212                    'LANG_PHPBB_VERSION'        => $lang_cfg['phpbb_version'],
213                    'LANG_AUTHOR'                => $lang_entries['lang_author'],
214                    'L_MISSING_FILES'            => $this->language->lang('THOSE_MISSING_LANG_FILES', $lang_entries['lang_local_name']),
215                    'L_MISSING_VARS_EXPLAIN'    => $this->language->lang('THOSE_MISSING_LANG_VARIABLES', $lang_entries['lang_local_name']),
216                ));
217
218                // If current lang is different from the default lang, then highlight missing files and variables
219                if ($lang_iso != $this->config['default_lang'])
220                {
221                    try
222                    {
223                        $iterator = new \phpbb\finder\recursive_path_iterator($this->phpbb_root_path . 'language/' . $this->config['default_lang'] . '/');
224                    }
225                    catch (\Exception $e)
226                    {
227                        return;
228                    }
229
230                    foreach ($iterator as $file_info)
231                    {
232                        $relative_path = $iterator->getInnerIterator()->getSubPathname();
233                        $relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path);
234
235                        if (file_exists($this->phpbb_root_path . 'language/' . $lang_iso . '/' . $relative_path))
236                        {
237                            if (substr($relative_path, -strlen($this->php_ext)) === $this->php_ext)
238                            {
239                                $missing_vars = $this->compare_language_files($this->config['default_lang'], $lang_iso, $relative_path);
240
241                                if (!empty($missing_vars))
242                                {
243                                    $this->template->assign_block_vars('missing_varfile', array(
244                                        'FILE_NAME'            => $relative_path,
245                                    ));
246
247                                    foreach ($missing_vars as $var)
248                                    {
249                                        $this->template->assign_block_vars('missing_varfile.variable', array(
250                                                'VAR_NAME'            => $var,
251                                        ));
252                                    }
253                                }
254                            }
255                        }
256                        else
257                        {
258                            $this->template->assign_block_vars('missing_files', array(
259                                'FILE_NAME' => $relative_path,
260                            ));
261                        }
262                    }
263                }
264                return;
265            break;
266
267            case 'delete':
268
269                if (!$lang_id)
270                {
271                    trigger_error($this->language->lang('NO_LANG_ID') . adm_back_link($this->u_action), E_USER_WARNING);
272                }
273
274                $sql = 'SELECT *
275                    FROM ' . LANG_TABLE . '
276                    WHERE lang_id = ' . $lang_id;
277                $result = $this->db->sql_query($sql);
278                $row = $this->db->sql_fetchrow($result);
279                $this->db->sql_freeresult($result);
280
281                if ($row['lang_iso'] == $this->config['default_lang'])
282                {
283                    trigger_error($this->language->lang('NO_REMOVE_DEFAULT_LANG') . adm_back_link($this->u_action), E_USER_WARNING);
284                }
285
286                if (confirm_box(true))
287                {
288                    $this->db->sql_query('DELETE FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id);
289
290                    $sql = 'UPDATE ' . USERS_TABLE . "
291                        SET user_lang = '" . $this->db->sql_escape($this->config['default_lang']) . "'
292                        WHERE user_lang = '" . $this->db->sql_escape($row['lang_iso']) . "'";
293                    $this->db->sql_query($sql);
294
295                    // We also need to remove the translated entries for custom profile fields - we want clean tables, don't we?
296                    $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' WHERE lang_id = ' . $lang_id;
297                    $this->db->sql_query($sql);
298
299                    $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' WHERE lang_id = ' . $lang_id;
300                    $this->db->sql_query($sql);
301
302                    $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_DELETED', false, array($row['lang_english_name']));
303
304                    $delete_message = $this->language->lang('LANGUAGE_PACK_DELETED', $row['lang_english_name']);
305                    $lang_iso = $row['lang_iso'];
306                    /**
307                     * Run code after language deleted
308                     *
309                     * @event core.acp_language_after_delete
310                     * @var    string     lang_iso         Language ISO code
311                     * @var    string  delete_message  Delete message appear to user
312                     * @since 3.2.2-RC1
313                     */
314                    $vars = array('lang_iso', 'delete_message');
315                    extract($this->dispatcher->trigger_event('core.acp_language_after_delete', compact($vars)));
316
317                    trigger_error($delete_message . adm_back_link($this->u_action));
318                }
319                else
320                {
321                    $s_hidden_fields = array(
322                        'i'            => $id,
323                        'mode'        => $mode,
324                        'action'    => $action,
325                        'id'        => $lang_id,
326                    );
327                    confirm_box(false, $this->language->lang('DELETE_LANGUAGE_CONFIRM', $row['lang_english_name']), build_hidden_fields($s_hidden_fields));
328                }
329            break;
330
331            case 'install':
332                if (!check_link_hash($this->request->variable('hash', ''), 'acp_language'))
333                {
334                    trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING);
335                }
336
337                $lang_iso = $this->request->variable('iso', '');
338                $lang_iso = basename($lang_iso);
339
340                if (!$lang_iso || !file_exists("{$this->phpbb_root_path}language/$lang_iso/composer.json"))
341                {
342                    trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING);
343                }
344
345                try
346                {
347                    $lang_pack = $this->language_helper->get_language_data_from_composer_file("{$this->phpbb_root_path}language/$lang_iso/composer.json");
348                }
349                catch (\DomainException $e)
350                {
351                    trigger_error($this->language->lang('LANGUAGE_PACK_NOT_EXIST') . adm_back_link($this->u_action), E_USER_WARNING);
352                }
353
354                $sql = 'SELECT lang_iso
355                    FROM ' . LANG_TABLE . "
356                    WHERE lang_iso = '" . $this->db->sql_escape($lang_iso) . "'";
357                $result = $this->db->sql_query($sql);
358                $row = $this->db->sql_fetchrow($result);
359                $this->db->sql_freeresult($result);
360
361                if ($row)
362                {
363                    trigger_error($this->language->lang('LANGUAGE_PACK_ALREADY_INSTALLED') . adm_back_link($this->u_action), E_USER_WARNING);
364                }
365
366                if (!$lang_pack['name'] || !$lang_pack['local_name'])
367                {
368                    trigger_error($this->language->lang('INVALID_LANGUAGE_PACK') . adm_back_link($this->u_action), E_USER_WARNING);
369                }
370
371                // Add language pack
372                $sql_ary = array(
373                    'lang_iso'            => $lang_pack['iso'],
374                    'lang_dir'            => $lang_pack['iso'],
375                    'lang_english_name'    => $lang_pack['name'],
376                    'lang_local_name'    => $lang_pack['local_name'],
377                    'lang_author'        => $lang_pack['author']
378                );
379
380                $this->db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary));
381                $lang_id = $this->db->sql_nextid();
382
383                // Now let's copy the default language entries for custom profile fields for this new language - makes admin's life easier.
384                $sql = 'SELECT lang_id
385                    FROM ' . LANG_TABLE . "
386                    WHERE lang_iso = '" . $this->db->sql_escape($this->config['default_lang']) . "'";
387                $result = $this->db->sql_query($sql);
388                $default_lang_id = (int) $this->db->sql_fetchfield('lang_id');
389                $this->db->sql_freeresult($result);
390
391                // We want to notify the admin that custom profile fields need to be updated for the new language.
392                $notify_cpf_update = false;
393
394                // From the mysql documentation:
395                // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14.
396                // Due to this we stay on the safe side if we do the insertion "the manual way"
397
398                $sql = 'SELECT field_id, lang_name, lang_explain, lang_default_value
399                    FROM ' . PROFILE_LANG_TABLE . '
400                    WHERE lang_id = ' . $default_lang_id;
401                $result = $this->db->sql_query($sql);
402
403                while ($row = $this->db->sql_fetchrow($result))
404                {
405                    $row['lang_id'] = $lang_id;
406                    $this->db->sql_query('INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $row));
407                    $notify_cpf_update = true;
408                }
409                $this->db->sql_freeresult($result);
410
411                $sql = 'SELECT field_id, option_id, field_type, lang_value
412                    FROM ' . PROFILE_FIELDS_LANG_TABLE . '
413                    WHERE lang_id = ' . $default_lang_id;
414                $result = $this->db->sql_query($sql);
415
416                while ($row = $this->db->sql_fetchrow($result))
417                {
418                    $row['lang_id'] = $lang_id;
419                    $this->db->sql_query('INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $row));
420                    $notify_cpf_update = true;
421                }
422                $this->db->sql_freeresult($result);
423
424                $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_LANGUAGE_PACK_INSTALLED', false, array($lang_pack['name']));
425
426                $message = $this->language->lang('LANGUAGE_PACK_INSTALLED', $lang_pack['name']);
427                $message .= ($notify_cpf_update) ? '<br /><br />' . $this->language->lang('LANGUAGE_PACK_CPF_UPDATE') : '';
428                trigger_error($message . adm_back_link($this->u_action));
429
430            break;
431        }
432
433        $sql = 'SELECT user_lang, COUNT(user_lang) AS lang_count
434            FROM ' . USERS_TABLE . '
435            GROUP BY user_lang';
436        $result = $this->db->sql_query($sql);
437
438        $lang_count = array();
439        while ($row = $this->db->sql_fetchrow($result))
440        {
441            $lang_count[$row['user_lang']] = $row['lang_count'];
442        }
443        $this->db->sql_freeresult($result);
444
445        $sql = 'SELECT *
446            FROM ' . LANG_TABLE . '
447            ORDER BY lang_english_name';
448        $result = $this->db->sql_query($sql);
449
450        $installed = array();
451
452        while ($row = $this->db->sql_fetchrow($result))
453        {
454            $installed[] = $row['lang_iso'];
455            $tagstyle = ($row['lang_iso'] == $this->config['default_lang']) ? '*' : '';
456
457            $this->template->assign_block_vars('lang', array(
458                'U_DETAILS'            => $this->u_action . "&amp;action=details&amp;id={$row['lang_id']}",
459                'U_DOWNLOAD'        => $this->u_action . "&amp;action=download&amp;id={$row['lang_id']}",
460                'U_DELETE'            => $this->u_action . "&amp;action=delete&amp;id={$row['lang_id']}",
461
462                'ENGLISH_NAME'        => $row['lang_english_name'],
463                'TAG'                => $tagstyle,
464                'LOCAL_NAME'        => $row['lang_local_name'],
465                'ISO'                => $row['lang_iso'],
466                'USED_BY'            => (isset($lang_count[$row['lang_iso']])) ? $lang_count[$row['lang_iso']] : 0,
467            ));
468        }
469        $this->db->sql_freeresult($result);
470
471        $new_ary = $iso = array();
472
473        $iso = $this->language_helper->get_available_languages();
474
475        foreach ($iso as $lang_array)
476        {
477            $lang_iso = $lang_array['iso'];
478
479            if (!in_array($lang_iso, $installed))
480            {
481                $new_ary[$lang_iso] = $lang_array;
482            }
483        }
484
485        unset($installed);
486
487        if (count($new_ary))
488        {
489            foreach ($new_ary as $iso => $lang_ary)
490            {
491                $this->template->assign_block_vars('notinst', array(
492                    'ISO'            => htmlspecialchars($lang_ary['iso'], ENT_COMPAT),
493                    'LOCAL_NAME'    => htmlspecialchars($lang_ary['local_name'], ENT_COMPAT, 'UTF-8'),
494                    'NAME'            => htmlspecialchars($lang_ary['name'], ENT_COMPAT, 'UTF-8'),
495                    'U_INSTALL'        => $this->u_action . '&amp;action=install&amp;iso=' . urlencode($lang_ary['iso']) . '&amp;hash=' . generate_link_hash('acp_language'))
496                );
497            }
498        }
499
500        unset($new_ary);
501    }
502
503    /**
504    * Compare two language files
505    */
506    function compare_language_files($source_lang, $dest_lang, $file)
507    {
508        $source_file = $this->phpbb_root_path . 'language/' . $source_lang . '/' . $file;
509        $dest_file = $this->phpbb_root_path . 'language/' . $dest_lang . '/' . $file;
510
511        if (!file_exists($dest_file))
512        {
513            return array();
514        }
515
516        $lang = array();
517        include($source_file);
518        $lang_entry_src = $lang;
519
520        $lang = array();
521        include($dest_file);
522        $lang_entry_dst = $lang;
523
524        unset($lang);
525
526        return array_diff(array_keys($lang_entry_src), array_keys($lang_entry_dst));
527    }
528}