diff --git a/.travis.yml b/.travis.yml index f296f10230..d6c23da993 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,21 +3,28 @@ php: - 5.3.3 - 5.3 - 5.4 + - 5.5 env: - DB=mysql - DB=postgres +matrix: + allow_failures: + - php: 5.5 + before_script: - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'DROP DATABASE IF EXISTS phpbb_tests;' -U postgres; fi" - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database phpbb_tests;' -U postgres; fi" - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS phpbb_tests;'; fi" + - travis/install-php-extensions.sh - pyrus set auto_discover 1 - pyrus install --force phpunit/DbUnit - phpenv rehash - cd phpBB - php ../composer.phar install --dev - - cd ../ + - cd .. + - sh -c "if [ `php -r "echo (int) version_compare(PHP_VERSION, '5.3.19', '>=');"` = "1" ]; then travis/setup-webserver.sh; fi" script: - phpunit --configuration travis/phpunit-$DB-travis.xml diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 3ccb368665..294a35b615 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -2,11 +2,11 @@ "use strict"; -var img_templates = { +var imgTemplates = { up: $('.template-up-img'), - up_disabled: $('.template-up-img-disabled'), + upDisabled: $('.template-up-img-disabled'), down: $('.template-down-img'), - down_disabled: $('.template-down-img-disabled') + downDisabled: $('.template-down-img-disabled') }; /** @@ -15,20 +15,19 @@ var img_templates = { * an item is moved up. It moves the row up or down, and deactivates / * activates any up / down icons that require it (the ones at the top or bottom). */ -phpbb.add_ajax_callback('row_down', function() { +phpbb.addAjaxCallback('row_down', function() { var el = $(this), tr = el.parents('tr'), - tr_swap = tr.next(); + trSwap = tr.next(); /* * If the element was the first one, we have to: * - Add the up-link to the row we moved * - Remove the up-link on the next row */ - if (tr.is(':first-child')) - { - var up_img = img_templates.up.clone().attr('href', tr.attr('data-up')); - tr.find('.up').html(up_img); + if (tr.is(':first-child')) { + var upImg = imgTemplates.up.clone().attr('href', tr.attr('data-up')); + tr.find('.up').html(upImg); phpbb.ajaxify({ selector: tr.find('.up').children('a'), @@ -36,45 +35,43 @@ phpbb.add_ajax_callback('row_down', function() { overlay: false }); - tr_swap.find('.up').html(img_templates.up_disabled.clone()); + trSwap.find('.up').html(imgTemplates.upDisabled.clone()); } - tr.insertAfter(tr_swap); + tr.insertAfter(trSwap); /* * As well as: * - Remove the down-link on the moved row, if it is now the last row * - Add the down-link to the next row, if it was the last row */ - if (tr.is(':last-child')) - { - tr.find('.down').html(img_templates.down_disabled.clone()); + if (tr.is(':last-child')) { + tr.find('.down').html(imgTemplates.downDisabled.clone()); - var down_img = img_templates.down.clone().attr('href', tr_swap.attr('data-down')); - tr_swap.find('.down').html(down_img); + var downImg = imgTemplates.down.clone().attr('href', trSwap.attr('data-down')); + trSwap.find('.down').html(downImg); phpbb.ajaxify({ - selector: tr_swap.find('.down').children('a'), + selector: trSwap.find('.down').children('a'), callback: 'row_down', overlay: false }); } }); -phpbb.add_ajax_callback('row_up', function() { +phpbb.addAjaxCallback('row_up', function() { var el = $(this), tr = el.parents('tr'), - tr_swap = tr.prev(); + trSwap = tr.prev(); /* * If the element was the last one, we have to: * - Add the down-link to the row we moved * - Remove the down-link on the next row */ - if (tr.is(':last-child')) - { - var down_img = img_templates.down.clone().attr('href', tr.attr('data-down')); - tr.find('.down').html(down_img); + if (tr.is(':last-child')) { + var downImg = imgTemplates.down.clone().attr('href', tr.attr('data-down')); + tr.find('.down').html(downImg); phpbb.ajaxify({ selector: tr.find('.down').children('a'), @@ -82,25 +79,24 @@ phpbb.add_ajax_callback('row_up', function() { overlay: false }); - tr_swap.find('.down').html(img_templates.down_disabled.clone()); + trSwap.find('.down').html(imgTemplates.downDisabled.clone()); } - tr.insertBefore(tr_swap); + tr.insertBefore(trSwap); /* * As well as: * - Remove the up-link on the moved row, if it is now the first row * - Add the up-link to the previous row, if it was the first row */ - if (tr.is(':first-child')) - { - tr.find('.up').html(img_templates.up_disabled.clone()); + if (tr.is(':first-child')) { + tr.find('.up').html(imgTemplates.upDisabled.clone()); - var up_img = img_templates.up.clone().attr('href', tr_swap.attr('data-up')); - tr_swap.find('.up').html(up_img); + var upImg = imgTemplates.up.clone().attr('href', trSwap.attr('data-up')); + trSwap.find('.up').html(upImg); phpbb.ajaxify({ - selector: tr_swap.find('.up').children('a'), + selector: trSwap.find('.up').children('a'), callback: 'row_up', overlay: false }); @@ -112,29 +108,26 @@ phpbb.add_ajax_callback('row_up', function() { * It does this by replacing the text, and replacing all instances of "activate" * in the href with "deactivate", and vice versa. */ -phpbb.add_ajax_callback('activate_deactivate', function(res) { +phpbb.addAjaxCallback('activate_deactivate', function(res) { var el = $(this), - new_href = el.attr('href'); + newHref = el.attr('href'); el.text(res.text); - if (new_href.indexOf('deactivate') !== -1) - { - new_href = new_href.replace('deactivate', 'activate') - } - else - { - new_href = new_href.replace('activate', 'deactivate') + if (newHref.indexOf('deactivate') !== -1) { + newHref = newHref.replace('deactivate', 'activate') + } else { + newHref = newHref.replace('activate', 'deactivate') } - el.attr('href', new_href); + el.attr('href', newHref); }); /** * The removes the parent row of the link or form that triggered the callback, * and is good for stuff like the removal of forums. */ -phpbb.add_ajax_callback('row_delete', function() { +phpbb.addAjaxCallback('row_delete', function() { $(this).parents('tr').remove(); }); @@ -145,8 +138,7 @@ $('[data-ajax]').each(function() { ajax = $this.attr('data-ajax'), fn; - if (ajax !== 'false') - { + if (ajax !== 'false') { fn = (ajax !== 'true') ? ajax : null; phpbb.ajaxify({ selector: this, diff --git a/phpBB/adm/style/timezone.js b/phpBB/adm/style/timezone.js index 4556ea5f94..419d37c34f 100644 --- a/phpBB/adm/style/timezone.js +++ b/phpBB/adm/style/timezone.js @@ -1,11 +1,11 @@ (function($) { // Avoid conflicts with other libraries $('#tz_date').change(function() { - phpbb.timezone_switch_date(false); + phpbb.timezoneSwitchDate(false); }); $(document).ready( - phpbb.timezone_enable_date_selection + phpbb.timezoneEnableDateSelection ); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index 30c0a199bf..16ed04746d 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -1,5 +1,5 @@ var phpbb = {}; -phpbb.alert_time = 100; +phpbb.alertTime = 100; (function($) { // Avoid conflicts with other libraries @@ -12,46 +12,42 @@ var keymap = { }; var dark = $('#darkenwrapper'); -var loading_alert = $('#loadingalert'); +var loadingAlert = $('#loadingalert'); var phpbbAlertTimer = null; /** - * Display a loading screen. + * Display a loading screen * - * @returns object Returns loading_alert. + * @returns object Returns loadingAlert. */ -phpbb.loading_alert = function() { - if (dark.is(':visible')) - { - loading_alert.fadeIn(phpbb.alert_time); - } - else - { - loading_alert.show(); - dark.fadeIn(phpbb.alert_time, function() { +phpbb.loadingAlert = function() { + if (dark.is(':visible')) { + loadingAlert.fadeIn(phpbb.alertTime); + } else { + loadingAlert.show(); + dark.fadeIn(phpbb.alertTime, function() { // Wait five seconds and display an error if nothing has been returned by then. phpbbAlertTimer = setTimeout(function() { - if (loading_alert.is(':visible')) - { + if (loadingAlert.is(':visible')) { phpbb.alert($('#phpbb_alert').attr('data-l-err'), $('#phpbb_alert').attr('data-l-timeout-processing-req')); } }, 5000); }); } - return loading_alert; -} + return loadingAlert; +}; /** * Clear loading alert timeout */ phpbb.clearLoadingTimeout = function() { - if (phpbbAlertTimer != null) { + if (phpbbAlertTimer !== null) { clearTimeout(phpbbAlertTimer); phpbbAlertTimer = null; } -} +}; /** * Display a simple alert similar to JSs native alert(). @@ -78,7 +74,7 @@ phpbb.alert = function(title, msg, fadedark) { div.find('.alert_close').unbind('click'); fade = (typeof fadedark !== 'undefined' && !fadedark) ? div : dark; - fade.fadeOut(phpbb.alert_time, function() { + fade.fadeOut(phpbb.alertTime, function() { div.hide(); }); @@ -101,27 +97,22 @@ phpbb.alert = function(title, msg, fadedark) { e.preventDefault(); }); - if (loading_alert.is(':visible')) - { - loading_alert.fadeOut(phpbb.alert_time, function() { + if (loadingAlert.is(':visible')) { + loadingAlert.fadeOut(phpbb.alertTime, function() { dark.append(div); - div.fadeIn(phpbb.alert_time); + div.fadeIn(phpbb.alertTime); }); - } - else if (dark.is(':visible')) - { + } else if (dark.is(':visible')) { dark.append(div); - div.fadeIn(phpbb.alert_time); - } - else - { + div.fadeIn(phpbb.alertTime); + } else { dark.append(div); div.show(); - dark.fadeIn(phpbb.alert_time); + dark.fadeIn(phpbb.alertTime); } return div; -} +}; /** * Display a simple yes / no box to the user. @@ -144,13 +135,13 @@ phpbb.confirm = function(msg, callback, fadedark) { e.stopPropagation(); }); - var click_handler = function(e) { + var clickHandler = function(e) { var res = this.className === 'button1'; var fade = (typeof fadedark !== 'undefined' && !fadedark && res) ? div : dark; - fade.fadeOut(phpbb.alert_time, function() { + fade.fadeOut(phpbb.alertTime, function() { div.hide(); }); - div.find('input[type="button"]').unbind('click', click_handler); + div.find('input[type="button"]').unbind('click', clickHandler); callback(res); if (e) { @@ -158,11 +149,11 @@ phpbb.confirm = function(msg, callback, fadedark) { e.stopPropagation(); } }; - div.find('input[type="button"]').one('click', click_handler); + div.find('input[type="button"]').one('click', clickHandler); dark.one('click', function(e) { div.find('.alert_close').unbind('click'); - dark.fadeOut(phpbb.alert_time, function() { + dark.fadeOut(phpbb.alertTime, function() { div.hide(); }); callback(false); @@ -185,7 +176,7 @@ phpbb.confirm = function(msg, callback, fadedark) { div.find('.alert_close').one('click', function(e) { var fade = (typeof fadedark !== 'undefined' && fadedark) ? div : dark; - fade.fadeOut(phpbb.alert_time, function() { + fade.fadeOut(phpbb.alertTime, function() { div.hide(); }); callback(false); @@ -193,27 +184,22 @@ phpbb.confirm = function(msg, callback, fadedark) { e.preventDefault(); }); - if (loading_alert.is(':visible')) - { - loading_alert.fadeOut(phpbb.alert_time, function() { + if (loadingAlert.is(':visible')) { + loadingAlert.fadeOut(phpbb.alertTime, function() { dark.append(div); - div.fadeIn(phpbb.alert_time); + div.fadeIn(phpbb.alertTime); }); - } - else if (dark.is(':visible')) - { + } else if (dark.is(':visible')) { dark.append(div); - div.fadeIn(phpbb.alert_time); - } - else - { + div.fadeIn(phpbb.alertTime); + } else { dark.append(div); div.show(); - dark.fadeIn(phpbb.alert_time); + dark.fadeIn(phpbb.alertTime); } return div; -} +}; /** * Turn a querystring into an array. @@ -221,17 +207,16 @@ phpbb.confirm = function(msg, callback, fadedark) { * @argument string string The querystring to parse. * @returns object The object created. */ -phpbb.parse_querystring = function(string) { +phpbb.parseQuerystring = function(string) { var params = {}, i, split; string = string.split('&'); - for (i = 0; i < string.length; i++) - { + for (i = 0; i < string.length; i++) { split = string[i].split('='); params[split[0]] = decodeURIComponent(split[1]); } return params; -} +}; /** @@ -257,14 +242,13 @@ phpbb.ajaxify = function(options) { refresh = options.refresh, callback = options.callback, overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true, - is_form = elements.is('form'), - event_name = is_form ? 'submit' : 'click'; + isForm = elements.is('form'), + eventName = isForm ? 'submit' : 'click'; - elements.bind(event_name, function(event) { + elements.bind(eventName, function(event) { var action, method, data, submit, that = this, $this = $(this); - if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') - { + if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') { return; } @@ -278,80 +262,65 @@ phpbb.ajaxify = function(options) { * * @param object res The object sent back by the server. */ - function return_handler(res) - { + function returnHandler(res) { var alert; phpbb.clearLoadingTimeout(); // Is a confirmation required? - if (typeof res.S_CONFIRM_ACTION === 'undefined') - { + if (typeof res.S_CONFIRM_ACTION === 'undefined') { // If a confirmation is not required, display an alert and call the // callbacks. - if (typeof res.MESSAGE_TITLE !== 'undefined') - { + if (typeof res.MESSAGE_TITLE !== 'undefined') { alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); - } - else - { - dark.fadeOut(phpbb.alert_time); + } else { + dark.fadeOut(phpbb.alertTime); } - if (typeof phpbb.ajax_callbacks[callback] === 'function') - { - phpbb.ajax_callbacks[callback].call(that, res); + if (typeof phpbb.ajaxCallbacks[callback] === 'function') { + phpbb.ajaxCallbacks[callback].call(that, res); } // If the server says to refresh the page, check whether the page should // be refreshed and refresh page after specified time if required. - if (res.REFRESH_DATA) - { - if (typeof refresh === 'function') - { + if (res.REFRESH_DATA) { + if (typeof refresh === 'function') { refresh = refresh(res.REFRESH_DATA.url); - } - else if (typeof refresh !== 'boolean') - { + } else if (typeof refresh !== 'boolean') { refresh = false; } setTimeout(function() { - if (refresh) - { + if (refresh) { window.location = res.REFRESH_DATA.url; } // Hide the alert even if we refresh the page, in case the user // presses the back button. - dark.fadeOut(phpbb.alert_time, function() { + dark.fadeOut(phpbb.alertTime, function() { alert.hide(); }); }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds } - } - else - { + } else { // If confirmation is required, display a diologue to the user. phpbb.confirm(res.MESSAGE_TEXT, function(del) { - if (del) - { - phpbb.loading_alert(); + if (del) { + phpbb.loadingAlert(); data = $('
').serialize(); $.ajax({ url: res.S_CONFIRM_ACTION, type: 'POST', data: data + '&confirm=' + res.YES_VALUE, - success: return_handler, - error: error_handler + success: returnHandler, + error: errorHandler }); } }, false); } } - function error_handler() - { + function errorHandler() { var alert; phpbb.clearLoadingTimeout(); @@ -360,25 +329,21 @@ phpbb.ajaxify = function(options) { // If the element is a form, POST must be used and some extra data must // be taken from the form. - var run_filter = (typeof options.filter === 'function'); + var runFilter = (typeof options.filter === 'function'); - if (is_form) - { + if (isForm) { action = $this.attr('action').replace('&', '&'); data = $this.serializeArray(); method = $this.attr('method') || 'GET'; - if ($this.find('input[type="submit"][data-clicked]')) - { + if ($this.find('input[type="submit"][data-clicked]')) { submit = $this.find('input[type="submit"][data-clicked]'); data.push({ name: submit.attr('name'), value: submit.val() }); } - } - else - { + } else { action = this.href; data = null; method = 'GET'; @@ -386,28 +351,27 @@ phpbb.ajaxify = function(options) { // If filter function returns false, cancel the AJAX functionality, // and return true (meaning that the HTTP request will be sent normally). - if (run_filter && !options.filter.call(this, data)) - { + if (runFilter && !options.filter.call(this, data)) { return; } if (overlay && (typeof $this.attr('data-overlay') === 'undefined' || $this.attr('data-overlay') == 'true')) { - phpbb.loading_alert(); + phpbb.loadingAlert(); } $.ajax({ url: action, type: method, data: data, - success: return_handler, - error: error_handler + success: returnHandler, + error: errorHandler }); event.preventDefault(); }); - if (is_form) { + if (isForm) { elements.find('input:submit').click(function () { var $this = $(this); @@ -417,14 +381,14 @@ phpbb.ajaxify = function(options) { } return this; -} +}; /** * Hide the optgroups that are not the selected timezone * -* @param bool keep_selection Shall we keep the value selected, or shall the user be forced to repick one. +* @param bool keepSelection Shall we keep the value selected, or shall the user be forced to repick one. */ -phpbb.timezone_switch_date = function(keep_selection) { +phpbb.timezoneSwitchDate = function(keepSelection) { if ($('#timezone_copy').length == 0) { // We make a backup of the original dropdown, so we can remove optgroups // instead of setting display to none, because IE and chrome will not @@ -448,30 +412,30 @@ phpbb.timezone_switch_date = function(keep_selection) { if ($("#timezone > optgroup[label='" + $('#tz_date').val() + "'] > option").size() == 1) { // If there is only one timezone for the selected date, we just select that automatically. $("#timezone > optgroup[label='" + $('#tz_date').val() + "'] > option:first").attr('selected', true); - keep_selection = true; + keepSelection = true; } - if (typeof keep_selection !== 'undefined' && !keep_selection) { + if (typeof keepSelection !== 'undefined' && !keepSelection) { var timezoneOptions = $('#timezone > optgroup option'); if (timezoneOptions.filter(':selected').length <= 0) { timezoneOptions.filter(':first').attr('selected', true); } } -} +}; /** * Display the date/time select */ -phpbb.timezone_enable_date_selection = function() { +phpbb.timezoneEnableDateSelection = function() { $('#tz_select_date').css('display', 'block'); -} +}; /** * Preselect a date/time or suggest one, if it is not picked. * -* @param bool force_selector Shall we select the suggestion? +* @param bool forceSelector Shall we select the suggestion? */ -phpbb.timezone_preselect_select = function(force_selector) { +phpbb.timezonePreselectSelect = function(forceSelector) { // The offset returned here is in minutes and negated. // http://www.w3schools.com/jsref/jsref_getTimezoneOffset.asp @@ -500,21 +464,21 @@ phpbb.timezone_preselect_select = function(force_selector) { } var prefix = 'GMT' + sign + hours + ':' + minutes; - var prefix_length = prefix.length; - var selector_options = $('#tz_date > option'); + var prefixLength = prefix.length; + var selectorOptions = $('#tz_date > option'); - for (var i = 0; i < selector_options.length; ++i) { - var option = selector_options[i]; + for (var i = 0; i < selectorOptions.length; ++i) { + var option = selectorOptions[i]; - if (option.value.substring(0, prefix_length) == prefix) { - if ($('#tz_date').val() != option.value && !force_selector) { + if (option.value.substring(0, prefixLength) == prefix) { + if ($('#tz_date').val() != option.value && !forceSelector) { // We do not select the option for the user, but notify him, // that we would suggest a different setting. - phpbb.timezone_switch_date(true); + phpbb.timezoneSwitchDate(true); $('#tz_select_date_suggest').css('display', 'inline'); } else { option.selected = true; - phpbb.timezone_switch_date(!force_selector); + phpbb.timezoneSwitchDate(!forceSelector); $('#tz_select_date_suggest').css('display', 'none'); } @@ -526,9 +490,9 @@ phpbb.timezone_preselect_select = function(force_selector) { return; } } -} +}; -phpbb.ajax_callbacks = {}; +phpbb.ajaxCallbacks = {}; /** * Adds an AJAX callback to be used by phpbb.ajaxify. @@ -538,14 +502,12 @@ phpbb.ajax_callbacks = {}; * @param string id The name of the callback. * @param function callback The callback to be called. */ -phpbb.add_ajax_callback = function(id, callback) -{ - if (typeof callback === 'function') - { - phpbb.ajax_callbacks[id] = callback; +phpbb.addAjaxCallback = function(id, callback) { + if (typeof callback === 'function') { + phpbb.ajaxCallbacks[id] = callback; } return this; -} +}; /** @@ -553,14 +515,14 @@ phpbb.add_ajax_callback = function(id, callback) * the alt-text data attribute, and replaces the text in the attribute with the * current text so that the process can be repeated. */ -phpbb.add_ajax_callback('alt_text', function() { +phpbb.addAjaxCallback('alt_text', function() { var el = $(this), - alt_text; + altText; - alt_text = el.attr('data-alt-text'); + altText = el.attr('data-alt-text'); el.attr('data-alt-text', el.text()); - el.attr('title', alt_text); - el.text(alt_text); + el.attr('title', altText); + el.text(altText); }); /** @@ -572,28 +534,28 @@ phpbb.add_ajax_callback('alt_text', function() { * Additionally it replaces the class of the link's parent * and changes the link itself. */ -phpbb.add_ajax_callback('toggle_link', function() { +phpbb.addAjaxCallback('toggle_link', function() { var el = $(this), - toggle_text, - toggle_url, - toggle_class; + toggleText, + toggleUrl, + toggleClass; // Toggle link text - toggle_text = el.attr('data-toggle-text'); + toggleText = el.attr('data-toggle-text'); el.attr('data-toggle-text', el.text()); - el.attr('title', toggle_text); - el.text(toggle_text); + el.attr('title', toggleText); + el.text(toggleText); // Toggle link url - toggle_url = el.attr('data-toggle-url'); + toggleUrl = el.attr('data-toggle-url'); el.attr('data-toggle-url', el.attr('href')); - el.attr('href', toggle_url); + el.attr('href', toggleUrl); // Toggle class of link parent - toggle_class = el.attr('data-toggle-class'); + toggleClass = el.attr('data-toggle-class'); el.attr('data-toggle-class', el.parent().attr('class')); - el.parent().attr('class', toggle_class); + el.parent().attr('class', toggleClass); }); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/config/migrator.yml b/phpBB/config/migrator.yml new file mode 100644 index 0000000000..999a2d41a3 --- /dev/null +++ b/phpBB/config/migrator.yml @@ -0,0 +1,49 @@ +services: + migrator: + class: phpbb_db_migrator + arguments: + - @config + - @dbal.conn + - @dbal.tools + - %tables.migrations% + - %core.root_path% + - %core.php_ext% + - %core.table_prefix% + - @migrator.tool_collection + + migrator.tool_collection: + class: phpbb_di_service_collection + arguments: + - @service_container + tags: + - { name: service_collection, tag: migrator.tool } + + migrator.tool.config: + class: phpbb_db_migration_tool_config + arguments: + - @config + tags: + - { name: migrator.tool } + + migrator.tool.module: + class: phpbb_db_migration_tool_module + arguments: + - @dbal.conn + - @cache + - @user + - %core.root_path% + - %core.php_ext% + - %tables.modules% + tags: + - { name: migrator.tool } + + migrator.tool.permission: + class: phpbb_db_migration_tool_permission + arguments: + - @dbal.conn + - @cache + - @auth + - %core.root_path% + - %core.php_ext% + tags: + - { name: migrator.tool } diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 6bde65ec49..645dabdca0 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -1,6 +1,7 @@ imports: - { resource: tables.yml } - { resource: cron_tasks.yml } + - { resource: migrator.yml } - { resource: avatars.yml } services: @@ -102,6 +103,12 @@ services: calls: - [sql_connect, [%dbal.dbhost%, %dbal.dbuser%, %dbal.dbpasswd%, %dbal.dbname%, %dbal.dbport%, false, %dbal.new_link%]] + dbal.tools: + file: %core.root_path%includes/db/db_tools.%core.php_ext% + class: phpbb_db_tools + arguments: + - @dbal.conn + event.subscriber_loader: class: phpbb_event_extension_subscriber_loader arguments: diff --git a/phpBB/config/tables.yml b/phpBB/config/tables.yml index cfc6dbcfed..dd53417b1c 100644 --- a/phpBB/config/tables.yml +++ b/phpBB/config/tables.yml @@ -1,3 +1,5 @@ parameters: tables.config: %core.table_prefix%config tables.ext: %core.table_prefix%ext + tables.migrations: %core.table_prefix%migrations + tables.modules: %core.table_prefix%modules diff --git a/phpBB/develop/create_schema_files.php b/phpBB/develop/create_schema_files.php index 895a945e4e..df721d4907 100644 --- a/phpBB/develop/create_schema_files.php +++ b/phpBB/develop/create_schema_files.php @@ -1273,6 +1273,19 @@ function get_schema_struct() ), ); + $schema_data['phpbb_migrations'] = array( + 'COLUMNS' => array( + 'migration_name' => array('VCHAR', ''), + 'migration_depends_on' => array('TEXT', ''), + 'migration_schema_done' => array('BOOL', 0), + 'migration_data_done' => array('BOOL', 0), + 'migration_data_state' => array('TEXT', ''), + 'migration_start_time' => array('TIMESTAMP', 0), + 'migration_end_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => 'migration_name', + ); + $schema_data['phpbb_modules'] = array( 'COLUMNS' => array( 'module_id' => array('UINT', NULL, 'auto_increment'), @@ -1520,18 +1533,21 @@ function get_schema_struct() $schema_data['phpbb_reports'] = array( 'COLUMNS' => array( - 'report_id' => array('UINT', NULL, 'auto_increment'), - 'reason_id' => array('USINT', 0), - 'post_id' => array('UINT', 0), - 'pm_id' => array('UINT', 0), - 'user_id' => array('UINT', 0), - 'user_notify' => array('BOOL', 0), - 'report_closed' => array('BOOL', 0), - 'report_time' => array('TIMESTAMP', 0), - 'report_text' => array('MTEXT_UNI', ''), - 'reported_post_text' => array('MTEXT_UNI', ''), - 'reported_post_uid' => array('VCHAR:8', ''), - 'reported_post_bitfield' => array('VCHAR:255', ''), + 'report_id' => array('UINT', NULL, 'auto_increment'), + 'reason_id' => array('USINT', 0), + 'post_id' => array('UINT', 0), + 'pm_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'user_notify' => array('BOOL', 0), + 'report_closed' => array('BOOL', 0), + 'report_time' => array('TIMESTAMP', 0), + 'report_text' => array('MTEXT_UNI', ''), + 'reported_post_text' => array('MTEXT_UNI', ''), + 'reported_post_uid' => array('VCHAR:8', ''), + 'reported_post_bitfield' => array('VCHAR:255', ''), + 'reported_post_enable_magic_url' => array('BOOL', 1), + 'reported_post_enable_smilies' => array('BOOL', 1), + 'reported_post_enable_bbcode' => array('BOOL', 1) ), 'PRIMARY_KEY' => 'report_id', 'KEYS' => array( diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index 25e199ab32..92fd466214 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -400,7 +400,7 @@ class acp_groups foreach ($test_variables as $test => $type) { - if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || in_array($test, $set_attributes))) + if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0 || in_array($test, $set_attributes))) { settype($submit_ary[$test], $type); $group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test]; diff --git a/phpBB/includes/captcha/captcha_non_gd.php b/phpBB/includes/captcha/captcha_non_gd.php index c2b97423e6..bb5067cafa 100644 --- a/phpBB/includes/captcha/captcha_non_gd.php +++ b/phpBB/includes/captcha/captcha_non_gd.php @@ -118,7 +118,7 @@ class captcha $new_line = ''; $end = strlen($scanline) - ceil($width/2); - for ($i = floor($width/2); $i < $end; $i++) + for ($i = (int) floor($width / 2); $i < $end; $i++) { $pixel = ord($scanline{$i}); diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 68af41ab20..68c96a2759 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -237,6 +237,7 @@ define('ICONS_TABLE', $table_prefix . 'icons'); define('LANG_TABLE', $table_prefix . 'lang'); define('LOG_TABLE', $table_prefix . 'log'); define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); +define('MIGRATIONS_TABLE', $table_prefix . 'migrations'); define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); define('MODULES_TABLE', $table_prefix . 'modules'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php index 2bb016cebd..e8c26fa502 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -345,6 +345,17 @@ class phpbb_db_tools } } + /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + /** * Gets a list of tables in the database. * @@ -674,6 +685,8 @@ class phpbb_db_tools * Handle passed database update array. * Expected structure... * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables * change_columns: Column changes (only type, not name) * add_columns: Add columns to a table * drop_keys: Dropping keys @@ -1817,6 +1830,22 @@ class phpbb_db_tools case 'mssql': case 'mssqlnative': + // remove default cosntraints first + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) + SET @drop_default_name = + (SELECT so.name FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')) + IF @drop_default_name <> '' + BEGIN + SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' + EXEC(@cmd) + END"; $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; break; diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php new file mode 100644 index 0000000000..ffdcd97780 --- /dev/null +++ b/phpBB/includes/db/migration/exception.php @@ -0,0 +1,55 @@ +parameters = $parameters; + } + + /** + * Output the error as a string + * + * @return string + */ + public function __toString() + { + return $this->message . ': ' . var_export($this->parameters, true); + } +} diff --git a/phpBB/includes/db/migration/migration.php b/phpBB/includes/db/migration/migration.php new file mode 100644 index 0000000000..5f14a6953c --- /dev/null +++ b/phpBB/includes/db/migration/migration.php @@ -0,0 +1,190 @@ +sql_query() */ + protected $queries = array(); + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_db_driver $db + * @param phpbb_db_tools $db_tools + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $table_prefix + */ + public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->table_prefix = $table_prefix; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->errors = array(); + } + + /** + * Defines other migrations to be applied first + * + * @return array An array of migration class names + */ + static public function depends_on() + { + return array(); + } + + /** + * Allows you to check if the migration is effectively installed (entirely optional) + * + * This is checked when a migration is installed. If true is returned, the migration will be set as + * installed without performing the database changes. + * This function is intended to help moving to migrations from a previous database updater, where some + * migrations may have been installed already even though they are not yet listed in the migrations table. + * + * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + */ + public function effectively_installed() + { + return false; + } + + /** + * Updates the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function update_schema() + { + return array(); + } + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function revert_schema() + { + return array(); + } + + /** + * Updates data by returning a list of instructions to be executed + * + * @return array Array of data update instructions + */ + public function update_data() + { + return array(); + } + + /** + * Reverts data by returning a list of instructions to be executed + * + * @return array Array of data instructions that will be performed on revert + * NOTE: calls to tools (such as config.add) are automatically reverted when + * possible, so you should not attempt to revert those, this is mostly for + * otherwise unrevertable calls (custom functions for example) + */ + public function revert_data() + { + return array(); + } + + /** + * Wrapper for running queries to generate user feedback on updates + * + * @param string $sql SQL query to run on the database + * @return mixed Query result from db->sql_query() + */ + protected function sql_query($sql) + { + $this->queries[] = $sql; + + $this->db->sql_return_on_error(true); + + if ($sql === 'begin') + { + $result = $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $result = $this->db->sql_transaction('commit'); + } + else + { + $result = $this->db->sql_query($sql); + if ($this->db->sql_error_triggered) + { + $this->errors[] = array( + 'sql' => $this->db->sql_error_sql, + 'code' => $this->db->sql_error_returned, + ); + } + } + + $this->db->sql_return_on_error(false); + + return $result; + } + + /** + * Get the list of queries run + * + * @return array + */ + public function get_queries() + { + return $this->queries; + } +} diff --git a/phpBB/includes/db/migration/tool/config.php b/phpBB/includes/db/migration/tool/config.php new file mode 100644 index 0000000000..d9cc20053e --- /dev/null +++ b/phpBB/includes/db/migration/tool/config.php @@ -0,0 +1,150 @@ +config = $config; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'config'; + } + + /** + * Add a config setting. + * + * @param string $config_name The name of the config setting + * you would like to add + * @param mixed $config_value The value of the config setting + * @param bool $is_dynamic True if it is dynamic (changes very often) + * and should not be stored in the cache, false if not. + * @return null + */ + public function add($config_name, $config_value, $is_dynamic = false) + { + if (isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_ALREADY_EXISTS', $config_name); + } + + $this->config->set($config_name, $config_value, !$is_dynamic); + } + + /** + * Update an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update($config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set($config_name, $config_value); + } + + /** + * Update a config setting if the first argument equal to the + * current config value + * + * @param string $compare If equal to the current config value, will be + * updated to the new config value, otherwise not + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update_if_equals($compare, $config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set_atomic($config_name, $compare, $config_value); + } + + /** + * Remove an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to remove + * @return null + */ + public function remove($config_name) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->delete($config_name); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'update_if_equals': + $call = 'update_if_equals'; + + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[2], + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/includes/db/migration/tool/interface.php b/phpBB/includes/db/migration/tool/interface.php new file mode 100644 index 0000000000..ced53b2023 --- /dev/null +++ b/phpBB/includes/db/migration/tool/interface.php @@ -0,0 +1,33 @@ +db = $db; + $this->cache = $cache; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->modules_table = $modules_table; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'module'; + } + + /** + * Module Exists + * + * Check if a module exists + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module_id|module_langname you would like to + * check for to see if it exists + * @return bool true/false if module exists + */ + public function exists($class, $parent, $module) + { + // the main root directory should return true + if (!$module) + { + return true; + } + + $parent_sql = ''; + if ($parent !== false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + return false; + } + + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql + AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if ($module_id) + { + return true; + } + + return false; + } + + /** + * Module Add + * + * Add a new module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string $parent The parent module_id|module_langname (0 for no parent) + * @param array $data an array of the data on the new module. + * This can be setup in two different ways. + * 1. The "manual" way. For inserting a category or one at a time. + * It will be merged with the base array shown a bit below, + * but at the least requires 'module_langname' to be sent, and, + * if you want to create a module (instead of just a category) you must + * send module_basename and module_mode. + * array( + * 'module_enabled' => 1, + * 'module_display' => 1, + * 'module_basename' => '', + * 'module_class' => $class, + * 'parent_id' => (int) $parent, + * 'module_langname' => '', + * 'module_mode' => '', + * 'module_auth' => '', + * ) + * 2. The "automatic" way. For inserting multiple at a time based on the + * specs in the info file for the module(s). For this to work the + * modules must be correctly setup in the info file. + * An example follows (this would insert the settings, log, and flag + * modes from the includes/acp/info/acp_asacp.php file): + * array( + * 'module_basename' => 'asacp', + * 'modes' => array('settings', 'log', 'flag'), + * ) + * Optionally you may not send 'modes' and it will insert all of the + * modules in that info file. + * @param string|bool $include_path If you would like to use a custom include + * path, specify that here + * @return null + */ + public function add($class, $parent = 0, $data = array(), $include_path = false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + // allow sending the name as a string in $data to create a category + if (!is_array($data)) + { + $data = array('module_langname' => $data); + } + + if (!isset($data['module_langname'])) + { + // The "automatic" way + $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; + $basename = str_replace(array('/', '\\'), '', $basename); + $class = str_replace(array('/', '\\'), '', $class); + + $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path; + $info_file = "$class/info/$basename.{$this->php_ext}"; + + // The manual and automatic ways both failed... + if (!file_exists($include_path . $info_file)) + { + throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $info_file); + } + + $classname = "{$basename}_info"; + + if (!class_exists($classname)) + { + include($include_path . $info_file); + } + + $info = new $classname; + $module = $info->module(); + unset($info); + + $result = ''; + foreach ($module['modes'] as $mode => $module_info) + { + if (!isset($data['modes']) || in_array($mode, $data['modes'])) + { + $new_module = array( + 'module_basename' => $basename, + 'module_langname' => $module_info['title'], + 'module_mode' => $mode, + 'module_auth' => $module_info['auth'], + 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true, + 'before' => (isset($module_info['before'])) ? $module_info['before'] : false, + 'after' => (isset($module_info['after'])) ? $module_info['after'] : false, + ); + + // Run the "manual" way with the data we've collected. + $this->add($class, $parent, $new_module); + } + } + + return; + } + + // The "manual" way + $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); + add_log('admin', 'LOG_MODULE_ADD', $module_log_name); + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + throw new phpbb_db_migration_exception('MODULE_PARENT_NOT_EXIST', $parent); + } + + $parent = $data['parent_id'] = $module_id; + } + else if (!$this->exists($class, false, $parent)) + { + throw new phpbb_db_migration_exception('MODULE_PARENT_NOT_EXIST', $parent); + } + + if ($this->exists($class, $parent, $data['module_langname'])) + { + throw new phpbb_db_migration_exception('MODULE_ALREADY_EXIST', $data['module_langname']); + } + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + $this->user->add_lang('acp/modules'); + } + $acp_modules = new acp_modules(); + + $module_data = array( + 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, + 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, + 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '', + 'module_class' => $class, + 'parent_id' => (int) $parent, + 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '', + 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', + 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', + ); + $result = $acp_modules->update_module_data($module_data, true); + + // update_module_data can either return a string or an empty array... + if (is_string($result)) + { + // Error + throw new phpbb_db_migration_exception('MODULE_ERROR', $result); + } + else + { + // Success + + // Move the module if requested above/below an existing one + if (isset($data['before']) && $data['before']) + { + $sql = 'SELECT left_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['before']) . "'"; + $this->db->sql_query($sql); + $to_left = (int) $this->db->sql_fetchfield('left_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_left + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = $to_left, right_id = " . ($to_left + 1) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + else if (isset($data['after']) && $data['after']) + { + $sql = 'SELECT right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['after']) . "'"; + $this->db->sql_query($sql); + $to_right = (int) $this->db->sql_fetchfield('right_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_right + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . ' + SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + } + + // Clear the Modules Cache + $this->cache->destroy("_modules_$class"); + } + + /** + * Module Remove + * + * Remove a module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module id|module_langname + * @param string|bool $include_path If you would like to use a custom include path, + * specify that here + * @return null + */ + public function remove($class, $parent = 0, $module = '', $include_path = false) + { + // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto + if (is_array($module)) + { + if (isset($module['module_langname'])) + { + // Manual Method + return $this->remove($class, $parent, $module['module_langname'], $include_path); + } + + // Failed. + if (!isset($module['module_basename'])) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST'); + } + + // Automatic method + $basename = str_replace(array('/', '\\'), '', $module['module_basename']); + $class = str_replace(array('/', '\\'), '', $class); + + $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path; + $info_file = "$class/info/$basename.{$this->php_ext}"; + + if (!file_exists($include_path . $info_file)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $info_file); + } + + $classname = "{$basename}_info"; + + if (!class_exists($classname)) + { + include($include_path . $info_file); + } + + $info = new $classname; + $module_info = $info->module(); + unset($info); + + foreach ($module_info['modes'] as $mode => $info) + { + if (!isset($module['modes']) || in_array($mode, $module['modes'])) + { + $this->remove($class, $parent, $info['title']) . '