diff options
author | Joas Schilling <nickvergessen@gmx.de> | 2013-03-03 20:06:52 +0100 |
---|---|---|
committer | Joas Schilling <nickvergessen@gmx.de> | 2013-03-03 20:06:52 +0100 |
commit | 42c062cedf0b451141e321dd4e7cac84905bcac6 (patch) | |
tree | 7c6fa2647ebbd56e909356de4a27551dff0be626 /phpBB | |
parent | 3cee183dd4882fdd00deb911bab69287d7754135 (diff) | |
parent | bee4f8d8185d4ff5278be758db4ea4a814f09b4f (diff) | |
download | forums-42c062cedf0b451141e321dd4e7cac84905bcac6.tar forums-42c062cedf0b451141e321dd4e7cac84905bcac6.tar.gz forums-42c062cedf0b451141e321dd4e7cac84905bcac6.tar.bz2 forums-42c062cedf0b451141e321dd4e7cac84905bcac6.tar.xz forums-42c062cedf0b451141e321dd4e7cac84905bcac6.zip |
Merge branch 'develop' of https://github.com/phpbb/phpbb3 into feature/softdelete-1-permission
* 'develop' of https://github.com/phpbb/phpbb3: (480 commits)
[ticket/6723] Show info that message has been deleted before delivery
[ticket/11385] Fix issue with migration module tool not getting extension module info
[ticket/11386] Fix failing tests from constructor changes
[ticket/11386] Fix circular reference error & serialize error
[ticket/11386] Remove tests that check if finder cache is working
[ticket/11386] Forgot to get the migration classes
[ticket/11386] Update tests with new constructors for ext.manager/migrator
[ticket/11386] Use finder to find migration files
[ticket/11363] Fix to make get_module_infos get from all extensions
[ticket/11381] Make finder able to search in all available extensions
[ticket/11103] Revert whitespace changes
[ticket/11103] Few more minor language things
[ticket/11103] Don't call generate_board_url many times
[ticket/11103] Case time in queries as an int
[ticket/11103] Fix effectively installed check
[ticket/11103] Remove padding from notifications for now.
[ticket/11363] Fix a couple bugs and throw errors if the file not found
[ticket/11372] Migrator should only check if effectively installed if not
[ticket/11363] Load module info files for extensions too
[ticket/11103] Notifications Migration file
...
Conflicts:
phpBB/includes/functions_posting.php
phpBB/includes/mcp/mcp_queue.php
phpBB/includes/search/fulltext_mysql.php
phpBB/includes/search/fulltext_native.php
phpBB/includes/search/fulltext_postgres.php
phpBB/includes/search/fulltext_sphinx.php
phpBB/install/database_update.php
phpBB/styles/prosilver/template/ajax.js
Diffstat (limited to 'phpBB')
231 files changed, 16427 insertions, 5762 deletions
diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php index 0f84af6f9e..564a19ce64 100644 --- a/phpBB/adm/index.php +++ b/phpBB/adm/index.php @@ -42,7 +42,6 @@ if (!$auth->acl_get('a_')) // We define the admin variables now, because the user is now able to use the admin related features... define('IN_ADMIN', true); -$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : './'; // Some oft used variables $safe_mode = (@ini_get('safe_mode') == '1' || strtolower(@ini_get('safe_mode')) === 'on') ? true : false; diff --git a/phpBB/adm/style/acp_ext_enable.html b/phpBB/adm/style/acp_ext_enable.html index 3f7be2c847..35585207eb 100644 --- a/phpBB/adm/style/acp_ext_enable.html +++ b/phpBB/adm/style/acp_ext_enable.html @@ -7,7 +7,13 @@ <p>{L_EXTENSIONS_EXPLAIN}</p> <p>{L_ENABLE_EXPLAIN}</p> - <!-- IF PRE --> + <!-- IF MIGRATOR_ERROR --> + <div class="errorbox"> + <p><strong>{L_MIGRATION_EXCEPTION_ERROR}</strong></p> + <p>{MIGRATOR_ERROR}</p> + <p><a href="{U_RETURN}">{L_RETURN}</a></p> + </div> + <!-- ELSEIF PRE --> <div class="errorbox"> <p>{L_ENABLE_CONFIRM}</p> </div> diff --git a/phpBB/adm/style/acp_ext_purge.html b/phpBB/adm/style/acp_ext_purge.html index 00a58721cb..94bef82ca5 100644 --- a/phpBB/adm/style/acp_ext_purge.html +++ b/phpBB/adm/style/acp_ext_purge.html @@ -7,7 +7,13 @@ <p>{L_EXTENSIONS_EXPLAIN}</p> <p>{L_PURGE_EXPLAIN}</p> - <!-- IF PRE --> + <!-- IF MIGRATOR_ERROR --> + <div class="errorbox"> + <p><strong>{L_MIGRATION_EXCEPTION_ERROR}</strong></p> + <p>{MIGRATOR_ERROR}</p> + <p><a href="{U_RETURN}">{L_RETURN}</a></p> + </div> + <!-- ELSEIF PRE --> <div class="errorbox"> <p>{L_PURGE_CONFIRM}</p> </div> 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/install_header.html b/phpBB/adm/style/install_header.html index 6826654ded..5631b83e17 100644 --- a/phpBB/adm/style/install_header.html +++ b/phpBB/adm/style/install_header.html @@ -5,7 +5,7 @@ <!-- IF META -->{META}<!-- ENDIF --> <title>{PAGE_TITLE}</title> -<link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" /> +<link href="{T_TEMPLATE_PATH}/admin.css" rel="stylesheet" type="text/css" media="screen" /> <script type="text/javascript"> // <![CDATA[ @@ -49,7 +49,7 @@ function dE(n, s, type) </form> <!-- ENDIF --> </div> - + <div id="page-body"> <div id="tabs"> <ul> @@ -73,5 +73,5 @@ function dE(n, s, type) <!-- END l_block2 --> </ul> </div> - + <div id="main" class="install-body"> diff --git a/phpBB/adm/style/install_update_diff.html b/phpBB/adm/style/install_update_diff.html index ecdc40e157..1f30fe4d13 100644 --- a/phpBB/adm/style/install_update_diff.html +++ b/phpBB/adm/style/install_update_diff.html @@ -5,7 +5,7 @@ <!-- IF META -->{META}<!-- ENDIF --> <title>{PAGE_TITLE}</title> -<link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" /> +<link href="{T_TEMPLATE_PATH}/admin.css" rel="stylesheet" type="text/css" media="screen" /> <script type="text/javascript"> // <![CDATA[ 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/adm/swatch.php b/phpBB/adm/swatch.php index c01651e0f0..3ae38d0d8b 100644 --- a/phpBB/adm/swatch.php +++ b/phpBB/adm/swatch.php @@ -21,8 +21,6 @@ $user->session_begin(false); $auth->acl($user->data); $user->setup(); -$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : './'; - // Set custom template for admin area $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index c18bec7035..9909359c2c 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,35 +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. - setTimeout(function() { - if (loading_alert.is(':visible')) - { + phpbbAlertTimer = setTimeout(function() { + 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) { + clearTimeout(phpbbAlertTimer); + phpbbAlertTimer = null; + } +}; /** * Display a simple alert similar to JSs native alert(). @@ -67,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(); }); @@ -90,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. @@ -133,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) { @@ -147,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); @@ -174,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); @@ -182,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. @@ -210,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; -} +}; /** @@ -246,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; } @@ -267,104 +262,88 @@ 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 = $('<form>' + res.S_HIDDEN_FIELDS + '</form>').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(); alert = phpbb.alert(dark.attr('data-ajax-error-title'), dark.attr('data-ajax-error-text')); } // 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'; @@ -372,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); @@ -403,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 @@ -434,27 +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) { - $('#timezone > option:first').attr('selected', true); + 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 @@ -483,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'); } @@ -509,9 +490,22 @@ phpbb.timezone_preselect_select = function(force_selector) { return; } } -} +}; -phpbb.ajax_callbacks = {}; +// Toggle notification list +$('#notification_list_button').click(function(e) { + $('#notification_list').toggle(); + e.preventDefault(); +}); +$('#phpbb').click(function(e) { + var target = $(e.target); + + if (!target.is('#notification_list') && !target.is('#notification_list_button') && !target.parents().is('#notification_list')) { + $('#notification_list').hide(); + } +}); + +phpbb.ajaxCallbacks = {}; /** * Adds an AJAX callback to be used by phpbb.ajaxify. @@ -521,14 +515,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; -} +}; /** @@ -536,14 +528,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); }); /** @@ -555,28 +547,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/common.php b/phpBB/common.php index add0b07726..f502d37c8f 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -63,6 +63,10 @@ if (!defined('PHPBB_INSTALLED')) exit; } +// 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/class_loader.' . $phpEx); @@ -83,18 +87,7 @@ $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_pat $phpbb_class_loader_ext->register(); // Set up container -$phpbb_container = phpbb_create_dumped_container_unless_debug( - array( - new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), - new phpbb_di_extension_core($phpbb_root_path), - ), - array( - new phpbb_di_pass_collection_pass(), - new phpbb_di_pass_kernel_pass(), - ), - $phpbb_root_path, - $phpEx -); +$phpbb_container = phpbb_create_default_container($phpbb_root_path, $phpEx); $phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); @@ -127,8 +120,9 @@ $phpbb_style = $phpbb_container->get('style'); // Add own hook handler require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('phpbb_template', 'display'))); +$phpbb_hook_finder = $phpbb_container->get('hook_finder'); -foreach ($cache->obtain_hooks() as $hook) +foreach ($phpbb_hook_finder->find() as $hook) { @include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx); } diff --git a/phpBB/config/migrator.yml b/phpBB/config/migrator.yml new file mode 100644 index 0000000000..42445ef9bf --- /dev/null +++ b/phpBB/config/migrator.yml @@ -0,0 +1,51 @@ +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 + calls: + - [set_extension_manager, [@ext.manager]] + + 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/notifications.yml b/phpBB/config/notifications.yml new file mode 100644 index 0000000000..60aa63a854 --- /dev/null +++ b/phpBB/config/notifications.yml @@ -0,0 +1,314 @@ +services: + notification.type_collection: + class: phpbb_di_service_collection + arguments: + - @service_container + tags: + - { name: service_collection, tag: notification.type } + + notification.method_collection: + class: phpbb_di_service_collection + arguments: + - @service_container + tags: + - { name: service_collection, tag: notification.method } + + notification.type.approve_post: + class: phpbb_notification_type_approve_post + scope: prototype # scope MUST be prototype for this to work! # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.approve_topic: + class: phpbb_notification_type_approve_topic + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.bookmark: + class: phpbb_notification_type_bookmark + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.disapprove_post: + class: phpbb_notification_type_disapprove_post + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.disapprove_topic: + class: phpbb_notification_type_disapprove_topic + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.pm: + class: phpbb_notification_type_pm + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.post: + class: phpbb_notification_type_post + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.post_in_queue: + class: phpbb_notification_type_post_in_queue + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.quote: + class: phpbb_notification_type_quote + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.report_pm: + class: phpbb_notification_type_report_pm + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.report_pm_closed: + class: phpbb_notification_type_report_pm_closed + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.report_post: + class: phpbb_notification_type_report_post + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.report_post_closed: + class: phpbb_notification_type_report_post + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.topic: + class: phpbb_notification_type_topic + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.type.topic_in_queue: + class: phpbb_notification_type_topic_in_queue + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + tags: + - { name: notification.type } + + notification.method.email: + class: phpbb_notification_method_email + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + tags: + - { name: notification.method } + + notification.method.jabber: + class: phpbb_notification_method_jabber + scope: prototype # scope MUST be prototype for this to work! + arguments: + - @user_loader + - @dbal.conn + - @cache.driver + - @user + - @auth + - @config + - %core.root_path% + - %core.php_ext% + tags: + - { name: notification.method } diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index febdc5acb8..250e4a782b 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -1,6 +1,8 @@ imports: - { resource: tables.yml } - { resource: cron_tasks.yml } + - { resource: notifications.yml } + - { resource: migrator.yml } services: auth: @@ -10,6 +12,10 @@ services: class: phpbb_cache_service arguments: - @cache.driver + - @config + - @dbal.conn + - %core.root_path% + - %core.php_ext% cache.driver: class: %cache.driver.class% @@ -90,6 +96,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: @@ -101,12 +113,15 @@ services: ext.manager: class: phpbb_extension_manager arguments: + - @service_container - @dbal.conn - @config - %tables.ext% - %core.root_path% - .%core.php_ext% - @cache.driver + calls: + - [set_migrator, [@migrator]] ext.finder: class: phpbb_extension_finder @@ -123,6 +138,13 @@ services: - @dispatcher - @controller.resolver + hook_finder: + class: phpbb_hook_finder + arguments: + - %core.root_path% + - .%core.php_ext% + - @cache.driver + kernel_request_subscriber: class: phpbb_event_kernel_request_subscriber arguments: @@ -145,6 +167,21 @@ services: tags: - { name: kernel.event_subscriber } + notification_manager: + class: phpbb_notification_manager + arguments: + - @notification.type_collection + - @notification.method_collection + - @service_container + - @user_loader + - @dbal.conn + - @user + - %core.root_path% + - %core.php_ext% + - %tables.notification_types% + - %tables.notifications% + - %tables.user_notifications% + request: class: phpbb_request @@ -187,3 +224,11 @@ services: user: class: phpbb_user + + user_loader: + class: phpbb_user_loader + arguments: + - @dbal.conn + - %core.root_path% + - %core.php_ext% + - %tables.users% diff --git a/phpBB/config/tables.yml b/phpBB/config/tables.yml index cfc6dbcfed..10db8fbab6 100644 --- a/phpBB/config/tables.yml +++ b/phpBB/config/tables.yml @@ -1,3 +1,9 @@ parameters: tables.config: %core.table_prefix%config tables.ext: %core.table_prefix%ext + tables.notification_types: %core.table_prefix%notification_types + tables.notifications: %core.table_prefix%notifications + tables.user_notifications: %core.table_prefix%user_notifications + tables.users: %core.table_prefix%users + 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 51fb2f4f35..74c6bec1ac 100644 --- a/phpBB/develop/create_schema_files.php +++ b/phpBB/develop/create_schema_files.php @@ -1276,6 +1276,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'), @@ -1298,6 +1311,32 @@ function get_schema_struct() ), ); + $schema_data['phpbb_notification_types'] = array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ); + + $schema_data['phpbb_notifications'] = array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', NULL, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ); + $schema_data['phpbb_poll_options'] = array( 'COLUMNS' => array( 'poll_option_id' => array('TINT:4', 0), @@ -1526,18 +1565,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( @@ -1763,6 +1805,16 @@ function get_schema_struct() ), ); + $schema_data['phpbb_user_notifications'] = array( + 'COLUMNS' => array( + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'method' => array('VCHAR:255', ''), + 'notify' => array('BOOL', 1), + ), + ); + $schema_data['phpbb_user_group'] = array( 'COLUMNS' => array( 'group_id' => array('UINT', 0), diff --git a/phpBB/docs/AUTHORS b/phpBB/docs/AUTHORS index c63bd743a7..be82f06c96 100644 --- a/phpBB/docs/AUTHORS +++ b/phpBB/docs/AUTHORS @@ -23,11 +23,11 @@ involved in phpBB. phpBB Lead Developer: naderman (Nils Adermann) phpBB Developers: bantu (Andreas Fischer) + EXreaction (Nathan Guse) igorw (Igor Wiedler) imkingdavid (David King) nickvergessen (Joas Schilling) Oleg (Oleg Pudeyev) - rxu (Ruslan Uzdenov) Contributions by: leviatan21 (Gabriel Vazquez) Raimon (Raimon Meuldijk) @@ -53,6 +53,7 @@ phpBB Developers: A_Jelly_Doughnut (Josh Woody) [01/2010 - 11/2010] dhn (Dominik Dröscher) [05/2007 - 01/2011] GrahamJE (Graham Eames) [09/2005 - 11/2006] kellanved (Henry Sudhof) [04/2007 - 03/2011] + rxu (Ruslan Uzdenov) [04/2010 - 12/2012] TerraFrost (Jim Wigginton) [04/2009 - 01/2011] ToonArmy (Chris Smith) [06/2008 - 11/2011] Vic D'Elfant (Vic D'Elfant) [04/2007 - 04/2009] diff --git a/phpBB/docs/coding-guidelines.html b/phpBB/docs/coding-guidelines.html index eb569d12d5..14c2281323 100644 --- a/phpBB/docs/coding-guidelines.html +++ b/phpBB/docs/coding-guidelines.html @@ -72,6 +72,7 @@ <ol style="list-style-type: lower-roman;"> <li><a href="#templates">General Templating</a></li> <li><a href="#stylestree">Styles Tree</a></li> + <li><a href="#template-events">Template Events</a></li> </ol></li> <li><a href="#charsets">Character Sets and Encodings</a></li> <li><a href="#translation">Translation (<abbr title="Internationalisation">i18n</abbr>/<abbr title="Localisation">L10n</abbr>) Guidelines</a> @@ -1678,6 +1679,57 @@ version = 3.1.0 parent = prosilver </pre></div> + <a name="template-events"></a><h3>4.iii. Template Events</h3> + <p>Template events must follow this format: <code><!-- EVENT event_name --></code></p> + <p>Using the above example, files named <code>event_name.html</code> located within extensions will be injected into the location of the event.</p> + + <h4>Template event naming guidelines:</h4> + <ul> + <li>An event name must be all lowercase, with each word separated by an underscore.</li> + <li>An event name must briefly describe the location and purpose of the event.</li> + <li>An event name must end with one of the following suffixes:</li> + <ul> + <li><code>_prepend</code> - This event adds an item to the beginning of a block of related items, or adds to the beginning of individual items in a block.</li> + <li><code>_append</code> - This event adds an item to the end of a block of related items, or adds to the end of individual items in a block.</li> + <li><code>_before</code> - This event adds content directly before the specified block</li> + <li><code>_after</code> - This event adds content directly after the specified block</li> + </ul> + </ul> + + <h4>Template event documentation</h4> + <p>Events must be documented in <code>phpBB/docs/events.md</code> in alphabetical order based on the event name. The format is as follows:</p> + + <ul><li>An event found in only one template file: + <div class="codebox"><pre>event_name +=== +* Location: styles/<style_name>/template/filename.html +* Purpose: A brief description of what this event should be used for. +This may span multiple lines. +</pre></div></li> + <li>An event found in multiple template files: + <div class="codebox"><pre>event_name +=== +* Locations: + + first/file/path.html + + second/file/path.html +* Purpose: Same as above. +</pre></div> + <li>An event that is found multiple times in a file should have the number of instances in parenthesis next to the filename. + <div class="codebox"><pre>event_name +=== +* Locations: + + first/file/path.html (2) + + second/file/path.html +* Purpose: Same as above. +</pre></div></li> + <li>An actual example event documentation: + <div class="codebox"><pre>forumlist_body_last_post_title_prepend +==== +* Locations: + + styles/prosilver/template/forumlist_body.html + + styles/subsilver2/template/forumlist_body.html +* Purpose: Add content before the post title of the latest post in a forum on the forum list.</pre></div></ul><br /> + </div> <div class="back2top"><a href="#wrap" class="top">Back to Top</a></div> diff --git a/phpBB/docs/sphinx.sample.conf b/phpBB/docs/sphinx.sample.conf index aa0e8d905d..fcaba6dcf3 100644 --- a/phpBB/docs/sphinx.sample.conf +++ b/phpBB/docs/sphinx.sample.conf @@ -10,21 +10,36 @@ source source_phpbb_{SPHINX_ID}_main sql_query_pre = UPDATE phpbb_sphinx SET max_doc_id = MAX(post_id) WHERE counter_id = 1 sql_query_range = SELECT MIN(post_id), MAX(post_id) FROM phpbb_posts sql_range_step = 5000 - sql_query = SELECT
\ - p.post_id AS id,
\ - p.forum_id,
\ - p.topic_id,
\ - p.poster_id,
\ - CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
\ - p.post_time,
\ - p.post_subject,
\ - p.post_subject as title,
\ - p.post_text as data,
\ - t.topic_last_post_time,
\ - 0 as deleted
\ - FROM phpbb_posts p, phpbb_topics t
\ - WHERE
\ - p.topic_id = t.topic_id
\ + sql_query = SELECT +\ + p.post_id AS id, +\ + p.forum_id, +\ + p.topic_id, +\ + p.poster_id, +\ + CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, +\ + p.post_time, +\ + p.post_subject, +\ + p.post_subject as title, +\ + p.post_text as data, +\ + t.topic_last_post_time, +\ + 0 as deleted +\ + FROM phpbb_posts p, phpbb_topics t +\ + WHERE +\ + p.topic_id = t.topic_id +\ AND p.post_id >= $start AND p.post_id <= $end sql_query_post = sql_query_post_index = UPDATE phpbb_sphinx SET max_doc_id = $maxid WHERE counter_id = 1 @@ -42,21 +57,36 @@ source source_phpbb_{SPHINX_ID}_delta : source_phpbb_{SPHINX_ID}_main { sql_query_range = sql_range_step = - sql_query = SELECT
\ - p.post_id AS id,
\ - p.forum_id,
\ - p.topic_id,
\ - p.poster_id,
\ - CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
\ - p.post_time,
\ - p.post_subject,
\ - p.post_subject as title,
\ - p.post_text as data,
\ - t.topic_last_post_time,
\ - 0 as deleted
\ - FROM phpbb_posts p, phpbb_topics t
\ - WHERE
\ - p.topic_id = t.topic_id
\ + sql_query = SELECT +\ + p.post_id AS id, +\ + p.forum_id, +\ + p.topic_id, +\ + p.poster_id, +\ + CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, +\ + p.post_time, +\ + p.post_subject, +\ + p.post_subject as title, +\ + p.post_text as data, +\ + t.topic_last_post_time, +\ + 0 as deleted +\ + FROM phpbb_posts p, phpbb_topics t +\ + WHERE +\ + p.topic_id = t.topic_id +\ AND p.post_id >= ( SELECT max_doc_id FROM phpbb_sphinx WHERE counter_id=1 ) sql_query_pre = } diff --git a/phpBB/download/file.php b/phpBB/download/file.php index 8c282e36d7..4a392b002d 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -56,18 +56,7 @@ if (isset($_GET['avatar'])) $phpbb_class_loader_ext->register(); // Set up container - $phpbb_container = phpbb_create_dumped_container_unless_debug( - array( - new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), - new phpbb_di_extension_core($phpbb_root_path), - ), - array( - new phpbb_di_pass_collection_pass(), - new phpbb_di_pass_kernel_pass(), - ), - $phpbb_root_path, - $phpEx - ); + $phpbb_container = phpbb_create_default_container($phpbb_root_path, $phpEx); $phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 322e1c55d8..a213102cc6 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -314,6 +314,7 @@ class acp_board 'load_online_time' => array('lang' => 'ONLINE_LENGTH', 'validate' => 'int:0', 'type' => 'text:4:3', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']), 'legend2' => 'GENERAL_OPTIONS', + 'load_notifications' => array('lang' => 'LOAD_NOTIFICATIONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'load_db_track' => array('lang' => 'YES_POST_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'load_db_lastread' => array('lang' => 'YES_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'load_anon_lastread' => array('lang' => 'YES_ANON_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), @@ -413,8 +414,8 @@ class acp_board 'board_email_form' => array('lang' => 'BOARD_EMAIL_FORM', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), 'email_function_name' => array('lang' => 'EMAIL_FUNCTION_NAME', 'validate' => 'string', 'type' => 'text:20:50', 'explain' => true), 'email_package_size' => array('lang' => 'EMAIL_PACKAGE_SIZE', 'validate' => 'int:0', 'type' => 'text:5:5', 'explain' => true), - 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true), - 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true), + 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true), + 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true), 'board_email_sig' => array('lang' => 'EMAIL_SIG', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true), 'board_hide_emails' => array('lang' => 'BOARD_HIDE_EMAILS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index a0bcf62ecc..24211196bd 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -37,7 +37,7 @@ class acp_extensions $this->template = $template; $this->user = $user; - $user->add_lang(array('install', 'acp/extensions')); + $user->add_lang(array('install', 'acp/extensions', 'migrator')); $this->page_title = 'ACP_EXTENSIONS'; @@ -103,11 +103,18 @@ class acp_extensions trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); } - if ($phpbb_extension_manager->enable_step($ext_name)) + try { - $template->assign_var('S_NEXT_STEP', true); + if ($phpbb_extension_manager->enable_step($ext_name)) + { + $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); + meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); + } + } + catch (phpbb_db_migration_exception $e) + { + $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user)); } $this->tpl_name = 'acp_ext_enable'; @@ -156,11 +163,18 @@ class acp_extensions break; case 'purge': - if ($phpbb_extension_manager->purge_step($ext_name)) + try { - $template->assign_var('S_NEXT_STEP', true); + if ($phpbb_extension_manager->purge_step($ext_name)) + { + $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); + meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); + } + } + catch (phpbb_db_migration_exception $e) + { + $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user)); } $this->tpl_name = 'acp_ext_purge'; diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php index e8f0a01b2e..970b033995 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -206,7 +206,7 @@ class acp_forums ($action != 'edit' || empty($forum_id) || ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth')))) { copy_forum_permissions($forum_perm_from, $forum_data['forum_id'], ($action == 'edit') ? true : false); - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); $copied_permissions = true; } /* Commented out because of questionable UI workflow - re-visit for 3.0.7 @@ -266,7 +266,7 @@ class acp_forums add_log('admin', 'LOG_FORUM_' . strtoupper($action), $row['forum_name'], $move_forum_name); $cache->destroy('sql', FORUMS_TABLE); } - + if ($request->is_ajax()) { $json_response = new phpbb_json_response; @@ -767,7 +767,7 @@ class acp_forums if (!empty($forum_perm_from) && $forum_perm_from != $forum_id) { copy_forum_permissions($forum_perm_from, $forum_id, true); - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); $auth->acl_clear_prefetch(); $cache->destroy('sql', FORUMS_TABLE); diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index b604e20094..21b1d4b837 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -126,13 +126,34 @@ class acp_groups { trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); } + else if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } if (confirm_box(true)) { $group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name']; + group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } - if (!sizeof($mark_ary)) + break; + case 'set_default_on_all': + if (confirm_box(true)) { + $group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name']; + $start = 0; do @@ -163,28 +184,25 @@ class acp_groups $db->sql_freeresult($result); } while ($start); + + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); } else { - group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); } - - trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); - } - else - { - confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( - 'mark' => $mark_ary, - 'g' => $group_id, - 'i' => $id, - 'mode' => $mode, - 'action' => $action)) - ); - } - break; - case 'deleteusers': + if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } case 'delete': if (!$group_id) { @@ -439,7 +457,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]; @@ -698,7 +716,7 @@ class acp_groups 'U_ACTION' => $this->u_action . "&g=$group_id", 'U_BACK' => $this->u_action, 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=list&field=usernames'), - 'U_DEFAULT_ALL' => "{$this->u_action}&action=default&g=$group_id", + 'U_DEFAULT_ALL' => "{$this->u_action}&action=set_default_on_all&g=$group_id", )); // Grab the members diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 27813b1cc7..64c2b338fd 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -24,7 +24,7 @@ class acp_main function main($id, $mode) { - global $config, $db, $user, $auth, $template, $request; + global $config, $db, $cache, $user, $auth, $template, $request; global $phpbb_root_path, $phpbb_admin_path, $phpEx; // Show restore permissions notice @@ -129,7 +129,7 @@ class acp_main set_config('record_online_users', 1, true); set_config('record_online_date', time(), true); add_log('admin', 'LOG_RESET_ONLINE'); - + if ($request->is_ajax()) { trigger_error('RESET_ONLINE_SUCCESS'); @@ -184,7 +184,7 @@ class acp_main update_last_username(); add_log('admin', 'LOG_RESYNC_STATS'); - + if ($request->is_ajax()) { trigger_error('RESYNC_STATS_SUCCESS'); @@ -251,7 +251,7 @@ class acp_main } add_log('admin', 'LOG_RESYNC_POSTCOUNTS'); - + if ($request->is_ajax()) { trigger_error('RESYNC_POSTCOUNTS_SUCCESS'); @@ -266,7 +266,7 @@ class acp_main set_config('board_startdate', time() - 1); add_log('admin', 'LOG_RESET_DATE'); - + if ($request->is_ajax()) { trigger_error('RESET_DATE_SUCCESS'); @@ -346,7 +346,7 @@ class acp_main } add_log('admin', 'LOG_RESYNC_POST_MARKING'); - + if ($request->is_ajax()) { trigger_error('RESYNC_POST_MARKING_SUCCESS'); @@ -359,10 +359,10 @@ class acp_main // Clear permissions $auth->acl_clear_prefetch(); - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); add_log('admin', 'LOG_PURGE_CACHE'); - + if ($request->is_ajax()) { trigger_error('PURGE_CACHE_SUCCESS'); @@ -413,7 +413,7 @@ class acp_main $db->sql_query($sql); add_log('admin', 'LOG_PURGE_SESSIONS'); - + if ($request->is_ajax()) { trigger_error('PURGE_SESSIONS_SUCCESS'); diff --git a/phpBB/includes/acp/acp_modules.php b/phpBB/includes/acp/acp_modules.php index 8528dc91c4..fce26bf45f 100644 --- a/phpBB/includes/acp/acp_modules.php +++ b/phpBB/includes/acp/acp_modules.php @@ -535,8 +535,14 @@ class acp_modules /** * Get available module information from module files + * + * @param string $module + * @param bool|string $module_class + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @return array */ - function get_module_infos($module = '', $module_class = false) + function get_module_infos($module = '', $module_class = false, $use_all_available = false) { global $phpbb_root_path, $phpEx; @@ -556,7 +562,7 @@ class acp_modules ->extension_directory("/$module_class") ->core_path("includes/$module_class/info/") ->core_prefix($module_class . '_') - ->get_classes(); + ->get_classes(true, $use_all_available); foreach ($modules as $module) { @@ -740,15 +746,15 @@ class acp_modules */ function remove_cache_file() { - global $cache; + global $phpbb_container; // Sanitise for future path use, it's escaped as appropriate for queries $p_class = str_replace(array('.', '/', '\\'), '', basename($this->module_class)); - $cache->destroy('_modules_' . $p_class); + $phpbb_container->get('cache.driver')->destroy('_modules_' . $p_class); // Additionally remove sql cache - $cache->destroy('sql', MODULES_TABLE); + $phpbb_container->get('cache.driver')->destroy('sql', MODULES_TABLE); } /** diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index dd071074de..a64765f4f5 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -656,7 +656,7 @@ class acp_permissions */ function set_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id) { - global $user, $auth; + global $db, $cache, $user, $auth; global $request; $psubmit = request_var('psubmit', array(0 => array(0 => 0))); @@ -726,13 +726,13 @@ class acp_permissions // Do we need to recache the moderator lists? if ($permission_type == 'm_') { - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); } // Remove users who are now moderators or admins from everyones foes list if ($permission_type == 'm_' || $permission_type == 'a_') { - update_foes($group_id, $user_id); + phpbb_update_foes($db, $auth, $group_id, $user_id); } $this->log_action($mode, 'add', $permission_type, $ug_type, $ug_id, $forum_id); @@ -745,7 +745,7 @@ class acp_permissions */ function set_all_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id) { - global $user, $auth; + global $db, $cache, $user, $auth; global $request; // User or group to be set? @@ -794,13 +794,13 @@ class acp_permissions // Do we need to recache the moderator lists? if ($permission_type == 'm_') { - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); } // Remove users who are now moderators or admins from everyones foes list if ($permission_type == 'm_' || $permission_type == 'a_') { - update_foes($group_id, $user_id); + phpbb_update_foes($db, $auth, $group_id, $user_id); } $this->log_action($mode, 'add', $permission_type, $ug_type, $ug_ids, $forum_ids); @@ -858,7 +858,7 @@ class acp_permissions */ function remove_permissions($mode, $permission_type, &$auth_admin, &$user_id, &$group_id, &$forum_id) { - global $user, $db, $auth; + global $user, $db, $cache, $auth; // User or group to be set? $ug_type = (sizeof($user_id)) ? 'user' : 'group'; @@ -874,7 +874,7 @@ class acp_permissions // Do we need to recache the moderator lists? if ($permission_type == 'm_') { - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); } $this->log_action($mode, 'del', $permission_type, $ug_type, (($ug_type == 'user') ? $user_id : $group_id), (sizeof($forum_id) ? $forum_id : array(0 => 0))); @@ -952,12 +952,7 @@ class acp_permissions if ($user_id != $user->data['user_id']) { - $sql = 'SELECT user_id, username, user_permissions, user_type - FROM ' . USERS_TABLE . ' - WHERE user_id = ' . $user_id; - $result = $db->sql_query($sql); - $userdata = $db->sql_fetchrow($result); - $db->sql_freeresult($result); + $userdata = $auth->obtain_user_data($user_id); } else { @@ -1172,7 +1167,7 @@ class acp_permissions */ function copy_forum_permissions() { - global $auth, $cache, $template, $user; + global $db, $auth, $cache, $template, $user; $user->add_lang('acp/forums'); @@ -1187,7 +1182,7 @@ class acp_permissions { if (copy_forum_permissions($src, $dest)) { - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); $auth->acl_clear_prefetch(); $cache->destroy('sql', FORUMS_TABLE); diff --git a/phpBB/includes/acp/acp_ranks.php b/phpBB/includes/acp/acp_ranks.php index d9ed5b17f1..6b06d03f52 100644 --- a/phpBB/includes/acp/acp_ranks.php +++ b/phpBB/includes/acp/acp_ranks.php @@ -71,7 +71,7 @@ class acp_ranks 'rank_min' => $min_posts, 'rank_image' => htmlspecialchars_decode($rank_image) ); - + if ($rank_id) { $sql = 'UPDATE ' . RANKS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " WHERE rank_id = $rank_id"; @@ -122,7 +122,7 @@ class acp_ranks $cache->destroy('_ranks'); add_log('admin', 'LOG_RANK_REMOVED', $rank_title); - + if ($request->is_ajax()) { $json_response = new phpbb_json_response; @@ -151,7 +151,7 @@ class acp_ranks case 'add': $data = $ranks = $existing_imgs = array(); - + $sql = 'SELECT * FROM ' . RANKS_TABLE . ' ORDER BY rank_min ASC, rank_special ASC'; @@ -209,17 +209,17 @@ class acp_ranks 'RANK_TITLE' => (isset($ranks['rank_title'])) ? $ranks['rank_title'] : '', 'S_FILENAME_LIST' => $filename_list, - 'RANK_IMAGE' => ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : $phpbb_admin_path . 'images/spacer.gif', + 'RANK_IMAGE' => ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : htmlspecialchars($phpbb_admin_path) . 'images/spacer.gif', 'S_SPECIAL_RANK' => (isset($ranks['rank_special']) && $ranks['rank_special']) ? true : false, 'MIN_POSTS' => (isset($ranks['rank_min']) && !$ranks['rank_special']) ? $ranks['rank_min'] : 0) ); - + return; break; } - + $template->assign_vars(array( 'U_ACTION' => $this->u_action) ); @@ -241,7 +241,7 @@ class acp_ranks 'U_EDIT' => $this->u_action . '&action=edit&id=' . $row['rank_id'], 'U_DELETE' => $this->u_action . '&action=delete&id=' . $row['rank_id']) - ); + ); } $db->sql_freeresult($result); diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php index db77825ae7..266495972b 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -137,11 +137,13 @@ class acp_styles */ protected function action_cache() { + global $db, $cache, $auth; + $this->cache->purge(); // Clear permissions $this->auth->acl_clear_prefetch(); - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); add_log('admin', 'LOG_PURGE_CACHE'); diff --git a/phpBB/includes/acp/info/acp_extensions.php b/phpBB/includes/acp/info/acp_extensions.php index 03d7059165..174b365af0 100644 --- a/phpBB/includes/acp/info/acp_extensions.php +++ b/phpBB/includes/acp/info/acp_extensions.php @@ -16,10 +16,10 @@ class acp_extensions_info { return array( 'filename' => 'acp_extensions', - 'title' => 'ACP_EXTENSIONS_MANAGEMENT', + 'title' => 'ACP_EXTENSION_MANAGEMENT', 'version' => '1.0.0', 'modes' => array( - 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSIONS_MANAGEMENT')), + 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSION_MANAGEMENT')), ), ); } diff --git a/phpBB/includes/auth/auth.php b/phpBB/includes/auth/auth.php index e3bccaf47b..2535247571 100644 --- a/phpBB/includes/auth/auth.php +++ b/phpBB/includes/auth/auth.php @@ -103,6 +103,26 @@ class phpbb_auth } /** + * Retrieves data wanted by acl function from the database for the + * specified user. + * + * @param int $user_id User ID + * @return array User attributes + */ + public function obtain_user_data($user_id) + { + global $db; + + $sql = 'SELECT user_id, username, user_permissions, user_type + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + $user_data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + return $user_data; + } + + /** * Fill ACL array with relevant bitstrings from user_permissions column * @access private */ @@ -191,7 +211,7 @@ class phpbb_auth /** * Get forums with the specified permission setting - * if the option is prefixed with !, then the result becomes nagated + * if the option is prefixed with !, then the result becomes negated * * @param bool $clean set to true if only values needs to be returned which are set/unset */ diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 691abe0438..85decbe3e8 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -367,12 +367,10 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { - global $db; - // Remove extra spaces and tabs $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index d403bbcd71..53f684d1c8 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -85,6 +85,7 @@ interface phpbb_cache_driver_interface * result to persistent storage. In other words, there is no need * to call save() afterwards. * + * @param phpbb_db_driver $db Database connection * @param string $query SQL query, should be used for generating storage key * @param mixed $query_result The result from dbal::sql_query, to be passed to * dbal::sql_fetchrow to get all rows and store them @@ -95,7 +96,7 @@ interface phpbb_cache_driver_interface * representing the query should be returned. Otherwise * the original $query_result should be returned. */ - public function sql_save($query, $query_result, $ttl); + public function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl); /** * Check if result for a given SQL query exists in cache. diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index c39f9f7850..f77a1df316 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -283,12 +283,10 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { - global $db; - // Remove extra spaces and tabs $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); $hash = md5($query); diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php index 687604d14f..2fadc27ba3 100644 --- a/phpBB/includes/cache/driver/null.php +++ b/phpBB/includes/cache/driver/null.php @@ -105,9 +105,9 @@ class phpbb_cache_driver_null extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { return $query_result; } diff --git a/phpBB/includes/cache/service.php b/phpBB/includes/cache/service.php index e63ec6e33a..69c5e0fdd0 100644 --- a/phpBB/includes/cache/service.php +++ b/phpBB/includes/cache/service.php @@ -21,16 +21,57 @@ if (!defined('IN_PHPBB')) */ class phpbb_cache_service { - private $driver; + /** + * Cache driver. + * + * @var phpbb_cache_driver_interface + */ + protected $driver; + + /** + * The config. + * + * @var phpbb_config + */ + protected $config; + + /** + * Database connection. + * + * @var phpbb_db_driver + */ + protected $db; + + /** + * Root path. + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extension. + * + * @var string + */ + protected $php_ext; /** * Creates a cache service around a cache driver * * @param phpbb_cache_driver_interface $driver The cache driver + * @param phpbb_config $config The config + * @param phpbb_db_driver $db Database connection + * @param string $phpbb_root_path Root path + * @param string $php_ext PHP extension */ - public function __construct(phpbb_cache_driver_interface $driver = null) + public function __construct(phpbb_cache_driver_interface $driver, phpbb_config $config, phpbb_db_driver $db, $phpbb_root_path, $php_ext) { $this->set_driver($driver); + $this->config = $config; + $this->db = $db; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; } /** @@ -64,21 +105,19 @@ class phpbb_cache_service */ function obtain_word_list() { - global $db; - if (($censors = $this->driver->get('_word_censors')) === false) { $sql = 'SELECT word, replacement FROM ' . WORDS_TABLE; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $censors = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $censors['match'][] = get_censor_preg_expression($row['word']); $censors['replace'][] = $row['replacement']; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_word_censors', $censors); } @@ -93,23 +132,21 @@ class phpbb_cache_service { if (($icons = $this->driver->get('_icons')) === false) { - global $db; - // Topic icons $sql = 'SELECT * FROM ' . ICONS_TABLE . ' ORDER BY icons_order'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $icons = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $icons[$row['icons_id']]['img'] = $row['icons_url']; $icons[$row['icons_id']]['width'] = (int) $row['icons_width']; $icons[$row['icons_id']]['height'] = (int) $row['icons_height']; $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting']; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_icons', $icons); } @@ -124,15 +161,13 @@ class phpbb_cache_service { if (($ranks = $this->driver->get('_ranks')) === false) { - global $db; - $sql = 'SELECT * FROM ' . RANKS_TABLE . ' ORDER BY rank_min DESC'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $ranks = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { if ($row['rank_special']) { @@ -150,7 +185,7 @@ class phpbb_cache_service ); } } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_ranks', $ranks); } @@ -169,8 +204,6 @@ class phpbb_cache_service { if (($extensions = $this->driver->get('_extensions')) === false) { - global $db; - $extensions = array( '_allowed_post' => array(), '_allowed_pm' => array(), @@ -181,9 +214,9 @@ class phpbb_cache_service FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g WHERE e.group_id = g.group_id AND (g.allow_group = 1 OR g.allow_in_pm = 1)'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $extension = strtolower(trim($row['extension'])); @@ -210,7 +243,7 @@ class phpbb_cache_service $extensions['_allowed_pm'][$extension] = 0; } } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_extensions', $extensions); } @@ -275,9 +308,7 @@ class phpbb_cache_service { if (($bots = $this->driver->get('_bots')) === false) { - global $db; - - switch ($db->sql_layer) + switch ($this->db->sql_layer) { case 'mssql': case 'mssql_odbc': @@ -303,14 +334,14 @@ class phpbb_cache_service ORDER BY LENGTH(bot_agent) DESC'; break; } - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $bots = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $bots[] = $row; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_bots', $bots); } @@ -323,8 +354,6 @@ class phpbb_cache_service */ function obtain_cfg_items($style) { - global $config, $phpbb_root_path; - $parsed_array = $this->driver->get('_cfg_' . $style['style_path']); if ($parsed_array === false) @@ -332,14 +361,14 @@ class phpbb_cache_service $parsed_array = array(); } - $filename = $phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; + $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; if (!file_exists($filename)) { return $parsed_array; } - if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) + if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) { // Re-parse cfg file $parsed_array = parse_cfg_file($filename); @@ -358,54 +387,20 @@ class phpbb_cache_service { if (($usernames = $this->driver->get('_disallowed_usernames')) === false) { - global $db; - $sql = 'SELECT disallow_username FROM ' . DISALLOW_TABLE; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); $usernames = array(); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#')); } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $this->driver->put('_disallowed_usernames', $usernames); } return $usernames; } - - /** - * Obtain hooks... - */ - function obtain_hooks() - { - global $phpbb_root_path, $phpEx; - - if (($hook_files = $this->driver->get('_hooks')) === false) - { - $hook_files = array(); - - // Now search for hooks... - $dh = @opendir($phpbb_root_path . 'includes/hooks/'); - - if ($dh) - { - while (($file = readdir($dh)) !== false) - { - if (strpos($file, 'hook_') === 0 && substr($file, -(strlen($phpEx) + 1)) === '.' . $phpEx) - { - $hook_files[] = substr($file, 0, -(strlen($phpEx) + 1)); - } - } - closedir($dh); - } - - $this->driver->put('_hooks', $hook_files); - } - - return $hook_files; - } } 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 62c06dc1d0..d0deb36a89 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -241,8 +241,11 @@ 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('NOTIFICATION_TYPES_TABLE', $table_prefix . 'notification_types'); +define('NOTIFICATIONS_TABLE', $table_prefix . 'notifications'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes'); define('POSTS_TABLE', $table_prefix . 'posts'); @@ -276,6 +279,7 @@ define('TOPICS_POSTED_TABLE', $table_prefix . 'topics_posted'); define('TOPICS_TRACK_TABLE', $table_prefix . 'topics_track'); define('TOPICS_WATCH_TABLE', $table_prefix . 'topics_watch'); define('USER_GROUP_TABLE', $table_prefix . 'user_group'); +define('USER_NOTIFICATIONS_TABLE', $table_prefix . 'user_notifications'); define('USERS_TABLE', $table_prefix . 'users'); define('WARNINGS_TABLE', $table_prefix . 'warnings'); define('WORDS_TABLE', $table_prefix . 'words'); diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index b3462ddf67..3c6d4971b9 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -143,7 +143,7 @@ class phpbb_datetime extends DateTime 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), - 'lang' => $user->lang['datetime'], + 'lang' => array_filter($user->lang['datetime'], 'is_string'), ); // Short representation of month in format? Some languages use different terms for the long and short format of May diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php index 2bb016cebd..983cdc18ea 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -303,7 +303,7 @@ class phpbb_db_tools * @param phpbb_db_driver $db Database connection * @param bool $return_statements True if only statements should be returned and no SQL being executed */ - function phpbb_db_tools(&$db, $return_statements = false) + function phpbb_db_tools(phpbb_db_driver $db, $return_statements = false) { $this->db = $db; $this->return_statements = $return_statements; @@ -346,6 +346,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. * * @return array Array of table names (all lower case) @@ -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/driver/driver.php b/phpBB/includes/db/driver/driver.php index 25daa7243d..8dda94bc2c 100644 --- a/phpBB/includes/db/driver/driver.php +++ b/phpBB/includes/db/driver/driver.php @@ -206,7 +206,7 @@ class phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -256,7 +256,7 @@ class phpbb_db_driver $this->sql_rowseek($rownum, $query_id); } - if (!is_object($query_id) && $cache->sql_exists($query_id)) + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_fetchfield($query_id, $field); } @@ -822,7 +822,7 @@ class phpbb_db_driver */ function sql_report($mode, $query = '') { - global $cache, $starttime, $phpbb_root_path, $user; + global $cache, $starttime, $phpbb_root_path, $phpbb_admin_path, $user; global $request; if (is_object($request) && !$request->variable('explain', false)) @@ -852,7 +852,7 @@ class phpbb_db_driver <head> <meta charset="utf-8"> <title>SQL Report</title> - <link href="' . $phpbb_root_path . 'adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="' . htmlspecialchars($phpbb_admin_path) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" /> </head> <body id="errorpage"> <div id="wrap"> diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php index a55175c345..787c28b812 100644 --- a/phpBB/includes/db/driver/firebird.php +++ b/phpBB/includes/db/driver/firebird.php @@ -154,7 +154,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver } $this->last_query_text = $query; - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -267,10 +267,10 @@ class phpbb_db_driver_firebird extends phpbb_db_driver } } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -330,7 +330,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -396,7 +396,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php index ac957e7698..89c2c2351b 100644 --- a/phpBB/includes/db/driver/mssql.php +++ b/phpBB/includes/db/driver/mssql.php @@ -150,7 +150,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -165,10 +165,10 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -240,7 +240,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -277,7 +277,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -316,7 +316,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php index 13e74e66d4..f7834443eb 100644 --- a/phpBB/includes/db/driver/mssql_odbc.php +++ b/phpBB/includes/db/driver/mssql_odbc.php @@ -179,7 +179,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver } $this->last_query_text = $query; - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -194,10 +194,10 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -270,7 +270,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -311,7 +311,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php index 4b1639aba2..656cbd2437 100644 --- a/phpBB/includes/db/driver/mssqlnative.php +++ b/phpBB/includes/db/driver/mssqlnative.php @@ -317,7 +317,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver } $this->last_query_text = $query; - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -337,7 +337,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php index 6fc6fab483..9de7283a42 100644 --- a/phpBB/includes/db/driver/mysql.php +++ b/phpBB/includes/db/driver/mysql.php @@ -188,7 +188,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -203,10 +203,10 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -265,7 +265,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -286,7 +286,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -314,7 +314,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php index be28a95715..7448bf1670 100644 --- a/phpBB/includes/db/driver/mysqli.php +++ b/phpBB/includes/db/driver/mysqli.php @@ -184,7 +184,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -199,9 +199,9 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } } else if (defined('DEBUG')) @@ -256,7 +256,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && $cache->sql_exists($query_id)) + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -283,7 +283,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && $cache->sql_exists($query_id)) + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -311,7 +311,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && $cache->sql_exists($query_id)) + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php index 6263ea8414..e21e07055d 100644 --- a/phpBB/includes/db/driver/oracle.php +++ b/phpBB/includes/db/driver/oracle.php @@ -267,7 +267,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver } $this->last_query_text = $query; - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -443,10 +443,10 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -498,7 +498,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -550,7 +550,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -619,7 +619,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php index 147ecd04d9..14854d179d 100644 --- a/phpBB/includes/db/driver/postgres.php +++ b/phpBB/includes/db/driver/postgres.php @@ -193,7 +193,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver } $this->last_query_text = $query; - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -208,10 +208,10 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -278,7 +278,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -299,7 +299,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -348,7 +348,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php index 6b9cc64d89..7188f0daa2 100644 --- a/phpBB/includes/db/driver/sqlite.php +++ b/phpBB/includes/db/driver/sqlite.php @@ -134,7 +134,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -149,10 +149,10 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl) + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { @@ -210,7 +210,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -231,7 +231,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -259,7 +259,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if ($cache->sql_exists($query_id)) + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/migration/data/30x/3_0_1.php b/phpBB/includes/db/migration/data/30x/3_0_1.php new file mode 100644 index 0000000000..c996a0138a --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_1.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_1_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.1')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10.php b/phpBB/includes/db/migration/data/30x/3_0_10.php new file mode 100644 index 0000000000..122f93d6b4 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_10.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.10', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php new file mode 100644 index 0000000000..0ed05812dc --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php @@ -0,0 +1,30 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.10-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9'); + } + + public function update_data() + { + return array( + array('config.add', array('email_max_chunk_size', 50)), + + array('config.update', array('version', '3.0.10-rc1')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php new file mode 100644 index 0000000000..b14b3b00aa --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.10-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-rc2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php new file mode 100644 index 0000000000..473057d65d --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.10-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-rc3')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11.php b/phpBB/includes/db/migration/data/30x/3_0_11.php new file mode 100644 index 0000000000..e063c699cc --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_11.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.11', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php new file mode 100644 index 0000000000..dddfc0e0e7 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php @@ -0,0 +1,95 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.11-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), + array('custom', array(array(&$this, 'delete_orphan_private_messages'))), + + array('config.update', array('version', '3.0.11-rc1')), + ); + } + + public function cleanup_deactivated_styles() + { + // Updates users having current style a deactivated one + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_active = 0'; + $result = $this->sql_query($sql); + + $deactivated_style_ids = array(); + while ($style_id = $this->db->sql_fetchfield('style_id', false, $result)) + { + $deactivated_style_ids[] = (int) $style_id; + } + $this->db->sql_freeresult($result); + + if (!empty($deactivated_style_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $this->config['default_style'] .' + WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids); + $this->sql_query($sql); + } + } + + public function delete_orphan_private_messages() + { + // Delete orphan private messages + $batch_size = 500; + + $sql_array = array( + 'SELECT' => 'p.msg_id', + 'FROM' => array( + PRIVMSGS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(PRIVMSGS_TO_TABLE => 't'), + 'ON' => 'p.msg_id = t.msg_id', + ), + ), + 'WHERE' => 't.user_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + + $result = $this->db->sql_query_limit($sql, $batch_size); + + $delete_pms = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $delete_pms[] = (int) $row['msg_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($delete_pms)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms); + $this->sql_query($sql); + + // Return false to have the Migrator call this function again + return false; + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php new file mode 100644 index 0000000000..fac8523e8c --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php @@ -0,0 +1,50 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.11-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11_rc1'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue' => array('BOOL', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11-rc2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php new file mode 100644 index 0000000000..6a31a51201 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php @@ -0,0 +1,123 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.12-RC1 **/ + +class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.12-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'update_bots'))), + array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), + + array('config.update', array('version', '3.0.12-rc1')), + ); + } + + public function disable_bots_from_receiving_pms() + { + // Disable receiving pms for bots + $sql = 'SELECT user_id + FROM ' . BOTS_TABLE; + $result = $this->db->sql_query($sql); + + $bot_user_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $bot_user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($bot_user_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_allow_pm = 0 + WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids); + $this->sql_query($sql); + } + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'acl_u_sig\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'signature\''; + $this->sql_query($sql); + } + + public function update_bots() + { + // Update bots + if (!function_exists('user_delete')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $bots_updates = array( + // Bot Deletions + 'NG-Search [Bot]' => false, + 'Nutch/CVS [Bot]' => false, + 'OmniExplorer [Bot]' => false, + 'Seekport [Bot]' => false, + 'Synoo [Bot]' => false, + 'WiseNut [Bot]' => false, + + // Bot Updates + // Bot name to bot user agent map + 'Baidu [Spider]' => 'Baiduspider', + 'Exabot [Bot]' => 'Exabot', + 'Voyager [Bot]' => 'voyager/', + 'W3C [Validator]' => 'W3C_Validator', + ); + + foreach ($bots_updates as $bot_name => $bot_agent) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_IGNORE . " + AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'"; + $result = $this->db->sql_query($sql); + $bot_user_id = (int) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if ($bot_user_id) + { + if ($bot_agent === false) + { + $sql = 'DELETE FROM ' . BOTS_TABLE . " + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + + user_delete('remove', $bot_user_id); + } + else + { + $sql = 'UPDATE ' . BOTS_TABLE . " + SET bot_agent = '" . $this->db->sql_escape($bot_agent) . "' + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + } + } + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php new file mode 100644 index 0000000000..562ccf077c --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php @@ -0,0 +1,108 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.1-rc1', '>='); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list' => array('BOOL', 1), + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('UINT', 0), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'groups' => array( + 'group_legend', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('session_forum_id'), + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name' => array('group_legend', 'group_name'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list', + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('group_legend'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'fix_unset_last_view_time'))), + array('custom', array(array(&$this, 'reset_smiley_size'))), + + array('config.update', array('version', '3.0.1-rc1')), + ); + } + + public function fix_unset_last_view_time() + { + $sql = 'UPDATE ' . $this->table_prefix . "topics + SET topic_last_view_time = topic_last_post_time + WHERE topic_last_view_time = 0"; + $this->sql_query($sql); + } + + public function reset_smiley_size() + { + // Update smiley sizes + $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); + + foreach ($smileys as $smiley) + { + if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley)) + { + list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley); + + $sql = 'UPDATE ' . SMILIES_TABLE . ' + SET smiley_width = ' . $width . ', smiley_height = ' . $height . " + WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'"; + + $this->sql_query($sql); + } + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2.php b/phpBB/includes/db/migration/data/30x/3_0_2.php new file mode 100644 index 0000000000..eed5acef82 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php new file mode 100644 index 0000000000..a960e90765 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php @@ -0,0 +1,32 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.2-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_1'); + } + + public function update_data() + { + return array( + array('config.add', array('referer_validation', '1')), + array('config.add', array('check_attachment_content', '1')), + array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), + + array('config.update', array('version', '3.0.2-rc1')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php new file mode 100644 index 0000000000..8917dfea77 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php @@ -0,0 +1,80 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.2-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2_rc1'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'drafts' => array( + 'draft_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'posts' => array( + 'post_subject' => array('STEXT_UNI', '', 'true_sort'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'topics' => array( + 'topic_title' => array('STEXT_UNI', '', 'true_sort'), + 'topic_last_post_subject' => array('STEXT_UNI', ''), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid' => array('session_forum_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array( + 'session_forum_id', + ), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2-rc2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_3.php b/phpBB/includes/db/migration/data/30x/3_0_3.php new file mode 100644 index 0000000000..8984cf7b76 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_3 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_3_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.3')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php new file mode 100644 index 0000000000..4b102e1a2e --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php @@ -0,0 +1,83 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.3-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id', + 'template_inherit_path', + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('enable_queue_trigger', '0')), + array('config.add', array('queue_trigger_posts', '3')), + array('config.add', array('pm_max_recipients', '0')), + array('custom', array(array(&$this, 'set_group_default_max_recipients'))), + array('config.add', array('dbms_version', $this->db->sql_server_info(true))), + array('permission.add', array('u_masspm_group', true, 'u_masspm')), + array('custom', array(array(&$this, 'correct_acp_email_permissions'))), + + array('config.update', array('version', '3.0.3-rc1')), + ); + } + + public function correct_acp_email_permissions() + { + $sql = 'UPDATE ' . $this->table_prefix . 'modules + SET module_auth = \'acl_a_email && cfg_email_enable\' + WHERE module_class = \'acp\' + AND module_basename = \'email\''; + $this->sql_query($sql); + } + + public function set_group_default_max_recipients() + { + // Set maximum number of recipients for the registered users, bots, guests group + $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 + WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); + $this->sql_query($sql); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_4.php b/phpBB/includes/db/migration/data/30x/3_0_4.php new file mode 100644 index 0000000000..9a0c132e78 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_4.php @@ -0,0 +1,49 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_4 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_4_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'rename_log_delete_topic'))), + + array('config.update', array('version', '3.0.4')), + ); + } + + public function rename_log_delete_topic() + { + if ($this->db->sql_layer == 'oracle') + { + // log_operation is CLOB - but we can change this later + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation = 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php new file mode 100644 index 0000000000..8ad75a557b --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php @@ -0,0 +1,123 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.4-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_3'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile' => array('BOOL', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_id' => array('UINT', NULL, 'auto_increment'), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_imageset' => array( + 'imageset_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'image_id' => array('UINT', NULL, 'auto_increment'), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_theme' => array( + 'theme_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template' => array( + 'template_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template_data' => array( + 'template_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_style' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_custom_profile_fields'))), + + array('config.update', array('version', '3.0.4-rc1')), + ); + } + + public function update_custom_profile_fields() + { + // Update the Custom Profile Fields based on previous settings to the new format + $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'field_required' => 0, + 'field_show_on_reg' => 0, + 'field_hide' => 0, + 'field_show_profile'=> 0, + ); + + if ($row['field_required']) + { + $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_show_on_reg']) + { + $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_hide']) + { + // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module + $sql_ary['field_hide'] = 1; + } + else + { + // equivelant to "none", which is the "Display in user control panel" option + $sql_ary['field_show_profile'] = 1; + } + + $this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); + } + + $this->db->sql_freeresult($result); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5.php b/phpBB/includes/db/migration/data/30x/3_0_5.php new file mode 100644 index 0000000000..16d2dee457 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_5.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.5', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5_rc1part2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php new file mode 100644 index 0000000000..ea17cc1e31 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php @@ -0,0 +1,124 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.5-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_4'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + ), + ); + } + + public function update_data() + { + $search_indexing_state = $this->config['search_indexing_state']; + + return array( + array('config.add', array('captcha_gd_wave', 0)), + array('config.add', array('captcha_gd_3d_noise', 1)), + array('config.add', array('captcha_gd_fonts', 1)), + array('config.add', array('confirm_refresh', 1)), + array('config.add', array('max_num_search_keywords', 10)), + array('config.remove', array('search_indexing_state')), + array('config.add', array('search_indexing_state', $search_indexing_state, true)), + array('custom', array(array(&$this, 'hash_old_passwords'))), + array('custom', array(array(&$this, 'update_ichiro_bot'))), + ); + } + + public function hash_old_passwords() + { + $sql = 'SELECT user_id, user_password + FROM ' . $this->table_prefix . 'users + WHERE user_pass_convert = 1'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strlen($row['user_password']) == 32) + { + $sql_ary = array( + 'user_password' => phpbb_hash($row['user_password']), + ); + + $this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']); + } + } + $this->db->sql_freeresult($result); + } + + public function update_ichiro_bot() + { + // Adjust bot entry + $sql = 'UPDATE ' . $this->table_prefix . "bots + SET bot_agent = 'ichiro/' + WHERE bot_agent = 'ichiro/2'"; + $this->sql_query($sql); + } + + public function remove_duplicate_auth_options() + { + // Before we are able to add a unique key to auth_option, we need to remove duplicate entries + $sql = 'SELECT auth_option + FROM ' . $this->table_prefix . 'acl_options + GROUP BY auth_option + HAVING COUNT(*) >= 2'; + $result = $this->db->sql_query($sql); + + $auth_options = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $auth_options[] = $row['auth_option']; + } + $this->db->sql_freeresult($result); + + // Remove specific auth options + if (!empty($auth_options)) + { + foreach ($auth_options as $option) + { + // Select auth_option_ids... the largest id will be preserved + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $db->sql_escape($option) . "' + ORDER BY auth_option_id DESC"; + // sql_query_limit not possible here, due to bug in postgresql layer + $result = $this->db->sql_query($sql); + + // Skip first row, this is our original auth option we want to preserve + $row = $this->db->sql_fetchrow($result); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Ok, remove this auth option... + $this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + } + $this->db->sql_freeresult($result); + } + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php new file mode 100644 index 0000000000..8538347b1a --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php @@ -0,0 +1,42 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.5-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5_rc1'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'acl_options' => array('auth_option'), + ), + 'add_unique_index' => array( + $this->table_prefix . 'acl_options' => array( + 'auth_option' => array('auth_option'), + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5-rc1')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6.php b/phpBB/includes/db/migration/data/30x/3_0_6.php new file mode 100644 index 0000000000..bb651dc7cd --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_6.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.6', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php new file mode 100644 index 0000000000..38c282ebf0 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php @@ -0,0 +1,324 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.6-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_new' => array('BOOL', 1), + 'user_reminded' => array('TINT:4', 0), + 'user_reminded_time' => array('TIMESTAMP', 0), + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported' => array('BOOL', 0), + ), + $this->table_prefix . 'reports' => array( + 'pm_id' => array('UINT', 0), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt' => array('BOOL', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_options' => array('UINT:20', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_options' => array('UINT:11', 230271), + ), + ), + 'add_index' => array( + $this->table_prefix . 'reports' => array( + 'post_id' => array('post_id'), + 'pm_id' => array('pm_id'), + ), + $this->table_prefix . 'posts' => array( + 'post_username' => array('post_username:255'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts', + ), + $this->table_prefix . 'users' => array( + 'user_new', + 'user_reminded', + 'user_reminded_time', + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth', + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported', + ), + $this->table_prefix . 'reports' => array( + 'pm_id', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt', + ), + $this->table_prefix . 'forums' => array( + 'forum_options', + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'reports' => array( + 'post_id', + 'pm_id', + ), + $this->table_prefix . 'posts' => array( + 'post_username', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')), + array('if', array( + ($this->config['captcha_gd']), + array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')), + )), + + array('config.add', array('feed_enable', 0)), + array('config.add', array('feed_limit', 10)), + array('config.add', array('feed_overall_forums', 1)), + array('config.add', array('feed_overall_forums_limit', 15)), + array('config.add', array('feed_overall_topics', 0)), + array('config.add', array('feed_overall_topics_limit', 15)), + array('config.add', array('feed_forum', 1)), + array('config.add', array('feed_topic', 1)), + array('config.add', array('feed_item_statistics', 1)), + + array('config.add', array('smilies_per_page', 50)), + array('config.add', array('allow_pm_report', 1)), + array('config.add', array('min_post_chars', 1)), + array('config.add', array('allow_quick_reply', 1)), + array('config.add', array('new_member_post_limit', 0)), + array('config.add', array('new_member_group_default', 0)), + array('config.add', array('delete_time', $this->config['edit_time'])), + + array('config.add', array('allow_avatar', 0)), + array('if', array( + ($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']), + array('config.update', array('allow_avatar', 1)), + )), + array('config.add', array('allow_avatar_remote_upload', 0)), + array('if', array( + ($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']), + array('config.update', array('allow_avatar_remote_upload', 1)), + )), + + array('module.add', array( + 'acp', + 'ACP_BOARD_CONFIGURATION', + array( + 'module_basename' => 'acp_board', + 'modes' => array('feed'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_CAT_USERS', + array( + 'module_basename' => 'acp_users', + 'modes' => array('warnings'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_SERVER_CONFIGURATION', + array( + 'module_basename' => 'acp_send_statistics', + 'modes' => array('send_statistics'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_FORUM_BASED_PERMISSIONS', + array( + 'module_basename' => 'acp_permissions', + 'modes' => array('setting_forum_copy'), + ), + )), + array('module.add', array( + 'mcp', + 'MCP_REPORTS', + array( + 'module_basename' => 'mcp_pm_reports', + 'modes' => array('pm_reports','pm_reports_closed','pm_report_details'), + ), + )), + array('custom', array(array(&$this, 'add_newly_registered_group'))), + array('custom', array(array(&$this, 'set_user_options_default'))), + + array('config.update', array('version', '3.0.6-rc1')), + ); + } + + public function set_user_options_default() + { + // 229376 is the added value to enable all three signature options + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; + $this->sql_query($sql); + } + + public function add_newly_registered_group() + { + // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'NEWLY_REGISTERED'"; + $result = $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (!$group_id) + { + $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; + $this->sql_query($sql); + + $group_id = $this->db->sql_nextid(); + } + + // Insert new user role... at the end of the chain + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_USER_NEW_MEMBER' + AND role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $u_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$u_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; + $this->sql_query($sql); + $u_role = $this->db->sql_nextid(); + + // Now add the correct data to the roles... + // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; + $this->sql_query($sql); + + // Add user role to group + $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; + $this->sql_query($sql); + } + + // Insert new forum role + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' + AND role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $f_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$f_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; + $this->sql_query($sql); + $f_role = $this->db->sql_nextid(); + + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; + $this->sql_query($sql); + } + + // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) + $sql = 'SELECT 1 + FROM ' . USERS_TABLE . ' + WHERE user_new = 0'; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; + $this->sql_query($sql); + } + + // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... + // Check if the role is already assigned... + $sql = 'SELECT forum_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id = ' . $f_role; + $result = $this->db->sql_query($sql); + $is_options = (int) $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + // Not assigned at all... :/ + if (!$is_options) + { + // Get postable forums + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type != ' . FORUM_LINK; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)'); + } + $this->db->sql_freeresult($result); + } + + // Clear permissions... + include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + $auth_admin = new auth_admin(); + $auth_admin->acl_clear_prefetch(); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php new file mode 100644 index 0000000000..a939dbd489 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.6-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-rc2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php new file mode 100644 index 0000000000..b3f09d8ab8 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php @@ -0,0 +1,40 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.6-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc2'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_cp_fields'))), + + array('config.update', array('version', '3.0.6-rc3')), + ); + } + + public function update_cp_fields() + { + // Update the Custom Profile Fields based on previous settings to the new format + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' + SET field_show_on_vt = 1 + WHERE field_hide = 0 + AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; + $this->sql_query($sql); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php new file mode 100644 index 0000000000..fc2923f99b --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.6-rc4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-rc4')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7.php b/phpBB/includes/db/migration/data/30x/3_0_7.php new file mode 100644 index 0000000000..9ff2e9e4ab --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_7.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.7', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php b/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php new file mode 100644 index 0000000000..c9cc9d19ac --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_pl1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.7-pl1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7-pl1')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php new file mode 100644 index 0000000000..ffebf66f2d --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php @@ -0,0 +1,76 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.7-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'log' => array( + 'log_time', + ), + ), + 'add_index' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id' => array('topic_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'log' => array( + 'log_time' => array('log_time'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('feed_overall', 1)), + array('config.add', array('feed_http_auth', 0)), + array('config.add', array('feed_limit_post', $this->config['feed_limit'])), + array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])), + array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])), + array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), + array('custom', array(array(&$this, 'delete_text_templates'))), + + array('config.update', array('version', '3.0.7-rc1')), + ); + } + + public function delete_text_templates() + { + // Delete all text-templates from the template_data + $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' + WHERE template_filename ' . $this->db->sql_like_expression($this->db->any_char . '.txt'); + $this->sql_query($sql); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php new file mode 100644 index 0000000000..55bc2bc679 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php @@ -0,0 +1,73 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.7-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_email_hash'))), + + array('config.update', array('version', '3.0.7-rc2')), + ); + } + + public function update_email_hash($start = 0) + { + $limit = 1000; + + $sql = 'SELECT user_id, user_email, user_email_hash + FROM ' . USERS_TABLE . ' + WHERE user_type <> ' . USER_IGNORE . " + AND user_email <> ''"; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $i = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $i++; + + // Snapshot of the phpbb_email_hash() function + // We cannot call it directly because the auto updater updates the DB first. :/ + $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); + + if ($user_email_hash != $row['user_email_hash']) + { + $sql_ary = array( + 'user_email_hash' => $user_email_hash, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $row['user_id']; + $this->sql_query($sql); + } + } + $this->db->sql_freeresult($result); + + if ($i < $limit) + { + // Completed + return; + } + + // Return the next start, will be sent to $start when this function is called again + return $start + $limit; + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_8.php b/phpBB/includes/db/migration/data/30x/3_0_8.php new file mode 100644 index 0000000000..8998ef9627 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_8.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_8 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.8', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_8_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.8')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php new file mode 100644 index 0000000000..aeff35333e --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php @@ -0,0 +1,221 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.8-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_pl1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'update_bots'))), + array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))), + array('module.add', array( + 'acp', + 'ACP_MESSAGES', + array( + 'module_basename' => 'acp_board', + 'modes' => array('post'), + ), + )), + array('config.add', array('load_unreads_search', 1)), + array('config.update_if_equals', array(600, 'queue_interval', 60)), + array('config.update_if_equals', array(50, 'email_package_size', 20)), + + array('config.update', array('version', '3.0.8-rc1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings. + $sql = 'SELECT lang_dir + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + + $extension_groups_updated = array(); + while ($lang_dir = $this->db->sql_fetchfield('lang_dir')) + { + $lang_dir = basename($lang_dir); + + // The language strings we need are either in language/.../acp/attachments.php + // in the update package if we're updating to 3.0.8-RC1 or later, + // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. + // On an already updated board, they can also already be in language/.../acp/attachments.php + // in the board root. + $lang_files = array( + "{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}", + ); + + foreach ($lang_files as $lang_file) + { + if (!file_exists($lang_file)) + { + continue; + } + + $lang = array(); + include($lang_file); + + foreach($lang as $lang_key => $lang_val) + { + if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) + { + continue; + } + + $sql_ary = array( + 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'"; + $this->sql_query($sql); + + $extension_groups_updated[$lang_key] = true; + } + } + } + $this->db->sql_freeresult($result); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'avatar\''; + $this->sql_query($sql); + } + + public function update_bots() + { + $bot_name = 'Bing [Bot]'; + $bot_name_clean = utf8_clean_string($bot_name); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'"; + $result = $this->db->sql_query($sql); + $bing_already_added = (bool) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if (!$bing_already_added) + { + $bot_agent = 'bingbot/'; + $bot_ip = ''; + $sql = 'SELECT group_id, group_colour + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'BOTS'"; + $result = $this->db->sql_query($sql); + $group_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$group_row) + { + // default fallback, should never get here + $group_row['group_id'] = 6; + $group_row['group_colour'] = '9E8DA7'; + } + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $user_row = array( + 'user_type' => USER_IGNORE, + 'group_id' => $group_row['group_id'], + 'username' => $bot_name, + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => $group_row['group_colour'], + 'user_email' => '', + 'user_lang' => $this->config['default_lang'], + 'user_style' => $this->config['default_style'], + 'user_timezone' => 0, + 'user_dateformat' => $this->config['default_dateformat'], + 'user_allow_massemail' => 0, + ); + + $user_id = user_add($user_row); + + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( + 'bot_active' => 1, + 'bot_name' => (string) $bot_name, + 'user_id' => (int) $user_id, + 'bot_agent' => (string) $bot_agent, + 'bot_ip' => (string) $bot_ip, + )); + + $this->sql_query($sql); + } + } + + public function delete_orphan_shadow_topics() + { + // Delete shadow topics pointing to not existing topics + $batch_size = 500; + + // Set of affected forums we have to resync + $sync_forum_ids = array(); + + $sql_array = array( + 'SELECT' => 't1.topic_id, t1.forum_id', + 'FROM' => array( + TOPICS_TABLE => 't1', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TOPICS_TABLE => 't2'), + 'ON' => 't1.topic_moved_id = t2.topic_id', + ), + ), + 'WHERE' => 't1.topic_moved_id <> 0 + AND t2.topic_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $batch_size); + + $topic_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $topic_ids[] = (int) $row['topic_id']; + + $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($topic_ids)) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids); + $this->db->sql_query($sql); + + // Sync the forums we have deleted shadow topics from. + sync('forum', 'forum_id', $sync_forum_ids, true, true); + + return false; + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9.php b/phpBB/includes/db/migration/data/30x/3_0_9.php new file mode 100644 index 0000000000..d5269ea6f0 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_9.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.9', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php new file mode 100644 index 0000000000..1f8622798e --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php @@ -0,0 +1,124 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.9-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_8'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'login_attempts' => array( + 'COLUMNS' => array( + // this column was removed from the database updater + // after 3.0.9-RC3 was released. It might still exist + // in 3.0.9-RCX installations and has to be dropped in + // 3.0.12 after the db_tools class is capable of properly + // removing a primary key. + // 'attempt_id' => array('UINT', NULL, 'auto_increment'), + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + //'PRIMARY_KEY' => 'attempt_id', + 'KEYS' => array( + 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'att_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'bbcodes' => array( + 'bbcode_id' => array('USINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'login_attempts', + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('ip_login_limit_max', 50)), + array('config.add', array('ip_login_limit_time', 21600)), + array('config.add', array('ip_login_limit_use_forwarded', 0)), + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), + + array('config.update', array('version', '3.0.9-rc1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings, again. + $sql = 'SELECT group_id, group_name + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->any_char); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE group_id = ' . $row['group_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function fix_firebird_qa_captcha() + { + // Recover from potentially broken Q&A CAPTCHA table on firebird + // Q&A CAPTCHA was uninstallable, so it's safe to remove these + // without data loss + if ($this->db_tools->sql_layer == 'firebird') + { + $tables = array( + $this->table_prefix . 'captcha_questions', + $this->table_prefix . 'captcha_answers', + $this->table_prefix . 'qa_confirm', + ); + foreach ($tables as $table) + { + if ($this->db_tools->sql_table_exists($table)) + { + $this->db_tools->sql_table_drop($table); + } + } + } + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php new file mode 100644 index 0000000000..c0e662aa45 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.9-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc2')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php new file mode 100644 index 0000000000..d6d1f14b2e --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.9-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc3')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php new file mode 100644 index 0000000000..e673249343 --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.0.9-rc4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc4')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/310/dev.php b/phpBB/includes/db/migration/data/310/dev.php new file mode 100644 index 0000000000..13b36bbf30 --- /dev/null +++ b/phpBB/includes/db/migration/data/310/dev.php @@ -0,0 +1,405 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_dev extends phpbb_db_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.1.0-dev', '>='); + } + + static public function depends_on() + { + return array( + 'phpbb_db_migration_data_310_extensions', + 'phpbb_db_migration_data_310_style_update_p2', + 'phpbb_db_migration_data_310_timezone_p2', + 'phpbb_db_migration_data_310_reported_posts_display', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm' => array('BOOL', 0), + ), + $this->table_prefix . 'styles' => array( + 'style_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'style_parent_id' => array('UINT:4', 0), + 'style_parent_tree' => array('TEXT', ''), + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text' => array('MTEXT_UNI', ''), + 'reported_post_uid' => array('VCHAR:8', ''), + 'reported_post_bitfield' => array('VCHAR:255', ''), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm', + ), + $this->table_prefix . 'styles' => array( + 'style_path', + 'bbcode_bitfield', + 'style_parent_id', + 'style_parent_tree', + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text', + 'reported_post_uid', + 'reported_post_bitfield', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), + + array('config.add', array('fulltext_postgres_ts_name', 'simple')), + array('config.add', array('fulltext_postgres_min_word_len', 4)), + array('config.add', array('fulltext_postgres_max_word_len', 254)), + array('config.add', array('fulltext_sphinx_stopwords', 0)), + array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)), + + array('config.add', array('load_jquery_cdn', 0)), + array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')), + + array('config.add', array('use_system_cron', 0)), + + array('config.add', array('legend_sort_groupname', 0)), + array('config.add', array('teampage_forums', 1)), + array('config.add', array('teampage_memberships', 1)), + + array('config.add', array('load_cpf_pm', 0)), + + array('config.add', array('display_last_subject', 1)), + + array('config.add', array('assets_version', 1)), + + array('config.add', array('site_home_url', '')), + array('config.add', array('site_home_text', '')), + + array('permission.add', array('u_chgprofileinfo', true, 'u_sig')), + + array('module.add', array( + 'acp', + 'ACP_GROUPS', + array( + 'module_basename' => 'acp_groups', + 'modes' => array('position'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_ATTACHMENTS', + array( + 'module_basename' => 'acp_attachments', + 'modes' => array('manage'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_STYLE_MANAGEMENT', + array( + 'module_basename' => 'acp_styles', + 'modes' => array('install', 'cache'), + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PROFILE', + array( + 'module_basename' => 'ucp_profile', + 'modes' => array('autologin_keys'), + ), + )), + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_LANGUAGE' + )), + + array('module.remove', array( + 'acp', + false, + 'ACP_TEMPLATES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_THEMES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_IMAGESETS', + )), + + array('custom', array(array($this, 'rename_module_basenames'))), + array('custom', array(array($this, 'rename_styles_module'))), + array('custom', array(array($this, 'add_group_teampage'))), + array('custom', array(array($this, 'update_group_legend'))), + array('custom', array(array($this, 'localise_global_announcements'))), + array('custom', array(array($this, 'update_ucp_pm_basename'))), + array('custom', array(array($this, 'update_ucp_profile_auth'))), + array('custom', array(array($this, 'move_customise_modules'))), + + array('config.update', array('version', '3.1.0-dev')), + ); + } + + public function move_customise_modules() + { + // Move language management to new location in the Customise tab + // First get language module id + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_language'"; + $result = $this->db->sql_query($sql); + $language_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + // Next get language management module id of the one just created + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_langname = 'ACP_LANGUAGE'"; + $result = $this->db->sql_query($sql); + $language_management_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + } + // acp_modules calls adm_back_link, which is undefined at this point + if (!function_exists('adm_back_link')) + { + include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); + } + $module_manager = new acp_modules(); + $module_manager->module_class = 'acp'; + $module_manager->move_module($language_module_id, $language_management_module_id); + } + + public function update_ucp_pm_basename() + { + $sql = 'SELECT module_id, module_basename + FROM ' . MODULES_TABLE . " + WHERE module_basename <> 'ucp_pm' AND + module_langname='UCP_PM'"; + $result = $this->db->sql_query_limit($sql, 1); + + if ($row = $this->db->sql_fetchrow($result)) + { + // This update is still not applied. Applying it + + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_basename = 'ucp_pm' + WHERE module_id = " . (int) $row['module_id']; + + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function update_ucp_profile_auth() + { + // Update the auth setting for the module + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_chgprofileinfo' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'profile_info'"; + $this->sql_query($sql); + } + + public function rename_styles_module() + { + // Rename styles module to Customise + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_langname = 'ACP_CAT_CUSTOMISE' + WHERE module_langname = 'ACP_CAT_STYLES'"; + $this->sql_query($sql); + } + + public function rename_module_basenames() + { + // rename all module basenames to full classname + $sql = 'SELECT module_id, module_basename, module_class + FROM ' . MODULES_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $module_id = (int) $row['module_id']; + unset($row['module_id']); + + if (!empty($row['module_basename']) && !empty($row['module_class'])) + { + // all the class names start with class name or with phpbb_ for auto loading + if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && + strpos($row['module_basename'], 'phpbb_') !== 0) + { + $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; + + $sql_update = $this->db->sql_build_array('UPDATE', $row); + + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET ' . $sql_update . ' + WHERE module_id = ' . $module_id; + $this->sql_query($sql); + } + } + } + + $this->db->sql_freeresult($result); + } + + public function add_group_teampage() + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 1 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'ADMINISTRATORS'"; + $this->sql_query($sql); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 2 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'GLOBAL_MODERATORS'"; + $this->sql_query($sql); + } + + public function update_group_legend() + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . ' + WHERE group_legend = 1 + ORDER BY group_name ASC'; + $result = $this->db->sql_query($sql); + + $next_legend = 1; + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . $next_legend . ' + WHERE group_id = ' . (int) $row['group_id']; + $this->sql_query($sql); + + $next_legend++; + } + $this->db->sql_freeresult($result); + } + + public function localise_global_announcements() + { + // Localise Global Announcements + $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = 0 + AND topic_type = ' . POST_GLOBAL; + $result = $this->db->sql_query($sql); + + $global_announcements = $update_lastpost_data = array(); + $update_lastpost_data['forum_last_post_time'] = 0; + $update_forum_data = array( + 'forum_posts' => 0, + 'forum_topics' => 0, + 'forum_topics_real' => 0, + ); + + while ($row = $this->db->sql_fetchrow($result)) + { + $global_announcements[] = (int) $row['topic_id']; + + $update_forum_data['forum_posts'] += (int) $row['topic_posts']; + $update_forum_data['forum_topics_real']++; + if ($row['topic_approved']) + { + $update_forum_data['forum_topics']++; + } + + if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) + { + $update_lastpost_data = array( + 'forum_last_post_id' => (int) $row['topic_last_post_id'], + 'forum_last_post_subject' => $row['topic_last_post_subject'], + 'forum_last_post_time' => (int) $row['topic_last_post_time'], + 'forum_last_poster_id' => (int) $row['topic_last_poster_id'], + 'forum_last_poster_name' => $row['topic_last_poster_name'], + 'forum_last_poster_colour' => $row['topic_last_poster_colour'], + ); + } + } + $this->db->sql_freeresult($result); + + if (!empty($global_announcements)) + { + // Update the post/topic-count for the forum and the last-post if needed + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST; + $result = $this->db->sql_query_limit($sql, 1); + $ga_forum_id = $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT forum_last_post_time + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $ga_forum_id; + $result = $this->db->sql_query($sql); + $lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time'); + $this->db->sql_freeresult($result); + + $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; + $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; + $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; + if ($lastpost < $update_lastpost_data['forum_last_post_time']) + { + $sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data); + } + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $sql_update . ' + WHERE forum_id = ' . $ga_forum_id; + $this->sql_query($sql); + + // Update some forum_ids + $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); + foreach ($table_ary as $table) + { + $sql = "UPDATE $table + SET forum_id = $ga_forum_id + WHERE " . $this->db->sql_in_set('topic_id', $global_announcements); + $this->sql_query($sql); + } + unset($table_ary); + } + } +} diff --git a/phpBB/includes/db/migration/data/310/extensions.php b/phpBB/includes/db/migration/data/310/extensions.php new file mode 100644 index 0000000000..6a9caa1cfc --- /dev/null +++ b/phpBB/includes/db/migration/data/310/extensions.php @@ -0,0 +1,69 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_extensions extends phpbb_db_migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'ext'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'ext' => array( + 'COLUMNS' => array( + 'ext_name' => array('VCHAR', ''), + 'ext_active' => array('BOOL', 0), + 'ext_state' => array('TEXT', ''), + ), + 'KEYS' => array( + 'ext_name' => array('UNIQUE', 'ext_name'), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'ext', + ), + ); + } + + public function update_data() + { + return array( + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_EXTENSION_MANAGEMENT' + )), + array('module.add', array( + 'acp', + 'ACP_EXTENSION_MANAGEMENT', + array( + 'module_basename' => 'acp_extensions', + 'modes' => array('main'), + ), + )), + array('permission.add', array('a_extensions', true, 'a_styles')), + ); + } +} diff --git a/phpBB/includes/db/migration/data/310/notifications.php b/phpBB/includes/db/migration/data/310/notifications.php new file mode 100644 index 0000000000..82bfd4cb2d --- /dev/null +++ b/phpBB/includes/db/migration/data/310/notifications.php @@ -0,0 +1,160 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_notifications extends phpbb_db_migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'notifications'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_dev'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', NULL, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + $this->table_prefix . 'user_notifications' => array( + 'COLUMNS' => array( + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'method' => array('VCHAR:255', ''), + 'notify' => array('BOOL', 1), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + $this->table_prefix . 'user_notifications', + ), + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'ucp', + 'UCP_MAIN', + array( + 'module_basename' => 'ucp_notifications', + 'modes' => array('notification_list'), + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PREFS', + array( + 'module_basename' => 'ucp_notifications', + 'modes' => array('notification_options'), + ), + )), + array('config.add', array('load_notifications', 1)), + array('custom', array(array($this, 'convert_notifications'))), + ); + } + + public function convert_notifications() + { + $convert_notifications = array( + array( + 'check' => ($this->config['allow_topic_notify']), + 'item_type' => 'post', + ), + array( + 'check' => ($this->config['allow_forum_notify']), + 'item_type' => 'topic', + ), + array( + 'check' => ($this->config['allow_bookmarks']), + 'item_type' => 'bookmark', + ), + array( + 'check' => ($this->config['allow_privmsg']), + 'item_type' => 'pm', + ), + ); + + foreach ($convert_notifications as $convert_data) + { + if ($convert_data['check']) + { + $sql = 'SELECT user_id, user_notify_type + FROM ' . USERS_TABLE . ' + WHERE user_notify = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array( + 'item_type' => $convert_data['item_type'], + 'item_id' => 0, + 'user_id' => $row['user_id'], + 'method' => '', + ))); + + if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH) + { + $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array( + 'item_type' => $convert_data['item_type'], + 'item_id' => 0, + 'user_id' => $row['user_id'], + 'method' => 'email', + ))); + } + + if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH) + { + $this->sql_query('INSERT INTO ' . $this->table_prefix . 'user_notifications ' . $this->db->sql_build_array('INSERT', array( + 'item_type' => $convert_data['item_type'], + 'item_id' => 0, + 'user_id' => $row['user_id'], + 'method' => 'jabber', + ))); + } + } + $this->db->sql_freeresult($result); + } + } + } +} diff --git a/phpBB/includes/db/migration/data/310/reported_posts_display.php b/phpBB/includes/db/migration/data/310/reported_posts_display.php new file mode 100644 index 0000000000..80a0a0e43f --- /dev/null +++ b/phpBB/includes/db/migration/data/310/reported_posts_display.php @@ -0,0 +1,47 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_reported_posts_display extends phpbb_db_migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode' => array('BOOL', 1), + 'reported_post_enable_smilies' => array('BOOL', 1), + 'reported_post_enable_magic_url' => array('BOOL', 1), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode', + 'reported_post_enable_smilies', + 'reported_post_enable_magic_url', + ), + ), + ); + } +} diff --git a/phpBB/includes/db/migration/data/310/style_update_p1.php b/phpBB/includes/db/migration/data/310/style_update_p1.php new file mode 100644 index 0000000000..e324ce7f24 --- /dev/null +++ b/phpBB/includes/db/migration/data/310/style_update_p1.php @@ -0,0 +1,157 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_style_update_p1 extends phpbb_db_migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'styles_update'))), + ); + } + + public function styles_update() + { + // Get list of valid 3.1 styles + $available_styles = array('prosilver'); + + $iterator = new DirectoryIterator($this->phpbb_root_path . 'styles'); + $skip_dirs = array('.', '..', 'prosilver'); + foreach ($iterator as $fileinfo) + { + if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) + { + $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); + if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) + { + // 3.1 style + $available_styles[] = $fileinfo->getFilename(); + } + } + } + + // Get all installed styles + if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset')) + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id + AND i.imageset_id = s.imageset_id"; + } + else + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "stles_theme c + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id"; + } + $result = $this->db->sql_query($sql); + + $styles = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $styles[] = $row; + } + $this->db->sql_freeresult($result); + + // Decide which styles to keep, all others will be deleted + $valid_styles = array(); + foreach ($styles as $style_row) + { + if ( + // Delete styles with parent style (not supported yet) + $style_row['template_inherits_id'] == 0 && + // Check if components match + $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && + // Check if components are valid + in_array($style_row['template_path'], $available_styles) + ) + { + // Valid style. Keep it + $sql_ary = array( + 'style_path' => $style_row['template_path'], + 'bbcode_bitfield' => $style_row['bbcode_bitfield'], + 'style_parent_id' => 0, + 'style_parent_tree' => '', + ); + $this->sql_query('UPDATE ' . STYLES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE style_id = ' . $style_row['style_id']); + $valid_styles[] = (int) $style_row['style_id']; + } + } + + // Remove old entries from styles table + if (!sizeof($valid_styles)) + { + // No valid styles: remove everything and add prosilver + $this->sql_query('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary); + + $sql_ary = array( + 'style_name' => 'prosilver', + 'style_copyright' => '© phpBB Group', + 'style_active' => 1, + 'style_path' => 'prosilver', + 'bbcode_bitfield' => 'lNg=', + 'style_parent_id' => 0, + 'style_parent_tree' => '', + + // Will be removed in the next step + 'imageset_id' => 0, + 'template_id' => 0, + 'theme_id' => 0, + ); + + $sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->sql_query($sql); + + $sql = 'SELECT style_id + FROM ' . $table . " + WHERE style_name = 'prosilver'"; + $result = $this->sql_query($sql); + $default_style = $this->db->sql_fetchfield($result); + $this->db->sql_freeresult($result); + + set_config('default_style', $default_style); + + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0'; + $this->sql_query($sql); + } + else + { + // There are valid styles in styles table. Remove styles that are outdated + $this->sql_query('DELETE FROM ' . STYLES_TABLE . ' + WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true)); + + // Change default style + if (!in_array($this->config['default_style'], $valid_styles)) + { + $this->sql_query('UPDATE ' . CONFIG_TABLE . " + SET config_value = '" . $valid_styles[0] . "' + WHERE config_name = 'default_style'"); + } + + // Reset styles for users + $this->sql_query('UPDATE ' . USERS_TABLE . ' + SET user_style = 0 + WHERE ' . $this->db->sql_in_set('user_style', $valid_styles, true)); + } + } +} diff --git a/phpBB/includes/db/migration/data/310/style_update_p2.php b/phpBB/includes/db/migration/data/310/style_update_p2.php new file mode 100644 index 0000000000..7b10518a66 --- /dev/null +++ b/phpBB/includes/db/migration/data/310/style_update_p2.php @@ -0,0 +1,129 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_style_update_p2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_style_update_p1'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id', + 'template_id', + 'theme_id', + ), + ), + + 'drop_tables' => array( + $this->table_prefix . 'styles_imageset', + $this->table_prefix . 'styles_imageset_data', + $this->table_prefix . 'styles_template', + $this->table_prefix . 'styles_template_data', + $this->table_prefix . 'styles_theme', + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id' => array('UINT', 0), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + ), + ), + + 'add_tables' => array( + $this->table_prefix . 'styles_imageset' => array( + 'COLUMNS' => array( + 'imageset_id' => array('UINT', NULL, 'auto_increment'), + 'imageset_name' => array('VCHAR_UNI:255', ''), + 'imageset_copyright' => array('VCHAR_UNI', ''), + 'imageset_path' => array('VCHAR:100', ''), + ), + 'PRIMARY_KEY' => 'imageset_id', + 'KEYS' => array( + 'imgset_nm' => array('UNIQUE', 'imageset_name'), + ), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'COLUMNS' => array( + 'image_id' => array('UINT', NULL, 'auto_increment'), + 'image_name' => array('VCHAR:200', ''), + 'image_filename' => array('VCHAR:200', ''), + 'image_lang' => array('VCHAR:30', ''), + 'image_height' => array('USINT', 0), + 'image_width' => array('USINT', 0), + 'imageset_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'image_id', + 'KEYS' => array( + 'i_d' => array('INDEX', 'imageset_id'), + ), + ), + $this->table_prefix . 'styles_template' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', NULL, 'auto_increment'), + 'template_name' => array('VCHAR_UNI:255', ''), + 'template_copyright' => array('VCHAR_UNI', ''), + 'template_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'template_storedb' => array('BOOL', 0), + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'template_id', + 'KEYS' => array( + 'tmplte_nm' => array('UNIQUE', 'template_name'), + ), + ), + $this->table_prefix . 'styles_template_data' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', 0), + 'template_filename' => array('VCHAR:100', ''), + 'template_included' => array('TEXT', ''), + 'template_mtime' => array('TIMESTAMP', 0), + 'template_data' => array('MTEXT_UNI', ''), + ), + 'KEYS' => array( + 'tid' => array('INDEX', 'template_id'), + 'tfn' => array('INDEX', 'template_filename'), + ), + ), + $this->table_prefix . 'styles_theme' => array( + 'COLUMNS' => array( + 'theme_id' => array('UINT', NULL, 'auto_increment'), + 'theme_name' => array('VCHAR_UNI:255', ''), + 'theme_copyright' => array('VCHAR_UNI', ''), + 'theme_path' => array('VCHAR:100', ''), + 'theme_storedb' => array('BOOL', 0), + 'theme_mtime' => array('TIMESTAMP', 0), + 'theme_data' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'theme_id', + 'KEYS' => array( + 'theme_name' => array('UNIQUE', 'theme_name'), + ), + ), + ), + ); + } +} diff --git a/phpBB/includes/update_helpers.php b/phpBB/includes/db/migration/data/310/timezone.php index 69d678b2f8..6e50cbe45f 100644 --- a/phpBB/includes/update_helpers.php +++ b/phpBB/includes/db/migration/data/310/timezone.php @@ -1,16 +1,67 @@ <?php /** * -* @package phpBB3 +* @package migration * @copyright (c) 2012 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* */ -/** -* phpBB Update Helpers -*/ -class phpbb_update_helpers +class phpbb_db_migration_data_310_timezone extends phpbb_db_migration { + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_timezone' => array('VCHAR:100', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_timezones'))), + ); + } + + public function update_timezones() + { + // Update user timezones + $sql = 'SELECT user_dst, user_timezone + FROM ' . $this->table_prefix . 'users + GROUP BY user_timezone, user_dst'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_timezone = '" . $this->db->sql_escape($this->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' + WHERE user_timezone = '" . $this->db->sql_escape($row['user_timezone']) . "' + AND user_dst = " . (int) $row['user_dst']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + + // Update board default timezone + $sql = 'UPDATE ' . $this->table_prefix . "config + SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "' + WHERE config_name = 'board_timezone'"; + $this->sql_query($sql); + } + /** * Determine the new timezone for a given phpBB 3.0 timezone and * "Daylight Saving Time" option @@ -19,7 +70,7 @@ class phpbb_update_helpers * @param $dst int Users daylight saving time * @return string Users new php Timezone which is used since 3.1 */ - function convert_phpbb30_timezone($timezone, $dst) + public function convert_phpbb30_timezone($timezone, $dst) { $offset = $timezone + $dst; diff --git a/phpBB/includes/db/migration/data/310/timezone_p2.php b/phpBB/includes/db/migration/data/310/timezone_p2.php new file mode 100644 index 0000000000..113b979e4f --- /dev/null +++ b/phpBB/includes/db/migration/data/310/timezone_p2.php @@ -0,0 +1,43 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_timezone_p2 extends phpbb_db_migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_timezone'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst' => array('BOOL', 0), + ), + ), + ); + } +} diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php new file mode 100644 index 0000000000..e84330dd71 --- /dev/null +++ b/phpBB/includes/db/migration/exception.php @@ -0,0 +1,79 @@ +<?php +/** +* +* @package db +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migration_exception extends \Exception +{ + /** + * Extra parameters sent to exception to aid in debugging + * @var array + */ + protected $parameters; + + /** + * Throw an exception. + * + * First argument is the error message. + * Additional arguments will be output with the error message. + */ + public function __construct() + { + $parameters = func_get_args(); + $message = array_shift($parameters); + parent::__construct($message); + + $this->parameters = $parameters; + } + + /** + * Output the error as a string + * + * @return string + */ + public function __toString() + { + return $this->message . ': ' . var_export($this->parameters, true); + } + + /** + * Get the parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get localised message (with $user->lang()) + * + * @param phpbb_user $user + * @return string + */ + public function getLocalisedMessage(phpbb_user $user) + { + $parameters = $this->getParameters(); + array_unshift($parameters, $this->getMessage()); + + return call_user_func_array(array($user, 'lang'), $parameters); + } +} 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 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Abstract base class for database migrations +* +* Each migration consists of a set of schema and data changes to be implemented +* in a subclass. This class provides various utility methods to simplify editing +* a phpBB. +* +* @package db +*/ +abstract class phpbb_db_migration +{ + /** @var phpbb_config */ + protected $config; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_db_tools */ + protected $db_tools; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var array Errors, if any occured */ + protected $errors; + + /** @var array List of queries executed through $this->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..458a25fb66 --- /dev/null +++ b/phpBB/includes/db/migration/tool/config.php @@ -0,0 +1,150 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration config tool +* +* @package db +*/ +class phpbb_db_migration_tool_config implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_config */ + protected $config; + + /** + * Constructor + * + * @param phpbb_config $config + */ + public function __construct(phpbb_config $config) + { + $this->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_EXIST', $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 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration tool interface +* +* @package db +*/ +interface phpbb_db_migration_tool_interface +{ + /** + * Retrieve a short name used for commands in migrations. + * + * @return string short name + */ + public function get_name(); + + /** + * Reverse an original install action + * + * First argument is the original call to the class (e.g. add, remove) + * After the first argument, send the original arguments to the function in the original call + * + * @return null + */ + public function reverse(); +} diff --git a/phpBB/includes/db/migration/tool/module.php b/phpBB/includes/db/migration/tool/module.php new file mode 100644 index 0000000000..ad94c5aadb --- /dev/null +++ b/phpBB/includes/db/migration/tool/module.php @@ -0,0 +1,502 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration module management tool +* +* @package db +*/ +class phpbb_db_migration_tool_module implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_cache_service */ + protected $cache; + + /** @var dbal */ + protected $db; + + /** @var phpbb_user */ + protected $user; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $modules_table; + + /** + * Constructor + * + * @param phpbb_db_driver $db + * @param mixed $cache + * @param phpbb_user $user + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $modules_table + */ + public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_user $user, $phpbb_root_path, $php_ext, $modules_table) + { + $this->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); + + $module = $this->get_module_info($class, $basename); + + $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_NOT_EXIST', $parent); + } + + $parent = $data['parent_id'] = $module_id; + } + else if (!$this->exists($class, false, $parent)) + { + throw new phpbb_db_migration_exception('MODULE_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); + + $module_info = $this->get_module_info($class, $basename); + + foreach ($module_info['modes'] as $mode => $info) + { + if (!isset($module['modes']) || in_array($mode, $module['modes'])) + { + $this->remove($class, $parent, $info['title']); + } + } + } + else + { + if (!$this->exists($class, $parent, $module)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', ((isset($this->user->lang[$module])) ? $this->user->lang[$module] : $module)); + } + + $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); + + // we know it exists from the module_exists check + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $module_ids = array(); + if (!is_numeric($module)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($module) . "' + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + while ($module_id = $this->db->sql_fetchfield('module_id')) + { + $module_ids[] = (int) $module_id; + } + $this->db->sql_freeresult($result); + + $module_name = $module; + } + else + { + $module = (int) $module; + $sql = 'SELECT module_langname + FROM ' . $this->modules_table . " + WHERE module_id = $module + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + $module_name = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $module_ids[] = $module; + } + + 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(); + $acp_modules->module_class = $class; + + foreach ($module_ids as $module_id) + { + $result = $acp_modules->delete_module($module_id); + if (!empty($result)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_REMOVABLE', $module_id, $result); + } + } + + $this->cache->destroy("_modules_$class"); + } + } + + /** + * {@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; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } + + /** + * Wrapper for acp_modules::get_module_infos() + * + * @param string $class Module Class + * @param string $basename Module Basename + * @return array Module Information + */ + protected function get_module_info($class, $basename) + { + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + } + $acp_modules = new acp_modules(); + $module = $acp_modules->get_module_infos($basename, $class, true); + + if (empty($module)) + { + throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename); + } + + return array_pop($module); + } +} diff --git a/phpBB/includes/db/migration/tool/permission.php b/phpBB/includes/db/migration/tool/permission.php new file mode 100644 index 0000000000..4231fbe1dd --- /dev/null +++ b/phpBB/includes/db/migration/tool/permission.php @@ -0,0 +1,622 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration permission management tool +* +* @package db +*/ +class phpbb_db_migration_tool_permission implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_auth */ + protected $auth; + + /** @var phpbb_cache_service */ + protected $cache; + + /** @var dbal */ + protected $db; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** + * Constructor + * + * @param phpbb_db_driver $db + * @param mixed $cache + * @param phpbb_auth $auth + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_auth $auth, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->cache = $cache; + $this->auth = $auth; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'permission'; + } + + /** + * Permission Exists + * + * Check if a permission (auth) setting exists + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return bool true if it exists, false if not + */ + public function exists($auth_option, $global = true) + { + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" + . $type_sql; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return true; + } + + return false; + } + + /** + * Permission Add + * + * Add a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function add($auth_option, $global = true, $copy_from = false) + { + if ($this->exists($auth_option, $global)) + { + throw new phpbb_db_migration_exception('PERMISSION_ALREADY_EXIST', $auth_option); + } + + // We've added permissions, so set to true to notify the user. + $this->permissions_added = true; + + if (!class_exists('auth_admin')) + { + include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + } + $auth_admin = new auth_admin(); + + // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. + if ($this->exists($auth_option, !$global)) + { + $sql_ary = array( + 'is_global' => 1, + 'is_local' => 1, + ); + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; + $this->db->sql_query($sql); + } + else + { + if ($global) + { + $auth_admin->acl_add_option(array('global' => array($auth_option))); + } + else + { + $auth_admin->acl_add_option(array('local' => array($auth_option))); + } + } + + // The permission has been added, now we can copy it if needed + if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) + { + $old_id = $auth_admin->acl_options['id'][$copy_from]; + $new_id = $auth_admin->acl_options['id'][$auth_option]; + + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + + foreach ($tables as $table) + { + $sql = 'SELECT * + FROM ' . $table . ' + WHERE auth_option_id = ' . $old_id; + $result = $this->db->sql_query($sql); + + $sql_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['auth_option_id'] = $new_id; + $sql_ary[] = $row; + } + $this->db->sql_freeresult($result); + + if (!empty($sql_ary)) + { + $this->db->sql_multi_insert($table, $sql_ary); + } + } + + $auth_admin->acl_clear_prefetch(); + } + } + + /** + * Permission Remove + * + * Remove a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function remove($auth_option, $global = true) + { + if (!$this->exists($auth_option, $global)) + { + throw new phpbb_db_migration_exception('PERMISSION_NOT_EXIST', $auth_option); + } + + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + $sql = 'SELECT auth_option_id, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . + $type_sql; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $id = (int) $row['auth_option_id']; + + // If it is a local and global permission, do not remove the row! :P + if ($row['is_global'] && $row['is_local']) + { + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' + WHERE auth_option_id = ' . $id; + $this->db->sql_query($sql); + } + else + { + // Delete time + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); + foreach ($tables as $table) + { + $this->db->sql_query('DELETE FROM ' . $table . ' + WHERE auth_option_id = ' . $id); + } + } + + // Purge the auth cache + $this->cache->destroy('_acl_options'); + $this->auth->acl_clear_prefetch(); + } + + /** + * Add a new permission role + * + * @param string $role_name The new role name + * @param sting $role_type The type (u_, m_, a_) + * @return null + */ + public function role_add($role_name, $role_type, $role_description = '') + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if ($role_id) + { + return; + } + + $sql = 'SELECT MAX(role_order) AS max_role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; + $this->db->sql_query($sql); + $role_order = (int) $this->db->sql_fetchfield('max_role_order'); + $role_order = (!$role_order) ? 1 : $role_order + 1; + + $sql_ary = array( + 'role_name' => $role_name, + 'role_description' => $role_description, + 'role_type' => $role_type, + 'role_order' => $role_order, + ); + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + } + + /** + * Update the name on a permission role + * + * @param string $old_role_name The old role name + * @param string $new_role_name The new role name + * @return null + */ + public function role_update($old_role_name, $new_role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name); + } + + $sql = 'UPDATE ' . ACL_ROLES_TABLE . " + SET role_name = '" . $this->db->sql_escape($new_role_name) . "' + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Remove a permission role + * + * @param string $role_name The role name to remove + * @return null + */ + public function role_remove($role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $role_name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Set + * + * Allows you to set permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @param bool $has_permission True if you want to give them permission, + * false if you want to deny them permission + * @return null + */ + public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $new_auth = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $new_auth[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($new_auth)) + { + return; + } + + $current_auth = array(); + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will add the requested permissions to that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0 + AND forum_id = 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->set($role_name, $auth_option, 'role', $has_permission); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + } + + $sql_ary = array(); + switch ($type) + { + case 'role': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'role_id' => $role_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); + break; + + case 'group': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'group_id' => $group_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Unset + * + * Allows you to unset (remove) permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @return null + */ + public function permission_unset($name, $auth_option, $type = 'role') + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $to_remove = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $to_remove[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($to_remove)) + { + return; + } + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will remove the requested permissions from that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->permission_unset($role_name, $auth_option, 'role'); + } + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * {@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 'permission_set': + $call = 'permission_unset'; + break; + + case 'permission_unset': + $call = 'permission_set'; + break; + + case 'role_add': + $call = 'role_remove'; + break; + + case 'role_remove': + $call = 'role_add'; + break; + + case 'role_update': + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/includes/db/migrator.php b/phpBB/includes/db/migrator.php new file mode 100644 index 0000000000..de9c06948c --- /dev/null +++ b/phpBB/includes/db/migrator.php @@ -0,0 +1,757 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migrator +{ + /** @var phpbb_config */ + protected $config; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_db_tools */ + protected $db_tools; + + /** @var phpbb_extension_manager */ + protected $extension_manager; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $migrations_table; + + /** + * State of all migrations + * + * (SELECT * FROM migrations table) + * + * @var array + */ + protected $migration_state = array(); + + /** + * Array of all migrations available to be run + * + * @var array + */ + protected $migrations = array(); + + /** + * 'name' and 'class' of the last migration run + * + * @var array + */ + public $last_run_migration = false; + + /** + * Constructor of the database migrator + */ + public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + + $this->migrations_table = $migrations_table; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->table_prefix = $table_prefix; + + foreach ($tools as $tool) + { + $this->tools[$tool->get_name()] = $tool; + } + + $this->load_migration_state(); + } + + /** + * Set Extension Manager (required) + * + * Not in constructor to prevent circular reference error + */ + public function set_extension_manager(phpbb_extension_manager $extension_manager) + { + $this->extension_manager = $extension_manager; + } + + /** + * Loads all migrations and their application state from the database. + * + * @return null + */ + public function load_migration_state() + { + $this->migration_state = array(); + + // prevent errors in case the table does not exist yet + $this->db->sql_return_on_error(true); + + $sql = "SELECT * + FROM " . $this->migrations_table; + $result = $this->db->sql_query($sql); + + if (!$this->db->sql_error_triggered) + { + while ($migration = $this->db->sql_fetchrow($result)) + { + $this->migration_state[$migration['migration_name']] = $migration; + + $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); + } + } + + $this->db->sql_freeresult($result); + + $this->db->sql_return_on_error(false); + } + + /** + * Sets the list of available migration class names to the given array. + * + * @param array $class_names An array of migration class names + * @return null + */ + public function set_migrations($class_names) + { + $this->migrations = $class_names; + } + + /** + * This function adds all migrations in a specified directory to the migrations table + * + * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. + * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! + * + * @param string $path Path to migration data files + * @param bool $recursive Set to true to also load data files from subdirectories + * @return null + */ + public function populate_migrations_from_directory($path, $recursive = true) + { + $existing_migrations = $this->migrations; + + $this->migrations = array(); + $this->load_migrations($path, true, $recursive); + + foreach ($this->migrations as $name) + { + if ($this->migration_state($name) === false) + { + $state = array( + 'migration_depends_on' => $name::depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => time(), + 'migration_end_time' => time(), + ); + $this->insert_migration($name, $state); + } + } + + $this->migrations = $existing_migrations; + } + + /** + * Load migration data files from a directory + * + * Migration data files loaded with this function MUST contain + * ONLY ONE class in them (or an exception will be thrown). + * + * @param string $path Path to migration data files + * @param bool $check_fulfillable If TRUE (default), we will check + * if all of the migrations are fulfillable after loading them. + * If FALSE, we will not check. You SHOULD check at least once + * to prevent errors (if including multiple directories, check + * with the last call to prevent throwing errors unnecessarily). + * @return array Array of migration names + */ + public function load_migrations($path, $check_fulfillable = true) + { + if (!is_dir($path)) + { + throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path); + } + + $migrations = array(); + + $finder = $this->extension_manager->get_finder(); + $files = $finder + ->extension_directory("/") + ->find_from_paths(array('/' => $path)); + foreach ($files as $file) + { + $migrations[$file['path'] . $file['filename']] = ''; + } + $migrations = $finder->get_classes_from_files($migrations); + + foreach ($migrations as $migration) + { + if (!in_array($migration, $this->migrations)) + { + $this->migrations[] = $migration; + } + } + + if ($check_fulfillable) + { + foreach ($this->migrations as $name) + { + $unfulfillable = $this->unfulfillable($name); + if ($unfulfillable !== false) + { + throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable); + } + } + } + + return $this->migrations; + } + + /** + * Runs a single update step from the next migration to be applied. + * + * The update step can either be a schema or a (partial) data update. To + * check if update() needs to be called again use the finished() method. + * + * @return null + */ + public function update() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name]) || + !$this->migration_state[$name]['migration_schema_done'] || + !$this->migration_state[$name]['migration_data_done']) + { + if (!$this->try_apply($name)) + { + continue; + } + else + { + return; + } + } + } + } + + /** + * Attempts to apply a step of the given migration or one of its dependencies + * + * @param string The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_apply($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = (isset($this->migration_state[$name])) ? + $this->migration_state[$name] : + array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => false, + 'migration_data_done' => false, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + foreach ($state['migration_depends_on'] as $depend) + { + if (!isset($this->migration_state[$depend]) || + !$this->migration_state[$depend]['migration_schema_done'] || + !$this->migration_state[$depend]['migration_data_done']) + { + return $this->try_apply($depend); + } + } + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + ); + + if (!isset($this->migration_state[$name])) + { + if ($migration->effectively_installed()) + { + $state = array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + } + else + { + $state['migration_start_time'] = time(); + } + } + + if (!$state['migration_schema_done']) + { + $this->apply_schema_changes($migration->update_schema()); + $state['migration_schema_done'] = true; + } + else if (!$state['migration_data_done']) + { + try + { + $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true); + $state['migration_end_time'] = ($result === true) ? time() : 0; + } + catch (phpbb_db_migration_exception $e) + { + // Revert the schema changes + $this->revert($name); + + // Rethrow exception + throw $e; + } + } + + $this->insert_migration($name, $state); + + return true; + } + + /** + * Runs a single revert step from the last migration installed + * + * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! + * The revert step can either be a schema or a (partial) data revert. To + * check if revert() needs to be called again use the migration_state() method. + * + * @param string $migration String migration name to revert (including any that depend on this migration) + * @return null + */ + public function revert($migration) + { + if (!isset($this->migration_state[$migration])) + { + // Not installed + return; + } + + foreach ($this->migration_state as $name => $state) + { + if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on'])) + { + $this->revert($name); + } + } + + $this->try_revert($migration); + } + + /** + * Attempts to revert a step of the given migration or one of its dependencies + * + * @param string The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_revert($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = $this->migration_state[$name]; + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + ); + + if ($state['migration_data_done']) + { + if ($state['migration_data_state'] !== 'revert_data') + { + $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); + + $state['migration_data_state'] = ($result === true) ? 'revert_data' : $result; + } + else + { + $result = $this->process_data_step($migration->revert_data(), '', false); + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true) ? false : true; + } + + $this->insert_migration($name, $state); + } + else + { + $this->apply_schema_changes($migration->revert_schema()); + + $sql = 'DELETE FROM ' . $this->migrations_table . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + unset($this->migration_state[$name]); + } + + return true; + } + + /** + * Apply schema changes from a migration + * + * Just calls db_tools->perform_schema_changes + * + * @param array $schema_changes from migration + */ + protected function apply_schema_changes($schema_changes) + { + $this->db_tools->perform_schema_changes($schema_changes); + } + + /** + * Process the data step of the migration + * + * @param array $steps The steps to run + * @param bool|string $state Current state of the migration + * @param bool $revert true to revert a data step + * @return bool|string migration state. True if completed, serialized array if not finished + */ + protected function process_data_step($steps, $state, $revert = false) + { + $state = ($state) ? unserialize($state) : false; + + foreach ($steps as $step_identifier => $step) + { + $last_result = false; + if ($state) + { + // Continue until we reach the step that matches the last step called + if ($state['step'] != $step_identifier) + { + continue; + } + + // We send the result from last time to the callable function + $last_result = $state['result']; + + // Set state to false since we reached the point we were at + $state = false; + } + + try + { + // Result will be null or true if everything completed correctly + $result = $this->run_step($step, $last_result, $revert); + if ($result !== null && $result !== true) + { + return serialize(array( + 'result' => $result, + 'step' => $step_identifier, + )); + } + } + catch (phpbb_db_migration_exception $e) + { + // We should try rolling back here + foreach ($steps as $reverse_step_identifier => $reverse_step) + { + // If we've reached the current step we can break because we reversed everything that was run + if ($reverse_step_identifier == $step_identifier) + { + break; + } + + // Reverse the step that was run + $result = $this->run_step($reverse_step, false, !$revert); + } + + // rethrow the exception + throw $e; + } + } + + return true; + } + + /** + * Run a single step + * + * An exception should be thrown if an error occurs + * + * @param mixed $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return null + */ + protected function run_step($step, $last_result = false, $reverse = false) + { + $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); + + if ($callable_and_parameters === false) + { + return; + } + + $callable = $callable_and_parameters[0]; + $parameters = $callable_and_parameters[1]; + + return call_user_func_array($callable, $parameters); + } + + /** + * Get a callable statement from a data step + * + * @param array $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters + */ + protected function get_callable_from_step(array $step, $last_result = false, $reverse = false) + { + $type = $step[0]; + $parameters = $step[1]; + + $parts = explode('.', $type); + + $class = $parts[0]; + $method = false; + + if (isset($parts[1])) + { + $method = $parts[1]; + } + + switch ($class) + { + case 'if': + if (!isset($parameters[0])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); + } + + if (!isset($parameters[1])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); + } + + $condition = $parameters[0]; + + if (!$condition) + { + return false; + } + + $step = $parameters[1]; + + return $this->get_callable_from_step($step); + break; + case 'custom': + if (!is_callable($parameters[0])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); + } + + return array( + $parameters[0], + array($last_result), + ); + break; + + default: + if (!$method) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); + } + + if (!isset($this->tools[$class])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); + } + + if (!method_exists(get_class($this->tools[$class]), $method)) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); + } + + // Attempt to reverse operations + if ($reverse) + { + array_unshift($parameters, $method); + + return array( + array($this->tools[$class], 'reverse'), + $parameters, + ); + } + + return array( + array($this->tools[$class], $method), + $parameters, + ); + break; + } + } + + /** + * Insert/Update migration row into the database + * + * @param string $name Name of the migration + * @param array $state + * @return null + */ + protected function insert_migration($name, $state) + { + $migration_row = $state; + $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); + + if (isset($this->migration_state[$name])) + { + $sql = 'UPDATE ' . $this->migrations_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + } + else + { + $migration_row['migration_name'] = $name; + $sql = 'INSERT INTO ' . $this->migrations_table . ' + ' . $this->db->sql_build_array('INSERT', $migration_row); + $this->db->sql_query($sql); + } + + $this->migration_state[$name] = $state; + } + + /** + * Checks if a migration's dependencies can even theoretically be satisfied. + * + * @param string $name The class name of the migration + * @return bool|string False if fulfillable, string of missing migration name if unfulfillable + */ + public function unfulfillable($name) + { + if (isset($this->migration_state[$name])) + { + return false; + } + + if (!class_exists($name)) + { + return $name; + } + + $migration = $this->get_migration($name); + $depends = $migration->depends_on(); + + foreach ($depends as $depend) + { + $unfulfillable = $this->unfulfillable($depend); + if ($unfulfillable !== false) + { + return $unfulfillable; + } + } + + return false; + } + + /** + * Checks whether all available, fulfillable migrations have been applied. + * + * @return bool Whether the migrations have been applied + */ + public function finished() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name])) + { + // skip unfulfillable migrations, but fulfillables mean we + // are not finished yet + if ($this->unfulfillable($name) !== false) + { + continue; + } + return false; + } + + $migration = $this->migration_state[$name]; + + if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) + { + return false; + } + } + + return true; + } + + /** + * Gets a migration state (whether it is installed and to what extent) + * + * @param string $migration String migration name to check if it is installed + * @return bool|array False if the migration has not at all been installed, array + */ + public function migration_state($migration) + { + if (!isset($this->migration_state[$migration])) + { + return false; + } + + return $this->migration_state[$migration]; + } + + /** + * Helper to get a migration + * + * @param string $name Name of the migration + * @return phpbb_db_migration + */ + protected function get_migration($name) + { + return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); + } +} diff --git a/phpBB/includes/di/extension/config.php b/phpBB/includes/di/extension/config.php index 97a6290066..6c272a6588 100644 --- a/phpBB/includes/di/extension/config.php +++ b/phpBB/includes/di/extension/config.php @@ -42,6 +42,7 @@ class phpbb_di_extension_config extends Extension { require($this->config_file); + $container->setParameter('core.adm_relative_path', (isset($phpbb_adm_relative_path) ? $phpbb_adm_relative_path : 'adm/')); $container->setParameter('core.table_prefix', $table_prefix); $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type)); $container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms)); diff --git a/phpBB/includes/extension/base.php b/phpBB/includes/extension/base.php index 9d076eb6c5..d51589d719 100644 --- a/phpBB/includes/extension/base.php +++ b/phpBB/includes/extension/base.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\ContainerInterface; + /** * A base class for extensions without custom enable/disable/purge code. * @@ -22,6 +24,19 @@ if (!defined('IN_PHPBB')) */ class phpbb_extension_base implements phpbb_extension_interface { + /** @var ContainerInterface */ + protected $container; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + /** * Single enable step that does nothing * diff --git a/phpBB/includes/extension/finder.php b/phpBB/includes/extension/finder.php index fb19b98429..f71e32bc8d 100644 --- a/phpBB/includes/extension/finder.php +++ b/phpBB/includes/extension/finder.php @@ -247,15 +247,28 @@ class phpbb_extension_finder * phpBB naming rules an incorrect class name will be returned. * * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions * @return array An array of found class names */ - public function get_classes($cache = true) + public function get_classes($cache = true, $use_all_available = false) { $this->query['extension_suffix'] .= $this->php_ext; $this->query['core_suffix'] .= $this->php_ext; - $files = $this->find($cache, false); + $files = $this->find($cache, false, $use_all_available); + return $this->get_classes_from_files($files); + } + + /** + * Get class names from a list of files + * + * @param array $files Array of files (from find()) + * @return array Array of class names + */ + public function get_classes_from_files($files) + { $classes = array(); foreach ($files as $file => $ext_name) { @@ -270,23 +283,27 @@ class phpbb_extension_finder * Finds all directories matching the configured options * * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions * @param bool $extension_keys Whether the result should have extension name as array key * @return array An array of paths to found directories */ - public function get_directories($cache = true, $extension_keys = false) + public function get_directories($cache = true, $use_all_available = false, $extension_keys = false) { - return $this->find_with_root_path($cache, true, $extension_keys); + return $this->find_with_root_path($cache, true, $use_all_available, $extension_keys); } /** * Finds all files matching the configured options. * * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions * @return array An array of paths to found files */ - public function get_files($cache = true) + public function get_files($cache = true, $use_all_available = false) { - return $this->find_with_root_path($cache, false); + return $this->find_with_root_path($cache, false, $use_all_available); } /** @@ -295,13 +312,15 @@ class phpbb_extension_finder * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise + * @param bool $use_all_available Use all available instead of just all + * enabled extensions * @param bool $extension_keys If true, result will be associative array * with extension name as key * @return array An array of paths to found items */ - protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false) + protected function find_with_root_path($cache = true, $is_dir = false, $use_all_available = false, $extension_keys = false) { - $items = $this->find($cache, $is_dir); + $items = $this->find($cache, $is_dir, $use_all_available); $result = array(); foreach ($items as $item => $ext_name) @@ -325,27 +344,59 @@ class phpbb_extension_finder * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise + * @param bool $use_all_available Use all available instead of just all + * enabled extensions * @return array An array of paths to found items */ - public function find($cache = true, $is_dir = false) + public function find($cache = true, $is_dir = false, $use_all_available = false) { - $this->query['is_dir'] = $is_dir; - $query = md5(serialize($this->query)); + if ($use_all_available) + { + $extensions = $this->extension_manager->all_available(); + } + else + { + $extensions = $this->extension_manager->all_enabled(); + } - if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) + if ($this->query['core_path']) { - return $this->cached_queries[$query]; + $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; } $files = array(); + $file_list = $this->find_from_paths($extensions, $cache, $is_dir); - $extensions = $this->extension_manager->all_enabled(); + foreach ($file_list as $file) + { + $files[$file['named_path']] = $file['ext_name']; + } - if ($this->query['core_path']) + return $files; + } + + /** + * Finds all file system entries matching the configured options from + * an array of paths + * + * @param array $extensions Array of extensions (name => full relative path) + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find_from_paths($extensions, $cache = true, $is_dir = false) + { + $this->query['is_dir'] = $is_dir; + $query = md5(serialize($this->query) . serialize($extensions)); + + if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) { - $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; + return $this->cached_queries[$query]; } + $files = array(); + foreach ($extensions as $name => $path) { $ext_name = $name; @@ -419,7 +470,12 @@ class phpbb_extension_finder (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && (!$directory || preg_match($directory_pattern, $relative_path))) { - $files[str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1))] = $ext_name; + $files[] = array( + 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)), + 'ext_name' => $ext_name, + 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/', + 'filename' => $filename, + ); } } } diff --git a/phpBB/includes/extension/interface.php b/phpBB/includes/extension/interface.php index 74ecb9b762..7b36a12bf6 100644 --- a/phpBB/includes/extension/interface.php +++ b/phpBB/includes/extension/interface.php @@ -45,7 +45,9 @@ interface phpbb_extension_interface * * @param mixed $old_state The return value of the previous call * of this method, or false on the first call - * @return null + * @return mixed Returns false after last step, otherwise + * temporary state which is passed as an + * argument to the next step */ public function disable_step($old_state); diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index 67cc81e407..0d760681b9 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\ContainerInterface; + /** * The extension manager provides means to activate/deactivate extensions. * @@ -22,8 +24,12 @@ if (!defined('IN_PHPBB')) */ class phpbb_extension_manager { + /** @var ContainerInterface */ + protected $container; + protected $db; protected $config; + protected $migrator; protected $cache; protected $php_ext; protected $extensions; @@ -34,6 +40,7 @@ class phpbb_extension_manager /** * Creates a manager and loads information from database * + * @param ContainerInterface $container A container * @param phpbb_db_driver $db A database connection * @param phpbb_config $config phpbb_config * @param string $extension_table The name of the table holding extensions @@ -42,8 +49,9 @@ class phpbb_extension_manager * @param phpbb_cache_driver_interface $cache A cache instance or null * @param string $cache_name The name of the cache variable, defaults to _ext */ - public function __construct(phpbb_db_driver $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') + public function __construct(ContainerInterface $container, phpbb_db_driver $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') { + $this->container = $container; $this->phpbb_root_path = $phpbb_root_path; $this->db = $db; $this->config = $config; @@ -61,6 +69,14 @@ class phpbb_extension_manager } /** + * Set migrator (get around circular reference) + */ + public function set_migrator(phpbb_db_migrator $migrator) + { + $this->migrator = $migrator; + } + + /** * Loads all extension information from the database * * @return null @@ -126,11 +142,11 @@ class phpbb_extension_manager if (class_exists($extension_class_name)) { - return new $extension_class_name; + return new $extension_class_name($this->container); } else { - return new phpbb_extension_base; + return new phpbb_extension_base($this->container); } } @@ -166,6 +182,12 @@ class phpbb_extension_manager $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; + // Returns false if not completed + if (!$this->handle_migrations($name, 'enable')) + { + return true; + } + $extension = $this->get_extension($name); $state = $extension->enable_step($old_state); @@ -317,6 +339,12 @@ class phpbb_extension_manager $old_state = unserialize($this->extensions[$name]['ext_state']); + // Returns false if not completed + if (!$this->handle_migrations($name, 'purge')) + { + return true; + } + $extension = $this->get_extension($name); $state = $extension->purge_step($old_state); @@ -384,7 +412,7 @@ class phpbb_extension_manager } $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/'), + new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', FilesystemIterator::NEW_CURRENT_AND_KEY | FilesystemIterator::FOLLOW_SYMLINKS), RecursiveIteratorIterator::SELF_FIRST); foreach ($iterator as $file_info) { @@ -490,4 +518,58 @@ class phpbb_extension_manager { return new phpbb_extension_finder($this, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); } + + /** + * Handle installing/reverting migrations + * + * @param string $extension_name Name of the extension + * @param string $mode enable or purge + * @return bool True if completed, False if not completed + */ + protected function handle_migrations($extension_name, $mode) + { + $migrations_path = $this->phpbb_root_path . $this->get_extension_path($extension_name) . 'migrations/'; + if (!file_exists($migrations_path) || !is_dir($migrations_path)) + { + return true; + } + + $migrations = $this->migrator->load_migrations($migrations_path); + + // What is a safe limit of execution time? Half the max execution time should be safe. + $safe_time_limit = (ini_get('max_execution_time') / 2); + $start_time = time(); + + if ($mode == 'enable') + { + while (!$this->migrator->finished()) + { + $this->migrator->update(); + + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + return false; + } + } + } + else if ($mode == 'purge') + { + foreach ($migrations as $migration) + { + while ($this->migrator->migration_state($migration) !== false) + { + $this->migrator->revert($migration); + + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + return false; + } + } + } + } + + return true; + } } diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index d8fd2ff6e6..5701b8baca 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -97,7 +97,18 @@ function request_var($var_name, $default, $multibyte = false, $cookie = false, $ } /** -* Set config value. Creates missing config entry. +* Sets a configuration option's value. +* +* Please note that this function does not update the is_dynamic value for +* an already existing config option. +* +* @param string $config_name The configuration option's name +* @param string $config_value New configuration value +* @param bool $is_dynamic Whether this variable should be cached (false) or +* if it changes too frequently (true) to be +* efficiently cached. +* +* @return null * * @deprecated */ @@ -119,7 +130,15 @@ function set_config($config_name, $config_value, $is_dynamic = false, phpbb_conf } /** -* Set dynamic config value with arithmetic operation. +* Increments an integer config value directly in the database. +* +* @param string $config_name The configuration option's name +* @param int $increment Amount to increment by +* @param bool $is_dynamic Whether this variable should be cached (false) or +* if it changes too frequently (true) to be +* efficiently cached. +* +* @return null * * @deprecated */ @@ -1328,7 +1347,7 @@ function phpbb_timezone_select($user, $default = '', $truncate = false) function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0) { global $db, $user, $config; - global $request; + global $request, $phpbb_container; $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time; @@ -1336,6 +1355,20 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ { if ($forum_id === false || !sizeof($forum_id)) { + // Mark all forums read (index page) + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark all topic notifications read for this user + $phpbb_notifications->mark_notifications_read(array( + 'topic', + 'quote', + 'bookmark', + 'post', + 'approve_topic', + 'approve_post', + ), false, $user->data['user_id'], $post_time); + if ($config['load_db_lastread'] && $user->data['is_registered']) { // Mark all forums read (index page) @@ -1390,6 +1423,32 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $forum_id = array($forum_id); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'topic', + 'approve_topic', + ), $forum_id, $user->data['user_id'], $post_time); + + // Mark all post/quote notifications read for this user in this forum + $topic_ids = array(); + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $topic_ids[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'quote', + 'bookmark', + 'post', + 'approve_post', + ), $topic_ids, $user->data['user_id'], $post_time); + // Add 0 to forums array to mark global announcements correctly // $forum_id[] = 0; @@ -1487,6 +1546,21 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ return; } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark post notifications read for this user in this topic + $phpbb_notifications->mark_notifications_read(array( + 'topic', + 'approve_topic', + ), $topic_id, $user->data['user_id'], $post_time); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'quote', + 'bookmark', + 'post', + 'approve_post', + ), $topic_id, $user->data['user_id'], $post_time); + if ($config['load_db_lastread'] && $user->data['is_registered']) { $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . " @@ -4995,7 +5069,7 @@ function phpbb_build_hidden_fields_for_query_params($request, $exclude = null) function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum') { global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path; - global $phpbb_dispatcher, $request; + global $phpbb_dispatcher, $request, $phpbb_container; if (defined('HEADER_INC')) { @@ -5184,8 +5258,26 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 $timezone_name = $user->lang['timezones'][$timezone_name]; } + // Output the notifications + $notifications = false; + if ($config['load_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) + { + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $notifications = $phpbb_notifications->load_notifications(array( + 'all_unread' => true, + 'limit' => 5, + )); + + foreach ($notifications['notifications'] as $notification) + { + $template->assign_block_vars('notifications', $notification->prepare_for_display()); + } + } + $hidden_fields_for_jumpbox = phpbb_build_hidden_fields_for_query_params($request, array('f')); + // The following assigns all _common_ variables that may be used at any point in a template. $template->assign_vars(array( 'SITENAME' => $config['sitename'], @@ -5202,6 +5294,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread, 'HIDDEN_FIELDS_FOR_JUMPBOX' => $hidden_fields_for_jumpbox, + 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', + 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $user->lang('NOTIFICATIONS_COUNT', $notifications['unread_count']) : '', + 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'), + 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'), + 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'], + 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'], 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'], 'S_USER_NEW' => $user->data['user_new'], @@ -5340,7 +5438,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 function page_footer($run_cron = true, $display_template = true, $exit_handler = true) { global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx; - global $request, $phpbb_dispatcher; + global $request, $phpbb_dispatcher, $phpbb_admin_path; // A listener can set this variable to `true` when it overrides this function $page_footer_override = false; @@ -5396,7 +5494,7 @@ function page_footer($run_cron = true, $display_template = true, $exit_handler = 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', 'CREDIT_LINE' => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'), - 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '') + 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id) : '') ); // Call cron-type script @@ -5583,7 +5681,7 @@ function phpbb_convert_30_dbms_to_31($dbms) /* $reflection = new \ReflectionClass($dbms); - + if ($reflection->isSubclassOf('phpbb_db_driver')) { return $dbms; diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 2f3fd7bac0..d6bd9e35dd 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -82,16 +82,16 @@ function adm_page_header($page_title) 'T_RANKS_PATH' => "{$phpbb_root_path}{$config['ranks_path']}/", 'T_UPLOAD_PATH' => "{$phpbb_root_path}{$config['upload_path']}/", - 'ICON_MOVE_UP' => '<img src="' . $phpbb_admin_path . 'images/icon_up.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />', - 'ICON_MOVE_UP_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_up_disabled.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />', - 'ICON_MOVE_DOWN' => '<img src="' . $phpbb_admin_path . 'images/icon_down.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />', - 'ICON_MOVE_DOWN_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_down_disabled.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />', - 'ICON_EDIT' => '<img src="' . $phpbb_admin_path . 'images/icon_edit.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />', - 'ICON_EDIT_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_edit_disabled.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />', - 'ICON_DELETE' => '<img src="' . $phpbb_admin_path . 'images/icon_delete.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />', - 'ICON_DELETE_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_delete_disabled.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />', - 'ICON_SYNC' => '<img src="' . $phpbb_admin_path . 'images/icon_sync.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />', - 'ICON_SYNC_DISABLED' => '<img src="' . $phpbb_admin_path . 'images/icon_sync_disabled.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />', + 'ICON_MOVE_UP' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_up.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />', + 'ICON_MOVE_UP_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_up_disabled.gif" alt="' . $user->lang['MOVE_UP'] . '" title="' . $user->lang['MOVE_UP'] . '" />', + 'ICON_MOVE_DOWN' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_down.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />', + 'ICON_MOVE_DOWN_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_down_disabled.gif" alt="' . $user->lang['MOVE_DOWN'] . '" title="' . $user->lang['MOVE_DOWN'] . '" />', + 'ICON_EDIT' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_edit.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />', + 'ICON_EDIT_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_edit_disabled.gif" alt="' . $user->lang['EDIT'] . '" title="' . $user->lang['EDIT'] . '" />', + 'ICON_DELETE' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_delete.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />', + 'ICON_DELETE_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_delete_disabled.gif" alt="' . $user->lang['DELETE'] . '" title="' . $user->lang['DELETE'] . '" />', + 'ICON_SYNC' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_sync.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />', + 'ICON_SYNC_DISABLED' => '<img src="' . htmlspecialchars($phpbb_admin_path) . 'images/icon_sync_disabled.gif" alt="' . $user->lang['RESYNC'] . '" title="' . $user->lang['RESYNC'] . '" />', 'S_USER_LANG' => $user->lang['USER_LANG'], 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'], @@ -443,6 +443,13 @@ function validate_config_vars($config_vars, &$cfg_array, &$error) } break; + case 'email': + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $cfg_array[$config_name])) + { + $error[] = $user->lang['EMAIL_INVALID_EMAIL']; + } + break; + // Absolute path case 'script_path': if (!$cfg_array[$config_name]) diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php index ae9dc94fc0..b844bd1966 100644 --- a/phpBB/includes/functions_admin.php +++ b/phpBB/includes/functions_admin.php @@ -618,7 +618,7 @@ function move_posts($post_ids, $topic_id, $auto_sync = true) */ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true) { - global $db, $config; + global $db, $config, $phpbb_container; $approved_topics = 0; $forum_ids = $topic_ids = array(); @@ -715,6 +715,14 @@ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_s set_config_count('num_topics', $approved_topics * (-1), true); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications(array( + 'topic', + 'approve_topic', + 'topic_in_queue', + ), $topic_ids); + return $return; } @@ -723,7 +731,7 @@ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_s */ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true) { - global $db, $config, $phpbb_root_path, $phpEx, $auth, $user; + global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container; if ($where_type === 'range') { @@ -892,6 +900,16 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications(array( + 'quote', + 'bookmark', + 'post', + 'approve_post', + 'post_in_queue', + ), $post_ids); + return sizeof($post_ids); } @@ -2326,13 +2344,17 @@ function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_fr } /** -* Cache moderators, called whenever permissions are changed via admin_permissions. Changes of username -* and group names must be carried through for the moderators table +* Cache moderators. Called whenever permissions are changed +* via admin_permissions. Changes of usernames and group names +* must be carried through for the moderators table. +* +* @param phpbb_db_driver $db Database connection +* @param phpbb_cache_driver_interface Cache driver +* @param phpbb_auth $auth Authentication object +* @return null */ -function cache_moderators() +function phpbb_cache_moderators($db, $cache, $auth) { - global $db, $cache, $auth, $phpbb_root_path, $phpEx; - // Remove cached sql results $cache->destroy('sql', MODERATOR_CACHE_TABLE); @@ -2503,6 +2525,20 @@ function cache_moderators() } /** +* Cache moderators. Called whenever permissions are changed +* via admin_permissions. Changes of usernames and group names +* must be carried through for the moderators table. +* +* @deprecated 3.1 +* @return null +*/ +function cache_moderators() +{ + global $db, $cache, $auth; + return phpbb_cache_moderators($db, $cache, $auth); +} + +/** * View log * If $log_count is set to false, we will skip counting all entries in the database. */ @@ -2774,12 +2810,16 @@ function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id } /** -* Update foes - remove moderators and administrators from foe lists... +* Removes moderators and administrators from foe lists. +* +* @param phpbb_db_driver $db Database connection +* @param phpbb_auth $auth Authentication object +* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore +* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore +* @return null */ -function update_foes($group_id = false, $user_id = false) +function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false) { - global $db, $auth; - // update foes for some user if (is_array($user_id) && sizeof($user_id)) { @@ -2889,6 +2929,20 @@ function update_foes($group_id = false, $user_id = false) } /** +* Removes moderators and administrators from foe lists. +* +* @deprecated 3.1 +* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore +* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore +* @return null +*/ +function update_foes($group_id = false, $user_id = false) +{ + global $db, $auth; + return phpbb_update_foes($db, $auth, $group_id, $user_id); +} + +/** * Lists inactive users */ function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC') diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index 8014574443..a3ed21c35b 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -105,6 +105,15 @@ function phpbb_create_compiled_container(array $extensions, array $passes, $phpb return $container; } +/** +* Create a compiled and dumped ContainerBuilder object +* +* @param array $extensions Array of Container extension objects +* @param array $passes Array of Compiler Pass objects +* @param string $phpbb_root_path Root path +* @param string $php_ext PHP Extension +* @return ContainerBuilder object (compiled) +*/ function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_root_path, $php_ext) { // Check for our cached container; if it exists, use it @@ -129,12 +138,60 @@ function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_ return $container; } +/** +* Create an environment-specific ContainerBuilder object +* +* If debug is enabled, the container is re-compiled every time. +* This ensures that the latest changes will always be reflected +* during development. +* +* Otherwise it will get the existing dumped container and use +* that one instead. +* +* @param array $extensions Array of Container extension objects +* @param array $passes Array of Compiler Pass objects +* @param string $phpbb_root_path Root path +* @param string $php_ext PHP Extension +* @return ContainerBuilder object (compiled) +*/ function phpbb_create_dumped_container_unless_debug(array $extensions, array $passes, $phpbb_root_path, $php_ext) { $container_factory = defined('DEBUG') ? 'phpbb_create_compiled_container' : 'phpbb_create_dumped_container'; return $container_factory($extensions, $passes, $phpbb_root_path, $php_ext); } +/** +* Create a default ContainerBuilder object +* +* Contains the default configuration of the phpBB container. +* +* @param array $extensions Array of Container extension objects +* @param array $passes Array of Compiler Pass objects +* @return ContainerBuilder object (compiled) +*/ +function phpbb_create_default_container($phpbb_root_path, $php_ext) +{ + return phpbb_create_dumped_container_unless_debug( + array( + new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext), + new phpbb_di_extension_core($phpbb_root_path), + ), + array( + new phpbb_di_pass_collection_pass(), + new phpbb_di_pass_kernel_pass(), + ), + $phpbb_root_path, + $php_ext + ); +} + +/** +* Get the filename under which the dumped container will be stored. +* +* @param string $phpbb_root_path Root path +* @param string $php_ext PHP Extension +* @return Path for dumped container +*/ function phpbb_container_filename($phpbb_root_path, $php_ext) { $filename = str_replace(array('/', '.'), array('slash', 'dot'), $phpbb_root_path); diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 2b4b7eb10d..8978e3fadd 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -32,6 +32,8 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'AVAILABLE' => true, '2.0.x' => false, ), + // Note: php 5.5 alpha 2 deprecated mysql. + // Keep mysqli before mysql in this list. 'mysqli' => array( 'LABEL' => 'MySQL with MySQLi Extension', 'SCHEMA' => 'mysql_41', @@ -508,6 +510,9 @@ function phpbb_create_config_file_data($data, $dbms, $debug = false, $debug_test 'dbuser' => $data['dbuser'], 'dbpasswd' => htmlspecialchars_decode($data['dbpasswd']), 'table_prefix' => $data['table_prefix'], + + 'adm_relative_path' => 'adm/', + 'acm_type' => 'phpbb_cache_driver_file', ); diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index d0a02567ad..821f0d970d 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -393,6 +393,28 @@ class messenger } /** + * Generates a valid message id to be used in emails + * + * @return string message id + */ + function generate_message_id() + { + global $config; + + $domain = 'phpbb.generated'; + if ($config['server_name']) + { + $domain = $config['server_name']; + } + else if (!empty($_SERVER['SERVER_NAME'])) + { + $domain = $_SERVER['SERVER_NAME']; + } + + return md5(unique_id(time())) . '@' . $domain; + } + + /** * Return email header */ function build_header($to, $cc, $bcc) @@ -418,7 +440,7 @@ class messenger $headers[] = 'Return-Path: <' . $config['board_email'] . '>'; $headers[] = 'Sender: <' . $config['board_email'] . '>'; $headers[] = 'MIME-Version: 1.0'; - $headers[] = 'Message-ID: <' . md5(unique_id(time())) . '@' . $config['server_name'] . '>'; + $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>'; $headers[] = 'Date: ' . date('r', time()); $headers[] = 'Content-Type: text/plain; charset=UTF-8'; // format=flowed $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 577cb58b34..f9dacae655 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -61,7 +61,7 @@ function generate_smilies($mode, $forum_id) 'body' => 'posting_smilies.html') ); - generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start); + generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start); } $display_link = false; @@ -1175,238 +1175,6 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id return true; } -/** -* User Notification -*/ -function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id, $topic_id, $post_id, $author_name = '') -{ - global $db, $user, $config, $phpbb_root_path, $phpEx, $auth; - - $topic_notification = ($mode == 'reply' || $mode == 'quote') ? true : false; - $forum_notification = ($mode == 'post') ? true : false; - - if (!$topic_notification && !$forum_notification) - { - trigger_error('NO_MODE'); - } - - if (($topic_notification && !$config['allow_topic_notify']) || ($forum_notification && !$config['allow_forum_notify'])) - { - return; - } - - $topic_title = ($topic_notification) ? $topic_title : $subject; - $topic_title = censor_text($topic_title); - - // Exclude guests, current user and banned users from notifications - if (!function_exists('phpbb_get_banned_user_ids')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - $sql_ignore_users = phpbb_get_banned_user_ids(); - $sql_ignore_users[ANONYMOUS] = ANONYMOUS; - $sql_ignore_users[$user->data['user_id']] = $user->data['user_id']; - - $notify_rows = array(); - - // -- get forum_userids || topic_userids - $sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber - FROM ' . (($topic_notification) ? TOPICS_WATCH_TABLE : FORUMS_WATCH_TABLE) . ' w, ' . USERS_TABLE . ' u - WHERE w.' . (($topic_notification) ? 'topic_id' : 'forum_id') . ' = ' . (($topic_notification) ? $topic_id : $forum_id) . ' - AND ' . $db->sql_in_set('w.user_id', $sql_ignore_users, true) . ' - AND w.notify_status = ' . NOTIFY_YES . ' - AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ') - AND u.user_id = w.user_id'; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $notify_user_id = (int) $row['user_id']; - $notify_rows[$notify_user_id] = array( - 'user_id' => $notify_user_id, - 'username' => $row['username'], - 'user_email' => $row['user_email'], - 'user_jabber' => $row['user_jabber'], - 'user_lang' => $row['user_lang'], - 'notify_type' => ($topic_notification) ? 'topic' : 'forum', - 'template' => ($topic_notification) ? 'topic_notify' : 'newtopic_notify', - 'method' => $row['user_notify_type'], - 'allowed' => false - ); - - // Add users who have been already notified to ignore list - $sql_ignore_users[$notify_user_id] = $notify_user_id; - } - $db->sql_freeresult($result); - - // forum notification is sent to those not already receiving topic notifications - if ($topic_notification) - { - $sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber - FROM ' . FORUMS_WATCH_TABLE . ' fw, ' . USERS_TABLE . " u - WHERE fw.forum_id = $forum_id - AND " . $db->sql_in_set('fw.user_id', $sql_ignore_users, true) . ' - AND fw.notify_status = ' . NOTIFY_YES . ' - AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ') - AND u.user_id = fw.user_id'; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $notify_user_id = (int) $row['user_id']; - $notify_rows[$notify_user_id] = array( - 'user_id' => $notify_user_id, - 'username' => $row['username'], - 'user_email' => $row['user_email'], - 'user_jabber' => $row['user_jabber'], - 'user_lang' => $row['user_lang'], - 'notify_type' => 'forum', - 'template' => 'forum_notify', - 'method' => $row['user_notify_type'], - 'allowed' => false - ); - } - $db->sql_freeresult($result); - } - - if (!sizeof($notify_rows)) - { - return; - } - - // Make sure users are allowed to read the forum - foreach ($auth->acl_get_list(array_keys($notify_rows), 'f_read', $forum_id) as $forum_id => $forum_ary) - { - foreach ($forum_ary as $auth_option => $user_ary) - { - foreach ($user_ary as $user_id) - { - $notify_rows[$user_id]['allowed'] = true; - } - } - } - - // Now, we have to do a little step before really sending, we need to distinguish our users a little bit. ;) - $msg_users = $delete_ids = $update_notification = array(); - foreach ($notify_rows as $user_id => $row) - { - if (!$row['allowed'] || !trim($row['user_email'])) - { - $delete_ids[$row['notify_type']][] = $row['user_id']; - } - else - { - $msg_users[] = $row; - $update_notification[$row['notify_type']][] = $row['user_id']; - - /* - * We also update the forums watch table for this user when we are - * sending out a topic notification to prevent sending out another - * notification in case this user is also subscribed to the forum - * this topic was posted in. - * Since an UPDATE query is used, this has no effect on users only - * subscribed to the topic (i.e. no row is created) and should not - * be a performance issue. - */ - if ($row['notify_type'] === 'topic') - { - $update_notification['forum'][] = $row['user_id']; - } - } - } - unset($notify_rows); - - // Now, we are able to really send out notifications - if (sizeof($msg_users)) - { - include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); - $messenger = new messenger(); - - $msg_list_ary = array(); - foreach ($msg_users as $row) - { - $pos = (!isset($msg_list_ary[$row['template']])) ? 0 : sizeof($msg_list_ary[$row['template']]); - - $msg_list_ary[$row['template']][$pos]['method'] = $row['method']; - $msg_list_ary[$row['template']][$pos]['email'] = $row['user_email']; - $msg_list_ary[$row['template']][$pos]['jabber'] = $row['user_jabber']; - $msg_list_ary[$row['template']][$pos]['name'] = $row['username']; - $msg_list_ary[$row['template']][$pos]['lang'] = $row['user_lang']; - $msg_list_ary[$row['template']][$pos]['user_id']= $row['user_id']; - } - unset($msg_users); - - foreach ($msg_list_ary as $email_template => $email_list) - { - foreach ($email_list as $addr) - { - $messenger->template($email_template, $addr['lang']); - - $messenger->to($addr['email'], $addr['name']); - $messenger->im($addr['jabber'], $addr['name']); - - $messenger->assign_vars(array( - 'USERNAME' => htmlspecialchars_decode($addr['name']), - 'TOPIC_TITLE' => htmlspecialchars_decode($topic_title), - 'FORUM_NAME' => htmlspecialchars_decode($forum_name), - 'AUTHOR_NAME' => htmlspecialchars_decode($author_name), - - 'U_FORUM' => generate_board_url() . "/viewforum.$phpEx?f=$forum_id", - 'U_TOPIC' => generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&t=$topic_id", - 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&t=$topic_id&p=$post_id&e=$post_id", - 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.$phpEx?uid={$addr['user_id']}&f=$forum_id&t=$topic_id&unwatch=topic", - 'U_STOP_WATCHING_FORUM' => generate_board_url() . "/viewforum.$phpEx?uid={$addr['user_id']}&f=$forum_id&unwatch=forum", - )); - - $messenger->send($addr['method']); - } - } - unset($msg_list_ary); - - $messenger->save_queue(); - } - - // Handle the DB updates - $db->sql_transaction('begin'); - - if (!empty($update_notification['topic'])) - { - $sql = 'UPDATE ' . TOPICS_WATCH_TABLE . ' - SET notify_status = ' . NOTIFY_NO . " - WHERE topic_id = $topic_id - AND " . $db->sql_in_set('user_id', $update_notification['topic']); - $db->sql_query($sql); - } - - if (!empty($update_notification['forum'])) - { - $sql = 'UPDATE ' . FORUMS_WATCH_TABLE . ' - SET notify_status = ' . NOTIFY_NO . " - WHERE forum_id = $forum_id - AND " . $db->sql_in_set('user_id', $update_notification['forum']); - $db->sql_query($sql); - } - - // Now delete the user_ids not authorised to receive notifications on this topic/forum - if (!empty($delete_ids['topic'])) - { - $sql = 'DELETE FROM ' . TOPICS_WATCH_TABLE . " - WHERE topic_id = $topic_id - AND " . $db->sql_in_set('user_id', $delete_ids['topic']); - $db->sql_query($sql); - } - - if (!empty($delete_ids['forum'])) - { - $sql = 'DELETE FROM ' . FORUMS_WATCH_TABLE . " - WHERE forum_id = $forum_id - AND " . $db->sql_in_set('user_id', $delete_ids['forum']); - $db->sql_query($sql); - } - - $db->sql_transaction('commit'); -} - // // Post handling functions // @@ -1698,7 +1466,7 @@ function delete_post($forum_id, $topic_id, $post_id, &$data, $is_soft = false, $ */ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $update_message = true, $update_search_index = true) { - global $db, $auth, $user, $config, $phpEx, $template, $phpbb_root_path; + global $db, $auth, $user, $config, $phpEx, $template, $phpbb_root_path, $phpbb_container; // We do not handle erasing posts here if ($mode == 'delete') @@ -2402,10 +2170,76 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u } // Send Notifications - if (($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $post_visibility == ITEM_APPROVED) + $notification_data = array_merge($data, array( + 'topic_title' => (isset($data['topic_title'])) ? $data['topic_title'] : $subject, + 'post_username' => $username, + 'poster_id' => $poster_id, + 'post_text' => $data['message'], + 'post_time' => $current_time, + 'post_subject' => $subject, + )); + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + if ($post_visibility == ITEM_APPROVED) { - $username = ($username) ? $username : $user->data['username']; - user_notification($mode, $subject, $data['topic_title'], $data['forum_name'], $data['forum_id'], $data['topic_id'], $data['post_id'], $username); + switch ($mode) + { + case 'post': + $phpbb_notifications->add_notifications(array( + 'quote', + 'topic', + ), $notification_data); + break; + + case 'reply': + case 'quote': + $phpbb_notifications->add_notifications(array( + 'quote', + 'bookmark', + 'post', + ), $notification_data); + break; + + case 'edit_topic': + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + $phpbb_notifications->update_notifications(array( + 'quote', + 'bookmark', + 'topic', + 'post', + ), $notification_data); + break; + } + } + else + { + switch ($mode) + { + case 'post': + $phpbb_notifications->add_notifications('topic_in_queue', $notification_data); + break; + + case 'reply': + case 'quote': + $phpbb_notifications->add_notifications('post_in_queue', $notification_data); + break; + + case 'edit_topic': + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + $phpbb_notifications->delete_notifications('topic', $data['topic_id']); + + $phpbb_notifications->delete_notifications(array( + 'quote', + 'bookmark', + 'post', + ), $data['post_id']); + break; + } } $params = $add_anchor = ''; diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index ba939d490e..14278a2529 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -876,7 +876,11 @@ function update_unread_status($unread, $msg_id, $user_id, $folder_id) return; } - global $db, $user; + global $db, $user, $phpbb_container; + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_read('pm', $msg_id, $user_id); $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . " SET pm_unread = 0 @@ -981,7 +985,7 @@ function handle_mark_actions($user_id, $mark_action) */ function delete_pm($user_id, $msg_ids, $folder_id) { - global $db, $user, $phpbb_root_path, $phpEx; + global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; $user_id = (int) $user_id; $folder_id = (int) $folder_id; @@ -1093,6 +1097,10 @@ function delete_pm($user_id, $msg_ids, $folder_id) $user->data['user_unread_privmsg'] -= $num_unread; } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications('pm', array_keys($delete_rows)); + // Now we have to check which messages we can delete completely $sql = 'SELECT msg_id FROM ' . PRIVMSGS_TO_TABLE . ' @@ -1157,7 +1165,7 @@ function phpbb_delete_user_pms($user_id) */ function phpbb_delete_users_pms($user_ids) { - global $db, $user, $phpbb_root_path, $phpEx; + global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; $user_id_sql = $db->sql_in_set('user_id', $user_ids); $author_id_sql = $db->sql_in_set('author_id', $user_ids); @@ -1202,6 +1210,8 @@ function phpbb_delete_users_pms($user_ids) $db->sql_transaction('begin'); + $phpbb_notifications = $phpbb_container->get('notification_manager'); + if (!empty($undelivered_msg)) { // A pm is delivered, if for any recipient the message was moved @@ -1270,6 +1280,8 @@ function phpbb_delete_users_pms($user_ids) WHERE folder_id = ' . PRIVMSGS_NO_BOX . ' AND ' . $db->sql_in_set('msg_id', $delivered_msg); $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('pm', $delivered_msg); } if (!empty($undelivered_msg)) @@ -1281,6 +1293,8 @@ function phpbb_delete_users_pms($user_ids) $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('pm', $undelivered_msg); } } @@ -1323,6 +1337,8 @@ function phpbb_delete_users_pms($user_ids) $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' WHERE ' . $db->sql_in_set('msg_id', $delete_ids); $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('pm', $delete_ids); } } @@ -1559,7 +1575,7 @@ function get_folder_status($folder_id, $folder) */ function submit_pm($mode, $subject, &$data, $put_in_outbox = true) { - global $db, $auth, $config, $phpEx, $template, $user, $phpbb_root_path; + global $db, $auth, $config, $phpEx, $template, $user, $phpbb_root_path, $phpbb_container; // We do not handle erasing pms here if ($mode == 'delete') @@ -1859,95 +1875,23 @@ function submit_pm($mode, $subject, &$data, $put_in_outbox = true) $db->sql_transaction('commit'); // Send Notifications - if ($mode != 'edit') - { - pm_notification($mode, $data['from_username'], $recipients, $subject, $data['message'], $data['msg_id']); - } - - return $data['msg_id']; -} - -/** -* PM Notification -*/ -function pm_notification($mode, $author, $recipients, $subject, $message, $msg_id) -{ - global $db, $user, $config, $phpbb_root_path, $phpEx, $auth; - - $subject = censor_text($subject); - - // Exclude guests, current user and banned users from notifications - unset($recipients[ANONYMOUS], $recipients[$user->data['user_id']]); - - if (!sizeof($recipients)) - { - return; - } - - if (!function_exists('phpbb_get_banned_user_ids')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - $banned_users = phpbb_get_banned_user_ids(array_keys($recipients)); - $recipients = array_diff(array_keys($recipients), $banned_users); - - if (!sizeof($recipients)) - { - return; - } + $pm_data = array_merge($data, array( + 'message_subject' => $subject, + 'recipients' => $recipients, + )); - $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_pm, user_notify_type, user_jabber - FROM ' . USERS_TABLE . ' - WHERE ' . $db->sql_in_set('user_id', $recipients); - $result = $db->sql_query($sql); + $phpbb_notifications = $phpbb_container->get('notification_manager'); - $msg_list_ary = array(); - while ($row = $db->sql_fetchrow($result)) + if ($mode == 'edit') { - if ($row['user_notify_pm'] == 1 && trim($row['user_email'])) - { - $msg_list_ary[] = array( - 'method' => $row['user_notify_type'], - 'email' => $row['user_email'], - 'jabber' => $row['user_jabber'], - 'name' => $row['username'], - 'lang' => $row['user_lang'] - ); - } + $phpbb_notifications->update_notifications('pm', $pm_data); } - $db->sql_freeresult($result); - - if (!sizeof($msg_list_ary)) + else { - return; + $phpbb_notifications->add_notifications('pm', $pm_data); } - include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); - $messenger = new messenger(); - - foreach ($msg_list_ary as $pos => $addr) - { - $messenger->template('privmsg_notify', $addr['lang']); - - $messenger->to($addr['email'], $addr['name']); - $messenger->im($addr['jabber'], $addr['name']); - - $messenger->assign_vars(array( - 'SUBJECT' => htmlspecialchars_decode($subject), - 'AUTHOR_NAME' => htmlspecialchars_decode($author), - 'USERNAME' => htmlspecialchars_decode($addr['name']), - - 'U_INBOX' => generate_board_url() . "/ucp.$phpEx?i=pm&folder=inbox", - 'U_VIEW_MESSAGE' => generate_board_url() . "/ucp.$phpEx?i=pm&mode=view&p=$msg_id", - )); - - $messenger->send($addr['method']); - } - unset($msg_list_ary); - - $messenger->save_queue(); - - unset($messenger); + return $data['msg_id']; } /** diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 8f9c9198f4..b7878ddfc7 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -2698,12 +2698,12 @@ function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow } $db->sql_freeresult($result); - if (isset($sql_ary['group_avatar']) && !$sql_ary['group_avatar']) + if (isset($sql_ary['group_avatar'])) { remove_default_avatar($group_id, $user_ary); } - if (isset($sql_ary['group_rank']) && !$sql_ary['group_rank']) + if (isset($sql_ary['group_rank'])) { remove_default_rank($group_id, $user_ary); } @@ -2842,7 +2842,7 @@ function avatar_remove_db($avatar_name) */ function group_delete($group_id, $group_name = false) { - global $db, $phpbb_root_path, $phpEx, $phpbb_dispatcher; + global $db, $cache, $auth, $phpbb_root_path, $phpEx, $phpbb_dispatcher; if (!$group_name) { @@ -2913,12 +2913,12 @@ function group_delete($group_id, $group_name = false) extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars))); // Re-cache moderators - if (!function_exists('cache_moderators')) + if (!function_exists('phpbb_cache_moderators')) { include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); } - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); add_log('admin', 'LOG_GROUP_DELETE', $group_name); @@ -3208,8 +3208,8 @@ function remove_default_avatar($group_id, $user_ids) user_avatar_width = 0, user_avatar_height = 0 WHERE group_id = " . (int) $group_id . " - AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "' - AND " . $db->sql_in_set('user_id', $user_ids); + AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "' + AND " . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); } @@ -3246,9 +3246,9 @@ function remove_default_rank($group_id, $user_ids) $sql = 'UPDATE ' . USERS_TABLE . ' SET user_rank = 0 WHERE group_id = ' . (int)$group_id . ' - AND user_rank <> 0 - AND user_rank = ' . (int)$row['group_rank'] . ' - AND ' . $db->sql_in_set('user_id', $user_ids); + AND user_rank <> 0 + AND user_rank = ' . (int)$row['group_rank'] . ' + AND ' . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); } @@ -3277,7 +3277,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna case 'demote': case 'promote': - $sql = 'SELECT user_id FROM ' . USER_GROUP_TABLE . " + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . " WHERE group_id = $group_id AND user_pending = 1 AND " . $db->sql_in_set('user_id', $user_id_ary); @@ -3375,7 +3376,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna return 'NO_USERS'; } - $sql = 'SELECT user_id, group_id FROM ' . USERS_TABLE . ' + $sql = 'SELECT user_id, group_id + FROM ' . USERS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true); $result = $db->sql_query($sql); @@ -3463,7 +3465,7 @@ function group_validate_groupname($group_id, $group_name) */ function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false) { - global $cache, $db, $phpbb_dispatcher; + global $phpbb_container, $db, $phpbb_dispatcher; if (empty($user_id_ary)) { @@ -3509,45 +3511,69 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal } } - // Before we update the user attributes, we will make a list of those having now the group avatar assigned - if (isset($sql_ary['user_avatar'])) + $updated_sql_ary = $sql_ary; + + // Before we update the user attributes, we will update the rank for users that don't have a custom rank + if (isset($sql_ary['user_rank'])) { - // Ok, get the original avatar data from users having an uploaded one (we need to remove these from the filesystem) - $sql = 'SELECT user_id, group_id, user_avatar - FROM ' . USERS_TABLE . ' - WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . ' - AND user_avatar_type = ' . AVATAR_UPLOAD; - $result = $db->sql_query($sql); + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . ' + WHERE user_rank = 0 + AND ' . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + unset($sql_ary['user_rank']); + } - while ($row = $db->sql_fetchrow($result)) + // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar + $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width'); + + if (isset($sql_ary['user_avatar'])) + { + $avatar_sql_ary = array(); + foreach ($avatar_options as $avatar_option) { - avatar_delete('user', $row); - } - $db->sql_freeresult($result); + if (isset($sql_ary[$avatar_option])) + { + $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option]; + } + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . " + WHERE user_avatar = '' + AND " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); } - else + + // Remove the avatar options, as we already updated them + foreach ($avatar_options as $avatar_option) { - unset($sql_ary['user_avatar_type']); - unset($sql_ary['user_avatar_height']); - unset($sql_ary['user_avatar_width']); + unset($sql_ary[$avatar_option]); } - $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE ' . $db->sql_in_set('user_id', $user_id_ary); - $db->sql_query($sql); + if (!empty($sql_ary)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + } if (isset($sql_ary['user_colour'])) { // Update any cached colour information for these users - $sql = 'UPDATE ' . FORUMS_TABLE . " SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary); $db->sql_query($sql); - $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' WHERE " . $db->sql_in_set('topic_poster', $user_id_ary); $db->sql_query($sql); - $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary); $db->sql_query($sql); @@ -3559,6 +3585,9 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal } } + // Make all values available for the event + $sql_ary = $updated_sql_ary; + /** * Event when the default group is set for an array of users * @@ -3579,7 +3608,7 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal } // Because some tables/caches use usercolour-specific data we need to purge this here. - $cache->destroy('sql', MODERATOR_CACHE_TABLE); + $phpbb_container->get('cache.driver')->destroy('sql', MODERATOR_CACHE_TABLE); } /** @@ -3678,7 +3707,7 @@ function group_memberships($group_id_ary = false, $user_id_ary = false, $return_ */ function group_update_listings($group_id) { - global $auth; + global $db, $cache, $auth; $hold_ary = $auth->acl_group_raw_data($group_id, array('a_', 'm_')); @@ -3720,22 +3749,22 @@ function group_update_listings($group_id) if ($mod_permissions) { - if (!function_exists('cache_moderators')) + if (!function_exists('phpbb_cache_moderators')) { global $phpbb_root_path, $phpEx; include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); } - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); } if ($mod_permissions || $admin_permissions) { - if (!function_exists('update_foes')) + if (!function_exists('phpbb_update_foes')) { global $phpbb_root_path, $phpEx; include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); } - update_foes(array($group_id)); + phpbb_update_foes($db, $auth, array($group_id)); } } diff --git a/phpBB/includes/hook/finder.php b/phpBB/includes/hook/finder.php new file mode 100644 index 0000000000..065e685514 --- /dev/null +++ b/phpBB/includes/hook/finder.php @@ -0,0 +1,84 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* The hook finder locates installed hooks. +* +* @package phpBB3 +*/ +class phpbb_hook_finder +{ + protected $phpbb_root_path; + protected $cache; + protected $php_ext; + + /** + * Creates a new finder instance. + * + * @param string $phpbb_root_path Path to the phpbb root directory + * @param string $php_ext php file extension + * @param phpbb_cache_driver_interface $cache A cache instance or null + */ + public function __construct($phpbb_root_path, $php_ext, phpbb_cache_driver_interface $cache = null) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->cache = $cache; + $this->php_ext = $php_ext; + } + + /** + * Finds all hook files. + * + * @param bool $cache Whether the result should be cached + * @return array An array of paths to found hook files + */ + public function find($cache = true) + { + if (!defined('DEBUG') && $cache && $this->cache) + { + $hook_files = $this->cache->get('_hooks'); + if ($hook_files !== false) + { + return $hook_files; + } + } + + $hook_files = array(); + + // Now search for hooks... + $dh = @opendir($this->phpbb_root_path . 'includes/hooks/'); + + if ($dh) + { + while (($file = readdir($dh)) !== false) + { + if (strpos($file, 'hook_') === 0 && substr($file, -(strlen($this->php_ext) + 1)) === '.' . $this->php_ext) + { + $hook_files[] = substr($file, 0, -(strlen($this->php_ext) + 1)); + } + } + closedir($dh); + } + + if ($cache && $this->cache) + { + $this->cache->put('_hooks', $hook_files); + } + + return $hook_files; + } +} diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index be18dba944..99ff397a66 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -33,7 +33,7 @@ class mcp_pm_reports function main($id, $mode) { global $auth, $db, $user, $template, $cache; - global $config, $phpbb_root_path, $phpEx, $action; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container; include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); include_once($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); @@ -89,6 +89,10 @@ class mcp_pm_reports trigger_error('NO_REPORT'); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_read_by_parent('report_pm', $report_id, $user->data['user_id']); + $pm_id = $report['pm_id']; $report_id = $report['report_id']; @@ -122,6 +126,7 @@ class mcp_pm_reports $message = bbcode_nl2br($message); $message = smiley_text($message); + $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); if ($pm_info['message_attachment'] && $auth->acl_get('u_pm_download')) { diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index d9ec10e6da..6f9f16bde4 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -33,7 +33,7 @@ class mcp_queue public function main($id, $mode) { global $auth, $db, $user, $template, $cache, $request; - global $config, $phpbb_root_path, $phpEx, $action; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container; include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); @@ -149,12 +149,16 @@ class mcp_queue $post_id = request_var('p', 0); $topic_id = request_var('t', 0); + $phpbb_notifications = $phpbb_container->get('notification_manager'); + if ($topic_id) { $topic_info = get_topic_data(array($topic_id), 'm_approve'); if (isset($topic_info[$topic_id]['topic_first_post_id'])) { $post_id = (int) $topic_info[$topic_id]['topic_first_post_id']; + + $phpbb_notifications->mark_notifications_read('topic_in_queue', $topic_id, $user->data['user_id']); } else { @@ -162,6 +166,8 @@ class mcp_queue } } + $phpbb_notifications->mark_notifications_read('post_in_queue', $post_id, $user->data['user_id']); + $post_info = get_post_data(array($post_id), 'm_approve', true); if (!sizeof($post_info)) @@ -567,8 +573,8 @@ class mcp_queue */ static public function approve_posts($action, $post_id_list, $id, $mode) { - global $db, $template, $user, $config; - global $phpEx, $phpbb_root_path, $request; + global $db, $template, $user, $config, $request, $phpbb_container; + global $phpEx, $phpbb_root_path; if (!check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_approve'))) { diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 7b038b476b..72400ce623 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -33,7 +33,7 @@ class mcp_reports function main($id, $mode) { global $auth, $db, $user, $template, $cache; - global $config, $phpbb_root_path, $phpEx, $action; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container; include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); @@ -71,7 +71,7 @@ class mcp_reports // closed reports are accessed by report id $report_id = request_var('r', 0); - $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour + $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, r.reported_post_enable_magic_url, r.reported_post_enable_smilies, r.reported_post_enable_bbcode, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour FROM ' . REPORTS_TABLE . ' r, ' . REPORTS_REASONS_TABLE . ' rr, ' . USERS_TABLE . ' u WHERE ' . (($report_id) ? 'r.report_id = ' . $report_id : "r.post_id = $post_id") . ' AND rr.reason_id = r.reason_id @@ -87,6 +87,10 @@ class mcp_reports trigger_error('NO_REPORT'); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_read('report_post', $post_id, $user->data['user_id']); + if (!$report_id && $report['report_closed']) { trigger_error('REPORT_CLOSED'); @@ -94,6 +98,10 @@ class mcp_reports $post_id = $report['post_id']; $report_id = $report['report_id']; + + $parse_post_flags = $report['reported_post_enable_bbcode'] ? OPTION_FLAG_BBCODE : 0; + $parse_post_flags += $report['reported_post_enable_smilies'] ? OPTION_FLAG_SMILIES : 0; + $parse_post_flags += $report['reported_post_enable_magic_url'] ? OPTION_FLAG_LINKS : 0; $post_info = get_post_data(array($post_id), 'm_report', true); @@ -136,18 +144,7 @@ class mcp_reports $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; - // Process message, leave it uncensored - $message = $post_info['post_text']; - if ($post_info['bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode($post_info['bbcode_bitfield']); - $bbcode->bbcode_second_pass($message, $post_info['bbcode_uid'], $post_info['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) @@ -168,7 +165,7 @@ class mcp_reports if (sizeof($attachments)) { $update_count = array(); - parse_attachments($post_info['forum_id'], $message, $attachments, $update_count); + parse_attachments($post_info['forum_id'], $report['reported_post_text'], $attachments, $update_count); } // Display not already displayed Attachments for this post, we already parsed them. ;) @@ -227,7 +224,7 @@ class mcp_reports 'REPORTER_NAME' => get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']), 'U_VIEW_REPORTER_PROFILE' => get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']), - 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, false), + 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], $parse_post_flags, false), 'POST_SUBJECT' => ($post_info['post_subject']) ? $post_info['post_subject'] : $user->lang['NO_SUBJECT'], 'POST_DATE' => $user->format_date($post_info['post_time']), 'POST_IP' => $post_info['poster_ip'], @@ -443,7 +440,7 @@ class mcp_reports function close_report($report_id_list, $mode, $action, $pm = false) { global $db, $template, $user, $config, $auth; - global $phpEx, $phpbb_root_path; + global $phpEx, $phpbb_root_path, $phpbb_container; $pm_where = ($pm) ? ' AND r.post_id = 0 ' : ' AND r.pm_id = 0 '; $id_column = ($pm) ? 'pm_id' : 'post_id'; @@ -629,11 +626,11 @@ function close_report($report_id_list, $mode, $action, $pm = false) } } - $messenger = new messenger(); - // Notify reporters if (sizeof($notify_reporters)) { + $phpbb_notifications = $phpbb_container->get('notification_manager'); + foreach ($notify_reporters as $report_id => $reporter) { if ($reporter['user_id'] == ANONYMOUS) @@ -643,30 +640,25 @@ function close_report($report_id_list, $mode, $action, $pm = false) $post_id = $reporter[$id_column]; - $messenger->template((($pm) ? 'pm_report_' : 'report_') . $action . 'd', $reporter['user_lang']); - - $messenger->to($reporter['user_email'], $reporter['username']); - $messenger->im($reporter['user_jabber'], $reporter['username']); - if ($pm) { - $messenger->assign_vars(array( - 'USERNAME' => htmlspecialchars_decode($reporter['username']), - 'CLOSER_NAME' => htmlspecialchars_decode($user->data['username']), - 'PM_SUBJECT' => htmlspecialchars_decode(censor_text($post_info[$post_id]['message_subject'])), - )); + $phpbb_notifications->add_notifications('report_pm_closed', array_merge($post_info[$post_id], array( + 'reporter' => $reporter['user_id'], + 'closer_id' => $user->data['user_id'], + 'from_user_id' => $post_info[$post_id]['author_id'], + ))); + + $phpbb_notifications->delete_notifications('report_pm', $post_id); } else { - $messenger->assign_vars(array( - 'USERNAME' => htmlspecialchars_decode($reporter['username']), - 'CLOSER_NAME' => htmlspecialchars_decode($user->data['username']), - 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($post_info[$post_id]['post_subject'])), - 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($post_info[$post_id]['topic_title']))) - ); - } + $phpbb_notifications->add_notifications('report_post_closed', array_merge($post_info[$post_id], array( + 'reporter' => $reporter['user_id'], + 'closer_id' => $user->data['user_id'], + ))); - $messenger->send($reporter['user_notify_type']); + $phpbb_notifications->delete_notifications('report_post', $post_id); + } } } @@ -681,8 +673,6 @@ function close_report($report_id_list, $mode, $action, $pm = false) unset($notify_reporters, $post_info, $reports); - $messenger->save_queue(); - $success_msg = (sizeof($report_id_list) == 1) ? "{$pm_prefix}REPORT_" . strtoupper($action) . 'D_SUCCESS' : "{$pm_prefix}REPORTS_" . strtoupper($action) . 'D_SUCCESS'; } else diff --git a/phpBB/includes/notification/manager.php b/phpBB/includes/notification/manager.php new file mode 100644 index 0000000000..ff83d4bb37 --- /dev/null +++ b/phpBB/includes/notification/manager.php @@ -0,0 +1,853 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Notifications service class +* @package notifications +*/ +class phpbb_notification_manager +{ + /** @var array */ + protected $notification_types; + + /** @var array */ + protected $notification_methods; + + /** @var ContainerBuilder */ + protected $phpbb_container; + + /** @var phpbb_user_loader */ + protected $user_loader; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_user */ + protected $user; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $notification_types_table; + + /** @var string */ + protected $notifications_table; + + /** @var string */ + protected $user_notifications_table; + + /** + * Notification Constructor + * + * @param array $notification_types + * @param array $notification_methods + * @param ContainerBuilder $phpbb_container + * @param phpbb_user_loader $user_loader + * @param phpbb_db_driver $db + * @param phpbb_user $user + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $notification_types_table + * @param string $notifications_table + * @param string $user_notifications_table + * @return phpbb_notification_manager + */ + public function __construct($notification_types, $notification_methods, $phpbb_container, phpbb_user_loader $user_loader, phpbb_db_driver $db, $user, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) + { + $this->notification_types = $notification_types; + $this->notification_methods = $notification_methods; + $this->phpbb_container = $phpbb_container; + + $this->user_loader = $user_loader; + $this->db = $db; + $this->user = $user; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->notification_types_table = $notification_types_table; + $this->notifications_table = $notifications_table; + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Load the user's notifications + * + * @param array $options Optional options to control what notifications are loaded + * notification_id Notification id to load (or array of notification ids) + * user_id User id to load notifications for (Default: $user->data['user_id']) + * order_by Order by (Default: notification_time) + * order_dir Order direction (Default: DESC) + * limit Number of notifications to load (Default: 5) + * start Notifications offset (Default: 0) + * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) + * count_unread Count all unread notifications? (Default: false) + * count_total Count all notifications? (Default: false) + * @return array Array of information based on the request with keys: + * 'notifications' array of notification type objects + * 'unread_count' number of unread notifications the user has if count_unread is true in the options + * 'total_count' number of notifications the user has if count_total is true in the options + */ + public function load_notifications(array $options = array()) + { + // Merge default options + $options = array_merge(array( + 'notification_id' => false, + 'user_id' => $this->user->data['user_id'], + 'order_by' => 'notification_time', + 'order_dir' => 'DESC', + 'limit' => 0, + 'start' => 0, + 'all_unread' => false, + 'count_unread' => false, + 'count_total' => false, + ), $options); + + // If all_unread, count_unread must be true + $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; + + // Anonymous users and bots never receive notifications + if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) + { + return array( + 'notifications' => array(), + 'unread_count' => 0, + 'total_count' => 0, + ); + } + + $notifications = $user_ids = array(); + $load_special = array(); + $total_count = $unread_count = 0; + + if ($options['count_unread']) + { + // Get the total number of unread notifications + $sql = 'SELECT COUNT(n.notification_id) AS unread_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $unread_count = (int) $this->db->sql_fetchfield('unread_count', $result); + $this->db->sql_freeresult($result); + } + + if ($options['count_total']) + { + // Get the total number of notifications + $sql = 'SELECT COUNT(n.notification_id) AS total_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $total_count = (int) $this->db->sql_fetchfield('total_count', $result); + $this->db->sql_freeresult($result); + } + + if (!$options['count_total'] || $total_count) + { + $rowset = array(); + + // Get the main notifications + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . + (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + + // Get all unread notifications + if ($unread_count && $options['all_unread'] && !empty($rowset)) + { + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + } + + foreach ($rowset as $row) + { + $notification = $this->get_item_type_class($row['item_type'], $row); + + // Array of user_ids to query all at once + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + // Some notification types also require querying additional tables themselves + if (!isset($load_special[$row['item_type']])) + { + $load_special[$row['item_type']] = array(); + } + $load_special[$row['item_type']] = array_merge($load_special[$row['item_type']], $notification->get_load_special()); + + $notifications[$row['notification_id']] = $notification; + } + + $this->user_loader->load_users($user_ids); + + // Allow each type to load its own special items + foreach ($load_special as $item_type => $data) + { + $item_class = $this->get_item_type_class($item_type); + + $item_class->load_special($data, $notifications); + } + } + + return array( + 'notifications' => $notifications, + 'unread_count' => $unread_count, + 'total_count' => $total_count, + ); + } + + /** + * Mark notifications read + * + * @param bool|string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types + * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read($item_type, $item_id, $user_id, $time = false) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE notification_time <= " . (int) $time . + (($item_type !== false) ? ' AND ' . (is_array($item_type) ? $this->db->sql_in_set('item_type', $item_type) : " item_type = '" . $this->db->sql_escape($item_type) . "'") : '') . + (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); + $this->db->sql_query($sql); + } + + /** + * Mark notifications read from a parent identifier + * + * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read_by_parent($item_type, $item_parent_id, $user_id, $time = false) + { + if (is_array($item_type)) + { + foreach ($item_type as $type) + { + $this->mark_notifications_read_by_parent($type, $item_parent_id, $user_id, $time); + } + + return; + } + + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND notification_time <= " . (int) $time . + (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id) : 'item_parent_id = ' . (int) $item_parent_id) : '') . + (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); + $this->db->sql_query($sql); + } + + /** + * Mark notifications read + * + * @param int|array $notification_id Notification id or array of notification ids. + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read_by_id($notification_id, $time = false) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE notification_time <= " . (int) $time . ' + AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); + $this->db->sql_query($sql); + } + + /** + * Add a notification + * + * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive + * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array + * @param array $data Data specific for this type that will be inserted + * @param array $options Optional options to control what notifications are loaded + * ignore_users array of data to specify which users should not receive certain types of notifications + * @return array Information about what users were notified and how they were notified + */ + public function add_notifications($item_type, $data, array $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (is_array($item_type)) + { + $notified_users = array(); + $temp_options = $options; + + foreach ($item_type as $type) + { + $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users; + $notified_users += $this->add_notifications($type, $data, $temp_options); + } + + return $notified_users; + } + + $item_id = $this->get_item_type_class($item_type)->get_item_id($data); + + // find out which users want to receive this type of notification + $notify_users = $this->get_item_type_class($item_type)->find_users_for_notification($data, $options); + + $this->add_notifications_for_users($item_type, $data, $notify_users); + + return $notify_users; + } + + /** + * Add a notification for specific users + * + * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be inserted + * @param array $notify_users User list to notify + */ + public function add_notifications_for_users($item_type, $data, $notify_users) + { + if (is_array($item_type)) + { + foreach ($item_type as $type) + { + $this->add_notifications_for_users($type, $data, $notify_users); + } + + return; + } + + $sql = 'SELECT notification_type + FROM ' . $this->notification_types_table . " + WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'"; + $result = $this->db->sql_query($sql); + + if ($this->db->sql_fetchrow($result) === false) + { + // Does not exist in the database, must add the item type + $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( + 'notification_type' => $item_type, + 'notification_type_enabled' => 1, + )); + $this->db->sql_query($sql); + } + + $this->db->sql_freeresult($result); + + $item_id = $this->get_item_type_class($item_type)->get_item_id($data); + + $user_ids = array(); + $notification_objects = $notification_methods = array(); + $new_rows = array(); + + // Never send notifications to the anonymous user! + unset($notify_users[ANONYMOUS]); + + // Make sure not to send new notifications to users who've already been notified about this item + // This may happen when an item was added, but now new users are able to see the item + $sql = 'SELECT n.user_id + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt + WHERE n.item_type = '" . $this->db->sql_escape($item_type) . "' + AND n.item_id = " . (int) $item_id . ' + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + unset($notify_users[$row['user_id']]); + } + $this->db->sql_freeresult($result); + + if (!sizeof($notify_users)) + { + return; + } + + // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications) + $notification = $this->get_item_type_class($item_type); + $pre_create_data = $notification->pre_create_insert_array($data, $notify_users); + unset($notification); + + // Go through each user so we can insert a row in the DB and then notify them by their desired means + foreach ($notify_users as $user => $methods) + { + $notification = $this->get_item_type_class($item_type); + + $notification->user_id = (int) $user; + + // Store the creation array in our new rows that will be inserted later + $new_rows[] = $notification->create_insert_array($data, $pre_create_data); + + // Users are needed to send notifications + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + foreach ($methods as $method) + { + // setup the notification methods and add the notification to the queue + if ($method) // blank means we just insert it as a notification, but do not notify them by any other means + { + if (!isset($notification_methods[$method])) + { + $notification_methods[$method] = $this->get_method_class($method); + } + + $notification_methods[$method]->add_to_queue($notification); + } + } + } + + // insert into the db + $this->db->sql_multi_insert($this->notifications_table, $new_rows); + + // We need to load all of the users to send notifications + $this->user_loader->load_users($user_ids); + + // run the queue for each method to send notifications + foreach ($notification_methods as $method) + { + $method->notify(); + } + } + + /** + * Update a notification + * + * @param string|array $item_type Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be updated + */ + public function update_notifications($item_type, $data) + { + if (is_array($item_type)) + { + foreach ($item_type as $type) + { + $this->update_notifications($type, $data); + } + + return; + } + + $notification = $this->get_item_type_class($item_type); + + // Allow the notifications class to over-ride the update_notifications functionality + if (method_exists($notification, 'update_notifications')) + { + // Return False to over-ride the rest of the update + if ($notification->update_notifications($data) === false) + { + return; + } + } + + $item_id = $notification->get_item_id($data); + $update_array = $notification->create_update_array($data); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $update_array) . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id; + $this->db->sql_query($sql); + } + + /** + * Delete a notification + * + * @param string|array $item_type Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types) + * @param int|array $item_id Identifier within the type (or array of ids) + * @param array $data Data specific for this type that will be updated + */ + public function delete_notifications($item_type, $item_id) + { + if (is_array($item_type)) + { + foreach ($item_type as $type) + { + $this->delete_notifications($type, $item_id); + } + + return; + } + + $sql = 'DELETE FROM ' . $this->notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND " . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id); + $this->db->sql_query($sql); + } + + /** + * Get all of the subscription types + * + * @return array Array of item types + */ + public function get_subscription_types() + { + $subscription_types = array(); + + foreach ($this->notification_types as $type_name => $data) + { + $type = $this->get_item_type_class($type_name); + + if ($type instanceof phpbb_notification_type_interface && $type->is_available()) + { + $options = array_merge(array( + 'id' => $type->get_type(), + 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()), + 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS', + ), (($type::$notification_option !== false) ? $type::$notification_option : array())); + + $subscription_types[$options['group']][$options['id']] = $options; + } + } + + // Move Miscellaneous to the very last section + if (isset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'])) + { + $miscellaneous = $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']; + unset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']); + $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous; + } + + return $subscription_types; + } + + /** + * Get all of the subscription methods + * + * @return array Array of methods + */ + public function get_subscription_methods() + { + $subscription_methods = array(); + + foreach ($this->notification_methods as $method_name => $data) + { + $method = $this->get_method_class($method_name); + + if ($method instanceof phpbb_notification_method_interface && $method->is_available()) + { + $subscription_methods[$method_name] = array( + 'id' => $method->get_type(), + 'lang' => 'NOTIFICATION_METHOD_' . strtoupper($method->get_type()), + ); + } + } + + return $subscription_methods; + } + + /** + * Get global subscriptions (item_id = 0) + * + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + * + * @return array Subscriptions + */ + public function get_global_subscriptions($user_id = false) + { + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + $subscriptions = array(); + + foreach ($this->get_subscription_types() as $group_name => $types) + { + foreach ($types as $id => $type) + { + $sql = 'SELECT method, notify + FROM ' . $this->user_notifications_table . ' + WHERE user_id = ' . (int) $user_id . " + AND item_type = '" . $this->db->sql_escape($id) . "' + AND item_id = 0"; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + if (!$row) + { + // No rows at all, default to '' + $subscriptions[$id] = array(''); + } + else + { + do + { + if (!$row['notify']) + { + continue; + } + + if (!isset($subscriptions[$id])) + { + $subscriptions[$id] = array(); + } + + $subscriptions[$id][] = $row['method']; + } + while ($row = $this->db->sql_fetchrow($result)); + } + + $this->db->sql_freeresult($result); + } + } + + return $subscriptions; + } + + /** + * Add a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. '', 'email', or 'jabber' + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function add_subscription($item_type, $item_id = 0, $method = '', $user_id = false) + { + if ($method !== '') + { + $this->add_subscription($item_type, $item_type, '', $user_id); + } + + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + $sql = 'SELECT notify + FROM ' . $this->user_notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + $current = $this->db->sql_fetchfield('notify'); + $this->db->sql_freeresult(); + + if ($current === false) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 1, + )); + $this->db->sql_query($sql); + } + else if (!$current) + { + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 1 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + } + } + + /** + * Delete a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. '', 'email', or 'jabber' + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function delete_subscription($item_type, $item_id = 0, $method = '', $user_id = false) + { + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + // If no method, make sure that no other notification methods for this item are selected before deleting + if ($method === '') + { + $sql = 'SELECT COUNT(*) as num_notifications + FROM ' . $this->user_notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method <> '' + AND notify = 1"; + $this->db->sql_query($sql); + $num_notifications = $this->db->sql_fetchfield('num_notifications'); + $this->db->sql_freeresult(); + + if ($num_notifications) + { + return; + } + } + + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 0 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 0, + )); + $this->db->sql_query($sql); + } + } + + /** + * Disable all notifications of a certain type + * + * This should be called when an extension which has notification types + * is disabled so that all those notifications are hidden and do not + * cause errors + * + * @param string $item_type Type identifier of the subscription + */ + public function disable_notifications($item_type) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 0 + WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'"; + $this->db->sql_query($sql); + } + + /** + * Purge all notifications of a certain type + * + * This should be called when an extension which has notification types + * is purged so that all those notifications are removed + * + * @param string $item_type Type identifier of the subscription + */ + public function purge_notifications($item_type) + { + $sql = 'DELETE FROM ' . $this->notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "'"; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . $this->notification_types_table . " + WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'"; + $this->db->sql_query($sql); + } + + /** + * Enable all notifications of a certain type + * + * This should be called when an extension which has notification types + * that was disabled is re-enabled so that all those notifications that + * were hidden are shown again + * + * @param string $item_type Type identifier of the subscription + */ + public function enable_notifications($item_type) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 1 + WHERE notification_type = '" . $this->db->sql_escape($item_type) . "'"; + $this->db->sql_query($sql); + } + + /** + * Delete all notifications older than a certain time + * + * @param int $timestamp Unix timestamp to delete all notifications that were created before + */ + public function prune_notifications($timestamp) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_time < ' . (int) $timestamp; + $this->db->sql_query($sql); + } + + /** + * Helper to get the notifications item type class and set it up + */ + public function get_item_type_class($item_type, $data = array()) + { + $item_type = (strpos($item_type, 'notification.type.') === 0) ? $item_type : 'notification.type.' . $item_type; + + $item = $this->load_object($item_type); + + $item->set_initial_data($data); + + return $item; + } + + /** + * Helper to get the notifications method class and set it up + */ + public function get_method_class($method_name) + { + $method_name = (strpos($method_name, 'notification.method.') === 0) ? $method_name : 'notification.method.' . $method_name; + + return $this->load_object($method_name); + } + + /** + * Helper to load objects (notification types/methods) + */ + protected function load_object($object_name) + { + $object = $this->phpbb_container->get($object_name); + + if (method_exists($object, 'set_notification_manager')) + { + $object->set_notification_manager($this); + } + + return $object; + } +} diff --git a/phpBB/includes/notification/method/base.php b/phpBB/includes/notification/method/base.php new file mode 100644 index 0000000000..22418c9be8 --- /dev/null +++ b/phpBB/includes/notification/method/base.php @@ -0,0 +1,116 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Base notifications method class +* @package notifications +*/ +abstract class phpbb_notification_method_base implements phpbb_notification_method_interface +{ + /** @var phpbb_notification_manager */ + protected $notification_manager; + + /** @var phpbb_user_loader */ + protected $user_loader; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_cache_service */ + protected $cache; + + /** @var phpbb_template */ + protected $template; + + /** @var phpbb_extension_manager */ + protected $extension_manager; + + /** @var phpbb_user */ + protected $user; + + /** @var phpbb_auth */ + protected $auth; + + /** @var phpbb_config */ + protected $config; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** + * Queue of messages to be sent + * + * @var array + */ + protected $queue = array(); + + /** + * Notification Method Base Constructor + * + * @param phpbb_user_loader $user_loader + * @param phpbb_db_driver $db + * @param phpbb_cache_driver_interface $cache + * @param phpbb_user $user + * @param phpbb_auth $auth + * @param phpbb_config $config + * @param string $phpbb_root_path + * @param string $php_ext + * @return phpbb_notification_method_base + */ + public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext) + { + $this->user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->config = $config; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Set notification manager (required) + * + * @param phpbb_notification_manager $notification_manager + */ + public function set_notification_manager(phpbb_notification_manager $notification_manager) + { + $this->notification_manager = $notification_manager; + } + + /** + * Add a notification to the queue + * + * @param phpbb_notification_type_interface $notification + */ + public function add_to_queue(phpbb_notification_type_interface $notification) + { + $this->queue[] = $notification; + } + + /** + * Empty the queue + */ + protected function empty_queue() + { + $this->queue = array(); + } +} diff --git a/phpBB/includes/notification/method/email.php b/phpBB/includes/notification/method/email.php new file mode 100644 index 0000000000..429dfda2ba --- /dev/null +++ b/phpBB/includes/notification/method/email.php @@ -0,0 +1,129 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Email notification method class +* This class handles sending emails for notifications +* +* @package notifications +*/ +class phpbb_notification_method_email extends phpbb_notification_method_base +{ + /** + * Get notification method name + * + * @return string + */ + public function get_type() + { + return 'email'; + } + + /** + * Notify method (since jabber gets sent through the same messenger, we let the jabber class inherit from this to reduce code duplication) + * + * @var mixed + */ + protected $notify_method = NOTIFY_EMAIL; + + /** + * Base directory to prepend to the email template name + * + * @var string + */ + protected $email_template_base_dir = ''; + + /** + * Is this method available for the user? + * This is checked on the notifications options + */ + public function is_available() + { + // Email is always available + return true; + } + + /** + * Parse the queue and notify the users + */ + public function notify() + { + if (!sizeof($this->queue)) + { + return; + } + + // Load all users we want to notify (we need their email address) + $user_ids = $users = array(); + foreach ($this->queue as $notification) + { + $user_ids[] = $notification->user_id; + } + + // We do not send emails to banned users + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + $banned_users = phpbb_get_banned_user_ids($user_ids); + + // Load all the users we need + $this->user_loader->load_users($user_ids); + + // Load the messenger + if (!class_exists('messenger')) + { + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + $messenger = new messenger(); + $board_url = generate_board_url(); + + // Time to go through the queue and send emails + foreach ($this->queue as $notification) + { + if ($notification->get_email_template() === false) + { + continue; + } + + $user = $this->user_loader->get_user($notification->user_id); + + if ($user['user_type'] == USER_IGNORE || in_array($notification->user_id, $banned_users)) + { + continue; + } + + $messenger->template($this->email_template_base_dir . $notification->get_email_template(), $user['user_lang']); + + $messenger->to($user['user_email'], $user['username']); + + $messenger->assign_vars(array_merge(array( + 'USERNAME' => $user['username'], + + 'U_NOTIFICATION_SETTINGS' => generate_board_url() . '/ucp.' . $this->php_ext . '?i=ucp_notifications', + ), $notification->get_email_template_variables())); + + $messenger->send($this->notify_method); + } + + // Save the queue in the messenger class (has to be called or these emails could be lost?) + $messenger->save_queue(); + + // We're done, empty the queue + $this->empty_queue(); + } +} diff --git a/phpBB/includes/notification/method/interface.php b/phpBB/includes/notification/method/interface.php new file mode 100644 index 0000000000..ef875942cc --- /dev/null +++ b/phpBB/includes/notification/method/interface.php @@ -0,0 +1,48 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Base notifications method interface +* @package notifications +*/ +interface phpbb_notification_method_interface +{ + /** + * Get notification method name + * + * @return string + */ + public function get_type(); + + /** + * Is this method available for the user? + * This is checked on the notifications options + */ + public function is_available(); + + /** + * Add a notification to the queue + * + * @param phpbb_notification_type_interface $notification + */ + public function add_to_queue(phpbb_notification_type_interface $notification); + + /** + * Parse the queue and notify the users + */ + public function notify(); +} diff --git a/phpBB/includes/notification/method/jabber.php b/phpBB/includes/notification/method/jabber.php new file mode 100644 index 0000000000..e3eb571fbc --- /dev/null +++ b/phpBB/includes/notification/method/jabber.php @@ -0,0 +1,77 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Jabber notification method class +* This class handles sending Jabber messages for notifications +* +* @package notifications +*/ +class phpbb_notification_method_jabber extends phpbb_notification_method_email +{ + /** + * Get notification method name + * + * @return string + */ + public function get_type() + { + return 'jabber'; + } + + /** + * Notify method (since jabber gets sent through the same messenger, we let the jabber class inherit from this to reduce code duplication) + * + * @var mixed + */ + protected $notify_method = NOTIFY_IM; + + /** + * Base directory to prepend to the email template name + * + * @var string + */ + protected $email_template_base_dir = 'short/'; + + /** + * Is this method available for the user? + * This is checked on the notifications options + */ + public function is_available() + { + return ($this->global_available() && $this->user->data['jabber']); + } + + /** + * Is this method available at all? + * This is checked before notifications are sent + */ + public function global_available() + { + return ($this->config['jab_enable'] && @extension_loaded('xml')); + } + + public function notify() + { + if (!$this->global_available()) + { + return; + } + + return parent::notify(); + } +} diff --git a/phpBB/includes/notification/type/approve_post.php b/phpBB/includes/notification/type/approve_post.php new file mode 100644 index 0000000000..1a30781c35 --- /dev/null +++ b/phpBB/includes/notification/type/approve_post.php @@ -0,0 +1,140 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post approved notifications class +* This class handles notifications for posts when they are approved (to their authors) +* +* @package notifications +*/ +class phpbb_notification_type_approve_post extends phpbb_notification_type_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'approve_post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_APPROVED'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = array(''); + + $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('post_subject', $post['post_subject']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_approved'; + } +} diff --git a/phpBB/includes/notification/type/approve_topic.php b/phpBB/includes/notification/type/approve_topic.php new file mode 100644 index 0000000000..e728e9ac30 --- /dev/null +++ b/phpBB/includes/notification/type/approve_topic.php @@ -0,0 +1,138 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Topic approved notifications class +* This class handles notifications for topics when they are approved (for authors) +* +* @package notifications +*/ +class phpbb_notification_type_approve_topic extends phpbb_notification_type_topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'approve_topic'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_APPROVED'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = array(''); + + $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_approved'; + } +} diff --git a/phpBB/includes/notification/type/base.php b/phpBB/includes/notification/type/base.php new file mode 100644 index 0000000000..600ef7c965 --- /dev/null +++ b/phpBB/includes/notification/type/base.php @@ -0,0 +1,479 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Base notifications class +* @package notifications +*/ +abstract class phpbb_notification_type_base implements phpbb_notification_type_interface +{ + /** @var phpbb_notification_manager */ + protected $notification_manager; + + /** @var phpbb_user_loader */ + protected $user_loader; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_cache_service */ + protected $cache; + + /** @var phpbb_template */ + protected $template; + + /** @var phpbb_user */ + protected $user; + + /** @var phpbb_auth */ + protected $auth; + + /** @var phpbb_config */ + protected $config; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $notification_types_table; + + /** @var string */ + protected $notifications_table; + + /** @var string */ + protected $user_notifications_table; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use its default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = false; + + /** + * Indentification data + * item_type - Type of the item (translates to the notification type) + * item_id - ID of the item (e.g. post_id, msg_id) + * item_parent_id - Parent item id (ex: for topic => forum_id, for post => topic_id, etc) + * user_id + * notification_read + * notification_time + * notification_data (special serialized field that each notification type can use to store stuff) + * + * @var array $data Notification row from the database + * This must be private, all interaction should use __get(), __set(), get_data(), set_data() + */ + private $data = array(); + + /** + * Notification Type Base Constructor + * + * @param phpbb_user_loader $user_loader + * @param phpbb_db_driver $db + * @param phpbb_cache_driver_interface $cache + * @param phpbb_user $user + * @param phpbb_auth $auth + * @param phpbb_config $config + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $notification_types_table + * @param string $notifications_table + * @param string $user_notifications_table + * @return phpbb_notification_type_base + */ + public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) + { + $this->user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->config = $config; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->notification_types_table = $notification_types_table; + $this->notifications_table = $notifications_table; + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Set notification manager (required) + * + * @param phpbb_notification_manager $notification_manager + */ + public function set_notification_manager(phpbb_notification_manager $notification_manager) + { + $this->notification_manager = $notification_manager; + } + + /** + * Set initial data from the database + * + * @param array $data Row directly from the database + */ + public function set_initial_data($data = array()) + { + // The row from the database (unless this is a new notification we're going to add) + $this->data = $data; + $this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array(); + } + + /** + * Magic method to get data from this notification + * + * @param mixed $name + * @return mixed + */ + public function __get($name) + { + return (!isset($this->data[$name])) ? null : $this->data[$name]; + } + + + /** + * Magic method to set data on this notification + * + * @param mixed $name + * @return null + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + /** + * Magic method to get a string of this notification + * + * Primarily for testing + * + * @param string $name + * @return mixed + */ + public function __toString() + { + return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type(); + } + + /** + * Get special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to get + * @return mixed + */ + protected function get_data($name) + { + return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null); + } + + /** + * Set special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to set + * @param mixed $value Value to set to the variable + * @return mixed + */ + protected function set_data($name, $value) + { + $this->data['notification_data'][$name] = $value; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * @param array $pre_create_data Data from pre_create_insert_array() + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($type_data, $pre_create_data = array()) + { + // Defaults + $this->data = array_merge(array( + 'item_id' => static::get_item_id($type_data), + 'item_type' => $this->get_type(), + 'item_parent_id' => static::get_item_parent_id($type_data), + + 'notification_time' => time(), + 'notification_read' => false, + + 'notification_data' => array(), + ), $this->data); + + $data = $this->data; + + $data['notification_data'] = serialize($data['notification_data']); + + return $data; + } + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data) + { + $data = $this->create_insert_array($type_data); + + // Unset data unique to each row + unset( + $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function) + $data['notification_id'], + $data['notification_read'], + $data['user_id'] + ); + + return $data; + } + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_read($return = false) + { + return $this->mark(false, $return); + } + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_unread($return = false) + { + return $this->mark(true, $return); + } + + /** + * Prepare to output the notification to the template + * + * @return array Template variables + */ + public function prepare_for_display() + { + if ($this->get_url()) + { + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id); + } + else + { + $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); + + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&redirect=' . urlencode($redirect)); + } + + return array( + 'NOTIFICATION_ID' => $this->notification_id, + + 'AVATAR' => $this->get_avatar(), + + 'FORMATTED_TITLE' => $this->get_title(), + + 'URL' => $this->get_url(), + 'TIME' => $this->user->format_date($this->notification_time), + + 'UNREAD' => !$this->notification_read, + + 'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '', + ); + } + + /** + * -------------- Fall back functions ------------------- + */ + + /** + * URL to unsubscribe to this notification (fall back) + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + */ + public function get_unsubscribe_url($method = false) + { + return false; + } + + /** + * Get the user's avatar (fall back) + * + * @return string + */ + public function get_avatar() + { + return ''; + } + + /** + * Get the special items to load (fall back) + * + * @return array + */ + public function get_load_special() + { + return array(); + } + + /** + * Load the special items (fall back) + */ + public function load_special($data, $notifications) + { + return; + } + + /** + * Is available (fall back) + * + * @return bool + */ + public function is_available() + { + return true; + } + + /** + * Pre create insert array function (fall back) + * + * @return array + */ + public function pre_create_insert_array($type_data, $notify_users) + { + return array(); + } + + /** + * -------------- Helper functions ------------------- + */ + + /** + * Find the users who want to receive notifications (helper) + * + * @param array $user_ids User IDs to check if they want to receive notifications + * (Bool False to check all users besides anonymous and bots (USER_IGNORE)) + * + * @return array + */ + protected function check_user_notification_options($user_ids = false, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + 'item_type' => $this->get_type(), + 'item_id' => 0, // Global by default + ), $options); + + if ($user_ids === false) + { + $user_ids = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_id <> ' . ANONYMOUS . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + } + + if (empty($user_ids)) + { + return array(); + } + + $rowset = $resulting_user_ids = array(); + + $sql = 'SELECT user_id, method, notify + FROM ' . $this->user_notifications_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . " + AND item_type = '" . $this->db->sql_escape($options['item_type']) . "' + AND item_id = " . (int) $options['item_id']; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $resulting_user_ids[] = $row['user_id']; + + if (!$row['notify'] || (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))) + { + continue; + } + + if (!isset($rowset[$row['user_id']])) + { + $rowset[$row['user_id']] = array(); + } + + $rowset[$row['user_id']][] = $row['method']; + } + + $this->db->sql_freeresult($result); + + foreach ($user_ids as $user_id) + { + if (!in_array($user_id, $resulting_user_ids) && !isset($options['ignore_users'][$user_id])) + { + // No rows at all for this user, default to '' + $rowset[$user_id] = array(''); + } + } + + return $rowset; + } + + /** + * Mark this item read/unread helper + * + * @param bool $unread Unread (True/False) (Default: False) + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + protected function mark($unread = true, $return = false) + { + $this->notification_read = (bool) !$unread; + + $where = array( + "item_type = '" . $this->db->sql_escape($this->item_type) . "'", + 'item_id = ' . (int) $this->item_id, + 'user_id = ' . (int) $this->user_id, + ); + $where = implode(' AND ', $where); + + if ($return) + { + return $where; + } + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET notification_read = ' . (int) $this->notification_read . ' + WHERE ' . $where; + $this->db->sql_query($sql); + } +} diff --git a/phpBB/includes/notification/type/bookmark.php b/phpBB/includes/notification/type/bookmark.php new file mode 100644 index 0000000000..4e48a967d0 --- /dev/null +++ b/phpBB/includes/notification/type/bookmark.php @@ -0,0 +1,137 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Bookmark updating notifications class +* This class handles notifications for replies to a bookmarked topic +* +* @package notifications +*/ +class phpbb_notification_type_bookmark extends phpbb_notification_type_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'bookmark'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_BOOKMARK'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_BOOKMARK', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_bookmarks']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $post['topic_id']) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt + WHERE n.item_type = '" . $this->get_type() . "' + AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'bookmark'; + } +} diff --git a/phpBB/includes/notification/type/disapprove_post.php b/phpBB/includes/notification/type/disapprove_post.php new file mode 100644 index 0000000000..951c7e0254 --- /dev/null +++ b/phpBB/includes/notification/type/disapprove_post.php @@ -0,0 +1,120 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post disapproved notifications class +* This class handles notifications for posts when they are disapproved (for authors) +* +* @package notifications +*/ +class phpbb_notification_type_disapprove_post extends phpbb_notification_type_approve_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'disapprove_post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_DISAPPROVED'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->user->lang( + $this->language_key, + censor_text($this->get_data('topic_title')), + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + $data = parent::create_insert_array($post); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_disapproved'; + } +} diff --git a/phpBB/includes/notification/type/disapprove_topic.php b/phpBB/includes/notification/type/disapprove_topic.php new file mode 100644 index 0000000000..038e528797 --- /dev/null +++ b/phpBB/includes/notification/type/disapprove_topic.php @@ -0,0 +1,120 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Topic disapproved notifications class +* This class handles notifications for topics when they are disapproved (for authors) +* +* @package notifications +*/ +class phpbb_notification_type_disapprove_topic extends phpbb_notification_type_approve_topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'disapprove_topic'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_DISAPPROVED'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->user->lang( + $this->language_key, + censor_text($this->get_data('topic_title')), + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_disapproved'; + } +} diff --git a/phpBB/includes/notification/type/interface.php b/phpBB/includes/notification/type/interface.php new file mode 100644 index 0000000000..a40fdafd09 --- /dev/null +++ b/phpBB/includes/notification/type/interface.php @@ -0,0 +1,189 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Base notifications interface +* @package notifications +*/ +interface phpbb_notification_type_interface +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type(); + + /** + * Set initial data from the database + * + * @param array $data Row directly from the database + */ + public function set_initial_data($data); + + /** + * Get the id of the item + * + * @param array $type_data The type specific data + */ + public static function get_item_id($type_data); + + /** + * Get the id of the parent + * + * @param array $type_data The type specific data + */ + public static function get_item_parent_id($type_data); + + /** + * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) + * + * @return bool True/False whether or not this is available to the user + */ + public function is_available(); + + /** + * Find the users who want to receive notifications + * + * @param array $type_data The type specific data + * @param array $options Options for finding users for notification + * ignore_users => array of users and user types that should not receive notifications from this type because they've already been notified + * e.g.: array(2 => array(''), 3 => array('', 'email'), ...) + * + * @return array + */ + public function find_users_for_notification($type_data, $options); + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query(); + + /** + * Get the special items to load + * + * @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type + */ + public function get_load_special(); + + /** + * Load the special items + * + * @param array $data Data from get_load_special() + * @param array $notifications Array of notifications (key is notification_id, value is the notification objects) + */ + public function load_special($data, $notifications); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title(); + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url(); + + /** + * URL to unsubscribe to this notification + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + */ + public function get_unsubscribe_url($method); + + /** + * Get the user's avatar (the user who caused the notification typically) + * + * @return string + */ + public function get_avatar(); + + /** + * Prepare to output the notification to the template + */ + public function prepare_for_display(); + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template(); + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables(); + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $type_data The type specific data + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($type_data, $notify_users); + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $type_data The type specific data + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($type_data, $pre_create_data); + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data); + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_read($return); + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_unread($return); +} diff --git a/phpBB/includes/notification/type/pm.php b/phpBB/includes/notification/type/pm.php new file mode 100644 index 0000000000..b3db7ad5ad --- /dev/null +++ b/phpBB/includes/notification/type/pm.php @@ -0,0 +1,184 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Private message notifications class +* This class handles notifications for private messages +* +* @package notifications +*/ +class phpbb_notification_type_pm extends phpbb_notification_type_base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'pm'; + } + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_PM', + ); + + /** + * Is available + */ + public function is_available() + { + return ($this->config['allow_privmsg'] && $this->auth->acl_get('u_readpm')); + } + + /** + * Get the id of the + * + * @param array $pm The data from the private message + */ + public static function get_item_id($pm) + { + return (int) $pm['msg_id']; + } + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + */ + public static function get_item_parent_id($pm) + { + // No parent + return 0; + } + + /** + * Find the users who want to receive notifications + * + * @param array $pm Data from + * + * @return array + */ + public function find_users_for_notification($pm, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (!sizeof($pm['recipients'])) + { + return array(); + } + + unset($pm['recipients'][$pm['from_user_id']]); + + $this->user_loader->load_users(array_keys($pm['recipients'])); + + return $this->check_user_notification_options(array_keys($pm['recipients']), $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('from_user_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile'); + + return $this->user->lang('NOTIFICATION_PM', $username, $this->get_data('message_subject')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'privmsg_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('from_user_id')); + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_MESSAGE' => generate_board_url() . '/ucp.' . $this->php_ext . "?i=pm&mode=view&p={$this->item_id}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=pm&mode=view&p={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('from_user_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('from_user_id', $pm['from_user_id']); + + $this->set_data('message_subject', $pm['message_subject']); + + return parent::create_insert_array($pm, $pre_create_data); + } +} diff --git a/phpBB/includes/notification/type/post.php b/phpBB/includes/notification/type/post.php new file mode 100644 index 0000000000..ddfa720e5e --- /dev/null +++ b/phpBB/includes/notification/type/post.php @@ -0,0 +1,370 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post notifications class +* This class handles notifications for replies to a topic +* +* @package notifications +*/ +class phpbb_notification_type_post extends phpbb_notification_type_base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_POST', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_topic_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + */ + public static function get_item_id($post) + { + return (int) $post['post_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + */ + public static function get_item_parent_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt + WHERE n.item_type = '" . $this->get_type() . "' + AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $responders = $this->get_data('responders'); + $usernames = array(); + + if (!is_array($responders)) + { + $responders = array(); + } + + $responders = array_merge(array(array( + 'poster_id' => $this->get_data('poster_id'), + 'username' => $this->get_data('post_username'), + )), $responders); + + foreach ($responders as $responder) + { + if ($responder['username']) + { + $usernames[] = $responder['username']; + } + else + { + $usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile'); + } + } + + return $this->user->lang( + $this->language_key, + implode(', ', $usernames), + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", + 'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + $responders = $this->get_data('responders'); + $users = array( + $this->get_data('poster_id'), + ); + + if (is_array($responders)) + { + foreach ($responders as $responder) + { + $users[] = $responder['poster_id']; + } + } + + return $users; + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!sizeof($notify_users)) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + + return $tracking_data; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_subject', $post['post_subject']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_id', $post['forum_id']); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + return parent::create_insert_array($post, $pre_create_data); + } + + /** + * Add responders to the notification + * + * @param mixed $post + */ + public function add_responders($post) + { + // Do not add them as a responder if they were the original poster that created the notification + if ($this->get_data('poster_id') == $post['poster_id']) + { + return array('notification_data' => serialize($this->get_data(false))); + } + + $responders = $this->get_data('responders'); + + $responders = ($responders === null) ? array() : $responders; + + foreach ($responders as $responder) + { + // Do not add them as a responder multiple times + if ($responder['poster_id'] == $post['poster_id']) + { + return array('notification_data' => serialize($this->get_data(false))); + } + } + + $responders[] = array( + 'poster_id' => $post['poster_id'], + 'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''), + ); + + $this->set_data('responders', $responders); + + return array('notification_data' => serialize($this->get_data(false))); + } +} diff --git a/phpBB/includes/notification/type/post_in_queue.php b/phpBB/includes/notification/type/post_in_queue.php new file mode 100644 index 0000000000..1c29bee3cd --- /dev/null +++ b/phpBB/includes/notification/type/post_in_queue.php @@ -0,0 +1,137 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post in queue notifications class +* This class handles notifications for posts that are put in the moderation queue (for moderators) +* +* @package notifications +*/ +class phpbb_notification_type_post_in_queue extends phpbb_notification_type_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'post_in_queue'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_IN_QUEUE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_approve'; + + /** + * Is available + */ + public function is_available() + { + $m_approve = $this->auth->acl_getf($this->permission, true); + + return (!empty($m_approve)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global + $auth_approve = $this->auth->acl_get_list(false, $this->permission, array($post['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $auth_approve[$post['forum_id']] = array_unique(array_merge($auth_approve[$post['forum_id']], $auth_approve[0])); + + return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->get_data('forum_id')}&p={$this->item_id}"); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_in_queue'; + } +} diff --git a/phpBB/includes/notification/type/quote.php b/phpBB/includes/notification/type/quote.php new file mode 100644 index 0000000000..5453b267c8 --- /dev/null +++ b/phpBB/includes/notification/type/quote.php @@ -0,0 +1,221 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post quoting notifications class +* This class handles notifications for quoting users in a post +* +* @package notifications +*/ +class phpbb_notification_type_quote extends phpbb_notification_type_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'quote'; + } + + /** + * regular expression to match to find usernames + * + * @var string + */ + protected static $regular_expression_match = '#\[quote="(.+?)"#'; + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_QUOTE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_QUOTE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return true; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $usernames = false; + preg_match_all(self::$regular_expression_match, $post['post_text'], $usernames); + + if (empty($usernames[1])) + { + return array(); + } + + $usernames[1] = array_unique($usernames[1]); + + $usernames = array_map('utf8_clean_string', $usernames[1]); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE ' . $this->db->sql_in_set('username_clean', $usernames) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt + WHERE n.item_type = '" . $this->get_type() . "' + AND n.item_parent_id = " . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Update a notification + * + * @param array $data Data specific for this type that will be updated + */ + public function update_notifications($post) + { + $old_notifications = array(); + $sql = 'SELECT n.user_id + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . " nt + WHERE n.item_type = '" . $this->get_type() . "' + AND n.item_id = " . self::get_item_id($post) . ' + AND nt.notification_type = n.item_type + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $old_notifications[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + // Find the new users to notify + $notifications = $this->find_users_for_notification($post); + + // Find the notifications we must delete + $remove_notifications = array_diff($old_notifications, array_keys($notifications)); + + // Find the notifications we must add + $add_notifications = array(); + foreach (array_diff(array_keys($notifications), $old_notifications) as $user_id) + { + $add_notifications[$user_id] = $notifications[$user_id]; + } + + // Add the necessary notifications + $this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications); + + // Remove the necessary notifications + if (!empty($remove_notifications)) + { + $sql = 'DELETE FROM ' . $this->notifications_table . " + WHERE item_type = '" . $this->get_type() . "' + AND item_id = " . self::get_item_id($post) . ' + AND ' . $this->db->sql_in_set('user_id', $remove_notifications); + $this->db->sql_query($sql); + } + + // return true to continue with the update code in the notifications service (this will update the rest of the notifications) + return true; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'quote'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('poster_id')); + + return array_merge(parent::get_email_template_variables(), array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + )); + } +} diff --git a/phpBB/includes/notification/type/report_pm.php b/phpBB/includes/notification/type/report_pm.php new file mode 100644 index 0000000000..3fa73bab41 --- /dev/null +++ b/phpBB/includes/notification/type/report_pm.php @@ -0,0 +1,229 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Private message reproted notifications class +* This class handles notifications for private messages when they are reported +* +* @package notifications +*/ +class phpbb_notification_type_report_pm extends phpbb_notification_type_pm +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'report_pm'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_PM'; + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_report'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + */ + public static function get_item_parent_id($pm) + { + return (int) $pm['report_id']; + } + + /** + * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) + * + * @return bool True/False whether or not this is available to the user + */ + public function is_available() + { + $m_approve = $this->auth->acl_getf($this->permission, true); + + return (!empty($m_approve)); + } + + + /** + * Find the users who want to receive notifications + * (copied from post_in_queue) + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // Global + $post['forum_id'] = 0; + + $auth_approve = $this->auth->acl_get_list(false, $this->permission, $post['forum_id']); + + if (empty($auth_approve)) + { + return array(); + } + + if (($key = array_search($this->user->data['user_id'], $auth_approve[$post['forum_id']][$this->permission]))) + { + unset($auth_approve[$post['forum_id']][$this->permission][$key]); + } + + return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_pm'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_REPORT' => generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->user->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + if ($this->get_data('report_text')) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->get_data('report_text') + ); + } + + if (isset($this->user->lang[$this->get_data('reason_title')])) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->user->lang[$this->get_data('reason_title')] + ); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('reporter_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/includes/notification/type/report_pm_closed.php b/phpBB/includes/notification/type/report_pm_closed.php new file mode 100644 index 0000000000..63dfa92064 --- /dev/null +++ b/phpBB/includes/notification/type/report_pm_closed.php @@ -0,0 +1,155 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* PM report closed notifications class +* This class handles notifications for when reports are closed on PMs (for the one who reported the PM) +* +* @package notifications +*/ +class phpbb_notification_type_report_pm_closed extends phpbb_notification_type_pm +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'report_pm_closed'; + } + + /** + * Email template to use to send notifications + * + * @var string + */ + public $email_template = ''; + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_CLOSED'; + + public function is_available() + { + return false; + } + + /** + * Find the users who want to receive notifications + * + * @param array $pm Data from + * + * @return array + */ + public function find_users_for_notification($pm, $options = array()) + { + if ($pm['reporter'] == $this->user->data['user_id']) + { + return array(); + } + + return array($pm['reporter'] => array('')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->get_user_avatar($this->get_data('closer_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $pm PM Data + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('closer_id', $pm['closer_id']); + + $data = parent::create_insert_array($pm, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } +} diff --git a/phpBB/includes/notification/type/report_post.php b/phpBB/includes/notification/type/report_post.php new file mode 100644 index 0000000000..de5c54a291 --- /dev/null +++ b/phpBB/includes/notification/type/report_post.php @@ -0,0 +1,196 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Reported post notifications class +* This class handles notifications for reported posts +* +* @package notifications +*/ +class phpbb_notification_type_report_post extends phpbb_notification_type_post_in_queue +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'report_post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_POST'; + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_report'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id' and 'lang') + */ + public static $notification_option = array( + 'id' => 'report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $notify_users = parent::find_users_for_notification($post, $options); + + // never notify reporter + unset($notify_users[$this->user->data['user_id']]); + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_post'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + return array( + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_REPORT' => "{$board_url}/mcp.{$this->php_ext}?f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports", + 'U_VIEW_POST' => "{$board_url}/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->user->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + if ($this->get_data('report_text')) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->get_data('report_text') + ); + } + + if (isset($this->user->lang[$this->get_data('reason_title')])) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->user->lang[$this->get_data('reason_title')] + ); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('reporter_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/includes/notification/type/report_post_closed.php b/phpBB/includes/notification/type/report_post_closed.php new file mode 100644 index 0000000000..3916cd8db7 --- /dev/null +++ b/phpBB/includes/notification/type/report_post_closed.php @@ -0,0 +1,155 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Post report closed notifications class +* This class handles notifications for when reports are closed on posts (for the one who reported the post) +* +* @package notifications +*/ +class phpbb_notification_type_report_post_closed extends phpbb_notification_type_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'report_post_closed'; + } + + /** + * Email template to use to send notifications + * + * @var string + */ + public $email_template = ''; + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_CLOSED'; + + public function is_available() + { + return false; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + if ($post['reporter'] == $this->user->data['user_id']) + { + return array(); + } + + return array($post['reporter'] => array('')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('closer_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('closer_id', $post['closer_id']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } +} diff --git a/phpBB/includes/notification/type/topic.php b/phpBB/includes/notification/type/topic.php new file mode 100644 index 0000000000..2549b29409 --- /dev/null +++ b/phpBB/includes/notification/type/topic.php @@ -0,0 +1,277 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Topic notifications class +* This class handles notifications for new topics +* +* @package notifications +*/ +class phpbb_notification_type_topic extends phpbb_notification_type_base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'topic'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_TOPIC', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_forum_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + */ + public static function get_item_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + */ + public static function get_item_parent_id($post) + { + return (int) $post['forum_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . FORUMS_WATCH_TABLE . ' + WHERE forum_id = ' . (int) $topic['forum_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $topic['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $topic['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('topic_title')), + $this->get_data('forum_name') + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'newtopic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}", + 'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('poster_id')); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!sizeof($notify_users)) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + + return $tracking_data; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/includes/notification/type/topic_in_queue.php b/phpBB/includes/notification/type/topic_in_queue.php new file mode 100644 index 0000000000..dc0b9f9869 --- /dev/null +++ b/phpBB/includes/notification/type/topic_in_queue.php @@ -0,0 +1,130 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Topic in queue notifications class +* This class handles notifications for topics when they are put in the moderation queue (for moderators) +* +* @package notifications +*/ +class phpbb_notification_type_topic_in_queue extends phpbb_notification_type_topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'topic_in_queue'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_IN_QUEUE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + public static $notification_option = array( + 'id' => 'needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Is available + */ + public function is_available() + { + $m_approve = $this->auth->acl_getf('m_approve', true); + + return (!empty($m_approve)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global + $auth_approve = $this->auth->acl_get_list(false, 'm_approve', array($topic['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $auth_approve[$topic['forum_id']] = array_unique(array_merge($auth_approve[$topic['forum_id']], $auth_approve[0])); + + return $this->check_user_notification_options($auth_approve[$topic['forum_id']]['m_approve'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $topic Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($topic, $pre_create_data = array()) + { + $data = parent::create_insert_array($topic, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_in_queue'; + } +} diff --git a/phpBB/includes/search/base.php b/phpBB/includes/search/base.php index b364dead9a..914cef9167 100644 --- a/phpBB/includes/search/base.php +++ b/phpBB/includes/search/base.php @@ -94,7 +94,7 @@ class phpbb_search_base * * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE */ - function obtain_ids($search_key, &$result_count, &$id_ary, $start, $per_page, $sort_dir) + function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) { global $cache; @@ -109,6 +109,19 @@ class phpbb_search_base $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; $complete = true; + // Change start parameter in case out of bounds + if ($result_count) + { + if ($start < 0) + { + $start = 0; + } + else if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + } + } + // change the start to the actual end of the current request if the sort direction differs // from the dirction in the cache and reverse the ids later if ($reverse_ids) diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 913334bd08..e3ec56423e 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -353,7 +353,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts if (!$this->search_query) @@ -375,6 +375,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base implode(',', $author_ary) ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -475,16 +480,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $id_ary = array_unique($id_ary); - if (!sizeof($id_ary)) - { - return false; - } - // if the total result count is not cached yet, retrieve it from the db if (!$result_count) { - $sql = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql); + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); $result_count = (int) $this->db->sql_fetchfield('result_count'); $this->db->sql_freeresult($result); @@ -494,6 +494,21 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } } + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -520,7 +535,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No author? No posts if (!sizeof($author_ary)) @@ -544,6 +559,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $author_name, ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -638,8 +658,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base // retrieve the total result count if needed if (!$result_count) { - $sql = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql); + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); $result_count = (int) $this->db->sql_fetchfield('result_count'); $this->db->sql_freeresult($result); @@ -649,6 +669,20 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } } + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php index 3dc44be682..90ea553ddc 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -516,7 +516,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts. if (empty($this->search_query)) @@ -848,10 +848,6 @@ class phpbb_search_fulltext_native extends phpbb_search_base } $this->db->sql_freeresult($result); - if (!sizeof($id_ary)) - { - return false; - } // if we use mysql and the total result count is not cached yet, retrieve it from the db if (!$total_results && $is_mysql) @@ -860,14 +856,14 @@ class phpbb_search_fulltext_native extends phpbb_search_base $sql_array_copy = $sql_array; $sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id '; - $sql = $this->db->sql_build_query('SELECT', $sql_array_copy); + $sql_calc = $this->db->sql_build_query('SELECT', $sql_array_copy); unset($sql_array_copy); - $this->db->sql_query($sql); + $this->db->sql_query($sql_calc); $this->db->sql_freeresult($result); - $sql = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql); + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); $total_results = (int) $this->db->sql_fetchfield('total_results'); $this->db->sql_freeresult($result); @@ -877,6 +873,20 @@ class phpbb_search_fulltext_native extends phpbb_search_base } } + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -903,7 +913,11 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ +<<<<<<< HEAD public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) +======= + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) +>>>>>>> bee4f8d8185d4ff5278be758db4ea4a814f09b4f { // No author? No posts if (!sizeof($author_ary)) @@ -1077,13 +1091,13 @@ class phpbb_search_fulltext_native extends phpbb_search_base if (!$total_results && $is_mysql) { // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. - $sql = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); + $sql_calc = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); - $this->db->sql_query($sql); + $this->db->sql_query($sql_calc); $this->db->sql_freeresult($result); - $sql = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql); + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); $total_results = (int) $this->db->sql_fetchfield('total_results'); $this->db->sql_freeresult($result); @@ -1093,6 +1107,19 @@ class phpbb_search_fulltext_native extends phpbb_search_base } } + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 71ef347a1b..d5deb47222 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -343,7 +343,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts if (!$this->search_query) @@ -371,6 +371,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base implode(',', $author_ary) ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -463,10 +468,14 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $tmp_sql_match[] = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')"; } + $this->db->sql_transaction('begin'); + + $sql_from = "FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p"; + $sql_where = "WHERE (" . implode(' OR ', $tmp_sql_match) . ") + $sql_where_options"; $sql = "SELECT $sql_select - FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p - WHERE (" . implode(' OR ', $tmp_sql_match) . ") - $sql_where_options + $sql_from + $sql_where ORDER BY $sql_sort"; $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); @@ -478,15 +487,15 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $id_ary = array_unique($id_ary); - if (!sizeof($id_ary)) - { - return false; - } - // if the total result count is not cached yet, retrieve it from the db if (!$result_count) { - $result_count = sizeof ($id_ary); + $sql_count = "SELECT COUNT(*) as result_count + $sql_from + $sql_where"; + $result = $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); if (!$result_count) { @@ -494,6 +503,23 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } } + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -520,7 +546,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ +<<<<<<< HEAD public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) +======= + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) +>>>>>>> bee4f8d8185d4ff5278be758db4ea4a814f09b4f { // No author? No posts if (!sizeof($author_ary)) @@ -544,6 +574,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $author_name, ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -623,6 +658,8 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $field = 'topic_id'; } + $this->db->sql_transaction('begin'); + // Only read one block of posts from the db and then cache it $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); @@ -635,7 +672,35 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base // retrieve the total result count if needed if (!$result_count) { - $result_count = sizeof ($id_ary); + if ($type == 'posts') + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time"; + } + else + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key]"; + } + + $result = $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); if (!$result_count) { @@ -643,6 +708,22 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } } + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 7c0be91083..441fb2583b 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -454,7 +454,7 @@ class phpbb_search_fulltext_sphinx * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts. if (!strlen($this->search_query) && !sizeof($author_ary)) @@ -611,6 +611,25 @@ class phpbb_search_fulltext_sphinx } } + $result_count = $result['total_found']; + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + + // Could be connection to localhost:9312 failed (errno=111, + // msg=Connection refused) during rotate, retry if so + $retries = SPHINX_CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) + { + usleep(SPHINX_CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + } + } + $id_ary = array(); if (isset($result['matches'])) { @@ -631,8 +650,6 @@ class phpbb_search_fulltext_sphinx return false; } - $result_count = $result['total_found']; - $id_ary = array_slice($id_ary, 0, (int) $per_page); return $result_count; @@ -880,8 +897,8 @@ class phpbb_search_fulltext_sphinx <dd><input id="fulltext_sphinx_indexer_mem_limit" type="text" size="4" maxlength="10" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" /> ' . $this->user->lang['MIB'] . '</dd> </dl> <dl> - <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</dt> - <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6">' . $this->config_file_data . '</textarea>' : $this->config_file_data) . '</dd> + <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</span></dt> + <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd> <dl> '; diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index ee8a4094c7..6bc71da0c1 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -346,7 +346,7 @@ class phpbb_session $session_id = $request->variable('sid', ''); if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) { - send_status_line(401, 'Not authorized'); + send_status_line(401, 'Unauthorized'); redirect(append_sid("{$phpbb_root_path}index.$phpEx")); } diff --git a/phpBB/includes/sphinxapi.php b/phpBB/includes/sphinxapi.php index bd83b1d2e0..6c3b66710c 100644 --- a/phpBB/includes/sphinxapi.php +++ b/phpBB/includes/sphinxapi.php @@ -1,1712 +1,1712 @@ -<?php
-
-//
-// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
-//
-
-//
-// Copyright (c) 2001-2012, Andrew Aksyonoff
-// Copyright (c) 2008-2012, Sphinx Technologies Inc
-// All rights reserved
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License. You should have
-// received a copy of the GPL license along with this program; if you
-// did not, you can find it at http://www.gnu.org/
-//
-
-/////////////////////////////////////////////////////////////////////////////
-// PHP version of Sphinx searchd client (PHP API)
-/////////////////////////////////////////////////////////////////////////////
-
-/// known searchd commands
-define ( "SEARCHD_COMMAND_SEARCH", 0 );
-define ( "SEARCHD_COMMAND_EXCERPT", 1 );
-define ( "SEARCHD_COMMAND_UPDATE", 2 );
-define ( "SEARCHD_COMMAND_KEYWORDS", 3 );
-define ( "SEARCHD_COMMAND_PERSIST", 4 );
-define ( "SEARCHD_COMMAND_STATUS", 5 );
-define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 );
-
-/// current client-side command implementation versions
-define ( "VER_COMMAND_SEARCH", 0x119 );
-define ( "VER_COMMAND_EXCERPT", 0x104 );
-define ( "VER_COMMAND_UPDATE", 0x102 );
-define ( "VER_COMMAND_KEYWORDS", 0x100 );
-define ( "VER_COMMAND_STATUS", 0x100 );
-define ( "VER_COMMAND_QUERY", 0x100 );
-define ( "VER_COMMAND_FLUSHATTRS", 0x100 );
-
-/// known searchd status codes
-define ( "SEARCHD_OK", 0 );
-define ( "SEARCHD_ERROR", 1 );
-define ( "SEARCHD_RETRY", 2 );
-define ( "SEARCHD_WARNING", 3 );
-
-/// known match modes
-define ( "SPH_MATCH_ALL", 0 );
-define ( "SPH_MATCH_ANY", 1 );
-define ( "SPH_MATCH_PHRASE", 2 );
-define ( "SPH_MATCH_BOOLEAN", 3 );
-define ( "SPH_MATCH_EXTENDED", 4 );
-define ( "SPH_MATCH_FULLSCAN", 5 );
-define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
-
-/// known ranking modes (ext2 only)
-define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
-define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
-define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
-define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
-define ( "SPH_RANK_PROXIMITY", 4 );
-define ( "SPH_RANK_MATCHANY", 5 );
-define ( "SPH_RANK_FIELDMASK", 6 );
-define ( "SPH_RANK_SPH04", 7 );
-define ( "SPH_RANK_EXPR", 8 );
-define ( "SPH_RANK_TOTAL", 9 );
-
-/// known sort modes
-define ( "SPH_SORT_RELEVANCE", 0 );
-define ( "SPH_SORT_ATTR_DESC", 1 );
-define ( "SPH_SORT_ATTR_ASC", 2 );
-define ( "SPH_SORT_TIME_SEGMENTS", 3 );
-define ( "SPH_SORT_EXTENDED", 4 );
-define ( "SPH_SORT_EXPR", 5 );
-
-/// known filter types
-define ( "SPH_FILTER_VALUES", 0 );
-define ( "SPH_FILTER_RANGE", 1 );
-define ( "SPH_FILTER_FLOATRANGE", 2 );
-
-/// known attribute types
-define ( "SPH_ATTR_INTEGER", 1 );
-define ( "SPH_ATTR_TIMESTAMP", 2 );
-define ( "SPH_ATTR_ORDINAL", 3 );
-define ( "SPH_ATTR_BOOL", 4 );
-define ( "SPH_ATTR_FLOAT", 5 );
-define ( "SPH_ATTR_BIGINT", 6 );
-define ( "SPH_ATTR_STRING", 7 );
-define ( "SPH_ATTR_MULTI", 0x40000001 );
-define ( "SPH_ATTR_MULTI64", 0x40000002 );
-
-/// known grouping functions
-define ( "SPH_GROUPBY_DAY", 0 );
-define ( "SPH_GROUPBY_WEEK", 1 );
-define ( "SPH_GROUPBY_MONTH", 2 );
-define ( "SPH_GROUPBY_YEAR", 3 );
-define ( "SPH_GROUPBY_ATTR", 4 );
-define ( "SPH_GROUPBY_ATTRPAIR", 5 );
-
-// important properties of PHP's integers:
-// - always signed (one bit short of PHP_INT_SIZE)
-// - conversion from string to int is saturated
-// - float is double
-// - div converts arguments to floats
-// - mod converts arguments to ints
-
-// the packing code below works as follows:
-// - when we got an int, just pack it
-// if performance is a problem, this is the branch users should aim for
-//
-// - otherwise, we got a number in string form
-// this might be due to different reasons, but we assume that this is
-// because it didn't fit into PHP int
-//
-// - factor the string into high and low ints for packing
-// - if we have bcmath, then it is used
-// - if we don't, we have to do it manually (this is the fun part)
-//
-// - x64 branch does factoring using ints
-// - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
-//
-// unpacking routines are pretty much the same.
-// - return ints if we can
-// - otherwise format number into a string
-
-/// pack 64-bit signed
-function sphPackI64 ( $v )
-{
- assert ( is_numeric($v) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- $v = (int)$v;
- return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
- }
-
- // x32, int
- if ( is_int($v) )
- return pack ( "NN", $v < 0 ? -1 : 0, $v );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- {
- if ( bccomp ( $v, 0 ) == -1 )
- $v = bcadd ( "18446744073709551616", $v );
- $h = bcdiv ( $v, "4294967296", 0 );
- $l = bcmod ( $v, "4294967296" );
- return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
- }
-
- // x32, no-bcmath
- $p = max(0, strlen($v) - 13);
- $lo = abs((float)substr($v, $p));
- $hi = abs((float)substr($v, 0, $p));
-
- $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
- $q = floor($m/4294967296.0);
- $l = $m - ($q*4294967296.0);
- $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
-
- if ( $v<0 )
- {
- if ( $l==0 )
- $h = 4294967296.0 - $h;
- else
- {
- $h = 4294967295.0 - $h;
- $l = 4294967296.0 - $l;
- }
- }
- return pack ( "NN", $h, $l );
-}
-
-/// pack 64-bit unsigned
-function sphPackU64 ( $v )
-{
- assert ( is_numeric($v) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- assert ( $v>=0 );
-
- // x64, int
- if ( is_int($v) )
- return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
-
- // x64, bcmath
- if ( function_exists("bcmul") )
- {
- $h = bcdiv ( $v, 4294967296, 0 );
- $l = bcmod ( $v, 4294967296 );
- return pack ( "NN", $h, $l );
- }
-
- // x64, no-bcmath
- $p = max ( 0, strlen($v) - 13 );
- $lo = (int)substr ( $v, $p );
- $hi = (int)substr ( $v, 0, $p );
-
- $m = $lo + $hi*1316134912;
- $l = $m % 4294967296;
- $h = $hi*2328 + (int)($m/4294967296);
-
- return pack ( "NN", $h, $l );
- }
-
- // x32, int
- if ( is_int($v) )
- return pack ( "NN", 0, $v );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- {
- $h = bcdiv ( $v, "4294967296", 0 );
- $l = bcmod ( $v, "4294967296" );
- return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
- }
-
- // x32, no-bcmath
- $p = max(0, strlen($v) - 13);
- $lo = (float)substr($v, $p);
- $hi = (float)substr($v, 0, $p);
-
- $m = $lo + $hi*1316134912.0;
- $q = floor($m / 4294967296.0);
- $l = $m - ($q * 4294967296.0);
- $h = $hi*2328.0 + $q;
-
- return pack ( "NN", $h, $l );
-}
-
-// unpack 64-bit unsigned
-function sphUnpackU64 ( $v )
-{
- list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
-
- if ( PHP_INT_SIZE>=8 )
- {
- if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
- if ( $lo<0 ) $lo += (1<<32);
-
- // x64, int
- if ( $hi<=2147483647 )
- return ($hi<<32) + $lo;
-
- // x64, bcmath
- if ( function_exists("bcmul") )
- return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
-
- // x64, no-bcmath
- $C = 100000;
- $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
- $l = (($hi % $C) << 32) + ($lo % $C);
- if ( $l>$C )
- {
- $h += (int)($l / $C);
- $l = $l % $C;
- }
-
- if ( $h==0 )
- return $l;
- return sprintf ( "%d%05d", $h, $l );
- }
-
- // x32, int
- if ( $hi==0 )
- {
- if ( $lo>0 )
- return $lo;
- return sprintf ( "%u", $lo );
- }
-
- $hi = sprintf ( "%u", $hi );
- $lo = sprintf ( "%u", $lo );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
-
- // x32, no-bcmath
- $hi = (float)$hi;
- $lo = (float)$lo;
-
- $q = floor($hi/10000000.0);
- $r = $hi - $q*10000000.0;
- $m = $lo + $r*4967296.0;
- $mq = floor($m/10000000.0);
- $l = $m - $mq*10000000.0;
- $h = $q*4294967296.0 + $r*429.0 + $mq;
-
- $h = sprintf ( "%.0f", $h );
- $l = sprintf ( "%07.0f", $l );
- if ( $h=="0" )
- return sprintf( "%.0f", (float)$l );
- return $h . $l;
-}
-
-// unpack 64-bit signed
-function sphUnpackI64 ( $v )
-{
- list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
-
- // x64
- if ( PHP_INT_SIZE>=8 )
- {
- if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
- if ( $lo<0 ) $lo += (1<<32);
-
- return ($hi<<32) + $lo;
- }
-
- // x32, int
- if ( $hi==0 )
- {
- if ( $lo>0 )
- return $lo;
- return sprintf ( "%u", $lo );
- }
- // x32, int
- elseif ( $hi==-1 )
- {
- if ( $lo<0 )
- return $lo;
- return sprintf ( "%.0f", $lo - 4294967296.0 );
- }
-
- $neg = "";
- $c = 0;
- if ( $hi<0 )
- {
- $hi = ~$hi;
- $lo = ~$lo;
- $c = 1;
- $neg = "-";
- }
-
- $hi = sprintf ( "%u", $hi );
- $lo = sprintf ( "%u", $lo );
-
- // x32, bcmath
- if ( function_exists("bcmul") )
- return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
-
- // x32, no-bcmath
- $hi = (float)$hi;
- $lo = (float)$lo;
-
- $q = floor($hi/10000000.0);
- $r = $hi - $q*10000000.0;
- $m = $lo + $r*4967296.0;
- $mq = floor($m/10000000.0);
- $l = $m - $mq*10000000.0 + $c;
- $h = $q*4294967296.0 + $r*429.0 + $mq;
- if ( $l==10000000 )
- {
- $l = 0;
- $h += 1;
- }
-
- $h = sprintf ( "%.0f", $h );
- $l = sprintf ( "%07.0f", $l );
- if ( $h=="0" )
- return $neg . sprintf( "%.0f", (float)$l );
- return $neg . $h . $l;
-}
-
-
-function sphFixUint ( $value )
-{
- if ( PHP_INT_SIZE>=8 )
- {
- // x64 route, workaround broken unpack() in 5.2.2+
- if ( $value<0 ) $value += (1<<32);
- return $value;
- }
- else
- {
- // x32 route, workaround php signed/unsigned braindamage
- return sprintf ( "%u", $value );
- }
-}
-
-
-/// sphinx searchd client class
-class SphinxClient
-{
- var $_host; ///< searchd host (default is "localhost")
- var $_port; ///< searchd port (default is 9312)
- var $_offset; ///< how many records to seek from result-set start (default is 0)
- var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
- var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
- var $_weights; ///< per-field weights (default is 1 for all fields)
- var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
- var $_sortby; ///< attribute to sort by (defualt is "")
- var $_min_id; ///< min ID to match (default is 0, which means no limit)
- var $_max_id; ///< max ID to match (default is 0, which means no limit)
- var $_filters; ///< search filters
- var $_groupby; ///< group-by attribute name
- var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
- var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
- var $_groupdistinct;///< group-by count-distinct attribute
- var $_maxmatches; ///< max matches to retrieve
- var $_cutoff; ///< cutoff to stop searching at (default is 0)
- var $_retrycount; ///< distributed retries count
- var $_retrydelay; ///< distributed retries delay
- var $_anchor; ///< geographical anchor point
- var $_indexweights; ///< per-index weights
- var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
- var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
- var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
- var $_fieldweights; ///< per-field-name weights
- var $_overrides; ///< per-query attribute values overrides
- var $_select; ///< select-list (attributes or expressions, with optional aliases)
-
- var $_error; ///< last error message
- var $_warning; ///< last warning message
- var $_connerror; ///< connection error vs remote error flag
-
- var $_reqs; ///< requests array for multi-query
- var $_mbenc; ///< stored mbstring encoding
- var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
- var $_timeout; ///< connect timeout
-
- /////////////////////////////////////////////////////////////////////////////
- // common stuff
- /////////////////////////////////////////////////////////////////////////////
-
- /// create a new client object and fill defaults
- function SphinxClient ()
- {
- // per-client-object settings
- $this->_host = "localhost";
- $this->_port = 9312;
- $this->_path = false;
- $this->_socket = false;
-
- // per-query settings
- $this->_offset = 0;
- $this->_limit = 20;
- $this->_mode = SPH_MATCH_ALL;
- $this->_weights = array ();
- $this->_sort = SPH_SORT_RELEVANCE;
- $this->_sortby = "";
- $this->_min_id = 0;
- $this->_max_id = 0;
- $this->_filters = array ();
- $this->_groupby = "";
- $this->_groupfunc = SPH_GROUPBY_DAY;
- $this->_groupsort = "@group desc";
- $this->_groupdistinct= "";
- $this->_maxmatches = 1000;
- $this->_cutoff = 0;
- $this->_retrycount = 0;
- $this->_retrydelay = 0;
- $this->_anchor = array ();
- $this->_indexweights= array ();
- $this->_ranker = SPH_RANK_PROXIMITY_BM25;
- $this->_rankexpr = "";
- $this->_maxquerytime= 0;
- $this->_fieldweights= array();
- $this->_overrides = array();
- $this->_select = "*";
-
- $this->_error = ""; // per-reply fields (for single-query case)
- $this->_warning = "";
- $this->_connerror = false;
-
- $this->_reqs = array (); // requests storage (for multi-query case)
- $this->_mbenc = "";
- $this->_arrayresult = false;
- $this->_timeout = 0;
- }
-
- function __destruct()
- {
- if ( $this->_socket !== false )
- fclose ( $this->_socket );
- }
-
- /// get last error message (string)
- function GetLastError ()
- {
- return $this->_error;
- }
-
- /// get last warning message (string)
- function GetLastWarning ()
- {
- return $this->_warning;
- }
-
- /// get last error flag (to tell network connection errors from searchd errors or broken responses)
- function IsConnectError()
- {
- return $this->_connerror;
- }
-
- /// set searchd host name (string) and port (integer)
- function SetServer ( $host, $port = 0 )
- {
- assert ( is_string($host) );
- if ( $host[0] == '/')
- {
- $this->_path = 'unix://' . $host;
- return;
- }
- if ( substr ( $host, 0, 7 )=="unix://" )
- {
- $this->_path = $host;
- return;
- }
-
- assert ( is_int($port) );
- $this->_host = $host;
- $this->_port = $port;
- $this->_path = '';
-
- }
-
- /// set server connection timeout (0 to remove)
- function SetConnectTimeout ( $timeout )
- {
- assert ( is_numeric($timeout) );
- $this->_timeout = $timeout;
- }
-
-
- function _Send ( $handle, $data, $length )
- {
- if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
- {
- $this->_error = 'connection unexpectedly closed (timed out?)';
- $this->_connerror = true;
- return false;
- }
- return true;
- }
-
- /////////////////////////////////////////////////////////////////////////////
-
- /// enter mbstring workaround mode
- function _MBPush ()
- {
- $this->_mbenc = "";
- if ( ini_get ( "mbstring.func_overload" ) & 2 )
- {
- $this->_mbenc = mb_internal_encoding();
- mb_internal_encoding ( "latin1" );
- }
- }
-
- /// leave mbstring workaround mode
- function _MBPop ()
- {
- if ( $this->_mbenc )
- mb_internal_encoding ( $this->_mbenc );
- }
-
- /// connect to searchd server
- function _Connect ()
- {
- if ( $this->_socket!==false )
- {
- // we are in persistent connection mode, so we have a socket
- // however, need to check whether it's still alive
- if ( !@feof ( $this->_socket ) )
- return $this->_socket;
-
- // force reopen
- $this->_socket = false;
- }
-
- $errno = 0;
- $errstr = "";
- $this->_connerror = false;
-
- if ( $this->_path )
- {
- $host = $this->_path;
- $port = 0;
- }
- else
- {
- $host = $this->_host;
- $port = $this->_port;
- }
-
- if ( $this->_timeout<=0 )
- $fp = @fsockopen ( $host, $port, $errno, $errstr );
- else
- $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
-
- if ( !$fp )
- {
- if ( $this->_path )
- $location = $this->_path;
- else
- $location = "{$this->_host}:{$this->_port}";
-
- $errstr = trim ( $errstr );
- $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
- $this->_connerror = true;
- return false;
- }
-
- // send my version
- // this is a subtle part. we must do it before (!) reading back from searchd.
- // because otherwise under some conditions (reported on FreeBSD for instance)
- // TCP stack could throttle write-write-read pattern because of Nagle.
- if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
- {
- fclose ( $fp );
- $this->_error = "failed to send client protocol version";
- return false;
- }
-
- // check version
- list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
- $v = (int)$v;
- if ( $v<1 )
- {
- fclose ( $fp );
- $this->_error = "expected searchd protocol version 1+, got version '$v'";
- return false;
- }
-
- return $fp;
- }
-
- /// get and check response packet from searchd server
- function _GetResponse ( $fp, $client_ver )
- {
- $response = "";
- $len = 0;
-
- $header = fread ( $fp, 8 );
- if ( strlen($header)==8 )
- {
- list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
- $left = $len;
- while ( $left>0 && !feof($fp) )
- {
- $chunk = fread ( $fp, min ( 8192, $left ) );
- if ( $chunk )
- {
- $response .= $chunk;
- $left -= strlen($chunk);
- }
- }
- }
- if ( $this->_socket === false )
- fclose ( $fp );
-
- // check response
- $read = strlen ( $response );
- if ( !$response || $read!=$len )
- {
- $this->_error = $len
- ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
- : "received zero-sized searchd response";
- return false;
- }
-
- // check status
- if ( $status==SEARCHD_WARNING )
- {
- list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
- $this->_warning = substr ( $response, 4, $wlen );
- return substr ( $response, 4+$wlen );
- }
- if ( $status==SEARCHD_ERROR )
- {
- $this->_error = "searchd error: " . substr ( $response, 4 );
- return false;
- }
- if ( $status==SEARCHD_RETRY )
- {
- $this->_error = "temporary searchd error: " . substr ( $response, 4 );
- return false;
- }
- if ( $status!=SEARCHD_OK )
- {
- $this->_error = "unknown status code '$status'";
- return false;
- }
-
- // check version
- if ( $ver<$client_ver )
- {
- $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
- $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
- }
-
- return $response;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // searching
- /////////////////////////////////////////////////////////////////////////////
-
- /// set offset and count into result set,
- /// and optionally set max-matches and cutoff limits
- function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
- {
- assert ( is_int($offset) );
- assert ( is_int($limit) );
- assert ( $offset>=0 );
- assert ( $limit>0 );
- assert ( $max>=0 );
- $this->_offset = $offset;
- $this->_limit = $limit;
- if ( $max>0 )
- $this->_maxmatches = $max;
- if ( $cutoff>0 )
- $this->_cutoff = $cutoff;
- }
-
- /// set maximum query time, in milliseconds, per-index
- /// integer, 0 means "do not limit"
- function SetMaxQueryTime ( $max )
- {
- assert ( is_int($max) );
- assert ( $max>=0 );
- $this->_maxquerytime = $max;
- }
-
- /// set matching mode
- function SetMatchMode ( $mode )
- {
- assert ( $mode==SPH_MATCH_ALL
- || $mode==SPH_MATCH_ANY
- || $mode==SPH_MATCH_PHRASE
- || $mode==SPH_MATCH_BOOLEAN
- || $mode==SPH_MATCH_EXTENDED
- || $mode==SPH_MATCH_FULLSCAN
- || $mode==SPH_MATCH_EXTENDED2 );
- $this->_mode = $mode;
- }
-
- /// set ranking mode
- function SetRankingMode ( $ranker, $rankexpr="" )
- {
- assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
- assert ( is_string($rankexpr) );
- $this->_ranker = $ranker;
- $this->_rankexpr = $rankexpr;
- }
-
- /// set matches sorting mode
- function SetSortMode ( $mode, $sortby="" )
- {
- assert (
- $mode==SPH_SORT_RELEVANCE ||
- $mode==SPH_SORT_ATTR_DESC ||
- $mode==SPH_SORT_ATTR_ASC ||
- $mode==SPH_SORT_TIME_SEGMENTS ||
- $mode==SPH_SORT_EXTENDED ||
- $mode==SPH_SORT_EXPR );
- assert ( is_string($sortby) );
- assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
-
- $this->_sort = $mode;
- $this->_sortby = $sortby;
- }
-
- /// bind per-field weights by order
- /// DEPRECATED; use SetFieldWeights() instead
- function SetWeights ( $weights )
- {
- assert ( is_array($weights) );
- foreach ( $weights as $weight )
- assert ( is_int($weight) );
-
- $this->_weights = $weights;
- }
-
- /// bind per-field weights by name
- function SetFieldWeights ( $weights )
- {
- assert ( is_array($weights) );
- foreach ( $weights as $name=>$weight )
- {
- assert ( is_string($name) );
- assert ( is_int($weight) );
- }
- $this->_fieldweights = $weights;
- }
-
- /// bind per-index weights by name
- function SetIndexWeights ( $weights )
- {
- assert ( is_array($weights) );
- foreach ( $weights as $index=>$weight )
- {
- assert ( is_string($index) );
- assert ( is_int($weight) );
- }
- $this->_indexweights = $weights;
- }
-
- /// set IDs range to match
- /// only match records if document ID is beetwen $min and $max (inclusive)
- function SetIDRange ( $min, $max )
- {
- assert ( is_numeric($min) );
- assert ( is_numeric($max) );
- assert ( $min<=$max );
- $this->_min_id = $min;
- $this->_max_id = $max;
- }
-
- /// set values set filter
- /// only match records where $attribute value is in given set
- function SetFilter ( $attribute, $values, $exclude=false )
- {
- assert ( is_string($attribute) );
- assert ( is_array($values) );
- assert ( count($values) );
-
- if ( is_array($values) && count($values) )
- {
- foreach ( $values as $value )
- assert ( is_numeric($value) );
-
- $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
- }
- }
-
- /// set range filter
- /// only match records if $attribute value is beetwen $min and $max (inclusive)
- function SetFilterRange ( $attribute, $min, $max, $exclude=false )
- {
- assert ( is_string($attribute) );
- assert ( is_numeric($min) );
- assert ( is_numeric($max) );
- assert ( $min<=$max );
-
- $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
- }
-
- /// set float range filter
- /// only match records if $attribute value is beetwen $min and $max (inclusive)
- function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
- {
- assert ( is_string($attribute) );
- assert ( is_float($min) );
- assert ( is_float($max) );
- assert ( $min<=$max );
-
- $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
- }
-
- /// setup anchor point for geosphere distance calculations
- /// required to use @geodist in filters and sorting
- /// latitude and longitude must be in radians
- function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
- {
- assert ( is_string($attrlat) );
- assert ( is_string($attrlong) );
- assert ( is_float($lat) );
- assert ( is_float($long) );
-
- $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
- }
-
- /// set grouping attribute and function
- function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
- {
- assert ( is_string($attribute) );
- assert ( is_string($groupsort) );
- assert ( $func==SPH_GROUPBY_DAY
- || $func==SPH_GROUPBY_WEEK
- || $func==SPH_GROUPBY_MONTH
- || $func==SPH_GROUPBY_YEAR
- || $func==SPH_GROUPBY_ATTR
- || $func==SPH_GROUPBY_ATTRPAIR );
-
- $this->_groupby = $attribute;
- $this->_groupfunc = $func;
- $this->_groupsort = $groupsort;
- }
-
- /// set count-distinct attribute for group-by queries
- function SetGroupDistinct ( $attribute )
- {
- assert ( is_string($attribute) );
- $this->_groupdistinct = $attribute;
- }
-
- /// set distributed retries count and delay
- function SetRetries ( $count, $delay=0 )
- {
- assert ( is_int($count) && $count>=0 );
- assert ( is_int($delay) && $delay>=0 );
- $this->_retrycount = $count;
- $this->_retrydelay = $delay;
- }
-
- /// set result set format (hash or array; hash by default)
- /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
- function SetArrayResult ( $arrayresult )
- {
- assert ( is_bool($arrayresult) );
- $this->_arrayresult = $arrayresult;
- }
-
- /// set attribute values override
- /// there can be only one override per attribute
- /// $values must be a hash that maps document IDs to attribute values
- function SetOverride ( $attrname, $attrtype, $values )
- {
- assert ( is_string ( $attrname ) );
- assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
- assert ( is_array ( $values ) );
-
- $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
- }
-
- /// set select-list (attributes or expressions), SQL-like syntax
- function SetSelect ( $select )
- {
- assert ( is_string ( $select ) );
- $this->_select = $select;
- }
-
- //////////////////////////////////////////////////////////////////////////////
-
- /// clear all filters (for multi-queries)
- function ResetFilters ()
- {
- $this->_filters = array();
- $this->_anchor = array();
- }
-
- /// clear groupby settings (for multi-queries)
- function ResetGroupBy ()
- {
- $this->_groupby = "";
- $this->_groupfunc = SPH_GROUPBY_DAY;
- $this->_groupsort = "@group desc";
- $this->_groupdistinct= "";
- }
-
- /// clear all attribute value overrides (for multi-queries)
- function ResetOverrides ()
- {
- $this->_overrides = array ();
- }
-
- //////////////////////////////////////////////////////////////////////////////
-
- /// connect to searchd server, run given search query through given indexes,
- /// and return the search results
- function Query ( $query, $index="*", $comment="" )
- {
- assert ( empty($this->_reqs) );
-
- $this->AddQuery ( $query, $index, $comment );
- $results = $this->RunQueries ();
- $this->_reqs = array (); // just in case it failed too early
-
- if ( !is_array($results) )
- return false; // probably network error; error message should be already filled
-
- $this->_error = $results[0]["error"];
- $this->_warning = $results[0]["warning"];
- if ( $results[0]["status"]==SEARCHD_ERROR )
- return false;
- else
- return $results[0];
- }
-
- /// helper to pack floats in network byte order
- function _PackFloat ( $f )
- {
- $t1 = pack ( "f", $f ); // machine order
- list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
- return pack ( "N", $t2 );
- }
-
- /// add query to multi-query batch
- /// returns index into results array from RunQueries() call
- function AddQuery ( $query, $index="*", $comment="" )
- {
- // mbstring workaround
- $this->_MBPush ();
-
- // build request
- $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
- if ( $this->_ranker==SPH_RANK_EXPR )
- $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
- $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
- $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
- $req .= pack ( "N", strlen($query) ) . $query; // query itself
- $req .= pack ( "N", count($this->_weights) ); // weights
- foreach ( $this->_weights as $weight )
- $req .= pack ( "N", (int)$weight );
- $req .= pack ( "N", strlen($index) ) . $index; // indexes
- $req .= pack ( "N", 1 ); // id64 range marker
- $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
-
- // filters
- $req .= pack ( "N", count($this->_filters) );
- foreach ( $this->_filters as $filter )
- {
- $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
- $req .= pack ( "N", $filter["type"] );
- switch ( $filter["type"] )
- {
- case SPH_FILTER_VALUES:
- $req .= pack ( "N", count($filter["values"]) );
- foreach ( $filter["values"] as $value )
- $req .= sphPackI64 ( $value );
- break;
-
- case SPH_FILTER_RANGE:
- $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
- break;
-
- case SPH_FILTER_FLOATRANGE:
- $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
- break;
-
- default:
- assert ( 0 && "internal error: unhandled filter type" );
- }
- $req .= pack ( "N", $filter["exclude"] );
- }
-
- // group-by clause, max-matches count, group-sort clause, cutoff count
- $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
- $req .= pack ( "N", $this->_maxmatches );
- $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
- $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
- $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
-
- // anchor point
- if ( empty($this->_anchor) )
- {
- $req .= pack ( "N", 0 );
- } else
- {
- $a =& $this->_anchor;
- $req .= pack ( "N", 1 );
- $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
- $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
- $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
- }
-
- // per-index weights
- $req .= pack ( "N", count($this->_indexweights) );
- foreach ( $this->_indexweights as $idx=>$weight )
- $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
-
- // max query time
- $req .= pack ( "N", $this->_maxquerytime );
-
- // per-field weights
- $req .= pack ( "N", count($this->_fieldweights) );
- foreach ( $this->_fieldweights as $field=>$weight )
- $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
-
- // comment
- $req .= pack ( "N", strlen($comment) ) . $comment;
-
- // attribute overrides
- $req .= pack ( "N", count($this->_overrides) );
- foreach ( $this->_overrides as $key => $entry )
- {
- $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
- $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
- foreach ( $entry["values"] as $id=>$val )
- {
- assert ( is_numeric($id) );
- assert ( is_numeric($val) );
-
- $req .= sphPackU64 ( $id );
- switch ( $entry["type"] )
- {
- case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break;
- case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break;
- default: $req .= pack ( "N", $val ); break;
- }
- }
- }
-
- // select-list
- $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
-
- // mbstring workaround
- $this->_MBPop ();
-
- // store request to requests array
- $this->_reqs[] = $req;
- return count($this->_reqs)-1;
- }
-
- /// connect to searchd, run queries batch, and return an array of result sets
- function RunQueries ()
- {
- if ( empty($this->_reqs) )
- {
- $this->_error = "no queries defined, issue AddQuery() first";
- return false;
- }
-
- // mbstring workaround
- $this->_MBPush ();
-
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop ();
- return false;
- }
-
- // send query, get response
- $nreqs = count($this->_reqs);
- $req = join ( "", $this->_reqs );
- $len = 8+strlen($req);
- $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
-
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- // query sent ok; we can reset reqs now
- $this->_reqs = array ();
-
- // parse and return response
- return $this->_ParseSearchResponse ( $response, $nreqs );
- }
-
- /// parse and return search query (or queries) response
- function _ParseSearchResponse ( $response, $nreqs )
- {
- $p = 0; // current position
- $max = strlen($response); // max position for checks, to protect against broken responses
-
- $results = array ();
- for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
- {
- $results[] = array();
- $result =& $results[$ires];
-
- $result["error"] = "";
- $result["warning"] = "";
-
- // extract status
- list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $result["status"] = $status;
- if ( $status!=SEARCHD_OK )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $message = substr ( $response, $p, $len ); $p += $len;
-
- if ( $status==SEARCHD_WARNING )
- {
- $result["warning"] = $message;
- } else
- {
- $result["error"] = $message;
- continue;
- }
- }
-
- // read schema
- $fields = array ();
- $attrs = array ();
-
- list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- while ( $nfields-->0 && $p<$max )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $fields[] = substr ( $response, $p, $len ); $p += $len;
- }
- $result["fields"] = $fields;
-
- list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- while ( $nattrs-->0 && $p<$max )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $attr = substr ( $response, $p, $len ); $p += $len;
- list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $attrs[$attr] = $type;
- }
- $result["attrs"] = $attrs;
-
- // read match count
- list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
-
- // read matches
- $idx = -1;
- while ( $count-->0 && $p<$max )
- {
- // index into result array
- $idx++;
-
- // parse document id and weight
- if ( $id64 )
- {
- $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
- list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- }
- else
- {
- list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
- substr ( $response, $p, 8 ) ) );
- $p += 8;
- $doc = sphFixUint($doc);
- }
- $weight = sprintf ( "%u", $weight );
-
- // create match entry
- if ( $this->_arrayresult )
- $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
- else
- $result["matches"][$doc]["weight"] = $weight;
-
- // parse and create attributes
- $attrvals = array ();
- foreach ( $attrs as $attr=>$type )
- {
- // handle 64bit ints
- if ( $type==SPH_ATTR_BIGINT )
- {
- $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
- continue;
- }
-
- // handle floats
- if ( $type==SPH_ATTR_FLOAT )
- {
- list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
- $attrvals[$attr] = $fval;
- continue;
- }
-
- // handle everything else as unsigned ints
- list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- if ( $type==SPH_ATTR_MULTI )
- {
- $attrvals[$attr] = array ();
- $nvalues = $val;
- while ( $nvalues-->0 && $p<$max )
- {
- list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $attrvals[$attr][] = sphFixUint($val);
- }
- } else if ( $type==SPH_ATTR_MULTI64 )
- {
- $attrvals[$attr] = array ();
- $nvalues = $val;
- while ( $nvalues>0 && $p<$max )
- {
- $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
- $nvalues -= 2;
- }
- } else if ( $type==SPH_ATTR_STRING )
- {
- $attrvals[$attr] = substr ( $response, $p, $val );
- $p += $val;
- } else
- {
- $attrvals[$attr] = sphFixUint($val);
- }
- }
-
- if ( $this->_arrayresult )
- $result["matches"][$idx]["attrs"] = $attrvals;
- else
- $result["matches"][$doc]["attrs"] = $attrvals;
- }
-
- list ( $total, $total_found, $msecs, $words ) =
- array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
- $result["total"] = sprintf ( "%u", $total );
- $result["total_found"] = sprintf ( "%u", $total_found );
- $result["time"] = sprintf ( "%.3f", $msecs/1000 );
- $p += 16;
-
- while ( $words-->0 && $p<$max )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $word = substr ( $response, $p, $len ); $p += $len;
- list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
- $result["words"][$word] = array (
- "docs"=>sprintf ( "%u", $docs ),
- "hits"=>sprintf ( "%u", $hits ) );
- }
- }
-
- $this->_MBPop ();
- return $results;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // excerpts generation
- /////////////////////////////////////////////////////////////////////////////
-
- /// connect to searchd server, and generate exceprts (snippets)
- /// of given documents for given query. returns false on failure,
- /// an array of snippets on success
- function BuildExcerpts ( $docs, $index, $words, $opts=array() )
- {
- assert ( is_array($docs) );
- assert ( is_string($index) );
- assert ( is_string($words) );
- assert ( is_array($opts) );
-
- $this->_MBPush ();
-
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return false;
- }
-
- /////////////////
- // fixup options
- /////////////////
-
- if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>";
- if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
- if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
- if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
- if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0;
- if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0;
- if ( !isset($opts["around"]) ) $opts["around"] = 5;
- if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false;
- if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false;
- if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false;
- if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false;
- if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false;
- if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false;
- if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1;
- if ( !isset($opts["load_files"]) ) $opts["load_files"] = false;
- if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index";
- if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false;
- if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none";
- if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false;
- if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false;
-
-
- /////////////////
- // build request
- /////////////////
-
- // v.1.2 req
- $flags = 1; // remove spaces
- if ( $opts["exact_phrase"] ) $flags |= 2;
- if ( $opts["single_passage"] ) $flags |= 4;
- if ( $opts["use_boundaries"] ) $flags |= 8;
- if ( $opts["weight_order"] ) $flags |= 16;
- if ( $opts["query_mode"] ) $flags |= 32;
- if ( $opts["force_all_words"] ) $flags |= 64;
- if ( $opts["load_files"] ) $flags |= 128;
- if ( $opts["allow_empty"] ) $flags |= 256;
- if ( $opts["emit_zones"] ) $flags |= 512;
- if ( $opts["load_files_scattered"] ) $flags |= 1024;
- $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
- $req .= pack ( "N", strlen($index) ) . $index; // req index
- $req .= pack ( "N", strlen($words) ) . $words; // req words
-
- // options
- $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
- $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
- $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
- $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] );
- $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
- $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
- $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
-
- // documents
- $req .= pack ( "N", count($docs) );
- foreach ( $docs as $doc )
- {
- assert ( is_string($doc) );
- $req .= pack ( "N", strlen($doc) ) . $doc;
- }
-
- ////////////////////////////
- // send query, get response
- ////////////////////////////
-
- $len = strlen($req);
- $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- //////////////////
- // parse response
- //////////////////
-
- $pos = 0;
- $res = array ();
- $rlen = strlen($response);
- for ( $i=0; $i<count($docs); $i++ )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
- $pos += 4;
-
- if ( $pos+$len > $rlen )
- {
- $this->_error = "incomplete reply";
- $this->_MBPop ();
- return false;
- }
- $res[] = $len ? substr ( $response, $pos, $len ) : "";
- $pos += $len;
- }
-
- $this->_MBPop ();
- return $res;
- }
-
-
- /////////////////////////////////////////////////////////////////////////////
- // keyword generation
- /////////////////////////////////////////////////////////////////////////////
-
- /// connect to searchd server, and generate keyword list for a given query
- /// returns false on failure,
- /// an array of words on success
- function BuildKeywords ( $query, $index, $hits )
- {
- assert ( is_string($query) );
- assert ( is_string($index) );
- assert ( is_bool($hits) );
-
- $this->_MBPush ();
-
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return false;
- }
-
- /////////////////
- // build request
- /////////////////
-
- // v.1.0 req
- $req = pack ( "N", strlen($query) ) . $query; // req query
- $req .= pack ( "N", strlen($index) ) . $index; // req index
- $req .= pack ( "N", (int)$hits );
-
- ////////////////////////////
- // send query, get response
- ////////////////////////////
-
- $len = strlen($req);
- $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
- if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- //////////////////
- // parse response
- //////////////////
-
- $pos = 0;
- $res = array ();
- $rlen = strlen($response);
- list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
- $pos += 4;
- for ( $i=0; $i<$nwords; $i++ )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
- $tokenized = $len ? substr ( $response, $pos, $len ) : "";
- $pos += $len;
-
- list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
- $normalized = $len ? substr ( $response, $pos, $len ) : "";
- $pos += $len;
-
- $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
-
- if ( $hits )
- {
- list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
- $pos += 8;
- $res [$i]["docs"] = $ndocs;
- $res [$i]["hits"] = $nhits;
- }
-
- if ( $pos > $rlen )
- {
- $this->_error = "incomplete reply";
- $this->_MBPop ();
- return false;
- }
- }
-
- $this->_MBPop ();
- return $res;
- }
-
- function EscapeString ( $string )
- {
- $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
- $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
-
- return str_replace ( $from, $to, $string );
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // attribute updates
- /////////////////////////////////////////////////////////////////////////////
-
- /// batch update given attributes in given rows in given indexes
- /// returns amount of updated documents (0 or more) on success, or -1 on failure
- function UpdateAttributes ( $index, $attrs, $values, $mva=false )
- {
- // verify everything
- assert ( is_string($index) );
- assert ( is_bool($mva) );
-
- assert ( is_array($attrs) );
- foreach ( $attrs as $attr )
- assert ( is_string($attr) );
-
- assert ( is_array($values) );
- foreach ( $values as $id=>$entry )
- {
- assert ( is_numeric($id) );
- assert ( is_array($entry) );
- assert ( count($entry)==count($attrs) );
- foreach ( $entry as $v )
- {
- if ( $mva )
- {
- assert ( is_array($v) );
- foreach ( $v as $vv )
- assert ( is_int($vv) );
- } else
- assert ( is_int($v) );
- }
- }
-
- // build request
- $this->_MBPush ();
- $req = pack ( "N", strlen($index) ) . $index;
-
- $req .= pack ( "N", count($attrs) );
- foreach ( $attrs as $attr )
- {
- $req .= pack ( "N", strlen($attr) ) . $attr;
- $req .= pack ( "N", $mva ? 1 : 0 );
- }
-
- $req .= pack ( "N", count($values) );
- foreach ( $values as $id=>$entry )
- {
- $req .= sphPackU64 ( $id );
- foreach ( $entry as $v )
- {
- $req .= pack ( "N", $mva ? count($v) : $v );
- if ( $mva )
- foreach ( $v as $vv )
- $req .= pack ( "N", $vv );
- }
- }
-
- // connect, send query, get response
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop ();
- return -1;
- }
-
- $len = strlen($req);
- $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
- if ( !$this->_Send ( $fp, $req, $len+8 ) )
- {
- $this->_MBPop ();
- return -1;
- }
-
- if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
- {
- $this->_MBPop ();
- return -1;
- }
-
- // parse response
- list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
- $this->_MBPop ();
- return $updated;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // persistent connections
- /////////////////////////////////////////////////////////////////////////////
-
- function Open()
- {
- if ( $this->_socket !== false )
- {
- $this->_error = 'already connected';
- return false;
- }
- if ( !$fp = $this->_Connect() )
- return false;
-
- // command, command version = 0, body length = 4, body = 1
- $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
- if ( !$this->_Send ( $fp, $req, 12 ) )
- return false;
-
- $this->_socket = $fp;
- return true;
- }
-
- function Close()
- {
- if ( $this->_socket === false )
- {
- $this->_error = 'not connected';
- return false;
- }
-
- fclose ( $this->_socket );
- $this->_socket = false;
-
- return true;
- }
-
- //////////////////////////////////////////////////////////////////////////
- // status
- //////////////////////////////////////////////////////////////////////////
-
- function Status ()
- {
- $this->_MBPush ();
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return false;
- }
-
- $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
- if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
- {
- $this->_MBPop ();
- return false;
- }
-
- $res = substr ( $response, 4 ); // just ignore length, error handling, etc
- $p = 0;
- list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
-
- $res = array();
- for ( $i=0; $i<$rows; $i++ )
- for ( $j=0; $j<$cols; $j++ )
- {
- list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
- $res[$i][] = substr ( $response, $p, $len ); $p += $len;
- }
-
- $this->_MBPop ();
- return $res;
- }
-
- //////////////////////////////////////////////////////////////////////////
- // flush
- //////////////////////////////////////////////////////////////////////////
-
- function FlushAttributes ()
- {
- $this->_MBPush ();
- if (!( $fp = $this->_Connect() ))
- {
- $this->_MBPop();
- return -1;
- }
-
- $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
- if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
- !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
- {
- $this->_MBPop ();
- return -1;
- }
-
- $tag = -1;
- if ( strlen($response)==4 )
- list(,$tag) = unpack ( "N*", $response );
- else
- $this->_error = "unexpected response length";
-
- $this->_MBPop ();
- return $tag;
- }
-}
-
-//
-// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
-//
+<?php + +// +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $ +// + +// +// Copyright (c) 2001-2012, Andrew Aksyonoff +// Copyright (c) 2008-2012, Sphinx Technologies Inc +// All rights reserved +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License. You should have +// received a copy of the GPL license along with this program; if you +// did not, you can find it at http://www.gnu.org/ +// + +///////////////////////////////////////////////////////////////////////////// +// PHP version of Sphinx searchd client (PHP API) +///////////////////////////////////////////////////////////////////////////// + +/// known searchd commands +define ( "SEARCHD_COMMAND_SEARCH", 0 ); +define ( "SEARCHD_COMMAND_EXCERPT", 1 ); +define ( "SEARCHD_COMMAND_UPDATE", 2 ); +define ( "SEARCHD_COMMAND_KEYWORDS", 3 ); +define ( "SEARCHD_COMMAND_PERSIST", 4 ); +define ( "SEARCHD_COMMAND_STATUS", 5 ); +define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 ); + +/// current client-side command implementation versions +define ( "VER_COMMAND_SEARCH", 0x119 ); +define ( "VER_COMMAND_EXCERPT", 0x104 ); +define ( "VER_COMMAND_UPDATE", 0x102 ); +define ( "VER_COMMAND_KEYWORDS", 0x100 ); +define ( "VER_COMMAND_STATUS", 0x100 ); +define ( "VER_COMMAND_QUERY", 0x100 ); +define ( "VER_COMMAND_FLUSHATTRS", 0x100 ); + +/// known searchd status codes +define ( "SEARCHD_OK", 0 ); +define ( "SEARCHD_ERROR", 1 ); +define ( "SEARCHD_RETRY", 2 ); +define ( "SEARCHD_WARNING", 3 ); + +/// known match modes +define ( "SPH_MATCH_ALL", 0 ); +define ( "SPH_MATCH_ANY", 1 ); +define ( "SPH_MATCH_PHRASE", 2 ); +define ( "SPH_MATCH_BOOLEAN", 3 ); +define ( "SPH_MATCH_EXTENDED", 4 ); +define ( "SPH_MATCH_FULLSCAN", 5 ); +define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED) + +/// known ranking modes (ext2 only) +define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one +define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality) +define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1 +define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts +define ( "SPH_RANK_PROXIMITY", 4 ); +define ( "SPH_RANK_MATCHANY", 5 ); +define ( "SPH_RANK_FIELDMASK", 6 ); +define ( "SPH_RANK_SPH04", 7 ); +define ( "SPH_RANK_EXPR", 8 ); +define ( "SPH_RANK_TOTAL", 9 ); + +/// known sort modes +define ( "SPH_SORT_RELEVANCE", 0 ); +define ( "SPH_SORT_ATTR_DESC", 1 ); +define ( "SPH_SORT_ATTR_ASC", 2 ); +define ( "SPH_SORT_TIME_SEGMENTS", 3 ); +define ( "SPH_SORT_EXTENDED", 4 ); +define ( "SPH_SORT_EXPR", 5 ); + +/// known filter types +define ( "SPH_FILTER_VALUES", 0 ); +define ( "SPH_FILTER_RANGE", 1 ); +define ( "SPH_FILTER_FLOATRANGE", 2 ); + +/// known attribute types +define ( "SPH_ATTR_INTEGER", 1 ); +define ( "SPH_ATTR_TIMESTAMP", 2 ); +define ( "SPH_ATTR_ORDINAL", 3 ); +define ( "SPH_ATTR_BOOL", 4 ); +define ( "SPH_ATTR_FLOAT", 5 ); +define ( "SPH_ATTR_BIGINT", 6 ); +define ( "SPH_ATTR_STRING", 7 ); +define ( "SPH_ATTR_MULTI", 0x40000001 ); +define ( "SPH_ATTR_MULTI64", 0x40000002 ); + +/// known grouping functions +define ( "SPH_GROUPBY_DAY", 0 ); +define ( "SPH_GROUPBY_WEEK", 1 ); +define ( "SPH_GROUPBY_MONTH", 2 ); +define ( "SPH_GROUPBY_YEAR", 3 ); +define ( "SPH_GROUPBY_ATTR", 4 ); +define ( "SPH_GROUPBY_ATTRPAIR", 5 ); + +// important properties of PHP's integers: +// - always signed (one bit short of PHP_INT_SIZE) +// - conversion from string to int is saturated +// - float is double +// - div converts arguments to floats +// - mod converts arguments to ints + +// the packing code below works as follows: +// - when we got an int, just pack it +// if performance is a problem, this is the branch users should aim for +// +// - otherwise, we got a number in string form +// this might be due to different reasons, but we assume that this is +// because it didn't fit into PHP int +// +// - factor the string into high and low ints for packing +// - if we have bcmath, then it is used +// - if we don't, we have to do it manually (this is the fun part) +// +// - x64 branch does factoring using ints +// - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int +// +// unpacking routines are pretty much the same. +// - return ints if we can +// - otherwise format number into a string + +/// pack 64-bit signed +function sphPackI64 ( $v ) +{ + assert ( is_numeric($v) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + $v = (int)$v; + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", $v < 0 ? -1 : 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + if ( bccomp ( $v, 0 ) == -1 ) + $v = bcadd ( "18446744073709551616", $v ); + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = abs((float)substr($v, $p)); + $hi = abs((float)substr($v, 0, $p)); + + $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 + $q = floor($m/4294967296.0); + $l = $m - ($q*4294967296.0); + $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 + + if ( $v<0 ) + { + if ( $l==0 ) + $h = 4294967296.0 - $h; + else + { + $h = 4294967295.0 - $h; + $l = 4294967296.0 - $l; + } + } + return pack ( "NN", $h, $l ); +} + +/// pack 64-bit unsigned +function sphPackU64 ( $v ) +{ + assert ( is_numeric($v) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + assert ( $v>=0 ); + + // x64, int + if ( is_int($v) ) + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + + // x64, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, 4294967296, 0 ); + $l = bcmod ( $v, 4294967296 ); + return pack ( "NN", $h, $l ); + } + + // x64, no-bcmath + $p = max ( 0, strlen($v) - 13 ); + $lo = (int)substr ( $v, $p ); + $hi = (int)substr ( $v, 0, $p ); + + $m = $lo + $hi*1316134912; + $l = $m % 4294967296; + $h = $hi*2328 + (int)($m/4294967296); + + return pack ( "NN", $h, $l ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = (float)substr($v, $p); + $hi = (float)substr($v, 0, $p); + + $m = $lo + $hi*1316134912.0; + $q = floor($m / 4294967296.0); + $l = $m - ($q * 4294967296.0); + $h = $hi*2328.0 + $q; + + return pack ( "NN", $h, $l ); +} + +// unpack 64-bit unsigned +function sphUnpackU64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + // x64, int + if ( $hi<=2147483647 ) + return ($hi<<32) + $lo; + + // x64, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x64, no-bcmath + $C = 100000; + $h = ((int)($hi / $C) << 32) + (int)($lo / $C); + $l = (($hi % $C) << 32) + ($lo % $C); + if ( $l>$C ) + { + $h += (int)($l / $C); + $l = $l % $C; + } + + if ( $h==0 ) + return $l; + return sprintf ( "%d%05d", $h, $l ); + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0; + $h = $q*4294967296.0 + $r*429.0 + $mq; + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return sprintf( "%.0f", (float)$l ); + return $h . $l; +} + +// unpack 64-bit signed +function sphUnpackI64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + return ($hi<<32) + $lo; + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + // x32, int + elseif ( $hi==-1 ) + { + if ( $lo<0 ) + return $lo; + return sprintf ( "%.0f", $lo - 4294967296.0 ); + } + + $neg = ""; + $c = 0; + if ( $hi<0 ) + { + $hi = ~$hi; + $lo = ~$lo; + $c = 1; + $neg = "-"; + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0 + $c; + $h = $q*4294967296.0 + $r*429.0 + $mq; + if ( $l==10000000 ) + { + $l = 0; + $h += 1; + } + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return $neg . sprintf( "%.0f", (float)$l ); + return $neg . $h . $l; +} + + +function sphFixUint ( $value ) +{ + if ( PHP_INT_SIZE>=8 ) + { + // x64 route, workaround broken unpack() in 5.2.2+ + if ( $value<0 ) $value += (1<<32); + return $value; + } + else + { + // x32 route, workaround php signed/unsigned braindamage + return sprintf ( "%u", $value ); + } +} + + +/// sphinx searchd client class +class SphinxClient +{ + var $_host; ///< searchd host (default is "localhost") + var $_port; ///< searchd port (default is 9312) + var $_offset; ///< how many records to seek from result-set start (default is 0) + var $_limit; ///< how many records to return from result-set starting at offset (default is 20) + var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) + var $_weights; ///< per-field weights (default is 1 for all fields) + var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) + var $_sortby; ///< attribute to sort by (defualt is "") + var $_min_id; ///< min ID to match (default is 0, which means no limit) + var $_max_id; ///< max ID to match (default is 0, which means no limit) + var $_filters; ///< search filters + var $_groupby; ///< group-by attribute name + var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) + var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) + var $_groupdistinct;///< group-by count-distinct attribute + var $_maxmatches; ///< max matches to retrieve + var $_cutoff; ///< cutoff to stop searching at (default is 0) + var $_retrycount; ///< distributed retries count + var $_retrydelay; ///< distributed retries delay + var $_anchor; ///< geographical anchor point + var $_indexweights; ///< per-index weights + var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) + var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR) + var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) + var $_fieldweights; ///< per-field-name weights + var $_overrides; ///< per-query attribute values overrides + var $_select; ///< select-list (attributes or expressions, with optional aliases) + + var $_error; ///< last error message + var $_warning; ///< last warning message + var $_connerror; ///< connection error vs remote error flag + + var $_reqs; ///< requests array for multi-query + var $_mbenc; ///< stored mbstring encoding + var $_arrayresult; ///< whether $result["matches"] should be a hash or an array + var $_timeout; ///< connect timeout + + ///////////////////////////////////////////////////////////////////////////// + // common stuff + ///////////////////////////////////////////////////////////////////////////// + + /// create a new client object and fill defaults + function SphinxClient () + { + // per-client-object settings + $this->_host = "localhost"; + $this->_port = 9312; + $this->_path = false; + $this->_socket = false; + + // per-query settings + $this->_offset = 0; + $this->_limit = 20; + $this->_mode = SPH_MATCH_ALL; + $this->_weights = array (); + $this->_sort = SPH_SORT_RELEVANCE; + $this->_sortby = ""; + $this->_min_id = 0; + $this->_max_id = 0; + $this->_filters = array (); + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + $this->_maxmatches = 1000; + $this->_cutoff = 0; + $this->_retrycount = 0; + $this->_retrydelay = 0; + $this->_anchor = array (); + $this->_indexweights= array (); + $this->_ranker = SPH_RANK_PROXIMITY_BM25; + $this->_rankexpr = ""; + $this->_maxquerytime= 0; + $this->_fieldweights= array(); + $this->_overrides = array(); + $this->_select = "*"; + + $this->_error = ""; // per-reply fields (for single-query case) + $this->_warning = ""; + $this->_connerror = false; + + $this->_reqs = array (); // requests storage (for multi-query case) + $this->_mbenc = ""; + $this->_arrayresult = false; + $this->_timeout = 0; + } + + function __destruct() + { + if ( $this->_socket !== false ) + fclose ( $this->_socket ); + } + + /// get last error message (string) + function GetLastError () + { + return $this->_error; + } + + /// get last warning message (string) + function GetLastWarning () + { + return $this->_warning; + } + + /// get last error flag (to tell network connection errors from searchd errors or broken responses) + function IsConnectError() + { + return $this->_connerror; + } + + /// set searchd host name (string) and port (integer) + function SetServer ( $host, $port = 0 ) + { + assert ( is_string($host) ); + if ( $host[0] == '/') + { + $this->_path = 'unix://' . $host; + return; + } + if ( substr ( $host, 0, 7 )=="unix://" ) + { + $this->_path = $host; + return; + } + + assert ( is_int($port) ); + $this->_host = $host; + $this->_port = $port; + $this->_path = ''; + + } + + /// set server connection timeout (0 to remove) + function SetConnectTimeout ( $timeout ) + { + assert ( is_numeric($timeout) ); + $this->_timeout = $timeout; + } + + + function _Send ( $handle, $data, $length ) + { + if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length ) + { + $this->_error = 'connection unexpectedly closed (timed out?)'; + $this->_connerror = true; + return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////////// + + /// enter mbstring workaround mode + function _MBPush () + { + $this->_mbenc = ""; + if ( ini_get ( "mbstring.func_overload" ) & 2 ) + { + $this->_mbenc = mb_internal_encoding(); + mb_internal_encoding ( "latin1" ); + } + } + + /// leave mbstring workaround mode + function _MBPop () + { + if ( $this->_mbenc ) + mb_internal_encoding ( $this->_mbenc ); + } + + /// connect to searchd server + function _Connect () + { + if ( $this->_socket!==false ) + { + // we are in persistent connection mode, so we have a socket + // however, need to check whether it's still alive + if ( !@feof ( $this->_socket ) ) + return $this->_socket; + + // force reopen + $this->_socket = false; + } + + $errno = 0; + $errstr = ""; + $this->_connerror = false; + + if ( $this->_path ) + { + $host = $this->_path; + $port = 0; + } + else + { + $host = $this->_host; + $port = $this->_port; + } + + if ( $this->_timeout<=0 ) + $fp = @fsockopen ( $host, $port, $errno, $errstr ); + else + $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout ); + + if ( !$fp ) + { + if ( $this->_path ) + $location = $this->_path; + else + $location = "{$this->_host}:{$this->_port}"; + + $errstr = trim ( $errstr ); + $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)"; + $this->_connerror = true; + return false; + } + + // send my version + // this is a subtle part. we must do it before (!) reading back from searchd. + // because otherwise under some conditions (reported on FreeBSD for instance) + // TCP stack could throttle write-write-read pattern because of Nagle. + if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) ) + { + fclose ( $fp ); + $this->_error = "failed to send client protocol version"; + return false; + } + + // check version + list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); + $v = (int)$v; + if ( $v<1 ) + { + fclose ( $fp ); + $this->_error = "expected searchd protocol version 1+, got version '$v'"; + return false; + } + + return $fp; + } + + /// get and check response packet from searchd server + function _GetResponse ( $fp, $client_ver ) + { + $response = ""; + $len = 0; + + $header = fread ( $fp, 8 ); + if ( strlen($header)==8 ) + { + list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); + $left = $len; + while ( $left>0 && !feof($fp) ) + { + $chunk = fread ( $fp, min ( 8192, $left ) ); + if ( $chunk ) + { + $response .= $chunk; + $left -= strlen($chunk); + } + } + } + if ( $this->_socket === false ) + fclose ( $fp ); + + // check response + $read = strlen ( $response ); + if ( !$response || $read!=$len ) + { + $this->_error = $len + ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" + : "received zero-sized searchd response"; + return false; + } + + // check status + if ( $status==SEARCHD_WARNING ) + { + list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_warning = substr ( $response, 4, $wlen ); + return substr ( $response, 4+$wlen ); + } + if ( $status==SEARCHD_ERROR ) + { + $this->_error = "searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status==SEARCHD_RETRY ) + { + $this->_error = "temporary searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status!=SEARCHD_OK ) + { + $this->_error = "unknown status code '$status'"; + return false; + } + + // check version + if ( $ver<$client_ver ) + { + $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", + $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); + } + + return $response; + } + + ///////////////////////////////////////////////////////////////////////////// + // searching + ///////////////////////////////////////////////////////////////////////////// + + /// set offset and count into result set, + /// and optionally set max-matches and cutoff limits + function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) + { + assert ( is_int($offset) ); + assert ( is_int($limit) ); + assert ( $offset>=0 ); + assert ( $limit>0 ); + assert ( $max>=0 ); + $this->_offset = $offset; + $this->_limit = $limit; + if ( $max>0 ) + $this->_maxmatches = $max; + if ( $cutoff>0 ) + $this->_cutoff = $cutoff; + } + + /// set maximum query time, in milliseconds, per-index + /// integer, 0 means "do not limit" + function SetMaxQueryTime ( $max ) + { + assert ( is_int($max) ); + assert ( $max>=0 ); + $this->_maxquerytime = $max; + } + + /// set matching mode + function SetMatchMode ( $mode ) + { + assert ( $mode==SPH_MATCH_ALL + || $mode==SPH_MATCH_ANY + || $mode==SPH_MATCH_PHRASE + || $mode==SPH_MATCH_BOOLEAN + || $mode==SPH_MATCH_EXTENDED + || $mode==SPH_MATCH_FULLSCAN + || $mode==SPH_MATCH_EXTENDED2 ); + $this->_mode = $mode; + } + + /// set ranking mode + function SetRankingMode ( $ranker, $rankexpr="" ) + { + assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL ); + assert ( is_string($rankexpr) ); + $this->_ranker = $ranker; + $this->_rankexpr = $rankexpr; + } + + /// set matches sorting mode + function SetSortMode ( $mode, $sortby="" ) + { + assert ( + $mode==SPH_SORT_RELEVANCE || + $mode==SPH_SORT_ATTR_DESC || + $mode==SPH_SORT_ATTR_ASC || + $mode==SPH_SORT_TIME_SEGMENTS || + $mode==SPH_SORT_EXTENDED || + $mode==SPH_SORT_EXPR ); + assert ( is_string($sortby) ); + assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); + + $this->_sort = $mode; + $this->_sortby = $sortby; + } + + /// bind per-field weights by order + /// DEPRECATED; use SetFieldWeights() instead + function SetWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $weight ) + assert ( is_int($weight) ); + + $this->_weights = $weights; + } + + /// bind per-field weights by name + function SetFieldWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $name=>$weight ) + { + assert ( is_string($name) ); + assert ( is_int($weight) ); + } + $this->_fieldweights = $weights; + } + + /// bind per-index weights by name + function SetIndexWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $index=>$weight ) + { + assert ( is_string($index) ); + assert ( is_int($weight) ); + } + $this->_indexweights = $weights; + } + + /// set IDs range to match + /// only match records if document ID is beetwen $min and $max (inclusive) + function SetIDRange ( $min, $max ) + { + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + $this->_min_id = $min; + $this->_max_id = $max; + } + + /// set values set filter + /// only match records where $attribute value is in given set + function SetFilter ( $attribute, $values, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_array($values) ); + assert ( count($values) ); + + if ( is_array($values) && count($values) ) + { + foreach ( $values as $value ) + assert ( is_numeric($value) ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); + } + } + + /// set range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// set float range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_float($min) ); + assert ( is_float($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// setup anchor point for geosphere distance calculations + /// required to use @geodist in filters and sorting + /// latitude and longitude must be in radians + function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) + { + assert ( is_string($attrlat) ); + assert ( is_string($attrlong) ); + assert ( is_float($lat) ); + assert ( is_float($long) ); + + $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); + } + + /// set grouping attribute and function + function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) + { + assert ( is_string($attribute) ); + assert ( is_string($groupsort) ); + assert ( $func==SPH_GROUPBY_DAY + || $func==SPH_GROUPBY_WEEK + || $func==SPH_GROUPBY_MONTH + || $func==SPH_GROUPBY_YEAR + || $func==SPH_GROUPBY_ATTR + || $func==SPH_GROUPBY_ATTRPAIR ); + + $this->_groupby = $attribute; + $this->_groupfunc = $func; + $this->_groupsort = $groupsort; + } + + /// set count-distinct attribute for group-by queries + function SetGroupDistinct ( $attribute ) + { + assert ( is_string($attribute) ); + $this->_groupdistinct = $attribute; + } + + /// set distributed retries count and delay + function SetRetries ( $count, $delay=0 ) + { + assert ( is_int($count) && $count>=0 ); + assert ( is_int($delay) && $delay>=0 ); + $this->_retrycount = $count; + $this->_retrydelay = $delay; + } + + /// set result set format (hash or array; hash by default) + /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs + function SetArrayResult ( $arrayresult ) + { + assert ( is_bool($arrayresult) ); + $this->_arrayresult = $arrayresult; + } + + /// set attribute values override + /// there can be only one override per attribute + /// $values must be a hash that maps document IDs to attribute values + function SetOverride ( $attrname, $attrtype, $values ) + { + assert ( is_string ( $attrname ) ); + assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) ); + assert ( is_array ( $values ) ); + + $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values ); + } + + /// set select-list (attributes or expressions), SQL-like syntax + function SetSelect ( $select ) + { + assert ( is_string ( $select ) ); + $this->_select = $select; + } + + ////////////////////////////////////////////////////////////////////////////// + + /// clear all filters (for multi-queries) + function ResetFilters () + { + $this->_filters = array(); + $this->_anchor = array(); + } + + /// clear groupby settings (for multi-queries) + function ResetGroupBy () + { + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + } + + /// clear all attribute value overrides (for multi-queries) + function ResetOverrides () + { + $this->_overrides = array (); + } + + ////////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, run given search query through given indexes, + /// and return the search results + function Query ( $query, $index="*", $comment="" ) + { + assert ( empty($this->_reqs) ); + + $this->AddQuery ( $query, $index, $comment ); + $results = $this->RunQueries (); + $this->_reqs = array (); // just in case it failed too early + + if ( !is_array($results) ) + return false; // probably network error; error message should be already filled + + $this->_error = $results[0]["error"]; + $this->_warning = $results[0]["warning"]; + if ( $results[0]["status"]==SEARCHD_ERROR ) + return false; + else + return $results[0]; + } + + /// helper to pack floats in network byte order + function _PackFloat ( $f ) + { + $t1 = pack ( "f", $f ); // machine order + list(,$t2) = unpack ( "L*", $t1 ); // int in machine order + return pack ( "N", $t2 ); + } + + /// add query to multi-query batch + /// returns index into results array from RunQueries() call + function AddQuery ( $query, $index="*", $comment="" ) + { + // mbstring workaround + $this->_MBPush (); + + // build request + $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker ); + if ( $this->_ranker==SPH_RANK_EXPR ) + $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr; + $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode + $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; + $req .= pack ( "N", strlen($query) ) . $query; // query itself + $req .= pack ( "N", count($this->_weights) ); // weights + foreach ( $this->_weights as $weight ) + $req .= pack ( "N", (int)$weight ); + $req .= pack ( "N", strlen($index) ) . $index; // indexes + $req .= pack ( "N", 1 ); // id64 range marker + $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range + + // filters + $req .= pack ( "N", count($this->_filters) ); + foreach ( $this->_filters as $filter ) + { + $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; + $req .= pack ( "N", $filter["type"] ); + switch ( $filter["type"] ) + { + case SPH_FILTER_VALUES: + $req .= pack ( "N", count($filter["values"]) ); + foreach ( $filter["values"] as $value ) + $req .= sphPackI64 ( $value ); + break; + + case SPH_FILTER_RANGE: + $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] ); + break; + + case SPH_FILTER_FLOATRANGE: + $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); + break; + + default: + assert ( 0 && "internal error: unhandled filter type" ); + } + $req .= pack ( "N", $filter["exclude"] ); + } + + // group-by clause, max-matches count, group-sort clause, cutoff count + $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; + $req .= pack ( "N", $this->_maxmatches ); + $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; + $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); + $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; + + // anchor point + if ( empty($this->_anchor) ) + { + $req .= pack ( "N", 0 ); + } else + { + $a =& $this->_anchor; + $req .= pack ( "N", 1 ); + $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; + $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; + $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); + } + + // per-index weights + $req .= pack ( "N", count($this->_indexweights) ); + foreach ( $this->_indexweights as $idx=>$weight ) + $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); + + // max query time + $req .= pack ( "N", $this->_maxquerytime ); + + // per-field weights + $req .= pack ( "N", count($this->_fieldweights) ); + foreach ( $this->_fieldweights as $field=>$weight ) + $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); + + // comment + $req .= pack ( "N", strlen($comment) ) . $comment; + + // attribute overrides + $req .= pack ( "N", count($this->_overrides) ); + foreach ( $this->_overrides as $key => $entry ) + { + $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"]; + $req .= pack ( "NN", $entry["type"], count($entry["values"]) ); + foreach ( $entry["values"] as $id=>$val ) + { + assert ( is_numeric($id) ); + assert ( is_numeric($val) ); + + $req .= sphPackU64 ( $id ); + switch ( $entry["type"] ) + { + case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break; + case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break; + default: $req .= pack ( "N", $val ); break; + } + } + } + + // select-list + $req .= pack ( "N", strlen($this->_select) ) . $this->_select; + + // mbstring workaround + $this->_MBPop (); + + // store request to requests array + $this->_reqs[] = $req; + return count($this->_reqs)-1; + } + + /// connect to searchd, run queries batch, and return an array of result sets + function RunQueries () + { + if ( empty($this->_reqs) ) + { + $this->_error = "no queries defined, issue AddQuery() first"; + return false; + } + + // mbstring workaround + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return false; + } + + // send query, get response + $nreqs = count($this->_reqs); + $req = join ( "", $this->_reqs ); + $len = 8+strlen($req); + $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header + + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) ) + { + $this->_MBPop (); + return false; + } + + // query sent ok; we can reset reqs now + $this->_reqs = array (); + + // parse and return response + return $this->_ParseSearchResponse ( $response, $nreqs ); + } + + /// parse and return search query (or queries) response + function _ParseSearchResponse ( $response, $nreqs ) + { + $p = 0; // current position + $max = strlen($response); // max position for checks, to protect against broken responses + + $results = array (); + for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) + { + $results[] = array(); + $result =& $results[$ires]; + + $result["error"] = ""; + $result["warning"] = ""; + + // extract status + list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $result["status"] = $status; + if ( $status!=SEARCHD_OK ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $message = substr ( $response, $p, $len ); $p += $len; + + if ( $status==SEARCHD_WARNING ) + { + $result["warning"] = $message; + } else + { + $result["error"] = $message; + continue; + } + } + + // read schema + $fields = array (); + $attrs = array (); + + list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nfields-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $fields[] = substr ( $response, $p, $len ); $p += $len; + } + $result["fields"] = $fields; + + list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nattrs-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attr = substr ( $response, $p, $len ); $p += $len; + list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrs[$attr] = $type; + } + $result["attrs"] = $attrs; + + // read match count + list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + + // read matches + $idx = -1; + while ( $count-->0 && $p<$max ) + { + // index into result array + $idx++; + + // parse document id and weight + if ( $id64 ) + { + $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + } + else + { + list ( $doc, $weight ) = array_values ( unpack ( "N*N*", + substr ( $response, $p, 8 ) ) ); + $p += 8; + $doc = sphFixUint($doc); + } + $weight = sprintf ( "%u", $weight ); + + // create match entry + if ( $this->_arrayresult ) + $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); + else + $result["matches"][$doc]["weight"] = $weight; + + // parse and create attributes + $attrvals = array (); + foreach ( $attrs as $attr=>$type ) + { + // handle 64bit ints + if ( $type==SPH_ATTR_BIGINT ) + { + $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8; + continue; + } + + // handle floats + if ( $type==SPH_ATTR_FLOAT ) + { + list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); + $attrvals[$attr] = $fval; + continue; + } + + // handle everything else as unsigned ints + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + if ( $type==SPH_ATTR_MULTI ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues-->0 && $p<$max ) + { + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrvals[$attr][] = sphFixUint($val); + } + } else if ( $type==SPH_ATTR_MULTI64 ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues>0 && $p<$max ) + { + $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + $nvalues -= 2; + } + } else if ( $type==SPH_ATTR_STRING ) + { + $attrvals[$attr] = substr ( $response, $p, $val ); + $p += $val; + } else + { + $attrvals[$attr] = sphFixUint($val); + } + } + + if ( $this->_arrayresult ) + $result["matches"][$idx]["attrs"] = $attrvals; + else + $result["matches"][$doc]["attrs"] = $attrvals; + } + + list ( $total, $total_found, $msecs, $words ) = + array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); + $result["total"] = sprintf ( "%u", $total ); + $result["total_found"] = sprintf ( "%u", $total_found ); + $result["time"] = sprintf ( "%.3f", $msecs/1000 ); + $p += 16; + + while ( $words-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $word = substr ( $response, $p, $len ); $p += $len; + list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + $result["words"][$word] = array ( + "docs"=>sprintf ( "%u", $docs ), + "hits"=>sprintf ( "%u", $hits ) ); + } + } + + $this->_MBPop (); + return $results; + } + + ///////////////////////////////////////////////////////////////////////////// + // excerpts generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate exceprts (snippets) + /// of given documents for given query. returns false on failure, + /// an array of snippets on success + function BuildExcerpts ( $docs, $index, $words, $opts=array() ) + { + assert ( is_array($docs) ); + assert ( is_string($index) ); + assert ( is_string($words) ); + assert ( is_array($opts) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // fixup options + ///////////////// + + if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>"; + if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>"; + if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; + if ( !isset($opts["limit"]) ) $opts["limit"] = 256; + if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0; + if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0; + if ( !isset($opts["around"]) ) $opts["around"] = 5; + if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; + if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; + if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; + if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; + if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false; + if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false; + if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1; + if ( !isset($opts["load_files"]) ) $opts["load_files"] = false; + if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index"; + if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false; + if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none"; + if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false; + if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false; + + + ///////////////// + // build request + ///////////////// + + // v.1.2 req + $flags = 1; // remove spaces + if ( $opts["exact_phrase"] ) $flags |= 2; + if ( $opts["single_passage"] ) $flags |= 4; + if ( $opts["use_boundaries"] ) $flags |= 8; + if ( $opts["weight_order"] ) $flags |= 16; + if ( $opts["query_mode"] ) $flags |= 32; + if ( $opts["force_all_words"] ) $flags |= 64; + if ( $opts["load_files"] ) $flags |= 128; + if ( $opts["allow_empty"] ) $flags |= 256; + if ( $opts["emit_zones"] ) $flags |= 512; + if ( $opts["load_files_scattered"] ) $flags |= 1024; + $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", strlen($words) ) . $words; // req words + + // options + $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; + $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; + $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; + $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] ); + $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2 + $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"]; + $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"]; + + // documents + $req .= pack ( "N", count($docs) ); + foreach ( $docs as $doc ) + { + assert ( is_string($doc) ); + $req .= pack ( "N", strlen($doc) ) . $doc; + } + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + for ( $i=0; $i<count($docs); $i++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); + $pos += 4; + + if ( $pos+$len > $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + $res[] = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + } + + $this->_MBPop (); + return $res; + } + + + ///////////////////////////////////////////////////////////////////////////// + // keyword generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate keyword list for a given query + /// returns false on failure, + /// an array of words on success + function BuildKeywords ( $query, $index, $hits ) + { + assert ( is_string($query) ); + assert ( is_string($index) ); + assert ( is_bool($hits) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // build request + ///////////////// + + // v.1.0 req + $req = pack ( "N", strlen($query) ) . $query; // req query + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", (int)$hits ); + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); + $pos += 4; + for ( $i=0; $i<$nwords; $i++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $tokenized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $normalized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); + + if ( $hits ) + { + list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); + $pos += 8; + $res [$i]["docs"] = $ndocs; + $res [$i]["hits"] = $nhits; + } + + if ( $pos > $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + } + + $this->_MBPop (); + return $res; + } + + function EscapeString ( $string ) + { + $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' ); + $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' ); + + return str_replace ( $from, $to, $string ); + } + + ///////////////////////////////////////////////////////////////////////////// + // attribute updates + ///////////////////////////////////////////////////////////////////////////// + + /// batch update given attributes in given rows in given indexes + /// returns amount of updated documents (0 or more) on success, or -1 on failure + function UpdateAttributes ( $index, $attrs, $values, $mva=false ) + { + // verify everything + assert ( is_string($index) ); + assert ( is_bool($mva) ); + + assert ( is_array($attrs) ); + foreach ( $attrs as $attr ) + assert ( is_string($attr) ); + + assert ( is_array($values) ); + foreach ( $values as $id=>$entry ) + { + assert ( is_numeric($id) ); + assert ( is_array($entry) ); + assert ( count($entry)==count($attrs) ); + foreach ( $entry as $v ) + { + if ( $mva ) + { + assert ( is_array($v) ); + foreach ( $v as $vv ) + assert ( is_int($vv) ); + } else + assert ( is_int($v) ); + } + } + + // build request + $this->_MBPush (); + $req = pack ( "N", strlen($index) ) . $index; + + $req .= pack ( "N", count($attrs) ); + foreach ( $attrs as $attr ) + { + $req .= pack ( "N", strlen($attr) ) . $attr; + $req .= pack ( "N", $mva ? 1 : 0 ); + } + + $req .= pack ( "N", count($values) ); + foreach ( $values as $id=>$entry ) + { + $req .= sphPackU64 ( $id ); + foreach ( $entry as $v ) + { + $req .= pack ( "N", $mva ? count($v) : $v ); + if ( $mva ) + foreach ( $v as $vv ) + $req .= pack ( "N", $vv ); + } + } + + // connect, send query, get response + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return -1; + } + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header + if ( !$this->_Send ( $fp, $req, $len+8 ) ) + { + $this->_MBPop (); + return -1; + } + + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) + { + $this->_MBPop (); + return -1; + } + + // parse response + list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_MBPop (); + return $updated; + } + + ///////////////////////////////////////////////////////////////////////////// + // persistent connections + ///////////////////////////////////////////////////////////////////////////// + + function Open() + { + if ( $this->_socket !== false ) + { + $this->_error = 'already connected'; + return false; + } + if ( !$fp = $this->_Connect() ) + return false; + + // command, command version = 0, body length = 4, body = 1 + $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 ); + if ( !$this->_Send ( $fp, $req, 12 ) ) + return false; + + $this->_socket = $fp; + return true; + } + + function Close() + { + if ( $this->_socket === false ) + { + $this->_error = 'not connected'; + return false; + } + + fclose ( $this->_socket ); + $this->_socket = false; + + return true; + } + + ////////////////////////////////////////////////////////////////////////// + // status + ////////////////////////////////////////////////////////////////////////// + + function Status () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1 + if ( !( $this->_Send ( $fp, $req, 12 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) ) + { + $this->_MBPop (); + return false; + } + + $res = substr ( $response, 4 ); // just ignore length, error handling, etc + $p = 0; + list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + + $res = array(); + for ( $i=0; $i<$rows; $i++ ) + for ( $j=0; $j<$cols; $j++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $res[$i][] = substr ( $response, $p, $len ); $p += $len; + } + + $this->_MBPop (); + return $res; + } + + ////////////////////////////////////////////////////////////////////////// + // flush + ////////////////////////////////////////////////////////////////////////// + + function FlushAttributes () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return -1; + } + + $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0 + if ( !( $this->_Send ( $fp, $req, 8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) ) + { + $this->_MBPop (); + return -1; + } + + $tag = -1; + if ( strlen($response)==4 ) + list(,$tag) = unpack ( "N*", $response ); + else + $this->_error = "unexpected response length"; + + $this->_MBPop (); + return $tag; + } +} + +// +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $ +// diff --git a/phpBB/includes/style/extension_path_provider.php b/phpBB/includes/style/extension_path_provider.php index 4eac300424..6976a45ed0 100644 --- a/phpBB/includes/style/extension_path_provider.php +++ b/phpBB/includes/style/extension_path_provider.php @@ -92,7 +92,7 @@ class phpbb_style_extension_path_provider extends phpbb_extension_provider imple if ($path && !phpbb_is_absolute($path)) { $result = $finder->directory('/' . $this->ext_dir_prefix . $path) - ->get_directories(true, true); + ->get_directories(true, false, true); foreach ($result as $ext => $ext_path) { $directories[$ext][] = $ext_path; diff --git a/phpBB/includes/ucp/info/ucp_notifications.php b/phpBB/includes/ucp/info/ucp_notifications.php new file mode 100644 index 0000000000..98d8b9db61 --- /dev/null +++ b/phpBB/includes/ucp/info/ucp_notifications.php @@ -0,0 +1,35 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @package module_install +*/ +class ucp_notifications_info +{ + function module() + { + return array( + 'filename' => 'ucp_notifications', + 'title' => 'UCP_NOTIFICATION_OPTIONS', + 'version' => '1.0.0', + 'modes' => array( + 'notification_options' => array('title' => 'UCP_NOTIFICATION_OPTIONS', 'auth' => '', 'cat' => array('UCP_PREFS')), + 'notification_list' => array('title' => 'UCP_NOTIFICATION_LIST', 'auth' => '', 'cat' => array('UCP_MAIN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index a0d0baf10f..577761dfde 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -50,7 +50,7 @@ class ucp_activate trigger_error('ALREADY_ACTIVATED'); } - if (($user_row['user_inactive_reason'] == INACTIVE_MANUAL) || $user_row['user_actkey'] != $key) + if ($user_row['user_inactive_reason'] == INACTIVE_MANUAL || $user_row['user_actkey'] !== $key) { trigger_error('WRONG_ACTIVATION'); } diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index 9652986cf2..b9a06bc3b4 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -25,7 +25,7 @@ class ucp_groups function main($id, $mode) { - global $config, $phpbb_root_path, $phpEx; + global $config, $phpbb_root_path, $phpEx, $phpbb_admin_path; global $db, $user, $auth, $cache, $template; global $request; @@ -438,7 +438,7 @@ class ucp_groups $group_name = $group_row['group_name']; $group_type = $group_row['group_type']; - $avatar_img = (!empty($group_row['group_avatar'])) ? get_user_avatar($group_row['group_avatar'], $group_row['group_avatar_type'], $group_row['group_avatar_width'], $group_row['group_avatar_height'], 'GROUP_AVATAR') : '<img src="' . $phpbb_root_path . 'adm/images/no_avatar.gif" alt="" />'; + $avatar_img = (!empty($group_row['group_avatar'])) ? get_user_avatar($group_row['group_avatar'], $group_row['group_avatar_type'], $group_row['group_avatar_width'], $group_row['group_avatar_height'], 'GROUP_AVATAR') : '<img src="' . $phpbb_admin_path . 'images/no_avatar.gif" alt="" />'; $template->assign_vars(array( 'GROUP_NAME' => ($group_type == GROUP_SPECIAL) ? $user->lang['G_' . $group_name] : $group_name, @@ -618,7 +618,7 @@ class ucp_groups foreach ($test_variables as $test => $type) { - if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test])) + if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0)) { settype($submit_ary[$test], $type); $group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test]; @@ -730,7 +730,7 @@ class ucp_groups 'GROUP_CLOSED' => $type_closed, 'GROUP_HIDDEN' => $type_hidden, - 'U_SWATCH' => append_sid("{$phpbb_root_path}adm/swatch.$phpEx", 'form=ucp&name=group_colour'), + 'U_SWATCH' => append_sid("{$phpbb_admin_path}swatch.$phpEx", 'form=ucp&name=group_colour'), 'S_UCP_ACTION' => $this->u_action . "&action=$action&g=$group_id", 'L_AVATAR_EXPLAIN' => phpbb_avatar_explanation_string(), )); diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php new file mode 100644 index 0000000000..338c921e94 --- /dev/null +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -0,0 +1,226 @@ +<?php +/** +* +* @package notifications +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class ucp_notifications +{ + public $u_action; + + public function main($id, $mode) + { + global $config, $template, $user, $request, $phpbb_container; + global $phpbb_root_path, $phpEx; + + add_form_key('ucp_notification'); + + $start = $request->variable('start', 0); + $form_time = min($request->variable('form_time', 0), time()); + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + switch ($mode) + { + case 'notification_options': + $subscriptions = $phpbb_notifications->get_global_subscriptions(false); + + // Add/remove subscriptions + if ($request->is_set_post('submit')) + { + if (!check_form_key('ucp_notification')) + { + trigger_error('FORM_INVALID'); + } + + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach($phpbb_notifications->get_subscription_types() as $group => $subscription_types) + { + foreach($subscription_types as $type => $data) + { + foreach($notification_methods as $method => $method_data) + { + if ($request->is_set_post($type . '_' . $method_data['id']) && (!isset($subscriptions[$type]) || !in_array($method_data['id'], $subscriptions[$type]))) + { + $phpbb_notifications->add_subscription($type, 0, $method_data['id']); + } + else if (!$request->is_set_post($type . '_' . $method_data['id']) && isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type])) + { + $phpbb_notifications->delete_subscription($type, 0, $method_data['id']); + } + } + + if ($request->is_set_post($type . '_notification') && !isset($subscriptions[$type])) + { + $phpbb_notifications->add_subscription($type); + } + else if (!$request->is_set_post($type . '_notification') && isset($subscriptions[$type])) + { + $phpbb_notifications->delete_subscription($type); + } + } + } + + meta_refresh(3, $this->u_action); + $message = $user->lang['PREFERENCES_UPDATED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>'); + trigger_error($message); + } + + $this->output_notification_methods('notification_methods', $phpbb_notifications, $template, $user); + + $this->output_notification_types($subscriptions, 'notification_types', $phpbb_notifications, $template, $user); + + $this->tpl_name = 'ucp_notifications'; + $this->page_title = 'UCP_NOTIFICATION_OPTIONS'; + break; + + case 'notification_list': + default: + // Mark all items read + if ($request->variable('mark', '') == 'all' && (confirm_box(true) || check_link_hash($request->variable('token', ''), 'mark_all_notifications_read'))) + { + if (confirm_box(true)) + { + $phpbb_notifications->mark_notifications_read(false, false, $user->data['user_id'], $form_time); + + meta_refresh(3, $this->u_action); + $message = $user->lang['NOTIFICATIONS_MARK_ALL_READ_SUCCESS'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>'); + trigger_error($message); + } + else + { + confirm_box(false, 'NOTIFICATIONS_MARK_ALL_READ', build_hidden_fields(array( + 'mark' => 'all', + 'form_time' => $form_time, + ))); + } + } + + // Mark specific notifications read + if ($request->is_set_post('submit')) + { + if (!check_form_key('ucp_notification')) + { + trigger_error('FORM_INVALID'); + } + + $mark_read = $request->variable('mark', array(0)); + + if (!empty($mark_read)) + { + $phpbb_notifications->mark_notifications_read_by_id($mark_read, $form_time); + } + } + + $notifications = $phpbb_notifications->load_notifications(array( + 'start' => $start, + 'limit' => $config['topics_per_page'], + 'count_total' => true, + )); + + foreach ($notifications['notifications'] as $notification) + { + $template->assign_block_vars('notification_list', $notification->prepare_for_display()); + } + + $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=ucp_notifications&mode=notification_list"); + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $notifications['total_count'], $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $notifications['total_count'], $config['topics_per_page'], $start), + 'TOTAL_COUNT' => $user->lang('NOTIFICATIONS_COUNT', $notifications['total_count']), + 'U_MARK_ALL' => $base_url . '&mark=all&token=' . generate_link_hash('mark_all_notifications_read'), + )); + + $this->tpl_name = 'ucp_notifications'; + $this->page_title = 'UCP_NOTIFICATION_LIST'; + break; + } + + $template->assign_vars(array( + 'TITLE' => $user->lang($this->page_title), + 'TITLE_EXPLAIN' => $user->lang($this->page_title . '_EXPLAIN'), + + 'MODE' => $mode, + + 'FORM_TIME' => time(), + )); + } + + /** + * Output all the notification types to the template + * + * @param string $block + * @param phpbb_notification_manager $phpbb_notifications + * @param phpbb_template $template + * @param phpbb_user $user + */ + public function output_notification_types($subscriptions, $block = 'notification_types', phpbb_notification_manager $phpbb_notifications, phpbb_template $template, phpbb_user $user) + { + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach($phpbb_notifications->get_subscription_types() as $group => $subscription_types) + { + $template->assign_block_vars($block, array( + 'GROUP_NAME' => $user->lang($group), + )); + + foreach($subscription_types as $type => $data) + { + $template->assign_block_vars($block, array( + 'TYPE' => $type, + + 'NAME' => $user->lang($data['lang']), + 'EXPLAIN' => (isset($user->lang[$data['lang'] . '_EXPLAIN'])) ? $user->lang($data['lang'] . '_EXPLAIN') : '', + + 'SUBSCRIBED' => (isset($subscriptions[$type])) ? true : false, + )); + + foreach($notification_methods as $method => $method_data) + { + $template->assign_block_vars($block . '.notification_methods', array( + 'METHOD' => $method_data['id'], + + 'NAME' => $user->lang($method_data['lang']), + + 'SUBSCRIBED' => (isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type])) ? true : false, + )); + } + } + } + } + + /** + * Output all the notification methods to the template + * + * @param string $block + * @param phpbb_notification_manager $phpbb_notifications + * @param phpbb_template $template + * @param phpbb_user $user + */ + public function output_notification_methods($block = 'notification_methods', phpbb_notification_manager $phpbb_notifications, phpbb_template $template, phpbb_user $user) + { + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach($notification_methods as $method => $method_data) + { + $template->assign_block_vars($block, array( + 'METHOD' => $method_data['id'], + + 'NAME' => $user->lang($method_data['lang']), + )); + } + } +} diff --git a/phpBB/includes/ucp/ucp_pm_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php index 5577e8dab3..c2d12d17c2 100644 --- a/phpBB/includes/ucp/ucp_pm_compose.php +++ b/phpBB/includes/ucp/ucp_pm_compose.php @@ -353,7 +353,7 @@ function compose_pm($id, $mode, $action, $user_folders = array()) $message_attachment = 0; $message_text = $message_subject = ''; - if ($to_user_id && $action == 'post') + if ($to_user_id && $to_user_id != ANONYMOUS && $action == 'post') { $address_list['u'][$to_user_id] = 'to'; } diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 23892c2c8c..7c3286c1d1 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -46,8 +46,6 @@ class ucp_prefs 'viewemail' => request_var('viewemail', (bool) $user->data['user_allow_viewemail']), 'massemail' => request_var('massemail', (bool) $user->data['user_allow_massemail']), 'hideonline' => request_var('hideonline', (bool) !$user->data['user_allow_viewonline']), - 'notifypm' => request_var('notifypm', (bool) $user->data['user_notify_pm']), - 'popuppm' => request_var('popuppm', (bool) $user->optionget('popuppm')), 'allowpm' => request_var('allowpm', (bool) $user->data['user_allow_pm']), ); @@ -81,15 +79,12 @@ class ucp_prefs if (!sizeof($error)) { - $user->optionset('popuppm', $data['popuppm']); - $sql_ary = array( 'user_allow_pm' => $data['allowpm'], 'user_allow_viewemail' => $data['viewemail'], 'user_allow_massemail' => $data['massemail'], 'user_allow_viewonline' => ($auth->acl_get('u_hideonline')) ? !$data['hideonline'] : $user->data['user_allow_viewonline'], 'user_notify_type' => $data['notifymethod'], - 'user_notify_pm' => $data['notifypm'], 'user_options' => $user->data['user_options'], 'user_dateformat' => $data['dateformat'], @@ -172,8 +167,6 @@ class ucp_prefs 'S_MASS_EMAIL' => $data['massemail'], 'S_ALLOW_PM' => $data['allowpm'], 'S_HIDE_ONLINE' => $data['hideonline'], - 'S_NOTIFY_PM' => $data['notifypm'], - 'S_POPUP_PM' => $data['popuppm'], 'DATE_FORMAT' => $data['dateformat'], 'A_DATE_FORMAT' => addslashes($data['dateformat']), diff --git a/phpBB/includes/user_loader.php b/phpBB/includes/user_loader.php new file mode 100644 index 0000000000..77128d6570 --- /dev/null +++ b/phpBB/includes/user_loader.php @@ -0,0 +1,231 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* User loader class +* +* This handles loading users from the database and +* storing in them in a temporary cache so we do not +* have to query the same user multiple times in +* different services. +*/ +class phpbb_user_loader +{ + /** @var phpbb_db_driver */ + protected $db = null; + + /** @var string */ + protected $phpbb_root_path = null; + + /** @var string */ + protected $php_ext = null; + + /** @var string */ + protected $users_table = null; + + /** + * Users loaded from the DB + * + * @var array Array of user data that we've loaded from the DB + */ + protected $users = array(); + + /** + * User loader constructor + * + * @param phpbb_db_driver $db A database connection + * @param string $phpbb_root_path Path to the phpbb includes directory. + * @param string $php_ext php file extension + * @param string $users_table The name of the database table (phpbb_users) + */ + public function __construct(phpbb_db_driver $db, $phpbb_root_path, $php_ext, $users_table) + { + $this->db = $db; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->users_table = $users_table; + } + + /** + * Load user helper + * + * @param array $user_ids + */ + public function load_users(array $user_ids) + { + $user_ids[] = ANONYMOUS; + + // Load the users + $user_ids = array_unique($user_ids); + + // Do not load users we already have in $this->users + $user_ids = array_diff($user_ids, array_keys($this->users)); + + if (sizeof($user_ids)) + { + $sql = 'SELECT * + FROM ' . $this->users_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->users[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + } + } + + /** + * Load a user by username + * + * Stores the full data in the user cache so they do not need to be loaded again + * Returns the user id so you may use get_user() from the returned value + * + * @param string $username Raw username to load (will be cleaned) + * @return int User ID for the username + */ + public function load_user_by_username($username) + { + $sql = 'SELECT * + FROM ' . $this->users_table . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->users[$row['user_id']] = $row; + + return $row['user_id']; + } + + return ANONYMOUS; + } + + /** + * Get a user row from our users cache + * + * @param int $user_id User ID of the user you want to retreive + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return array|bool Row from the database of the user or Anonymous if the user wasn't loaded/does not exist + * or bool False if the anonymous user was not loaded + */ + public function get_user($user_id, $query = false) + { + if (isset($this->users[$user_id])) + { + return $this->users[$user_id]; + } + // Query them if we must (if ANONYMOUS is sent as the user_id and we have not loaded Anonymous yet, we must load Anonymous as a last resort) + else if ($query || $user_id == ANONYMOUS) + { + $this->load_users(array($user_id)); + + return $this->get_user($user_id); + } + + return $this->get_user(ANONYMOUS); + } + + /** + * Get username + * + * @param int $user_id User ID of the user you want to retreive the username for + * @param string $mode The mode to load (same as get_username_string). One of the following: + * profile (for getting an url to the profile) + * username (for obtaining the username) + * colour (for obtaining the user colour) + * full (for obtaining a html string representing a coloured link to the users profile) + * no_profile (the same as full but forcing no profile link) + * @param string $guest_username Optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then. + * @param string $custom_profile_url Optional parameter to specify a profile url. The user id get appended to this url as &u={user_id} + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return string + */ + public function get_username($user_id, $mode, $guest_username = false, $custom_profile_url = false, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + return get_username_string($mode, $user['user_id'], $user['username'], $user['user_colour'], $guest_username, $custom_profile_url); + } + + /** + * Get avatar + * + * @param int $user_id User ID of the user you want to retreive the avatar for + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return string + */ + public function get_avatar($user_id, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + if (!function_exists('get_user_avatar')) + { + include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); + } + + return get_user_avatar($user['user_avatar'], $user['user_avatar_type'], $user['user_avatar_width'], $user['user_avatar_height']); + } + + /** + * Get rank + * + * @param int $user_id User ID of the user you want to retreive the rank for + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return array Array with keys 'rank_title', 'rank_img', and 'rank_img_src' + */ + public function get_rank($user_id, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + if (!function_exists('get_user_rank')) + { + include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); + } + + $rank = array( + 'rank_title', + 'rank_img', + 'rank_img_src', + ); + + get_user_rank($user['user_rank'], (($user['user_id'] == ANONYMOUS) ? false : $user['user_posts']), $rank['rank_title'], $rank['rank_img'], $rank['rank_img_src']); + + return $rank; + } +} diff --git a/phpBB/index.php b/phpBB/index.php index 845d0f0c02..74fc1b9bda 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -24,6 +24,30 @@ $user->session_begin(); $auth->acl($user->data); $user->setup('viewforum'); +// Mark notifications read +if (($mark_notification = $request->variable('mark_notification', 0))) +{ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $notification = $phpbb_notifications->load_notifications(array( + 'notification_id' => $mark_notification + )); + + if (isset($notification['notifications'][$mark_notification])) + { + $notification = $notification['notifications'][$mark_notification]; + + $notification->mark_read(); + + if (($redirect = $request->variable('redirect', ''))) + { + redirect(append_sid($phpbb_root_path . $redirect)); + } + + redirect($notification->get_url()); + } +} + display_forums('', $config['load_moderators']); $order_legend = ($config['legend_sort_groupname']) ? 'group_name' : 'group_legend'; diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index dc5f11be04..4938ef0f87 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -1,39 +1,23 @@ <?php /** * -* @package install -* @copyright (c) 2006 phpBB Group +* @package phpBB3 +* @copyright (c) 2012 phpBB Group * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ +$update_start_time = time(); + use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -define('UPDATES_TO_VERSION', '3.1.0-dev'); - -// Enter any version to update from to test updates. The version within the db will not be updated. -define('DEBUG_FROM_VERSION', false); - -// Which oldest version does this updater support? -define('OLDEST_FROM_VERSION', '3.0.0'); - -// Return if we "just include it" to find out for which version the database update is responsible for -if (defined('IN_PHPBB') && defined('IN_INSTALL')) -{ - $updates_to_version = UPDATES_TO_VERSION; - $debug_from_version = DEBUG_FROM_VERSION; - $oldest_from_version = OLDEST_FROM_VERSION; - - return; -} - /** +* @ignore */ define('IN_PHPBB', true); define('IN_INSTALL', true); - $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); @@ -57,69 +41,78 @@ if (!function_exists('phpbb_require_updated')) } } -phpbb_require_updated('includes/startup.' . $phpEx); +function phpbb_end_update($cache) +{ + $cache->purge(); -$updates_to_version = UPDATES_TO_VERSION; -$debug_from_version = DEBUG_FROM_VERSION; -$oldest_from_version = OLDEST_FROM_VERSION; +?> + </p> + </div> + </div> + <span class="corners-bottom"><span></span></span> + </div> + </div> + </div> -@set_time_limit(0); + <div id="page-footer"> + Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group + </div> + </div> +</body> +</html> -// Include essential scripts -include($phpbb_root_path . 'config.' . $phpEx); +<?php + garbage_collection(); + exit_handler(); +} + +phpbb_require_updated('includes/startup.' . $phpEx); + +include($phpbb_root_path . 'config.' . $phpEx); if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type)) { die("Please read: <a href='../docs/INSTALL.html'>INSTALL.html</a> before attempting to update."); } +// 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/class_loader.' . $phpEx); require($phpbb_root_path . 'includes/functions.' . $phpEx); +require($phpbb_root_path . 'includes/functions_content.' . $phpEx); require($phpbb_root_path . 'includes/functions_container.' . $phpEx); -phpbb_require_updated('includes/functions_content.' . $phpEx, true); - -require($phpbb_root_path . 'includes/functions_admin.' . $phpEx); require($phpbb_root_path . 'includes/constants.' . $phpEx); require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); -phpbb_require_updated('includes/db/db_tools.' . $phpEx); - -// new table constants are separately defined here in case the updater is run -// before the files are updated -if (!defined('LOGIN_ATTEMPT_TABLE')) -{ - define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); -} -if (!defined('EXT_TABLE')) -{ - define('EXT_TABLE', $table_prefix . 'ext'); -} +// Set PHP error handler to ours +set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); // Setup class loader first $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", ".$phpEx"); $phpbb_class_loader->register(); -$phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_path}ext/", ".$phpEx"); -$phpbb_class_loader_ext->register(); -// Set up container -$phpbb_container = phpbb_create_dumped_container_unless_debug( - array( - new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), - new phpbb_di_extension_core($phpbb_root_path), - ), - array( - new phpbb_di_pass_collection_pass(), - new phpbb_di_pass_kernel_pass(), - ), - $phpbb_root_path, - $phpEx +// Set up container (must be done here because extensions table may not exist) +$container_extensions = array( + new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), + new phpbb_di_extension_core($phpbb_root_path), +); +$container_passes = array( + new phpbb_di_pass_collection_pass(), + //new phpbb_di_pass_kernel_pass(), ); +$phpbb_container = phpbb_create_container($container_extensions, $phpbb_root_path, $phpEx); -$phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); -$phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); +// Compile the container +foreach ($container_passes as $pass) +{ + $phpbb_container->addCompilerPass($pass); +} +$phpbb_container->compile(); // set up caching $cache = $phpbb_container->get('cache'); @@ -134,13 +127,22 @@ $db = $phpbb_container->get('dbal.conn'); // make sure request_var uses this request instance request_var('', 0, false, false, $request); // "dependency injection" for a function +// Grab global variables, re-cache if necessary +$config = $phpbb_container->get('config'); +set_config(null, null, null, $config); +set_config_count(null, null, null, $config); +$orig_version = $config['version']; + +$user->add_lang(array('common', 'acp/common', 'install', 'migrator')); + // Add own hook handler, if present. :o if (file_exists($phpbb_root_path . 'includes/hooks/index.' . $phpEx)) { require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display'))); - foreach ($cache->obtain_hooks() as $hook) + $phpbb_hook_finder = $phpbb_container->get('hook_finder'); + foreach ($phpbb_hook_finder->find() as $hook) { @include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx); } @@ -150,2789 +152,100 @@ else $phpbb_hook = false; } -// Connect to DB -$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false); - -// We do not need this any longer, unset for safety purposes -unset($dbpasswd); - -$user->ip = ''; -if ($request->server('REMOTE_ADDR')) -{ - $user->ip = (function_exists('phpbb_ip_normalise')) ? phpbb_ip_normalise($request->server('REMOTE_ADDR')) : $request->server('REMOTE_ADDR'); -} - -$sql = "SELECT config_value - FROM " . CONFIG_TABLE . " - WHERE config_name = 'default_lang'"; -$result = $db->sql_query($sql); -$row = $db->sql_fetchrow($result); -$db->sql_freeresult($result); - -$language = basename(request_var('language', '')); - -if (!$language) -{ - $language = $row['config_value']; -} - -if (!file_exists($phpbb_root_path . 'language/' . $language)) -{ - die('No language found!'); -} - -// And finally, load the relevant language files -include($phpbb_root_path . 'language/' . $language . '/common.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/acp/common.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/install.' . $phpEx); - -// Set PHP error handler to ours -//set_error_handler('msg_handler'); - -// Define some variables for the database update -$inline_update = (request_var('type', 0)) ? true : false; - -// To let set_config() calls succeed, we need to make the config array available globally -$config = new phpbb_config_db($db, $cache->get_driver(), CONFIG_TABLE); -set_config(null, null, null, $config); -set_config_count(null, null, null, $config); - -// Update asset_version -if (isset($config['assets_version'])) -{ - set_config('assets_version', $config['assets_version'] + 1); -} - -// phpbb_db_tools will be taken from new files (under install/update/new) -// if possible, falling back to the board's copy. -$db_tools = new phpbb_db_tools($db, true); - -$database_update_info = database_update_info(); - -$error_ary = array(); -$errored = false; - -$sql = 'SELECT topic_id - FROM ' . TOPICS_TABLE . ' - WHERE forum_id = 0 - AND topic_type = ' . POST_GLOBAL; -$result = $db->sql_query_limit($sql, 1); -$has_global = (int) $db->sql_fetchfield('topic_id'); -$db->sql_freeresult($result); -$ga_forum_id = request_var('ga_forum_id', 0); - -if ($has_global && !$ga_forum_id) -{ - ?> - <!DOCTYPE html> - <html dir="<?php echo $lang['DIRECTION']; ?>" lang="<?php echo $lang['USER_LANG']; ?>"> - <head> - <meta charset="utf-8"> - - <title><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></title> - - <link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" /> - - </head> - - <body> - <div id="wrap"> - <div id="page-header"> </div> - - <div id="page-body"> - <div id="acp"> - <div class="panel"> - <span class="corners-top"><span></span></span> - <div id="content"> - <div id="main" class="install-body"> - - <h1><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></h1> - - <br /> - - <form action="" method="post" id="select_ga_forum_id"> - <?php - if (isset($lang['SELECT_FORUM_GA'])) - { - // Language string is available: - echo $lang['SELECT_FORUM_GA']; - } - else - { - echo 'In phpBB 3.1 the global announcements are linked to forums. Select a forum for your current global announcements (can be moved later):'; - } - ?> - <select id="ga_forum_id" name="ga_forum_id"><?php echo make_forum_select(false, false, true, true) ?></select> - - <input type="submit" name="post" value="<?php echo $lang['SUBMIT']; ?>" class="button1" /> - </form> - <?php - _print_footer(); - exit_handler(); -} - header('Content-type: text/html; charset=UTF-8'); - ?> <!DOCTYPE html> -<html dir="<?php echo $lang['DIRECTION']; ?>" lang="<?php echo $lang['USER_LANG']; ?>"> +<html dir="<?php echo $user->lang['DIRECTION']; ?>" lang="<?php echo $user->lang['USER_LANG']; ?>"> <head> <meta charset="utf-8"> -<title><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></title> +<title><?php echo $user->lang['UPDATING_TO_LATEST_STABLE']; ?></title> -<link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" /> +<link href="<?php echo htmlspecialchars($phpbb_admin_path); ?>style/admin.css" rel="stylesheet" type="text/css" media="screen" /> </head> <body> -<div id="wrap"> - <div id="page-header"> </div> - - <div id="page-body"> - <div id="acp"> - <div class="panel"> - <span class="corners-top"><span></span></span> - <div id="content"> - <div id="main" class="install-body"> - - <h1><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></h1> - - <br /> - - <p><?php echo $lang['DATABASE_TYPE']; ?> :: <strong><?php echo $db->sql_layer; ?></strong><br /> -<?php - -if ($debug_from_version !== false) -{ - $config['version'] = $debug_from_version; -} - -echo $lang['PREVIOUS_VERSION'] . ' :: <strong>' . $config['version'] . '</strong><br />'; -echo $lang['UPDATED_VERSION'] . ' :: <strong>' . $updates_to_version . '</strong></p>'; - -$current_version = str_replace('rc', 'RC', strtolower($config['version'])); -$latest_version = str_replace('rc', 'RC', strtolower($updates_to_version)); -$orig_version = $config['version']; - -// Fill DB version -if (empty($config['dbms_version'])) -{ - set_config('dbms_version', $db->sql_server_info(true)); -} - -// Firebird update from Firebird 2.0 to 2.1+ required? -if ($db->sql_layer == 'firebird') -{ - // We do not trust any PHP5 function enabled, we will simply test for a function new in 2.1 - $db->sql_return_on_error(true); - - $sql = 'SELECT 1 FROM RDB$DATABASE - WHERE BIN_AND(10, 1) = 0'; - $result = $db->sql_query($sql); - - if (!$result || $db->sql_error_triggered) - { - echo '<br /><br />'; - echo '<h1>' . $lang['ERROR'] . '</h1><br />'; - - echo '<p>' . $lang['FIREBIRD_DBMS_UPDATE_REQUIRED'] . '</p>'; - - _print_footer(); - - exit_handler(); - exit; - } - - $db->sql_freeresult($result); - $db->sql_return_on_error(false); -} - -// MySQL update from MySQL 3.x/4.x to > 4.1.x required? -if ($db->sql_layer == 'mysql' || $db->sql_layer == 'mysql4' || $db->sql_layer == 'mysqli') -{ - // Verify by fetching column... if the column type matches the new type we update dbms_version... - $sql = "SHOW COLUMNS FROM " . CONFIG_TABLE; - $result = $db->sql_query($sql); - - $column_type = ''; - while ($row = $db->sql_fetchrow($result)) - { - $field = strtolower($row['Field']); - - if ($field == 'config_value') - { - $column_type = strtolower($row['Type']); - break; - } - } - $db->sql_freeresult($result); - - // If column type is blob, but mysql version says we are on > 4.1.3, then the schema needs an update - if (strpos($column_type, 'blob') !== false && version_compare($db->sql_server_info(true), '4.1.3', '>=')) - { - echo '<br /><br />'; - echo '<h1>' . $lang['ERROR'] . '</h1><br />'; - - echo '<p>' . sprintf($lang['MYSQL_SCHEMA_UPDATE_REQUIRED'], $config['dbms_version'], $db->sql_server_info(true)) . '</p>'; - - _print_footer(); - - exit_handler(); - exit; - } -} - -// Now check if the user wants to update from a version we no longer support updates from -if (version_compare($current_version, $oldest_from_version, '<')) -{ - echo '<br /><br /><h1>' . $lang['ERROR'] . '</h1><br />'; - echo '<p>' . sprintf($lang['DB_UPDATE_NOT_SUPPORTED'], $oldest_from_version, $current_version) . '</p>'; - - _print_footer(); - exit_handler(); - exit; -} - -// If the latest version and the current version are 'unequal', we will update the version_update_from, else we do not update anything. -if ($inline_update) -{ - if ($current_version !== $latest_version) - { - set_config('version_update_from', $orig_version); - } -} -else -{ - // If not called from the update script, we will actually remove the traces - $db->sql_query('DELETE FROM ' . CONFIG_TABLE . " WHERE config_name = 'version_update_from'"); -} - -// Schema updates -?> - <br /><br /> - - <h1><?php echo $lang['UPDATE_DATABASE_SCHEMA']; ?></h1> - - <br /> - <p><?php echo $lang['PROGRESS']; ?> :: <strong> - -<?php - -flush(); - -// We go through the schema changes from the lowest to the highest version -// We try to also include versions 'in-between'... -$no_updates = true; -$versions = array_keys($database_update_info); -for ($i = 0; $i < sizeof($versions); $i++) -{ - $version = $versions[$i]; - $schema_changes = $database_update_info[$version]; - - $next_version = (isset($versions[$i + 1])) ? $versions[$i + 1] : $updates_to_version; - - // If the installed version to be updated to is < than the current version, and if the current version is >= as the version to be updated to next, we will skip the process - if (version_compare($version, $current_version, '<') && version_compare($current_version, $next_version, '>=')) - { - continue; - } - - if (!sizeof($schema_changes)) - { - continue; - } - - $no_updates = false; - - // We run one index after the other... to be consistent with schema changes... - foreach ($schema_changes as $key => $changes) - { - $statements = $db_tools->perform_schema_changes(array($key => $changes)); - - foreach ($statements as $sql) - { - _sql($sql, $errored, $error_ary); - } - } -} - -_write_result($no_updates, $errored, $error_ary); - -// Data updates -$error_ary = array(); -$errored = $no_updates = false; - -?> - -<br /><br /> -<h1><?php echo $lang['UPDATING_DATA']; ?></h1> -<br /> -<p><?php echo $lang['PROGRESS']; ?> :: <strong> - -<?php - -flush(); - -$no_updates = true; -$versions = array_keys($database_update_info); - -// some code magic -for ($i = 0; $i < sizeof($versions); $i++) -{ - $version = $versions[$i]; - $next_version = (isset($versions[$i + 1])) ? $versions[$i + 1] : $updates_to_version; - - // If the installed version to be updated to is < than the current version, and if the current version is >= as the version to be updated to next, we will skip the process - if (version_compare($version, $current_version, '<') && version_compare($current_version, $next_version, '>=')) - { - continue; - } - - change_database_data($no_updates, $version); -} - -_write_result($no_updates, $errored, $error_ary); - -$error_ary = array(); -$errored = $no_updates = false; - -?> - -<br /><br /> -<h1><?php echo $lang['UPDATE_VERSION_OPTIMIZE']; ?></h1> -<br /> -<p><?php echo $lang['PROGRESS']; ?> :: <strong> - -<?php - -flush(); - -if ($debug_from_version === false) -{ - // update the version - $sql = "UPDATE " . CONFIG_TABLE . " - SET config_value = '$updates_to_version' - WHERE config_name = 'version'"; - _sql($sql, $errored, $error_ary); -} - -// Reset permissions -$sql = 'UPDATE ' . USERS_TABLE . " - SET user_permissions = '', - user_perm_from = 0"; -_sql($sql, $errored, $error_ary); - -// Update the dbms version if everything is ok... -set_config('dbms_version', $db->sql_server_info(true)); - -/* Optimize/vacuum analyze the tables where appropriate -// this should be done for each version in future along with -// the version number update -switch ($db->sql_layer) -{ - case 'mysql': - case 'mysqli': - case 'mysql4': - $sql = 'OPTIMIZE TABLE ' . $table_prefix . 'auth_access, ' . $table_prefix . 'banlist, ' . $table_prefix . 'categories, ' . $table_prefix . 'config, ' . $table_prefix . 'disallow, ' . $table_prefix . 'forum_prune, ' . $table_prefix . 'forums, ' . $table_prefix . 'groups, ' . $table_prefix . 'posts, ' . $table_prefix . 'posts_text, ' . $table_prefix . 'privmsgs, ' . $table_prefix . 'privmsgs_text, ' . $table_prefix . 'ranks, ' . $table_prefix . 'search_results, ' . $table_prefix . 'search_wordlist, ' . $table_prefix . 'search_wordmatch, ' . $table_prefix . 'sessions_keys' . $table_prefix . 'smilies, ' . $table_prefix . 'themes, ' . $table_prefix . 'themes_name, ' . $table_prefix . 'topics, ' . $table_prefix . 'topics_watch, ' . $table_prefix . 'user_group, ' . $table_prefix . 'users, ' . $table_prefix . 'vote_desc, ' . $table_prefix . 'vote_results, ' . $table_prefix . 'vote_voters, ' . $table_prefix . 'words'; - _sql($sql, $errored, $error_ary); - break; - - case 'postgresql': - _sql("VACUUM ANALYZE", $errored, $error_ary); - break; -} -*/ - -_write_result($no_updates, $errored, $error_ary); - -?> - -<br /> -<h1><?php echo $lang['UPDATE_COMPLETED']; ?></h1> - -<br /> - -<?php - -if (!$inline_update) -{ -?> - - <p style="color:red"><?php echo $lang['UPDATE_FILES_NOTICE']; ?></p> + <div id="wrap"> + <div id="page-header"> </div> - <p><?php echo $lang['COMPLETE_LOGIN_TO_BOARD']; ?></p> + <div id="page-body"> + <div id="acp"> + <div class="panel"> + <span class="corners-top"><span></span></span> + <div id="content"> + <div id="main" class="install-body"> -<?php -} -else -{ -?> + <h1><?php echo $user->lang['UPDATING_TO_LATEST_STABLE']; ?></h1> - <p><?php echo ((isset($lang['INLINE_UPDATE_SUCCESSFUL'])) ? $lang['INLINE_UPDATE_SUCCESSFUL'] : 'The database update was successful. Now you need to continue the update process.'); ?></p> + <br /> - <p><a href="<?php echo append_sid("{$phpbb_root_path}install/index.{$phpEx}", "mode=update&sub=file_check&language=$language"); ?>" class="button1"><?php echo (isset($lang['CONTINUE_UPDATE_NOW'])) ? $lang['CONTINUE_UPDATE_NOW'] : 'Continue the update process now'; ?></a></p> + <p><?php echo $user->lang['DATABASE_TYPE']; ?> :: <strong><?php echo $db->sql_layer; ?></strong><br /> + <?php echo $user->lang['PREVIOUS_VERSION']; ?> :: <strong><?php echo $config['version']; ?></strong><br /> <?php -} - -// Add database update to log -add_log('admin', 'LOG_UPDATE_DATABASE', $orig_version, $updates_to_version); - -// Now we purge the session table as well as all cache files -$cache->purge(); - -_print_footer(); - -garbage_collection(); - -if (function_exists('exit_handler')) -{ - exit_handler(); -} /** -* Print out footer +* @todo firebird/mysql update? */ -function _print_footer() -{ - echo <<<EOF - </div> - </div> - <span class="corners-bottom"><span></span></span> - </div> - </div> - </div> - - <div id="page-footer"> - Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group - </div> -</div> -</body> -</html> -EOF; +// End startup code + +// Make sure migrations have been installed. +$db_tools = $phpbb_container->get('dbal.tools'); +if (!$db_tools->sql_table_exists($table_prefix . 'migrations')) +{ + $db_tools->sql_create_table($table_prefix . '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', + )); } -/** -* Function for triggering an sql statement -*/ -function _sql($sql, &$errored, &$error_ary, $echo_dot = true) -{ - global $db; - - if (defined('DEBUG')) - { - echo "<br />\n{$sql}\n<br />"; - } +$migrator = $phpbb_container->get('migrator'); +$migrator->load_migrations($phpbb_root_path . 'includes/db/migration/data/'); - $db->sql_return_on_error(true); +// What is a safe limit of execution time? Half the max execution time should be safe. +$safe_time_limit = (ini_get('max_execution_time') / 2); - if ($sql === 'begin') - { - $result = $db->sql_transaction('begin'); - } - else if ($sql === 'commit') - { - $result = $db->sql_transaction('commit'); - } - else - { - $result = $db->sql_query($sql); - if ($db->sql_error_triggered) - { - $errored = true; - $error_ary['sql'][] = $db->sql_error_sql; - $error_ary['error_code'][] = $db->sql_error_returned; - } - } - - $db->sql_return_on_error(false); - - if ($echo_dot) - { - echo ". \n"; - flush(); - } - - return $result; -} - -function _write_result($no_updates, $errored, $error_ary) +while (!$migrator->finished()) { - global $lang; - - if ($no_updates) + try { - echo ' ' . $lang['NO_UPDATES_REQUIRED'] . '</strong></p>'; + $migrator->update(); } - else + catch (phpbb_db_migration_exception $e) { - echo ' <span class="success">' . $lang['DONE'] . '</span></strong><br />' . $lang['RESULT'] . ' :: '; + echo $e->getLocalisedMessage($user); - if ($errored) - { - echo ' <strong>' . $lang['SOME_QUERIES_FAILED'] . '</strong> <ul>'; - - for ($i = 0; $i < sizeof($error_ary['sql']); $i++) - { - echo '<li>' . $lang['ERROR'] . ' :: <strong>' . htmlspecialchars($error_ary['error_code'][$i]['message']) . '</strong><br />'; - echo $lang['SQL'] . ' :: <strong>' . htmlspecialchars($error_ary['sql'][$i]) . '</strong><br /><br /></li>'; - } - - echo '</ul> <br /><br />' . $lang['SQL_FAILURE_EXPLAIN'] . '</p>'; - } - else - { - echo '<strong>' . $lang['NO_ERRORS'] . '</strong></p>'; - } + phpbb_end_update($cache); } -} -function _add_modules($modules_to_install) -{ - global $phpbb_root_path, $phpEx, $db, $phpbb_extension_manager, $config; + echo $migrator->last_run_migration['name'] . '<br />'; - // modules require an extension manager - if (empty($phpbb_extension_manager)) + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $update_start_time) >= $safe_time_limit) { - $phpbb_extension_manager = new phpbb_extension_manager($db, $config, EXT_TABLE, $phpbb_root_path, ".$phpEx"); - } - - include_once($phpbb_root_path . 'includes/acp/acp_modules.' . $phpEx); + //echo '<meta http-equiv="refresh" content="0;url=' . str_replace('&', '&', append_sid($phpbb_root_path . 'test.' . $phpEx)) . '" />'; + echo $user->lang['DATABASE_UPDATE_NOT_COMPLETED'] . '<br />'; + echo '<a href="' . append_sid($phpbb_root_path . 'test.' . $phpEx) . '">' . $user->lang['DATABASE_UPDATE_CONTINUE'] . '</a>'; - $_module = new acp_modules(); - - foreach ($modules_to_install as $module_mode => $module_data) - { - $_module->module_class = $module_data['class']; - - // Determine parent id first - $sql = 'SELECT module_id - FROM ' . MODULES_TABLE . " - WHERE module_class = '" . $db->sql_escape($module_data['class']) . "' - AND module_langname = '" . $db->sql_escape($module_data['cat']) . "' - AND module_mode = '' - AND module_basename = ''"; - $result = $db->sql_query($sql); - - // There may be more than one categories with the same name - $categories = array(); - while ($row = $db->sql_fetchrow($result)) - { - $categories[] = (int) $row['module_id']; - } - $db->sql_freeresult($result); - - if (!sizeof($categories)) - { - continue; - } - - // Add the module to all categories found - foreach ($categories as $parent_id) - { - // Check if the module already exists - $sql = 'SELECT * - FROM ' . MODULES_TABLE . " - WHERE module_basename = '" . $db->sql_escape($module_data['base']) . "' - AND module_class = '" . $db->sql_escape($module_data['class']) . "' - AND module_langname = '" . $db->sql_escape($module_data['title']) . "' - AND module_mode = '" . $db->sql_escape($module_mode) . "' - AND module_auth = '" . $db->sql_escape($module_data['auth']) . "' - AND parent_id = {$parent_id}"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // If it exists, we simply continue with the next category - if ($row) - { - continue; - } - - // Build the module sql row - $module_row = array( - 'module_basename' => $module_data['base'], - 'module_enabled' => (isset($module_data['enabled'])) ? (int) $module_data['enabled'] : 1, - 'module_display' => (isset($module_data['display'])) ? (int) $module_data['display'] : 1, - 'parent_id' => $parent_id, - 'module_class' => $module_data['class'], - 'module_langname' => $module_data['title'], - 'module_mode' => $module_mode, - 'module_auth' => $module_data['auth'], - ); - - $_module->update_module_data($module_row, true); - - // Ok, do we need to re-order the module, move it up or down? - if (!isset($module_data['after'])) - { - continue; - } - - $after_mode = $module_data['after'][0]; - $after_langname = $module_data['after'][1]; - - // First of all, get the module id for the module this one has to be placed after - $sql = 'SELECT left_id - FROM ' . MODULES_TABLE . " - WHERE module_class = '" . $db->sql_escape($module_data['class']) . "' - AND module_basename = '" . $db->sql_escape($module_data['base']) . "' - AND module_langname = '" . $db->sql_escape($after_langname) . "' - AND module_mode = '" . $db->sql_escape($after_mode) . "' - AND parent_id = '{$parent_id}'"; - $result = $db->sql_query($sql); - $first_left_id = (int) $db->sql_fetchfield('left_id'); - $db->sql_freeresult($result); - - if (!$first_left_id) - { - continue; - } - - // Ok, count the number of modules between $after_mode and the added module - $sql = 'SELECT COUNT(module_id) as num_modules - FROM ' . MODULES_TABLE . " - WHERE module_class = '" . $db->sql_escape($module_data['class']) . "' - AND parent_id = {$parent_id} - AND left_id BETWEEN {$first_left_id} AND {$module_row['left_id']}"; - $result = $db->sql_query($sql); - $steps = (int) $db->sql_fetchfield('num_modules'); - $db->sql_freeresult($result); - - // We need to substract 2 - $steps -= 2; - - if ($steps <= 0) - { - continue; - } - - // Ok, move module up $num_modules times. ;) - $_module->move_module_by($module_row, 'move_up', $steps); - } + phpbb_end_update($cache); } - - $_module->remove_cache_file(); } -/** -* Add a new permission, optionally copy permission setting from another -* -* @param auth_admin $auth_admin auth_admin object -* @param phpbb_db_driver $db Database object -* @param string $permission_name Name of the permission to add -* @param bool $is_global True is global, false is local -* @param string $copy_from Optional permission name from which to copy -* @return bool true on success, false on failure -*/ -function _add_permission(auth_admin $auth_admin, phpbb_db_driver $db, $permission_name, $is_global = true, $copy_from = '') +if ($orig_version != $config['version']) { - // Only add a permission that don't already exist - if (!empty($auth_admin->acl_options['id'][$permission_name])) - { - return true; - } - - $permission_scope = $is_global ? 'global' : 'local'; - - $result = $auth_admin->acl_add_option(array( - $permission_scope => array($permission_name), - )); - - if (!$result) - { - return $result; - } - - // The permission has been added, now we can copy it if needed - if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) - { - $old_id = $auth_admin->acl_options['id'][$copy_from]; - $new_id = $auth_admin->acl_options['id'][$permission_name]; - - $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); - - foreach ($tables as $table) - { - $sql = 'SELECT * - FROM ' . $table . ' - WHERE auth_option_id = ' . $old_id; - $result = _sql($sql, $errored, $error_ary); - - $sql_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - $row['auth_option_id'] = $new_id; - $sql_ary[] = $row; - } - $db->sql_freeresult($result); - - if (sizeof($sql_ary)) - { - $db->sql_multi_insert($table, $sql_ary); - } - } - - $auth_admin->acl_clear_prefetch(); - } - - return true; + add_log('admin', 'LOG_UPDATE_DATABASE', $orig_version, $config['version']); } -/**************************************************************************** -* ADD YOUR DATABASE SCHEMA CHANGES HERE * -*****************************************************************************/ -function database_update_info() -{ - return array( - // Changes from 3.0.0 to the next version - '3.0.0' => array( - // Add the following columns - 'add_columns' => array( - FORUMS_TABLE => array( - 'display_subforum_list' => array('BOOL', 1), - ), - SESSIONS_TABLE => array( - 'session_forum_id' => array('UINT', 0), - ), - ), - 'drop_keys' => array( - GROUPS_TABLE => array('group_legend'), - ), - 'add_index' => array( - SESSIONS_TABLE => array( - 'session_forum_id' => array('session_forum_id'), - ), - GROUPS_TABLE => array( - 'group_legend_name' => array('group_legend', 'group_name'), - ), - ), - ), - // No changes from 3.0.1-RC1 to 3.0.1 - '3.0.1-RC1' => array(), - // No changes from 3.0.1 to 3.0.2-RC1 - '3.0.1' => array(), - // Changes from 3.0.2-RC1 to 3.0.2-RC2 - '3.0.2-RC1' => array( - 'change_columns' => array( - DRAFTS_TABLE => array( - 'draft_subject' => array('STEXT_UNI', ''), - ), - FORUMS_TABLE => array( - 'forum_last_post_subject' => array('STEXT_UNI', ''), - ), - POSTS_TABLE => array( - 'post_subject' => array('STEXT_UNI', '', 'true_sort'), - ), - PRIVMSGS_TABLE => array( - 'message_subject' => array('STEXT_UNI', ''), - ), - TOPICS_TABLE => array( - 'topic_title' => array('STEXT_UNI', '', 'true_sort'), - 'topic_last_post_subject' => array('STEXT_UNI', ''), - ), - ), - 'drop_keys' => array( - SESSIONS_TABLE => array('session_forum_id'), - ), - 'add_index' => array( - SESSIONS_TABLE => array( - 'session_fid' => array('session_forum_id'), - ), - ), - ), - // No changes from 3.0.2-RC2 to 3.0.2 - '3.0.2-RC2' => array(), - - // Changes from 3.0.2 to 3.0.3-RC1 - '3.0.2' => array( - // Add the following columns - 'add_columns' => array( - STYLES_TEMPLATE_TABLE => array( - 'template_inherits_id' => array('UINT:4', 0), - 'template_inherit_path' => array('VCHAR', ''), - ), - GROUPS_TABLE => array( - 'group_max_recipients' => array('UINT', 0), - ), - ), - ), - - // No changes from 3.0.3-RC1 to 3.0.3 - '3.0.3-RC1' => array(), - - // Changes from 3.0.3 to 3.0.4-RC1 - '3.0.3' => array( - 'add_columns' => array( - PROFILE_FIELDS_TABLE => array( - 'field_show_profile' => array('BOOL', 0), - ), - ), - 'change_columns' => array( - STYLES_TABLE => array( - 'style_id' => array('UINT', NULL, 'auto_increment'), - 'template_id' => array('UINT', 0), - 'theme_id' => array('UINT', 0), - 'imageset_id' => array('UINT', 0), - ), - STYLES_IMAGESET_TABLE => array( - 'imageset_id' => array('UINT', NULL, 'auto_increment'), - ), - STYLES_IMAGESET_DATA_TABLE => array( - 'image_id' => array('UINT', NULL, 'auto_increment'), - 'imageset_id' => array('UINT', 0), - ), - STYLES_THEME_TABLE => array( - 'theme_id' => array('UINT', NULL, 'auto_increment'), - ), - STYLES_TEMPLATE_TABLE => array( - 'template_id' => array('UINT', NULL, 'auto_increment'), - ), - STYLES_TEMPLATE_DATA_TABLE => array( - 'template_id' => array('UINT', 0), - ), - FORUMS_TABLE => array( - 'forum_style' => array('UINT', 0), - ), - USERS_TABLE => array( - 'user_style' => array('UINT', 0), - ), - ), - ), - - // Changes from 3.0.4-RC1 to 3.0.4 - '3.0.4-RC1' => array(), - - // Changes from 3.0.4 to 3.0.5-RC1 - '3.0.4' => array( - 'change_columns' => array( - FORUMS_TABLE => array( - 'forum_style' => array('UINT', 0), - ), - ), - ), - - // No changes from 3.0.5-RC1 to 3.0.5 - '3.0.5-RC1' => array(), - - // Changes from 3.0.5 to 3.0.6-RC1 - '3.0.5' => array( - 'add_columns' => array( - CONFIRM_TABLE => array( - 'attempts' => array('UINT', 0), - ), - USERS_TABLE => array( - 'user_new' => array('BOOL', 1), - 'user_reminded' => array('TINT:4', 0), - 'user_reminded_time'=> array('TIMESTAMP', 0), - ), - GROUPS_TABLE => array( - 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), - ), - PRIVMSGS_TABLE => array( - 'message_reported' => array('BOOL', 0), - ), - REPORTS_TABLE => array( - 'pm_id' => array('UINT', 0), - ), - PROFILE_FIELDS_TABLE => array( - 'field_show_on_vt' => array('BOOL', 0), - ), - FORUMS_TABLE => array( - 'forum_options' => array('UINT:20', 0), - ), - ), - 'change_columns' => array( - USERS_TABLE => array( - 'user_options' => array('UINT:11', 230271), - ), - ), - 'add_index' => array( - REPORTS_TABLE => array( - 'post_id' => array('post_id'), - 'pm_id' => array('pm_id'), - ), - POSTS_TABLE => array( - 'post_username' => array('post_username:255'), - ), - ), - ), - - // No changes from 3.0.6-RC1 to 3.0.6-RC2 - '3.0.6-RC1' => array(), - // No changes from 3.0.6-RC2 to 3.0.6-RC3 - '3.0.6-RC2' => array(), - // No changes from 3.0.6-RC3 to 3.0.6-RC4 - '3.0.6-RC3' => array(), - // No changes from 3.0.6-RC4 to 3.0.6 - '3.0.6-RC4' => array(), - - // Changes from 3.0.6 to 3.0.7-RC1 - '3.0.6' => array( - 'drop_keys' => array( - LOG_TABLE => array('log_time'), - ), - 'add_index' => array( - TOPICS_TRACK_TABLE => array( - 'topic_id' => array('topic_id'), - ), - ), - ), - - // No changes from 3.0.7-RC1 to 3.0.7-RC2 - '3.0.7-RC1' => array(), - // No changes from 3.0.7-RC2 to 3.0.7 - '3.0.7-RC2' => array(), - // No changes from 3.0.7 to 3.0.7-PL1 - '3.0.7' => array(), - // No changes from 3.0.7-PL1 to 3.0.8-RC1 - '3.0.7-PL1' => array(), - // No changes from 3.0.8-RC1 to 3.0.8 - '3.0.8-RC1' => array(), - // Changes from 3.0.8 to 3.0.9-RC1 - '3.0.8' => array( - 'add_tables' => array( - LOGIN_ATTEMPT_TABLE => array( - 'COLUMNS' => array( - // this column was removed from the database updater - // after 3.0.9-RC3 was released. It might still exist - // in 3.0.9-RCX installations and has to be dropped in - // 3.0.12 after the db_tools class is capable of properly - // removing a primary key. - // 'attempt_id' => array('UINT', NULL, 'auto_increment'), - 'attempt_ip' => array('VCHAR:40', ''), - 'attempt_browser' => array('VCHAR:150', ''), - 'attempt_forwarded_for' => array('VCHAR:255', ''), - 'attempt_time' => array('TIMESTAMP', 0), - 'user_id' => array('UINT', 0), - 'username' => array('VCHAR_UNI:255', 0), - 'username_clean' => array('VCHAR_CI', 0), - ), - //'PRIMARY_KEY' => 'attempt_id', - 'KEYS' => array( - 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), - 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), - 'att_time' => array('INDEX', array('attempt_time')), - 'user_id' => array('INDEX', 'user_id'), - ), - ), - ), - 'change_columns' => array( - BBCODES_TABLE => array( - 'bbcode_id' => array('USINT', 0), - ), - ), - ), - // No changes from 3.0.9-RC1 to 3.0.9-RC2 - '3.0.9-RC1' => array(), - // No changes from 3.0.9-RC2 to 3.0.9-RC3 - '3.0.9-RC2' => array(), - // No changes from 3.0.9-RC3 to 3.0.9-RC4 - '3.0.9-RC3' => array(), - // No changes from 3.0.9-RC4 to 3.0.9 - '3.0.9-RC4' => array(), - // No changes from 3.0.9 to 3.0.10-RC1 - '3.0.9' => array(), - // No changes from 3.0.10-RC1 to 3.0.10-RC2 - '3.0.10-RC1' => array(), - // No changes from 3.0.10-RC2 to 3.0.10-RC3 - '3.0.10-RC2' => array(), - // No changes from 3.0.10-RC3 to 3.0.10 - '3.0.10-RC3' => array(), - // No changes from 3.0.10 to 3.0.11-RC1 - '3.0.10' => array(), - // Changes from 3.0.11-RC1 to 3.0.11-RC2 - '3.0.11-RC1' => array( - 'add_columns' => array( - PROFILE_FIELDS_TABLE => array( - 'field_show_novalue' => array('BOOL', 0), - ), - ), - ), - // No changes from 3.0.11-RC2 to 3.0.11 - '3.0.11-RC2' => array(), - // No changes from 3.0.11 to 3.0.12-RC1 - '3.0.11' => array(), - - /** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.12-RC1 */ - - // Changes from 3.1.0-dev to 3.1.0-A1 - '3.1.0-dev' => array( - 'add_tables' => array( - EXT_TABLE => array( - 'COLUMNS' => array( - 'ext_name' => array('VCHAR', ''), - 'ext_active' => array('BOOL', 0), - 'ext_state' => array('TEXT', ''), - ), - 'KEYS' => array( - 'ext_name' => array('UNIQUE', 'ext_name'), - ), - ), - ), - 'add_columns' => array( - GROUPS_TABLE => array( - 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), - ), - PROFILE_FIELDS_TABLE => array( - 'field_show_on_pm' => array('BOOL', 0), - ), - STYLES_TABLE => array( - 'style_path' => array('VCHAR:100', ''), - 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), - 'style_parent_id' => array('UINT:4', 0), - 'style_parent_tree' => array('TEXT', ''), - ), - REPORTS_TABLE => array( - 'reported_post_text' => array('MTEXT_UNI', ''), - 'reported_post_uid' => array('VCHAR:8', ''), - 'reported_post_bitfield' => array('VCHAR:255', ''), - ), - ), - 'change_columns' => array( - GROUPS_TABLE => array( - 'group_legend' => array('UINT', 0), - ), - USERS_TABLE => array( - 'user_timezone' => array('VCHAR:100', ''), - ), - ), - ), - ); -} - -/**************************************************************************** -* ADD YOUR DATABASE DATA CHANGES HERE * -* REMEMBER: You NEED to enter a schema array above and a data array here, * -* even if both or one of them are empty. * -*****************************************************************************/ -function change_database_data(&$no_updates, $version) -{ - global $db, $errored, $error_ary, $config, $phpbb_root_path, $phpEx, $db_tools; - - $update_helpers = new phpbb_update_helpers(); - - switch ($version) - { - case '3.0.0': - - $sql = 'UPDATE ' . TOPICS_TABLE . " - SET topic_last_view_time = topic_last_post_time - WHERE topic_last_view_time = 0"; - _sql($sql, $errored, $error_ary); - - // Update smiley sizes - $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); - - foreach ($smileys as $smiley) - { - if (file_exists($phpbb_root_path . 'images/smilies/' . $smiley)) - { - list($width, $height) = getimagesize($phpbb_root_path . 'images/smilies/' . $smiley); - - $sql = 'UPDATE ' . SMILIES_TABLE . ' - SET smiley_width = ' . $width . ', smiley_height = ' . $height . " - WHERE smiley_url = '" . $db->sql_escape($smiley) . "'"; - - _sql($sql, $errored, $error_ary); - } - } - - $no_updates = false; - break; - - // No changes from 3.0.1-RC1 to 3.0.1 - case '3.0.1-RC1': - break; - - // changes from 3.0.1 to 3.0.2-RC1 - case '3.0.1': - - set_config('referer_validation', '1'); - set_config('check_attachment_content', '1'); - set_config('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title'); - - $no_updates = false; - break; - - // No changes from 3.0.2-RC1 to 3.0.2-RC2 - case '3.0.2-RC1': - break; - - // No changes from 3.0.2-RC2 to 3.0.2 - case '3.0.2-RC2': - break; - - // Changes from 3.0.2 to 3.0.3-RC1 - case '3.0.2': - set_config('enable_queue_trigger', '0'); - set_config('queue_trigger_posts', '3'); - - set_config('pm_max_recipients', '0'); - - // Set maximum number of recipients for the registered users, bots, guests group - $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 - WHERE ' . $db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); - _sql($sql, $errored, $error_ary); - - // Not prefilling yet - set_config('dbms_version', ''); - - // Add new permission u_masspm_group and duplicate settings from u_masspm - include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); - $auth_admin = new auth_admin(); - - // Only add the new permission if it does not already exist - if (empty($auth_admin->acl_options['id']['u_masspm_group'])) - { - $auth_admin->acl_add_option(array('global' => array('u_masspm_group'))); - - // Now the tricky part, filling the permission - $old_id = $auth_admin->acl_options['id']['u_masspm']; - $new_id = $auth_admin->acl_options['id']['u_masspm_group']; - - $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); - - foreach ($tables as $table) - { - $sql = 'SELECT * - FROM ' . $table . ' - WHERE auth_option_id = ' . $old_id; - $result = _sql($sql, $errored, $error_ary); - - $sql_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - $row['auth_option_id'] = $new_id; - $sql_ary[] = $row; - } - $db->sql_freeresult($result); - - if (sizeof($sql_ary)) - { - $db->sql_multi_insert($table, $sql_ary); - } - } - - // Remove any old permission entries - $auth_admin->acl_clear_prefetch(); - } - - /** - * Do not resync post counts here. An admin may do this later from the ACP - $start = 0; - $step = ($config['num_posts']) ? (max((int) ($config['num_posts'] / 5), 20000)) : 20000; - - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_posts = 0'; - _sql($sql, $errored, $error_ary); - - do - { - $sql = 'SELECT COUNT(post_id) AS num_posts, poster_id - FROM ' . POSTS_TABLE . ' - WHERE post_id BETWEEN ' . ($start + 1) . ' AND ' . ($start + $step) . ' - AND post_postcount = 1 AND post_approved = 1 - GROUP BY poster_id'; - $result = _sql($sql, $errored, $error_ary); - - if ($row = $db->sql_fetchrow($result)) - { - do - { - $sql = 'UPDATE ' . USERS_TABLE . " SET user_posts = user_posts + {$row['num_posts']} WHERE user_id = {$row['poster_id']}"; - _sql($sql, $errored, $error_ary); - } - while ($row = $db->sql_fetchrow($result)); - - $start += $step; - } - else - { - $start = 0; - } - $db->sql_freeresult($result); - } - while ($start); - */ - - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_auth = \'acl_a_email && cfg_email_enable\' - WHERE module_class = \'acp\' - AND module_basename = \'email\''; - _sql($sql, $errored, $error_ary); - - $no_updates = false; - break; - - // Changes from 3.0.3-RC1 to 3.0.3 - case '3.0.3-RC1': - if ($db->sql_layer == 'oracle') - { - // log_operation is CLOB - but we can change this later - $sql = 'UPDATE ' . LOG_TABLE . " - SET log_operation = 'LOG_DELETE_TOPIC' - WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; - _sql($sql, $errored, $error_ary); - } - else - { - $sql = 'UPDATE ' . LOG_TABLE . " - SET log_operation = 'LOG_DELETE_TOPIC' - WHERE log_operation = 'LOG_TOPIC_DELETED'"; - _sql($sql, $errored, $error_ary); - } - - $no_updates = false; - break; - - // Changes from 3.0.3 to 3.0.4-RC1 - case '3.0.3': - // Update the Custom Profile Fields based on previous settings to the new format - $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide - FROM ' . PROFILE_FIELDS_TABLE; - $result = _sql($sql, $errored, $error_ary); - - while ($row = $db->sql_fetchrow($result)) - { - $sql_ary = array( - 'field_required' => 0, - 'field_show_on_reg' => 0, - 'field_hide' => 0, - 'field_show_profile'=> 0, - ); - - if ($row['field_required']) - { - $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; - } - else if ($row['field_show_on_reg']) - { - $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; - } - else if ($row['field_hide']) - { - // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module - $sql_ary['field_hide'] = 1; - } - else - { - // equivelant to "none", which is the "Display in user control panel" option - $sql_ary['field_show_profile'] = 1; - } - - _sql('UPDATE ' . PROFILE_FIELDS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); - } - $no_updates = false; - - break; - - // Changes from 3.0.4-RC1 to 3.0.4 - case '3.0.4-RC1': - break; - - // Changes from 3.0.4 to 3.0.5-RC1 - case '3.0.4': - - // Captcha config variables - set_config('captcha_gd_wave', 0); - set_config('captcha_gd_3d_noise', 1); - set_config('captcha_gd_fonts', 1); - set_config('confirm_refresh', 1); - - // Maximum number of keywords - set_config('max_num_search_keywords', 10); - - // Remove static config var and put it back as dynamic variable - $sql = 'UPDATE ' . CONFIG_TABLE . " - SET is_dynamic = 1 - WHERE config_name = 'search_indexing_state'"; - _sql($sql, $errored, $error_ary); - - // Hash old MD5 passwords - $sql = 'SELECT user_id, user_password - FROM ' . USERS_TABLE . ' - WHERE user_pass_convert = 1'; - $result = _sql($sql, $errored, $error_ary); - - while ($row = $db->sql_fetchrow($result)) - { - if (strlen($row['user_password']) == 32) - { - $sql_ary = array( - 'user_password' => phpbb_hash($row['user_password']), - ); - - _sql('UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id'], $errored, $error_ary); - } - } - $db->sql_freeresult($result); - - // Adjust bot entry - $sql = 'UPDATE ' . BOTS_TABLE . " - SET bot_agent = 'ichiro/' - WHERE bot_agent = 'ichiro/2'"; - _sql($sql, $errored, $error_ary); - - - // Before we are able to add a unique key to auth_option, we need to remove duplicate entries - - // We get duplicate entries first - $sql = 'SELECT auth_option - FROM ' . ACL_OPTIONS_TABLE . ' - GROUP BY auth_option - HAVING COUNT(*) >= 2'; - $result = $db->sql_query($sql); - - $auth_options = array(); - while ($row = $db->sql_fetchrow($result)) - { - $auth_options[] = $row['auth_option']; - } - $db->sql_freeresult($result); - - // Remove specific auth options - if (!empty($auth_options)) - { - foreach ($auth_options as $option) - { - // Select auth_option_ids... the largest id will be preserved - $sql = 'SELECT auth_option_id - FROM ' . ACL_OPTIONS_TABLE . " - WHERE auth_option = '" . $db->sql_escape($option) . "' - ORDER BY auth_option_id DESC"; - // sql_query_limit not possible here, due to bug in postgresql layer - $result = $db->sql_query($sql); - - // Skip first row, this is our original auth option we want to preserve - $row = $db->sql_fetchrow($result); - - while ($row = $db->sql_fetchrow($result)) - { - // Ok, remove this auth option... - _sql('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary); - _sql('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary); - _sql('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary); - _sql('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary); - } - $db->sql_freeresult($result); - } - } - - // Now make auth_option UNIQUE, by dropping the old index and adding a UNIQUE one. - $changes = array( - 'drop_keys' => array( - ACL_OPTIONS_TABLE => array('auth_option'), - ), - ); - - global $db_tools; - - $statements = $db_tools->perform_schema_changes($changes); - - foreach ($statements as $sql) - { - _sql($sql, $errored, $error_ary); - } - - $changes = array( - 'add_unique_index' => array( - ACL_OPTIONS_TABLE => array( - 'auth_option' => array('auth_option'), - ), - ), - ); - - $statements = $db_tools->perform_schema_changes($changes); - - foreach ($statements as $sql) - { - _sql($sql, $errored, $error_ary); - } - - $no_updates = false; - - break; - - // No changes from 3.0.5-RC1 to 3.0.5 - case '3.0.5-RC1': - break; - - // Changes from 3.0.5 to 3.0.6-RC1 - case '3.0.5': - // Let's see if the GD Captcha can be enabled... we simply look for what *is* enabled... - if (!empty($config['captcha_gd']) && !isset($config['captcha_plugin'])) - { - set_config('captcha_plugin', 'phpbb_captcha_gd'); - } - else if (!isset($config['captcha_plugin'])) - { - set_config('captcha_plugin', 'phpbb_captcha_nogd'); - } - - // Entries for the Feed Feature - set_config('feed_enable', '0'); - set_config('feed_limit', '10'); - - set_config('feed_overall_forums', '1'); - set_config('feed_overall_forums_limit', '15'); - - set_config('feed_overall_topics', '0'); - set_config('feed_overall_topics_limit', '15'); - - set_config('feed_forum', '1'); - set_config('feed_topic', '1'); - set_config('feed_item_statistics', '1'); - - // Entries for smiley pagination - set_config('smilies_per_page', '50'); - - // Entry for reporting PMs - set_config('allow_pm_report', '1'); - - // Install modules - $modules_to_install = array( - 'feed' => array( - 'base' => 'board', - 'class' => 'acp', - 'title' => 'ACP_FEED_SETTINGS', - 'auth' => 'acl_a_board', - 'cat' => 'ACP_BOARD_CONFIGURATION', - 'after' => array('signature', 'ACP_SIGNATURE_SETTINGS') - ), - 'warnings' => array( - 'base' => 'users', - 'class' => 'acp', - 'title' => 'ACP_USER_WARNINGS', - 'auth' => 'acl_a_user', - 'display' => 0, - 'cat' => 'ACP_CAT_USERS', - 'after' => array('feedback', 'ACP_USER_FEEDBACK') - ), - 'send_statistics' => array( - 'base' => 'send_statistics', - 'class' => 'acp', - 'title' => 'ACP_SEND_STATISTICS', - 'auth' => 'acl_a_server', - 'cat' => 'ACP_SERVER_CONFIGURATION' - ), - 'setting_forum_copy' => array( - 'base' => 'permissions', - 'class' => 'acp', - 'title' => 'ACP_FORUM_PERMISSIONS_COPY', - 'auth' => 'acl_a_fauth && acl_a_authusers && acl_a_authgroups && acl_a_mauth', - 'cat' => 'ACP_FORUM_BASED_PERMISSIONS', - 'after' => array('setting_forum_local', 'ACP_FORUM_PERMISSIONS') - ), - 'pm_reports' => array( - 'base' => 'pm_reports', - 'class' => 'mcp', - 'title' => 'MCP_PM_REPORTS_OPEN', - 'auth' => 'aclf_m_report', - 'cat' => 'MCP_REPORTS' - ), - 'pm_reports_closed' => array( - 'base' => 'pm_reports', - 'class' => 'mcp', - 'title' => 'MCP_PM_REPORTS_CLOSED', - 'auth' => 'aclf_m_report', - 'cat' => 'MCP_REPORTS' - ), - 'pm_report_details' => array( - 'base' => 'pm_reports', - 'class' => 'mcp', - 'title' => 'MCP_PM_REPORT_DETAILS', - 'auth' => 'aclf_m_report', - 'cat' => 'MCP_REPORTS' - ), - ); - - _add_modules($modules_to_install); - - // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = 'NEWLY_REGISTERED'"; - $result = $db->sql_query($sql); - $group_id = (int) $db->sql_fetchfield('group_id'); - $db->sql_freeresult($result); - - if (!$group_id) - { - $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; - _sql($sql, $errored, $error_ary); - - $group_id = $db->sql_nextid(); - } - - // Insert new user role... at the end of the chain - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = 'ROLE_USER_NEW_MEMBER' - AND role_type = 'u_'"; - $result = $db->sql_query($sql); - $u_role = (int) $db->sql_fetchfield('role_id'); - $db->sql_freeresult($result); - - if (!$u_role) - { - $sql = 'SELECT MAX(role_order) as max_order_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_type = 'u_'"; - $result = $db->sql_query($sql); - $next_order_id = (int) $db->sql_fetchfield('max_order_id'); - $db->sql_freeresult($result); - - $next_order_id++; - - $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; - _sql($sql, $errored, $error_ary); - $u_role = $db->sql_nextid(); - - if (!$errored) - { - // Now add the correct data to the roles... - // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups - $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; - _sql($sql, $errored, $error_ary); - - // Add user role to group - $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; - _sql($sql, $errored, $error_ary); - } - } - - // Insert new forum role - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' - AND role_type = 'f_'"; - $result = $db->sql_query($sql); - $f_role = (int) $db->sql_fetchfield('role_id'); - $db->sql_freeresult($result); - - if (!$f_role) - { - $sql = 'SELECT MAX(role_order) as max_order_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_type = 'f_'"; - $result = $db->sql_query($sql); - $next_order_id = (int) $db->sql_fetchfield('max_order_id'); - $db->sql_freeresult($result); - - $next_order_id++; - - $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; - _sql($sql, $errored, $error_ary); - $f_role = $db->sql_nextid(); - - if (!$errored) - { - $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; - _sql($sql, $errored, $error_ary); - } - } - - // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) - $sql = 'SELECT 1 - FROM ' . USERS_TABLE . ' - WHERE user_new = 0'; - $result = $db->sql_query_limit($sql, 1); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$row) - { - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; - _sql($sql, $errored, $error_ary); - } - - // Newly registered users limit - if (!isset($config['new_member_post_limit'])) - { - set_config('new_member_post_limit', (!empty($config['enable_queue_trigger'])) ? $config['queue_trigger_posts'] : 0); - } - - if (!isset($config['new_member_group_default'])) - { - set_config('new_member_group_default', 0); - } - - // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... - // Check if the role is already assigned... - $sql = 'SELECT forum_id - FROM ' . ACL_GROUPS_TABLE . ' - WHERE group_id = ' . $group_id . ' - AND auth_role_id = ' . $f_role; - $result = $db->sql_query($sql); - $is_options = (int) $db->sql_fetchfield('forum_id'); - $db->sql_freeresult($result); - - // Not assigned at all... :/ - if (!$is_options) - { - // Get postable forums - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE forum_type != ' . FORUM_LINK; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - _sql('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)', $errored, $error_ary); - } - $db->sql_freeresult($result); - } - - // Clear permissions... - include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); - $auth_admin = new auth_admin(); - $auth_admin->acl_clear_prefetch(); - - if (!isset($config['allow_avatar'])) - { - if ($config['allow_avatar_upload'] || $config['allow_avatar_local'] || $config['allow_avatar_remote']) - { - set_config('allow_avatar', '1'); - } - else - { - set_config('allow_avatar', '0'); - } - } - - if (!isset($config['allow_avatar_remote_upload'])) - { - if ($config['allow_avatar_remote'] && $config['allow_avatar_upload']) - { - set_config('allow_avatar_remote_upload', '1'); - } - else - { - set_config('allow_avatar_remote_upload', '0'); - } - } - - // Minimum number of characters - if (!isset($config['min_post_chars'])) - { - set_config('min_post_chars', '1'); - } - - if (!isset($config['allow_quick_reply'])) - { - set_config('allow_quick_reply', '1'); - } - - // Set every members user_options column to enable - // bbcode, smilies and URLs for signatures by default - $sql = 'SELECT user_options - FROM ' . USERS_TABLE . ' - WHERE user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; - $result = $db->sql_query_limit($sql, 1); - $user_option = (int) $db->sql_fetchfield('user_options'); - $db->sql_freeresult($result); - - // Check if we already updated the database by checking bit 15 which we used to store the sig_bbcode option - if (!($user_option & 1 << 15)) - { - // 229376 is the added value to enable all three signature options - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; - _sql($sql, $errored, $error_ary); - } - - if (!isset($config['delete_time'])) - { - set_config('delete_time', $config['edit_time']); - } - - $no_updates = false; - break; - - // No changes from 3.0.6-RC1 to 3.0.6-RC2 - case '3.0.6-RC1': - break; - - // Changes from 3.0.6-RC2 to 3.0.6-RC3 - case '3.0.6-RC2': - - // Update the Custom Profile Fields based on previous settings to the new format - $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' - SET field_show_on_vt = 1 - WHERE field_hide = 0 - AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; - _sql($sql, $errored, $error_ary); - $no_updates = false; - - break; - - // No changes from 3.0.6-RC3 to 3.0.6-RC4 - case '3.0.6-RC3': - break; - - // No changes from 3.0.6-RC4 to 3.0.6 - case '3.0.6-RC4': - break; - - // Changes from 3.0.6 to 3.0.7-RC1 - case '3.0.6': - - // ATOM Feeds - set_config('feed_overall', '1'); - set_config('feed_http_auth', '0'); - set_config('feed_limit_post', (string) (isset($config['feed_limit']) ? (int) $config['feed_limit'] : 15)); - set_config('feed_limit_topic', (string) (isset($config['feed_overall_topics_limit']) ? (int) $config['feed_overall_topics_limit'] : 10)); - set_config('feed_topics_new', (!empty($config['feed_overall_topics']) ? '1' : '0')); - set_config('feed_topics_active', (!empty($config['feed_overall_topics']) ? '1' : '0')); - - // Delete all text-templates from the template_data - $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' - WHERE template_filename ' . $db->sql_like_expression($db->any_char . '.txt'); - _sql($sql, $errored, $error_ary); - - $no_updates = false; - break; - - // Changes from 3.0.7-RC1 to 3.0.7-RC2 - case '3.0.7-RC1': - - $sql = 'SELECT user_id, user_email, user_email_hash - FROM ' . USERS_TABLE . ' - WHERE user_type <> ' . USER_IGNORE . " - AND user_email <> ''"; - $result = $db->sql_query($sql); - - $i = 0; - while ($row = $db->sql_fetchrow($result)) - { - // Snapshot of the phpbb_email_hash() function - // We cannot call it directly because the auto updater updates the DB first. :/ - $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); - - if ($user_email_hash != $row['user_email_hash']) - { - $sql_ary = array( - 'user_email_hash' => $user_email_hash, - ); +echo $user->lang['DATABASE_UPDATE_COMPLETE']; - $sql = 'UPDATE ' . USERS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . (int) $row['user_id']; - _sql($sql, $errored, $error_ary, ($i % 100 == 0)); - - ++$i; - } - } - $db->sql_freeresult($result); - - $no_updates = false; - - break; - - // No changes from 3.0.7-RC2 to 3.0.7 - case '3.0.7-RC2': - break; - - // No changes from 3.0.7 to 3.0.7-PL1 - case '3.0.7': - break; - - // Changes from 3.0.7-PL1 to 3.0.8-RC1 - case '3.0.7-PL1': - // Update file extension group names to use language strings. - $sql = 'SELECT lang_dir - FROM ' . LANG_TABLE; - $result = $db->sql_query($sql); - - $extension_groups_updated = array(); - while ($lang_dir = $db->sql_fetchfield('lang_dir')) - { - $lang_dir = basename($lang_dir); - - // The language strings we need are either in language/.../acp/attachments.php - // in the update package if we're updating to 3.0.8-RC1 or later, - // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. - // On an already updated board, they can also already be in language/.../acp/attachments.php - // in the board root. - $lang_files = array( - "{$phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.$phpEx", - "{$phpbb_root_path}language/$lang_dir/install.$phpEx", - "{$phpbb_root_path}language/$lang_dir/acp/attachments.$phpEx", - ); - - foreach ($lang_files as $lang_file) - { - if (!file_exists($lang_file)) - { - continue; - } - - $lang = array(); - include($lang_file); - - foreach($lang as $lang_key => $lang_val) - { - if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) - { - continue; - } - - $sql_ary = array( - 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' - ); - - $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " - WHERE group_name = '" . $db->sql_escape($lang_val) . "'"; - _sql($sql, $errored, $error_ary); - - $extension_groups_updated[$lang_key] = true; - } - } - } - $db->sql_freeresult($result); - - // Install modules - $modules_to_install = array( - 'post' => array( - 'base' => 'board', - 'class' => 'acp', - 'title' => 'ACP_POST_SETTINGS', - 'auth' => 'acl_a_board', - 'cat' => 'ACP_MESSAGES', - 'after' => array('message', 'ACP_MESSAGE_SETTINGS') - ), - ); - - _add_modules($modules_to_install); - - // update - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' - WHERE module_class = \'ucp\' - AND module_basename = \'profile\' - AND module_mode = \'avatar\''; - _sql($sql, $errored, $error_ary); - - // add Bing Bot - $bot_name = 'Bing [Bot]'; - $bot_name_clean = utf8_clean_string($bot_name); - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $db->sql_escape($bot_name_clean) . "'"; - $result = $db->sql_query($sql); - $bing_already_added = (bool) $db->sql_fetchfield('user_id'); - $db->sql_freeresult($result); - - if (!$bing_already_added) - { - $bot_agent = 'bingbot/'; - $bot_ip = ''; - $sql = 'SELECT group_id, group_colour - FROM ' . GROUPS_TABLE . " - WHERE group_name = 'BOTS'"; - $result = $db->sql_query($sql); - $group_row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$group_row) - { - // default fallback, should never get here - $group_row['group_id'] = 6; - $group_row['group_colour'] = '9E8DA7'; - } - - if (!function_exists('user_add')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - - $user_row = array( - 'user_type' => USER_IGNORE, - 'group_id' => $group_row['group_id'], - 'username' => $bot_name, - 'user_regdate' => time(), - 'user_password' => '', - 'user_colour' => $group_row['group_colour'], - 'user_email' => '', - 'user_lang' => $config['default_lang'], - 'user_style' => $config['default_style'], - 'user_timezone' => 'UTC', - 'user_dateformat' => $config['default_dateformat'], - 'user_allow_massemail' => 0, - ); - - $user_id = user_add($user_row); - - $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $db->sql_build_array('INSERT', array( - 'bot_active' => 1, - 'bot_name' => (string) $bot_name, - 'user_id' => (int) $user_id, - 'bot_agent' => (string) $bot_agent, - 'bot_ip' => (string) $bot_ip, - )); - - _sql($sql, $errored, $error_ary); - } - // end Bing Bot addition - - // Delete shadow topics pointing to not existing topics - $batch_size = 500; - - // Set of affected forums we have to resync - $sync_forum_ids = array(); - - do - { - $sql_array = array( - 'SELECT' => 't1.topic_id, t1.forum_id', - 'FROM' => array( - TOPICS_TABLE => 't1', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(TOPICS_TABLE => 't2'), - 'ON' => 't1.topic_moved_id = t2.topic_id', - ), - ), - 'WHERE' => 't1.topic_moved_id <> 0 - AND t2.topic_id IS NULL', - ); - $sql = $db->sql_build_query('SELECT', $sql_array); - $result = $db->sql_query_limit($sql, $batch_size); - - $topic_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $topic_ids[] = (int) $row['topic_id']; - - $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; - } - $db->sql_freeresult($result); - - if (!empty($topic_ids)) - { - $sql = 'DELETE FROM ' . TOPICS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', $topic_ids); - $db->sql_query($sql); - } - } - while (sizeof($topic_ids) == $batch_size); - - // Sync the forums we have deleted shadow topics from. - sync('forum', 'forum_id', $sync_forum_ids, true, true); - - // Unread posts search load switch - set_config('load_unreads_search', '1'); - - // Reduce queue interval to 60 seconds, email package size to 20 - if ($config['queue_interval'] == 600) - { - set_config('queue_interval', '60'); - } - - if ($config['email_package_size'] == 50) - { - set_config('email_package_size', '20'); - } - - $no_updates = false; - break; - - // No changes from 3.0.8-RC1 to 3.0.8 - case '3.0.8-RC1': - break; - - // Changes from 3.0.8 to 3.0.9-RC1 - case '3.0.8': - set_config('ip_login_limit_max', '50'); - set_config('ip_login_limit_time', '21600'); - set_config('ip_login_limit_use_forwarded', '0'); - - // Update file extension group names to use language strings, again. - $sql = 'SELECT group_id, group_name - FROM ' . EXTENSION_GROUPS_TABLE . ' - WHERE group_name ' . $db->sql_like_expression('EXT_GROUP_' . $db->any_char); - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $sql_ary = array( - 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' - ); - - $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE group_id = ' . $row['group_id']; - _sql($sql, $errored, $error_ary); - } - $db->sql_freeresult($result); - - global $db_tools, $table_prefix; - - // Recover from potentially broken Q&A CAPTCHA table on firebird - // Q&A CAPTCHA was uninstallable, so it's safe to remove these - // without data loss - if ($db_tools->sql_layer == 'firebird') - { - $tables = array( - $table_prefix . 'captcha_questions', - $table_prefix . 'captcha_answers', - $table_prefix . 'qa_confirm', - ); - foreach ($tables as $table) - { - if ($db_tools->sql_table_exists($table)) - { - $db_tools->sql_table_drop($table); - } - } - } - - $no_updates = false; - break; - - // No changes from 3.0.9-RC1 to 3.0.9-RC2 - case '3.0.9-RC1': - break; - - // No changes from 3.0.9-RC2 to 3.0.9-RC3 - case '3.0.9-RC2': - break; - - // No changes from 3.0.9-RC3 to 3.0.9-RC4 - case '3.0.9-RC3': - break; - - // No changes from 3.0.9-RC4 to 3.0.9 - case '3.0.9-RC4': - break; - - // Changes from 3.0.9 to 3.0.10-RC1 - case '3.0.9': - if (!isset($config['email_max_chunk_size'])) - { - set_config('email_max_chunk_size', '50'); - } - - $no_updates = false; - break; - - // No changes from 3.0.10-RC1 to 3.0.10-RC2 - case '3.0.10-RC1': - break; - - // No changes from 3.0.10-RC2 to 3.0.10-RC3 - case '3.0.10-RC2': - break; - - // No changes from 3.0.10-RC3 to 3.0.10 - case '3.0.10-RC3': - break; - - // Changes from 3.0.10 to 3.0.11-RC1 - case '3.0.10': - // Updates users having current style a deactivated one - $sql = 'SELECT style_id - FROM ' . STYLES_TABLE . ' - WHERE style_active = 0'; - $result = $db->sql_query($sql); - - $deactivated_style_ids = array(); - while ($style_id = $db->sql_fetchfield('style_id', false, $result)) - { - $deactivated_style_ids[] = (int) $style_id; - } - $db->sql_freeresult($result); - - if (!empty($deactivated_style_ids)) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_style = ' . (int) $config['default_style'] .' - WHERE ' . $db->sql_in_set('user_style', $deactivated_style_ids); - _sql($sql, $errored, $error_ary); - } - - // Delete orphan private messages - $batch_size = 500; - - $sql_array = array( - 'SELECT' => 'p.msg_id', - 'FROM' => array( - PRIVMSGS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(PRIVMSGS_TO_TABLE => 't'), - 'ON' => 'p.msg_id = t.msg_id', - ), - ), - 'WHERE' => 't.user_id IS NULL', - ); - $sql = $db->sql_build_query('SELECT', $sql_array); - - do - { - $result = $db->sql_query_limit($sql, $batch_size); - - $delete_pms = array(); - while ($row = $db->sql_fetchrow($result)) - { - $delete_pms[] = (int) $row['msg_id']; - } - $db->sql_freeresult($result); - - if (!empty($delete_pms)) - { - $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' - WHERE ' . $db->sql_in_set('msg_id', $delete_pms); - _sql($sql, $errored, $error_ary); - } - } - while (sizeof($delete_pms) == $batch_size); - - $no_updates = false; - break; - - // No changes from 3.0.11-RC1 to 3.0.11-RC2 - case '3.0.11-RC1': - break; - - // No changes from 3.0.11-RC2 to 3.0.11 - case '3.0.11-RC2': - break; - - // Changes from 3.0.11 to 3.0.12-RC1 - case '3.0.11': - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_auth = \'acl_u_sig\' - WHERE module_class = \'ucp\' - AND module_basename = \'profile\' - AND module_mode = \'signature\''; - _sql($sql, $errored, $error_ary); - - // Update bots - if (!function_exists('user_delete')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - - $bots_updates = array( - // Bot Deletions - 'NG-Search [Bot]' => false, - 'Nutch/CVS [Bot]' => false, - 'OmniExplorer [Bot]' => false, - 'Seekport [Bot]' => false, - 'Synoo [Bot]' => false, - 'WiseNut [Bot]' => false, - - // Bot Updates - // Bot name to bot user agent map - 'Baidu [Spider]' => 'Baiduspider', - 'Exabot [Bot]' => 'Exabot', - 'Voyager [Bot]' => 'voyager/', - 'W3C [Validator]' => 'W3C_Validator', - ); - - foreach ($bots_updates as $bot_name => $bot_agent) - { - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE user_type = ' . USER_IGNORE . " - AND username_clean = '" . $db->sql_escape(utf8_clean_string($bot_name)) . "'"; - $result = $db->sql_query($sql); - $bot_user_id = (int) $db->sql_fetchfield('user_id'); - $db->sql_freeresult($result); - - if ($bot_user_id) - { - if ($bot_agent === false) - { - $sql = 'DELETE FROM ' . BOTS_TABLE . " - WHERE user_id = $bot_user_id"; - _sql($sql, $errored, $error_ary); - - user_delete('remove', $bot_user_id); - } - else - { - $sql = 'UPDATE ' . BOTS_TABLE . " - SET bot_agent = '" . $db->sql_escape($bot_agent) . "' - WHERE user_id = $bot_user_id"; - _sql($sql, $errored, $error_ary); - } - } - } - - // Disable receiving pms for bots - $sql = 'SELECT user_id - FROM ' . BOTS_TABLE; - $result = $db->sql_query($sql); - - $bot_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $bot_user_ids[] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - if (!empty($bot_user_ids)) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_allow_pm = 0 - WHERE ' . $db->sql_in_set('user_id', $bot_user_ids); - _sql($sql, $errored, $error_ary); - } - - $no_updates = false; - break; - - // Changes from 3.1.0-dev to 3.1.0-A1 - case '3.1.0-dev': - - // rename all module basenames to full classname - $sql = 'SELECT module_id, module_basename, module_class - FROM ' . MODULES_TABLE; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $module_id = (int) $row['module_id']; - unset($row['module_id']); - - if (!empty($row['module_basename']) && !empty($row['module_class'])) - { - // all the class names start with class name or with phpbb_ for auto loading - if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && - strpos($row['module_basename'], 'phpbb_') !== 0) - { - $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; - - $sql_update = $db->sql_build_array('UPDATE', $row); - - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET ' . $sql_update . ' - WHERE module_id = ' . $module_id; - _sql($sql, $errored, $error_ary); - } - } - } - - $db->sql_freeresult($result); - - if (substr($config['search_type'], 0, 6) !== 'phpbb_') - { - // try to guess the new auto loaded search class name - // works for native and mysql fulltext - set_config('search_type', 'phpbb_search_' . $config['search_type']); - } - - if (!isset($config['fulltext_postgres_ts_name'])) - { - set_config('fulltext_postgres_ts_name', 'simple'); - } - - if (!isset($config['fulltext_postgres_min_word_len'])) - { - set_config('fulltext_postgres_min_word_len', 4); - } - - if (!isset($config['fulltext_postgres_max_word_len'])) - { - set_config('fulltext_postgres_max_word_len', 254); - } - - if (!isset($config['fulltext_sphinx_stopwords'])) - { - set_config('fulltext_sphinx_stopwords', 0); - } - - if (!isset($config['fulltext_sphinx_indexer_mem_limit'])) - { - set_config('fulltext_sphinx_indexer_mem_limit', 512); - } - - if (!isset($config['load_jquery_cdn'])) - { - set_config('load_jquery_cdn', 0); - set_config('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'); - } - - if (!isset($config['use_system_cron'])) - { - set_config('use_system_cron', 0); - } - - $sql = 'SELECT group_teampage - FROM ' . GROUPS_TABLE . ' - WHERE group_teampage > 0'; - $result = $db->sql_query_limit($sql, 1); - $added_groups_teampage = (bool) $db->sql_fetchfield('group_teampage'); - $db->sql_freeresult($result); - - if (!$added_groups_teampage) - { - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_teampage = 1 - WHERE group_type = ' . GROUP_SPECIAL . " - AND group_name = 'ADMINISTRATORS'"; - _sql($sql, $errored, $error_ary); - - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_teampage = 2 - WHERE group_type = ' . GROUP_SPECIAL . " - AND group_name = 'GLOBAL_MODERATORS'"; - _sql($sql, $errored, $error_ary); - } - - if (!isset($config['legend_sort_groupname'])) - { - set_config('legend_sort_groupname', '0'); - set_config('teampage_forums', '1'); - } - - $sql = 'SELECT group_legend - FROM ' . GROUPS_TABLE . ' - WHERE group_teampage > 1'; - $result = $db->sql_query_limit($sql, 1); - $updated_group_legend = (bool) $db->sql_fetchfield('group_teampage'); - $db->sql_freeresult($result); - - if (!$updated_group_legend) - { - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . ' - WHERE group_legend = 1 - ORDER BY group_name ASC'; - $result = $db->sql_query($sql); - - $next_legend = 1; - while ($row = $db->sql_fetchrow($result)) - { - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = ' . $next_legend . ' - WHERE group_id = ' . (int) $row['group_id']; - _sql($sql, $errored, $error_ary); - - $next_legend++; - } - $db->sql_freeresult($result); - unset($next_legend); - } - - // Rename styles module to Customise - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_langname = 'ACP_CAT_CUSTOMISE' - WHERE module_langname = 'ACP_CAT_STYLES'"; - _sql($sql, $errored, $error_ary); - - // Install modules - $modules_to_install = array( - 'position' => array( - 'base' => 'acp_groups', - 'class' => 'acp', - 'title' => 'ACP_GROUPS_POSITION', - 'auth' => 'acl_a_group', - 'cat' => 'ACP_GROUPS', - ), - 'manage' => array( - 'base' => 'acp_attachments', - 'class' => 'acp', - 'title' => 'ACP_MANAGE_ATTACHMENTS', - 'auth' => 'acl_a_attach', - 'cat' => 'ACP_ATTACHMENTS', - ), - 'install' => array( - 'base' => 'acp_styles', - 'class' => 'acp', - 'title' => 'ACP_STYLES_INSTALL', - 'auth' => 'acl_a_styles', - 'cat' => 'ACP_STYLE_MANAGEMENT', - ), - 'cache' => array( - 'base' => 'acp_styles', - 'class' => 'acp', - 'title' => 'ACP_STYLES_CACHE', - 'auth' => 'acl_a_styles', - 'cat' => 'ACP_STYLE_MANAGEMENT', - ), - 'autologin_keys' => array( - 'base' => 'ucp_profile', - 'class' => 'ucp', - 'title' => 'UCP_PROFILE_AUTOLOGIN_KEYS', - 'auth' => '', - 'cat' => 'UCP_PROFILE', - ), - // To add a category, the mode and basename must be empty - // The mode is taken from the array key - '' => array( - 'base' => '', - 'class' => 'acp', - 'title' => 'ACP_EXTENSION_MANAGEMENT', - 'auth' => 'acl_a_extensions', - 'cat' => 'ACP_CAT_CUSTOMISE', - ), - 'extensions' => array( - 'base' => 'acp_extensions', - 'class' => 'acp', - 'title' => 'ACP_EXTENSIONS', - 'auth' => 'acl_a_extensions', - 'cat' => 'ACP_EXTENSION_MANAGEMENT', - ), - ); - - _add_modules($modules_to_install); - - // We need a separate array for the new language sub heading - // because it requires another empty key - $modules_to_install = array( - '' => array( - 'base' => '', - 'class' => 'acp', - 'title' => 'ACP_LANGUAGE', - 'auth' => 'acl_a_language', - 'cat' => 'ACP_CAT_CUSTOMISE', - ), - ); - - _add_modules($modules_to_install); - - // Move language management to new location in the Customise tab - // First get language module id - $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " - WHERE module_basename = 'acp_language'"; - $result = $db->sql_query($sql); - $language_module_id = $db->sql_fetchfield('module_id'); - $db->sql_freeresult($result); - // Next get language management module id of the one just created - $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " - WHERE module_langname = 'ACP_LANGUAGE'"; - $result = $db->sql_query($sql); - $language_management_module_id = $db->sql_fetchfield('module_id'); - $db->sql_freeresult($result); - - if (!class_exists('acp_modules')) - { - include($phpbb_root_path . 'includes/acp/acp_modules.' . $phpEx); - } - // acp_modules calls adm_back_link, which is undefined at this point - if (!function_exists('adm_back_link')) - { - include($phpbb_root_path . 'includes/functions_acp.' . $phpEx); - } - $module_manager = new acp_modules(); - $module_manager->module_class = 'acp'; - $module_manager->move_module($language_module_id, $language_management_module_id); - - $sql = 'DELETE FROM ' . MODULES_TABLE . " - WHERE (module_basename = 'styles' OR module_basename = 'acp_styles') AND (module_mode = 'imageset' OR module_mode = 'theme' OR module_mode = 'template')"; - _sql($sql, $errored, $error_ary); - - // Localise Global Announcements - $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour - FROM ' . TOPICS_TABLE . ' - WHERE forum_id = 0 - AND topic_type = ' . POST_GLOBAL; - $result = $db->sql_query($sql); - - $global_announcements = $update_lastpost_data = array(); - $update_lastpost_data['forum_last_post_time'] = 0; - $update_forum_data = array( - 'forum_posts' => 0, - 'forum_topics' => 0, - 'forum_topics_real' => 0, - ); - - while ($row = $db->sql_fetchrow($result)) - { - $global_announcements[] = (int) $row['topic_id']; - - $update_forum_data['forum_posts'] += (int) $row['topic_posts']; - $update_forum_data['forum_topics_real']++; - if ($row['topic_approved']) - { - $update_forum_data['forum_topics']++; - } - - if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) - { - $update_lastpost_data = array( - 'forum_last_post_id' => (int) $row['topic_last_post_id'], - 'forum_last_post_subject' => $row['topic_last_post_subject'], - 'forum_last_post_time' => (int) $row['topic_last_post_time'], - 'forum_last_poster_id' => (int) $row['topic_last_poster_id'], - 'forum_last_poster_name' => $row['topic_last_poster_name'], - 'forum_last_poster_colour' => $row['topic_last_poster_colour'], - ); - } - } - $db->sql_freeresult($result); - - if (!empty($global_announcements)) - { - // Update the post/topic-count for the forum and the last-post if needed - $ga_forum_id = request_var('ga_forum_id', 0); - - $sql = 'SELECT forum_last_post_time - FROM ' . FORUMS_TABLE . ' - WHERE forum_id = ' . $ga_forum_id; - $result = $db->sql_query($sql); - $lastpost = (int) $db->sql_fetchfield('forum_last_post_time'); - $db->sql_freeresult($result); - - $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; - $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; - $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; - if ($lastpost < $update_lastpost_data['forum_last_post_time']) - { - $sql_update .= ', ' . $db->sql_build_array('UPDATE', $update_lastpost_data); - } - - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET ' . $sql_update . ' - WHERE forum_id = ' . $ga_forum_id; - _sql($sql, $errored, $error_ary); - - // Update some forum_ids - $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); - foreach ($table_ary as $table) - { - $sql = "UPDATE $table - SET forum_id = $ga_forum_id - WHERE " . $db->sql_in_set('topic_id', $global_announcements); - _sql($sql, $errored, $error_ary); - } - unset($table_ary); - } - - // Allow custom profile fields in pm templates - if (!isset($config['load_cpf_pm'])) - { - set_config('load_cpf_pm', '0'); - } - - if (!isset($config['teampage_memberships'])) - { - set_config('teampage_memberships', '1'); - } - - // Check if styles table was already updated - if ($db_tools->sql_table_exists(STYLES_THEME_TABLE)) - { - // Get list of valid 3.1 styles - $available_styles = array('prosilver'); - - $iterator = new DirectoryIterator($phpbb_root_path . 'styles'); - $skip_dirs = array('.', '..', 'prosilver'); - foreach ($iterator as $fileinfo) - { - if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) - { - $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); - if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) - { - // 3.1 style - $available_styles[] = $fileinfo->getFilename(); - } - } - } - - // Get all installed styles - if ($db_tools->sql_table_exists(STYLES_IMAGESET_TABLE)) - { - $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path - FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i - WHERE t.template_id = s.template_id - AND c.theme_id = s.theme_id - AND i.imageset_id = s.imageset_id"; - } - else - { - $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id - FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . " c - WHERE t.template_id = s.template_id - AND c.theme_id = s.theme_id"; - } - $result = $db->sql_query($sql); - - $styles = array(); - while ($row = $db->sql_fetchrow($result)) - { - $styles[] = $row; - } - $db->sql_freeresult($result); - - // Decide which styles to keep, all others will be deleted - $valid_styles = array(); - foreach ($styles as $style_row) - { - if ( - // Delete styles with parent style (not supported yet) - $style_row['template_inherits_id'] == 0 && - // Check if components match - $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && - // Check if components are valid - in_array($style_row['template_path'], $available_styles) - ) - { - // Valid style. Keep it - $sql_ary = array( - 'style_path' => $style_row['template_path'], - 'bbcode_bitfield' => $style_row['bbcode_bitfield'], - 'style_parent_id' => 0, - 'style_parent_tree' => '', - ); - _sql('UPDATE ' . STYLES_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE style_id = ' . $style_row['style_id'], $errored, $error_ary); - $valid_styles[] = (int) $style_row['style_id']; - } - } - - // Remove old styles tables - $changes = array( - 'drop_columns' => array( - STYLES_TABLE => array( - 'imageset_id', - 'template_id', - 'theme_id', - ), - ), - - 'drop_tables' => array( - STYLES_IMAGESET_TABLE, - STYLES_IMAGESET_DATA_TABLE, - STYLES_TEMPLATE_TABLE, - STYLES_TEMPLATE_DATA_TABLE, - STYLES_THEME_TABLE, - ) - ); - $statements = $db_tools->perform_schema_changes($changes); - - foreach ($statements as $sql) - { - _sql($sql, $errored, $error_ary); - } - - // Remove old entries from styles table - if (!sizeof($valid_styles)) - { - // No valid styles: remove everything and add prosilver - _sql('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary); - - $sql = 'INSERT INTO ' . STYLES_TABLE . " (style_name, style_copyright, style_active, style_path, bbcode_bitfield, style_parent_id, style_parent_tree) VALUES ('prosilver', '© phpBB Group', 1, 'prosilver', 'kNg=', 0, '')"; - _sql($sql, $errored, $error_ary); - - $sql = 'SELECT style_id - FROM ' . $table . " - WHERE style_name = 'prosilver'"; - $result = _sql($sql, $errored, $error_ary); - $default_style = $db->sql_fetchfield($result); - $db->sql_freeresult($result); - - set_config('default_style', $default_style); - - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0'; - _sql($sql, $errored, $error_ary); - } - else - { - // There are valid styles in styles table. Remove styles that are outdated - _sql('DELETE FROM ' . STYLES_TABLE . ' WHERE ' . $db->sql_in_set('style_id', $valid_styles, true), $errored, $error_ary); - - // Change default style - if (!in_array($config['default_style'], $valid_styles)) - { - set_config('default_style', $valid_styles[0]); - } - - // Reset styles for users - _sql('UPDATE ' . USERS_TABLE . ' SET user_style = 0 WHERE ' . $db->sql_in_set('user_style', $valid_styles, true), $errored, $error_ary); - } - } - - // Create config value for displaying last subject on forum list - if (!isset($config['display_last_subject'])) - { - $config->set('display_last_subject', '1'); - } - - if (!isset($config['assets_version'])) - { - $config->set('assets_version', '1'); - } - - // If the column exists, we did not yet update the users timezone - if ($db_tools->sql_column_exists(USERS_TABLE, 'user_dst')) - { - // Update user timezones - $sql = 'SELECT user_dst, user_timezone - FROM ' . USERS_TABLE . ' - GROUP BY user_timezone, user_dst'; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_timezone = '" . $db->sql_escape($update_helpers->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' - WHERE user_timezone = '" . $db->sql_escape($row['user_timezone']) . "' - AND user_dst = " . (int) $row['user_dst']; - _sql($sql, $errored, $error_ary); - } - $db->sql_freeresult($result); - - // Update board default timezone - set_config('board_timezone', $update_helpers->convert_phpbb30_timezone($config['board_timezone'], $config['board_dst'])); - - // After we have calculated the timezones we can delete user_dst column from user table. - $db_tools->sql_column_remove(USERS_TABLE, 'user_dst'); - } - - if (!isset($config['site_home_url'])) - { - $config->set('site_home_url', ''); - $config->set('site_home_text', ''); - } - - // PHPBB3-10601: Make inbox default. Add basename to ucp's pm category - - // Get the category wanted while checking, at the same time, if this has already been applied - $sql = 'SELECT module_id, module_basename - FROM ' . MODULES_TABLE . " - WHERE module_basename <> 'ucp_pm' AND - module_langname='UCP_PM' - "; - $result = $db->sql_query_limit($sql, 1); - - if ($row = $db->sql_fetchrow($result)) - { - // This update is still not applied. Applying it - - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_basename = 'ucp_pm' - WHERE module_id = " . (int) $row['module_id']; - - _sql($sql, $errored, $error_ary); - } - $db->sql_freeresult($result); - - // Add new permissions - include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); - $auth_admin = new auth_admin(); - - _add_permission($auth_admin, $db, 'u_chgprofileinfo', true, 'u_sig'); - _add_permission($auth_admin, $db, 'a_extensions', true, 'a_styles'); - - // Update the auth setting for the module - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_auth = 'acl_u_chgprofileinfo' - WHERE module_class = 'ucp' - AND module_basename = 'ucp_profile' - AND module_mode = 'profile_info'"; - _sql($sql, $errored, $error_ary); - - $no_updates = false; - - break; - } -} +phpbb_end_update($cache); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 2be5adaaac..a03fda6395 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -73,6 +73,10 @@ else } @ini_set('memory_limit', $mem_limit); +// 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 essential scripts require($phpbb_root_path . 'includes/class_loader.' . $phpEx); @@ -97,9 +101,6 @@ $phpbb_container = phpbb_create_install_container($phpbb_root_path, $phpEx); $phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); -// set up caching -$cache = $phpbb_container->get('cache'); - $phpbb_dispatcher = $phpbb_container->get('dispatcher'); $request = $phpbb_container->get('request'); @@ -195,7 +196,8 @@ if (file_exists($phpbb_root_path . 'includes/hooks/index.' . $phpEx)) require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display'))); - foreach ($cache->obtain_hooks() as $hook) + $phpbb_hook_finder = $phpbb_container->get('hook_finder'); + foreach ($phpbb_hook_finder->find() as $hook) { @include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx); } @@ -215,9 +217,9 @@ $phpbb_style_path_provider = new phpbb_style_path_provider(); $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, new phpbb_template_context()); $phpbb_style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_resource_locator, $phpbb_style_path_provider, $template); $phpbb_style->set_ext_dir_prefix('adm/'); -$phpbb_style->set_custom_style('admin', '../adm/style', array(), ''); +$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); $template->assign_var('T_ASSETS_PATH', '../assets'); -$template->assign_var('T_TEMPLATE_PATH', '../adm/style'); +$template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); $install = new module(); @@ -361,7 +363,7 @@ class module } define('HEADER_INC', true); - global $template, $lang, $stage, $phpbb_root_path; + global $template, $lang, $stage, $phpbb_root_path, $phpbb_admin_path; $template->assign_vars(array( 'L_CHANGE' => $lang['CHANGE'], @@ -370,7 +372,7 @@ class module 'L_SELECT_LANG' => $lang['SELECT_LANG'], 'L_SKIP' => $lang['SKIP'], 'PAGE_TITLE' => $this->get_page_title(), - 'T_IMAGE_PATH' => $phpbb_root_path . 'adm/images/', + 'T_IMAGE_PATH' => htmlspecialchars($phpbb_admin_path) . 'images/', 'S_CONTENT_DIRECTION' => $lang['DIRECTION'], 'S_CONTENT_FLOW_BEGIN' => ($lang['DIRECTION'] == 'ltr') ? 'left' : 'right', @@ -551,7 +553,7 @@ class module */ function error($error, $line, $file, $skip = false) { - global $lang, $db, $template; + global $lang, $db, $template, $phpbb_admin_path; if ($skip) { @@ -573,7 +575,7 @@ class module echo '<head>'; echo '<meta charset="utf-8">'; echo '<title>' . $lang['INST_ERR_FATAL'] . '</title>'; - echo '<link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" />'; + echo '<link href="' . htmlspecialchars($phpbb_admin_path) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" />'; echo '</head>'; echo '<body id="errorpage">'; echo '<div id="wrap">'; diff --git a/phpBB/install/install_convert.php b/phpBB/install/install_convert.php index a9a4612ddf..270117481a 100644 --- a/phpBB/install/install_convert.php +++ b/phpBB/install/install_convert.php @@ -1537,6 +1537,7 @@ class install_convert extends module function finish_conversion() { global $db, $phpbb_root_path, $phpEx, $convert, $config, $language, $user, $template; + global $cache, $auth; $db->sql_query('DELETE FROM ' . CONFIG_TABLE . " WHERE config_name = 'convert_progress' @@ -1546,7 +1547,7 @@ class install_convert extends module $db->sql_query('DELETE FROM ' . SESSIONS_TABLE); @unlink($phpbb_root_path . 'cache/data_global.' . $phpEx); - cache_moderators(); + phpbb_cache_moderators($db, $cache, $auth); // And finally, add a note to the log add_log('admin', 'LOG_INSTALL_CONVERTED', $convert->convertor_data['forum_name'], $config['version']); diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 14f6ca30fb..f0280acc40 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -52,12 +52,13 @@ class install_install extends module function main($mode, $sub) { - global $lang, $template, $language, $phpbb_root_path, $cache; + global $lang, $template, $language, $phpbb_root_path, $phpEx; + global $phpbb_container, $cache; switch ($sub) { case 'intro': - $cache->purge(); + $phpbb_container->get('cache.driver')->purge(); $this->page_title = $lang['SUB_INTRO']; @@ -101,12 +102,19 @@ class install_install extends module break; case 'final': + // Create a normal container now + $phpbb_container = phpbb_create_default_container($phpbb_root_path, $phpEx); + + // Sets the global $cache variable + $cache = $phpbb_container->get('cache'); + $this->build_search_index($mode, $sub); $this->add_modules($mode, $sub); $this->add_language($mode, $sub); $this->add_bots($mode, $sub); $this->email_admin($mode, $sub); $this->disable_avatars_if_unwritable(); + $this->populate_migrations($phpbb_container->get('migrator'), $phpbb_root_path); // Remove the lock file @unlink($phpbb_root_path . 'cache/install_lock'); @@ -1449,12 +1457,12 @@ class install_install extends module */ function add_modules($mode, $sub) { - global $db, $lang, $phpbb_root_path, $phpEx, $phpbb_extension_manager, $config; + global $db, $lang, $phpbb_root_path, $phpEx, $phpbb_extension_manager, $config, $phpbb_container; // modules require an extension manager if (empty($phpbb_extension_manager)) { - $phpbb_extension_manager = new phpbb_extension_manager($db, $config, EXT_TABLE, $phpbb_root_path, ".$phpEx"); + $phpbb_extension_manager = $phpbb_container->get('ext.manager'); } include_once($phpbb_root_path . 'includes/acp/acp_modules.' . $phpEx); @@ -1807,7 +1815,7 @@ class install_install extends module */ function email_admin($mode, $sub) { - global $auth, $config, $db, $lang, $template, $user, $phpbb_root_path, $phpEx; + global $auth, $config, $db, $lang, $template, $user, $phpbb_root_path, $phpbb_admin_path, $phpEx; $this->page_title = $lang['STAGE_FINAL']; @@ -1854,7 +1862,7 @@ class install_install extends module 'TITLE' => $lang['INSTALL_CONGRATS'], 'BODY' => sprintf($lang['INSTALL_CONGRATS_EXPLAIN'], $config['version'], append_sid($phpbb_root_path . 'install/index.' . $phpEx, 'mode=convert&language=' . $data['language']), '../docs/README.html'), 'L_SUBMIT' => $lang['INSTALL_LOGIN'], - 'U_ACTION' => append_sid($phpbb_root_path . 'adm/index.' . $phpEx, 'i=send_statistics&mode=send_statistics'), + 'U_ACTION' => append_sid($phpbb_admin_path . 'index.' . $phpEx, 'i=send_statistics&mode=send_statistics'), )); } @@ -1874,6 +1882,21 @@ class install_install extends module } /** + * Populate migrations for the installation + * + * This "installs" all migrations from (root path)/includes/db/migrations/data. + * "installs" means it adds all migrations to the migrations table, but does not + * perform any of the actions in the migrations. + * + * @param phpbb_db_migrator $migrator + * @param string $phpbb_root_path + */ + function populate_migrations($migrator, $phpbb_root_path) + { + $migrator->populate_migrations_from_directory($phpbb_root_path . 'includes/db/migration/data/'); + } + + /** * Generate a list of available mail server authentication methods */ function mail_auth_select($selected_method) @@ -2091,7 +2114,7 @@ class install_install extends module ), 'ACP_CAT_CUSTOMISE' => array( 'ACP_STYLE_MANAGEMENT', - 'ACP_EXTENSIONS_MANAGEMENT', + 'ACP_EXTENSION_MANAGEMENT', 'ACP_LANGUAGE', ), 'ACP_CAT_MAINTENANCE' => array( diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 53f9c52556..bfceed6b17 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -72,7 +72,7 @@ class install_update extends module function main($mode, $sub) { global $phpbb_style, $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; - global $request; + global $request, $phpbb_admin_path, $phpbb_adm_relative_path; $this->tpl_name = 'install_update'; $this->page_title = 'UPDATE_INSTALLATION'; @@ -132,7 +132,7 @@ class install_update extends module } // Set custom template again. ;) - $phpbb_style->set_custom_style('admin', '../adm/style', array(), ''); + $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); $template->assign_vars(array( 'S_USER_LANG' => $user->lang['USER_LANG'], @@ -217,7 +217,7 @@ class install_update extends module if ($this->test_update === false) { // Got the updater template itself updated? If so, we are able to directly use it - but only if all three files are present - if (in_array('adm/style/install_update.html', $this->update_info['files'])) + if (in_array($phpbb_adm_relative_path . 'style/install_update.html', $this->update_info['files'])) { $this->tpl_name = '../../install/update/new/adm/style/install_update'; } @@ -494,6 +494,7 @@ class install_update extends module $template->assign_vars(array( 'S_FILE_CHECK' => true, 'S_ALL_UP_TO_DATE' => $all_up_to_date, + 'L_ALL_FILES_UP_TO_DATE' => $user->lang('ALL_FILES_UP_TO_DATE', append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $phpbb_adm_relative_path . 'index.php%3Fi=send_statistics%26mode=send_statistics')), 'S_VERSION_UP_TO_DATE' => $up_to_date, 'U_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=file_check"), 'U_UPDATE_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=update_files"), @@ -1078,12 +1079,12 @@ class install_update extends module */ function show_diff(&$update_list) { - global $phpbb_root_path, $template, $user; + global $phpbb_root_path, $template, $user, $phpbb_adm_relative_path; $this->tpl_name = 'install_update_diff'; // Got the diff template itself updated? If so, we are able to directly use it - if (in_array('adm/style/install_update_diff.html', $this->update_info['files'])) + if (in_array($phpbb_adm_relative_path . 'style/install_update_diff.html', $this->update_info['files'])) { $this->tpl_name = '../../install/update/new/adm/style/install_update_diff'; } diff --git a/phpBB/install/schemas/firebird_schema.sql b/phpBB/install/schemas/firebird_schema.sql index d1a4f843b1..892a4eb887 100644 --- a/phpBB/install/schemas/firebird_schema.sql +++ b/phpBB/install/schemas/firebird_schema.sql @@ -589,6 +589,20 @@ CREATE TABLE phpbb_moderator_cache ( CREATE INDEX phpbb_moderator_cache_disp_idx ON phpbb_moderator_cache(display_on_index);; CREATE INDEX phpbb_moderator_cache_forum_id ON phpbb_moderator_cache(forum_id);; +# Table: 'phpbb_migrations' +CREATE TABLE phpbb_migrations ( + migration_name VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + migration_depends_on BLOB SUB_TYPE TEXT CHARACTER SET NONE DEFAULT '' NOT NULL, + migration_schema_done INTEGER DEFAULT 0 NOT NULL, + migration_data_done INTEGER DEFAULT 0 NOT NULL, + migration_data_state BLOB SUB_TYPE TEXT CHARACTER SET NONE DEFAULT '' NOT NULL, + migration_start_time INTEGER DEFAULT 0 NOT NULL, + migration_end_time INTEGER DEFAULT 0 NOT NULL +);; + +ALTER TABLE phpbb_migrations ADD PRIMARY KEY (migration_name);; + + # Table: 'phpbb_modules' CREATE TABLE phpbb_modules ( module_id INTEGER NOT NULL, @@ -621,6 +635,43 @@ BEGIN END;; +# Table: 'phpbb_notification_types' +CREATE TABLE phpbb_notification_types ( + notification_type VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + notification_type_enabled INTEGER DEFAULT 1 NOT NULL +);; + +ALTER TABLE phpbb_notification_types ADD PRIMARY KEY (notification_type, notification_type_enabled);; + + +# Table: 'phpbb_notifications' +CREATE TABLE phpbb_notifications ( + notification_id INTEGER NOT NULL, + item_type VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + item_id INTEGER DEFAULT 0 NOT NULL, + item_parent_id INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + notification_read INTEGER DEFAULT 0 NOT NULL, + notification_time INTEGER DEFAULT 1 NOT NULL, + notification_data BLOB SUB_TYPE TEXT CHARACTER SET UTF8 DEFAULT '' NOT NULL +);; + +ALTER TABLE phpbb_notifications ADD PRIMARY KEY (notification_id);; + +CREATE INDEX phpbb_notifications_item_ident ON phpbb_notifications(item_type, item_id);; +CREATE INDEX phpbb_notifications_user ON phpbb_notifications(user_id, notification_read);; + +CREATE GENERATOR phpbb_notifications_gen;; +SET GENERATOR phpbb_notifications_gen TO 0;; + +CREATE TRIGGER t_phpbb_notifications FOR phpbb_notifications +BEFORE INSERT +AS +BEGIN + NEW.notification_id = GEN_ID(phpbb_notifications_gen, 1); +END;; + + # Table: 'phpbb_poll_options' CREATE TABLE phpbb_poll_options ( poll_option_id INTEGER DEFAULT 0 NOT NULL, @@ -918,8 +969,11 @@ CREATE TABLE phpbb_reports ( report_time INTEGER DEFAULT 0 NOT NULL, report_text BLOB SUB_TYPE TEXT CHARACTER SET UTF8 DEFAULT '' NOT NULL, reported_post_text BLOB SUB_TYPE TEXT CHARACTER SET UTF8 DEFAULT '' NOT NULL, + reported_post_uid VARCHAR(8) CHARACTER SET NONE DEFAULT '' NOT NULL, reported_post_bitfield VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, - reported_post_uid VARCHAR(8) CHARACTER SET NONE DEFAULT '' NOT NULL + reported_post_enable_magic_url INTEGER DEFAULT 1 NOT NULL, + reported_post_enable_smilies INTEGER DEFAULT 1 NOT NULL, + reported_post_enable_bbcode INTEGER DEFAULT 1 NOT NULL );; ALTER TABLE phpbb_reports ADD PRIMARY KEY (report_id);; @@ -1213,6 +1267,16 @@ CREATE INDEX phpbb_topics_watch_topic_id ON phpbb_topics_watch(topic_id);; CREATE INDEX phpbb_topics_watch_user_id ON phpbb_topics_watch(user_id);; CREATE INDEX phpbb_topics_watch_notify_stat ON phpbb_topics_watch(notify_status);; +# Table: 'phpbb_user_notifications' +CREATE TABLE phpbb_user_notifications ( + item_type VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + item_id INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + method VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + notify INTEGER DEFAULT 1 NOT NULL +);; + + # Table: 'phpbb_user_group' CREATE TABLE phpbb_user_group ( group_id INTEGER DEFAULT 0 NOT NULL, diff --git a/phpBB/install/schemas/mssql_schema.sql b/phpBB/install/schemas/mssql_schema.sql index e4aea02526..8578078281 100644 --- a/phpBB/install/schemas/mssql_schema.sql +++ b/phpBB/install/schemas/mssql_schema.sql @@ -720,6 +720,28 @@ GO /* + Table: 'phpbb_migrations' +*/ +CREATE TABLE [phpbb_migrations] ( + [migration_name] [varchar] (255) DEFAULT ('') NOT NULL , + [migration_depends_on] [varchar] (8000) DEFAULT ('') NOT NULL , + [migration_schema_done] [int] DEFAULT (0) NOT NULL , + [migration_data_done] [int] DEFAULT (0) NOT NULL , + [migration_data_state] [varchar] (8000) DEFAULT ('') NOT NULL , + [migration_start_time] [int] DEFAULT (0) NOT NULL , + [migration_end_time] [int] DEFAULT (0) NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [phpbb_migrations] WITH NOCHECK ADD + CONSTRAINT [PK_phpbb_migrations] PRIMARY KEY CLUSTERED + ( + [migration_name] + ) ON [PRIMARY] +GO + + +/* Table: 'phpbb_modules' */ CREATE TABLE [phpbb_modules] ( @@ -755,6 +777,53 @@ GO /* + Table: 'phpbb_notification_types' +*/ +CREATE TABLE [phpbb_notification_types] ( + [notification_type] [varchar] (255) DEFAULT ('') NOT NULL , + [notification_type_enabled] [int] DEFAULT (1) NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [phpbb_notification_types] WITH NOCHECK ADD + CONSTRAINT [PK_phpbb_notification_types] PRIMARY KEY CLUSTERED + ( + [notification_type], + [notification_type_enabled] + ) ON [PRIMARY] +GO + + +/* + Table: 'phpbb_notifications' +*/ +CREATE TABLE [phpbb_notifications] ( + [notification_id] [int] IDENTITY (1, 1) NOT NULL , + [item_type] [varchar] (255) DEFAULT ('') NOT NULL , + [item_id] [int] DEFAULT (0) NOT NULL , + [item_parent_id] [int] DEFAULT (0) NOT NULL , + [user_id] [int] DEFAULT (0) NOT NULL , + [notification_read] [int] DEFAULT (0) NOT NULL , + [notification_time] [int] DEFAULT (1) NOT NULL , + [notification_data] [varchar] (4000) DEFAULT ('') NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [phpbb_notifications] WITH NOCHECK ADD + CONSTRAINT [PK_phpbb_notifications] PRIMARY KEY CLUSTERED + ( + [notification_id] + ) ON [PRIMARY] +GO + +CREATE INDEX [item_ident] ON [phpbb_notifications]([item_type], [item_id]) ON [PRIMARY] +GO + +CREATE INDEX [user] ON [phpbb_notifications]([user_id], [notification_read]) ON [PRIMARY] +GO + + +/* Table: 'phpbb_poll_options' */ CREATE TABLE [phpbb_poll_options] ( @@ -1117,8 +1186,11 @@ CREATE TABLE [phpbb_reports] ( [report_time] [int] DEFAULT (0) NOT NULL , [report_text] [text] DEFAULT ('') NOT NULL , [reported_post_text] [text] DEFAULT ('') NOT NULL , + [reported_post_uid] [varchar] (8) DEFAULT ('') NOT NULL , [reported_post_bitfield] [varchar] (255) DEFAULT ('') NOT NULL , - [reported_post_uid] [varchar] (8) DEFAULT ('') NOT NULL + [reported_post_enable_magic_url] [int] DEFAULT (1) NOT NULL , + [reported_post_enable_smilies] [int] DEFAULT (1) NOT NULL , + [reported_post_enable_bbcode] [int] DEFAULT (1) NOT NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO @@ -1487,6 +1559,19 @@ GO /* + Table: 'phpbb_user_notifications' +*/ +CREATE TABLE [phpbb_user_notifications] ( + [item_type] [varchar] (255) DEFAULT ('') NOT NULL , + [item_id] [int] DEFAULT (0) NOT NULL , + [user_id] [int] DEFAULT (0) NOT NULL , + [method] [varchar] (255) DEFAULT ('') NOT NULL , + [notify] [int] DEFAULT (1) NOT NULL +) ON [PRIMARY] +GO + + +/* Table: 'phpbb_user_group' */ CREATE TABLE [phpbb_user_group] ( diff --git a/phpBB/install/schemas/mysql_40_schema.sql b/phpBB/install/schemas/mysql_40_schema.sql index 3ef1b12842..86c60fdcd4 100644 --- a/phpBB/install/schemas/mysql_40_schema.sql +++ b/phpBB/install/schemas/mysql_40_schema.sql @@ -413,6 +413,19 @@ CREATE TABLE phpbb_moderator_cache ( ); +# Table: 'phpbb_migrations' +CREATE TABLE phpbb_migrations ( + migration_name varbinary(255) DEFAULT '' NOT NULL, + migration_depends_on blob NOT NULL, + migration_schema_done tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + migration_data_done tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + migration_data_state blob NOT NULL, + migration_start_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + migration_end_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + PRIMARY KEY (migration_name) +); + + # Table: 'phpbb_modules' CREATE TABLE phpbb_modules ( module_id mediumint(8) UNSIGNED NOT NULL auto_increment, @@ -433,6 +446,30 @@ CREATE TABLE phpbb_modules ( ); +# Table: 'phpbb_notification_types' +CREATE TABLE phpbb_notification_types ( + notification_type varbinary(255) DEFAULT '' NOT NULL, + notification_type_enabled tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + PRIMARY KEY (notification_type, notification_type_enabled) +); + + +# Table: 'phpbb_notifications' +CREATE TABLE phpbb_notifications ( + notification_id mediumint(8) UNSIGNED NOT NULL auto_increment, + item_type varbinary(255) DEFAULT '' NOT NULL, + item_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + item_parent_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + notification_read tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + notification_time int(11) UNSIGNED DEFAULT '1' NOT NULL, + notification_data blob NOT NULL, + PRIMARY KEY (notification_id), + KEY item_ident (item_type, item_id), + KEY user (user_id, notification_read) +); + + # Table: 'phpbb_poll_options' CREATE TABLE phpbb_poll_options ( poll_option_id tinyint(4) DEFAULT '0' NOT NULL, @@ -655,8 +692,11 @@ CREATE TABLE phpbb_reports ( report_time int(11) UNSIGNED DEFAULT '0' NOT NULL, report_text mediumblob NOT NULL, reported_post_text mediumblob NOT NULL, - reported_post_bitfield varbinary(255) DEFAULT '' NOT NULL, reported_post_uid varbinary(8) DEFAULT '' NOT NULL, + reported_post_bitfield varbinary(255) DEFAULT '' NOT NULL, + reported_post_enable_magic_url tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + reported_post_enable_smilies tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + reported_post_enable_bbcode tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, PRIMARY KEY (report_id), KEY post_id (post_id), KEY pm_id (pm_id) @@ -861,6 +901,16 @@ CREATE TABLE phpbb_topics_watch ( ); +# Table: 'phpbb_user_notifications' +CREATE TABLE phpbb_user_notifications ( + item_type varbinary(255) DEFAULT '' NOT NULL, + item_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + method varbinary(255) DEFAULT '' NOT NULL, + notify tinyint(1) UNSIGNED DEFAULT '1' NOT NULL +); + + # Table: 'phpbb_user_group' CREATE TABLE phpbb_user_group ( group_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/mysql_41_schema.sql b/phpBB/install/schemas/mysql_41_schema.sql index 8266be0b83..b6c6ef1c05 100644 --- a/phpBB/install/schemas/mysql_41_schema.sql +++ b/phpBB/install/schemas/mysql_41_schema.sql @@ -413,6 +413,19 @@ CREATE TABLE phpbb_moderator_cache ( ) CHARACTER SET `utf8` COLLATE `utf8_bin`; +# Table: 'phpbb_migrations' +CREATE TABLE phpbb_migrations ( + migration_name varchar(255) DEFAULT '' NOT NULL, + migration_depends_on text NOT NULL, + migration_schema_done tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + migration_data_done tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + migration_data_state text NOT NULL, + migration_start_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + migration_end_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + PRIMARY KEY (migration_name) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + # Table: 'phpbb_modules' CREATE TABLE phpbb_modules ( module_id mediumint(8) UNSIGNED NOT NULL auto_increment, @@ -433,6 +446,30 @@ CREATE TABLE phpbb_modules ( ) CHARACTER SET `utf8` COLLATE `utf8_bin`; +# Table: 'phpbb_notification_types' +CREATE TABLE phpbb_notification_types ( + notification_type varchar(255) DEFAULT '' NOT NULL, + notification_type_enabled tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + PRIMARY KEY (notification_type, notification_type_enabled) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + +# Table: 'phpbb_notifications' +CREATE TABLE phpbb_notifications ( + notification_id mediumint(8) UNSIGNED NOT NULL auto_increment, + item_type varchar(255) DEFAULT '' NOT NULL, + item_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + item_parent_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + notification_read tinyint(1) UNSIGNED DEFAULT '0' NOT NULL, + notification_time int(11) UNSIGNED DEFAULT '1' NOT NULL, + notification_data text NOT NULL, + PRIMARY KEY (notification_id), + KEY item_ident (item_type, item_id), + KEY user (user_id, notification_read) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + # Table: 'phpbb_poll_options' CREATE TABLE phpbb_poll_options ( poll_option_id tinyint(4) DEFAULT '0' NOT NULL, @@ -655,8 +692,11 @@ CREATE TABLE phpbb_reports ( report_time int(11) UNSIGNED DEFAULT '0' NOT NULL, report_text mediumtext NOT NULL, reported_post_text mediumtext NOT NULL, - reported_post_bitfield varchar(255) DEFAULT '' NOT NULL, reported_post_uid varchar(8) DEFAULT '' NOT NULL, + reported_post_bitfield varchar(255) DEFAULT '' NOT NULL, + reported_post_enable_magic_url tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + reported_post_enable_smilies tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, + reported_post_enable_bbcode tinyint(1) UNSIGNED DEFAULT '1' NOT NULL, PRIMARY KEY (report_id), KEY post_id (post_id), KEY pm_id (pm_id) @@ -861,6 +901,16 @@ CREATE TABLE phpbb_topics_watch ( ) CHARACTER SET `utf8` COLLATE `utf8_bin`; +# Table: 'phpbb_user_notifications' +CREATE TABLE phpbb_user_notifications ( + item_type varchar(255) DEFAULT '' NOT NULL, + item_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + method varchar(255) DEFAULT '' NOT NULL, + notify tinyint(1) UNSIGNED DEFAULT '1' NOT NULL +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + # Table: 'phpbb_user_group' CREATE TABLE phpbb_user_group ( group_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/oracle_schema.sql b/phpBB/install/schemas/oracle_schema.sql index ed8fabcd04..df19e88443 100644 --- a/phpBB/install/schemas/oracle_schema.sql +++ b/phpBB/install/schemas/oracle_schema.sql @@ -802,6 +802,22 @@ CREATE INDEX phpbb_moderator_cache_forum_id ON phpbb_moderator_cache (forum_id) / /* + Table: 'phpbb_migrations' +*/ +CREATE TABLE phpbb_migrations ( + migration_name varchar2(255) DEFAULT '' , + migration_depends_on clob DEFAULT '' , + migration_schema_done number(1) DEFAULT '0' NOT NULL, + migration_data_done number(1) DEFAULT '0' NOT NULL, + migration_data_state clob DEFAULT '' , + migration_start_time number(11) DEFAULT '0' NOT NULL, + migration_end_time number(11) DEFAULT '0' NOT NULL, + CONSTRAINT pk_phpbb_migrations PRIMARY KEY (migration_name) +) +/ + + +/* Table: 'phpbb_modules' */ CREATE TABLE phpbb_modules ( @@ -844,6 +860,54 @@ END; /* + Table: 'phpbb_notification_types' +*/ +CREATE TABLE phpbb_notification_types ( + notification_type varchar2(255) DEFAULT '' , + notification_type_enabled number(1) DEFAULT '1' NOT NULL, + CONSTRAINT pk_phpbb_notification_types PRIMARY KEY (notification_type, notification_type_enabled) +) +/ + + +/* + Table: 'phpbb_notifications' +*/ +CREATE TABLE phpbb_notifications ( + notification_id number(8) NOT NULL, + item_type varchar2(255) DEFAULT '' , + item_id number(8) DEFAULT '0' NOT NULL, + item_parent_id number(8) DEFAULT '0' NOT NULL, + user_id number(8) DEFAULT '0' NOT NULL, + notification_read number(1) DEFAULT '0' NOT NULL, + notification_time number(11) DEFAULT '1' NOT NULL, + notification_data clob DEFAULT '' , + CONSTRAINT pk_phpbb_notifications PRIMARY KEY (notification_id) +) +/ + +CREATE INDEX phpbb_notifications_item_ident ON phpbb_notifications (item_type, item_id) +/ +CREATE INDEX phpbb_notifications_user ON phpbb_notifications (user_id, notification_read) +/ + +CREATE SEQUENCE phpbb_notifications_seq +/ + +CREATE OR REPLACE TRIGGER t_phpbb_notifications +BEFORE INSERT ON phpbb_notifications +FOR EACH ROW WHEN ( + new.notification_id IS NULL OR new.notification_id = 0 +) +BEGIN + SELECT phpbb_notifications_seq.nextval + INTO :new.notification_id + FROM dual; +END; +/ + + +/* Table: 'phpbb_poll_options' */ CREATE TABLE phpbb_poll_options ( @@ -1222,8 +1286,11 @@ CREATE TABLE phpbb_reports ( report_time number(11) DEFAULT '0' NOT NULL, report_text clob DEFAULT '' , reported_post_text clob DEFAULT '' , - reported_post_bitfield varchar2(255) DEFAULT '' , reported_post_uid varchar2(8) DEFAULT '' , + reported_post_bitfield varchar2(255) DEFAULT '' , + reported_post_enable_magic_url number(1) DEFAULT '1' NOT NULL, + reported_post_enable_smilies number(1) DEFAULT '1' NOT NULL, + reported_post_enable_bbcode number(1) DEFAULT '1' NOT NULL, CONSTRAINT pk_phpbb_reports PRIMARY KEY (report_id) ) / @@ -1602,6 +1669,19 @@ CREATE INDEX phpbb_topics_watch_notify_stat ON phpbb_topics_watch (notify_status / /* + Table: 'phpbb_user_notifications' +*/ +CREATE TABLE phpbb_user_notifications ( + item_type varchar2(255) DEFAULT '' , + item_id number(8) DEFAULT '0' NOT NULL, + user_id number(8) DEFAULT '0' NOT NULL, + method varchar2(255) DEFAULT '' , + notify number(1) DEFAULT '1' NOT NULL +) +/ + + +/* Table: 'phpbb_user_group' */ CREATE TABLE phpbb_user_group ( diff --git a/phpBB/install/schemas/postgres_schema.sql b/phpBB/install/schemas/postgres_schema.sql index b45e40ed56..b2adc87f75 100644 --- a/phpBB/install/schemas/postgres_schema.sql +++ b/phpBB/install/schemas/postgres_schema.sql @@ -576,6 +576,21 @@ CREATE INDEX phpbb_moderator_cache_disp_idx ON phpbb_moderator_cache (display_on CREATE INDEX phpbb_moderator_cache_forum_id ON phpbb_moderator_cache (forum_id); /* + Table: 'phpbb_migrations' +*/ +CREATE TABLE phpbb_migrations ( + migration_name varchar(255) DEFAULT '' NOT NULL, + migration_depends_on varchar(8000) DEFAULT '' NOT NULL, + migration_schema_done INT2 DEFAULT '0' NOT NULL CHECK (migration_schema_done >= 0), + migration_data_done INT2 DEFAULT '0' NOT NULL CHECK (migration_data_done >= 0), + migration_data_state varchar(8000) DEFAULT '' NOT NULL, + migration_start_time INT4 DEFAULT '0' NOT NULL CHECK (migration_start_time >= 0), + migration_end_time INT4 DEFAULT '0' NOT NULL CHECK (migration_end_time >= 0), + PRIMARY KEY (migration_name) +); + + +/* Table: 'phpbb_modules' */ CREATE SEQUENCE phpbb_modules_seq; @@ -600,6 +615,36 @@ CREATE INDEX phpbb_modules_module_enabled ON phpbb_modules (module_enabled); CREATE INDEX phpbb_modules_class_left_id ON phpbb_modules (module_class, left_id); /* + Table: 'phpbb_notification_types' +*/ +CREATE TABLE phpbb_notification_types ( + notification_type varchar(255) DEFAULT '' NOT NULL, + notification_type_enabled INT2 DEFAULT '1' NOT NULL CHECK (notification_type_enabled >= 0), + PRIMARY KEY (notification_type, notification_type_enabled) +); + + +/* + Table: 'phpbb_notifications' +*/ +CREATE SEQUENCE phpbb_notifications_seq; + +CREATE TABLE phpbb_notifications ( + notification_id INT4 DEFAULT nextval('phpbb_notifications_seq'), + item_type varchar(255) DEFAULT '' NOT NULL, + item_id INT4 DEFAULT '0' NOT NULL CHECK (item_id >= 0), + item_parent_id INT4 DEFAULT '0' NOT NULL CHECK (item_parent_id >= 0), + user_id INT4 DEFAULT '0' NOT NULL CHECK (user_id >= 0), + notification_read INT2 DEFAULT '0' NOT NULL CHECK (notification_read >= 0), + notification_time INT4 DEFAULT '1' NOT NULL CHECK (notification_time >= 0), + notification_data varchar(4000) DEFAULT '' NOT NULL, + PRIMARY KEY (notification_id) +); + +CREATE INDEX phpbb_notifications_item_ident ON phpbb_notifications (item_type, item_id); +CREATE INDEX phpbb_notifications_user ON phpbb_notifications (user_id, notification_read); + +/* Table: 'phpbb_poll_options' */ CREATE TABLE phpbb_poll_options ( @@ -861,8 +906,11 @@ CREATE TABLE phpbb_reports ( report_time INT4 DEFAULT '0' NOT NULL CHECK (report_time >= 0), report_text TEXT DEFAULT '' NOT NULL, reported_post_text TEXT DEFAULT '' NOT NULL, - reported_post_bitfield varchar(255) DEFAULT '' NOT NULL, reported_post_uid varchar(8) DEFAULT '' NOT NULL, + reported_post_bitfield varchar(255) DEFAULT '' NOT NULL, + reported_post_enable_magic_url INT2 DEFAULT '1' NOT NULL CHECK (reported_post_enable_magic_url >= 0), + reported_post_enable_smilies INT2 DEFAULT '1' NOT NULL CHECK (reported_post_enable_smilies >= 0), + reported_post_enable_bbcode INT2 DEFAULT '1' NOT NULL CHECK (reported_post_enable_bbcode >= 0), PRIMARY KEY (report_id) ); @@ -1106,6 +1154,18 @@ CREATE INDEX phpbb_topics_watch_user_id ON phpbb_topics_watch (user_id); CREATE INDEX phpbb_topics_watch_notify_stat ON phpbb_topics_watch (notify_status); /* + Table: 'phpbb_user_notifications' +*/ +CREATE TABLE phpbb_user_notifications ( + item_type varchar(255) DEFAULT '' NOT NULL, + item_id INT4 DEFAULT '0' NOT NULL CHECK (item_id >= 0), + user_id INT4 DEFAULT '0' NOT NULL CHECK (user_id >= 0), + method varchar(255) DEFAULT '' NOT NULL, + notify INT2 DEFAULT '1' NOT NULL CHECK (notify >= 0) +); + + +/* Table: 'phpbb_user_group' */ CREATE TABLE phpbb_user_group ( diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index d1df20c928..c191bb5b2b 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -175,6 +175,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jquery_cdn', INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jumpbox', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_moderators', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_notifications', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online_guests', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online_time', '5'); @@ -772,4 +773,10 @@ INSERT INTO phpbb_extensions (group_id, extension) VALUES (9, 'mp3'); INSERT INTO phpbb_extensions (group_id, extension) VALUES (9, 'ogg'); INSERT INTO phpbb_extensions (group_id, extension) VALUES (9, 'ogm'); +# User Notification Options (for first user) +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('phpbb_notification_type_post', 0, 2, ''); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('phpbb_notification_type_post', 0, 2, 'phpbb_notification_method_email'); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('phpbb_notification_type_topic', 0, 2, ''); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('phpbb_notification_type_topic', 0, 2, 'phpbb_notification_method_email'); + # POSTGRES COMMIT # diff --git a/phpBB/install/schemas/sqlite_schema.sql b/phpBB/install/schemas/sqlite_schema.sql index 5b3cd8dcc5..4375ce01ad 100644 --- a/phpBB/install/schemas/sqlite_schema.sql +++ b/phpBB/install/schemas/sqlite_schema.sql @@ -401,6 +401,19 @@ CREATE TABLE phpbb_moderator_cache ( CREATE INDEX phpbb_moderator_cache_disp_idx ON phpbb_moderator_cache (display_on_index); CREATE INDEX phpbb_moderator_cache_forum_id ON phpbb_moderator_cache (forum_id); +# Table: 'phpbb_migrations' +CREATE TABLE phpbb_migrations ( + migration_name varchar(255) NOT NULL DEFAULT '', + migration_depends_on text(65535) NOT NULL DEFAULT '', + migration_schema_done INTEGER UNSIGNED NOT NULL DEFAULT '0', + migration_data_done INTEGER UNSIGNED NOT NULL DEFAULT '0', + migration_data_state text(65535) NOT NULL DEFAULT '', + migration_start_time INTEGER UNSIGNED NOT NULL DEFAULT '0', + migration_end_time INTEGER UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (migration_name) +); + + # Table: 'phpbb_modules' CREATE TABLE phpbb_modules ( module_id INTEGER PRIMARY KEY NOT NULL , @@ -420,6 +433,29 @@ CREATE INDEX phpbb_modules_left_right_id ON phpbb_modules (left_id, right_id); CREATE INDEX phpbb_modules_module_enabled ON phpbb_modules (module_enabled); CREATE INDEX phpbb_modules_class_left_id ON phpbb_modules (module_class, left_id); +# Table: 'phpbb_notification_types' +CREATE TABLE phpbb_notification_types ( + notification_type varchar(255) NOT NULL DEFAULT '', + notification_type_enabled INTEGER UNSIGNED NOT NULL DEFAULT '1', + PRIMARY KEY (notification_type, notification_type_enabled) +); + + +# Table: 'phpbb_notifications' +CREATE TABLE phpbb_notifications ( + notification_id INTEGER PRIMARY KEY NOT NULL , + item_type varchar(255) NOT NULL DEFAULT '', + item_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + item_parent_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + user_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + notification_read INTEGER UNSIGNED NOT NULL DEFAULT '0', + notification_time INTEGER UNSIGNED NOT NULL DEFAULT '1', + notification_data text(65535) NOT NULL DEFAULT '' +); + +CREATE INDEX phpbb_notifications_item_ident ON phpbb_notifications (item_type, item_id); +CREATE INDEX phpbb_notifications_user ON phpbb_notifications (user_id, notification_read); + # Table: 'phpbb_poll_options' CREATE TABLE phpbb_poll_options ( poll_option_id tinyint(4) NOT NULL DEFAULT '0', @@ -636,8 +672,11 @@ CREATE TABLE phpbb_reports ( report_time INTEGER UNSIGNED NOT NULL DEFAULT '0', report_text mediumtext(16777215) NOT NULL DEFAULT '', reported_post_text mediumtext(16777215) NOT NULL DEFAULT '', + reported_post_uid varchar(8) NOT NULL DEFAULT '', reported_post_bitfield varchar(255) NOT NULL DEFAULT '', - reported_post_uid varchar(8) NOT NULL DEFAULT '' + reported_post_enable_magic_url INTEGER UNSIGNED NOT NULL DEFAULT '1', + reported_post_enable_smilies INTEGER UNSIGNED NOT NULL DEFAULT '1', + reported_post_enable_bbcode INTEGER UNSIGNED NOT NULL DEFAULT '1' ); CREATE INDEX phpbb_reports_post_id ON phpbb_reports (post_id); @@ -835,6 +874,16 @@ CREATE INDEX phpbb_topics_watch_topic_id ON phpbb_topics_watch (topic_id); CREATE INDEX phpbb_topics_watch_user_id ON phpbb_topics_watch (user_id); CREATE INDEX phpbb_topics_watch_notify_stat ON phpbb_topics_watch (notify_status); +# Table: 'phpbb_user_notifications' +CREATE TABLE phpbb_user_notifications ( + item_type varchar(255) NOT NULL DEFAULT '', + item_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + user_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + method varchar(255) NOT NULL DEFAULT '', + notify INTEGER UNSIGNED NOT NULL DEFAULT '1' +); + + # Table: 'phpbb_user_group' CREATE TABLE phpbb_user_group ( group_id INTEGER UNSIGNED NOT NULL DEFAULT '0', diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 2235b14f04..a21831bd39 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -252,6 +252,8 @@ $lang = array_merge($lang, array( 'IP' => 'User IP', 'IP_HOSTNAME' => 'IP addresses or hostnames', + 'LOAD_NOTIFICATIONS' => 'Display Notifications', + 'LOAD_NOTIFICATIONS_EXPLAIN' => 'Display the notifications list on every page (typically in the header).', 'LOGGED_IN_AS' => 'You are logged in as:', 'LOGIN_ADMIN' => 'To administer the board you must be an authenticated user.', 'LOGIN_ADMIN_CONFIRM' => 'To administer the board you must re-authenticate yourself.', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 94aa4f7642..c7ef3df6a1 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -119,6 +119,17 @@ $lang = array_merge($lang, array( 1 => 'Users browsing this forum: %2$s and %1$d guest', 2 => 'Users browsing this forum: %2$s and %1$d guests', ), + 'BUTTON_EDIT' => 'Edit', + 'BUTTON_FORUM_LOCKED' => 'Locked', + 'BUTTON_NEW_TOPIC' => 'New Topic', + 'BUTTON_PM' => 'PM', + 'BUTTON_PM_FORWARD' => 'Forward', + 'BUTTON_PM_NEW' => 'New PM', + 'BUTTON_PM_REPLY' => 'Send Reply', + 'BUTTON_PM_REPLY_ALL' => 'Reply All', + 'BUTTON_POST_REPLY' => 'Post Reply', + 'BUTTON_QUOTE' => 'Quote', + 'BUTTON_TOPIC_LOCKED' => 'Locked', 'BYTES' => 'Bytes', 'CANCEL' => 'Cancel', @@ -176,6 +187,7 @@ $lang = array_merge($lang, array( 'ELLIPSIS' => '…', 'EMAIL' => 'Email', // Short form for EMAIL_ADDRESS 'EMAIL_ADDRESS' => 'Email address', + 'EMAIL_INVALID_EMAIL' => 'The email address you entered is invalid.', 'EMAIL_SMTP_ERROR_RESPONSE' => 'Ran into problems sending email at <strong>Line %1$s</strong>. Response: %2$s.', 'EMPTY_SUBJECT' => 'You must specify a subject when posting a new topic.', 'EMPTY_MESSAGE_SUBJECT' => 'You must specify a subject when composing a new message.', @@ -350,6 +362,7 @@ $lang = array_merge($lang, array( 'MARK' => 'Mark', 'MARK_ALL' => 'Mark all', 'MARK_FORUMS_READ' => 'Mark forums read', + 'MARK_READ' => 'Mark read', 'MARK_SUBFORUMS_READ' => 'Mark subforums read', 'MB' => 'MB', 'MIB' => 'MiB', @@ -384,10 +397,31 @@ $lang = array_merge($lang, array( 'NEXT_STEP' => 'Next', 'NEVER' => 'Never', 'NO' => 'No', + 'NO_NOTIFICATIONS' => 'You have no notifications', 'NOT_ALLOWED_MANAGE_GROUP' => 'You are not allowed to manage this group.', 'NOT_AUTHORISED' => 'You are not authorised to access this area.', 'NOT_WATCHING_FORUM' => 'You are no longer subscribed to updates on this forum.', 'NOT_WATCHING_TOPIC' => 'You are no longer subscribed to this topic.', + 'NOTIFICATIONS' => 'Notifications', + 'NOTIFICATIONS_COUNT' => array( + 0 => '%d Notifications', + 1 => '<strong>%d</strong> Notification', + 2 => '<strong>%d</strong> Notifications', + ), + 'NOTIFICATION_BOOKMARK' => '%1$s replied to the topic "%2$s" you have bookmarked.', + 'NOTIFICATION_PM' => '%1$s sent you a Private Message "%2$s".', + 'NOTIFICATION_POST' => '%1$s replied to the topic "%2$s".', + 'NOTIFICATION_POST_APPROVED' => 'Your post was approved "%2$s".', + 'NOTIFICATION_POST_DISAPPROVED' => 'Your post "%1$s" was disapproved for reason: "%2$s".', + 'NOTIFICATION_POST_IN_QUEUE' => 'A new post titled "%2$s" was posted by %1$s and needs approval.', + 'NOTIFICATION_QUOTE' => '%1$s quoted you in the post "%2$s".', + 'NOTIFICATION_REPORT_PM' => '%1$s reported a Private Message "%2$s" for reason: "%3$s".', + 'NOTIFICATION_REPORT_POST' => '%1$s reported a post "%2$s" for reason: "%3$s".', + 'NOTIFICATION_REPORT_CLOSED' => '%1$s closed the report you made for "%2$s".', + 'NOTIFICATION_TOPIC' => '%1$s posted a new topic "%2$s" in the forum "%3$s".', + 'NOTIFICATION_TOPIC_APPROVED' => 'Your topic "%2$s" in the forum "%3$s" was approved.', + 'NOTIFICATION_TOPIC_DISAPPROVED' => 'Your topic "%1$s" was disapproved for reason: "%2$s".', + 'NOTIFICATION_TOPIC_IN_QUEUE' => 'A new topic titled "%2$s" was posted by %1$s and needs approval.', 'NOTIFY_ADMIN' => 'Please notify the board administrator or webmaster.', 'NOTIFY_ADMIN_EMAIL' => 'Please notify the board administrator or webmaster: <a href="mailto:%1$s">%1$s</a>', 'NO_ACCESS_ATTACHMENT' => 'You are not allowed to access this file.', @@ -579,6 +613,7 @@ $lang = array_merge($lang, array( 'SEARCH_UNREAD' => 'View unread posts', 'SEARCH_USER_POSTS' => 'Search user’s posts', 'SECONDS' => 'Seconds', + 'SEE_ALL' => 'See All', 'SELECT' => 'Select', 'SELECT_ALL_CODE' => 'Select all', 'SELECT_DESTINATION_FORUM' => 'Please select a destination forum', diff --git a/phpBB/language/en/email/bookmark.txt b/phpBB/language/en/email/bookmark.txt new file mode 100644 index 0000000000..95f17b5693 --- /dev/null +++ b/phpBB/language/en/email/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a topic you bookmarked, "{TOPIC_TITLE}" at "{SITENAME}", has received a reply since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/forum_notify.txt b/phpBB/language/en/email/forum_notify.txt index 490780a0a6..66f3a68689 100644 --- a/phpBB/language/en/email/forum_notify.txt +++ b/phpBB/language/en/email/forum_notify.txt @@ -2,7 +2,7 @@ Subject: Forum post notification - "{FORUM_NAME}" Hello {USERNAME}, -You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new reply to the topic "{TOPIC_TITLE}"<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit. You can use the following link to view the last unread reply, no more notifications will be sent until you visit the topic. +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new reply to the topic "{TOPIC_TITLE}"<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit. You can use the following link to view the last unread reply, no more notifications will be sent until you visit the topic. {U_NEWEST_POST} diff --git a/phpBB/language/en/email/newtopic_notify.txt b/phpBB/language/en/email/newtopic_notify.txt index eda1370938..bf6799e5be 100644 --- a/phpBB/language/en/email/newtopic_notify.txt +++ b/phpBB/language/en/email/newtopic_notify.txt @@ -2,7 +2,7 @@ Subject: New topic notification - "{FORUM_NAME}" Hello {USERNAME}, -You are receiving this notification because you are watching the forum, "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. {U_FORUM} @@ -10,4 +10,4 @@ If you no longer wish to watch this forum you can either click the "Unsubscribe {U_STOP_WATCHING_FORUM} -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/email/post_disapproved.txt b/phpBB/language/en/email/post_disapproved.txt index 3bc64bb611..2f8a8381cb 100644 --- a/phpBB/language/en/email/post_disapproved.txt +++ b/phpBB/language/en/email/post_disapproved.txt @@ -9,4 +9,4 @@ The following reason was given for the disapproval: {REASON} -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/email/post_in_queue.txt b/phpBB/language/en/email/post_in_queue.txt new file mode 100644 index 0000000000..8d56ce6c4d --- /dev/null +++ b/phpBB/language/en/email/post_in_queue.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" needs approval. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/privmsg_notify.txt b/phpBB/language/en/email/privmsg_notify.txt index d3a86cc73c..41fdbb782c 100644 --- a/phpBB/language/en/email/privmsg_notify.txt +++ b/phpBB/language/en/email/privmsg_notify.txt @@ -12,4 +12,4 @@ You can view your new message by clicking on the following link: You have requested that you be notified on this event, remember that you can always choose not to be notified of new messages by changing the appropriate setting in your profile. -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/email/quote.txt b/phpBB/language/en/email/quote.txt new file mode 100644 index 0000000000..2b9525801f --- /dev/null +++ b/phpBB/language/en/email/quote.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because "{AUTHOR_NAME}" quoted you in the topic "{TOPIC_TITLE}" at "{SITENAME}". You can use the following link to view the reply made. + +If you want to view the quoted post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies quoting you, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/report_pm.txt b/phpBB/language/en/email/report_pm.txt new file mode 100644 index 0000000000..66ae82d074 --- /dev/null +++ b/phpBB/language/en/email/report_pm.txt @@ -0,0 +1,14 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a Private Message titled "{SUBJECT}" by "{AUTHOR_NAME}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/report_post.txt b/phpBB/language/en/email/report_post.txt new file mode 100644 index 0000000000..46983be1ed --- /dev/null +++ b/phpBB/language/en/email/report_post.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/bookmark.txt b/phpBB/language/en/email/short/bookmark.txt new file mode 100644 index 0000000000..95f17b5693 --- /dev/null +++ b/phpBB/language/en/email/short/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a topic you bookmarked, "{TOPIC_TITLE}" at "{SITENAME}", has received a reply since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/newtopic_notify.txt b/phpBB/language/en/email/short/newtopic_notify.txt new file mode 100644 index 0000000000..bf6799e5be --- /dev/null +++ b/phpBB/language/en/email/short/newtopic_notify.txt @@ -0,0 +1,13 @@ +Subject: New topic notification - "{FORUM_NAME}" + +Hello {USERNAME}, + +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. + +{U_FORUM} + +If you no longer wish to watch this forum you can either click the "Unsubscribe forum" link found in the forum above, or by clicking the following link: + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/post_approved.txt b/phpBB/language/en/email/short/post_approved.txt new file mode 100644 index 0000000000..e715b54026 --- /dev/null +++ b/phpBB/language/en/email/short/post_approved.txt @@ -0,0 +1,14 @@ +Subject: Post approved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + + +{EMAIL_SIG}
\ No newline at end of file diff --git a/phpBB/language/en/email/short/post_disapproved.txt b/phpBB/language/en/email/short/post_disapproved.txt new file mode 100644 index 0000000000..2f8a8381cb --- /dev/null +++ b/phpBB/language/en/email/short/post_disapproved.txt @@ -0,0 +1,12 @@ +Subject: Post disapproved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/post_in_queue.txt b/phpBB/language/en/email/short/post_in_queue.txt new file mode 100644 index 0000000000..8d56ce6c4d --- /dev/null +++ b/phpBB/language/en/email/short/post_in_queue.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" needs approval. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/privmsg_notify.txt b/phpBB/language/en/email/short/privmsg_notify.txt new file mode 100644 index 0000000000..41fdbb782c --- /dev/null +++ b/phpBB/language/en/email/short/privmsg_notify.txt @@ -0,0 +1,15 @@ +Subject: New private message has arrived + +Hello {USERNAME}, + +You have received a new private message from "{AUTHOR_NAME}" to your account on "{SITENAME}" with the following subject: + +{SUBJECT} + +You can view your new message by clicking on the following link: + +{U_VIEW_MESSAGE} + +You have requested that you be notified on this event, remember that you can always choose not to be notified of new messages by changing the appropriate setting in your profile. + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/quote.txt b/phpBB/language/en/email/short/quote.txt new file mode 100644 index 0000000000..2b9525801f --- /dev/null +++ b/phpBB/language/en/email/short/quote.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because "{AUTHOR_NAME}" quoted you in the topic "{TOPIC_TITLE}" at "{SITENAME}". You can use the following link to view the reply made. + +If you want to view the quoted post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies quoting you, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/report_pm.txt b/phpBB/language/en/email/short/report_pm.txt new file mode 100644 index 0000000000..66ae82d074 --- /dev/null +++ b/phpBB/language/en/email/short/report_pm.txt @@ -0,0 +1,14 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a Private Message titled "{SUBJECT}" by "{AUTHOR_NAME}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/report_post.txt b/phpBB/language/en/email/short/report_post.txt new file mode 100644 index 0000000000..46983be1ed --- /dev/null +++ b/phpBB/language/en/email/short/report_post.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_approved.txt b/phpBB/language/en/email/short/topic_approved.txt new file mode 100644 index 0000000000..0b09918b89 --- /dev/null +++ b/phpBB/language/en/email/short/topic_approved.txt @@ -0,0 +1,11 @@ +Subject: Topic approved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_disapproved.txt b/phpBB/language/en/email/short/topic_disapproved.txt new file mode 100644 index 0000000000..a4bd9c151e --- /dev/null +++ b/phpBB/language/en/email/short/topic_disapproved.txt @@ -0,0 +1,12 @@ +Subject: Topic disapproved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_in_queue.txt b/phpBB/language/en/email/short/topic_in_queue.txt new file mode 100644 index 0000000000..ae8f9e2484 --- /dev/null +++ b/phpBB/language/en/email/short/topic_in_queue.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the topic "{TOPIC_TITLE}" at "{SITENAME}" needs approval. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_notify.txt b/phpBB/language/en/email/short/topic_notify.txt new file mode 100644 index 0000000000..472375fb22 --- /dev/null +++ b/phpBB/language/en/email/short/topic_notify.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because you are watching the topic "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to watch this topic you can either click the "Unsubscribe topic" link found at the bottom of the topic above, or by clicking the following link: + +{U_STOP_WATCHING_TOPIC} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/topic_approved.txt b/phpBB/language/en/email/topic_approved.txt index ffda378d30..0b09918b89 100644 --- a/phpBB/language/en/email/topic_approved.txt +++ b/phpBB/language/en/email/topic_approved.txt @@ -8,4 +8,4 @@ If you want to view the topic, click the following link: {U_VIEW_TOPIC} -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/email/topic_disapproved.txt b/phpBB/language/en/email/topic_disapproved.txt index 49ef58bf39..a4bd9c151e 100644 --- a/phpBB/language/en/email/topic_disapproved.txt +++ b/phpBB/language/en/email/topic_disapproved.txt @@ -9,4 +9,4 @@ The following reason was given for the disapproval: {REASON} -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/email/topic_in_queue.txt b/phpBB/language/en/email/topic_in_queue.txt new file mode 100644 index 0000000000..ae8f9e2484 --- /dev/null +++ b/phpBB/language/en/email/topic_in_queue.txt @@ -0,0 +1,17 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the topic "{TOPIC_TITLE}" at "{SITENAME}" needs approval. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/phpBB/language/en/email/topic_notify.txt b/phpBB/language/en/email/topic_notify.txt index fcfbcc2abd..472375fb22 100644 --- a/phpBB/language/en/email/topic_notify.txt +++ b/phpBB/language/en/email/topic_notify.txt @@ -2,7 +2,7 @@ Subject: Topic reply notification - "{TOPIC_TITLE}" Hello {USERNAME}, -You are receiving this notification because you are watching the topic, "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. +You are receiving this notification because you are watching the topic "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply<!-- IF AUTHOR_NAME --> by {AUTHOR_NAME}<!-- ENDIF --> since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. If you want to view the newest post made since your last visit, click the following link: {U_NEWEST_POST} @@ -17,4 +17,4 @@ If you no longer wish to watch this topic you can either click the "Unsubscribe {U_STOP_WATCHING_TOPIC} -{EMAIL_SIG}
\ No newline at end of file +{EMAIL_SIG} diff --git a/phpBB/language/en/install.php b/phpBB/language/en/install.php index 1c45deae11..7607512eab 100644 --- a/phpBB/language/en/install.php +++ b/phpBB/language/en/install.php @@ -374,7 +374,7 @@ $lang = array_merge($lang, array( // Updater $lang = array_merge($lang, array( - 'ALL_FILES_UP_TO_DATE' => 'All files are up to date with the latest phpBB version. You should now <a href="../ucp.php?mode=login">login to your board</a> and check if everything is working fine. Do not forget to delete, rename or move your install directory! Please send us updated information about your server and board configurations from the <a href="../ucp.php?mode=login&redirect=adm/index.php%3Fi=send_statistics%26mode=send_statistics">Send statistics</a> module in your ACP.', + 'ALL_FILES_UP_TO_DATE' => 'All files are up to date with the latest phpBB version. You should now <a href="%1$s">login to your board</a> and check if everything is working fine. Do not forget to delete, rename or move your install directory! Please send us updated information about your server and board configurations from the <a href="%2$s">Send statistics</a> module in your ACP.', 'ARCHIVE_FILE' => 'Source file within archive', 'BACK' => 'Back', @@ -397,7 +397,10 @@ $lang = array_merge($lang, array( 'CURRENT_VERSION' => 'Current version', 'DATABASE_TYPE' => 'Database type', + 'DATABASE_UPDATE_COMPLETE' => 'Database updater has completed!', + 'DATABASE_UPDATE_CONTINUE' => 'Continue database update.', 'DATABASE_UPDATE_INFO_OLD' => 'The database update file within the install directory is outdated. Please make sure you uploaded the correct version of the file.', + 'DATABASE_UPDATE_NOT_COMPLETED' => 'The database update has not yet completed.', 'DELETE_USER_REMOVE' => 'Delete user and remove posts', 'DELETE_USER_RETAIN' => 'Delete user but keep posts', 'DESTINATION' => 'Destination file', diff --git a/phpBB/language/en/migrator.php b/phpBB/language/en/migrator.php new file mode 100644 index 0000000000..84074c391c --- /dev/null +++ b/phpBB/language/en/migrator.php @@ -0,0 +1,56 @@ +<?php +/** +* +* migrator [English] +* +* @package language +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'CONFIG_ALREADY_EXIST' => 'The config setting "%s" unexpectedly already exists.', + 'CONFIG_NOT_EXIST' => 'The config setting "%s" unexpectedly does not exist.', + + 'GROUP_NOT_EXIST' => 'The group "%s" unexpectedly does not exist.', + + 'MIGRATION_EXCEPTION_ERROR' => 'Something went wrong during the request and an exception was thrown. The changes made before the error occurred were reversed to the best of our abilities, but you should check the board for errors.', + 'MIGRATION_NOT_FULFILLABLE' => 'The migration "%1$s" is not fulfillable, missing migration "%2$s".', + + 'MODULE_ALREADY_EXIST' => 'The module "%s" unexpectedly already exists.', + 'MODULE_ERROR' => 'An error occured while creating a module: %s', + 'MODULE_INFO_FILE_NOT_EXIST' => 'A required module info file is missing: %2$s', + 'MODULE_NOT_EXIST' => 'A required module does not exist: %s', + 'MODULE_NOT_REMOVABLE' => 'Module %1$s was unable to be removed: %2$s', + + 'PERMISSION_ALREADY_EXIST' => 'The permission setting "%s" unexpectedly already exists.', + 'PERMISSION_NOT_EXIST' => 'The permission setting "%s" unexpectedly does not exist.', + + 'ROLE_NOT_EXIST' => 'The permission role "%s" unexpectedly does not exist.', +)); diff --git a/phpBB/language/en/posting.php b/phpBB/language/en/posting.php index c5fc98e0f8..1ed312f68c 100644 --- a/phpBB/language/en/posting.php +++ b/phpBB/language/en/posting.php @@ -53,9 +53,9 @@ $lang = array_merge($lang, array( 'BBCODE_IS_OFF' => '%sBBCode%s is <em>OFF</em>', 'BBCODE_IS_ON' => '%sBBCode%s is <em>ON</em>', 'BBCODE_I_HELP' => 'Italic text: [i]text[/i]', - 'BBCODE_L_HELP' => 'List: [list]text[/list]', - 'BBCODE_LISTITEM_HELP' => 'List item: [*]text[/*]', - 'BBCODE_O_HELP' => 'Ordered list: [list=]text[/list]', + 'BBCODE_L_HELP' => 'List: [list][*]text[/list]', + 'BBCODE_LISTITEM_HELP' => 'List item: [*]text', + 'BBCODE_O_HELP' => 'Ordered list: e.g. [list=1][*]First point[/list] or [list=a][*]Point a[/list]', 'BBCODE_P_HELP' => 'Insert image: [img]http://image_url[/img]', 'BBCODE_Q_HELP' => 'Quote text: [quote]text[/quote]', 'BBCODE_S_HELP' => 'Font colour: [color=red]text[/color] Tip: you can also use color=#FF0000', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 267ae00710..c8b0686a5b 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -173,7 +173,6 @@ $lang = array_merge($lang, array( 'EDIT_DRAFT_EXPLAIN' => 'Here you are able to edit your draft. Drafts do not contain attachment and poll information.', 'EMAIL_BANNED_EMAIL' => 'The email address you entered is not allowed to be used.', - 'EMAIL_INVALID_EMAIL' => 'The email address you entered is invalid.', 'EMAIL_REMIND' => 'This must be the email address associated with your account. If you have not changed this via your user control panel then it is the email address you registered your account with.', 'EMAIL_TAKEN_EMAIL' => 'The entered email address is already in use.', 'EMPTY_DRAFT' => 'You must enter a message to submit your changes.', @@ -288,6 +287,25 @@ $lang = array_merge($lang, array( 'NEW_PASSWORD' => 'New password', 'NEW_PASSWORD_CONFIRM_EMPTY' => 'You did not enter a confirm password.', 'NEW_PASSWORD_ERROR' => 'The passwords you entered do not match.', + + 'NOTIFICATIONS_MARK_ALL_READ' => 'Mark all notifications read', + 'NOTIFICATIONS_MARK_ALL_READ_CONFIRM' => 'Are you sure you want to mark all notifications read?', + 'NOTIFICATIONS_MARK_ALL_READ_SUCCESS' => 'All notifications have been marked read.', + 'NOTIFICATION_GROUP_MISCELLANEOUS' => 'Miscellaneous Notifications', + 'NOTIFICATION_GROUP_MODERATION' => 'Moderation Notifications', + 'NOTIFICATION_GROUP_POSTING' => 'Posting Notifications', + 'NOTIFICATION_METHOD_EMAIL' => 'Email', + 'NOTIFICATION_METHOD_JABBER' => 'Jabber', + 'NOTIFICATION_TYPE' => 'Notification type', + 'NOTIFICATION_TYPE_BOOKMARK' => 'Someone replies to a topic you have bookmarked', + 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE' => 'A post or topic needs approval', + 'NOTIFICATION_TYPE_MODERATION_QUEUE' => 'Your topics/posts are approved or disapproved by a moderator', + 'NOTIFICATION_TYPE_PM' => 'Someone sends you a private message', + 'NOTIFICATION_TYPE_POST' => 'Someone replies to a topic to which you are subscribed', + 'NOTIFICATION_TYPE_QUOTE' => 'Someone quotes you in a post', + 'NOTIFICATION_TYPE_REPORT' => 'Someone reports a post', + 'NOTIFICATION_TYPE_TOPIC' => 'Someone creates a topic in a forum to which you are subscribed', + 'NOTIFY_METHOD' => 'Notification method', 'NOTIFY_METHOD_BOTH' => 'Both', 'NOTIFY_METHOD_EMAIL' => 'Email only', @@ -458,6 +476,11 @@ $lang = array_merge($lang, array( 'UCP_MSNM' => 'Windows Live Messenger', 'UCP_NO_ATTACHMENTS' => 'You have posted no files.', + 'UCP_NOTIFICATION_LIST' => 'Manage notifications', + 'UCP_NOTIFICATION_LIST_EXPLAIN' => 'Here you may view all past notifications.', + 'UCP_NOTIFICATION_OPTIONS' => 'Edit notification options', + 'UCP_NOTIFICATION_OPTIONS_EXPLAIN' => 'Here you can set your preferred notification methods for the board.', + 'UCP_PREFS' => 'Board preferences', 'UCP_PREFS_PERSONAL' => 'Edit global settings', 'UCP_PREFS_POST' => 'Edit posting defaults', diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 72b02e57bf..ea4d2a4e91 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -649,7 +649,7 @@ switch ($mode) 'S_GROUP_OPTIONS' => $group_options, 'S_CUSTOM_FIELDS' => (isset($profile_fields['row']) && sizeof($profile_fields['row'])) ? true : false, - 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', + 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', 'U_USER_BAN' => ($auth->acl_get('m_ban') && $user_id != $user->data['user_id']) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=ban&mode=user&u=' . $user_id, true, $user->session_id) : '', 'U_MCP_QUEUE' => ($auth->acl_getf_global('m_approve')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue', true, $user->session_id) : '', @@ -1033,7 +1033,7 @@ switch ($mode) // We validate form and field here, only id/class allowed $form = (!preg_match('/^[a-z0-9_-]+$/i', $form)) ? '' : $form; $field = (!preg_match('/^[a-z0-9_-]+$/i', $field)) ? '' : $field; - if (($mode == 'searchuser' || sizeof(array_intersect($request->variable_names(phpbb_request_interface::GET), $search_params)) > 0) && ($config['load_search'] || $auth->acl_get('a_'))) + if ((($mode == '' || $mode == 'searchuser') || sizeof(array_intersect($request->variable_names(phpbb_request_interface::GET), $search_params)) > 0) && ($config['load_search'] || $auth->acl_get('a_'))) { $username = request_var('username', '', true); $email = strtolower(request_var('email', '')); @@ -1388,7 +1388,7 @@ switch ($mode) } // Some search user specific data - if ($mode == 'searchuser' && ($config['load_search'] || $auth->acl_get('a_'))) + if (($mode == '' || $mode == 'searchuser') && ($config['load_search'] || $auth->acl_get('a_'))) { $group_selected = request_var('search_group_id', 0); $s_group_select = '<option value="0"' . ((!$group_selected) ? ' selected="selected"' : '') . '> </option>'; @@ -1459,7 +1459,7 @@ switch ($mode) 'S_IP_SEARCH_ALLOWED' => ($auth->acl_getf_global('m_info')) ? true : false, 'S_EMAIL_SEARCH_ALLOWED'=> ($auth->acl_get('a_user')) ? true : false, 'S_IN_SEARCH_POPUP' => ($form && $field) ? true : false, - 'S_SEARCH_USER' => true, + 'S_SEARCH_USER' => ($mode == 'searchuser' || ($mode == '' && $submit)), 'S_FORM_NAME' => $form, 'S_FIELD_NAME' => $field, 'S_SELECT_SINGLE' => $select_single, @@ -1611,7 +1611,7 @@ switch ($mode) 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), 'U_FIND_MEMBER' => ($config['load_search'] || $auth->acl_get('a_')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser' . (($start) ? "&start=$start" : '') . (!empty($params) ? '&' . implode('&', $params) : '')) : '', - 'U_HIDE_FIND_MEMBER' => ($mode == 'searchuser') ? $u_hide_find_member : '', + 'U_HIDE_FIND_MEMBER' => ($mode == 'searchuser' || ($mode == '' && $submit)) ? $u_hide_find_member : '', 'U_SORT_USERNAME' => $sort_url . '&sk=a&sd=' . (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a'), 'U_SORT_FROM' => $sort_url . '&sk=b&sd=' . (($sort_key == 'b' && $sort_dir == 'a') ? 'd' : 'a'), 'U_SORT_JOINED' => $sort_url . '&sk=c&sd=' . (($sort_key == 'c' && $sort_dir == 'a') ? 'd' : 'a'), diff --git a/phpBB/report.php b/phpBB/report.php index d792b8df6a..ce9fae13ef 100644 --- a/phpBB/report.php +++ b/phpBB/report.php @@ -71,11 +71,14 @@ if ($post_id) trigger_error('POST_NOT_EXIST'); } - $forum_id = (int) $report_data['forum_id']; - $topic_id = (int) $report_data['topic_id']; - $reported_post_text = $report_data['post_text']; - $reported_post_bitfield = $report_data['bbcode_bitfield']; - $reported_post_uid = $report_data['bbcode_uid']; + $forum_id = (int) $report_data['forum_id']; + $topic_id = (int) $report_data['topic_id']; + $reported_post_text = $report_data['post_text']; + $reported_post_bitfield = $report_data['bbcode_bitfield']; + $reported_post_uid = $report_data['bbcode_uid']; + $reported_post_enable_bbcode = $report_data['enable_bbcode']; + $reported_post_enable_smilies = $report_data['enable_smilies']; + $reported_post_enable_magic_url = $report_data['enable_magic_url']; $sql = 'SELECT * FROM ' . FORUMS_TABLE . ' @@ -134,9 +137,11 @@ else trigger_error($message); } - $reported_post_text = $report_data['message_text']; - $reported_post_bitfield = $report_data['bbcode_bitfield']; - $reported_post_uid = $report_data['bbcode_uid']; + $reported_post_text = $report_data['message_text']; + $reported_post_bitfield = $report_data['bbcode_bitfield']; + $reported_post_enable_bbcode = $report_data['reported_post_enable_bbcode']; + $reported_post_enable_smilies = $report_data['reported_post_enable_smilies']; + $reported_post_enable_magic_url = $report_data['reported_post_enable_magic_url']; } // Submit report? @@ -155,23 +160,28 @@ if ($submit && $reason_id) } $sql_ary = array( - 'reason_id' => (int) $reason_id, - 'post_id' => $post_id, - 'pm_id' => $pm_id, - 'user_id' => (int) $user->data['user_id'], - 'user_notify' => (int) $user_notify, - 'report_closed' => 0, - 'report_time' => (int) time(), - 'report_text' => (string) $report_text, - 'reported_post_text' => $reported_post_text, - 'reported_post_uid' => $reported_post_uid, - 'reported_post_bitfield'=> $reported_post_bitfield, + 'reason_id' => (int) $reason_id, + 'post_id' => $post_id, + 'pm_id' => $pm_id, + 'user_id' => (int) $user->data['user_id'], + 'user_notify' => (int) $user_notify, + 'report_closed' => 0, + 'report_time' => (int) time(), + 'report_text' => (string) $report_text, + 'reported_post_text' => $reported_post_text, + 'reported_post_uid' => $reported_post_uid, + 'reported_post_bitfield' => $reported_post_bitfield, + 'reported_post_enable_bbcode' => $reported_post_enable_bbcode, + 'reported_post_enable_smilies' => $reported_post_enable_smilies, + 'reported_post_enable_magic_url' => $reported_post_enable_magic_url, ); $sql = 'INSERT INTO ' . REPORTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); $db->sql_query($sql); $report_id = $db->sql_nextid(); + $phpbb_notifications = $phpbb_container->get('notification_manager'); + if ($post_id) { $sql = 'UPDATE ' . POSTS_TABLE . ' @@ -190,6 +200,10 @@ if ($submit && $reason_id) $lang_return = $user->lang['RETURN_TOPIC']; $lang_success = $user->lang['POST_REPORTED_SUCCESS']; + + $phpbb_notifications->add_notifications('report_post', array_merge($report_data, $row, $forum_data, array( + 'report_text' => $report_text, + ))); } else { @@ -216,6 +230,12 @@ if ($submit && $reason_id) $lang_return = $user->lang['RETURN_PM']; $lang_success = $user->lang['PM_REPORTED_SUCCESS']; + + $phpbb_notifications->add_notifications('report_pm', array_merge($report_data, $row, array( + 'report_text' => $report_text, + 'from_user_id' => $report_data['author_id'], + 'report_id' => $report_id, + ))); } meta_refresh(3, $redirect_url); diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 777a253615..357e9a6fa3 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -14,13 +14,13 @@ phpbb.closeDarkenWrapper = function(delay) { }; // This callback will mark all forum icons read -phpbb.add_ajax_callback('mark_forums_read', function(res) { +phpbb.addAjaxCallback('mark_forums_read', function(res) { var readTitle = res.NO_UNREAD_POSTS; var unreadTitle = res.UNREAD_POSTS; var iconsArray = { 'forum_unread': 'forum_read', 'forum_unread_subforum': 'forum_read_subforum', - 'forum_unread_locked': 'forum_read_locked', + 'forum_unread_locked': 'forum_read_locked' }; $('li.row').find('dl[class*="forum_unread"]').each(function() { @@ -37,14 +37,24 @@ phpbb.add_ajax_callback('mark_forums_read', function(res) { // Mark subforums read $('a.subforum[class*="unread"]').removeClass('unread').addClass('read'); + // Mark topics read if we are watching a category and showing active topics + if ($('#active_topics').length) { + phpbb.ajaxCallbacks['mark_topics_read'].call(this, res, false); + } + // Update mark forums read links $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS); phpbb.closeDarkenWrapper(3000); }); -// This callback will mark all topic icons read -phpbb.add_ajax_callback('mark_topics_read', function(res) { +/** +* This callback will mark all topic icons read +* +* @param update_topic_links bool Wether "Mark topics read" links should be +* updated. Defaults to true. +*/ +phpbb.addAjaxCallback('mark_topics_read', function(res, update_topic_links) { var readTitle = res.NO_UNREAD_POSTS; var unreadTitle = res.UNREAD_POSTS; var iconsArray = { @@ -58,6 +68,10 @@ phpbb.add_ajax_callback('mark_topics_read', function(res) { var classMap = {}; var classNames = []; + if (typeof update_topic_links === 'undefined') { + update_topic_links = true; + } + $.each(iconsArray, function(unreadClass, readClass) { $.each(iconsState, function(key, value) { // Only topics can be hot @@ -85,22 +99,22 @@ phpbb.add_ajax_callback('mark_topics_read', function(res) { $('a').has('span.icon_topic_newest').remove(); // Update mark topics read links - $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); + if (update_topic_links) { + $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); + } phpbb.closeDarkenWrapper(3000); }); // This callback finds the post from the delete link, and removes it. -phpbb.add_ajax_callback('post_delete', function() { +phpbb.addAjaxCallback('post_delete', function() { var el = $(this), - post_id; + postId; - if (el.attr('data-refresh') === undefined) - { - post_id = el[0].href.split('&p=')[1]; - var post = el.parents('#p' + post_id).css('pointer-events', 'none'); - if (post.hasClass('bg1') || post.hasClass('bg2')) - { + if (el.attr('data-refresh') === undefined) { + postId = el[0].href.split('&p=')[1]; + var post = el.parents('#p' + postId).css('pointer-events', 'none'); + if (post.hasClass('bg1') || post.hasClass('bg2')) { var posts1 = post.nextAll('.bg1'); post.nextAll('.bg2').removeClass('bg2').addClass('bg1'); posts1.removeClass('bg1').addClass('bg2'); @@ -112,9 +126,8 @@ phpbb.add_ajax_callback('post_delete', function() { }); // This callback removes the approve / disapprove div or link. -phpbb.add_ajax_callback('post_visibility', function(res) { +phpbb.addAjaxCallback('post_visibility', function(res) { var remove = (res.visible) ? $(this) : $(this).parents('.post'); - $(remove).css('pointer-events', 'none').fadeOut(function() { $(this).remove(); }); @@ -129,12 +142,12 @@ phpbb.add_ajax_callback('post_visibility', function(res) { }); // This removes the parent row of the link or form that fired the callback. -phpbb.add_ajax_callback('row_delete', function() { +phpbb.addAjaxCallback('row_delete', function() { $(this).parents('tr').remove(); }); // This handles friend / foe additions removals. -phpbb.add_ajax_callback('zebra', function(res) { +phpbb.addAjaxCallback('zebra', function(res) { var zebra; if (res.success) { @@ -151,8 +164,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, @@ -200,12 +212,9 @@ phpbb.ajaxify({ filter: function (data) { var action = $('#quick-mod-select').val(); - if (action === 'make_normal') - { + if (action === 'make_normal') { return $(this).find('select option[value="make_global"]').length > 0; - } - else if (action === 'lock' || action === 'unlock') - { + } else if (action === 'lock' || action === 'unlock') { return true; } @@ -221,6 +230,21 @@ $('#quick-mod-select').change(function () { $('#quickmodform').submit(); }); - +/** +* Toggle the member search panel in memberlist.php. +* +* If user returns to search page after viewing results the search panel is automatically displayed. +* In any case the link will toggle the display status of the search panel and link text will be +* appropriately changed based on the status of the search panel. +*/ +$('#member_search').click(function () { + $('#memberlist_search').slideToggle('fast'); + phpbb.ajax_callbacks['alt_text'].call(this); + // Focus on the username textbox if it's available and displayed + if ($('#memberlist_search').is(':visible')) { + $('#username').focus(); + } + return false; +}); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/styles/prosilver/template/mcp_front.html b/phpBB/styles/prosilver/template/mcp_front.html index 57e57d8254..886a4b784b 100644 --- a/phpBB/styles/prosilver/template/mcp_front.html +++ b/phpBB/styles/prosilver/template/mcp_front.html @@ -115,7 +115,7 @@ <ul class="topiclist cplist"> <!-- BEGIN pm_report --> - <li class="row<!-- IF report.S_ROW_COUNT is odd --> bg1<!-- ELSE --> bg2<!-- ENDIF -->"> + <li class="row<!-- IF pm_report.S_ROW_COUNT is odd --> bg1<!-- ELSE --> bg2<!-- ENDIF -->"> <dl> <dt> <a href="{pm_report.U_PM_DETAILS}" class="topictitle">{pm_report.PM_SUBJECT}</a> {pm_report.ATTACH_ICON_IMG}<br /> diff --git a/phpBB/styles/prosilver/template/memberlist_body.html b/phpBB/styles/prosilver/template/memberlist_body.html index 4ba0c5cb2a..17dc2c33c0 100644 --- a/phpBB/styles/prosilver/template/memberlist_body.html +++ b/phpBB/styles/prosilver/template/memberlist_body.html @@ -3,13 +3,11 @@ <!-- INCLUDE memberlist_search.html --> <form method="post" id="results" action="{S_MODE_ACTION}" onsubmit="insert_marked(this.user); return false"> -<!-- ELSEIF S_SEARCH_USER --> - <!-- INCLUDE overall_header.html --> - <!-- INCLUDE memberlist_search.html --> - <form method="post" action="{S_MODE_ACTION}"> - <!-- ELSE --> <!-- INCLUDE overall_header.html --> + <div class="panel" id="memberlist_search"<!-- IF not S_SEARCH_USER --> style="display: none;"<!-- ENDIF -->> + <!-- INCLUDE memberlist_search.html --> + </div> <form method="post" action="{S_MODE_ACTION}"> <!-- ENDIF --> @@ -32,8 +30,7 @@ <ul class="linklist"> <li> - - <!-- IF U_FIND_MEMBER and not S_SEARCH_USER --><a href="{U_FIND_MEMBER}">{L_FIND_USERNAME}</a> • <!-- ELSEIF S_SEARCH_USER and U_HIDE_FIND_MEMBER and not S_IN_SEARCH_POPUP --><a href="{U_HIDE_FIND_MEMBER}">{L_HIDE_MEMBER_SEARCH}</a> • <!-- ENDIF --> + <!-- IF U_FIND_MEMBER and not S_SEARCH_USER --><a href="{U_FIND_MEMBER}" id="member_search" data-alt-text="{LA_HIDE_MEMBER_SEARCH}">{L_FIND_USERNAME}</a> • <!-- ELSEIF S_SEARCH_USER and U_HIDE_FIND_MEMBER and not S_IN_SEARCH_POPUP --><a href="{U_HIDE_FIND_MEMBER}" id="member_search" data-alt-text="{LA_FIND_USERNAME}">{L_HIDE_MEMBER_SEARCH}</a> • <!-- ENDIF --> <strong style="font-size: 0.95em;"> <!-- BEGIN first_char --> <a href="{first_char.U_SORT}">{first_char.DESC}</a> diff --git a/phpBB/styles/prosilver/template/memberlist_search.html b/phpBB/styles/prosilver/template/memberlist_search.html index 6fed528cea..9de3ccf41b 100644 --- a/phpBB/styles/prosilver/template/memberlist_search.html +++ b/phpBB/styles/prosilver/template/memberlist_search.html @@ -37,9 +37,8 @@ function insert_single(user) } // ]]> </script> -<script type="text/javascript" src="{T_SUPER_TEMPLATE_PATH}/forum_fn.js"></script> <!-- ENDIF --> - +<!-- INCLUDEJS template/forum_fn.js --> <h2 class="solo">{L_FIND_USERNAME}</h2> <form method="post" action="{S_MODE_ACTION}" id="search_memberlist"> diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index 97dbc76e43..a53e0f8d60 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -5,7 +5,7 @@ <meta name="keywords" content="" /> <meta name="description" content="" /> {META} -<title><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --></title> +<title><!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --></title> <!-- IF S_ENABLE_FEEDS --> <!-- IF S_ENABLE_FEEDS_OVERALL --><link rel="alternate" type="application/atom+xml" title="{L_FEED} - {SITENAME}" href="{U_FEED}" /><!-- ENDIF --> @@ -136,6 +136,45 @@ <!-- IF not S_IS_BOT and S_USER_LOGGED_IN --> <ul class="linklist leftside"> + <!-- IF S_NOTIFICATIONS_DISPLAY --> + <li> + [ <a href="{U_VIEW_ALL_NOTIFICATIONS}" id="notification_list_button">{NOTIFICATIONS_COUNT}</a> ] • + <div id="notification_list" class="notification_list"> + <div class="pointer"><div class="pointer_inner"></div></div> + <div class="header"> + {L_NOTIFICATIONS} + <span class="header_settings"><a href="{U_NOTIFICATION_SETTINGS}">{L_SETTINGS}</a></span> + </div> + + <ul> + <!-- IF not .notifications --> + <li> + {L_NO_NOTIFICATIONS} + </li> + <!-- ENDIF --> + <!-- BEGIN notifications --> + <li class="<!-- IF notifications.UNREAD --> bg2<!-- ENDIF -->"> + <!-- IF notifications.URL --><a href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->"><!-- ENDIF --> + {notifications.AVATAR} + <div> + <p>{notifications.FORMATTED_TITLE}</p> + <p>» {notifications.TIME}</p> + + <!-- IF not notifications.URL and notifications.U_MARK_READ --> + <p><a href="{notifications.U_MARK_READ}">{L_MARK_READ}</a></p> + <!-- ENDIF --> + </div> + <!-- IF notifications.URL --></a><!-- ENDIF --> + </li> + <!-- END notifications --> + </ul> + + <div class="footer"> + <a href="{U_VIEW_ALL_NOTIFICATIONS}"><span>{L_SEE_ALL}</span></a> + </div> + </div> + </li> + <!-- ENDIF --> <li class="icon-ucp"> <a href="{U_PROFILE}" title="{L_PROFILE}" accesskey="e">{L_PROFILE}</a> <!-- IF S_DISPLAY_PM --> (<a href="{U_PRIVATEMSGS}">{PRIVATE_MESSAGE_INFO}<!-- IF PRIVATE_MESSAGE_INFO_UNREAD -->, {PRIVATE_MESSAGE_INFO_UNREAD}<!-- ENDIF --></a>)<!-- ENDIF --> diff --git a/phpBB/styles/prosilver/template/timezone.js b/phpBB/styles/prosilver/template/timezone.js index da0a2b0bfd..af8206d12d 100644 --- a/phpBB/styles/prosilver/template/timezone.js +++ b/phpBB/styles/prosilver/template/timezone.js @@ -1,19 +1,19 @@ (function($) { // Avoid conflicts with other libraries $('#tz_date').change(function() { - phpbb.timezone_switch_date(false); + phpbb.timezoneSwitchDate(false); }); $('#tz_select_date_suggest').click(function(){ - phpbb.timezone_preselect_select(true); + phpbb.timezonePreselectSelect(true); }); $(document).ready( - phpbb.timezone_enable_date_selection + phpbb.timezoneEnableDateSelection ); $(document).ready( - phpbb.timezone_preselect_select($('#tz_select_date_suggest').attr('data-is-registration') == 'true') + phpbb.timezonePreselectSelect($('#tz_select_date_suggest').attr('data-is-registration') == 'true') ); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/styles/prosilver/template/ucp_notifications.html b/phpBB/styles/prosilver/template/ucp_notifications.html new file mode 100644 index 0000000000..a2d558d0ca --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_notifications.html @@ -0,0 +1,133 @@ +<!-- INCLUDE ucp_header.html --> + +<form id="ucp" method="post" action="{S_UCP_ACTION}"{S_FORM_ENCTYPE}> + +<h2>{TITLE}</h2> +<div class="panel"> + <div class="inner"> + + <p>{TITLE_EXPLAIN}</p> + + <!-- IF MODE == 'notification_options' --> + <ul class="topiclist"> + <li class="header"> + <dl> + <dt>{L_NOTIFICATION_TYPE}</dt> + <!-- BEGIN notification_methods --> + <dd class="mark">{notification_methods.NAME}</dd> + <!-- END notification_methods --> + <dd class="mark">{L_NOTIFICATIONS}</dd> + </dl> + </li> + </ul> + <ul class="topiclist cplist"> + + <!-- BEGIN notification_types --> + <!-- IF notification_types.GROUP_NAME --> + <li class="row bg3"> + <dl> + <dt> + {notification_types.GROUP_NAME} + </dt> + </dl> + </li> + <!-- ELSE --> + <li class="row<!-- IF notification_types.S_ROW_COUNT is odd --> bg1<!-- ELSE --> bg2<!-- ENDIF -->"> + <dl> + <dt> + {notification_types.NAME} + <!-- IF notification_types.EXPLAIN --><br /> {notification_types.EXPLAIN}<!-- ENDIF --> + </dt> + <!-- BEGIN notification_methods --> + <dd class="mark"><input type="checkbox" name="{notification_types.TYPE}_{notification_methods.METHOD}"<!-- IF notification_methods.SUBSCRIBED --> checked="checked"<!-- ENDIF --> /> <dfn>{notification_methods.NAME}</dfn></dd> + <!-- END notification_methods --> + <dd class="mark"><input type="checkbox" name="{notification_types.TYPE}_notification"<!-- IF notification_types.SUBSCRIBED --> checked="checked"<!-- ENDIF --> /> <dfn>{notification_methods.NAME}</dfn></dd> + </dl> + </li> + <!-- ENDIF --> + <!-- END notification_types --> + </ul> + <!-- ELSE --> + <!-- IF .pagination or TOTAL_COUNT --> + <div class="topic-actions"> + <div class="pagination"> + <!-- IF U_MARK_ALL --><a href="{U_MARK_ALL}">{L_NOTIFICATIONS_MARK_ALL_READ}</a> • <!-- ENDIF --> + <!-- IF TOTAL_COUNT -->{TOTAL_COUNT} • <!-- ENDIF --> + <!-- IF .pagination --> + <!-- INCLUDE pagination.html --> + <!-- ELSE --> + {PAGE_NUMBER} + <!-- ENDIF --> + </div> + </div> + <!-- ENDIF --> + + <div class="notification_list"> + <ul class="topiclist"> + <li class="header"> + <dl> + <dt>{L_NOTIFICATIONS}</dt> + <dd class="mark">{L_MARK_READ}</dd> + </dl> + </li> + <!-- IF not .notifications --> + <li> + <dl> + <dt>{L_NO_NOTIFICATIONS}</dt> + </dl> + </li> + <!-- ENDIF --> + <!-- BEGIN notification_list --> + <li class="row<!-- IF notification_list.UNREAD --> bg3<!-- ELSE --><!-- IF notification_list.S_ROW_COUNT is odd --> bg1<!-- ELSE --> bg2<!-- ENDIF --><!-- ENDIF -->"> + <dl> + <dt> + <!-- IF notification_list.URL --><a href="<!-- IF notification_list.UNREAD -->{notification_list.U_MARK_READ}<!-- ELSE -->{notification_list.URL}<!-- ENDIF -->"><!-- ENDIF --> + {notification_list.AVATAR} + <div class="notifications"> + <p class="notifications_title">{notification_list.FORMATTED_TITLE}</p> + <p class="notifications_time">» {notification_list.TIME}</p> + + <!-- IF not notification_list.URL and notification_list.U_MARK_READ --> + <p><a href="{notification_list.U_MARK_READ}">{L_MARK_READ}</a></p> + <!-- ENDIF --> + </div> + <!-- IF notification_list.URL --></a><!-- ENDIF --> + </dt> + + <dd class="mark"> <!-- IF notification_list.UNREAD --><input type="checkbox" name="mark[]" value="{notification_list.NOTIFICATION_ID}" /> <dfn>{L_MARK_READ}</dfn><!-- ENDIF --> </dd> + </dl> + </li> + <!-- END notification_list --> + </ul> + </div> + + <!-- IF .pagination or TOTAL_COUNT --> + <div class="topic-actions"> + <div class="pagination"> + <!-- IF TOTAL_COUNT -->{TOTAL_COUNT} • <!-- ENDIF --> + <!-- IF .pagination --> + <!-- INCLUDE pagination.html --> + <!-- ELSE --> + {PAGE_NUMBER} + <!-- ENDIF --> + </div> + </div> + <!-- ENDIF --> + + <!-- ENDIF --> + </div> +</div> + +<!-- IF .notifications --> +<fieldset class="display-actions"> + <input type="hidden" name="form_time" value="{FORM_TIME}" /> + {S_HIDDEN_FIELDS} + <input type="submit" name="submit" value="{L_MARK_READ}" class="button1" /> + <div><a href="#" onclick="$('#ucp input:checkbox').attr('checked', true); return false;">{L_MARK_ALL}</a> • <a href="#" onclick="$('#ucp input:checkbox').attr('checked', false); return false;">{L_UNMARK_ALL}</a></div> + {S_FORM_TOKEN} +</fieldset> +<!-- ENDIF --> + +</form> + +<!-- INCLUDE ucp_footer.html --> diff --git a/phpBB/styles/prosilver/template/ucp_pm_history.html b/phpBB/styles/prosilver/template/ucp_pm_history.html index 6abc9b1402..3d886b1c3d 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_history.html +++ b/phpBB/styles/prosilver/template/ucp_pm_history.html @@ -24,7 +24,7 @@ <h3><a href="{history_row.U_VIEW_MESSAGE}" <!-- IF history_row.S_CURRENT_MSG -->class="current"<!-- ENDIF -->>{history_row.SUBJECT}</a></h3> <p class="author">{history_row.MINI_POST_IMG} {L_SENT_AT}{L_COLON} <strong>{history_row.SENT_DATE}</strong><br /> {L_MESSAGE_BY_AUTHOR} {history_row.MESSAGE_AUTHOR_FULL}</p> - <div class="content">{history_row.MESSAGE}</div> + <div class="content"><!-- IF history_row.MESSAGE -->{history_row.MESSAGE}<!-- ELSE --><span class="error">{L_MESSAGE_REMOVED_FROM_OUTBOX}</span><!-- ENDIF --></div> <div id="message_{history_row.MSG_ID}" style="display: none;">{history_row.DECODED_MESSAGE}</div> </div> diff --git a/phpBB/styles/prosilver/template/ucp_pm_message_header.html b/phpBB/styles/prosilver/template/ucp_pm_message_header.html index c47f93f739..7a80f72348 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_message_header.html +++ b/phpBB/styles/prosilver/template/ucp_pm_message_header.html @@ -8,10 +8,10 @@ <!-- IF FOLDER_STATUS and FOLDER_MAX_MESSAGES neq 0 --><p>{FOLDER_STATUS}</p><!-- ENDIF --> <!-- IF U_POST_REPLY_PM or U_POST_NEW_TOPIC or U_FORWARD_PM --> <div class="buttons"> - <!-- IF U_POST_REPLY_PM --><div class="pmreply-icon"><a title="{L_POST_REPLY_PM}" href="{U_POST_REPLY_PM}"><span></span>{L_POST_REPLY_PM}</a></div> - <!-- ELSEIF U_POST_NEW_TOPIC --><div class="newpm-icon"><a href="{U_POST_NEW_TOPIC}" accesskey="n" title="{L_UCP_PM_COMPOSE}"><span></span>{L_UCP_PM_COMPOSE}</a></div><!-- ENDIF --> - <!-- IF U_FORWARD_PM --><div class="forwardpm-icon"><a title="{L_POST_FORWARD_PM}" href="{U_FORWARD_PM}"><span></span>{L_FORWARD_PM}</a></div><!-- ENDIF --> - <!-- IF U_POST_REPLY_PM and S_PM_RECIPIENTS gt 1 --><div class="reply-all"><a class="left" title="{L_REPLY_TO_ALL}" href="{U_POST_REPLY_ALL}">{L_REPLY_TO_ALL}</a></div><!-- ENDIF --> + <!-- IF U_POST_REPLY_PM --><div class="pmreply-icon"><a title="{L_POST_REPLY_PM}" href="{U_POST_REPLY_PM}"><span></span>{L_BUTTON_PM_REPLY}</a></div> + <!-- ELSEIF U_POST_NEW_TOPIC --><div class="newpm-icon"><a href="{U_POST_NEW_TOPIC}" accesskey="n" title="{L_UCP_PM_COMPOSE}"><span></span>{L_BUTTON_PM_NEW}</a></div><!-- ENDIF --> + <!-- IF U_FORWARD_PM --><div class="forwardpm-icon"><a title="{L_POST_FORWARD_PM}" href="{U_FORWARD_PM}"><span></span>{L_BUTTON_PM_FORWARD}</a></div><!-- ENDIF --> + <!-- IF U_POST_REPLY_PM and S_PM_RECIPIENTS gt 1 --><div class="reply-all"><a title="{L_REPLY_TO_ALL}" href="{U_POST_REPLY_ALL}">{L_BUTTON_PM_REPLY_ALL}</a></div><!-- ENDIF --> </div> <!-- ENDIF --> diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 50ade02acd..9a639786b7 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -12,21 +12,21 @@ <dl> <dt><label for="viewemail0">{L_SHOW_EMAIL}{L_COLON}</label></dt> <dd> - <label for="viewemail1"><input type="radio" name="viewemail" id="viewemail1" value="1"<!-- IF S_VIEW_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> + <label for="viewemail1"><input type="radio" name="viewemail" id="viewemail1" value="1"<!-- IF S_VIEW_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> <label for="viewemail0"><input type="radio" name="viewemail" id="viewemail0" value="0"<!-- IF not S_VIEW_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> </dd> </dl> <dl> <dt><label for="massemail1">{L_ADMIN_EMAIL}{L_COLON}</label></dt> <dd> - <label for="massemail1"><input type="radio" name="massemail" id="massemail1" value="1"<!-- IF S_MASS_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> + <label for="massemail1"><input type="radio" name="massemail" id="massemail1" value="1"<!-- IF S_MASS_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> <label for="massemail0"><input type="radio" name="massemail" id="massemail0" value="0"<!-- IF not S_MASS_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> </dd> </dl> <dl> <dt><label for="allowpm1">{L_ALLOW_PM}{L_COLON}</label><br /><span>{L_ALLOW_PM_EXPLAIN}</span></dt> <dd> - <label for="allowpm1"><input type="radio" name="allowpm" id="allowpm1" value="1"<!-- IF S_ALLOW_PM --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> + <label for="allowpm1"><input type="radio" name="allowpm" id="allowpm1" value="1"<!-- IF S_ALLOW_PM --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> <label for="allowpm0"><input type="radio" name="allowpm" id="allowpm0" value="0"<!-- IF not S_ALLOW_PM --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> </dd> </dl> @@ -34,35 +34,21 @@ <dl> <dt><label for="hideonline0">{L_HIDE_ONLINE}{L_COLON}</label><br /><span>{L_HIDE_ONLINE_EXPLAIN}</span></dt> <dd> - <label for="hideonline1"><input type="radio" name="hideonline" id="hideonline1" value="1"<!-- IF S_HIDE_ONLINE --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> + <label for="hideonline1"><input type="radio" name="hideonline" id="hideonline1" value="1"<!-- IF S_HIDE_ONLINE --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> <label for="hideonline0"><input type="radio" name="hideonline" id="hideonline0" value="0"<!-- IF not S_HIDE_ONLINE --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> </dd> </dl> - <!-- ENDIF --> + <!-- ENDIF --> <!-- IF S_SELECT_NOTIFY --> <dl> <dt><label for="notifymethod0">{L_NOTIFY_METHOD}{L_COLON}</label></dt> <dd> - <label for="notifymethod0"><input type="radio" name="notifymethod" id="notifymethod0" value="0"<!-- IF S_NOTIFY_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_NOTIFY_METHOD_EMAIL}</label> - <label for="notifymethod1"><input type="radio" name="notifymethod" id="notifymethod1" value="1"<!-- IF S_NOTIFY_IM --> checked="checked"<!-- ENDIF --> /> {L_NOTIFY_METHOD_IM}</label> + <label for="notifymethod0"><input type="radio" name="notifymethod" id="notifymethod0" value="0"<!-- IF S_NOTIFY_EMAIL --> checked="checked"<!-- ENDIF --> /> {L_NOTIFY_METHOD_EMAIL}</label> + <label for="notifymethod1"><input type="radio" name="notifymethod" id="notifymethod1" value="1"<!-- IF S_NOTIFY_IM --> checked="checked"<!-- ENDIF --> /> {L_NOTIFY_METHOD_IM}</label> <label for="notifymethod2"><input type="radio" name="notifymethod" id="notifymethod2" value="2"<!-- IF S_NOTIFY_BOTH --> checked="checked"<!-- ENDIF --> /> {L_NOTIFY_METHOD_BOTH}</label> </dd> </dl> <!-- ENDIF --> - <dl> - <dt><label for="notifypm1">{L_NOTIFY_ON_PM}{L_COLON}</label></dt> - <dd> - <label for="notifypm1"><input type="radio" name="notifypm" id="notifypm1" value="1"<!-- IF S_NOTIFY_PM --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> - <label for="notifypm0"><input type="radio" name="notifypm" id="notifypm0" value="0"<!-- IF not S_NOTIFY_PM --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> - </dd> - </dl> - <dl> - <dt><label for="popuppm0">{L_POPUP_ON_PM}{L_COLON}</label></dt> - <dd> - <label for="popuppm1"><input type="radio" name="popuppm" id="popuppm1" value="1"<!-- IF S_POPUP_PM --> checked="checked"<!-- ENDIF --> /> {L_YES}</label> - <label for="popuppm0"><input type="radio" name="popuppm" id="popuppm0" value="0"<!-- IF not S_POPUP_PM --> checked="checked"<!-- ENDIF --> /> {L_NO}</label> - </dd> - </dl> <!-- IF S_MORE_LANGUAGES --> <dl> <dt><label for="lang">{L_BOARD_LANGUAGE}{L_COLON}</label></dt> @@ -89,9 +75,9 @@ </div> </div> - + <fieldset class="submit-buttons"> - {S_HIDDEN_FIELDS}<input type="reset" value="{L_RESET}" name="reset" class="button2" /> + {S_HIDDEN_FIELDS}<input type="reset" value="{L_RESET}" name="reset" class="button2" /> <input type="submit" name="submit" value="{L_SUBMIT}" class="button1" /> {S_FORM_TOKEN} </fieldset> @@ -105,9 +91,9 @@ function customDates() { var e = document.getElementById('dateoptions'); - + e.selectedIndex = e.length - 1; - + // Loop and match date_format in menu for (var i = 0; i < e.length; i++) { @@ -117,7 +103,7 @@ break; } } - + // Show/hide custom field if (e.selectedIndex == e.length - 1) { diff --git a/phpBB/styles/prosilver/template/ucp_register.html b/phpBB/styles/prosilver/template/ucp_register.html index 47537e9d8b..db95e5ba13 100644 --- a/phpBB/styles/prosilver/template/ucp_register.html +++ b/phpBB/styles/prosilver/template/ucp_register.html @@ -104,7 +104,4 @@ </div> </form> -<script type="text/javascript" src="{T_SUPER_TEMPLATE_PATH}/timezone.js"></script> -<script type="text/javascript">phpbb_preselect_tz_select();</script> - <!-- INCLUDE overall_footer.html --> diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html index f195dca957..3926ef91cd 100644 --- a/phpBB/styles/prosilver/template/viewforum_body.html +++ b/phpBB/styles/prosilver/template/viewforum_body.html @@ -39,7 +39,7 @@ <!-- IF not S_IS_BOT and S_DISPLAY_POST_INFO --> <div class="buttons"> - <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->post-icon<!-- ENDIF -->" title="<!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF -->"><a href="{U_POST_NEW_TOPIC}"><span></span><!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF --></a></div> + <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->post-icon<!-- ENDIF -->" title="<!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF -->"><a href="{U_POST_NEW_TOPIC}"><span></span><!-- IF S_IS_LOCKED -->{L_BUTTON_FORUM_LOCKED}<!-- ELSE -->{L_BUTTON_NEW_TOPIC}<!-- ENDIF --></a></div> </div> <!-- ENDIF --> @@ -130,7 +130,7 @@ <ul class="topiclist"> <li class="header"> <dl class="icon"> - <dt><!-- IF S_DISPLAY_ACTIVE -->{L_ACTIVE_TOPICS}<!-- ELSEIF topicrow.S_TOPIC_TYPE_SWITCH and (topicrow.S_POST_ANNOUNCE or topicrow.S_POST_GLOBAL) -->{L_ANNOUNCEMENTS}<!-- ELSE -->{L_TOPICS}<!-- ENDIF --></dt> + <dt<!-- IF S_DISPLAY_ACTIVE --> id="active_topics"<!-- ENDIF -->><!-- IF S_DISPLAY_ACTIVE -->{L_ACTIVE_TOPICS}<!-- ELSEIF topicrow.S_TOPIC_TYPE_SWITCH and (topicrow.S_POST_ANNOUNCE or topicrow.S_POST_GLOBAL) -->{L_ANNOUNCEMENTS}<!-- ELSE -->{L_TOPICS}<!-- ENDIF --></dt> <dd class="posts">{L_REPLIES}</dd> <dd class="views">{L_VIEWS}</dd> <dd class="lastpost"><span>{L_LAST_POST}</span></dd> @@ -206,7 +206,7 @@ <div class="topic-actions"> <!-- IF not S_IS_BOT and S_DISPLAY_POST_INFO --> <div class="buttons"> - <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->post-icon<!-- ENDIF -->" title="<!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF -->"><a href="{U_POST_NEW_TOPIC}"><span></span><!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF --></a></div> + <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->post-icon<!-- ENDIF -->" title="<!-- IF S_IS_LOCKED -->{L_FORUM_LOCKED}<!-- ELSE -->{L_POST_TOPIC}<!-- ENDIF -->"><a href="{U_POST_NEW_TOPIC}"><span></span><!-- IF S_IS_LOCKED -->{L_BUTTON_FORUM_LOCKED}<!-- ELSE -->{L_BUTTON_NEW_TOPIC}<!-- ENDIF --></a></div> </div> <!-- ENDIF --> diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index dc67f3b6e7..b7957c46ac 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -29,7 +29,7 @@ <div class="buttons"> <!-- IF not S_IS_BOT and S_DISPLAY_REPLY_INFO --> - <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->reply-icon<!-- ENDIF -->"><a href="{U_POST_REPLY_TOPIC}" title="<!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF -->"><span></span><!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED_SHORT}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF --></a></div> + <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->reply-icon<!-- ENDIF -->"><a href="{U_POST_REPLY_TOPIC}" title="<!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF -->"><span></span><!-- IF S_IS_LOCKED -->{L_BUTTON_TOPIC_LOCKED}<!-- ELSE -->{L_BUTTON_POST_REPLY}<!-- ENDIF --></a></div> <!-- ENDIF --> </div> @@ -287,7 +287,7 @@ <div class="topic-actions"> <div class="buttons"> <!-- IF not S_IS_BOT and S_DISPLAY_REPLY_INFO --> - <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->reply-icon<!-- ENDIF -->"><a href="{U_POST_REPLY_TOPIC}" title="<!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF -->"><span></span><!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED_SHORT}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF --></a></div> + <div class="<!-- IF S_IS_LOCKED -->locked-icon<!-- ELSE -->reply-icon<!-- ENDIF -->"><a href="{U_POST_REPLY_TOPIC}" title="<!-- IF S_IS_LOCKED -->{L_TOPIC_LOCKED}<!-- ELSE -->{L_POST_REPLY}<!-- ENDIF -->"><span></span><!-- IF S_IS_LOCKED -->{L_BUTTON_TOPIC_LOCKED}<!-- ELSE -->{L_BUTTON_POST_REPLY}<!-- ENDIF --></a></div> <!-- ENDIF --> </div> diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index e817380f8e..c3210887b6 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -14,40 +14,57 @@ .buttons div { float: left; margin: 0 5px 0 0; - background-position: 0 100%; } /* Rolloff state */ .buttons div a { - display: block; - width: 100%; - height: 100%; - background-position: 0 0; + display: inline-block; + line-height: 17.5px; + height: 18px; + font-size: 13px; + white-space: nowrap; + border: 1px solid transparent; + border-radius: 4px; + background: transparent none 0 0 repeat-x; + padding: 2px 22px 2px 8px; + font-family: Verdana, Arial, Helvetica; position: relative; - overflow: hidden; + text-decoration: none !important; + outline-style: none !important; + vertical-align: bottom; + *padding-right: 8px; } -/* Hide <a> text and hide off-state image when rolling over (prevents flicker in IE) */ -/*.buttons div span { display: none; }*/ -/*.buttons div a:hover { background-image: none; }*/ -.buttons div span { position: absolute; width: 100%; height: 100%; cursor: pointer;} -.buttons div a:hover span { background-position: 0 100%; } +.buttons div span { display: none; } + +.buttons div a:after { + content: ''; + display: block; + position: absolute; + top: 50%; + right: 6px; + width: 12px; + height: 12px; + margin-top: -6px; + background: transparent 0 0 no-repeat; +} + +.buttons div a:hover:after { + background-position: 0 -20px; +} /* Big button images */ -.reply-icon span { background: transparent none 0 0 no-repeat; } -.post-icon span { background: transparent none 0 0 no-repeat; } -.locked-icon span { background: transparent none 0 0 no-repeat; } -.pmreply-icon span { background: none 0 0 no-repeat; } -.newpm-icon span { background: none 0 0 no-repeat; } -.forwardpm-icon span { background: none 0 0 no-repeat; } - -/* Set big button dimensions */ -.buttons div.reply-icon { width: 96px; height: 25px; } -.buttons div.post-icon { width: 96px; height: 25px; } -.buttons div.locked-icon { width: 88px; height: 25px; } -.buttons div.pmreply-icon { width: 96px; height: 25px; } -.buttons div.newpm-icon { width: 84px; height: 25px; } -.buttons div.forwardpm-icon { width: 96px; height: 25px; } +.buttons div.reply-icon a:after, .buttons div.pmreply-icon a:after { background-position: -20px 0; } +.buttons div.reply-icon a:hover:after, .buttons div.pmreply-icon a:hover:after { background-position: -20px -20px; } + +.buttons div.post-icon a:after, .buttons div.newpm-icon a:after { background-position: 0 0; } +.buttons div.post-icon a:hover:after, .buttons div.newpm-icon a:hover:after { background-position: 0 -20px; } + +.buttons div.locked-icon a:after { background-position: -60px 0; } +.buttons div.locked-icon a:hover:after { background-position: -60px -20px; } + +.buttons div.forwardpm-icon a:after { background-position: -40px 0; } +.buttons div.forwardpm-icon a:hover:after { background-position: -40px -20px; } /* Sub-header (navigation bar) --------------------------------------------- */ diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index a5a18dc6a1..baff88d6b7 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -1,4 +1,4 @@ -/* +/* -------------------------------------------------------------- Colours and backgrounds for common.css -------------------------------------------------------------- */ @@ -65,7 +65,7 @@ hr { .panel { background-color: #ECF1F3; - color: #28313F; + color: #28313F; } .post:target .content { @@ -219,7 +219,7 @@ p.rules { background-image: none; } -/* +/* -------------------------------------------------------------- Colours and backgrounds for links.css -------------------------------------------------------------- */ @@ -312,7 +312,7 @@ a.topictitle:active { color: #105289; } -/* Profile searchresults */ +/* Profile searchresults */ .search .postprofile a { color: #105289; } @@ -349,7 +349,7 @@ a.arrow-right:hover { } -/* +/* -------------------------------------------------------------- Colours and backgrounds for content.css -------------------------------------------------------------- */ @@ -644,19 +644,11 @@ fieldset.polls dd div { background-image: url("./en/icon_user_online.gif"); } -/* +/* -------------------------------------------------------------- Colours and backgrounds for buttons.css -------------------------------------------------------------- */ -/* Big button images */ -.reply-icon span { background-image: url("./en/button_topic_reply.gif"); } -.post-icon span { background-image: url("./en/button_topic_new.gif"); } -.locked-icon span { background-image: url("./en/button_topic_locked.gif"); } -.pmreply-icon span { background-image: url("./en/button_pm_reply.gif") ;} -.newpm-icon span { background-image: url("./en/button_pm_new.gif") ;} -.forwardpm-icon span { background-image: url("./en/button_pm_forward.gif") ;} - a.print { background-image: url("./images/icon_print.gif"); } @@ -665,6 +657,33 @@ a.sendemail { background-image: url("./images/icon_sendemail.gif"); } +.buttons div a { + border-color: #C7C3BF; + background-color: #FFFFFF; + background-image: -moz-linear-gradient(top, #FFFFFF, #E9E9E9); + background-image: -webkit-linear-gradient(top, #FFFFFF, #E9E9E9); + background-image: -o-linear-gradient(top, #FFFFFF, #E9E9E9); + background-image: linear-gradient(to bottom, #FFFFFF, #E9E9E9); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#FFFFFF', EndColorStr='#E9E9E9')"; + box-shadow: 0 0 0 1px #FFFFFF inset; + -webkit-box-shadow: 0 0 0 1px #FFFFFF inset; + color: #BC2A4D !important; +} + +.buttons div a:hover { + border-color: #0a8ed0; + background-image: -moz-linear-gradient(top, #E9E9E9, #FFFFFF); + background-image: -webkit-linear-gradient(top, #E9E9E9, #FFFFFF); + background-image: -o-linear-gradient(top, #E9E9E9, #FFFFFF); + background-image: linear-gradient(to bottom, #E9E9E9, #FFFFFF); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#E9E9E9', EndColorStr='#FFFFFF')"; + text-shadow: 1px 1px 0 #FFFFFF, -1px -1px 0 #FFFFFF, -1px -1px 0 rgba(188, 42, 77, 0.2); +} + +.buttons div a:after { + background-image: url("images/buttons.png"); +} + /* Icon images ---------------------------------------- */ .sitehome { background-image: url("./images/icon_home.gif"); } @@ -752,7 +771,7 @@ a.sendemail { .pm_read { background-image: url("./images/topic_read.gif"); } .pm_unread { background-image: url("./images/topic_unread.gif"); } -/* +/* -------------------------------------------------------------- Colours and backgrounds for cp.css -------------------------------------------------------------- */ @@ -921,7 +940,7 @@ dl.mini dt { background-color: #EEE; } -/* +/* -------------------------------------------------------------- Colours and backgrounds for forms.css -------------------------------------------------------------- */ @@ -976,7 +995,7 @@ fieldset.quick-login input.inputbox { /* Input field styles ---------------------------------------- */ .inputbox { - background-color: #FFFFFF; + background-color: #FFFFFF; border-color: #B4BAC0; color: #333333; } @@ -1044,3 +1063,40 @@ input.disabled { background-color: #000000; } +#notification_list { + background-color: #FFFFFF; + border-color: #B9B9B9; +} + +#notification_list ul li { + border-bottom-color: #B9B9B9; +} + +#notification_list ul li:hover { + background-color: #CFE1F6; + color: #000000; +} + +#notification_list > .header, .notification_list > .footer { + border-color: #B9B9B9; + color: #000000; +} + +#notification_list > .header { + background: #F1F8FF; + background: -moz-linear-gradient(top, #F1F8FF 0%, #CADCEB 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F1F8FF), color-stop(100%, #CADCEB)); + background: -webkit-linear-gradient(top, #F1F8FF 0%, #CADCEB 100%); + background: -o-linear-gradient(top, #F1F8FF 0%, #CADCEB 100%); + background: -ms-linear-gradient(top, #F1F8FF 0%, #CADCEB 100%); + background: linear-gradient(to bottom, #F1F8FF 0%, #CADCEB 100%); +} + +.notification_list .pointer { + border-bottom-color: #B9B9B9; +} + +.notification_list .pointer_inner { + border-bottom-color: #F1F8FF; +} + diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 50b22f44df..02d891ab6a 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -9,8 +9,8 @@ b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; @@ -21,7 +21,7 @@ time, mark, audio, video { vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } @@ -164,7 +164,7 @@ ul ul, ol ul { ol ol ul, ol ul ul, ul ol ul, ul ul ul { list-style-type: square; -} +} /* Main blocks @@ -672,3 +672,111 @@ p.rules a { .smilies { vertical-align: text-bottom; } + +#notification_list { + display: none; + position: absolute; + width: 330px; + z-index: 1; + border: 1px solid; + box-shadow: 3px 3px 5px darkgray; + border-radius: 5px; + margin-top: 8px; +} + +#notification_list ul { + max-height: 350px; + overflow-y: auto; + overflow-x: hidden; +} + +#notification_list ul li { + width: 310px; + padding: 10px; + margin: 0; + float: left; + border-bottom: 1px solid; + list-style-type: none; + font-size: 0.95em; + clear: both; +} + +#notification_list > .header { + padding: 0 10px; + font-family: Arial, "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 11px; + font-weight: bold; + text-shadow: 1px 1px 1px white; + text-transform: uppercase; + line-height: 30px; + border-bottom: 1px solid; + border-radius: 5px 5px 0 0; +} + +#notification_list > .header > .header_settings { + float: right; + font-weight: normal; + text-transform: none; +} + +#notification_list > .footer { + text-align: center; + font-size: 1.2em; +} + +#notification_list ul li a, .notification_list dt > a, #notification_list > .footer > a { + display: block; +} + +.notification_list ul li img { + float: left; + max-width: 50px; + max-height: 50px; + margin-right: 5px; +} + +.notification_list ul li p { + margin: 0; + word-wrap: break-word; +} + +.notification_list ul.topiclist dt { + width: 88%; +} + +.notification_list .pointer, .notification_list .pointer_inner { + position: absolute; + width: 0; + height: 0; + border-top-width: 0; + border-bottom: 10px solid; + border-left: 10px dashed transparent; + border-right: 10px dashed transparent; + -webkit-transform: rotate(360deg); /* better anti-aliasing in webkit */ + display: block; +} + +.notification_list .pointer { + right: auto; + left: 10px; + top: -11px; +} + +.notification_list .pointer_inner { + top: auto; + bottom: -11px; + left: -10px; +} + +.notification_list div.notifications { + padding: 5px; +} + +.notification_list p.notifications_title { + font-weight: bold; +} + +.notification_list p.notifications_time { + font-size: 11px; +} + diff --git a/phpBB/styles/prosilver/theme/cp.css b/phpBB/styles/prosilver/theme/cp.css index da3ec1736e..e32ff8fcb8 100644 --- a/phpBB/styles/prosilver/theme/cp.css +++ b/phpBB/styles/prosilver/theme/cp.css @@ -287,20 +287,6 @@ dl.mini dd { line-height: 2.5em; } -/* PM panel adjustments */ -.reply-all a.left { - background-position: 3px 60%; -} - -.reply-all a.left:hover { - background-position: 0px 60%; -} - -.reply-all { - font-size: 11px; - padding-top: 5px; -} - /* Defined rules list for PM options */ ol.def-rules { padding-left: 0; diff --git a/phpBB/styles/prosilver/theme/en/stylesheet.css b/phpBB/styles/prosilver/theme/en/stylesheet.css index d17f9a5be4..1a3d0acb4b 100644 --- a/phpBB/styles/prosilver/theme/en/stylesheet.css +++ b/phpBB/styles/prosilver/theme/en/stylesheet.css @@ -1,11 +1,3 @@ -/* Set big button dimensions */ -.buttons div.reply-icon { width: 96px; height: 25px; } -.buttons div.post-icon { width: 96px; height: 25px; } -.buttons div.locked-icon { width: 88px; height: 25px; } -.buttons div.pmreply-icon { width: 96px; height: 25px; } -.buttons div.newpm-icon { width: 84px; height: 25px; } -.buttons div.forwardpm-icon { width: 96px; height: 25px; } - /* Set profile icon dimensions */ ul.profile-icons li.pm-icon { width: 28px; height: 20px; } ul.profile-icons li.quote-icon { width: 54px; height: 20px; } @@ -14,14 +6,6 @@ ul.profile-icons li.edit-icon { width: 42px; height: 20px; } /* Online image */ .online { background-image: url("./icon_user_online.gif"); } -/* Big button images */ -.reply-icon span { background-image: url("./button_topic_reply.gif"); } -.post-icon span { background-image: url("./button_topic_new.gif"); } -.locked-icon span { background-image: url("./button_topic_locked.gif"); } -.pmreply-icon span { background-image: url("./button_pm_reply.gif") ;} -.newpm-icon span { background-image: url("./button_pm_new.gif") ;} -.forwardpm-icon span { background-image: url("./button_pm_forward.gif") ;} - /* Icon images */ .pm-icon, .pm-icon a { background-image: url("./icon_contact_pm.gif"); } .quote-icon, .quote-icon a { background-image: url("./icon_post_quote.gif"); } diff --git a/phpBB/styles/prosilver/theme/images/buttons.png b/phpBB/styles/prosilver/theme/images/buttons.png Binary files differnew file mode 100644 index 0000000000..a19abdc2b8 --- /dev/null +++ b/phpBB/styles/prosilver/theme/images/buttons.png diff --git a/phpBB/styles/subsilver2/template/overall_footer.html b/phpBB/styles/subsilver2/template/overall_footer.html index 64e7587023..3a29a0d752 100644 --- a/phpBB/styles/subsilver2/template/overall_footer.html +++ b/phpBB/styles/subsilver2/template/overall_footer.html @@ -13,6 +13,7 @@ <script type="text/javascript" src="{T_JQUERY_LINK}"></script> <!-- IF S_JQUERY_FALLBACK --><script type="text/javascript">window.jQuery || document.write(unescape('%3Cscript src="{T_ASSETS_PATH}/javascript/jquery.js?assets_version={T_ASSETS_VERSION}" type="text/javascript"%3E%3C/script%3E'));</script><!-- ENDIF --> +<script type="text/javascript" src="{T_ASSETS_PATH}/javascript/core.js?assets_version={T_ASSETS_VERSION}"></script> {SCRIPTS} <!-- EVENT overall_footer_after --> diff --git a/phpBB/styles/subsilver2/template/overall_header.html b/phpBB/styles/subsilver2/template/overall_header.html index d5fd20f0fe..0317d6e45c 100644 --- a/phpBB/styles/subsilver2/template/overall_header.html +++ b/phpBB/styles/subsilver2/template/overall_header.html @@ -5,7 +5,7 @@ <meta name="keywords" content="" /> <meta name="description" content="" /> {META} -<title><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --></title> +<title><!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --></title> <!-- IF S_ENABLE_FEEDS --> <!-- IF S_ENABLE_FEEDS_OVERALL --><link rel="alternate" type="application/atom+xml" title="{L_FEED} - {SITENAME}" href="{U_FEED}" /><!-- ENDIF --> @@ -152,6 +152,46 @@ function marklist(id, name, state) <table width="100%" cellspacing="0"> <tr> <td class="genmed"> + <!-- IF S_NOTIFICATIONS_DISPLAY --> + [ <a href="{U_VIEW_ALL_NOTIFICATIONS}" id="notification_list_button">{NOTIFICATIONS_COUNT}</a> ] • + <div id="notification_list" class="notification_list"> + <div class="row1 header"> + {L_NOTIFICATIONS} + <span class="header_settings"><a href="{U_NOTIFICATION_SETTINGS}">{L_SETTINGS}</a></span> + </div> + + <div class="notification_scroll"> + <table class="tablebg" width="310px" cellspacing="1"> + <!-- BEGIN notifications --> + <tr class="row<!-- IF notifications.UNREAD -->2<!-- ELSE -->1<!-- ENDIF -->"> + <!-- IF notifications.AVATAR --> + <td width="50px"> + {notifications.AVATAR} + </td> + <td valign="top"> + <!-- ELSE --> + <td colspan="2" valign="top"> + <!-- ENDIF --> + <div class="notification_title"> + <!-- IF notifications.URL --><a href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->"><!-- ENDIF --> + {notifications.FORMATTED_TITLE} + <!-- IF notifications.URL --></a><!-- ENDIF --> + <br />» {notifications.TIME} + <!-- IF not notifications.URL and notifications.UNREAD --> + <br /><a href="{notifications.U_MARK_READ}">{L_MARK_READ}</a> + <!-- ENDIF --> + </div> + </td> + </tr> + <!-- END notifications --> + </table> + </div> + + <div class="row1 footer"> + <a href="{U_VIEW_ALL_NOTIFICATIONS}"><span>{L_SEE_ALL}</span></a> + </div> + </div> + <!-- ENDIF --> <!-- IF not S_IS_BOT --><a href="{U_LOGIN_LOGOUT}"><img src="{T_THEME_PATH}/images/icon_mini_login.gif" width="12" height="13" alt="*" /> {L_LOGIN_LOGOUT}</a> <!-- ENDIF --> <!-- IF U_RESTORE_PERMISSIONS --> <a href="{U_RESTORE_PERMISSIONS}"><img src="{T_THEME_PATH}/images/icon_mini_login.gif" width="12" height="13" alt="*" /> {L_RESTORE_PERMISSIONS}</a><!-- ENDIF --> <!-- IF S_BOARD_DISABLED and S_USER_LOGGED_IN --> <span style="color: red;">{L_BOARD_DISABLED}</span><!-- ENDIF --> diff --git a/phpBB/styles/subsilver2/template/timezone.js b/phpBB/styles/subsilver2/template/timezone.js index da0a2b0bfd..af8206d12d 100644 --- a/phpBB/styles/subsilver2/template/timezone.js +++ b/phpBB/styles/subsilver2/template/timezone.js @@ -1,19 +1,19 @@ (function($) { // Avoid conflicts with other libraries $('#tz_date').change(function() { - phpbb.timezone_switch_date(false); + phpbb.timezoneSwitchDate(false); }); $('#tz_select_date_suggest').click(function(){ - phpbb.timezone_preselect_select(true); + phpbb.timezonePreselectSelect(true); }); $(document).ready( - phpbb.timezone_enable_date_selection + phpbb.timezoneEnableDateSelection ); $(document).ready( - phpbb.timezone_preselect_select($('#tz_select_date_suggest').attr('data-is-registration') == 'true') + phpbb.timezonePreselectSelect($('#tz_select_date_suggest').attr('data-is-registration') == 'true') ); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/styles/subsilver2/template/ucp_notifications.html b/phpBB/styles/subsilver2/template/ucp_notifications.html new file mode 100644 index 0000000000..1a1fda4c17 --- /dev/null +++ b/phpBB/styles/subsilver2/template/ucp_notifications.html @@ -0,0 +1,151 @@ +<!-- INCLUDE ucp_header.html --> + +<form id="ucp" method="post" action="{S_UCP_ACTION}"{S_FORM_ENCTYPE}> + +<table width="100%" cellspacing="1"> + <tr> + <th colspan="4">{TITLE}</th> + </tr> + <tr> + <td class="row1" colspan="4" align="center"><span class="genmed">{TITLE_EXPLAIN}</span></td> + </tr> + <!-- IF MODE == 'notification_options' --> + <tr> + <th>{L_NOTIFICATION_TYPE}</th> + <th width="10%">{L_NOTIFICATIONS}</th> + <!-- BEGIN notification_methods --> + <th width="10%">{notification_methods.NAME}</th> + <!-- END notification_methods --> + </tr> + + <!-- BEGIN notification_types --> + <!-- IF notification_types.GROUP_NAME --> + <tr> + <td class="row3" colspan="3">{notification_types.GROUP_NAME}</td> + </tr> + <!-- ELSE --> + <!-- IF notification_types.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> + <td> + {notification_types.NAME} + <!-- IF notification_types.EXPLAIN --><br /> {notification_types.EXPLAIN}<!-- ENDIF --> + </td> + <td align="center"><input type="checkbox" name="{notification_types.TYPE}_notification"<!-- IF notification_types.SUBSCRIBED --> checked="checked"<!-- ENDIF --> /></td> + <!-- BEGIN notification_methods --> + <td align="center"><input type="checkbox" name="{notification_types.TYPE}_{notification_methods.METHOD}"<!-- IF notification_methods.SUBSCRIBED --> checked="checked"<!-- ENDIF --> /></td> + <!-- END notification_methods --> + </tr> + <!-- ENDIF --> + <!-- END notification_types --> + <tr> + <td class="cat" colspan="3" align="center"> + <input type="hidden" name="form_time" value="{FORM_TIME}" /> + {S_HIDDEN_FIELDS} + <input class="btnmain" type="submit" name="submit" value="{L_SUBMIT}" /> + <input class="btnlite" type="reset" value="{L_RESET}" name="reset" /> + {S_FORM_TOKEN} + </td> + </tr> + <!-- ELSE --> + <tr> + <td> + <table class="tablebg" width="100%" cellspacing="1" cellpadding="0"> + <tr> + <td class="row1"> + <table border="0" cellspacing="0" cellpadding="0" width="100%"> + <tr> + <td align="{S_CONTENT_FLOW_BEGIN}"> + <!-- IF TOTAL_COUNT --> + <table width="100%" cellspacing="1"> + <tr> + <td class="nav" valign="middle" nowrap="nowrap"> {PAGE_NUMBER}</td> + <td class="gensmall" nowrap="nowrap" width="100%"> [ <b>{TOTAL_COUNT}</b> ] </td> + </tr> + </table> + <!-- ENDIF --> + </td> + <td align="{S_CONTENT_FLOW_END}"><!-- INCLUDE pagination.html --></td> + </tr> + </table> + </td> + </tr> + </table> + + <div style="padding: 2px;"></div> + + <div class="notification_list"> + <table class="tablebg" width="100%" cellspacing="1"> + <tr> + <td class="cat" colspan="3"> + <table width="100%" cellspacing="0"> + <tr class="nav"> + <td align="{S_CONTENT_FLOW_END}" valign="middle"><!-- IF U_MARK_ALL --><a href="{U_MARK_ALL}">{L_NOTIFICATIONS_MARK_ALL_READ}</a><!-- ENDIF --></td> + </tr> + </table> + </td> + </tr> + <tr> + <th colspan="2">{L_NOTIFICATIONS}</th> + <th width="15%">{L_MARK_READ}</th> + </tr> + <!-- BEGIN notification_list --> + <tr class="row<!-- IF notification_list.UNREAD -->3<!-- ELSEIF notification_list.S_ROW_COUNT is even -->1<!-- ELSE -->2<!-- ENDIF -->"> + <!-- IF notification_list.AVATAR --> + <td width="50px"> + {notification_list.AVATAR} + </td> + <td valign="top"> + <!-- ELSE --> + <td colspan="2" valign="top" height="50px"> + <!-- ENDIF --> + <span class="gen"> + <!-- IF notification_list.URL --><a href="<!-- IF notification_list.UNREAD -->{notification_list.U_MARK_READ}<!-- ELSE -->{notification_list.URL}<!-- ENDIF -->"><!-- ENDIF --> + <strong>{notification_list.FORMATTED_TITLE}</strong> + <!-- IF notification_list.URL --></a><!-- ENDIF --><br /> + » {notification_list.TIME} + </span> + </td> + <td align="center"> + <!-- IF notification_list.UNREAD --><input type="checkbox" name="mark[]" value="{notification_list.NOTIFICATION_ID}" /><!-- ENDIF --> + </td> + </tr> + <!-- END notification_list --> + <tr> + <td class="cat" colspan="3" align="center"> + <input type="hidden" name="form_time" value="{FORM_TIME}" /> + {S_HIDDEN_FIELDS} + <input class="btnmain" type="submit" name="submit" value="{L_MARK_READ}" /> + {S_FORM_TOKEN} + </td> + </tr> + </table> + </div> + + <div style="padding: 2px;"></div> + + <!-- IF .pagination --> + <table class="tablebg" width="100%" cellspacing="1" cellpadding="0"> + <tr> + <td class="row1"> + <table border="0" cellspacing="0" cellpadding="0" width="100%"> + <tr> + <td align="{S_CONTENT_FLOW_BEGIN}"> + <!-- INCLUDE pagination.html --> + </td> + </tr> + </table> + </td> + </tr> + </table> + <!-- ENDIF --> + </td> + </tr> + <!-- ENDIF --> +</table> + +<!-- IF .notifications --> +<div class="gensmall" style="float: {S_CONTENT_FLOW_END}; padding-top: 2px;"><b><a href="#" onclick="$('#ucp input:checkbox').attr('checked', true); return false;">{L_MARK_ALL}</a> :: <a href="#" onclick="$('#ucp input:checkbox').attr('checked', false); return false;">{L_UNMARK_ALL}</a></b></div> +<!-- ENDIF --> + +</form> + +<!-- INCLUDE ucp_footer.html --> diff --git a/phpBB/styles/subsilver2/template/ucp_pm_history.html b/phpBB/styles/subsilver2/template/ucp_pm_history.html index 0cbbf33fbe..4f2b627b50 100644 --- a/phpBB/styles/subsilver2/template/ucp_pm_history.html +++ b/phpBB/styles/subsilver2/template/ucp_pm_history.html @@ -37,7 +37,7 @@ <td valign="top"> <table width="100%" cellspacing="0" cellpadding="2"> <tr> - <td><div class="postbody">{history_row.MESSAGE}</div><div id="message_{history_row.MSG_ID}" style="display: none;">{history_row.DECODED_MESSAGE}</div></td> + <td><div class="postbody"><!-- IF history_row.MESSAGE -->{history_row.MESSAGE}<!-- ELSE --><span class="error">{L_MESSAGE_REMOVED_FROM_OUTBOX}</span><!-- ENDIF --></div><div id="message_{history_row.MSG_ID}" style="display: none;">{history_row.DECODED_MESSAGE}</div></td> </tr> </table> </td> diff --git a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html index 9ebc045608..8f6e345e69 100644 --- a/phpBB/styles/subsilver2/template/ucp_prefs_personal.html +++ b/phpBB/styles/subsilver2/template/ucp_prefs_personal.html @@ -29,38 +29,30 @@ <td class="row3" colspan="2" align="center"><span class="gensmall error">{ERROR}</span></td> </tr> <!-- ENDIF --> -<tr> +<tr> <td class="row1" width="50%"><b class="genmed">{L_SHOW_EMAIL}{L_COLON}</b></td> <td class="row2"><input type="radio" class="radio" name="viewemail" value="1"<!-- IF S_VIEW_EMAIL --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="viewemail" value="0"<!-- IF not S_VIEW_EMAIL --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> </tr> -<tr> +<tr> <td class="row1" width="50%"><b class="genmed">{L_ADMIN_EMAIL}{L_COLON}</b></td> <td class="row2"><input type="radio" class="radio" name="massemail" value="1"<!-- IF S_MASS_EMAIL --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="massemail" value="0"<!-- IF not S_MASS_EMAIL --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> </tr> -<tr> +<tr> <td class="row1" width="50%"><b class="genmed">{L_ALLOW_PM}{L_COLON}</b><br /><span class="gensmall">{L_ALLOW_PM_EXPLAIN}</span></td> <td class="row2"><input type="radio" class="radio" name="allowpm" value="1"<!-- IF S_ALLOW_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="allowpm" value="0"<!-- IF not S_ALLOW_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> </tr> <!-- IF S_CAN_HIDE_ONLINE --> - <tr> + <tr> <td class="row1" width="50%"><b class="genmed">{L_HIDE_ONLINE}{L_COLON}</b><br /><span class="gensmall">{L_HIDE_ONLINE_EXPLAIN}</span></td> <td class="row2"><input type="radio" class="radio" name="hideonline" value="1"<!-- IF S_HIDE_ONLINE --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="hideonline" value="0"<!-- IF not S_HIDE_ONLINE --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> </tr> <!-- ENDIF --> <!-- IF S_SELECT_NOTIFY --> - <tr> + <tr> <td class="row1" width="50%"><b class="genmed">{L_NOTIFY_METHOD}{L_COLON}</b><br /><span class="gensmall">{L_NOTIFY_METHOD_EXPLAIN}</span></td> <td class="row2"><input type="radio" class="radio" name="notifymethod" value="0"<!-- IF S_NOTIFY_EMAIL --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NOTIFY_METHOD_EMAIL}</span> <input type="radio" class="radio" name="notifymethod" value="1"<!-- IF S_NOTIFY_IM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NOTIFY_METHOD_IM}</span> <input type="radio" class="radio" name="notifymethod" value="2"<!-- IF S_NOTIFY_BOTH --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NOTIFY_METHOD_BOTH}</span></td> </tr> <!-- ENDIF --> -<tr> - <td class="row1" width="50%"><b class="genmed">{L_NOTIFY_ON_PM}{L_COLON}</b></td> - <td class="row2"><input type="radio" class="radio" name="notifypm" value="1"<!-- IF S_NOTIFY_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="notifypm" value="0"<!-- IF not S_NOTIFY_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> -</tr> -<tr> - <td class="row1" width="50%"><b class="genmed">{L_POPUP_ON_PM}{L_COLON}</b></td> - <td class="row2"><input type="radio" class="radio" name="popuppm" value="1"<!-- IF S_POPUP_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_YES}</span> <input type="radio" class="radio" name="popuppm" value="0"<!-- IF not S_POPUP_PM --> checked="checked"<!-- ENDIF --> /><span class="genmed">{L_NO}</span></td> -</tr> <!-- IF S_MORE_LANGUAGES --> <tr> <td class="row1" width="50%"><b class="genmed">{L_BOARD_LANGUAGE}{L_COLON}</b></td> diff --git a/phpBB/styles/subsilver2/template/viewforum_body.html b/phpBB/styles/subsilver2/template/viewforum_body.html index c5e66a07ca..664a6a26c5 100644 --- a/phpBB/styles/subsilver2/template/viewforum_body.html +++ b/phpBB/styles/subsilver2/template/viewforum_body.html @@ -164,7 +164,7 @@ <table width="100%" cellspacing="0"> <tr class="nav"> <td valign="middle"> <!-- IF U_WATCH_FORUM_LINK and not S_IS_BOT --><a href="{U_WATCH_FORUM_LINK}">{S_WATCH_FORUM_TITLE}</a><!-- ENDIF --></td> - <td align="{S_CONTENT_FLOW_END}" valign="middle"><!-- IF not S_IS_BOT and U_MARK_TOPICS --><a href="{U_MARK_TOPICS}">{L_MARK_TOPICS_READ}</a><!-- ENDIF --> </td> + <td align="{S_CONTENT_FLOW_END}" valign="middle"><!-- IF not S_IS_BOT and U_MARK_TOPICS and .topicrow --><a href="{U_MARK_TOPICS}">{L_MARK_TOPICS_READ}</a><!-- ENDIF --> </td> </tr> </table> </td> diff --git a/phpBB/styles/subsilver2/theme/stylesheet.css b/phpBB/styles/subsilver2/theme/stylesheet.css index 977e5c20c6..df46c91d8d 100644 --- a/phpBB/styles/subsilver2/theme/stylesheet.css +++ b/phpBB/styles/subsilver2/theme/stylesheet.css @@ -181,7 +181,7 @@ p.datetime { p.searchbar { padding: 2px 0; white-space: nowrap; -} +} p.searchbarreg { margin: 0; @@ -464,7 +464,7 @@ textarea { background-color: #FAFAFA; color: #333333; font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; - font-size: 1.3em; + font-size: 1.3em; line-height: 1.4em; font-weight: normal; border: 1px solid #A9B8C2; @@ -1141,3 +1141,52 @@ a.imageset { padding-right: 18px; padding-left: 0; } + +#notification_list { + display: none; + position: absolute; + width: 310px; + z-index: 1; + box-shadow: 3px 3px 5px darkgray; +} + +#notification_list .notification_scroll { + max-height: 350px; + overflow-y: auto; + overflow-x: hidden; +} + +#notification_list .notification_title { + padding: 5px; +} + +#notification_list .header { + width: 298px; + padding: 5px; + font-weight: bold; + border: 1px solid #A9B8C2; + border-bottom: 0; +} + +#notification_list > .header > .header_settings { + float: right; + font-weight: normal; + text-transform: none; +} + +#notification_list .footer { + width: 300px; + text-align: center; + font-size: 1.2em; + border: 1px solid #A9B8C2; + border-top: 0; +} + +.notification_list img { + max-width: 50px; + max-height: 50px; +} + +#notification_list .footer > a { + display: block; +} diff --git a/phpBB/viewonline.php b/phpBB/viewonline.php index 687fef4c6c..6b26a3f5d6 100644 --- a/phpBB/viewonline.php +++ b/phpBB/viewonline.php @@ -216,7 +216,7 @@ while ($row = $db->sql_fetchrow($result)) $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); break; - case 'adm/index': + case $phpbb_adm_relative_path . 'index': $location = $user->lang['ACP']; $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); break; |