diff --git a/assets/javascript/ajax.js b/assets/javascript/ajax.js index 8c927afbd..53746a358 100644 --- a/assets/javascript/ajax.js +++ b/assets/javascript/ajax.js @@ -141,6 +141,17 @@ }); }; + phpbb.addAjaxCallback('titania.package.builder.add', function(result) { + var $revision = $(this).data('revision-id'); + + // Hide the other buttons + $('.package-builder-add').has('a:not([data-revision-id="' + $revision + '"])').hide(); + + // Change the button that was clicked + $('.package-builder-add > a').remove(); + $('.package-builder-add').append('<span class="package-builder-download-prompt">' + result.message + '</span>'); + }); + phpbb.addAjaxCallback('titania.rate', function(res) { if (res.rating) { var $rating = $(res.rating); diff --git a/config/controllers.yml b/config/controllers.yml index a8eb6d821..8fc923385 100644 --- a/config/controllers.yml +++ b/config/controllers.yml @@ -15,6 +15,20 @@ services: - '@phpbb.titania.config' - '@phpbb.titania.tracking' + phpbb.titania.controller.package_builder: + class: phpbb\titania\controller\package_builder + arguments: + - '@dbal.conn' + - '@template' + - '@config' + - '@user' + - '@language' + - '@phpbb.titania.controller.helper' + - '@phpbb.titania.cache' + - '@request' + - '@phpbb.titania.config' + - '%phpbb.titania.root_path%' + phpbb.titania.controller.faq: class: phpbb\titania\controller\faq arguments: diff --git a/config/routes/general.yml b/config/routes/general.yml index 3b9c76178..57e892edd 100644 --- a/config/routes/general.yml +++ b/config/routes/general.yml @@ -16,6 +16,18 @@ phpbb.titania.faq: path: /faq defaults: { _controller: phpbb.titania.controller.faq:handle } +phpbb.titania.package_builder.add: + path: /package-builder/add/{contrib}/{revision} + defaults: { _controller: phpbb.titania.controller.package_builder:add } + +phpbb.titania.package_builder.reset: + path: /package-builder/reset + defaults: { _controller: phpbb.titania.controller.package_builder:reset } + +phpbb.titania.package_builder.download: + path: /package-builder/download + defaults: { _controller: phpbb.titania.controller.package_builder:download } + phpbb.titania.queue_stats: path: /queue-stats/{contrib_type} defaults: { _controller: phpbb.titania.controller.queue_stats:display_stats } diff --git a/controller/package_builder.php b/controller/package_builder.php new file mode 100644 index 000000000..52ca5bfbb --- /dev/null +++ b/controller/package_builder.php @@ -0,0 +1,266 @@ +<?php +/** + * + * This file is part of the phpBB Customisation Database package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @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\titania\controller; + +use phpbb\titania\ext; +use Symfony\Component\HttpFoundation\JsonResponse; + +class package_builder +{ + const COOKIE_NAME = 'cdb_package_builder'; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\titania\controller\helper */ + protected $helper; + + /** @var \phpbb\titania\cache\service */ + protected $cache; + + /** @var \phpbb\request\request_interface */ + protected $request; + + /** @var \phpbb\titania\config\config */ + protected $ext_config; + + /** @var string */ + protected $ext_root_path; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\template\template $template + * @param \phpbb\config\config $config + * @param \phpbb\user $user + * @param \phpbb\language\language $language + * @param \phpbb\titania\controller\helper $helper + * @param \phpbb\titania\cache\service $cache + * @param \phpbb\request\request_interface $request + * @param \phpbb\titania\config\config $ext_config + * @param $ext_root_path + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\template\template $template, \phpbb\config\config $config, \phpbb\user $user, \phpbb\language\language $language, \phpbb\titania\controller\helper $helper, \phpbb\titania\cache\service $cache, \phpbb\request\request_interface $request, \phpbb\titania\config\config $ext_config, $ext_root_path) + { + $this->db = $db; + $this->template = $template; + $this->config = $config; + $this->user = $user; + $this->language = $language; + $this->helper = $helper; + $this->cache = $cache; + $this->request = $request; + $this->ext_config = $ext_config; + $this->ext_root_path = $ext_root_path; + } + + /** + * Get the cookie name + * @return string + */ + private function get_cookie_name() + { + return $this->config['cookie_name'] . '_' . self::COOKIE_NAME; + } + + /** + * Save the cookie + * @param $cookie_value + */ + private function save_cookie($cookie_value) + { + // One hour expiry + $this->user->set_cookie(self::COOKIE_NAME, $cookie_value, time() + (60 * 60)); + } + + /** + * Get the revisions and contributions already selected out of the cookie value + * @param $value + * @return array + */ + public static function split_cookie_values($value) + { + $results = [ + 'contribs' => [], + 'revisions' => [], + ]; + + $values = explode(',', $value); + + foreach ($values as $item) + { + if (strlen($item) > 0) + { + $split = explode('|', $item); + + $results['contribs'][] = $split[0]; + $results['revisions'][] = $split[1]; + } + } + + return $results; + } + + /** + * Create a json response + * @param $success + * @param $values + * @param string $message + * @return JsonResponse + */ + private function create_json_response($success, $values, $message = '') + { + $json = [ + 'success' => $success, + 'values' => $values, + ]; + + if (!empty($message)) + { + $json['message'] = $message; + } + + return new JsonResponse($json); + } + + /** + * Clear the existing values saved in the cookie + */ + public function reset() + { + $this->save_cookie(''); + + return $this->create_json_response(true, ''); + } + + /** + * Download the completed package + * @throws \phpbb\titania\entity\UnknownPropertyException + */ + public function download() + { + $versions = $this->cache->get_phpbb_versions(); + $latest_version = reset($versions); + + $phpbb_package = $this->ext_root_path . 'includes/phpbb_packages/phpBB-' . $latest_version . '.zip'; + $extract_path = $this->ext_config->__get('contrib_temp_path') . 'tmp/package_' . $this->user->data['user_id']; + + $zip = new \ZipArchive(); + + if ($zip->open($phpbb_package)) + { + // Unzip the revision to a temporary folder + $zip->extractTo($extract_path); + $zip->close(); + + // Take the revisions from the cookies, and plug them into the phpBB3 instance + // extensions to ext/, styles to style/ and language packs to language/ + $existing_cookie = $this->request->variable($this->get_cookie_name(), '', false, \phpbb\request\request_interface::COOKIE); + $existing_values = self::split_cookie_values($existing_cookie); + + // Get the attachments + $sql = 'SELECT r.revision_id, c.contrib_id, a.attachment_directory, a.physical_filename, c.contrib_type + FROM ' . TITANIA_REVISIONS_TABLE . ' r, ' . TITANIA_ATTACHMENTS_TABLE . ' a, ' . TITANIA_CONTRIBS_TABLE . ' c + WHERE r.attachment_id = a.attachment_id + AND c.contrib_id = r.contrib_id + AND ' . $this->db->sql_in_set('r.revision_id', $existing_values['revisions']); + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // TODO: Check that the revision ID hasn't been manipulated and this user has access to download the revision + + $contribution_archive = $this->ext_config->upload_path . $row['attachment_directory'] . '/' . $row['physical_filename']; + + $zip = new \ZipArchive(); + + if ($zip->open($contribution_archive)) + { + if ($row['contrib_type'] == ext::TITANIA_TYPE_EXTENSION) + { + $zip->extractTo($extract_path . '/phpBB3/ext'); + } + + if ($row['contrib_type'] == ext::TITANIA_TYPE_STYLE) + { + $zip->extractTo($extract_path . '/phpBB3/style'); + } + + if ($row['contrib_type'] == ext::TITANIA_TYPE_TRANSLATION) + { + // TODO: talk to Crizzo about where these need to end up being moved to + $zip->extractTo($extract_path . '/phpBB3/language'); + } + + $zip->close(); + } + } + + // TODO: Zip up the final package and send it to the browser + } + } + + /** + * Add a revision ID + * @param $contrib + * @param $revision + * @return JsonResponse + */ + public function add($contrib, $revision) + { + $json = null; + + $existing_cookie = $this->request->variable($this->get_cookie_name(), '', false, \phpbb\request\request_interface::COOKIE); + + // Validate whether it's already added + $existing_values = self::split_cookie_values($existing_cookie); + + if (in_array($contrib, $existing_values['contribs']) || in_array($revision, $existing_values['revisions'])) + { + // This contribution (or revision) has already been added + $json = $this->create_json_response(false, $existing_values, $this->language->lang('PACKAGE_ALREADY_ADDED')); + } + + else + { + // Cookies are my favourite food group, cookies taste nice. And in this case they are lightweight :D + $value = $contrib . '|' . $revision; + $cookie_value = (!empty($existing_cookie)) ? sprintf('%s,%s', $existing_cookie, $value) : $value; + + // Set the cookie value; expire after an hour (plenty of time for the user to download the package) + $this->save_cookie($cookie_value); + + $updated_values = self::split_cookie_values($cookie_value); + $download_link = sprintf('<a href="%s">%s</a>', $this->helper->route('phpbb.titania.package_builder.download'), $this->language->lang('PACKAGE_ADDED', count($updated_values['contribs']))); + + $json = $this->create_json_response(true, $updated_values, $download_link); + } + + return $json; + } +} diff --git a/includes/objects/revision.php b/includes/objects/revision.php index c7497913c..aa6d27430 100644 --- a/includes/objects/revision.php +++ b/includes/objects/revision.php @@ -81,6 +81,9 @@ class titania_revision extends \phpbb\titania\entity\database_base /** @var config */ protected $config; + /** @var \phpbb\request\request_interface */ + protected $request; + public function __construct($contrib, $revision_id = false) { // Configure object properties @@ -120,6 +123,7 @@ public function __construct($contrib, $revision_id = false) $this->translations = phpbb::$container->get('phpbb.titania.attachment.operator'); $this->cache = phpbb::$container->get('phpbb.titania.cache'); $this->config = phpbb::$container->get('config'); + $this->request = phpbb::$container->get('request'); } /** @@ -262,7 +266,12 @@ public function display($tpl_block = 'revisions', $show_queue = false, $all_vers 'U_DOWNLOAD' => $this->get_url(), 'U_COLORIZEIT' => $url_colorizeit, 'U_EDIT' => ($this->contrib && ($this->contrib->is_author || $this->contrib->is_active_coauthor || $this->contrib->type->acl_get('moderate'))) ? $this->contrib->get_url('revision', array('page' => 'edit', 'id' => $this->revision_id)) : '', + 'U_PACKAGE_ADD' => $this->controller_helper->route('phpbb.titania.package_builder.add', [ + 'contrib' => $this->contrib_id, + 'revision' => $this->revision_id, + ]), + 'S_PACKAGE' => $this->check_package_builder_link(), 'S_USE_QUEUE' => (titania::$config->use_queue && $this->contrib->type->use_queue) ? true : false, 'S_NEW' => ($this->revision_status == ext::TITANIA_REVISION_NEW) ? true : false, 'S_APPROVED' => ($this->revision_status == ext::TITANIA_REVISION_APPROVED) ? true : false, @@ -292,6 +301,49 @@ public function display($tpl_block = 'revisions', $show_queue = false, $all_vers } } + /** + * Check if the package builder link should be displayed + */ + private function check_package_builder_link() + { + // Check if the types are acceptable for the package manager + $show_package_builder = false; + + if ($this->contrib->type instanceof \phpbb\titania\contribution\extension\type + || $this->contrib->type instanceof \phpbb\titania\contribution\translation\type + || $this->contrib->type instanceof \phpbb\titania\contribution\style\type) + { + // If it's an extension, style or language pack we can show it if... + foreach ($this->phpbb_versions as $supported_version) + { + if ($supported_version['phpbb_version_branch'] === '32') + { + $show_package_builder = true; + break; + } + } + } + + if ($show_package_builder) + { + // Look at the existing package values + $existing_cookie = $this->request->variable($this->config['cookie_name'] . '_' . \phpbb\titania\controller\package_builder::COOKIE_NAME, '', false, \phpbb\request\request_interface::COOKIE); + + if (!empty($existing_cookie)) + { + $split_values = \phpbb\titania\controller\package_builder::split_cookie_values($existing_cookie); + + if (in_array($this->contrib_id, $split_values['contribs'])) + { + // No need to show the link if a revision from this contribution has already been added to the package + $show_package_builder = false; + } + } + } + + return $show_package_builder; + } + /** * Handle some stuff we need when submitting a revision */ diff --git a/language/en/common.php b/language/en/common.php index cf9f50661..e8bb1311d 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -194,6 +194,13 @@ 'ORDER' => 'Order', + 'PACKAGE_ADDED' => array( + 1 => 'Download package with 1 customisation', + 2 => 'Download package with %d customisations', + ), + + 'PACKAGE_ALREADY_ADDED' => 'This contribution has already been added to the package', + 'PAGE_REQUEST_INVALID' => 'The page request is invalid. Please try again.', 'PARENT_CATEGORY' => 'Parent Category', 'PARENT_CONTRIBUTION' => 'Parent Contribution', diff --git a/styles/prosilver/template/common/revision_list.html b/styles/prosilver/template/common/revision_list.html index 5dab5ede5..12882a719 100644 --- a/styles/prosilver/template/common/revision_list.html +++ b/styles/prosilver/template/common/revision_list.html @@ -19,7 +19,7 @@ <div class="list-inner"> {% if revisions.U_DOWNLOAD %}<a href="{{ revisions.U_DOWNLOAD }}" title="{{ lang('DOWNLOAD') }}">{% endif %} {% if revisions.NAME %}{{ revisions.NAME }}{% else %}<em>{{ lang('NO_REVISION_NAME') }}</em>{% endif %} - {% if revisions.U_DOWNLOAD %}</a>{% endif %} + {% if revisions.U_DOWNLOAD %}</a>{% if revisions.S_PACKAGE %} <span class="package-builder-add"><a href="{{ revisions.U_PACKAGE_ADD }}" data-ajax="titania.package.builder.add" data-revision-id="{{ revisions.REVISION_ID }}">[+]</a></span>{% endif %}{% endif %} </div> </dt> <dd class="general revision-status"> diff --git a/styles/prosilver/theme/common.css b/styles/prosilver/theme/common.css index 632097e2f..595c6496f 100644 --- a/styles/prosilver/theme/common.css +++ b/styles/prosilver/theme/common.css @@ -65,6 +65,9 @@ h3.section-name { text-align: left; } +.package-builder-download-prompt { + font-size: 0.8em; +} a.download-button, a.colorizeit-button { box-sizing: border-box;