Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 536
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
acp_extensions
0.00% covered (danger)
0.00%
0 / 534
0.00% covered (danger)
0.00%
0 / 11
13806
0.00% covered (danger)
0.00%
0 / 1
 main
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 main_mode
0.00% covered (danger)
0.00%
0 / 182
0.00% covered (danger)
0.00%
0 / 1
1806
 catalog_mode
0.00% covered (danger)
0.00%
0 / 170
0.00% covered (danger)
0.00%
0 / 1
702
 display_composer_exception
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 list_enabled_exts
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
72
 list_disabled_exts
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
72
 list_available_exts
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
42
 output_actions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 sort_extension_meta_data_table
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 output_metadata_to_template
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
182
 check_is_enableable
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
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
14use phpbb\exception\exception_interface;
15use phpbb\exception\runtime_exception;
16use phpbb\exception\version_check_exception;
17
18/**
19* @ignore
20*/
21if (!defined('IN_PHPBB'))
22{
23    exit;
24}
25
26class acp_extensions
27{
28    public $u_action;
29
30    private $db;
31
32    /** @var  phpbb\config\config */
33    private $config;
34
35    /** @var \phpbb\template\twig\twig */
36    private $template;
37    private $user;
38    private $log;
39
40    /** @var \phpbb\request\request */
41    private $request;
42    private $phpbb_dispatcher;
43
44    /** @var \phpbb\extension\manager */
45    private $ext_manager;
46
47    private $phpbb_container;
48    private $php_ini;
49    private $u_catalog_action;
50
51    /** @var string */
52    private $phpbb_root_path;
53
54    function main($id, $mode)
55    {
56        // Start the page
57        global $config, $user, $template, $request, $phpbb_extension_manager, $db, $phpbb_log, $phpbb_dispatcher, $phpbb_container, $phpbb_root_path;
58
59        $this->db       = $db;
60        $this->config = $config;
61        $this->template = $template;
62        $this->user = $user;
63        $this->request = $request;
64        $this->log = $phpbb_log;
65        $this->phpbb_dispatcher = $phpbb_dispatcher;
66        $this->ext_manager = $phpbb_extension_manager;
67        $this->phpbb_container = $phpbb_container;
68        $this->php_ini = $this->phpbb_container->get('php_ini');
69        $this->phpbb_root_path = $phpbb_root_path;
70
71        $this->user->add_lang(['install', 'acp/extensions', 'acp/modules', 'migrator']);
72
73        switch ($mode)
74        {
75            case 'catalog':
76                $this->catalog_mode($id, $mode);
77            break;
78            default:
79                $this->main_mode($id, $mode);
80            break;
81        }
82    }
83
84    public function main_mode($id, $mode)
85    {
86        global $phpbb_extension_manager, $phpbb_container, $phpbb_admin_path, $phpEx;
87
88        $this->page_title = 'ACP_EXTENSIONS';
89
90        $action = $this->request->variable('action', 'list');
91        $ext_name = $this->request->variable('ext_name', '');
92
93        // What is a safe limit of execution time? Half the max execution time should be safe.
94        $safe_time_limit = ($this->php_ini->getNumeric('max_execution_time') / 2);
95        $start_time = time();
96
97        // Cancel action
98        if ($this->request->is_set_post('cancel'))
99        {
100            $action = 'list';
101            $ext_name = '';
102        }
103
104        if (in_array($action, array('enable', 'disable', 'delete_data')) && !check_link_hash($this->request->variable('hash', ''), $action . '.' . $ext_name))
105        {
106            trigger_error('FORM_INVALID', E_USER_WARNING);
107        }
108
109        /**
110        * Event to run a specific action on extension
111        *
112        * @event core.acp_extensions_run_action_before
113        * @var    string    action            Action to run; if the event completes execution of the action, should be set to 'none'
114        * @var    string    u_action        Url we are at
115        * @var    string    ext_name        Extension name from request
116        * @var    int        safe_time_limit    Safe limit of execution time
117        * @var    int        start_time        Start time
118        * @var    string    tpl_name        Template file to load
119        * @since 3.1.11-RC1
120        * @changed 3.2.1-RC1            Renamed to core.acp_extensions_run_action_before, added tpl_name, added action 'none'
121        */
122        $u_action = $this->u_action;
123        $tpl_name = '';
124        $vars = array('action', 'u_action', 'ext_name', 'safe_time_limit', 'start_time', 'tpl_name');
125        extract($this->phpbb_dispatcher->trigger_event('core.acp_extensions_run_action_before', compact($vars)));
126
127        // In case they have been updated by the event
128        $this->u_action = $u_action;
129        $this->tpl_name = $tpl_name;
130
131        // If they've specified an extension, let's load the metadata manager and validate it.
132        if ($ext_name)
133        {
134            $md_manager = $this->ext_manager->create_extension_metadata_manager($ext_name);
135
136            try
137            {
138                $md_manager->get_metadata('all');
139            }
140            catch (exception_interface $e)
141            {
142                $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
143                trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING);
144            }
145        }
146
147        $this->u_catalog_action = append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&amp;mode=catalog");
148
149        // What are we doing?
150        switch ($action)
151        {
152            case 'none':
153                // Intentionally empty, used by extensions that execute additional actions in the prior event
154                break;
155
156            case 'set_config_version_check_force_unstable':
157                $force_unstable = $this->request->variable('force_unstable', false);
158
159                if ($force_unstable)
160                {
161                    $s_hidden_fields = build_hidden_fields(array(
162                        'force_unstable'    => $force_unstable,
163                    ));
164
165                    confirm_box(false, $this->user->lang('EXTENSION_FORCE_UNSTABLE_CONFIRM'), $s_hidden_fields);
166                }
167                else
168                {
169                    $this->config->set('extension_force_unstable', false);
170                    trigger_error($this->user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action));
171                }
172            break;
173
174            case 'list':
175            default:
176                if (confirm_box(true))
177                {
178                    $this->config->set('extension_force_unstable', true);
179                    trigger_error($this->user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action));
180                }
181
182                /** @var \phpbb\composer\manager $composer_manager */
183                $composer_manager = $phpbb_container->get('ext.composer.manager');
184
185                $managed_packages = [];
186                if ($composer_manager->check_requirements())
187                {
188                    $managed_packages = $composer_manager->get_managed_packages();
189                }
190
191                $this->list_enabled_exts($phpbb_extension_manager, $managed_packages);
192                $this->list_disabled_exts($phpbb_extension_manager, $managed_packages);
193                $this->list_available_exts($managed_packages);
194
195                $this->tpl_name = 'acp_ext_list';
196
197                $this->template->assign_vars(array(
198                    'U_VERSIONCHECK_FORCE'     => $this->u_action . '&amp;action=list&amp;versioncheck_force=1',
199                    'FORCE_UNSTABLE'        => $this->config['extension_force_unstable'],
200                    'U_ACTION'                 => $this->u_action,
201                    'MANAGED_EXTENSIONS'    => $managed_packages,
202                    'U_CATALOG_ACTION'         => $this->u_catalog_action,
203                ));
204
205                $this->request->disable_super_globals();
206            break;
207
208            case 'enable_pre':
209                try
210                {
211                    $md_manager->validate_enable();
212                }
213                catch (exception_interface $e)
214                {
215                    $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
216                    trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING);
217                }
218
219                $extension = $this->ext_manager->get_extension($ext_name);
220
221                $this->check_is_enableable($extension);
222
223                if ($this->ext_manager->is_enabled($ext_name))
224                {
225                    redirect($this->u_action);
226                }
227
228                $this->tpl_name = 'acp_ext_enable';
229
230                $this->template->assign_vars([
231                    'S_PRE_STEP'        => true,
232                    'CONFIRM_MESSAGE'    => $this->user->lang('EXTENSION_ENABLE_CONFIRM', $md_manager->get_metadata('display-name')),
233                    'U_ENABLE'            => $this->u_action . '&amp;action=enable&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('enable.' . $ext_name),
234                ]);
235            break;
236
237            case 'enable':
238                try
239                {
240                    $md_manager->validate_enable();
241                }
242                catch (exception_interface $e)
243                {
244                    $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
245                    trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING);
246                }
247
248                $extension = $this->ext_manager->get_extension($ext_name);
249
250                $this->check_is_enableable($extension);
251
252                try
253                {
254                    while ($this->ext_manager->enable_step($ext_name))
255                    {
256                        // Are we approaching the time limit? If so we want to pause the update and continue after refreshing
257                        if ((time() - $start_time) >= $safe_time_limit)
258                        {
259                            $this->template->assign_var('S_NEXT_STEP', true);
260
261                            meta_refresh(0, $this->u_action . '&amp;action=enable&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('enable.' . $ext_name));
262                        }
263                    }
264
265                    // Update custom style for admin area
266                    $this->template->set_custom_style(array(
267                        array(
268                            'name'         => 'adm',
269                            'ext_path'     => 'adm/style/',
270                        ),
271                    ), array($this->phpbb_root_path . 'adm/style'));
272
273                    $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_ENABLE', time(), array($ext_name));
274                }
275                catch (\phpbb\db\migration\exception $e)
276                {
277                    $this->template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($this->user));
278                }
279                catch (\Exception $e)
280                {
281                    $this->template->assign_var('MIGRATOR_ERROR', $e->getMessage());
282                    $this->template->assign_var('MIGRATOR_ERROR_STACK_TRACE', phpbb_filter_root_path($e->getTraceAsString()));
283                }
284
285                $this->tpl_name = 'acp_ext_enable';
286
287                $this->template->assign_vars([
288                    'U_RETURN'        => $this->u_action . '&amp;action=list',
289                ]);
290            break;
291
292            case 'disable_pre':
293                if (!$this->ext_manager->is_enabled($ext_name))
294                {
295                    redirect($this->u_action);
296                }
297
298                $this->tpl_name = 'acp_ext_disable';
299
300                $this->template->assign_vars([
301                    'S_PRE_STEP'        => true,
302                    'CONFIRM_MESSAGE'    => $this->user->lang('EXTENSION_DISABLE_CONFIRM', $md_manager->get_metadata('display-name')),
303                    'U_DISABLE'            => $this->u_action . '&amp;action=disable&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('disable.' . $ext_name),
304                ]);
305            break;
306
307            case 'disable':
308                if (!$this->ext_manager->is_enabled($ext_name))
309                {
310                    redirect($this->u_action);
311                }
312
313                while ($this->ext_manager->disable_step($ext_name))
314                {
315                    // Are we approaching the time limit? If so we want to pause the update and continue after refreshing
316                    if ((time() - $start_time) >= $safe_time_limit)
317                    {
318                        $this->template->assign_var('S_NEXT_STEP', true);
319
320                        meta_refresh(0, $this->u_action . '&amp;action=disable&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('disable.' . $ext_name));
321                    }
322                }
323                $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_DISABLE', time(), array($ext_name));
324
325                $this->tpl_name = 'acp_ext_disable';
326
327                $this->template->assign_vars([
328                    'U_RETURN'    => $this->u_action . '&amp;action=list',
329                ]);
330            break;
331
332            case 'delete_data_pre':
333                if ($this->ext_manager->is_enabled($ext_name))
334                {
335                    redirect($this->u_action);
336                }
337
338                $this->tpl_name = 'acp_ext_delete_data';
339
340                $this->template->assign_vars([
341                    'S_PRE_STEP'        => true,
342                    'CONFIRM_MESSAGE'    => $this->user->lang('EXTENSION_DELETE_DATA_CONFIRM', $md_manager->get_metadata('display-name')),
343                    'U_PURGE'            => $this->u_action . '&amp;action=delete_data&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('delete_data.' . $ext_name),
344                ]);
345            break;
346
347            case 'delete_data':
348                if ($this->ext_manager->is_enabled($ext_name))
349                {
350                    redirect($this->u_action);
351                }
352
353                try
354                {
355                    while ($this->ext_manager->purge_step($ext_name))
356                    {
357                        // Are we approaching the time limit? If so we want to pause the update and continue after refreshing
358                        if ((time() - $start_time) >= $safe_time_limit)
359                        {
360                            $this->template->assign_var('S_NEXT_STEP', true);
361
362                            meta_refresh(0, $this->u_action . '&amp;action=delete_data&amp;ext_name=' . urlencode($ext_name) . '&amp;hash=' . generate_link_hash('delete_data.' . $ext_name));
363                        }
364                    }
365                    $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_PURGE', time(), array($ext_name));
366                }
367                catch (\phpbb\db\migration\exception $e)
368                {
369                    $this->template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($this->user));
370                }
371                catch (\Exception $e)
372                {
373                    $this->template->assign_var('MIGRATOR_ERROR', $e->getMessage());
374                    $this->template->assign_var('MIGRATOR_ERROR_STACK_TRACE', phpbb_filter_root_path($e->getTraceAsString()));
375                }
376
377                $this->tpl_name = 'acp_ext_delete_data';
378
379                $this->template->assign_vars([
380                    'U_RETURN'    => $this->u_action . '&amp;action=list',
381                ]);
382            break;
383
384            case 'details':
385                // Output it to the template
386                $meta = $md_manager->get_metadata('all');
387                $this->output_metadata_to_template($meta);
388
389                if (isset($meta['extra']['version-check']))
390                {
391                    try
392                    {
393                        $updates_available = $this->ext_manager->version_check($md_manager, $this->request->variable('versioncheck_force', false), false, $this->config['extension_force_unstable'] ? 'unstable' : null);
394
395                        $this->template->assign_vars(array(
396                            'S_UP_TO_DATE' => empty($updates_available),
397                            'UP_TO_DATE_MSG' => $this->user->lang(empty($updates_available) ? 'UP_TO_DATE' : 'NOT_UP_TO_DATE', $md_manager->get_metadata('display-name')),
398                        ));
399
400                        $this->template->assign_block_vars('updates_available', $updates_available);
401                    }
402                    catch (runtime_exception $e)
403                    {
404                        $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
405
406                        $this->template->assign_vars(array(
407                            'S_VERSIONCHECK_FAIL' => true,
408                            'VERSIONCHECK_FAIL_REASON' => ($e->getMessage() !== 'VERSIONCHECK_FAIL') ? $message : '',
409                        ));
410                    }
411                    $this->template->assign_var('S_VERSIONCHECK', true);
412                }
413                else
414                {
415                    $this->template->assign_var('S_VERSIONCHECK', false);
416                }
417
418                $this->template->assign_vars(array(
419                    'U_BACK'                => $this->u_action . '&amp;action=list',
420                    'U_VERSIONCHECK_FORCE'    => $this->u_action . '&amp;action=details&amp;versioncheck_force=1&amp;ext_name=' . urlencode($md_manager->get_metadata('name')),
421                ));
422
423                $this->tpl_name = 'acp_ext_details';
424            break;
425        }
426
427        /**
428        * Event to run after a specific action on extension has completed
429        *
430        * @event core.acp_extensions_run_action_after
431        * @var    string    action            Action that has run
432        * @var    string    u_action        Url we are at
433        * @var    string    ext_name        Extension name from request
434        * @var    int        safe_time_limit    Safe limit of execution time
435        * @var    int        start_time        Start time
436        * @var    string    tpl_name        Template file to load
437        * @since 3.1.11-RC1
438        */
439        $u_action = $this->u_action;
440        $tpl_name = $this->tpl_name;
441        $vars = array('action', 'u_action', 'ext_name', 'safe_time_limit', 'start_time', 'tpl_name');
442        extract($this->phpbb_dispatcher->trigger_event('core.acp_extensions_run_action_after', compact($vars)));
443
444        // In case they have been updated by the event
445        $this->u_action = $u_action;
446        $this->tpl_name = $tpl_name;
447    }
448
449    /**
450     * Handles the catalog mode of the extensions list
451     *
452     * @param string $id
453     * @param string $mode
454     */
455    public function catalog_mode($id, $mode)
456    {
457        global $phpbb_container;
458
459        $action = $this->request->variable('action', 'list');
460
461        /** @var \phpbb\language\language $language */
462        $language = $phpbb_container->get('language');
463
464        /** @var \phpbb\composer\manager $composer_manager */
465        $composer_manager = $phpbb_container->get('ext.composer.manager');
466
467        /** @var \phpbb\extension\manager $extensions_manager */
468        $extensions_manager = $phpbb_container->get('ext.manager');
469
470        if (!$composer_manager->check_requirements())
471        {
472            $this->page_title = 'ACP_EXTENSIONS_CATALOG';
473            $this->tpl_name = 'message_body';
474
475            $this->template->assign_vars([
476                'MESSAGE_TITLE'    => $language->lang('EXTENSIONS_CATALOG_NOT_AVAILABLE'),
477                'MESSAGE_TEXT'    => $language->lang('EXTENSIONS_COMPOSER_NOT_WRITABLE'),
478            ]);
479
480            return;
481        }
482
483        switch ($action)
484        {
485            case 'install':
486                $this->page_title = 'ACP_EXTENSIONS_INSTALL';
487
488                $extension = $this->request->variable('extension', '');
489
490                if (empty($extension))
491                {
492                    redirect($this->u_action);
493                }
494
495                $formatter = new \phpbb\composer\io\html_output_formatter([
496                    'warning' => new \Symfony\Component\Console\Formatter\OutputFormatterStyle('black', 'yellow')
497                ]);
498
499                $composer_io = new \phpbb\composer\io\web_io($language, '', $phpbb_container->getParameter('extensions.composer.output'), $formatter);
500
501                try
502                {
503                    $composer_manager->install((array) $extension, $composer_io);
504                }
505                catch (\phpbb\exception\runtime_exception $e)
506                {
507                    $this->display_composer_exception($language, $e, $composer_io);
508                    return;
509                }
510                $this->tpl_name = 'detailed_message_body';
511
512                $this->template->assign_vars(array(
513                        'MESSAGE_TITLE'            => $language->lang('ACP_EXTENSIONS_INSTALL'),
514                        'MESSAGE_TEXT'            => $language->lang('EXTENSIONS_INSTALLED') . adm_back_link($this->u_action),
515                        'MESSAGE_DETAIL'        => $composer_io->getOutput(),
516                        'MESSAGE_DETAIL_LEGEND'    => $language->lang('COMPOSER_OUTPUT'),
517                        'S_USER_NOTICE'            => true,
518                    )
519                );
520
521            break;
522            case 'remove':
523                $this->page_title = 'ACP_EXTENSIONS_REMOVE';
524
525                $extension = $this->request->variable('extension', '');
526
527                if (empty($extension))
528                {
529                    redirect($this->u_action);
530                }
531
532                $formatter = new \phpbb\composer\io\html_output_formatter([
533                    'warning' => new \Symfony\Component\Console\Formatter\OutputFormatterStyle('black', 'yellow')
534                ]);
535
536                $composer_io = new \phpbb\composer\io\web_io($language, '', $phpbb_container->getParameter('extensions.composer.output'), $formatter);
537
538                try
539                {
540                    $composer_manager->remove((array) $extension, $composer_io);
541                }
542                catch (\phpbb\exception\runtime_exception $e)
543                {
544                    $this->display_composer_exception($language, $e, $composer_io);
545                    return;
546                }
547                $this->tpl_name = 'detailed_message_body';
548
549                $this->template->assign_vars(array(
550                        'MESSAGE_TITLE'            => $language->lang('ACP_EXTENSIONS_REMOVE'),
551                        'MESSAGE_TEXT'            => $language->lang('EXTENSIONS_REMOVED') . adm_back_link($this->u_action),
552                        'MESSAGE_DETAIL'        => $composer_io->getOutput(),
553                        'MESSAGE_DETAIL_LEGEND'    => $language->lang('COMPOSER_OUTPUT'),
554                        'S_USER_NOTICE'            => true,
555                    )
556                );
557
558            break;
559            case 'update':
560                $this->page_title = 'ACP_EXTENSIONS_UPDATE';
561
562                $extension = $this->request->variable('extension', '');
563
564                if (empty($extension))
565                {
566                    redirect($this->u_action);
567                }
568
569                $formatter = new \phpbb\composer\io\html_output_formatter([
570                    'warning' => new \Symfony\Component\Console\Formatter\OutputFormatterStyle('black', 'yellow')
571                ]);
572
573                $composer_io = new \phpbb\composer\io\web_io($language, '', $phpbb_container->getParameter('extensions.composer.output'), $formatter);
574
575                try
576                {
577                    $composer_manager->update((array) $extension, $composer_io);
578                }
579                catch (\phpbb\exception\runtime_exception $e)
580                {
581                    $this->display_composer_exception($language, $e, $composer_io);
582                    return;
583                }
584                $this->tpl_name = 'detailed_message_body';
585
586                $this->template->assign_vars(array(
587                        'MESSAGE_TITLE'            => $language->lang('ACP_EXTENSIONS_UPDATE'),
588                        'MESSAGE_TEXT'            => $language->lang('EXTENSIONS_UPDATED') . adm_back_link($this->u_action),
589                        'MESSAGE_DETAIL'        => $composer_io->getOutput(),
590                        'MESSAGE_DETAIL_LEGEND'    => $language->lang('COMPOSER_OUTPUT'),
591                        'S_USER_NOTICE'            => true,
592                    )
593                );
594
595            break;
596            case 'manage':
597                $this->page_title = 'ACP_EXTENSIONS_MANAGE';
598
599                $extension = $this->request->variable('extension', '');
600
601                if (empty($extension))
602                {
603                    redirect($this->u_action);
604                }
605
606                $formatter = new \phpbb\composer\io\html_output_formatter([
607                    'warning' => new \Symfony\Component\Console\Formatter\OutputFormatterStyle('black', 'yellow')
608                ]);
609
610                $composer_io = new \phpbb\composer\io\web_io($language, '', $phpbb_container->getParameter('extensions.composer.output'), $formatter);
611
612                try
613                {
614                    $composer_manager->start_managing($extension, $composer_io);
615                }
616                catch (\phpbb\exception\runtime_exception $e)
617                {
618                    $this->display_composer_exception($language, $e, $composer_io);
619                    return;
620                }
621                $this->tpl_name = 'detailed_message_body';
622
623                $this->template->assign_vars(array(
624                        'MESSAGE_TITLE'            => $language->lang('ACP_EXTENSIONS_MANAGE'),
625                        'MESSAGE_TEXT'            => $language->lang('EXTENSION_MANAGED_SUCCESS', $extension) . adm_back_link($this->u_action),
626                        'MESSAGE_DETAIL'        => $composer_io->getOutput(),
627                        'MESSAGE_DETAIL_LEGEND'    => $language->lang('COMPOSER_OUTPUT'),
628                        'S_USER_NOTICE'            => true,
629                    )
630                );
631
632            break;
633            case 'list':
634            default:
635                if (!$this->config['exts_composer_packagist'] && $this->request->is_set('enable_packagist') && confirm_box(true))
636                {
637                    $this->config->set('exts_composer_packagist', true);
638                    $composer_manager->reset_cache();
639
640                    trigger_error($language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action));
641                }
642
643                $submit = $this->request->is_set('update');
644                if ($submit)
645                {
646                    if (!check_form_key('catalog_settings'))
647                    {
648                        trigger_error($language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING);
649                    }
650
651                    $enable_packagist = $this->request->variable('enable_packagist', false);
652                    $enable_on_install = $this->request->variable('enable_on_install', false);
653                    $purge_on_remove = $this->request->variable('purge_on_remove', false);
654                    $minimum_stability = $this->request->variable('minimum_stability', 'stable');
655                    $repositories = array_unique(
656                        array_filter(
657                            array_map(
658                                'trim',
659                                explode("\n", $this->request->variable('repositories', ''))
660                            )
661                        )
662                    );
663
664                    $previous_minimum_stability = $this->config['exts_composer_minimum_stability'];
665                    $previous_repositories = $this->config['exts_composer_repositories'];
666                    $previous_enable_packagist = $this->config['exts_composer_packagist'];
667
668                    $this->config->set('exts_composer_enable_on_install', $enable_on_install);
669                    $this->config->set('exts_composer_purge_on_remove', $purge_on_remove);
670                    $this->config->set('exts_composer_minimum_stability', $minimum_stability);
671                    $this->config->set('exts_composer_repositories', json_encode($repositories, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
672
673                    if ($minimum_stability != $previous_minimum_stability
674                        || $repositories != $previous_repositories
675                        || $enable_packagist != $previous_enable_packagist)
676                    {
677                        $composer_manager->reset_cache();
678                    }
679
680                    if (!$this->config['exts_composer_packagist'] && $enable_packagist)
681                    {
682                        $s_hidden_fields = build_hidden_fields(array(
683                            'enable_packagist'    => $enable_packagist
684                        ));
685
686                        confirm_box(false, $language->lang('ENABLE_PACKAGIST_CONFIRM'), $s_hidden_fields);
687                    }
688                    else
689                    {
690                        $this->config->set('exts_composer_packagist', $enable_packagist);
691                        trigger_error($language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action));
692                    }
693                }
694
695                /** @var \phpbb\composer\extension_manager $manager */
696                $manager = $phpbb_container->get('ext.composer.manager');
697
698                /** @var \phpbb\pagination $pagination */
699                $pagination = $phpbb_container->get('pagination');
700
701                $start = $this->request->variable('start', 0);
702                $base_url = $this->u_action;
703
704                $available_extensions = $manager->get_available_packages();
705                $managed_packages = $manager->get_managed_packages();
706
707                $extensions = array_slice($available_extensions, $start, 20);
708
709                $pagination->generate_template_pagination($base_url, 'pagination', 'start', count($available_extensions), 20, $start);
710
711                $this->page_title = 'ACP_EXTENSIONS_CATALOG';
712                $this->tpl_name = 'acp_ext_catalog';
713
714                $this->template->assign_vars([
715                    'extensions'            => $extensions,
716                    'managed_extensions'    => array_keys($managed_packages),
717                    'installed_extensions'    => array_keys($extensions_manager->all_available()),
718                    'U_ACTION'                => $this->u_action,
719                    'settings' => [
720                        'enable_packagist'    => $this->config['exts_composer_packagist'],
721                        'enable_on_install'    => $this->config['exts_composer_enable_on_install'],
722                        'purge_on_remove'    => $this->config['exts_composer_purge_on_remove'],
723                        'minimum_stability'    => $this->config['exts_composer_minimum_stability'],
724                        'stabilities'        => array_keys(\Composer\Package\BasePackage::$stabilities),
725                        'repositories'        => json_decode($this->config['exts_composer_repositories'], true),
726                    ],
727                ]);
728
729                add_form_key('catalog_settings');
730
731            break;
732        }
733    }
734
735    /**
736     * Display an exception raised by the composer manager
737     *
738     * @param \phpbb\language\language           $language
739     * @param \phpbb\exception\runtime_exception $e
740     * @param \phpbb\composer\io\web_io          $composer_io
741     */
742    private function display_composer_exception(\phpbb\language\language $language, \phpbb\exception\runtime_exception $e, \phpbb\composer\io\web_io $composer_io)
743    {
744        $this->tpl_name = 'detailed_message_body';
745
746        if ($e->getPrevious())
747        {
748            $message_title = $language->lang_array($e->getMessage(), $e->get_parameters());
749
750            if ($e->getPrevious() instanceof \phpbb\exception\exception_interface)
751            {
752                $message_text  = $language->lang_array($e->getPrevious()->getMessage(), $e->getPrevious()->get_parameters()) . adm_back_link($this->u_action);
753            }
754            else
755            {
756                $message_text = $e->getPrevious()->getMessage() . adm_back_link($this->u_action);
757            }
758        }
759        else
760        {
761            $message_title = $language->lang('INFORMATION');
762            $message_text  = $language->lang_array($e->getMessage(), $e->get_parameters()) . adm_back_link($this->u_action);
763        }
764
765        $this->template->assign_vars(array(
766                'MESSAGE_TITLE'            => $message_title,
767                'MESSAGE_TEXT'            => $message_text,
768                'MESSAGE_DETAIL'        => $composer_io->getOutput(),
769                'MESSAGE_DETAIL_LEGEND'    => $language->lang('COMPOSER_OUTPUT'),
770                'S_USER_ERROR'            => true,
771            )
772        );
773    }
774
775    /**
776     * Lists all the enabled extensions and dumps to the template
777     *
778     * @param \phpbb\extension\manager  $phpbb_extension_manager     An instance of the extension manager
779     * @param array                     $managed_packages            List of managed packages
780     *
781     * @return void
782     */
783    public function list_enabled_exts(\phpbb\extension\manager $phpbb_extension_manager, array $managed_packages)
784    {
785        $enabled_extension_meta_data = array();
786
787        foreach ($this->ext_manager->all_enabled() as $name => $location)
788        {
789            $md_manager = $this->ext_manager->create_extension_metadata_manager($name);
790
791            try
792            {
793                $meta = $md_manager->get_metadata('all');
794                $enabled_extension_meta_data[$name] = array(
795                    'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'),
796                    'META_VERSION' => $meta['version'],
797                    'META_NAME' => $md_manager->get_metadata('name'),
798                );
799
800                if (isset($meta['extra']['version-check']))
801                {
802                    try
803                    {
804                        $force_update = $this->request->variable('versioncheck_force', false);
805                        $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update);
806
807                        $enabled_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates);
808                        $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = true;
809                        $enabled_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&amp;action=details&amp;versioncheck_force=1&amp;ext_name=' . urlencode($md_manager->get_metadata('name'));
810                    }
811                    catch (runtime_exception $e)
812                    {
813                        // Ignore exceptions due to the version check
814                    }
815                }
816                else
817                {
818                    $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
819                }
820            }
821            catch (runtime_exception $e)
822            {
823                $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
824                $this->template->assign_block_vars('disabled', array(
825                    'META_DISPLAY_NAME'        => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message),
826                    'S_VERSIONCHECK'        => false,
827                ));
828            }
829            catch (\RuntimeException $e)
830            {
831                $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
832            }
833        }
834
835        uasort($enabled_extension_meta_data, array($this, 'sort_extension_meta_data_table'));
836
837        foreach ($enabled_extension_meta_data as $name => $block_vars)
838        {
839            $block_vars['NAME'] = $name;
840            $block_vars['U_DETAILS'] = $this->u_action . '&amp;action=details&amp;ext_name=' . urlencode($name);
841
842            $this->template->assign_block_vars('enabled', $block_vars);
843
844            $this->output_actions('enabled', array(
845                'DISABLE'        => $this->u_action . '&amp;action=disable_pre&amp;ext_name=' . urlencode($name),
846            ));
847
848            if (isset($managed_packages[$block_vars['META_NAME']]))
849            {
850                $this->output_actions('enabled', [
851                    'UPDATE' => $this->u_catalog_action . '&amp;action=update&amp;extension=' . urlencode($block_vars['META_NAME']),
852                    'REMOVE' => $this->u_catalog_action . '&amp;action=remove&amp;extension=' . urlencode($block_vars['META_NAME']),
853                ]);
854            }
855        }
856    }
857
858    /**
859     * Lists all the disabled extensions and dumps to the template
860     *
861     * @param \phpbb\extension\manager  $phpbb_extension_manager     An instance of the extension manager
862     * @param array                     $managed_packages            List of managed packages
863     *
864     * @return void
865     */
866    public function list_disabled_exts(\phpbb\extension\manager $phpbb_extension_manager, array $managed_packages)
867    {
868        $disabled_extension_meta_data = array();
869
870        foreach ($this->ext_manager->all_disabled() as $name => $location)
871        {
872            $md_manager = $this->ext_manager->create_extension_metadata_manager($name);
873
874            try
875            {
876                $meta = $md_manager->get_metadata('all');
877                $disabled_extension_meta_data[$name] = array(
878                    'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'),
879                    'META_VERSION' => $meta['version'],
880                    'META_NAME' => $md_manager->get_metadata('name'),
881                );
882
883                if (isset($meta['extra']['version-check']))
884                {
885                    $force_update = $this->request->variable('versioncheck_force', false);
886                    $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update);
887
888                    $disabled_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates);
889                    $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = true;
890                    $disabled_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&amp;action=details&amp;versioncheck_force=1&amp;ext_name=' . urlencode($md_manager->get_metadata('name'));
891                }
892                else
893                {
894                    $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
895                }
896            }
897            catch (version_check_exception $e)
898            {
899                $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
900            }
901            catch (runtime_exception $e)
902            {
903                $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
904                $this->template->assign_block_vars('disabled', array(
905                    'META_DISPLAY_NAME'        => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message),
906                    'S_VERSIONCHECK'        => false,
907                ));
908            }
909            catch (\RuntimeException $e)
910            {
911                $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
912            }
913        }
914
915        uasort($disabled_extension_meta_data, array($this, 'sort_extension_meta_data_table'));
916
917        foreach ($disabled_extension_meta_data as $name => $block_vars)
918        {
919            $block_vars['NAME'] = $name;
920            $block_vars['U_DETAILS'] = $this->u_action . '&amp;action=details&amp;ext_name=' . urlencode($name);
921
922            $this->template->assign_block_vars('disabled', $block_vars);
923
924            $this->output_actions('disabled', array(
925                'ENABLE'        => $this->u_action . '&amp;action=enable_pre&amp;ext_name=' . urlencode($name),
926                'DELETE_DATA'    => $this->u_action . '&amp;action=delete_data_pre&amp;ext_name=' . urlencode($name),
927            ));
928
929            if (isset($managed_packages[$block_vars['META_NAME']]))
930            {
931                $this->output_actions('disabled', [
932                    'UPDATE' => $this->u_catalog_action . '&amp;action=update&amp;extension=' . urlencode($block_vars['META_NAME']),
933                    'REMOVE' => $this->u_catalog_action . '&amp;action=remove&amp;extension=' . urlencode($block_vars['META_NAME']),
934                ]);
935            }
936        }
937    }
938
939    /**
940     * Lists all the available extensions and dumps to the template
941     *
942     * @param array                     $managed_packages            List of managed packages
943     *
944     * @return void
945     */
946    public function list_available_exts(array $managed_packages)
947    {
948        $uninstalled = array_diff_key($this->ext_manager->all_available(), $this->ext_manager->all_configured());
949
950        $available_extension_meta_data = array();
951
952        foreach ($uninstalled as $name => $location)
953        {
954            $md_manager = $this->ext_manager->create_extension_metadata_manager($name);
955
956            try
957            {
958                $meta = $md_manager->get_metadata('all');
959                $available_extension_meta_data[$name] = array(
960                    'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'),
961                    'META_VERSION' => $meta['version'],
962                    'META_NAME' => $md_manager->get_metadata('name'),
963                );
964
965                if (isset($meta['extra']['version-check']))
966                {
967                    $force_update = $this->request->variable('versioncheck_force', false);
968                    $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update);
969
970                    $available_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates);
971                    $available_extension_meta_data[$name]['S_VERSIONCHECK'] = true;
972                    $available_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&amp;action=details&amp;versioncheck_force=1&amp;ext_name=' . urlencode($md_manager->get_metadata('name'));
973                }
974                else
975                {
976                    $available_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
977                }
978            }
979            catch (version_check_exception $e)
980            {
981                $available_extension_meta_data[$name]['S_VERSIONCHECK'] = false;
982            }
983            catch (runtime_exception $e)
984            {
985                $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
986                $this->template->assign_block_vars('not_installed', array(
987                    'META_DISPLAY_NAME'        => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message),
988                    'S_VERSIONCHECK'        => false,
989                ));
990            }
991        }
992
993        uasort($available_extension_meta_data, array($this, 'sort_extension_meta_data_table'));
994
995        foreach ($available_extension_meta_data as $name => $block_vars)
996        {
997            $block_vars['NAME'] = $name;
998            $block_vars['U_DETAILS'] = $this->u_action . '&amp;action=details&amp;ext_name=' . urlencode($name);
999
1000            $this->template->assign_block_vars('not_installed', $block_vars);
1001
1002            $this->output_actions('not_installed', array(
1003                'ENABLE'        => $this->u_action . '&amp;action=enable_pre&amp;ext_name=' . urlencode($name),
1004                'REMOVE' => $this->u_catalog_action . '&amp;action=remove&amp;extension=' . urlencode($block_vars['META_NAME']),
1005            ));
1006        }
1007    }
1008
1009    /**
1010    * Output actions to a block
1011    *
1012    * @param string $block
1013    * @param array $actions
1014    */
1015    private function output_actions($block, $actions)
1016    {
1017        foreach ($actions as $action => $url)
1018        {
1019            $this->template->assign_block_vars($block . '.actions', [
1020                'ACTION'            => $action,
1021                'L_ACTION'            => $this->user->lang('EXTENSION_' . $action),
1022                'L_ACTION_EXPLAIN'    => (isset($this->user->lang['EXTENSION_' . $action . '_EXPLAIN'])) ? $this->user->lang('EXTENSION_' . $action . '_EXPLAIN') : '',
1023                'U_ACTION'            => $url,
1024            ]);
1025        }
1026    }
1027
1028    /**
1029    * Sort helper for the table containing the metadata about the extensions.
1030    */
1031    protected function sort_extension_meta_data_table($val1, $val2)
1032    {
1033        return strnatcasecmp($val1['META_DISPLAY_NAME'], $val2['META_DISPLAY_NAME']);
1034    }
1035
1036    /**
1037    * Outputs extension metadata into the template
1038    *
1039    * @param array $metadata Array with all metadata for the extension
1040    * @return void
1041    */
1042    public function output_metadata_to_template($metadata)
1043    {
1044        $this->template->assign_vars(array(
1045            'META_NAME'            => $metadata['name'],
1046            'META_TYPE'            => $metadata['type'],
1047            'META_DESCRIPTION'    => (isset($metadata['description'])) ? $metadata['description'] : '',
1048            'META_HOMEPAGE'        => (isset($metadata['homepage'])) ? $metadata['homepage'] : '',
1049            'META_VERSION'        => $metadata['version'],
1050            'META_TIME'            => (isset($metadata['time'])) ? $metadata['time'] : '',
1051            'META_LICENSE'        => $metadata['license'],
1052
1053            'META_REQUIRE_PHP'        => (isset($metadata['require']['php'])) ? $metadata['require']['php'] : '',
1054            'META_REQUIRE_PHP_FAIL'    => (isset($metadata['require']['php'])) ? false : true,
1055
1056            'META_REQUIRE_PHPBB'        => (isset($metadata['extra']['soft-require']['phpbb/phpbb'])) ? $metadata['extra']['soft-require']['phpbb/phpbb'] : '',
1057            'META_REQUIRE_PHPBB_FAIL'    => (isset($metadata['extra']['soft-require']['phpbb/phpbb'])) ? false : true,
1058
1059            'META_DISPLAY_NAME'    => (isset($metadata['extra']['display-name'])) ? $metadata['extra']['display-name'] : '',
1060        ));
1061
1062        foreach ($metadata['authors'] as $author)
1063        {
1064            $this->template->assign_block_vars('meta_authors', array(
1065                'AUTHOR_NAME'        => $author['name'],
1066                'AUTHOR_EMAIL'        => (isset($author['email'])) ? $author['email'] : '',
1067                'AUTHOR_HOMEPAGE'    => (isset($author['homepage'])) ? $author['homepage'] : '',
1068                'AUTHOR_ROLE'        => (isset($author['role'])) ? $author['role'] : '',
1069            ));
1070        }
1071    }
1072
1073    /**
1074    * Checks whether the extension can be enabled. Triggers error if not.
1075    * Error message can be set by the extension.
1076    *
1077    * @param \phpbb\extension\extension_interface $extension Extension to check
1078    */
1079    protected function check_is_enableable(\phpbb\extension\extension_interface $extension)
1080    {
1081        $message = $extension->is_enableable();
1082        if ($message !== true)
1083        {
1084            if (empty($message))
1085            {
1086                $message = $this->user->lang('EXTENSION_NOT_ENABLEABLE');
1087            }
1088            else if (is_array($message))
1089            {
1090                $message = implode('<br>', $message);
1091            }
1092
1093            trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING);
1094        }
1095    }
1096}