diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css
index 145bcfaec1..dd5878a976 100644
--- a/phpBB/adm/style/admin.css
+++ b/phpBB/adm/style/admin.css
@@ -1776,7 +1776,7 @@ li.pagination ul {
/* Action Highlighting
---------------------------------------- */
-.successbox, .errorbox {
+.successbox, .errorbox, .warningbox {
padding: 8px;
margin: 10px 0;
color: #FFFFFF;
@@ -1800,6 +1800,10 @@ li.pagination ul {
background-color: #BC2A4D;
}
+.warningbox {
+ background-color: #fca600;
+}
+
.successbox h3, .errorbox h3 {
color: #FFFFFF;
margin: 0 0 0.5em;
diff --git a/phpBB/adm/style/installer_form.html b/phpBB/adm/style/installer_form.html
new file mode 100644
index 0000000000..dbcf3c19f8
--- /dev/null
+++ b/phpBB/adm/style/installer_form.html
@@ -0,0 +1,52 @@
+
diff --git a/phpBB/adm/style/installer_install.html b/phpBB/adm/style/installer_install.html
new file mode 100644
index 0000000000..7d62075c62
--- /dev/null
+++ b/phpBB/adm/style/installer_install.html
@@ -0,0 +1,13 @@
+
+{L_INSTALL}
+{CONTENT}
+
+
+
+
+
diff --git a/phpBB/assets/javascript/installer.js b/phpBB/assets/javascript/installer.js
new file mode 100644
index 0000000000..302d95e7c6
--- /dev/null
+++ b/phpBB/assets/javascript/installer.js
@@ -0,0 +1,205 @@
+/**
+ * Installer's AJAX frontend handler
+ */
+
+(function($) { // Avoid conflicts with other libraries
+ // Global variables
+ var pollTimer = null;
+ var nextReadPosition = 0;
+
+ // Template related variables
+ var $contentWrapper = $('.install-body').find('.main');
+
+ // Intercept form submits
+ intercept_form_submit($('#install_install'));
+
+ function poll_content(xhReq) {
+ var messages = xhReq.responseText;
+
+ do {
+ var unprocessed = messages.substring(nextReadPosition);
+ var messageEndIndex = unprocessed.indexOf('}\n\n');
+
+ if (messageEndIndex !== -1) {
+ var endOfMessageIndex = messageEndIndex + 3; // 3 is the length of "}\n\n"
+ var message = unprocessed.substring(0, endOfMessageIndex);
+ parse_message(message);
+ nextReadPosition += endOfMessageIndex;
+ }
+ } while (messageEndIndex !== -1);
+
+ if (xhReq.readyState === 4) {
+ $('#loading_indicator').css('display', 'none');
+ reset_polling();
+ }
+ }
+
+ function parse_message(messageJSON) {
+ $('#loading_indicator').css('display', 'none');
+
+ messageJSON = messageJSON.trim();
+ var responseObject = JSON.parse(messageJSON);
+
+ // Parse object
+ if (responseObject.hasOwnProperty('errors')) {
+ add_message('error', responseObject.errors)
+ }
+
+ if (responseObject.hasOwnProperty('warnings')) {
+ add_message('warning', responseObject.warnings)
+ }
+
+ if (responseObject.hasOwnProperty('logs')) {
+ add_message('log', responseObject.logs);
+ }
+
+ if (responseObject.hasOwnProperty('form')) {
+ add_form(responseObject.form);
+ }
+ }
+
+ function add_message(type, messages) {
+ // Get message containers
+ var errorContainer = $('#error-container');
+ var warningContainer = $('#warning-container');
+ var logContainer = $('#log-container');
+
+ var title, description, msgElement, arraySize = messages.length;
+ for (var i = 0; i < arraySize; i++) {
+ msgElement = $('');
+ title = $(document.createElement('strong'));
+ title.text(messages[i].title);
+ msgElement.append(title);
+
+ if (messages[i].hasOwnProperty('description')) {
+ description = $(document.createElement('p'));
+ description.text(messages[i].description);
+ msgElement.append(description);
+ }
+
+ switch (type) {
+ case 'error':
+ msgElement.addClass('errorbox');
+ errorContainer.append(msgElement);
+ break;
+ case 'warning':
+ msgElement.addClass('warningbox');
+ warningContainer.append(msgElement);
+ break;
+ case 'log':
+ msgElement.addClass('log');
+ logContainer.append(msgElement);
+ break;
+ }
+ }
+ }
+
+ function add_form(formHtml) {
+ var formContainer = $('#content-container');
+ formContainer.html(formHtml);
+ var form = $('#install_install');
+ intercept_form_submit(form);
+ }
+
+ function start_polling(xhReq) {
+ reset_polling();
+ pollTimer = setInterval(function () {
+ poll_content(xhReq);
+ }, 500);
+ }
+
+ function reset_polling() {
+ clearInterval(pollTimer);
+ nextReadPosition = 0;
+ }
+
+ function submit_form(form, submitBtn) {
+ form.css('display', 'none');
+
+ var xhReq = create_xhr_object();
+ xhReq.open('POST', form.attr('action'), true);
+ xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ xhReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ xhReq.send(get_form_fields(form, submitBtn));
+
+ // Clear content
+ setup_ajax_layout();
+ $('#loading_indicator').css('display', 'block');
+
+ start_polling(xhReq);
+ }
+
+ // Workaround for submit buttons
+ function get_form_fields(form, submitBtn) {
+ var formData = form.serialize();
+ //var submitBtn = form.find(':submit');
+ formData += ((formData.length) ? '&' : '') + encodeURIComponent(submitBtn.attr('name')) + '=';
+ formData += encodeURIComponent(submitBtn.attr('value'));
+
+ return formData;
+ }
+
+ function intercept_form_submit(form) {
+ if (!form.length) {
+ return;
+ }
+
+ form.find(':submit').bind('click', function (event) {
+ event.preventDefault();
+ submit_form(form, $(this));
+ });
+
+ }
+
+ /**
+ * jQuery cannot be used as the response is streamed, and
+ * as of now, jQuery does not provide access to the response until
+ * the connection is not closed.
+ */
+ function create_xhr_object() {
+ var xhReq;
+
+ if (window.XMLHttpRequest) {
+ xhReq = new XMLHttpRequest();
+ }
+ else if (window.ActiveXObject) {
+ xhReq = new ActiveXObject("Msxml2.XMLHTTP");
+ }
+
+ return xhReq;
+ }
+
+ function setup_ajax_layout() {
+ // Clear content
+ $contentWrapper.html('');
+
+ var $header = $('');
+ $header.attr('id', 'header-container');
+ $contentWrapper.append($header);
+
+ var $description = $('');
+ $description.attr('id', 'description-container');
+ $contentWrapper.append($description);
+
+ var $errorContainer = $('');
+ $errorContainer.attr('id', 'error-container');
+ $contentWrapper.append($errorContainer);
+
+ var $warningContainer = $('');
+ $warningContainer.attr('id', 'warning-container');
+ $contentWrapper.append($warningContainer);
+
+ var $installerContentWrapper = $('');
+ $installerContentWrapper.attr('id', 'content-container');
+ $contentWrapper.append($installerContentWrapper);
+
+ var $logContainer = $('');
+ $logContainer.attr('id', 'log-container');
+ $contentWrapper.append($logContainer);
+
+ var $spinner = $('');
+ $spinner.attr('id', 'loading_indicator');
+ $spinner.html(' ');
+ $contentWrapper.append($spinner);
+ }
+})(jQuery); // Avoid conflicts with other libraries
diff --git a/phpBB/config/installer/routing/environment.yml b/phpBB/config/installer/routing/environment.yml
new file mode 100644
index 0000000000..60324c975b
--- /dev/null
+++ b/phpBB/config/installer/routing/environment.yml
@@ -0,0 +1,2 @@
+core.default:
+ resource: "installer.yml"
diff --git a/phpBB/config/installer/routing/installer.yml b/phpBB/config/installer/routing/installer.yml
new file mode 100644
index 0000000000..80a995ab6e
--- /dev/null
+++ b/phpBB/config/installer/routing/installer.yml
@@ -0,0 +1,22 @@
+phpbb_installer_index:
+ path: /
+ defaults:
+ _controller: phpbb.installer.controller.welcome:handle
+ mode: "intro"
+
+phpbb_installer_license:
+ path: /license
+ defaults:
+ _controller: phpbb.installer.controller.welcome:handle
+ mode: "license"
+
+phpbb_installer_support:
+ path: /support
+ defaults:
+ _controller: phpbb.installer.controller.welcome:handle
+ mode: "support"
+
+phpbb_installer_install:
+ path: /install
+ defaults:
+ _controller: phpbb.installer.controller.install:handle
diff --git a/phpBB/install/app.php b/phpBB/install/app.php
new file mode 100644
index 0000000000..58ca141af7
--- /dev/null
+++ b/phpBB/install/app.php
@@ -0,0 +1,83 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+/**
+ * @ignore
+ */
+define('IN_PHPBB', true);
+define('IN_INSTALL', true);
+define('PHPBB_ENVIRONMENT', 'production');
+$phpbb_root_path = '../';
+$phpEx = substr(strrchr(__FILE__, '.'), 1);
+
+//
+// Let's do the common.php logic
+//
+require($phpbb_root_path . 'includes/startup.' . $phpEx);
+require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx);
+
+$phpbb_class_loader = new \phpbb\class_loader('phpbb\\install\\', "{$phpbb_root_path}install/", $phpEx);
+$phpbb_class_loader->register();
+
+$phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx);
+$phpbb_class_loader->register();
+
+// In case $phpbb_adm_relative_path is not set (in case of an update), use the default.
+$phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relative_path : 'adm/';
+$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path;
+
+// Include files
+require($phpbb_root_path . 'includes/functions.' . $phpEx);
+require($phpbb_root_path . 'includes/functions_content.' . $phpEx);
+include($phpbb_root_path . 'includes/functions_compatibility.' . $phpEx);
+require($phpbb_root_path . 'includes/functions_user.' . $phpEx);
+require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx);
+
+// Set PHP error handler to ours
+set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler');
+
+$phpbb_installer_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx);
+$phpbb_installer_container = $phpbb_installer_container_builder
+ ->with_environment('installer')
+ ->without_extensions()
+ ->without_cache()
+ ->get_container();
+
+// Path to templates
+$paths = array($phpbb_root_path . 'install/update/new/adm/style', $phpbb_admin_path . 'style');
+$paths = array_filter($paths, 'is_dir');
+
+/** @var \phpbb\filesystem\filesystem $phpbb_filesystem */
+$phpbb_filesystem = $phpbb_installer_container->get('filesystem');
+
+/** @var \phpbb\template\template $template */
+$template = $phpbb_installer_container->get('template');
+$template->set_custom_style(array(
+ array(
+ 'name' => 'adm',
+ 'ext_path' => 'adm/style/',
+ ),
+), $paths);
+
+/** @var \phpbb\language\language $language */
+$language = $phpbb_installer_container->get('language');
+$language->add_lang(array('common', 'acp/common', 'acp/board', 'install', 'posting'));
+
+/* @var $http_kernel \Symfony\Component\HttpKernel\HttpKernel */
+$http_kernel = $phpbb_installer_container->get('http_kernel');
+
+/* @var $symfony_request \phpbb\symfony_request */
+$symfony_request = $phpbb_installer_container->get('symfony_request');
+$response = $http_kernel->handle($symfony_request);
+$response->send();
+$http_kernel->terminate($symfony_request, $response);
diff --git a/phpBB/install/controller/helper.php b/phpBB/install/controller/helper.php
new file mode 100644
index 0000000000..7a5e20406d
--- /dev/null
+++ b/phpBB/install/controller/helper.php
@@ -0,0 +1,228 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\install\controller;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * A duplicate of \phpbb\controller\helper
+ *
+ * This class is necessary because of controller\helper's legacy function calls
+ * to page_header() page_footer() functions which has unavailable dependencies.
+ */
+class helper
+{
+ /**
+ * @var \phpbb\language\language
+ */
+ protected $language;
+
+ /**
+ * @var \phpbb\language\language_file_helper
+ */
+ protected $lang_helper;
+
+ /**
+ * @var \phpbb\install\helper\navigation\navigation_provider
+ */
+ protected $navigation_provider;
+
+ /**
+ * @var \phpbb\template\template
+ */
+ protected $template;
+
+ /**
+ * @var \phpbb\path_helper
+ */
+ protected $path_helper;
+
+ /**
+ * @var \phpbb\symfony_request
+ */
+ protected $request;
+
+ /**
+ * @var \phpbb\routing\router
+ */
+ protected $router;
+
+ /**
+ * @var string
+ */
+ protected $phpbb_admin_path;
+
+ /**
+ * @var string
+ */
+ protected $phpbb_root_path;
+
+ public function __construct(\phpbb\language\language $language, \phpbb\language\language_file_helper $lang_helper, \phpbb\install\helper\navigation\navigation_provider $nav, \phpbb\template\template $template, \phpbb\path_helper $path_helper, \phpbb\symfony_request $request, \phpbb\routing\router $router, $phpbb_root_path)
+ {
+ $this->language = $language;
+ $this->lang_helper = $lang_helper;
+ $this->navigation_provider = $nav;
+ $this->template = $template;
+ $this->path_helper = $path_helper;
+ $this->request = $request;
+ $this->router = $router;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->phpbb_admin_path = $phpbb_root_path . 'adm/';
+ }
+
+ /**
+ * Automate setting up the page and creating the response object.
+ *
+ * @param string $template_file The template handle to render
+ * @param string $page_title The title of the page to output
+ * @param int $status_code The status code to be sent to the page header
+ *
+ * @return Response object containing rendered page
+ */
+ public function render($template_file, $page_title = '', $status_code = 200)
+ {
+ $this->page_header($page_title);
+
+ $this->template->set_filenames(array(
+ 'body' => $template_file,
+ ));
+
+ return new Response($this->template->assign_display('body'), $status_code);
+ }
+
+ /**
+ * Set default template variables
+ *
+ * @param string $page_title
+ */
+ protected function page_header($page_title)
+ {
+ $this->template->assign_vars(array(
+ 'L_CHANGE' => $this->language->lang('CHANGE'),
+ 'L_COLON' => $this->language->lang('COLON'),
+ 'L_INSTALL_PANEL' => $this->language->lang('INSTALL_PANEL'),
+ 'L_SELECT_LANG' => $this->language->lang('SELECT_LANG'),
+ 'L_SKIP' => $this->language->lang('SKIP'),
+ 'PAGE_TITLE' => $this->language->lang($page_title),
+ 'T_IMAGE_PATH' => htmlspecialchars($this->phpbb_admin_path) . 'images/',
+ 'T_JQUERY_LINK' => $this->path_helper->get_web_root_path() . 'assets/javascript/jquery.min.js',
+ 'T_TEMPLATE_PATH' => $this->path_helper->get_web_root_path() . 'adm/style',
+ 'T_ASSETS_PATH' => $this->path_helper->get_web_root_path() . 'assets/',
+
+ 'S_CONTENT_DIRECTION' => $this->language->lang('DIRECTION'),
+ 'S_CONTENT_FLOW_BEGIN' => ($this->language->lang('DIRECTION') === 'ltr') ? 'left' : 'right',
+ 'S_CONTENT_FLOW_END' => ($this->language->lang('DIRECTION') === 'ltr') ? 'right' : 'left',
+ 'S_CONTENT_ENCODING' => 'UTF-8',
+
+ 'S_USER_LANG' => $this->language->lang('USER_LANG'),
+ )
+ );
+
+ $this->render_navigation();
+ }
+
+ /**
+ * Render navigation
+ */
+ protected function render_navigation()
+ {
+ // Get navigation items
+ $nav_array = $this->navigation_provider->get();
+
+ // @todo Sort navs by order
+
+ $active_main_menu = $this->get_active_main_menu($nav_array);
+
+ // Pass navigation to template
+ foreach ($nav_array as $key => $entry)
+ {
+ $this->template->assign_block_vars('t_block1', array(
+ 'L_TITLE' => $this->language->lang($entry['label']),
+ 'S_SELECTED' => ($active_main_menu === $key),
+ 'U_TITLE' => $this->route($entry['route']),
+ ));
+
+ if (is_array($entry[0]) && $active_main_menu === $key)
+ {
+ // @todo Sort navs by order
+
+ foreach ($entry[0] as $sub_entry)
+ {
+ $this->template->assign_block_vars('l_block1', array(
+ 'L_TITLE' => $this->language->lang($sub_entry['label']),
+ 'S_SELECTED' => (isset($sub_entry['route']) && $sub_entry['route'] === $this->request->get('_route')),
+ 'U_TITLE' => $this->route($sub_entry['route']),
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns path from route name
+ *
+ * @param string $route_name
+ *
+ * @return string
+ */
+ public function route($route_name)
+ {
+ $url = $this->router->generate($route_name);
+
+ return $url;
+ }
+
+ /**
+ * Render language select form
+ */
+ protected function render_language_select()
+ {
+ $langs = $this->lang_helper->get_available_languages();
+ }
+
+ /**
+ * Returns the name of the active main menu item
+ *
+ * @param array $nav_array
+ *
+ * @return string|bool Returns the name of the active main menu element, if the element not found, returns false
+ */
+ protected function get_active_main_menu($nav_array)
+ {
+ $active_route = $this->request->get('_route');
+
+ foreach ($nav_array as $nav_name => $nav_options)
+ {
+ $current_menu = $nav_name;
+
+ if (isset($nav_options['route']) && $nav_options['route'] === $active_route)
+ {
+ return $nav_name;
+ }
+
+ if (is_array($nav_options[0]))
+ {
+ foreach ($nav_options[0] as $sub_menus)
+ {
+ if (isset($sub_menus['route']) &&$sub_menus['route'] === $active_route)
+ {
+ return $current_menu;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/phpBB/install/controller/install.php b/phpBB/install/controller/install.php
new file mode 100644
index 0000000000..1217107484
--- /dev/null
+++ b/phpBB/install/controller/install.php
@@ -0,0 +1,109 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\install\controller;
+
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+/**
+ * Controller for installing phpBB
+ */
+class install
+{
+ /**
+ * @var \phpbb\install\controller\helper
+ */
+ protected $controller_helper;
+
+ /**
+ * @var \phpbb\install\helper\iohandler\factory
+ */
+ protected $iohandler_factory;
+
+ /**
+ * @var \phpbb\template\template
+ */
+ protected $template;
+
+ /**
+ * @var \phpbb\request\request_interface
+ */
+ protected $request;
+
+ /**
+ * @var \phpbb\install\installer
+ */
+ protected $installer;
+
+ /**
+ * Constructor
+ *
+ * @param helper $helper
+ * @param \phpbb\install\helper\iohandler\factory $factory
+ * @param \phpbb\request\request_interface $request
+ * @param \phpbb\install\installer $installer
+ */
+ public function __construct(helper $helper, \phpbb\install\helper\iohandler\factory $factory, \phpbb\template\template $template, \phpbb\request\request_interface $request, \phpbb\install\installer $installer)
+ {
+ $this->controller_helper = $helper;
+ $this->iohandler_factory = $factory;
+ $this->template = $template;
+ $this->request = $request;
+ $this->installer = $installer;
+ }
+
+ public function handle()
+ {
+ // @todo check that phpBB is not already installed
+
+ $this->template->assign_vars(array(
+ 'U_ACTION' => $this->controller_helper->route('phpbb_installer_install'),
+ ));
+
+ // Set up input-output handler
+ if ($this->request->is_ajax())
+ {
+ $this->iohandler_factory->set_environment('ajax');
+ }
+ else
+ {
+ $this->iohandler_factory->set_environment('nojs');
+ }
+
+ if ($this->request->is_ajax())
+ {
+ $installer = &$this->installer;
+
+ $response = new StreamedResponse();
+ $response->setCallback(function() use ($installer) {
+ $installer->run();
+ });
+
+ return $response;
+ }
+ else
+ {
+ // Determine whether the installation was started or not
+ if (true)
+ {
+ // If not, let's render the welcome page
+ $this->template->assign_vars(array(
+ 'SHOW_INSTALL_START_FORM' => true,
+ ));
+ return $this->controller_helper->render('installer_install.html', 'INSTALL');
+ }
+
+ // @todo: implement no js controller logic
+ }
+ }
+}
diff --git a/phpBB/install/controller/install_index.php b/phpBB/install/controller/install_index.php
new file mode 100644
index 0000000000..c61d68f7fb
--- /dev/null
+++ b/phpBB/install/controller/install_index.php
@@ -0,0 +1,79 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\install\controller;
+
+class install_index
+{
+ /**
+ * @var helper
+ */
+ protected $helper;
+
+ /**
+ * @var \phpbb\language\language
+ */
+ protected $language;
+
+ /**
+ * @var \phpbb\template\template
+ */
+ protected $template;
+
+ /**
+ * @var string
+ */
+ protected $phpbb_root_path;
+
+ /**
+ * Constructor
+ *
+ * @param helper $helper
+ * @param \phpbb\language\language $language
+ * @param \phpbb\template\template $template
+ * @param string $phpbb_root_path
+ */
+ public function __construct(helper $helper, \phpbb\language\language $language, \phpbb\template\template $template, $phpbb_root_path)
+ {
+ $this->helper = $helper;
+ $this->language = $language;
+ $this->template = $template;
+ $this->phpbb_root_path = $phpbb_root_path;
+ }
+
+ public function handle($mode)
+ {
+ switch ($mode)
+ {
+ case "intro":
+ $title = $this->language->lang('INTRODUCTION_TITLE');
+ $body = $this->language->lang('INTRODUCTION_BODY');
+ break;
+ case "support":
+ $title = $this->language->lang('SUPPORT_TITLE');
+ $body = $this->language->lang('SUPPORT_BODY');
+ break;
+ case "license":
+ $title = $this->language->lang('LICENSE_TITLE');
+ $body = implode("
\n", file($this->phpbb_root_path . 'docs/LICENSE.txt'));
+ break;
+ }
+
+ $this->template->assign_vars(array(
+ 'TITLE' => $title,
+ 'BODY' => $body,
+ ));
+
+ return $this->helper->render('install_main.html', $title);
+ }
+}