├── .gitignore ├── README.md ├── acp ├── main_info.php └── main_module.php ├── adm ├── images │ ├── collapse.png │ └── expand.png └── style │ ├── acp_digests.css │ ├── acp_digests.html │ ├── acp_digests.js │ ├── event │ ├── acp_overall_footer_after.html │ └── acp_overall_header_stylesheets_after.html │ ├── images │ ├── ui-icons_444444_256x240.png │ ├── ui-icons_555555_256x240.png │ ├── ui-icons_777620_256x240.png │ ├── ui-icons_777777_256x240.png │ ├── ui-icons_cc0000_256x240.png │ └── ui-icons_ffffff_256x240.png │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ ├── jquery.datetimepicker.css │ ├── jquery.datetimepicker.js │ ├── mail_digests_html.html │ └── mail_digests_text.html ├── composer.json ├── config ├── routing.yml ├── services.yml └── tables.yml ├── constants └── constants.php ├── controller ├── acp_controller.php ├── ucp_controller.php └── unsubscribe.php ├── core └── common.php ├── cron └── task │ └── digests.php ├── event └── main_listener.php ├── ext.php ├── includes └── html_messenger.php ├── language └── en │ ├── acp │ ├── common.php │ └── info_acp_common.php │ ├── common.php │ └── email │ ├── digests_html.txt │ ├── digests_plain_html.txt │ ├── digests_subscribe.txt │ ├── digests_subscription_edited.txt │ ├── digests_text.txt │ ├── digests_unsubscribe.txt │ └── digests_unsubscribe_one_click.txt ├── license.txt ├── migrations ├── convert_mod_data.php ├── convert_mod_modules.php ├── convert_mod_schema.php ├── release_3_0_2_data.php ├── release_3_0_2_modules.php ├── release_3_0_2_schema.php ├── release_3_0_5.php ├── release_3_0_7.php ├── release_3_2_1.php ├── release_3_2_10.php ├── release_3_2_15.php ├── release_3_2_16.php ├── release_3_2_17.php ├── release_3_2_6.php ├── release_3_2_7.php ├── release_3_3_12.php ├── release_3_3_13.php ├── release_3_3_14.php ├── release_3_3_16.php ├── release_3_3_4.php └── release_3_3_5.php ├── styles └── all │ ├── template │ ├── event │ │ ├── overall_header_head_append.html │ │ └── ucp_register_profile_fields_after.html │ ├── js │ │ ├── digests.js │ │ └── jquery-ui-1.12.1.min.js │ ├── mail_digests_html.html │ ├── mail_digests_text.html │ └── ucp_digests.html │ └── theme │ ├── digests.css │ ├── images │ ├── ui-icons_444444_256x240.png │ ├── ui-icons_555555_256x240.png │ ├── ui-icons_777620_256x240.png │ ├── ui-icons_777777_256x240.png │ ├── ui-icons_cc0000_256x240.png │ └── ui-icons_ffffff_256x240.png │ └── jquery-ui-1.12.1.min.css └── ucp ├── main_info.php └── main_module.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digests 2 | Digests extension for phpBB 3.3 3 | 4 | Please note that when deployed the extension will go under ext/phpbbservices/digests. Only the digests tree is shown in GitHub. 5 | 6 | If you are upgrading from the digests modification for phpBB 3.0, first read the FAQ on how to retain your digests subscribers: https://www.phpbb.com/customise/db/extension/digests_extension/faq/2731 7 | 8 | Digests no longer requires that a cron job be run hourly, but it's generally a good idea to set up what is known as a "system cron". See https://www.phpbb.com/customise/db/extension/digests_extension/faq/2716. 9 | 10 | After installation, there are recommended steps for testing digests. See: https://www.phpbb.com/customise/db/extension/digests_extension/faq/2736 11 | 12 | Czech, French, German and Spanish language translations exist. The translation may be out of date. Translations must be placed int the extension's language folder /ext/phpbbservices/digests/language, ex: /ext/phpbbservices/digests/language/fr for French. Thanks to our translators for providing these translations! 13 | 14 | Czech: https://github.com/petr-hendl/phpBBDigests-cs/ 15 | French: https://github.com/bonnaphil/digests-fr 16 | French: https://github.com/ssl-origin/digests-fr/releases 17 | German: https://github.com/Praggle/digests/releases 18 | Spanish: https://github.com/fernandoch777/digests-es 19 | -------------------------------------------------------------------------------- /acp/main_info.php: -------------------------------------------------------------------------------- 1 | '\phpbbservices\digests\acp\main_module', 18 | 'title' => 'ACP_CAT_DIGESTS', 19 | 'version' => '3.3.5', 20 | 'modes' => array( 21 | 'digests_general' => array('title' => 'ACP_DIGESTS_GENERAL_SETTINGS', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 22 | 'digests_user_defaults' => array('title' => 'ACP_DIGESTS_USER_DEFAULT_SETTINGS', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 23 | 'digests_edit_subscribers' => array('title' => 'ACP_DIGESTS_EDIT_SUBSCRIBERS', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 24 | 'digests_balance_load' => array('title' => 'ACP_DIGESTS_BALANCE_LOAD', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 25 | 'digests_mass_subscribe_unsubscribe' => array('title' => 'ACP_DIGESTS_MASS_SUBSCRIBE_UNSUBSCRIBE', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 26 | 'digests_test' => array('title' => 'ACP_DIGESTS_TEST', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 27 | 'digests_reset_cron_run_time' => array('title' => 'ACP_DIGESTS_RESET_CRON_RUN_TIME', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 28 | 'digests_clear_cached' => array('title' => 'ACP_DIGESTS_CLEAR_CACHED_DIGESTS', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 29 | 'digests_report' => array('title' => 'ACP_DIGESTS_REPORTS', 'auth' => 'ext_phpbbservices/digests && acl_a_user', 'cat' => array('ACP_CAT_DIGESTS')), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /acp/main_module.php: -------------------------------------------------------------------------------- 1 | get('phpbbservices.digests.controller.acp'); 32 | 33 | /** @var \phpbb\language\language $language */ 34 | $this->language = $phpbb_container->get('language'); 35 | $this->language->add_lang('common', 'phpbbservices/digests'); 36 | 37 | // Load a template from adm/style for our ACP pages 38 | $this->tpl_name = 'acp_digests'; 39 | 40 | // Set the page title for our ACP pages (modules) 41 | switch ($mode) 42 | { 43 | 44 | case 'digests_general': 45 | default: 46 | $this->page_title = $this->language->lang('ACP_DIGESTS_GENERAL_SETTINGS'); 47 | break; 48 | 49 | case 'digests_user_defaults': 50 | $this->page_title = $this->language->lang('ACP_DIGESTS_USER_DEFAULT_SETTINGS'); 51 | break; 52 | 53 | case 'digests_edit_subscribers': 54 | $this->page_title = $this->language->lang('ACP_DIGESTS_EDIT_SUBSCRIBERS'); 55 | break; 56 | 57 | case 'digests_balance_load': 58 | $this->page_title = $this->language->lang('ACP_DIGESTS_BALANCE_LOAD'); 59 | break; 60 | 61 | case 'digests_mass_subscribe_unsubscribe': 62 | $this->page_title = $this->language->lang('ACP_DIGESTS_MASS_SUBSCRIBE_UNSUBSCRIBE'); 63 | break; 64 | 65 | case 'digests_test': 66 | $this->page_title = $this->language->lang('ACP_DIGESTS_TEST'); 67 | break; 68 | 69 | case 'digests_reset_cron_run_time': 70 | $this->page_title = $this->language->lang('ACP_DIGESTS_RESET_CRON_RUN_TIME'); 71 | break; 72 | 73 | case 'digests_clear_cached': 74 | $this->page_title = $this->language->lang('ACP_DIGESTS_CLEAR_CACHED_DIGESTS'); 75 | break; 76 | 77 | case 'digests_report': 78 | $this->page_title = $this->language->lang('ACP_DIGESTS_REPORTS'); 79 | break; 80 | 81 | } 82 | 83 | // Load the display options handle in our ACP controller, passing the mode 84 | $acp_controller->display_options($mode, $this->u_action); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /adm/images/collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/images/collapse.png -------------------------------------------------------------------------------- /adm/images/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/images/expand.png -------------------------------------------------------------------------------- /adm/style/acp_digests.css: -------------------------------------------------------------------------------- 1 | #balance_table tr:nth-child(odd) 2 | { 3 | background-color: #DCEBFE !important; 4 | } 5 | .border-right-none { 6 | border-right-style:none; 7 | } 8 | #digests { 9 | background-color: inherit; 10 | } 11 | #down tr:nth-child(3) 12 | { 13 | background-color: #DCEBFE !important; 14 | } 15 | fieldset { 16 | padding: 0; 17 | border: 0; 18 | background-color: inherit; 19 | margin: 5px 0; 20 | overflow-x: auto; 21 | } 22 | fieldset dd { 23 | border-left: 0; 24 | } 25 | fieldset dt { 26 | padding-right: 5px; margin-right: 5px; 27 | } 28 | fieldset.sort-filter { 29 | background-color: #FFF; 30 | font-size: 13px; 31 | padding: 10px; 32 | border-radius: 3px; 33 | border: 1px solid #CCCFD3; 34 | } 35 | input[type="number"] 36 | { 37 | text-align: right; 38 | width: 5em !important; 39 | } 40 | .layout-fixed { 41 | table-layout: fixed; width: 100%; 42 | } 43 | .overflow-x 44 | { 45 | overflow-x:auto; 46 | } 47 | .point8-em { 48 | font-size:.8em; 49 | } 50 | .position-relative { 51 | position:relative; 52 | left: 20px; 53 | } 54 | .responsive th a:visited, .responsive th a:link { 55 | color: #ffffff; 56 | } 57 | #sort-filter dt label { 58 | font-size: 1em; 59 | } 60 | #sort-filter label, #sort-filter input, #sort-filter select { 61 | font-size: .85em; 62 | } 63 | table { 64 | border-spacing: 1px; 65 | border-collapse: separate; 66 | } 67 | .text-center { 68 | text-align:center; 69 | } 70 | .text-center-uc { 71 | text-align:center; text-transform:uppercase; 72 | } 73 | .text-left { 74 | text-align:left; 75 | } 76 | .text-left-uc { 77 | text-align:left; text-transform:uppercase; 78 | } 79 | .width-120 { 80 | width:120px; 81 | } 82 | .width-80 { 83 | width:80px; 84 | } 85 | #with_selected { 86 | font-size: 1em; 87 | } -------------------------------------------------------------------------------- /adm/style/acp_digests.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | "use strict"; 4 | // If the edit subscribers table row's expand/collapse icon is clicked on, or the associated link next to it is clicked on, 5 | // toggle the display of the associated detail row between not visible and visible. 6 | $("a[id^=link-], a[id^=imglink-]").click(function() { 7 | // Infer the ids that will be needed by parsing for the id of the table row with focus 8 | var id = $(this).attr('id'); 9 | var start = id.indexOf('-'); 10 | var rowId = id.substring(start + 1); 11 | var selector = '#user-' + rowId + '-detail'; 12 | var displayStyle = $(selector).css('display'); 13 | if (displayStyle === 'none') { 14 | $(selector).css('display', 'table-row'); 15 | $('#plusminus-' + rowId).attr({ 16 | 'src': collapseImageIdSrc, 17 | 'alt': collapseImageIdAlt, 18 | 'title': collapseImageIdTitle 19 | }); 20 | } else { 21 | $(selector).css('display', 'none'); 22 | $('#plusminus-' + rowId).attr({ 23 | 'src' : expandImageIdSrc, 24 | 'alt' : expandImageIdAlt, 25 | 'title' : expandImageIdTitle 26 | }); 27 | } 28 | }); 29 | 30 | // If any individual forum is unchecked, the all_forums checkbox should be unchecked. 31 | // If all individual forums are checked, the all_forums checkbox should be checked. 32 | $("[name*='-forums']").click(function() { 33 | var allChecked = true; // Assume all forums for the block under focus are checked 34 | var id = ($(this).attr('id')); 35 | var rowId = getRowId(id); // Returns the unique row id for the block being changed by parsing the id attribute 36 | $("[name*='-forums']").each(function() { 37 | var instanceId = $(this).attr('id'); 38 | var instanceRowId = getRowId(instanceId); // Returns the unique row id for the block being changed by parsing the id attribute 39 | if (instanceRowId === rowId && !$(this).is(':checked')) { 40 | $("#user-" + instanceRowId + "-all_forums").prop('checked', false); 41 | allChecked = false; // Flag if any forum is unchecked 42 | } 43 | }); 44 | if (allChecked) { 45 | ($("#user-" + rowId + "-all_forums").prop('checked', true)); 46 | } 47 | else { 48 | ($("#user-" + rowId + "-all_forums").prop('checked', false)); 49 | } 50 | }); 51 | 52 | // If the all forums checkbox is checked, all individual forums should be checked, and visa versa. 53 | $("[id*=all_forums]").click(function() { 54 | var id = ($(this).attr('id')); 55 | var rowId = getRowId(id); // Returns the unique row id for the block being changed by parsing the id attribute 56 | if ($(this).is(':checked')) { 57 | $("[name*='-forums']").each(function() { 58 | var instanceId = $(this).attr('id'); 59 | var instanceRowId = getRowId(instanceId); // Returns the unique row id for the block being changed by parsing the id attribute 60 | if (instanceRowId === rowId) { 61 | $(this).prop("checked", true); 62 | } 63 | }); 64 | } 65 | else { 66 | $("[name*='-forums']").each(function() { 67 | var instanceId = $(this).attr('id'); 68 | var instanceRowId = getRowId(instanceId); // Returns the unique row id for the block being changed by parsing the id attribute 69 | if (instanceRowId === rowId) { 70 | $(this).prop("checked", false); 71 | } 72 | }); 73 | } 74 | }); 75 | 76 | // If a field was not changed, disable it so it won't be sent to the web server. This helps get around PHP's 77 | // max_input_var resource limitation on the Edit subscribers screen. The field should also not be marked as required, 78 | // to avoid a potential Javascript error that could stop form submission. 79 | $('#acp_digests').submit(function() { 80 | if ($('#acp_digests').length === 1) { 81 | // Logic only applies on edit subscribers screen because stack won't exist otherwise. #acp_digests is an 82 | // ID only on the edit subscribers screen. 83 | $('input, select').each(function() { 84 | if (!inStack($(this).attr('name'))) { 85 | $(this).prop('required', false); 86 | $(this).prop('disabled', true); 87 | } 88 | }); 89 | } 90 | }); 91 | 92 | function getRowId(id) { 93 | var end = id.indexOf('-', 5); 94 | return id.substring(5, end); 95 | } 96 | 97 | }); -------------------------------------------------------------------------------- /adm/style/event/acp_overall_footer_after.html: -------------------------------------------------------------------------------- 1 | {% if S_INCLUDE_DIGESTS_MANUAL_MAILER %} 2 | 10 | {% endif %} -------------------------------------------------------------------------------- /adm/style/event/acp_overall_header_stylesheets_after.html: -------------------------------------------------------------------------------- 1 | {% if S_INCLUDE_DIGESTS_MANUAL_MAILER %} 2 | {% if not definition.INCLUDED_JQUERYUI1131JS %} 3 | {% DEFINE INCLUDED_JQUERYUI1131JS = true %} 4 | {% INCLUDEJS '@phpbbservices_digests/jquery-ui.min.js' %} 5 | {% endif %} 6 | {% if not definition.INCLUDED_DATETIMEPICKERCSS %} 7 | {% DEFINE INCLUDED_DATETIMEPICKERCSS = true %} 8 | {% INCLUDECSS '@phpbbservices_digests/jquery.datetimepicker.css' %} 9 | {% endif %} 10 | {% if not definition.INCLUDED_DATETIMEPICKERJS %} 11 | {% DEFINE INCLUDED_DATETIMEPICKERJS = true %} 12 | {% INCLUDEJS '@phpbbservices_digests/jquery.datetimepicker.js' %} 13 | {% endif %} 14 | {% endif %} -------------------------------------------------------------------------------- /adm/style/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /adm/style/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /adm/style/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /adm/style/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /adm/style/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /adm/style/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/adm/style/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /adm/style/jquery.datetimepicker.css: -------------------------------------------------------------------------------- 1 | .xdsoft_datetimepicker { 2 | box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.506); 3 | background: #fff; 4 | border-bottom: 1px solid #bbb; 5 | border-left: 1px solid #ccc; 6 | border-right: 1px solid #ccc; 7 | border-top: 1px solid #ccc; 8 | color: #333; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | padding: 8px; 11 | padding-left: 0; 12 | padding-top: 2px; 13 | position: absolute; 14 | z-index: 9999; 15 | -moz-box-sizing: border-box; 16 | box-sizing: border-box; 17 | display: none; 18 | } 19 | .xdsoft_datetimepicker.xdsoft_rtl { 20 | padding: 8px 0 8px 8px; 21 | } 22 | 23 | .xdsoft_datetimepicker iframe { 24 | position: absolute; 25 | left: 0; 26 | top: 0; 27 | width: 75px; 28 | height: 210px; 29 | background: transparent; 30 | border: none; 31 | } 32 | 33 | /*For IE8 or lower*/ 34 | .xdsoft_datetimepicker button { 35 | border: none !important; 36 | } 37 | 38 | .xdsoft_noselect { 39 | -webkit-touch-callout: none; 40 | -webkit-user-select: none; 41 | -khtml-user-select: none; 42 | -moz-user-select: none; 43 | -ms-user-select: none; 44 | -o-user-select: none; 45 | user-select: none; 46 | } 47 | 48 | .xdsoft_noselect::selection { background: transparent } 49 | .xdsoft_noselect::-moz-selection { background: transparent } 50 | 51 | .xdsoft_datetimepicker.xdsoft_inline { 52 | display: inline-block; 53 | position: static; 54 | box-shadow: none; 55 | } 56 | 57 | .xdsoft_datetimepicker * { 58 | -moz-box-sizing: border-box; 59 | box-sizing: border-box; 60 | padding: 0; 61 | margin: 0; 62 | } 63 | 64 | .xdsoft_datetimepicker .xdsoft_datepicker, .xdsoft_datetimepicker .xdsoft_timepicker { 65 | display: none; 66 | } 67 | 68 | .xdsoft_datetimepicker .xdsoft_datepicker.active, .xdsoft_datetimepicker .xdsoft_timepicker.active { 69 | display: block; 70 | } 71 | 72 | .xdsoft_datetimepicker .xdsoft_datepicker { 73 | width: 224px; 74 | float: left; 75 | margin-left: 8px; 76 | } 77 | .xdsoft_datetimepicker.xdsoft_rtl .xdsoft_datepicker { 78 | float: right; 79 | margin-right: 8px; 80 | margin-left: 0; 81 | } 82 | 83 | .xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_datepicker { 84 | width: 256px; 85 | } 86 | 87 | .xdsoft_datetimepicker .xdsoft_timepicker { 88 | width: 58px; 89 | float: left; 90 | text-align: center; 91 | margin-left: 8px; 92 | margin-top: 0; 93 | } 94 | .xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker { 95 | float: right; 96 | margin-right: 8px; 97 | margin-left: 0; 98 | } 99 | 100 | .xdsoft_datetimepicker .xdsoft_datepicker.active+.xdsoft_timepicker { 101 | margin-top: 8px; 102 | margin-bottom: 3px 103 | } 104 | 105 | .xdsoft_datetimepicker .xdsoft_monthpicker { 106 | position: relative; 107 | text-align: center; 108 | } 109 | 110 | .xdsoft_datetimepicker .xdsoft_label i, 111 | .xdsoft_datetimepicker .xdsoft_prev, 112 | .xdsoft_datetimepicker .xdsoft_next, 113 | .xdsoft_datetimepicker .xdsoft_today_button { 114 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0NBRjI1NjM0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0NBRjI1NjQ0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQ0FGMjU2MTQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQ0FGMjU2MjQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoNEP54AAAIOSURBVHja7Jq9TsMwEMcxrZD4WpBYeKUCe+kTMCACHZh4BFfHO/AAIHZGFhYkBBsSEqxsLCAgXKhbXYOTxh9pfJVP+qutnZ5s/5Lz2Y5I03QhWji2GIcgAokWgfCxNvcOCCGKqiSqhUp0laHOne05vdEyGMfkdxJDVjgwDlEQgYQBgx+ULJaWSXXS6r/ER5FBVR8VfGftTKcITNs+a1XpcFoExREIDF14AVIFxgQUS+h520cdud6wNkC0UBw6BCO/HoCYwBhD8QCkQ/x1mwDyD4plh4D6DDV0TAGyo4HcawLIBBSLDkHeH0Mg2yVP3l4TQMZQDDsEOl/MgHQqhMNuE0D+oBh0CIr8MAKyazBH9WyBuKxDWgbXfjNf32TZ1KWm/Ap1oSk/R53UtQ5xTh3LUlMmT8gt6g51Q9p+SobxgJQ/qmsfZhWywGFSl0yBjCLJCMgXail3b7+rumdVJ2YRss4cN+r6qAHDkPWjPjdJCF4n9RmAD/V9A/Wp4NQassDjwlB6XBiCxcJQWmZZb8THFilfy/lfrTvLghq2TqTHrRMTKNJ0sIhdo15RT+RpyWwFdY96UZ/LdQKBGjcXpcc1AlSFEfLmouD+1knuxBDUVrvOBmoOC/rEcN7OQxKVeJTCiAdUzUJhA2Oez9QTkp72OTVcxDcXY8iKNkxGAJXmJCOQwOa6dhyXsOa6XwEGAKdeb5ET3rQdAAAAAElFTkSuQmCC); 115 | } 116 | 117 | .xdsoft_datetimepicker .xdsoft_label i { 118 | opacity: 0.5; 119 | background-position: -92px -19px; 120 | display: inline-block; 121 | width: 9px; 122 | height: 20px; 123 | vertical-align: middle; 124 | } 125 | 126 | .xdsoft_datetimepicker .xdsoft_prev { 127 | float: left; 128 | background-position: -20px 0; 129 | } 130 | .xdsoft_datetimepicker .xdsoft_today_button { 131 | float: left; 132 | background-position: -70px 0; 133 | margin-left: 5px; 134 | } 135 | 136 | .xdsoft_datetimepicker .xdsoft_next { 137 | float: right; 138 | background-position: 0 0; 139 | } 140 | 141 | .xdsoft_datetimepicker .xdsoft_next, 142 | .xdsoft_datetimepicker .xdsoft_prev , 143 | .xdsoft_datetimepicker .xdsoft_today_button { 144 | background-color: transparent; 145 | background-repeat: no-repeat; 146 | border: 0 none; 147 | cursor: pointer; 148 | display: block; 149 | height: 30px; 150 | opacity: 0.5; 151 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 152 | outline: medium none; 153 | overflow: hidden; 154 | padding: 0; 155 | position: relative; 156 | text-indent: 100%; 157 | white-space: nowrap; 158 | width: 20px; 159 | min-width: 0; 160 | } 161 | 162 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev, 163 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_next { 164 | float: none; 165 | background-position: -40px -15px; 166 | height: 15px; 167 | width: 30px; 168 | display: block; 169 | margin-left: 14px; 170 | margin-top: 7px; 171 | } 172 | .xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_prev, 173 | .xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_next { 174 | float: none; 175 | margin-left: 0; 176 | margin-right: 14px; 177 | } 178 | 179 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev { 180 | background-position: -40px 0; 181 | margin-bottom: 7px; 182 | margin-top: 0; 183 | } 184 | 185 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box { 186 | height: 151px; 187 | overflow: hidden; 188 | border-bottom: 1px solid #ddd; 189 | } 190 | 191 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div { 192 | background: #f5f5f5; 193 | border-top: 1px solid #ddd; 194 | color: #666; 195 | font-size: 12px; 196 | text-align: center; 197 | border-collapse: collapse; 198 | cursor: pointer; 199 | border-bottom-width: 0; 200 | height: 25px; 201 | line-height: 25px; 202 | } 203 | 204 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div > div:first-child { 205 | border-top-width: 0; 206 | } 207 | 208 | .xdsoft_datetimepicker .xdsoft_today_button:hover, 209 | .xdsoft_datetimepicker .xdsoft_next:hover, 210 | .xdsoft_datetimepicker .xdsoft_prev:hover { 211 | opacity: 1; 212 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 213 | } 214 | 215 | .xdsoft_datetimepicker .xdsoft_label { 216 | display: inline; 217 | position: relative; 218 | z-index: 9999; 219 | margin: 0; 220 | padding: 5px 3px; 221 | font-size: 14px; 222 | line-height: 20px; 223 | font-weight: bold; 224 | background-color: #fff; 225 | float: left; 226 | width: 182px; 227 | text-align: center; 228 | cursor: pointer; 229 | } 230 | 231 | .xdsoft_datetimepicker .xdsoft_label:hover>span { 232 | text-decoration: underline; 233 | } 234 | 235 | .xdsoft_datetimepicker .xdsoft_label:hover i { 236 | opacity: 1.0; 237 | } 238 | 239 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select { 240 | border: 1px solid #ccc; 241 | position: absolute; 242 | right: 0; 243 | top: 30px; 244 | z-index: 101; 245 | display: none; 246 | background: #fff; 247 | max-height: 160px; 248 | overflow-y: hidden; 249 | } 250 | 251 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_monthselect{ right: -7px } 252 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_yearselect{ right: 2px } 253 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover { 254 | color: #fff; 255 | background: #ff8000; 256 | } 257 | 258 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option { 259 | padding: 2px 10px 2px 5px; 260 | text-decoration: none !important; 261 | } 262 | 263 | .xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current { 264 | background: #33aaff; 265 | box-shadow: #178fe5 0 1px 3px 0 inset; 266 | color: #fff; 267 | font-weight: 700; 268 | } 269 | 270 | .xdsoft_datetimepicker .xdsoft_month { 271 | width: 100px; 272 | text-align: right; 273 | } 274 | 275 | .xdsoft_datetimepicker .xdsoft_calendar { 276 | clear: both; 277 | } 278 | 279 | .xdsoft_datetimepicker .xdsoft_year{ 280 | width: 48px; 281 | margin-left: 5px; 282 | } 283 | 284 | .xdsoft_datetimepicker .xdsoft_calendar table { 285 | border-collapse: collapse; 286 | width: 100%; 287 | 288 | } 289 | 290 | .xdsoft_datetimepicker .xdsoft_calendar td > div { 291 | padding-right: 5px; 292 | } 293 | 294 | .xdsoft_datetimepicker .xdsoft_calendar th { 295 | height: 25px; 296 | } 297 | 298 | .xdsoft_datetimepicker .xdsoft_calendar td,.xdsoft_datetimepicker .xdsoft_calendar th { 299 | width: 14.2857142%; 300 | background: #f5f5f5; 301 | border: 1px solid #ddd; 302 | color: #666; 303 | font-size: 12px; 304 | text-align: right; 305 | vertical-align: middle; 306 | padding: 0; 307 | border-collapse: collapse; 308 | cursor: pointer; 309 | height: 25px; 310 | } 311 | .xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar td,.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar th { 312 | width: 12.5%; 313 | } 314 | 315 | .xdsoft_datetimepicker .xdsoft_calendar th { 316 | background: #f1f1f1; 317 | } 318 | 319 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_today { 320 | color: #33aaff; 321 | } 322 | 323 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_default { 324 | background: #ffe9d2; 325 | box-shadow: #ffb871 0 1px 4px 0 inset; 326 | color: #000; 327 | } 328 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_mint { 329 | background: #c1ffc9; 330 | box-shadow: #00dd1c 0 1px 4px 0 inset; 331 | color: #000; 332 | } 333 | 334 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_default, 335 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current, 336 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_current { 337 | background: #33aaff; 338 | box-shadow: #178fe5 0 1px 3px 0 inset; 339 | color: #fff; 340 | font-weight: 700; 341 | } 342 | 343 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month, 344 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled, 345 | .xdsoft_datetimepicker .xdsoft_time_box >div >div.xdsoft_disabled { 346 | opacity: 0.5; 347 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 348 | cursor: default; 349 | } 350 | 351 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled { 352 | opacity: 0.2; 353 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; 354 | } 355 | 356 | .xdsoft_datetimepicker .xdsoft_calendar td:hover, 357 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div:hover { 358 | color: #fff !important; 359 | background: #ff8000 !important; 360 | box-shadow: none !important; 361 | } 362 | 363 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current.xdsoft_disabled:hover, 364 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current.xdsoft_disabled:hover { 365 | background: #33aaff !important; 366 | box-shadow: #178fe5 0 1px 3px 0 inset !important; 367 | color: #fff !important; 368 | } 369 | 370 | .xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled:hover, 371 | .xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_disabled:hover { 372 | color: inherit !important; 373 | background: inherit !important; 374 | box-shadow: inherit !important; 375 | } 376 | 377 | .xdsoft_datetimepicker .xdsoft_calendar th { 378 | font-weight: 700; 379 | text-align: center; 380 | color: #999; 381 | cursor: default; 382 | } 383 | 384 | .xdsoft_datetimepicker .xdsoft_copyright { 385 | color: #ccc !important; 386 | font-size: 10px; 387 | clear: both; 388 | float: none; 389 | margin-left: 8px; 390 | } 391 | 392 | .xdsoft_datetimepicker .xdsoft_copyright a { color: #eee !important } 393 | .xdsoft_datetimepicker .xdsoft_copyright a:hover { color: #aaa !important } 394 | 395 | .xdsoft_time_box { 396 | position: relative; 397 | border: 1px solid #ccc; 398 | } 399 | .xdsoft_scrollbar >.xdsoft_scroller { 400 | background: #ccc !important; 401 | height: 20px; 402 | border-radius: 3px; 403 | } 404 | .xdsoft_scrollbar { 405 | position: absolute; 406 | width: 7px; 407 | right: 0; 408 | top: 0; 409 | bottom: 0; 410 | cursor: pointer; 411 | } 412 | .xdsoft_datetimepicker.xdsoft_rtl .xdsoft_scrollbar { 413 | left: 0; 414 | right: auto; 415 | } 416 | .xdsoft_scroller_box { 417 | position: relative; 418 | } 419 | 420 | .xdsoft_datetimepicker.xdsoft_dark { 421 | box-shadow: 0 5px 15px -5px rgba(255, 255, 255, 0.506); 422 | background: #000; 423 | border-bottom: 1px solid #444; 424 | border-left: 1px solid #333; 425 | border-right: 1px solid #333; 426 | border-top: 1px solid #333; 427 | color: #ccc; 428 | } 429 | 430 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box { 431 | border-bottom: 1px solid #222; 432 | } 433 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div { 434 | background: #0a0a0a; 435 | border-top: 1px solid #222; 436 | color: #999; 437 | } 438 | 439 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_label { 440 | background-color: #000; 441 | } 442 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select { 443 | border: 1px solid #333; 444 | background: #000; 445 | } 446 | 447 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover { 448 | color: #000; 449 | background: #007fff; 450 | } 451 | 452 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current { 453 | background: #cc5500; 454 | box-shadow: #b03e00 0 1px 3px 0 inset; 455 | color: #000; 456 | } 457 | 458 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_label i, 459 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_prev, 460 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_next, 461 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_today_button { 462 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUExQUUzOTA0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUExQUUzOTE0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQTFBRTM4RTQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQTFBRTM4RjQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pp0VxGEAAAIASURBVHja7JrNSgMxEMebtgh+3MSLr1T1Xn2CHoSKB08+QmR8Bx9A8e7RixdB9CKCoNdexIugxFlJa7rNZneTbLIpM/CnNLsdMvNjM8l0mRCiQ9Ye61IKCAgZAUnH+mU3MMZaHYChBnJUDzWOFZdVfc5+ZFLbrWDeXPwbxIqrLLfaeS0hEBVGIRQCEiZoHQwtlGSByCCdYBl8g8egTTAWoKQMRBRBcZxYlhzhKegqMOageErsCHVkk3hXIFooDgHB1KkHIHVgzKB4ADJQ/A1jAFmAYhkQqA5TOBtocrKrgXwQA8gcFIuAIO8sQSA7hidvPwaQGZSaAYHOUWJABhWWw2EMIH9QagQERU4SArJXo0ZZL18uvaxejXt/Em8xjVBXmvFr1KVm/AJ10tRe2XnraNqaJvKE3KHuUbfK1E+VHB0q40/y3sdQSxY4FHWeKJCunP8UyDdqJZenT3ntVV5jIYCAh20vT7ioP8tpf6E2lfEMwERe+whV1MHjwZB7PBiCxcGQWwKZKD62lfGNnP/1poFAA60T7rF1UgcKd2id3KDeUS+oLWV8DfWAepOfq00CgQabi9zjcgJVYVD7PVzQUAUGAQkbNJTBICDhgwYTjDYD6XeW08ZKh+A4pYkzenOxXUbvZcWz7E8ykRMnIHGX1XPl+1m2vPYpL+2qdb8CDAARlKFEz/ZVkAAAAABJRU5ErkJggg==); 463 | } 464 | 465 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td, 466 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th { 467 | background: #0a0a0a; 468 | border: 1px solid #222; 469 | color: #999; 470 | } 471 | 472 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th { 473 | background: #0e0e0e; 474 | } 475 | 476 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_today { 477 | color: #cc5500; 478 | } 479 | 480 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_default { 481 | background: #ffe9d2; 482 | box-shadow: #ffb871 0 1px 4px 0 inset; 483 | color:#000; 484 | } 485 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_mint { 486 | background: #c1ffc9; 487 | box-shadow: #00dd1c 0 1px 4px 0 inset; 488 | color:#000; 489 | } 490 | 491 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_default, 492 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_current, 493 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_current { 494 | background: #cc5500; 495 | box-shadow: #b03e00 0 1px 3px 0 inset; 496 | color: #000; 497 | } 498 | 499 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td:hover, 500 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div:hover { 501 | color: #000 !important; 502 | background: #007fff !important; 503 | } 504 | 505 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th { 506 | color: #666; 507 | } 508 | 509 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright { color: #333 !important } 510 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a { color: #111 !important } 511 | .xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a:hover { color: #555 !important } 512 | 513 | .xdsoft_dark .xdsoft_time_box { 514 | border: 1px solid #333; 515 | } 516 | 517 | .xdsoft_dark .xdsoft_scrollbar >.xdsoft_scroller { 518 | background: #333 !important; 519 | } 520 | .xdsoft_datetimepicker .xdsoft_save_selected { 521 | display: block; 522 | border: 1px solid #dddddd !important; 523 | margin-top: 5px; 524 | width: 100%; 525 | color: #454551; 526 | font-size: 13px; 527 | } 528 | .xdsoft_datetimepicker .blue-gradient-button { 529 | font-family: "museo-sans", "Book Antiqua", sans-serif; 530 | font-size: 12px; 531 | font-weight: 300; 532 | color: #82878c; 533 | height: 28px; 534 | position: relative; 535 | padding: 4px 17px 4px 33px; 536 | border: 1px solid #d7d8da; 537 | background: -moz-linear-gradient(top, #fff 0%, #f4f8fa 73%); 538 | /* FF3.6+ */ 539 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(73%, #f4f8fa)); 540 | /* Chrome,Safari4+ */ 541 | background: -webkit-linear-gradient(top, #fff 0%, #f4f8fa 73%); 542 | /* Chrome10+,Safari5.1+ */ 543 | background: -o-linear-gradient(top, #fff 0%, #f4f8fa 73%); 544 | /* Opera 11.10+ */ 545 | background: -ms-linear-gradient(top, #fff 0%, #f4f8fa 73%); 546 | /* IE10+ */ 547 | background: linear-gradient(to bottom, #fff 0%, #f4f8fa 73%); 548 | /* W3C */ 549 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff', endColorstr='#f4f8fa',GradientType=0 ); 550 | /* IE6-9 */ 551 | } 552 | .xdsoft_datetimepicker .blue-gradient-button:hover, .xdsoft_datetimepicker .blue-gradient-button:focus, .xdsoft_datetimepicker .blue-gradient-button:hover span, .xdsoft_datetimepicker .blue-gradient-button:focus span { 553 | color: #454551; 554 | background: -moz-linear-gradient(top, #f4f8fa 0%, #FFF 73%); 555 | /* FF3.6+ */ 556 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f4f8fa), color-stop(73%, #FFF)); 557 | /* Chrome,Safari4+ */ 558 | background: -webkit-linear-gradient(top, #f4f8fa 0%, #FFF 73%); 559 | /* Chrome10+,Safari5.1+ */ 560 | background: -o-linear-gradient(top, #f4f8fa 0%, #FFF 73%); 561 | /* Opera 11.10+ */ 562 | background: -ms-linear-gradient(top, #f4f8fa 0%, #FFF 73%); 563 | /* IE10+ */ 564 | background: linear-gradient(to bottom, #f4f8fa 0%, #FFF 73%); 565 | /* W3C */ 566 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f8fa', endColorstr='#FFF',GradientType=0 ); 567 | /* IE6-9 */ 568 | } 569 | -------------------------------------------------------------------------------- /adm/style/mail_digests_html.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /adm/style/mail_digests_text.html: -------------------------------------------------------------------------------- 1 | 2 | {% if S_SHOW_PMS %} 3 | {{ lang('YOU_HAVE_PRIVATE_MESSAGES') }} 4 | 5 | {% for pm in loops.pm %} 6 | {{ pm.NEW_UNREAD }}{{ lang('PRIVATE_MESSAGE') }} 7 | {{ lang('SUBJECT') }}{{ lang('COLON') }} {{ pm.PRIVATE_MESSAGE_SUBJECT }} 8 | {{ lang('FROM') }}{{ lang('COLON') }} {{ pm.FROM }} {{ lang('DIGESTS_ON') }} {{ pm.DATE }} 9 | {{ pm.CONTENT }} 10 | 11 | {% else %} 12 | {{ lang('DIGESTS_NO_PRIVATE_MESSAGES') }} 13 | {% endfor %} 14 | ____________________________________________________________ 15 | {% endif %} 16 | 17 | {% for forum in loops.forum %} 18 | 19 | 20 | {{ lang('FORUM') }}{{ lang('COLON') }} {{ forum.FORUM }} 21 | 22 | {% for topic in forum.topic %} 23 | 24 | 25 | {{ lang('TOPIC') }}{{ lang('COLON') }} {{ topic.TOPIC }} 26 | 27 | {% for post in topic.post %} 28 | 29 | 30 | {% if not post.S_FIRST_POST %} 31 | {{ lang('DIGESTS_REPLY') }}{{ lang('COLON') }} {{ post.SUBJECT }} 32 | {% endif %} 33 | {{ lang('DIGESTS_BY') }}{{ lang('COLON') }} {{ post.FROM }} {{ lang('DIGESTS_ON') }} {{ post.DATE }} 34 | {% if S_SHOW_POST_TEXT %}{{ post.CONTENT }}{% endif %} 35 | 36 | {% endfor %} 37 | {% endfor %} 38 | {% else %} 39 | 40 | {{ lang('DIGESTS_NO_POSTS') }} 41 | {% endfor %} -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpbbservices/digests", 3 | "type": "phpbb-extension", 4 | "description": "Sends daily, weekly or monthly digests of posts or bookmarked topics to subscribers via email.", 5 | "homepage": "https://www.phpbbservices.com/my-software/digests_wp/digests-extension", 6 | "version": "3.3.18", 7 | "time": "2023-02-05", 8 | "license": "GPL-2.0-only", 9 | "authors": [ 10 | { 11 | "name": "Mark D. Hamill", 12 | "email": "mark@phpbbservices.com", 13 | "homepage": "https://www.phpbbservices.com/", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=7.1.3,<=8.1", 19 | "composer/installers": "~1.0", 20 | "ext-libxml": "*", 21 | "ext-dom": "*" 22 | }, 23 | "extra": { 24 | "display-name": "Digests", 25 | "soft-require": { 26 | "phpbb/phpbb": ">=3.3.0,<4.0" 27 | }, 28 | "version-check": { 29 | "host": "www.phpbbservices.com", 30 | "directory": "/digests/updatecheck/", 31 | "filename": "3.3.json", 32 | "ssl": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/routing.yml: -------------------------------------------------------------------------------- 1 | phpbbservices_digests_controller: 2 | path: /digests/unsubscribe 3 | defaults: { _controller: phpbbservices.digests.controller.unsubscribe:handle } -------------------------------------------------------------------------------- /config/services.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: tables.yml } 3 | 4 | services: 5 | phpbbservices.digests.controller.acp: 6 | class: phpbbservices\digests\controller\acp_controller 7 | arguments: 8 | - '@auth' 9 | - '@config' 10 | - '@dbal.conn' 11 | - '@phpbbservices.digests.common' 12 | - '@language' 13 | - '@phpbbservices.digests.cron.task.cron_task' 14 | - '@pagination' 15 | - '@ext.manager' 16 | - '@log' 17 | - '@path_helper' 18 | - '%core.root_path%' 19 | - '%core.php_ext%' 20 | - '@request' 21 | - '@template' 22 | - '@user' 23 | - '%phpbbservices.digests.tables.digests_subscribed_forums_table%' 24 | - '%phpbbservices.digests.tables.digests_report_table%' 25 | - '%phpbbservices.digests.tables.digests_report_details_table%' 26 | phpbbservices.digests.controller.ucp: 27 | class: phpbbservices\digests\controller\ucp_controller 28 | arguments: 29 | - '@auth' 30 | - '@config' 31 | - '@dbal.conn' 32 | - '@phpbbservices.digests.common' 33 | - '@language' 34 | - '@request' 35 | - '@template' 36 | - '@user' 37 | - '%core.root_path%' 38 | - '%core.php_ext%' 39 | - '%phpbbservices.digests.tables.digests_subscribed_forums_table%' 40 | phpbbservices.digests.listener: 41 | class: phpbbservices\digests\event\main_listener 42 | arguments: 43 | - '@config' 44 | - '@template' 45 | - '@request' 46 | - '@dbal.conn' 47 | - '%phpbbservices.digests.tables.digests_subscribed_forums_table%' 48 | - '%phpbbservices.digests.tables.digests_report_details_table%' 49 | tags: 50 | - { name: event.listener } 51 | phpbbservices.digests.common: 52 | class: phpbbservices\digests\core\common 53 | arguments: 54 | - '@language' 55 | - '%core.root_path%' 56 | - '@filesystem' 57 | - '@log' 58 | - '@user' 59 | - '%core.php_ext%' 60 | - '@config' 61 | - '@dbal.conn' 62 | phpbbservices.digests.cron.task.cron_task: 63 | class: phpbbservices\digests\cron\task\digests 64 | arguments: 65 | - '@config' 66 | - '@request' 67 | - '@user' 68 | - '@dbal.conn' 69 | - '%core.php_ext%' 70 | - '%core.root_path%' 71 | - '@template' 72 | - '@auth' 73 | - '@log' 74 | - '@language' 75 | - '@notification_manager' 76 | - '@phpbbservices.digests.common' 77 | - '@profilefields.manager' 78 | - '@dispatcher' 79 | - '%phpbbservices.digests.tables.digests_subscribed_forums_table%' 80 | - '%phpbbservices.digests.tables.digests_report_table%' 81 | - '%phpbbservices.digests.tables.digests_report_details_table%' 82 | calls: 83 | - [set_name, [phpbbservices.digests.cron.task.cron_task]] 84 | tags: 85 | - { name: cron.task } 86 | phpbbservices.digests.controller.unsubscribe: 87 | class: phpbbservices\digests\controller\unsubscribe 88 | arguments: 89 | - '@request' 90 | - '@user' 91 | - '@dbal.conn' 92 | - '@phpbbservices.digests.common' 93 | - '@language' 94 | - '%phpbbservices.digests.tables.digests_subscribed_forums_table%' 95 | phpbbservices.digests.includes.html_messenger: 96 | class: phpbbservices\digests\includes\html_messenger 97 | arguments: 98 | - '@user' 99 | - '@dispatcher' 100 | - '@language' 101 | -------------------------------------------------------------------------------- /config/tables.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | phpbbservices.digests.tables.digests_subscribed_forums_table: '%core.table_prefix%digests_subscribed_forums' 3 | phpbbservices.digests.tables.digests_report_table: '%core.table_prefix%digests_report' 4 | phpbbservices.digests.tables.digests_report_details_table: '%core.table_prefix%digests_report_details' -------------------------------------------------------------------------------- /constants/constants.php: -------------------------------------------------------------------------------- 1 | db = $db; 42 | $this->helper = $helper; 43 | $this->language = $language; 44 | $this->request = $request; 45 | $this->subscribed_forums_table = $subscribed_forums_table; 46 | $this->user = $user; 47 | } 48 | 49 | /** 50 | * Controller handler for digests unsubscribe logic 51 | */ 52 | public function handle() 53 | { 54 | // This function handles one-click unsubscribe. The link is in the footer of the email digest. 55 | 56 | $user_id = $this->request->variable('u', ANONYMOUS, true); 57 | $salt = $this->request->variable('s', '', true); // The user_form_salt for the user must match the user_id for the unsubscribe request to be assumed to be legitimate 58 | 59 | $success = false; 60 | if ($user_id != ANONYMOUS) 61 | { 62 | $sql = 'SELECT user_form_salt, user_email 63 | FROM ' . USERS_TABLE . ' 64 | WHERE user_id = ' . (int) $user_id . ' AND ' . $this->db->sql_in_set('user_type', array(USER_IGNORE), true); 65 | $result = $this->db->sql_query($sql); 66 | $rowset = $this->db->sql_fetchrowset($result); 67 | $email = ''; 68 | 69 | if (count($rowset) == 1 && trim($rowset[0]['user_form_salt']) == trim($salt)) 70 | { 71 | // This unsubscribe request should be valid because the user_id matches the email address in the request 72 | $sql2 = 'UPDATE ' . USERS_TABLE . " 73 | SET user_digest_type = '" . constants::DIGESTS_NONE_VALUE . "' 74 | WHERE user_id = " . (int) $user_id; 75 | $this->db->sql_query($sql2); 76 | $success = true; 77 | 78 | // Delete any forum subscriptions 79 | $sql2 = 'DELETE FROM ' . $this->subscribed_forums_table . ' 80 | WHERE user_id = ' . (int) $user_id; 81 | $this->db->sql_query($sql2); 82 | 83 | $email = $rowset[0]['user_email']; 84 | 85 | } 86 | $this->db->sql_freeresult($result); 87 | 88 | } 89 | 90 | if ($success) 91 | { 92 | trigger_error($this->language->lang('DIGESTS_UNSUBSCRIBE_SUCCESS')); 93 | // Send a courtesy unsubscribe confirmation email 94 | $this->helper->notify_subscribers(array($email), 'digests_unsubscribe_one_click'); 95 | } 96 | else 97 | { 98 | trigger_error($this->language->lang('DIGESTS_UNSUBSCRIBE_FAILURE'), E_USER_WARNING); 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /core/common.php: -------------------------------------------------------------------------------- 1 | config = $config; 47 | $this->db = $db; 48 | $this->filesystem = $filesystem; 49 | $this->language = $language; 50 | $this->phpbb_log = $phpbb_log; 51 | $this->phpbb_root_path = $phpbb_root_path; 52 | $this->phpEx = $php_ext; 53 | $this->user = $user; 54 | } 55 | 56 | public function make_hour_string($hour, $user_dateformat) 57 | { 58 | 59 | // This function returns a string representing an hour (0-23) for display. It attempts to be smart by looking at 60 | // the user's date format and determining whether it supports AM/PM or not. 61 | 62 | static $display_hour_array_am_pm = array(12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11); 63 | 64 | // Is AM/PM expected? 65 | $use_lowercase_am_pm = strstr($user_dateformat,'a'); 66 | $use_uppercase_am_pm = strstr($user_dateformat,'A'); 67 | $twenty_four_hour_time = !($use_lowercase_am_pm || $use_uppercase_am_pm); 68 | 69 | if ($twenty_four_hour_time) 70 | { 71 | return $hour . $this->language->lang('DIGESTS_HOURS_ABBREVIATION'); 72 | } 73 | else 74 | { 75 | $am_pm = ($hour < 12) ? $this->language->lang('DIGESTS_AM') : $this->language->lang('DIGESTS_PM'); 76 | if ($use_uppercase_am_pm) 77 | { 78 | return $display_hour_array_am_pm[$hour] . strtoupper($am_pm); 79 | } 80 | else 81 | { 82 | return $display_hour_array_am_pm[$hour] . strtolower($am_pm); 83 | } 84 | } 85 | 86 | } 87 | 88 | public function make_tz_offset ($tz_text, $show_sign = false) 89 | { 90 | // This function translates a text timezone (like America/New_York) to an hour offset from UTC, doing magic like figuring out if DST applies 91 | if (!$this->validate_date($tz_text)) 92 | { 93 | // Date string is invalid so assume UTC 94 | $timeOffset = 0; 95 | } 96 | else 97 | { 98 | $tz = new \DateTimeZone($tz_text); 99 | $datetime_tz = new \DateTime('now', $tz); 100 | $timeOffset = $tz->getOffset($datetime_tz) / 3600; 101 | } 102 | return ($show_sign && $timeOffset >= 0) ? '+' . $timeOffset : $timeOffset; 103 | } 104 | 105 | public function validate_date($date) 106 | { 107 | // This functions checks to see if a date format (like America/New_York) is valid. If not, it returns false. 108 | $d = \DateTime::createFromFormat('e', $date); 109 | return $d && $d->format('e') === $date; 110 | } 111 | 112 | public function validate_iso_date($date) 113 | { 114 | 115 | // Validates an ISO-8601 date string. Found here: https://stackoverflow.com/questions/8003446/php-validate-iso-8601-date-string. 116 | // This version does not require Zulu or use gmmktime, or require T. Example: 2018-12-19 14:00:00 117 | if (preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $date, $parts) == true) 118 | { 119 | $time = mktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]); 120 | 121 | $input_time = strtotime($date); 122 | if ($input_time === false) 123 | { 124 | return false; 125 | } 126 | return $input_time == $time; 127 | } 128 | else 129 | { 130 | return false; 131 | } 132 | 133 | } 134 | 135 | public function check_send_hour($hour) 136 | { 137 | // Ensures an hour falls between 0 and 24, adjusts if outside the range. 138 | if ($hour >= 24) 139 | { 140 | return (float) ($hour - 24); 141 | } 142 | else if ($hour < 0) 143 | { 144 | return (float) ($hour + 24); 145 | } 146 | else 147 | { 148 | return (float) $hour; 149 | } 150 | } 151 | 152 | public function make_directories() 153 | { 154 | // Makes the store/phpbbservices/digest directory. If they are successfully created, returns true. If they 155 | // cannot be created (likely due to permission issues), returns false. 156 | 157 | if (!$this->filesystem->exists($this->phpbb_root_path . 'store/phpbbservices')) 158 | { 159 | try 160 | { 161 | $this->filesystem->mkdir($this->phpbb_root_path . 'store/phpbbservices', '0777'); 162 | } 163 | catch (\Exception $e) 164 | { 165 | $this->phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_DIGESTS_EXCEPTION_ERROR', false, array($e->getMessage())); 166 | return false; 167 | } 168 | } 169 | 170 | if (!$this->filesystem->exists($this->phpbb_root_path . 'store/phpbbservices/digests')) 171 | { 172 | try 173 | { 174 | $this->filesystem->mkdir($this->phpbb_root_path . 'store/phpbbservices/digests', '0777'); 175 | } 176 | catch (\Exception $e) 177 | { 178 | $this->phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_DIGESTS_EXCEPTION_ERROR', false, array($e->getMessage())); 179 | return false; 180 | } 181 | } 182 | 183 | return true; 184 | 185 | } 186 | 187 | function notify_subscribers ($digest_notify_list, $email_template = '') 188 | { 189 | 190 | // This function parses $digest_notify_list, an array of user_ids that represent users that had their digest subscriptions changed, and sends them an email 191 | // letting them know an action has occurred. 192 | $emails_sent = 0; 193 | 194 | if (isset($digest_notify_list) && (count($digest_notify_list) > 0)) 195 | { 196 | 197 | if (!class_exists('messenger')) 198 | { 199 | include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->phpEx); // Used to send emails 200 | } 201 | 202 | $sql_array = array( 203 | 'SELECT' => 'username, user_email, user_lang, user_digest_type, user_digest_format', 204 | 205 | 'FROM' => array( 206 | USERS_TABLE => 'u', 207 | ), 208 | 209 | 'WHERE' => $this->db->sql_in_set('user_id', $digest_notify_list), 210 | ); 211 | 212 | $sql = $this->db->sql_build_query('SELECT', $sql_array); 213 | 214 | $result = $this->db->sql_query($sql); 215 | $rowset = $this->db->sql_fetchrowset($result); 216 | 217 | // E-mail setup 218 | $messenger = new \messenger(); 219 | 220 | foreach ($rowset as $row) 221 | { 222 | 223 | switch ($email_template) 224 | { 225 | case 'digests_unsubscribe_one_click': 226 | $digest_notify_template = $email_template; 227 | $digest_email_subject = $this->language->lang('DIGESTS_UNSUBSCRIBE_SUCCESS'); 228 | break; 229 | 230 | case 'digests_subscription_edited': 231 | $digest_notify_template = $email_template; 232 | $digest_email_subject = $this->language->lang('DIGESTS_SUBSCRIBE_EDITED'); 233 | break; 234 | 235 | default: 236 | // Mass subscribe/unsubscribe 237 | $digest_notify_template = ($this->config->offsetGet('phpbbservices_digests_subscribe_all')) ? 'digests_subscribe' : 'digests_unsubscribe'; 238 | $digest_email_subject = ($this->config->offsetGet('phpbbservices_digests_subscribe_all')) ? $this->language->lang('DIGESTS_SUBSCRIBE_SUBJECT') : $this->language->lang('DIGESTS_UNSUBSCRIBE_SUBJECT'); 239 | break; 240 | } 241 | 242 | // Set up associations between digest types as constants and their language equivalents 243 | switch ($row['user_digest_type']) 244 | { 245 | case constants::DIGESTS_WEEKLY_VALUE: 246 | $digest_type_text = strtolower($this->language->lang('DIGESTS_WEEKLY')); 247 | break; 248 | 249 | case constants::DIGESTS_MONTHLY_VALUE: 250 | $digest_type_text = strtolower($this->language->lang('DIGESTS_MONTHLY')); 251 | break; 252 | 253 | case constants::DIGESTS_NONE_VALUE: 254 | $digest_type_text = strtolower($this->language->lang('DIGESTS_NONE')); 255 | break; 256 | 257 | case constants::DIGESTS_DAILY_VALUE: 258 | default: 259 | $digest_type_text = strtolower($this->language->lang('DIGESTS_DAILY')); 260 | break; 261 | } 262 | 263 | // Set up associations between digest formats as constants and their language equivalents 264 | switch ($row['user_digest_format']) 265 | { 266 | case constants::DIGESTS_HTML_CLASSIC_VALUE: 267 | $digest_format_text = $this->language->lang('DIGESTS_FORMAT_HTML_CLASSIC'); 268 | break; 269 | 270 | case constants::DIGESTS_PLAIN_VALUE: 271 | $digest_format_text = $this->language->lang('DIGESTS_FORMAT_PLAIN'); 272 | break; 273 | 274 | case constants::DIGESTS_PLAIN_CLASSIC_VALUE: 275 | $digest_format_text = $this->language->lang('DIGESTS_FORMAT_PLAIN_CLASSIC'); 276 | break; 277 | 278 | case constants::DIGESTS_TEXT_VALUE: 279 | $digest_format_text = strtolower($this->language->lang('DIGESTS_FORMAT_TEXT')); 280 | break; 281 | 282 | case constants::DIGESTS_HTML_VALUE: 283 | default: 284 | $digest_format_text = $this->language->lang('DIGESTS_FORMAT_HTML'); 285 | break; 286 | } 287 | 288 | $messenger->template('@phpbbservices_digests/' . $digest_notify_template, $row['user_lang']); 289 | $messenger->to($row['user_email']); 290 | 291 | $from_addr = trim(($this->config->offsetGet('phpbbservices_digests_from_email_address') == '') ? $this->config->offsetGet('board_email') : $this->config->offsetGet('phpbbservices_digests_from_email_address')); 292 | $from_name = trim(($this->config->offsetGet('phpbbservices_digests_from_email_name') == '') ? $this->config->offsetGet('board_contact_name') : $this->config->offsetGet('phpbbservices_digests_from_email_name')); 293 | $from_name_exists = (bool) $from_name != ''; 294 | 295 | // SMTP delivery must strip text names due to likely bug in messenger class 296 | if ($this->config->offsetGet('smtp_delivery') || !$from_name_exists) 297 | { 298 | $messenger->from($from_addr); 299 | } 300 | else 301 | { 302 | $messenger->from($from_name . ' <' . $from_addr . '>'); 303 | } 304 | 305 | $messenger->replyto($from_addr); 306 | $messenger->subject($digest_email_subject); 307 | 308 | $messenger->assign_vars(array( 309 | 'DIGESTS_FORMAT' => $digest_format_text, 310 | 'DIGESTS_TYPE' => $digest_type_text, 311 | 'DIGESTS_UCP_LINK' => generate_board_url() . '/' . 'ucp.' . $this->phpEx, 312 | 'FORUM_NAME' => $this->config->offsetGet('sitename'), 313 | 'USERNAME' => $row['username'], 314 | ) 315 | ); 316 | 317 | $mail_sent = $messenger->send(NOTIFY_EMAIL, false); 318 | 319 | if (!$mail_sent) 320 | { 321 | $this->phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_DIGESTS_NOTIFICATION_ERROR', false, array($row['user_email'])); 322 | } 323 | else 324 | { 325 | $this->phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_DIGESTS_NOTIFICATION_SENT', false, array($row['user_email'], $row['username'])); 326 | $emails_sent++; 327 | } 328 | 329 | $messenger->reset(); 330 | 331 | } 332 | 333 | $messenger->save_queue(); // save queued emails for later delivery, if applicable 334 | $this->db->sql_freeresult($result); // Query be gone! 335 | 336 | } 337 | 338 | return $emails_sent; 339 | 340 | } 341 | 342 | } 343 | -------------------------------------------------------------------------------- /event/main_listener.php: -------------------------------------------------------------------------------- 1 | config = $config; 44 | $this->db = $db; 45 | $this->report_details_table = $report_details_table; 46 | $this->request = $request; 47 | $this->subscribed_forums_table = $subscribed_forums_table; 48 | $this->template = $template; 49 | } 50 | 51 | static public function getSubscribedEvents() 52 | { 53 | return array( 54 | 'core.user_setup' => 'load_language_on_setup', 55 | 'core.user_add_modify_data' => 'subscribe_digests_on_registration', 56 | 'core.ucp_register_data_before' => 'ucp_register_data_before', 57 | 'core.delete_user_after' => 'delete_user_after', 58 | 'core.user_active_flip_before' => 'set_no_digest_subscription' 59 | ); 60 | } 61 | 62 | public function load_language_on_setup($event) 63 | { 64 | $lang_set_ext = $event['lang_set_ext']; 65 | $lang_set_ext[] = array( 66 | 'ext_name' => 'phpbbservices/digests', 67 | 'lang_set' => 'common', 68 | ); 69 | $event['lang_set_ext'] = $lang_set_ext; 70 | } 71 | 72 | public function subscribe_digests_on_registration($event) 73 | { 74 | 75 | // This logic supports the subscribe a user to a digest automatically feature, if enabled. It also handles a subscription if the user is presented 76 | // the option to subscribe on registration and they select yes. 77 | 78 | $subscribe_on_registration = $this->request->variable('digest', '0') == '1'; // Test if user wanted to subscribe to digests on registration 79 | 80 | $is_human = ($event['sql_ary']['user_type'] == USER_IGNORE) ? false : true; 81 | if ($is_human && ($this->config->offsetGet('phpbbservices_digests_enable_auto_subscriptions') == 1 || $subscribe_on_registration)) 82 | { 83 | // Subscribe user with digest defaults 84 | $sql_ary['user_digest_attachments'] = $this->config->offsetGet('phpbbservices_digests_user_digest_attachments'); 85 | $sql_ary['user_digest_block_images'] = $this->config->offsetGet('phpbbservices_digests_user_digest_block_images'); 86 | $sql_ary['user_digest_filter_type'] = $this->config->offsetGet('phpbbservices_digests_user_digest_filter_type'); 87 | $sql_ary['user_digest_format'] = $this->config->offsetGet('phpbbservices_digests_user_digest_format'); 88 | $sql_ary['user_digest_max_display_words'] = $this->config->offsetGet('phpbbservices_digests_user_digest_max_display_words'); 89 | $sql_ary['user_digest_max_posts'] = $this->config->offsetGet('phpbbservices_digests_user_digest_max_posts'); 90 | $sql_ary['user_digest_min_words'] = $this->config->offsetGet('phpbbservices_digests_user_digest_min_words'); 91 | $sql_ary['user_digest_new_posts_only'] = $this->config->offsetGet('phpbbservices_digests_user_digest_new_posts_only'); 92 | $sql_ary['user_digest_no_post_text'] = $this->config->offsetGet('phpbbservices_digests_user_digest_no_post_text'); 93 | $sql_ary['user_digest_pm_mark_read'] = $this->config->offsetGet('phpbbservices_digests_user_digest_pm_mark_read'); 94 | $sql_ary['user_digest_popular'] = $this->config->offsetGet('phpbbservices_digests_user_digest_popular'); 95 | $sql_ary['user_digest_popularity_size'] = $this->config->offsetGet('phpbbservices_digests_user_digest_popularity_size'); 96 | $sql_ary['user_digest_remove_foes'] = $this->config->offsetGet('phpbbservices_digests_user_digest_remove_foes'); 97 | $sql_ary['user_digest_reset_lastvisit'] = $this->config->offsetGet('phpbbservices_digests_user_digest_reset_lastvisit'); 98 | $sql_ary['user_digest_send_hour_gmt'] = ($this->config->offsetGet('phpbbservices_digests_user_digest_send_hour_gmt') == -1) ? rand(0,23) : $this->config->offsetGet('phpbbservices_digests_user_digest_send_hour_gmt'); 99 | $sql_ary['user_digest_send_on_no_posts'] = $this->config->offsetGet('phpbbservices_digests_user_digest_send_on_no_posts'); 100 | $sql_ary['user_digest_show_mine'] = ($this->config->offsetGet('phpbbservices_digests_user_digest_show_mine') == 1) ? 0 : 1; 101 | $sql_ary['user_digest_show_pms'] = $this->config->offsetGet('phpbbservices_digests_user_digest_show_pms'); 102 | $sql_ary['user_digest_sortby'] = $this->config->offsetGet('phpbbservices_digests_user_digest_sortby'); 103 | $sql_ary['user_digest_toc'] = $this->config->offsetGet('phpbbservices_digests_user_digest_toc'); 104 | $sql_ary['user_digest_type'] = $this->config->offsetGet('phpbbservices_digests_user_digest_type'); 105 | 106 | $event['sql_ary'] = array_merge($event['sql_ary'], $sql_ary); 107 | } 108 | 109 | } 110 | 111 | public function ucp_register_data_before($event) 112 | { 113 | 114 | // Fields on registration form that allow a user to subscribe to digests, if this feature is enabled. 115 | $this->template->assign_vars(array( 116 | 'S_DIGESTS' => !$this->config->offsetGet('phpbbservices_digests_enable_auto_subscriptions') && $this->config->offsetGet('phpbbservices_digests_registration_field'), 117 | 'S_DIGESTS_REGISTER_CHECKED_YES' => ($this->config->offsetGet('phpbbservices_digests_user_digest_registration')) ? true : false, 118 | 'S_DIGESTS_REGISTER_CHECKED_NO' => ($this->config->offsetGet('phpbbservices_digests_user_digest_registration')) ? false : true, 119 | ) 120 | ); 121 | 122 | } 123 | 124 | /** 125 | * Event after the user(s) delete action has been performed 126 | * 127 | * @event core.delete_user_after 128 | * @var string mode Mode of posts deletion (retain|remove) 129 | * @var array user_ids ID(s) of the deleted user(s) 130 | * @var bool retain_username True if username should be retained, false otherwise 131 | * @var array user_rows Array containing data of the deleted user(s) 132 | * @since 3.1.0-a1 133 | * @changed 3.2.2-RC1 Added user_rows 134 | */ 135 | public function delete_user_after($event) 136 | { 137 | // If a user is being deleted in the ACP, delete any individual forum subscriptions and report statistics. 138 | // This is true regardless of whether there are any posts to be retained. 139 | if (($event['mode'] == 'remove') || ($event['mode'] == 'retain')) 140 | { 141 | $sql = 'DELETE FROM ' . $this->subscribed_forums_table . ' 142 | WHERE ' . $this->db->sql_in_set('user_id' , $event['user_ids']); 143 | $this->db->sql_query($sql); 144 | 145 | $sql = 'DELETE FROM ' . $this->report_details_table . ' 146 | WHERE ' . $this->db->sql_in_set('user_id' , $event['user_ids']); 147 | $this->db->sql_query($sql); 148 | } 149 | } 150 | 151 | /** 152 | * Check or modify activated/deactivated users data before submitting it to the database 153 | * 154 | * @event core.user_active_flip_before 155 | * @var string mode User type changing mode, can be: flip|activate|deactivate 156 | * @var int reason Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND 157 | * @var int activated The number of users to be activated 158 | * @var int deactivated The number of users to be deactivated 159 | * @var array user_id_ary Array with user ids to change user type 160 | * @var array sql_statements Array with users data to submit to the database, keys: user ids, values: arrays with user data 161 | * @since 3.1.4-RC1 162 | */ 163 | public function set_no_digest_subscription($event) 164 | { 165 | // Ensure that when a user is deactivated or flipped (deactivated is the typical case) they don't have 166 | // a digests subscription by default. Digests has a feature that will force or let users subscribe to digests 167 | // on registration if enabled, so we don't want to change this behavior. 168 | if ($event['mode'] !== 'activate') 169 | { 170 | $sql_statements = $event['sql_statements']; 171 | foreach ($sql_statements as $key => $user_id_sql_ary) 172 | { 173 | $user_id_sql_ary['user_digest_type'] = constants::DIGESTS_NONE_VALUE; 174 | $sql_statements[$key] = $user_id_sql_ary; 175 | } 176 | $event['sql_statements'] = $sql_statements; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /ext.php: -------------------------------------------------------------------------------- 1 | container->get('config'); 21 | 22 | // Only phpBB 3.3 is now supported. 23 | if ( 24 | phpbb_version_compare($config['version'], '3.3.0', '>=') && 25 | phpbb_version_compare($config['version'], '4.0', '<') 26 | ) 27 | { 28 | // Conditions met to install extension 29 | return true; 30 | } 31 | else 32 | { 33 | // Import the extension's language file 34 | $language = $this->container->get('language'); 35 | $language->add_lang('common', 'phpbbservices/digests'); 36 | 37 | // Return generic message indicating not all install requirements were met. 38 | return [$language->lang('DIGESTS_INSTALL_REQUIREMENTS')]; 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /includes/html_messenger.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | * For full copyright and license information, please see 10 | * the docs/CREDITS.txt file. 11 | * 12 | */ 13 | 14 | namespace phpbbservices\digests\includes; 15 | 16 | // The purpose of this class is to override the messenger class so HTML can be sent in email. The code is a copy and paste for the relevant events 17 | // from the 3.3.2 source for /includes/functions_messenger.php with minimal changes needed to add this functionality. 18 | 19 | class html_messenger extends \messenger 20 | { 21 | 22 | protected $user; 23 | protected $phpbb_dispatcher; 24 | protected $language; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param \phpbb\language\language $language Language object 30 | * @param \phpbb\event\dispatcher $phpbb_dispatcher Dispatcher object 31 | * @param bool $use_queue Whether to use phpBB's queue (true|false) 32 | * @param \phpbb\user $user User object 33 | * 34 | */ 35 | function __construct(\phpbb\user $user, \phpbb\event\dispatcher $phpbb_dispatcher, \phpbb\language\language $language, $use_queue = true) 36 | { 37 | 38 | // Since this class overwrites the messenger class, and the $config service in the messenger class is in a global 39 | // variable, we need to reference it here as a global variable instead of injecting it. This avoids a 40 | // The "config" service is private error triggered in Container.php. Thanks to 3Di for figuring this out! 41 | 42 | global $config; 43 | $this->config = $config; 44 | 45 | parent::__construct($use_queue); 46 | 47 | $this->language = $language; 48 | $this->phpbb_dispatcher = $phpbb_dispatcher; 49 | $this->user = $user; 50 | 51 | $this->use_queue = (!$this->config['email_package_size']) ? false : $use_queue; 52 | $this->subject = ''; 53 | } 54 | 55 | /** 56 | * Send the mail out to the recipients set previously in var $this->addresses 57 | * 58 | * @param int $method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH 59 | * @param bool $break Flag indicating if the function only formats the subject 60 | * and the message without sending it 61 | * @param bool $is_html true if you want HTML email headers because the content contains HTML, false assumes content is text 62 | * @param bool $is_digest true if sending a digest * 63 | * @return bool * Send the mail out to the recipients set previously in var $this->addresses 64 | */ 65 | 66 | function send($method = NOTIFY_EMAIL, $break = false, $is_html = false, $is_digest = false) 67 | { 68 | 69 | // We add some standard variables we always use, no need to specify them always 70 | $this->assign_vars(array( 71 | 'U_BOARD' => generate_board_url(), 72 | 'EMAIL_SIG' => str_replace('
', "\n", "-- \n" . htmlspecialchars_decode($this->config['board_email_sig'])), 73 | 'SITENAME' => htmlspecialchars_decode($this->config['sitename']), 74 | )); 75 | 76 | $subject = $this->subject; 77 | $template = $this->template; 78 | /** 79 | * Event to modify the template before parsing 80 | * 81 | * @event phpbbservices.digests.modify_notification_template 82 | * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH 83 | * @var bool break Flag indicating if the function only formats the subject 84 | * and the message without sending it 85 | * @var string subject The message subject 86 | * @var \phpbb\template\template template The (readonly) template object 87 | * @since 3.2.4-RC1 88 | */ 89 | $vars = array('method', 'break', 'subject', 'template'); 90 | extract($this->phpbb_dispatcher->trigger_event('phpbbservices.digests.modify_notification_template', compact($vars))); 91 | 92 | // Parse message through template 93 | $message = trim($this->template->assign_display('body')); 94 | 95 | /** 96 | * Event to modify notification message text after parsing 97 | * 98 | * @event phpbbservices.digests.modify_notification_message 99 | * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH 100 | * @var bool break Flag indicating if the function only formats the subject 101 | * and the message without sending it 102 | * @var string subject The message subject 103 | * @var string message The message text 104 | * @since 3.1.11-RC1 105 | */ 106 | $vars = array('method', 'break', 'subject', 'message'); 107 | extract($this->phpbb_dispatcher->trigger_event('phpbbservices.digests.modify_notification_message', compact($vars))); 108 | 109 | $this->subject = $subject; 110 | $this->msg = $message; 111 | unset($subject, $message, $template); 112 | 113 | // Because we use \n for newlines in the body message we need to fix line encoding errors for those admins who uploaded email template files in the wrong encoding 114 | $this->msg = str_replace("\r\n", "\n", $this->msg); 115 | 116 | // We now try and pull a subject from the email body ... if it exists, 117 | // do this here because the subject may contain a variable 118 | $drop_header = ''; 119 | $match = array(); 120 | if (!$is_digest && preg_match('#^(Subject:(.*?))$#m', $this->msg, $match)) 121 | { 122 | $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT')); 123 | $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#'); 124 | } 125 | else 126 | { 127 | $this->subject = (($this->subject != '') ? $this->subject : $this->language->lang('NO_EMAIL_SUBJECT')); 128 | } 129 | 130 | if (preg_match('#^(List-Unsubscribe:(.*?))$#m', $this->msg, $match)) 131 | { 132 | $this->extra_headers[] = $match[1]; 133 | $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#'); 134 | } 135 | 136 | if ($drop_header) 137 | { 138 | $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg)); 139 | } 140 | 141 | if ($break) 142 | { 143 | return true; 144 | } 145 | 146 | $result = true; 147 | switch ($method) 148 | { 149 | case NOTIFY_EMAIL: 150 | $result = $this->msg_email($is_html); 151 | break; 152 | 153 | case NOTIFY_IM: 154 | $result = $this->msg_jabber(); 155 | break; 156 | 157 | case NOTIFY_BOTH: 158 | $result = $this->msg_email($is_html); 159 | $this->msg_jabber(); 160 | break; 161 | } 162 | 163 | $this->reset(); 164 | return $result; 165 | } 166 | 167 | /** 168 | * Return email header 169 | */ 170 | function build_header($to, $cc, $bcc, $is_html = false) 171 | { 172 | 173 | // We could use keys here, but we won't do this for 3.0.x to retain backwards compatibility 174 | $headers = array(); 175 | 176 | $headers[] = 'From: ' . $this->from; 177 | 178 | if ($cc) 179 | { 180 | $headers[] = 'Cc: ' . $cc; 181 | } 182 | 183 | if ($bcc) 184 | { 185 | $headers[] = 'Bcc: ' . $bcc; 186 | } 187 | 188 | $headers[] = 'Reply-To: ' . $this->replyto; 189 | $headers[] = 'Return-Path: <' . $this->config['board_email'] . '>'; 190 | $headers[] = 'Sender: <' . $this->config['board_email'] . '>'; 191 | $headers[] = 'MIME-Version: 1.0'; 192 | $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>'; 193 | $headers[] = 'Date: ' . date('r', time()); 194 | $headers[] = ($is_html) ? 'Content-Type: text/html; charset=UTF-8' : 'Content-Type: text/plain; charset=UTF-8'; 195 | $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit 196 | 197 | $headers[] = 'X-Priority: ' . $this->mail_priority; 198 | $headers[] = 'X-MSMail-Priority: ' . (($this->mail_priority == MAIL_LOW_PRIORITY) ? 'Low' : (($this->mail_priority == MAIL_NORMAL_PRIORITY) ? 'Normal' : 'High')); 199 | $headers[] = 'X-Mailer: phpBB3'; 200 | $headers[] = 'X-MimeOLE: phpBB3'; 201 | $headers[] = 'X-phpBB-Origin: phpbb://' . str_replace(array('http://', 'https://'), array('', ''), generate_board_url()); 202 | 203 | /** 204 | * Event to modify email header entries 205 | * 206 | * @event phpbbservices.digests.modify_email_headers 207 | * @var array headers Array containing email header entries 208 | * @since 3.1.11-RC1 209 | */ 210 | $vars = array('headers'); 211 | extract($this->phpbb_dispatcher->trigger_event('phpbbservices.digests.modify_email_headers', compact($vars))); 212 | 213 | if (count($this->extra_headers)) 214 | { 215 | $headers = array_merge($headers, $this->extra_headers); 216 | } 217 | 218 | return $headers; 219 | } 220 | 221 | /** 222 | * Send out emails 223 | */ 224 | function msg_email($is_html=false) 225 | { 226 | 227 | if (empty($this->config['email_enable'])) 228 | { 229 | return false; 230 | } 231 | 232 | // Addresses to send to? 233 | if (empty($this->addresses) || (empty($this->addresses['to']) && empty($this->addresses['cc']) && empty($this->addresses['bcc']))) 234 | { 235 | // Send was successful. ;) 236 | return true; 237 | } 238 | 239 | $use_queue = false; 240 | if ($this->config['email_package_size'] && $this->use_queue) 241 | { 242 | if (empty($this->queue)) 243 | { 244 | $this->queue = new \queue(); 245 | $this->queue->init('email', $this->config['email_package_size']); 246 | } 247 | $use_queue = true; 248 | } 249 | 250 | $contact_name = htmlspecialchars_decode($this->config['board_contact_name'], ENT_COMPAT); 251 | $board_contact = (($contact_name !== '') ? '"' . mail_encode($contact_name) . '" ' : '') . '<' . $this->config['board_contact'] . '>'; 252 | 253 | $break = false; 254 | $addresses = $this->addresses; 255 | $subject = $this->subject; 256 | $msg = $this->msg; 257 | /** 258 | * Event to send message via external transport 259 | * 260 | * @event phpbbservices.digests.notification_message_email 261 | * @var bool break Flag indicating if the function return after hook 262 | * @var array addresses The message recipients 263 | * @var string subject The message subject 264 | * @var string msg The message text 265 | * @since 3.2.4-RC1 266 | */ 267 | $vars = array( 268 | 'break', 269 | 'addresses', 270 | 'subject', 271 | 'msg', 272 | ); 273 | extract($this->phpbb_dispatcher->trigger_event('phpbbservices.digests.notification_message_email', compact($vars))); 274 | 275 | if ($break) 276 | { 277 | return true; 278 | } 279 | 280 | if (empty($this->replyto)) 281 | { 282 | $this->replyto = $board_contact; 283 | } 284 | 285 | if (empty($this->from)) 286 | { 287 | $this->from = $board_contact; 288 | } 289 | 290 | $encode_eol = ($this->config['smtp_delivery']) ? "\r\n" : PHP_EOL; 291 | 292 | // Build to, cc and bcc strings 293 | $to = $cc = $bcc = ''; 294 | foreach ($this->addresses as $type => $address_ary) 295 | { 296 | if ($type == 'im') 297 | { 298 | continue; 299 | } 300 | 301 | foreach ($address_ary as $which_ary) 302 | { 303 | ${$type} .= ((${$type} != '') ? ', ' : '') . (($which_ary['name'] != '') ? mail_encode($which_ary['name'], $encode_eol) . ' <' . $which_ary['email'] . '>' : $which_ary['email']); 304 | } 305 | } 306 | 307 | // Build header 308 | $headers = $this->build_header($to, $cc, $bcc, $is_html); 309 | 310 | // Send message ... 311 | if (!$use_queue) 312 | { 313 | $mail_to = ($to == '') ? 'undisclosed-recipients:;' : $to; 314 | $err_msg = ''; 315 | 316 | if ($this->config['smtp_delivery']) 317 | { 318 | $result = smtpmail($this->addresses, mail_encode($this->subject), wordwrap(utf8_wordwrap($this->msg), 997, "\n", true), $err_msg, $headers); 319 | } 320 | else 321 | { 322 | $result = phpbb_mail($mail_to, $this->subject, $this->msg, $headers, PHP_EOL, $err_msg); 323 | } 324 | 325 | if (!$result) 326 | { 327 | $this->error('EMAIL', $err_msg); 328 | return false; 329 | } 330 | } 331 | else 332 | { 333 | $this->queue->put('email', array( 334 | 'to' => $to, 335 | 'addresses' => $this->addresses, 336 | 'subject' => $this->subject, 337 | 'msg' => $this->msg, 338 | 'headers' => $headers) 339 | ); 340 | } 341 | 342 | return true; 343 | } 344 | 345 | } 346 | -------------------------------------------------------------------------------- /language/en/acp/info_acp_common.php: -------------------------------------------------------------------------------- 1 | 1, 39 | 40 | 'ACP_CAT_DIGESTS' => 'Digests', 41 | 'ACP_DIGESTS_BALANCE_LOAD' => 'Balance load', 42 | 'ACP_DIGESTS_BALANCE_LOAD_EXPLAIN' => 'If too many digests going out at certain hours are causing performance issues, this will rebalance digest subscriptions so that roughly the same number of digests are sent for each hour wanted. The table below shows the current number and names of digest subscribers for each hour with overallocated hours bolded. This function updates digest send hours minimally. Changes occur only on those hours where the number of subscribers exceeds the average load, and only for subscribers that exceed the hourly average for that hour. Caution: subscribers may be upset that their subscription times were changed and may receive an email notification, depending on the setting in digests general settings. If you want you can restrict the balancing to a digest type, balance for specified hours and apply balancing to specified hours.', 43 | 'ACP_DIGESTS_BALANCE_OPTIONS' => 'Balancing options', 44 | 'ACP_DIGESTS_CLEAR_CACHED_DIGESTS' => 'Clear cached digests and reports', 45 | 'ACP_DIGESTS_CLEAR_CACHED_DIGESTS_EXPLAIN' => 'This page allows you to remove any cached digests in the /store/phpbbservices/digests directory. These can be generated by the manual mailer but represent a potential security vulnerability. If any files are shown, it is recommended that you remove them.

A total of %1$d files are cached. File names: %2$s.

You can also clear any report statistics generated here.', 46 | 'ACP_DIGESTS_GENERAL_SETTINGS' => 'General settings', 47 | 'ACP_DIGESTS_GENERAL_SETTINGS_EXPLAIN' => 'These are the general digests settings. Please note that if timely delivery of digests must be guaranteed then you must set up and enable phpBB’s system cron feature. Otherwise the next time there is board traffic, digests for the current and previous hours will be mailed. For more information, read the FAQs for the Digests extension.', 48 | 'ACP_DIGESTS_EDIT_SUBSCRIBERS' => 'Edit subscribers', 49 | 'ACP_DIGESTS_EDIT_SUBSCRIBERS_EXPLAIN' => 'This page allows you to see who is or is not receiving digests and change their individual digest settings.

You can quickly subscribe users with your digest defaults, or unsubscribe users, by checking their rows, then selecting “Subscribe checked rows only using defaults” or “Unsubscribe checked rows only” in the“ With selected” dropdown at the bottom right corner of the page.', 50 | 'ACP_DIGESTS_MASS_SUBSCRIBE_UNSUBSCRIBE' => 'Mass subscribe/unsubscribe', 51 | 'ACP_DIGESTS_MASS_SUBSCRIBE_UNSUBSCRIBE_EXPLAIN' => 'This feature allows administrators to conveniently subscribe or unsubscribe all members of your forum at once. Digests default settings are used to subscribe members. If a member already has a digest subscription, a mass subscription will retain their digest settings. You cannot specify the forums that will be subscribed. Users will be subscribed to all forums they can read. Caution: subscribers may be upset if they are subscribed or unsubscribed without their permission.', 52 | 'ACP_DIGESTS_REPORTS' => 'Mailing report', 53 | 'ACP_DIGESTS_REPORTS_EXPLAIN' => 'This page allows you to see summaries of digests that were mailed for various dates and hours, as well as performance information. Click on on a column heading to change how it is sorted. Click on the mailed or skipped value for a row to see details about when the mailing or skip actions occurred for the hour’s subscribers.', 54 | 'ACP_DIGESTS_RESET_CRON_RUN_TIME' => 'Reset mailer', 55 | 'ACP_DIGESTS_RESET_CRON_RUN_TIME_EXPLAIN' => '', 56 | 'ACP_DIGESTS_SETTINGS' => 'Digest settings', 57 | 'ACP_DIGESTS_TEST' => 'Manually run the mailer', 58 | 'ACP_DIGESTS_TEST_EXPLAIN' => 'This feature allows you to manually run digests for initial testing or troubleshooting. You can also use it to recreate digests for hours when they may not have been sent. Only one hour of digests are created and no user data are changed.

Subscribers that might receive digests for the current hour: %s', 59 | 'ACP_DIGESTS_USER_DEFAULT_SETTINGS' => 'User default settings', 60 | 'ACP_DIGESTS_USER_DEFAULT_SETTINGS_EXPLAIN' => 'These settings allow administrators to set the defaults users see when they subscribe to a digest.', 61 | 62 | 'LOG_CONFIG_DIGESTS_BAD_DIGEST_TYPE' => 'Warning: subscriber %1$s has a bad digest type of %2$s. Assumed a daily digest is wanted.', 63 | 'LOG_CONFIG_DIGESTS_BAD_SEND_HOUR' => 'User %1$s digest send hour is invalid. It is %2$d. The number should be >= 0 and < 24.', 64 | 'LOG_CONFIG_DIGESTS_BALANCE_LOAD' => 'Digests balance load run successfully', 65 | 'LOG_CONFIG_DIGESTS_BOARD_DISABLED' => 'Digests mailer run was attempted, but stopped because the board is disabled', 66 | 'LOG_CONFIG_DIGESTS_CACHE_CLEARED' => 'The store/phpbbservices/digests folder was emptied', 67 | 'LOG_CONFIG_DIGESTS_CLEAR_REPORT' => 'Cleared digests reports statistics', 68 | 'LOG_CONFIG_DIGESTS_CLEAR_SPOOL_ERROR' => 'Unable to clear files in the store/phpbbservices/digests folder. This may be due to a permissions issue or an incorrect path. The file permissions on the folder should be set to publicly writeable (777 on Unix-based systems).', 69 | 'LOG_CONFIG_DIGESTS_CREATE_DIRECTORY_ERROR' => 'Unable to create the folder %s. This may be due to insufficient permissions. The file permissions on the folder should be set to publicly writeable (777 on Unix-based systems).', 70 | 'LOG_CONFIG_DIGESTS_CRITICAL_ERROR' => 'The digests mailer unexpectedly errored.
Error number: [%1$s]
Error: %2$s
Program: %3$s Line: %4$s
', 71 | 'LOG_CONFIG_DIGESTS_DEBUG_POSTS_CURRENT_HOUR' => 'Debug: Subscribers SQL posts query: Date UTC: %s Hour UTC: %s SQL = %s', 72 | 'LOG_CONFIG_DIGESTS_DEBUG_SHOULD_RUN' => 'Debug: Should run: %s, digest can run after this time: %s', 73 | 'LOG_CONFIG_DIGESTS_DEBUG_SQL_CURRENT_HOUR' => 'Debug: Subscribers SQL query: Date UTC: %s Hour UTC: %s SQL = %s', 74 | 'LOG_CONFIG_DIGESTS_EDIT_SUBSCRIBERS' => 'Edited digest subscribers', 75 | 'LOG_CONFIG_DIGESTS_EMAILING_FAILURE' => 'Unable to email digests for date %1$s hour %2$d UTC. Execution time: %3$s of %4$d allowed seconds. %5$s MB of memory was used. A total of %6$u digests were emailed, %7$u digests were skipped and %8$u hours were processed before the mailer was aborted.', 76 | 'LOG_CONFIG_DIGESTS_EMAILING_SUCCESS' => 'Emailed digests for date %1$s hour %2$d UTC. Execution time: %3$s of %4$d allowed seconds. %5$s MB of memory was used. A total of %6$u digests were emailed, %7$u digests were skipped and %8$u hours were processed.', 77 | 'LOG_CONFIG_DIGESTS_EXCEPTION_ERROR' => 'The following PHP try/catch exception occurred: %s', 78 | 'LOG_CONFIG_DIGESTS_FILE_CLOSE_ERROR' => 'Unable to close file %s', 79 | 'LOG_CONFIG_DIGESTS_FILE_OPEN_ERROR' => 'Unable to open a file handler to the folder %s. This may be due to insufficient permissions. The file permissions on the folder should be set to publicly writeable (777 on Unix-based systems).', 80 | 'LOG_CONFIG_DIGESTS_FILE_WRITE_ERROR' => 'Unable to write file %s. This may be due to insufficient permissions. The file permissions on the folder should be set to publicly writeable (777 on Unix-based systems).', 81 | 'LOG_CONFIG_DIGESTS_FILTER_ERROR' => 'Digests mailer was called with an invalid user_digest_filter_type = %1$s for %2$s', 82 | 'LOG_CONFIG_DIGESTS_FORMAT_ERROR' => 'Digests mailer was called with an invalid user_digest_format of %1$s for %2$s', 83 | 'LOG_CONFIG_DIGESTS_GENERAL' => 'Altered digest general settings', 84 | 'LOG_CONFIG_DIGESTS_HOUR_RUN' => 'Running digests for %1$s at %2$02d UTC', 85 | 'LOG_CONFIG_DIGESTS_INCONSISTENT_DATES' => 'An unusual error occurred. No hours were processed because the last time digests were successfully sent (timestamp %1$d) was after the time digests were run (timestamp %2$d).', 86 | 'LOG_CONFIG_DIGESTS_LOG_END' => 'Ending digests mailer. A total of %1$u digests were emailed and %2$u digests were skipped.', 87 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_BAD' => 'Unable to send a digest to %1$s (%2$s). This problem should be investigated and fixed since it likely means there is a general emailing issue.', 88 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_BAD_NO_EMAIL' => 'Unable to send a digest to %s. This problem should be investigated and fixed since it likely means there is a general emailing issue.', 89 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_GOOD' => array( 90 | 1 => 'A %1$s digest was %2$s %3$s (%4$s) for date %5$s and hour %6$02d UTC containing %7$d post and %8$d private message', 91 | 2 => 'A %1$s digest was %2$s %3$s (%4$s) for date %5$s and hour %6$02d UTC containing %7$d posts and %8$d private messages', 92 | ), 93 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_GOOD_DISK' => 'A digest was written to store/phpbbservices/digests/%s. The digest was NOT emailed, but was placed here for analysis.', 94 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_GOOD_NO_EMAIL' => array( 95 | 1 => 'A %1$s digest was %2$s %3$s (%4$s)for date %5$s and hour %6$02d UTC containing %7$d post and %8$d private message', 96 | 2 => 'A %1$s digest was %2$s %3$s for date %4$s and hour %5$02d UTC containing %6$d posts and %7$d private messages', 97 | ), 98 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_NONE' => 'A digest was NOT sent to %1$s (%2$s) because user filters and preferences meant there was nothing to send', 99 | 'LOG_CONFIG_DIGESTS_LOG_ENTRY_NONE_NO_EMAIL' => 'A digest was NOT sent to %s because user filters and preferences meant there was nothing to send', 100 | 'LOG_CONFIG_DIGESTS_LOG_START' => 'Starting digests mailer', 101 | 'LOG_CONFIG_DIGESTS_MAILER_RAN_WITH_ERROR' => 'An error occurred while the mailer was running. One or more digests may have been successfully generated.', 102 | 'LOG_CONFIG_DIGESTS_MANUAL_RUN' => 'Manual run of the mailer invoked', 103 | 'LOG_CONFIG_DIGESTS_MESSAGE' => '%s', // Used for general debugging, otherwise hard to troubleshoot problems in cron mode. 104 | 'LOG_CONFIG_DIGESTS_MASS_SUBSCRIBE_UNSUBSCRIBE' => 'Executed a digests mass subscribe or unsubscribe action', 105 | 'LOG_CONFIG_DIGESTS_NO_ALLOWED_FORUMS' => 'Warning: subscriber %s does not have any forum permissions, so unless there are required forums, digests will never contain any content.', 106 | 'LOG_CONFIG_DIGESTS_NO_BOOKMARKS' => 'Warning: subscriber %s wants bookmarked topics in their digest but does not have any bookmarked topics.', 107 | 'LOG_CONFIG_DIGESTS_NO_RESOURCES' => 'Ending digests mailer abnormally. There are insufficient system resources to continue running digests. Error was triggered while creating a digests for “%1$s” for hour %2$d UTC. You may need to upgrade your hosting to reliably send all digests.', 108 | 'LOG_CONFIG_DIGESTS_NOTIFICATION_ERROR' => 'Unable to send an administrator generated digests email notification to %s', 109 | 'LOG_CONFIG_DIGESTS_NOTIFICATION_SENT' => 'An email was sent to %1$s (%2$s) indicating that their digest settings were changed', 110 | 'LOG_CONFIG_DIGESTS_REGULAR_CRON_RUN' => 'Regular (phpBB) cron run of the mailer invoked', 111 | 'LOG_CONFIG_DIGESTS_RESET_CRON_RUN_TIME' => 'Digests mailing time was reset', 112 | 'LOG_CONFIG_DIGESTS_RUN_TOO_SOON' => 'Less than an hour has elapsed since digests were last run. Run aborted.', 113 | 'LOG_CONFIG_DIGESTS_SIMULATION_DATE_TIME' => 'Administrator chose to create digests for %s board time.', 114 | 'LOG_CONFIG_DIGESTS_SORT_BY_ERROR' => 'Digests mailer was called with an invalid user_digest_sortby = %1$s for %2$s', 115 | 'LOG_CONFIG_DIGESTS_SYSTEM_CRON_RUN' => 'System cron run of the mailer invoked', 116 | 'LOG_CONFIG_DIGESTS_TIMEZONE_ERROR' => 'The user_timezone "%1$s" for username "%2$s" is invalid. Assumed a timezone of "%3$s". Please ask user to set a proper timezone in the User Control Panel. See http://php.net/manual/en/timezones.php for a list of valid timezones.', 117 | 'LOG_CONFIG_DIGESTS_USER_DEFAULTS' => 'Altered digest user default settings', 118 | )); 119 | -------------------------------------------------------------------------------- /language/en/common.php: -------------------------------------------------------------------------------- 1 | 1, 38 | 39 | 'DIGESTS_ALL_FORUMS' => 'All', 40 | 'DIGESTS_AM' => ' AM', // not used if date/time formats do not permit AM and PM 41 | 'DIGESTS_AUTHOR' => 'Author', 42 | 'DIGESTS_BAD_SEND_HOUR' => 'User %1$s digest send hour is invalid. It is %2$d. The number should be >= 0 and < 24.', 43 | 'DIGESTS_BLOCK_IMAGES' => 'Block images', 44 | 'DIGESTS_BLOCK_IMAGES_EXPLAIN' => 'Prohibits all images from appearing in your digests, including smilies and images attached to posts or private messages. This can be useful for slower connections, if minimizing bandwidth is a concern or for active boards with lots of images. Text digests never show images.', 45 | 'DIGESTS_BOARD_LIMIT' => '%d (Board limit)', 46 | 'DIGESTS_BY' => 'By', 47 | 'DIGESTS_CLOSED_QUOTE' => '”', 48 | 'DIGESTS_CLOSED_QUOTE_TEXT' => '"', 49 | 'DIGESTS_COUNT_LIMIT' => 'Maximum number of posts in the digest', 50 | 'DIGESTS_COUNT_LIMIT_EXPLAIN' => 'Enter a number greater than zero if you want to limit the number of posts in the digest.', 51 | 'DIGESTS_DAILY' => 'Daily', 52 | 'DIGESTS_DATE' => 'Date', 53 | 'DIGESTS_DELIMITER' => ' ‹ ', // Used to help show the hierarchy of forum names from the index on down. Should not change in language translations unless different character is used on breadcrumb menu. 54 | 'DIGESTS_DISABLED_MESSAGE' => 'To enable fields, select Basics and select a digest type', 55 | 'DIGESTS_DISCLAIMER_HTML' => '

One-click unsubscribe

You can also change or delete your subscription from the board’s User Control Panel.

If you have questions or feedback on your digests, please send them to the %2$s webmaster.

', 56 | 'DIGESTS_DISCLAIMER_TEXT' => 'One-click unsubscribe: %5$s 57 | 58 | You can also change or delete your subscription from the board\'s User Control Panel. 59 | 60 | If you have questions or feedback on your digests, please send them to the webmaster: %4$s.', 61 | 'DIGESTS_EXPLANATION' => 'Digests are email summaries of posts that are sent to you periodically. Digests can be sent daily, weekly or monthly at an hour of the day you select. You can specify those particular forums for which you want posts or by default you can elect to receive all posts for all forums for which you can read. You can cancel your digest subscription at any time by simply coming back to this page. Most users find digests to be very useful. We encourage you to give them a try!', 62 | 'DIGESTS_FILTER_ERROR' => 'Digests mailer was called with an invalid user_digest_filter_type = %s', 63 | 'DIGESTS_FILTER_FOES' => 'Remove posts from my foes', 64 | 'DIGESTS_FILTER_TYPE' => 'Types of posts in digest', 65 | 'DIGESTS_FOREIGN_LINK_REMOVED' => '[ Removed foreign link. Click on link for post or topic to see this link. ]', 66 | 'DIGESTS_FOREIGN_LINK_REMOVED_TEXT' => '[ Removed foreign link. Read the board topic to see this link. ]', 67 | 'DIGESTS_FORMAT_HTML' => 'Styled', 68 | 'DIGESTS_FORMAT_HTML_EXPLAIN' => 'Styles are applied if your email program allows', 69 | 'DIGESTS_FORMAT_HTML_CLASSIC' => 'Styled, posts laid out inside tables', 70 | 'DIGESTS_FORMAT_HTML_CLASSIC_EXPLAIN' => 'Similar to styled except topic posts and private messages are inside of tables', 71 | 'DIGESTS_FORMAT_PLAIN' => 'Plain', 72 | 'DIGESTS_FORMAT_PLAIN_EXPLAIN' => 'Styled with no colors or fonts', 73 | 'DIGESTS_FORMAT_PLAIN_CLASSIC' => 'Plain, posts laid out inside tables', 74 | 'DIGESTS_FORMAT_PLAIN_CLASSIC_EXPLAIN' => 'Similar to plain except topic posts and private messages are inside of tables', 75 | 'DIGESTS_FORMAT_STYLING' => 'Digest styling', 76 | 'DIGESTS_FORMAT_STYLING_EXPLAIN' => 'Please note that the styling actually rendered depends on the capabilities of your email program. Move your cursor over the styling type to learn more about each style.', 77 | 'DIGESTS_FORMAT_TEXT' => 'Text', 78 | 'DIGESTS_FORMAT_TEXT_EXPLAIN' => 'No styling will appear in the digest. Only plain text will be shown, probably in a fixed width font.', 79 | 'DIGESTS_FORUMS_WANTED' => 'Forums wanted', 80 | 'DIGESTS_FREQUENCY' => 'Type of digest wanted', 81 | 'DIGESTS_FREQUENCY_EXPLAIN' => 'Weekly digests are sent on %s. Monthly digests are sent on the first of the month. Coordinated Universal Time (UTC) is used for determining the day of the week.', 82 | 'DIGESTS_FREQUENCY_SHORT' => 'Digest type', 83 | 'DIGESTS_HOURS_ABBREVIATION' => ' h', // see: http://www.scienceeditingexperts.com/which-is-the-correct-abbreviation-for-hours-2h-2-h-2hs-2-hs-2hrs-or-2-hrs, DIGESTS_AM and DIGESTS_PM are used instead if specified in user_dateformat 84 | 'DIGESTS_INSTALL_REQUIREMENTS' => 'Your version of phpBB must be > 3.3.0 and < 4.0 to install this extension. Please address this issue, then try enabling the extension again.', 85 | 'DIGESTS_INTRODUCTION' => 'Here is the latest digest of posts from %s forums. Do not reply to this email to reply to topics, posts or private messages. But please do visit the board and join the discussion! (If the format of the digest looks off, make sure to download all remote content.)', 86 | 'DIGESTS_JUMP_TO_MSG' => 'Msg ID', 87 | 'DIGESTS_JUMP_TO_POST' => 'Post ID', 88 | 'DIGESTS_LASTVISIT_RESET' => 'Reset my last visit date when I am sent a digest', 89 | 'DIGESTS_LASTVISIT_RESET_EXPLAIN' => 'If enabled, the date and time your digest was created will be used to set your last visit date. Topic-related notifications for the hour digests were created are also marked as read.', 90 | 'DIGESTS_LINK' => 'Link', 91 | 'DIGESTS_MAILED_TOOLTIP' => 'Click to see mailed details', 92 | 'DIGESTS_MARK_READ' => 'Mark as read when they appear in the digest', 93 | 'DIGESTS_MAX_DISPLAY_WORDS' => 'Maximum words to display in a post', 94 | 'DIGESTS_MAX_DISPLAY_WORDS_EXPLAIN' => 'To ensure consistent rendering, if a post must be truncated the HTML markup will be removed from the post. Set to 0 to allow the full post text to appear. If the “Show no post text at all” option is checked, this field is ignored and no post text will appear in the digest.', 95 | 'DIGESTS_MAX_SIZE' => 'Maximum words to display in a post', 96 | 'DIGESTS_MAX_WORDS_NOTIFIER' => '... ', 97 | 'DIGESTS_MIN_SIZE' => 'Minimum words required in post for the post to appear in a digest', 98 | 'DIGESTS_MIN_SIZE_EXPLAIN' => 'If you leave set to 0, posts with text of any number of words are included.', 99 | 'DIGESTS_MIN_POPULARITY_VALUE' => 'Minimum value for popularity', 100 | 'DIGESTS_MIN_POPULARITY_VALUE_EXPLAIN' => 'A topic must have an average of at least this many posts per day over your digest time period (day, week or month) to appear in your digest. You cannot set it below the value set by the board administrator.', 101 | 'DIGESTS_MONTHLY' => 'Monthly', 102 | 'DIGESTS_NEW' => 'New', 103 | 'DIGESTS_NEW_POSTS_ONLY' => 'Show new posts only', 104 | 'DIGESTS_NEW_POSTS_ONLY_EXPLAIN' => 'This will filter out any posts posted prior to the date and time you last visited this board. If you visit the board frequently and read most of the posts, this will keep redundant posts from appearing in your digest. It may also mean that you will miss some posts in forums that you did not read.', 105 | 'DIGESTS_NO_BOOKMARKED_POSTS' => 'There are no new bookmarked posts.', 106 | 'DIGESTS_NO_CONSTRAINT' => 'No constraint', 107 | 'DIGESTS_NO_DETAILS_ERROR' => 'The database may be inconsistent. No digest detail records can be found for this date and hour.', 108 | 'DIGESTS_NO_FORUMS_AVAILABLE' => 'You do not have access to any forums', 109 | 'DIGESTS_NO_FORUMS_CHECKED' => 'At least one forum must be checked', 110 | 'DIGESTS_NO_LIMIT' => 'No limit', 111 | 'DIGESTS_NO_POSTS' => 'There are no new posts.', 112 | 'DIGESTS_NO_POST_TEXT' => 'Show no post text at all', 113 | 'DIGESTS_NO_PRIVATE_MESSAGES' => 'You have no new or unread private messages.', 114 | 'DIGESTS_NO_TIMEZONE' => 'You must specify your timezone in your profile before you can run digests.', 115 | 'DIGESTS_NONE' => 'None (unsubscribe)', 116 | 'DIGESTS_ON' => 'on', 117 | 'DIGESTS_OPEN_QUOTE' => '“', 118 | 'DIGESTS_OPEN_QUOTE_TEXT' => '"', 119 | 'DIGESTS_PM' => ' PM', // not used if date/time formats do not permit AM and PM 120 | 'DIGESTS_PM_SUBJECT' => 'Private message subject', 121 | 'DIGESTS_POST_IMAGE_TEXT' => '
(Click on the image to see it full size.)', 122 | 'DIGESTS_POST_TEXT' => 'Post Text', 123 | 'DIGESTS_POST_TIME' => 'Post Time', 124 | 'DIGESTS_POST_SIGNATURE_DELIMITER' => '
____________________
', // Place here whatever code (make sure it is valid HTML) you want to use to distinguish the end of a post from the beginning of the signature line 125 | 'DIGESTS_POSTED_TO_THE_TOPIC' => 'posted to the topic', 126 | 'DIGESTS_POSTS_TYPE_ANY' => 'All posts', 127 | 'DIGESTS_POSTS_TYPE_FIRST' => 'First posts of topics only', 128 | 'DIGESTS_POWERED_BY' => 'phpBB Services', 129 | 'DIGESTS_POWERED_BY_TEXT' => 'Digests extension for phpBB created by', 130 | 'DIGESTS_PRIVATE_MESSAGES_IN_DIGEST' => 'Add my unread private messages', 131 | 'DIGESTS_PUBLISH_DATE' => 'The digest was published specifically for %1$s on %2$s', 132 | 'DIGESTS_REGISTER' => 'Receive digests', 133 | 'DIGESTS_REGISTER_EXPLAIN' => 'The board’s defaults will be used. You can adjust digest settings or unsubscribe after completing registration.', 134 | 'DIGESTS_REMOVE_YOURS' => 'Remove my posts', 135 | 'DIGESTS_REPLY' => 'Reply', 136 | 'DIGESTS_ROBOT' => 'Robot', 137 | 'DIGESTS_SALUTATION' => 'Dear', 138 | 'DIGESTS_SEE_POPULAR_TOPICS_ONLY' => 'See popular topics only', 139 | 'DIGESTS_SELECT_FORUMS' => 'Include posts for these forums', 140 | 'DIGESTS_SELECT_FORUMS_EXPLAIN' => 'Please note the categories and forums shown are for those you are allowed to read only. Forum selection is disabled when you select bookmarked topics only. Password protected forums are not shown and cannot be selected. If all is unchecked and bookmarks are not selected, then you must select at least one forum to submit successfully.

Bolded forum names (if any) are forums that the administrator requires to be presented in any digest (other than for bookmarked topics only). You cannot unselect these forums. Forum names that have strikethrough text are disallowed by the administrator for presentation in any digest (other than for bookmarked topics only) and are thus unselected.', 141 | 'DIGESTS_SEND_HOUR' => 'Hour sent', 142 | 'DIGESTS_SEND_HOUR_EXPLAIN' => 'The digest arrival time is the time based on the timezone you set in your board preferences.', 143 | 'DIGESTS_SEND_ON_NO_POSTS' => 'Send a digest if there are no new posts', 144 | 'DIGESTS_SENDER' => 'Sender', 145 | 'DIGESTS_SENT_AT' => ' was sent a digest at ', 146 | 'DIGESTS_SENT_TO' => 'sent to', 147 | 'DIGESTS_SENT_YOU_A_MESSAGE' => 'sent you a private message with the subject', 148 | 'DIGESTS_SHOW_ATTACHMENTS' => 'Show attachments', 149 | 'DIGESTS_SHOW_ATTACHMENTS_EXPLAIN' => 'If enabled, attachment images will appear in your digest at the bottom of the post or private message. Non-image attachments appear as links (styled digests only). The BBCode [img] tag is not affected by this setting.', 150 | 'DIGESTS_SHOW_NEW_POSTS_ONLY' => 'Show new posts only', 151 | 'DIGESTS_SHOW_PMS' => 'Show my private messages', 152 | 'DIGESTS_SIZE_ERROR' => 'This field is a required field. You must enter a positive whole number, less than or equal to the maximum allowed by the board administrator. If this value is zero, there is no limit.', 153 | 'DIGESTS_SIZE_ERROR_MIN' => 'You must enter a whole number or leave the field blank. If this value is zero, there is no limit.', 154 | 'DIGESTS_SKIP' => 'Skip to content', 155 | 'DIGESTS_SKIPPED_AT' => ' was not sent a digest at ', 156 | 'DIGESTS_SKIPPED_TOOLTIP' => 'Click to see skipped details', 157 | 'DIGESTS_SORT_BY' => 'Post sort order', 158 | 'DIGESTS_SORT_BY_EXPLAIN' => 'All digests are sorted by category and then by forum, as they are shown on the main index. Sort options apply to how posts are ordered within forums and topics. Traditional Order is the default order used since phpBB 2, which is last topic post time (descending) and then by post time within the topic.', 159 | 'DIGESTS_SORT_FORUM_TOPIC' => 'Traditional Order', 160 | 'DIGESTS_SORT_FORUM_TOPIC_DESC' => 'Traditional Order, Latest Posts First', 161 | 'DIGESTS_SORT_POST_DATE' => 'From oldest to newest', 162 | 'DIGESTS_SORT_POST_DATE_DESC' => 'From newest to oldest', 163 | 'DIGESTS_SORT_USER_ORDER' => 'Use my board display preferences', 164 | 'DIGESTS_SUBJECT_TITLE' => '%1$s %2$s Digest', 165 | 'DIGESTS_TAG_REPLACED' => 'Notice: content removed for security reasons. Click on the post or topic link to see the complete post content.', 166 | 'DIGESTS_TITLE' => 'Digests', 167 | 'DIGESTS_TRANSLATED_BY' => 'translated by', 168 | 'DIGESTS_TRANSLATOR_NAME' => '', // Leave null string to suppress translator name 169 | 'DIGESTS_TRANSLATOR_CONTACT' => '', // Leave null string to suppress contact info, if used, use: mailto:name@emailaddress.com or a URL if you have a website. 170 | 'DIGESTS_TOC' => 'Table of contents', 171 | 'DIGESTS_TOC_EXPLAIN' => 'If the board is active, you might want to include a table of contents in your digest. In styled digests, the table of contents includes links that let you jump to a particular post or private message in the digest.', 172 | 'DIGESTS_UNKNOWN' => 'Unknown', 173 | 'DIGESTS_UNREAD' => 'Unread', 174 | 'DIGESTS_UNSUBSCRIBE_FAILURE' => 'Sorry, but we were unable to unsubscribe you from receiving digests.', 175 | 'DIGESTS_UNSUBSCRIBE_SUCCESS' => 'You have been unsubscribed from receiving further digests.', 176 | 'DIGESTS_UPDATED' => 'Your digest settings were saved', 177 | 'DIGESTS_USE_BOOKMARKS' => 'Bookmarked topics only', 178 | 'DIGESTS_WEEKDAY' => 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday', 179 | 'DIGESTS_WEEKLY' => 'Weekly', 180 | 'DIGESTS_YOU_HAVE_PRIVATE_MESSAGES' => 'You have new private messages', 181 | 182 | 'UCP_DIGESTS' => 'Digests', 183 | 'UCP_DIGESTS_ADDITIONAL_CRITERIA' => 'Additional criteria', 184 | 'UCP_DIGESTS_ADDITIONAL_CRITERIA_OPTIONS' => 'Additional criteria options', 185 | 'UCP_DIGESTS_BASICS' => 'Basics', 186 | 'UCP_DIGESTS_BASICS_OPTIONS' => 'Basics options', 187 | 'UCP_DIGESTS_FORUMS_SELECTION' => 'Forums selection', 188 | 'UCP_DIGESTS_FORUMS_SELECTION_OPTIONS' => 'Forums selection options', 189 | 'UCP_DIGESTS_MODE_ERROR' => 'Digests was called with an invalid mode of %s', 190 | 'UCP_DIGESTS_POST_FILTERS' => 'Post filters', 191 | 'UCP_DIGESTS_POST_FILTERS_OPTIONS' => 'Post filters options', 192 | )); 193 | -------------------------------------------------------------------------------- /language/en/email/digests_html.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ S_DIGESTS_TITLE }} 8 | 9 | 10 | 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 |

{{ S_DIGESTS_TITLE }}

51 | 52 |

{{ lang('DIGESTS_SALUTATION') }} {{ S_DIGESTS_SALUTATION_BLURB }}

53 | 54 |

{{ S_DIGESTS_INTRODUCTION }}

55 | 56 | {{ DIGESTS_TOC }} 57 | 58 |
59 |
60 | 61 | {{ DIGESTS_CONTENT }} 62 |
63 |

{{ S_DIGESTS_PUBLISH_DATE }}

64 |

{{ S_DIGESTS_DISCLAIMER }}

65 |
66 |
67 | 68 | -------------------------------------------------------------------------------- /language/en/email/digests_plain_html.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ S_DIGESTS_TITLE }} 8 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |

{{ S_DIGESTS_TITLE }}

27 | 28 |

{{ lang('DIGESTS_SALUTATION') }} {{ S_DIGESTS_SALUTATION_BLURB }}

29 | 30 |

{{ S_DIGESTS_INTRODUCTION }}

31 | 32 | {{ DIGESTS_TOC }} 33 | 34 |
35 |
36 | 37 | {{ DIGESTS_CONTENT }} 38 |
39 |

{{ S_DIGESTS_PUBLISH_DATE }}

40 |

{{ S_DIGESTS_DISCLAIMER }}

41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /language/en/email/digests_subscribe.txt: -------------------------------------------------------------------------------- 1 | Dear {{ USERNAME }}, 2 | 3 | This is to notify you that an administrator of {{ FORUM_NAME }} has subscribed you to receive a {{ DIGESTS_TYPE }} {{ DIGESTS_FORMAT }} digest. If you do not wish to receive the digest, you can unsubscribe by selecting the digests tab in the user control panel of the forum. Once logged in, select the unsubscribe option. It is accessible through this convenient link: 4 | 5 | {{ DIGESTS_UCP_LINK }} 6 | 7 | Sincerely, 8 | 9 | {{ FORUM_NAME }} Administration Team -------------------------------------------------------------------------------- /language/en/email/digests_subscription_edited.txt: -------------------------------------------------------------------------------- 1 | Dear {{ USERNAME }}, 2 | 3 | This is to notify you that an administrator of {{ FORUM_NAME }} has edited your digest settings. To view your new digest settings, use the link below and click on the digests link: 4 | 5 | {{ DIGESTS_UCP_LINK }} 6 | 7 | Sincerely, 8 | 9 | {{ FORUM_NAME }} Administration Team -------------------------------------------------------------------------------- /language/en/email/digests_text.txt: -------------------------------------------------------------------------------- 1 | {{ S_DIGESTS_TITLE }} 2 | 3 | {{ lang('DIGESTS_SALUTATION') }} {{ S_DIGESTS_SALUTATION_BLURB }} 4 | 5 | {{ S_DIGESTS_INTRODUCTION }} 6 | {{ DIGESTS_TOC }} 7 | ____________________________________________________________ 8 | {{ DIGESTS_CONTENT }} 9 | ____________________________________________________________ 10 | 11 | {{ S_DIGESTS_DISCLAIMER }} 12 | -------------------------------------------------------------------------------- /language/en/email/digests_unsubscribe.txt: -------------------------------------------------------------------------------- 1 | Dear {{ USERNAME }}, 2 | 3 | This is to notify you that an administrator of {{ FORUM_NAME }} has unsubscribed you from receiving an email digest. 4 | 5 | Sincerely, 6 | 7 | {{ FORUM_NAME }} Administration Team -------------------------------------------------------------------------------- /language/en/email/digests_unsubscribe_one_click.txt: -------------------------------------------------------------------------------- 1 | Dear {{ USERNAME }}, 2 | 3 | As requested, you are no longer subscribed to receive digests from the board {{ FORUM_NAME }}. 4 | 5 | Sincerely, 6 | 7 | {{ FORUM_NAME }} Administration Team -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /migrations/convert_mod_data.php: -------------------------------------------------------------------------------- 1 | config->offsetExists('digests_version'); 22 | } 23 | 24 | static public function depends_on() 25 | { 26 | return array( 27 | '\phpbbservices\digests\migrations\convert_mod_modules', 28 | '\phpbb\db\migration\data\v31x\v319', 29 | ); 30 | } 31 | 32 | public function update_data() 33 | { 34 | return array( 35 | array('custom', array(array($this, 'update_configs'))), 36 | ); 37 | } 38 | 39 | public function update_configs() 40 | { 41 | 42 | // To upgrade from the digests mod, the basic approach is to compare arrays of configuration variables and database column names. 43 | // Remove what has gone away, add what is missing. If there is a value already, retain it. 44 | 45 | // Load the configuration values for the extension for version 3.2.7 into an array. 46 | 47 | $new_config = array( 48 | 'digests_block_images' => 0, 49 | 'digests_cron_task_last_gc' => 0, // timestamp when the digests mailer was last run 50 | 'digests_cron_task_gc' => (60 * 60), // seconds between run; digests are sent hourly 51 | 'digests_custom_stylesheet_path' => '', 52 | 'digests_enable_auto_subscriptions' => 0, 53 | 'digests_enable_custom_stylesheets' => 0, 54 | 'digests_enable_log' => 1, 55 | 'digests_enable_subscribe_unsubscribe' => 0, 56 | 'digests_exclude_forums' => 0, 57 | 'digests_from_email_name' => '', 58 | 'digests_from_email_address' => '', 59 | 'digests_host' => 'phpbbservices.com', 60 | 'digests_include_admins' => 0, 61 | 'digests_include_forums' => 0, 62 | 'digests_lowercase_digest_type' => 0, 63 | 'digests_max_cron_hrs' => 0, 64 | 'digests_max_items' => 0, 65 | 'digests_notify_on_admin_changes' => 1, 66 | 'digests_page_url' => 'https://www.phpbbservices.com/my-software/digests_wp/digests-extension/', 67 | 'digests_registration_field' => 0, 68 | 'digests_reply_to_email_address' => '', 69 | 'digests_show_email' => 0, 70 | 'digests_show_forum_path' => 0, 71 | 'digests_strip_tags' => '', 72 | 'digests_subscribe_all' => 1, 73 | 'digests_test' => 0, 74 | 'digests_test_clear_spool' => 1, 75 | 'digests_test_day' => date('j'), 76 | 'digests_test_email_address' => '', 77 | 'digests_test_hour' => date('G'), 78 | 'digests_test_month' => date('n'), 79 | 'digests_test_send_to_admin' => 0, 80 | 'digests_test_spool' => 0, 81 | 'digests_test_time_use' => 0, 82 | 'digests_test_year' => date('Y'), 83 | 'digests_user_check_all_forums' => 1, 84 | 'digests_user_digest_attachments' => 1, 85 | 'digests_user_digest_block_images' => 0, 86 | 'digests_user_digest_filter_type' => constants::DIGESTS_ALL, 87 | 'digests_user_digest_format' => constants::DIGESTS_HTML_VALUE, 88 | 'digests_user_digest_max_display_words' => -1, 89 | 'digests_user_digest_max_posts' => 0, 90 | 'digests_user_digest_min_words' => 0, 91 | 'digests_user_digest_new_posts_only' => 0, 92 | 'digests_user_digest_no_post_text' => 0, 93 | 'digests_user_digest_pm_mark_read' => 0, 94 | 'digests_user_digest_registration' => 0, 95 | 'digests_user_digest_remove_foes' => 0, 96 | 'digests_user_digest_reset_lastvisit' => 0, 97 | 'digests_user_digest_send_hour_gmt' => -1, 98 | 'digests_user_digest_send_on_no_posts' => 0, 99 | 'digests_user_digest_show_mine' => 1, 100 | 'digests_user_digest_show_pms' => 1, 101 | 'digests_user_digest_sortby' => constants::DIGESTS_SORTBY_BOARD, 102 | 'digests_user_digest_toc' => 0, 103 | 'digests_user_digest_type' => constants::DIGESTS_DAILY_VALUE, 104 | 'digests_users_per_page' => 20, 105 | 'digests_weekly_digest_day' => 0, 106 | ); 107 | 108 | $remove_config = array(); 109 | 110 | // If the old configuration value exists, keep its value but it must change its name to add the vendor name as a prefix 111 | foreach ($this->config as $key => $value) 112 | { 113 | if (substr($key, 0, 8) == 'digests_') 114 | { 115 | if (array_key_exists($key, $new_config)) 116 | { 117 | // add the new config value with vendor in the config_name 118 | $this->config->set('phpbbservices_' . $key, $value); 119 | } 120 | // mark the old config value for deletion once outside of the loop 121 | $remove_config[] = $key; 122 | } 123 | } 124 | 125 | // Remove the old configuration variables, i.e. digests_* rather than phpbbservices_digests_* 126 | foreach ($remove_config as $key => $value) 127 | { 128 | $this->config->delete($value); 129 | } 130 | 131 | // Add in any new configuration variables using the defaults. These were introduced by later versions of digests or are new in the extension 132 | // and all must have phpbbservices_ as a prefix. 133 | foreach ($new_config as $key => $value) 134 | { 135 | if (array_key_exists($key, $this->config) === false) 136 | { 137 | $this->config->set('phpbbservices_' . $key, $value); 138 | } 139 | } 140 | 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /migrations/convert_mod_modules.php: -------------------------------------------------------------------------------- 1 | table_prefix . "modules 20 | WHERE module_class = 'acp' 21 | AND module_langname = 'ACP_DIGEST_SETTINGS'"; 22 | $result = $this->db->sql_query($sql); 23 | $module_id = $this->db->sql_fetchfield('module_id'); 24 | $this->db->sql_freeresult($result); 25 | 26 | return $module_id === false; 27 | } 28 | 29 | static public function depends_on() 30 | { 31 | return array('\phpbb\db\migration\data\v31x\v319'); 32 | } 33 | 34 | public function update_data() 35 | { 36 | // This is a hopefully temporary means to get around phpBB module tool bugs in phpBB 3.2. This worked on phpBB 3.1 37 | // but phpBB 3.2 won't remove the UCP_DIGESTS category, triggering an error. 38 | return array( 39 | // ----- Remove old ACP Modules, note the old pattern of ACP_DIGEST_% instead of ACP_DIGESTS_% used for the mod ----- // 40 | array('if', array( 41 | array('module.exists', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_GENERAL_SETTINGS')), 42 | array('module.remove', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_GENERAL_SETTINGS')), 43 | )), 44 | array('if', array( 45 | array('module.exists', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_USER_DEFAULT_SETTINGS')), 46 | array('module.remove', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_USER_DEFAULT_SETTINGS')), 47 | )), 48 | array('if', array( 49 | array('module.exists', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_EDIT_SUBSCRIBERS')), // Appeared in 2.2.16 50 | array('module.remove', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_EDIT_SUBSCRIBERS')), 51 | )), 52 | array('if', array( 53 | array('module.exists', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_BALANCE_LOAD')), // Appeared in 2.2.22 54 | array('module.remove', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_BALANCE_LOAD')), 55 | )), 56 | array('if', array( 57 | array('module.exists', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_MASS_SUBSCRIBE_UNSUBSCRIBE')), // Appeared in 2.2.25 58 | array('module.remove', array('acp', 'ACP_DIGEST_SETTINGS', 'ACP_DIGEST_MASS_SUBSCRIBE_UNSUBSCRIBE')), 59 | )), 60 | array('if', array( 61 | array('module.exists', array('acp', false, 'ACP_DIGEST_SETTINGS')), 62 | array('module.remove', array('acp', false, 'ACP_DIGEST_SETTINGS')), 63 | )), 64 | 65 | // ----- Remove UCP modules ----- // 66 | array('if', array( 67 | array('module.exists', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_BASICS')), 68 | array('module.remove', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_BASICS')), 69 | )), 70 | array('if', array( 71 | array('module.exists', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_POSTS_SELECTION')), // Only in 2.2.6, gone in 2.2.7 72 | array('module.remove', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_POSTS_SELECTION')), 73 | )), 74 | array('if', array( 75 | array('module.exists', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_FORUMS_SELECTION')), // Appeared in 2.2.7 76 | array('module.remove', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_FORUMS_SELECTION')), 77 | )), 78 | array('if', array( 79 | array('module.exists', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_POST_FILTERS')), 80 | array('module.remove', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_POST_FILTERS')), 81 | )), 82 | array('if', array( 83 | array('module.exists', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_ADDITIONAL_CRITERIA')), 84 | array('module.remove', array('ucp', 'UCP_DIGESTS', 'UCP_DIGESTS_ADDITIONAL_CRITERIA')), 85 | )), 86 | array('if', array( 87 | array('module.exists', array('ucp', false, 'UCP_DIGESTS')), 88 | array('module.remove', array('ucp', false, 'UCP_DIGESTS')), 89 | )), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /migrations/convert_mod_schema.php: -------------------------------------------------------------------------------- 1 | db_tools->sql_column_exists($this->table_prefix . 'users', 'user_digest_attachments'); 19 | } 20 | 21 | static public function depends_on() 22 | { 23 | return array( 24 | '\phpbbservices\digests\migrations\convert_mod_modules', 25 | '\phpbb\db\migration\data\v31x\v319', 26 | ); 27 | } 28 | 29 | public function update_data() 30 | { 31 | return array( 32 | array('custom', array(array($this, 'update_columns'))), 33 | ); 34 | } 35 | 36 | public function update_columns() 37 | { 38 | 39 | $new_columns = array( 40 | 'user_digest_attachments' => array('TINT:4', 1), 41 | 'user_digest_block_images' => array('TINT:4', 0), 42 | 'user_digest_filter_type' => array('VCHAR:3', 'ALL'), 43 | 'user_digest_format' => array('VCHAR:4', 'HTML'), 44 | 'user_digest_has_unsubscribed' => array('TINT:4', 0), 45 | 'user_digest_last_sent' => array('UINT:11', 0), 46 | 'user_digest_max_display_words' => array('INT:4', 0), 47 | 'user_digest_max_posts' => array('UINT', 0), 48 | 'user_digest_min_words' => array('UINT', 0), 49 | 'user_digest_new_posts_only' => array('TINT:4', 0), 50 | 'user_digest_no_post_text' => array('TINT:4', 0), 51 | 'user_digest_pm_mark_read' => array('TINT:4', 0), 52 | 'user_digest_remove_foes' => array('TINT:4', 0), 53 | 'user_digest_reset_lastvisit' => array('TINT:4', 1), 54 | 'user_digest_send_hour_gmt' => array('DECIMAL', '0.00'), 55 | 'user_digest_send_on_no_posts' => array('TINT:4', 0), 56 | 'user_digest_show_mine' => array('TINT:4', 1), 57 | 'user_digest_show_pms' => array('TINT:4', 1), 58 | 'user_digest_sortby' => array('VCHAR:13', 'board'), 59 | 'user_digest_toc' => array('TINT:4', 0), 60 | 'user_digest_type' => array('VCHAR:4', 'NONE'), 61 | ); 62 | 63 | // Get a succinct array of new column names without metadata to make it easier to find missing columns 64 | $new_column_names = array_keys($new_columns); 65 | 66 | $found_digest_columns = array(); 67 | 68 | // The tools class has some convenient methods we will use to add and remove columns 69 | $tools_factory = new \phpbb\db\tools\factory(); 70 | $tools = $tools_factory->get($this->db); 71 | 72 | // Get a list of the current columns in the phpbb_users table. 73 | $user_table_columns = array_keys($tools->sql_list_columns($this->table_prefix . 'users')); 74 | 75 | // Note the columns found that start with "user_digest_" 76 | foreach ($user_table_columns as $key => $value) 77 | { 78 | if (substr($value, 0, 12) == 'user_digest_') 79 | { 80 | $found_digest_columns[] = $value; 81 | } 82 | } 83 | 84 | // Delete those columns in the phpbb_users table for digests that are no longer used 85 | $columns_to_remove = array_diff($found_digest_columns, $new_column_names); 86 | foreach ($columns_to_remove as $key => $value) 87 | { 88 | $tools->sql_column_remove($this->table_prefix . 'users', $value); 89 | } 90 | 91 | // Add those digest columns to the phpbb_users table were not in the version of the mod previously installed 92 | $columns_to_add = array_diff($new_column_names, $found_digest_columns); 93 | foreach ($columns_to_add as $key => $value) 94 | { 95 | $tools->sql_column_add($this->table_prefix . 'users', $value, array($new_columns[$value][0], $new_columns[$value][1])); 96 | } 97 | 98 | // Note: the phpbb_digests_subscribed_forums should still exist as it was created for the phpBB 3.0 mod and is unchanged. 99 | 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /migrations/release_3_0_2_data.php: -------------------------------------------------------------------------------- 1 | config->offsetExists('phpbbservices_digests_enable_auto_subscriptions'); 20 | } 21 | 22 | static public function depends_on() 23 | { 24 | return array( 25 | '\phpbbservices\digests\migrations\convert_mod_data', 26 | '\phpbb\db\migration\data\v31x\v319', 27 | ); 28 | } 29 | 30 | public function update_data() 31 | { 32 | 33 | return array( 34 | 35 | // Add Digest extension configuration variables 36 | array('config.add', array('phpbbservices_digests_block_images', 0)), 37 | array('config.add', array('phpbbservices_digests_cron_task_last_gc', 0)), // timestamp when the digests mailer was last run 38 | array('config.add', array('phpbbservices_digests_cron_task_gc', (60 * 60))), // seconds between runs -- digests are sent hourly 39 | array('config.add', array('phpbbservices_digests_custom_stylesheet_path', '')), 40 | array('config.add', array('phpbbservices_digests_enable_auto_subscriptions', 0)), 41 | array('config.add', array('phpbbservices_digests_enable_custom_stylesheets', 0)), 42 | array('config.add', array('phpbbservices_digests_enable_log', 1)), 43 | array('config.add', array('phpbbservices_digests_enable_subscribe_unsubscribe', 0)), 44 | array('config.add', array('phpbbservices_digests_exclude_forums', 0)), 45 | array('config.add', array('phpbbservices_digests_from_email_name', '')), 46 | array('config.add', array('phpbbservices_digests_from_email_address', '')), 47 | array('config.add', array('phpbbservices_digests_host', 'phpbbservices.com')), 48 | array('config.add', array('phpbbservices_digests_include_admins', 0)), 49 | array('config.add', array('phpbbservices_digests_include_forums', 0)), 50 | array('config.add', array('phpbbservices_digests_max_items', 0)), 51 | array('config.add', array('phpbbservices_digests_notify_on_admin_changes', 1)), 52 | array('config.add', array('phpbbservices_digests_page_url', 'https://www.phpbbservices.com/my-software/digests_wp/digests-extension/')), 53 | array('config.add', array('phpbbservices_digests_registration_field', 0)), 54 | array('config.add', array('phpbbservices_digests_reply_to_email_address', '')), 55 | array('config.add', array('phpbbservices_digests_show_email', 0)), 56 | array('config.add', array('phpbbservices_digests_subscribe_all', 1)), 57 | array('config.add', array('phpbbservices_digests_test', 0)), 58 | array('config.add', array('phpbbservices_digests_test_clear_spool', 1)), 59 | array('config.add', array('phpbbservices_digests_test_day', date('j'))), 60 | array('config.add', array('phpbbservices_digests_test_email_address', '')), 61 | array('config.add', array('phpbbservices_digests_test_hour', date('G'))), 62 | array('config.add', array('phpbbservices_digests_test_month', date('n'))), 63 | array('config.add', array('phpbbservices_digests_test_send_to_admin', 0)), 64 | array('config.add', array('phpbbservices_digests_test_spool', 0)), 65 | array('config.add', array('phpbbservices_digests_test_time_use', 0)), 66 | array('config.add', array('phpbbservices_digests_test_year', date('Y'))), 67 | array('config.add', array('phpbbservices_digests_user_check_all_forums', 1)), 68 | array('config.add', array('phpbbservices_digests_user_digest_attachments', 1)), 69 | array('config.add', array('phpbbservices_digests_user_digest_block_images', 0)), 70 | array('config.add', array('phpbbservices_digests_user_digest_filter_type', constants::DIGESTS_ALL)), 71 | array('config.add', array('phpbbservices_digests_user_digest_format', constants::DIGESTS_HTML_VALUE)), 72 | array('config.add', array('phpbbservices_digests_user_digest_max_display_words', -1)), 73 | array('config.add', array('phpbbservices_digests_user_digest_max_posts', 0)), 74 | array('config.add', array('phpbbservices_digests_user_digest_min_words', 0)), 75 | array('config.add', array('phpbbservices_digests_user_digest_new_posts_only', 0)), 76 | array('config.add', array('phpbbservices_digests_user_digest_no_post_text', 0)), 77 | array('config.add', array('phpbbservices_digests_user_digest_pm_mark_read', 0)), 78 | array('config.add', array('phpbbservices_digests_user_digest_registration', 0)), 79 | array('config.add', array('phpbbservices_digests_user_digest_remove_foes', 0)), 80 | array('config.add', array('phpbbservices_digests_user_digest_reset_lastvisit', 0)), 81 | array('config.add', array('phpbbservices_digests_user_digest_send_hour_gmt', -1)), 82 | array('config.add', array('phpbbservices_digests_user_digest_send_on_no_posts', 0)), 83 | array('config.add', array('phpbbservices_digests_user_digest_show_mine', 1)), 84 | array('config.add', array('phpbbservices_digests_user_digest_show_pms', 1)), 85 | array('config.add', array('phpbbservices_digests_user_digest_sortby', constants::DIGESTS_SORTBY_BOARD)), 86 | array('config.add', array('phpbbservices_digests_user_digest_toc', 0)), 87 | array('config.add', array('phpbbservices_digests_user_digest_type', constants::DIGESTS_DAILY_VALUE)), 88 | array('config.add', array('phpbbservices_digests_users_per_page', 20)), 89 | array('config.add', array('phpbbservices_digests_weekly_digest_day', 0)), 90 | 91 | ); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /migrations/release_3_0_2_modules.php: -------------------------------------------------------------------------------- 1 | table_prefix . "modules 19 | WHERE module_class = 'acp' 20 | AND module_langname = 'ACP_DIGESTS_GENERAL_SETTINGS'"; 21 | $result = $this->db->sql_query($sql); 22 | $module_id = $this->db->sql_fetchfield('module_id'); 23 | $this->db->sql_freeresult($result); 24 | 25 | return $module_id !== false; 26 | } 27 | 28 | static public function depends_on() 29 | { 30 | return array( 31 | '\phpbbservices\digests\migrations\convert_mod_modules', 32 | '\phpbb\db\migration\data\v31x\v319', 33 | ); 34 | } 35 | 36 | public function update_data() 37 | { 38 | return array( 39 | // Add the ACP digests category under the extensions tab 40 | array('module.add', array( 41 | 'acp', 42 | 'ACP_CAT_DOT_MODS', 43 | 'ACP_CAT_DIGESTS' 44 | )), 45 | // Add the four ACP digest modules 46 | array('module.add', array( 47 | 'acp', 48 | 'ACP_CAT_DIGESTS', 49 | array( 50 | 'module_basename' => '\phpbbservices\digests\acp\main_module', 51 | 'modes' => array('digests_general', 'digests_user_defaults', 'digests_edit_subscribers', 'digests_balance_load', 'digests_mass_subscribe_unsubscribe', 'digests_test'), 52 | ), 53 | )), 54 | 55 | // Add the UCP digests category, a top level category 56 | array('module.add', array( 57 | 'ucp', 58 | false, 59 | 'UCP_DIGESTS', 60 | )), 61 | // Add the four UCP digest modules 62 | array('module.add', array( 63 | 'ucp', 64 | 'UCP_DIGESTS', 65 | array( 66 | 'module_basename' => '\phpbbservices\digests\ucp\main_module', 67 | 'modes' => array('basics', 'forums_selection', 'post_filters', 'additional_criteria'), 68 | ), 69 | )), 70 | 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /migrations/release_3_0_2_schema.php: -------------------------------------------------------------------------------- 1 | array( 28 | $this->table_prefix . 'users' => array( 29 | 'user_digest_attachments' => array('TINT:4', 1), 30 | 'user_digest_block_images' => array('TINT:4', 0), 31 | 'user_digest_filter_type' => array('VCHAR:3', 'ALL'), 32 | 'user_digest_format' => array('VCHAR:4', 'HTML'), 33 | 'user_digest_has_unsubscribed' => array('TINT:4', 0), 34 | 'user_digest_last_sent' => array('UINT:11', 0), 35 | 'user_digest_max_display_words' => array('INT:4', 0), 36 | 'user_digest_max_posts' => array('UINT', 0), 37 | 'user_digest_min_words' => array('UINT', 0), 38 | 'user_digest_new_posts_only' => array('TINT:4', 0), 39 | 'user_digest_no_post_text' => array('TINT:4', 0), 40 | 'user_digest_pm_mark_read' => array('TINT:4', 0), 41 | 'user_digest_remove_foes' => array('TINT:4', 0), 42 | 'user_digest_reset_lastvisit' => array('TINT:4', 1), 43 | 'user_digest_send_hour_gmt' => array('DECIMAL', '0.00'), 44 | 'user_digest_send_on_no_posts' => array('TINT:4', 0), 45 | 'user_digest_show_mine' => array('TINT:4', 1), 46 | 'user_digest_show_pms' => array('TINT:4', 1), 47 | 'user_digest_sortby' => array('VCHAR:13', 'board'), 48 | 'user_digest_toc' => array('TINT:4', 0), 49 | 'user_digest_type' => array('VCHAR:4', 'NONE'), 50 | ), 51 | ), 52 | 53 | 'add_tables' => array( 54 | $this->table_prefix . 'digests_subscribed_forums' => array( 55 | 'COLUMNS' => array( 56 | 'user_id' => array('UINT', 0), 57 | 'forum_id' => array('UINT', 0), 58 | ), 59 | 'PRIMARY_KEY' => array('user_id', 'forum_id'), 60 | ), 61 | ), 62 | 63 | ); 64 | } 65 | 66 | public function revert_schema() 67 | { 68 | return array( 69 | 'drop_columns' => array( 70 | $this->table_prefix . 'users' => array( 71 | 'user_digest_attachments', 72 | 'user_digest_block_images', 73 | 'user_digest_filter_type', 74 | 'user_digest_format', 75 | 'user_digest_has_unsubscribed', 76 | 'user_digest_last_sent', 77 | 'user_digest_max_display_words', 78 | 'user_digest_max_posts', 79 | 'user_digest_min_words', 80 | 'user_digest_new_posts_only', 81 | 'user_digest_no_post_text', 82 | 'user_digest_pm_mark_read', 83 | 'user_digest_remove_foes', 84 | 'user_digest_reset_lastvisit', 85 | 'user_digest_send_hour_gmt', 86 | 'user_digest_send_on_no_posts', 87 | 'user_digest_show_mine', 88 | 'user_digest_show_pms', 89 | 'user_digest_sortby', 90 | 'user_digest_type', 91 | 'user_digest_toc', 92 | ), 93 | ), 94 | 95 | 'drop_tables' => array( 96 | $this->table_prefix . 'digests_subscribed_forums', 97 | ), 98 | 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /migrations/release_3_0_5.php: -------------------------------------------------------------------------------- 1 | table_prefix . "modules 18 | WHERE module_class = 'acp' 19 | AND module_langname = 'ACP_DIGESTS_RESET_CRON_RUN_TIME'"; 20 | $result = $this->db->sql_query($sql); 21 | $module_id = $this->db->sql_fetchfield('module_id'); 22 | $this->db->sql_freeresult($result); 23 | 24 | return $module_id !== false; 25 | } 26 | 27 | static public function depends_on() 28 | { 29 | return array( 30 | '\phpbb\db\migration\data\v31x\v319', 31 | '\phpbbservices\digests\migrations\release_3_0_2_modules', 32 | '\phpbbservices\digests\migrations\release_3_0_2_data', 33 | '\phpbbservices\digests\migrations\release_3_0_2_schema', 34 | ); 35 | } 36 | 37 | public function update_data() 38 | { 39 | return array( 40 | 41 | // Add a new ACP digest modules 42 | array('module.add', array( 43 | 'acp', 44 | 'ACP_CAT_DIGESTS', 45 | array( 46 | 'module_basename' => '\phpbbservices\digests\acp\main_module', 47 | 'modes' => array('digests_reset_cron_run_time'), 48 | ), 49 | )), 50 | 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /migrations/release_3_0_7.php: -------------------------------------------------------------------------------- 1 | array( 37 | $this->table_prefix . 'users' => array( 38 | 'user_digest_popular' => array('TINT:4', 0), 39 | 'user_digest_popularity_size' => array('UINT', 5), 40 | ), 41 | ), 42 | ); 43 | } 44 | 45 | public function revert_schema() 46 | { 47 | return array( 48 | 'drop_columns' => array( 49 | $this->table_prefix . 'users' => array( 50 | 'user_digest_popular', 51 | 'user_digest_popularity_size', 52 | ), 53 | ), 54 | ); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /migrations/release_3_2_15.php: -------------------------------------------------------------------------------- 1 | db_tools->sql_table_exists($this->table_prefix . 'digests_subscribed_forums')) 37 | { 38 | $this->db->sql_query('DELETE FROM ' . $this->table_prefix . 'digests_subscribed_forums 39 | WHERE user_id NOT IN (SELECT user_id FROM ' . $this->table_prefix . 'users)'); 40 | } 41 | } 42 | 43 | public function remove_files() 44 | { 45 | 46 | // Remove the extension's directory and any files inside it. 47 | global $phpbb_container; 48 | 49 | $filesystem = $phpbb_container->get('filesystem'); 50 | $filepath = $this->phpbb_root_path . 'store/phpbbservices/digests'; 51 | if ($filesystem->exists($filepath)) 52 | { 53 | $filesystem->remove($filepath); 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /migrations/release_3_2_16.php: -------------------------------------------------------------------------------- 1 | config->offsetGet('phpbbservices_digests_cron_task_last_gc'); 28 | $this->config->delete('phpbbservices_digests_cron_task_last_gc'); 29 | $this->config->set('phpbbservices_digests_cron_task_last_gc', $last_gc, false); 30 | 31 | return array( 32 | // Remove functionality tied to the test button 33 | array('config.remove', array('phpbbservices_digests_test')), 34 | // Add the clear cached digests ACP module 35 | array('module.add', array( 36 | 'acp', 37 | 'ACP_CAT_DIGESTS', 38 | array( 39 | 'module_basename' => '\phpbbservices\digests\acp\main_module', 40 | 'modes' => array('digests_clear_cached'), 41 | )), 42 | ) 43 | ); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /migrations/release_3_3_13.php: -------------------------------------------------------------------------------- 1 | config->offsetGet('phpbbservices_digests_users_per_page'); 27 | if ($rows_per_page === 0) 28 | { 29 | $rows_per_page = 20; 30 | } 31 | 32 | return array( 33 | // Set the value of users per page to the value of rows per page 34 | array('if', array( 35 | ($this->config['phpbbservices_digests_users_per_page']), 36 | array('config.remove', array('phpbbservices_digests_users_per_page', $rows_per_page)), 37 | )), 38 | 39 | // Add some new configuration variables related to reporting 40 | array('config.add', array('phpbbservices_digests_clear_report', '0')), 41 | array('config.add', array('phpbbservices_digests_reporting_days', '30')), 42 | array('config.add', array('phpbbservices_digests_reporting_enable', '1')), 43 | array('config.add', array('phpbbservices_digests_rows_per_page', $rows_per_page)), 44 | 45 | // Add the reports ACP modules 46 | array('module.add', array( 47 | 'acp', 48 | 'ACP_CAT_DIGESTS', 49 | array( 50 | 'module_basename' => '\phpbbservices\digests\acp\main_module', 51 | 'modes' => array('digests_report'), 52 | ), 53 | ), 54 | )); 55 | } 56 | 57 | public function update_schema() 58 | { 59 | 60 | return array( 61 | 'add_tables' => array( 62 | // Add digests reports table 63 | $this->table_prefix . 'digests_report' => array( 64 | 'COLUMNS' => array( 65 | 'digests_report_id' => array('ULINT', null, 'auto_increment'), 66 | 'date_hour_sent_utc' => array('UINT:11', 0), 67 | 'started' => array('UINT:11', 0), 68 | 'ended' => array('UINT:11', 0), 69 | 'mailed' => array('USINT', 0), 70 | 'skipped' => array('USINT', 0), 71 | 'execution_time_secs' => array('DECIMAL:7', 0), 72 | 'memory_used_mb' => array('DECIMAL:13', 0), 73 | 'cron_type' => array('TINT:1', 0), 74 | ), 75 | 'PRIMARY_KEY' => array('digests_report_id'), 76 | 'KEYS' => array( 77 | 'date_hour_sent' => ['UNIQUE','date_hour_sent_utc'], 78 | 'started' => ['INDEX','started'], 79 | 'ended' => ['INDEX','ended'], 80 | 'execution_time' => ['INDEX','execution_time_secs'], 81 | 'memory_used' => ['INDEX','memory_used_mb'], 82 | ), 83 | ), 84 | // Add digests report details table 85 | $this->table_prefix . 'digests_report_details' => array( 86 | 'COLUMNS' => array( 87 | 'digests_report_id' => array('UINT', 0), 88 | 'user_id' => array('UINT:10', 0), 89 | 'digest_type' => array('VCHAR:4', 'NONE'), 90 | 'posts_in_digest' => array('USINT', 0), 91 | 'msgs_in_digest' => array('USINT', 0), 92 | 'creation_time' => array('UINT:11', 0), 93 | 'status' => array('TINT:1', 0), 94 | 'sent' => array('TINT:1', 0), 95 | ), 96 | 'KEYS' => array( 97 | 'report_id_user_id' => ['UNIQUE','digests_report_id,user_id'], 98 | 'user_id' => ['INDEX','user_id'], 99 | 'creation_time' => ['INDEX','creation_time'], 100 | ), 101 | ), 102 | ), 103 | ); 104 | 105 | } 106 | 107 | public function revert_schema() 108 | { 109 | return array( 110 | 'drop_tables' => array( 111 | $this->table_prefix . 'digests_report', 112 | $this->table_prefix . 'digests_report_details', 113 | ), 114 | ); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /migrations/release_3_3_14.php: -------------------------------------------------------------------------------- 1 | array( 27 | $this->table_prefix . 'users' => array( 28 | 'user_digest_last_sent_for' => array('UINT:11', 0), 29 | ), 30 | ), 31 | ); 32 | } 33 | 34 | public function revert_schema() 35 | { 36 | return array( 37 | 'drop_columns' => array( 38 | $this->table_prefix . 'users' => array( 39 | 'user_digest_last_sent_for', 40 | ), 41 | ), 42 | ); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /migrations/release_3_3_16.php: -------------------------------------------------------------------------------- 1 | db->sql_query('UPDATE ' . USERS_TABLE . " 34 | SET user_digest_type = '" . constants::DIGESTS_NONE_VALUE . "' 35 | WHERE user_type = " . USER_INACTIVE); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /migrations/release_3_3_4.php: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 |

{{ lang('DIGESTS_REGISTER_EXPLAIN') }}
6 |
7 | 8 | 9 |
10 |
11 | {% endif %} -------------------------------------------------------------------------------- /styles/all/template/js/digests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | $(document).ready(function(){ 3 | 4 | // Error handling popup settings 5 | $("#dialog").dialog({ 6 | title: dialogError, 7 | autoOpen: false, 8 | modal: true, 9 | minHeight: 0, 10 | draggable: false, 11 | resizeable: false, 12 | closeOnEscape: true, 13 | buttons: [ 14 | { 15 | text: ok, 16 | click: function() { 17 | $(this).dialog("close"); 18 | } 19 | } 20 | ] 21 | }); 22 | 23 | // If private messages will be allowed in the digest, the mark all private message checkbox must not be disabled. 24 | $("#pms1").click(function() { 25 | if ($("#pms1").is(':checked')) { 26 | $("#mark_read").prop("disabled", false); 27 | } 28 | }); 29 | 30 | // If private messages will not be allowed in the digest, the mark all private message checkbox must be disabled. 31 | $("#pms2").click(function() { 32 | if ($("#pms2").is(':checked')) { 33 | $("#mark_read").prop("disabled", true); 34 | } 35 | }); 36 | 37 | // If the all forums checkbox is checked, all individual forums should be checked, and visa versa. Ignore excluded 38 | // and included forums as these should always retain their original disabled setting. 39 | $("#all_forums").click(function(){ 40 | if ($("#all_forums").is(':checked')) { 41 | $("[name^=forums]").each(function() { 42 | if (!exclude_forum($(this).attr('id'))) { 43 | $(this).prop("checked", true); 44 | } 45 | }); 46 | ($("#all_forums").prop("checked", true)); 47 | } 48 | else { 49 | $("[name^=forums]").each(function() { 50 | if (!exclude_forum($(this).attr('id'))) { 51 | $(this).prop("checked", false); 52 | } 53 | }); 54 | } 55 | }); 56 | 57 | // If "See popular topics" field is set to No, the field "Minimum value of popularity" needs to be disabled, and visa versa. 58 | $("#popular1").click(function() { 59 | $("#popularity_size").prop("disabled", false); 60 | }); 61 | $("#popular2").click(function() { 62 | $("#popularity_size").prop("disabled", true); 63 | }); 64 | 65 | // If any individual forum is unchecked, the all_forums checkbox should be unchecked. Exception: required or excluded forums. 66 | // If all individual forums are checked, the all_forums checkbox should be checked. Exception: required or excluded forums. 67 | $("[name^=forums]").click(function() { 68 | var allChecked = true; // Assume all forums are checked 69 | $("[name^=forums]").each(function() { 70 | $("#all_forums").prop('checked', false); 71 | if ((!ignore_forum($(this).attr('id'))) && !$(this).is(':checked')) { 72 | allChecked = false; // Flag if any forum is unchecked 73 | } 74 | }); 75 | if (allChecked) { 76 | ($("#all_forums").prop('checked', true)); 77 | } 78 | else { 79 | ($("#all_forums").prop('checked', false)); 80 | } 81 | }); 82 | 83 | // If bookmarked topics only is selected, disable the forum controls, otherwise enable them. All forums checkbox also needs 84 | // to be enabled or disabled. 85 | $("#bookmarks, #all, #first").click(function() { 86 | var disabled = $("#bookmarks").is(':checked'); 87 | $("[name^=forums]").each(function() { 88 | if (!ignore_forum($(this).attr('id'))) { 89 | $(this).prop('disabled', disabled); 90 | } 91 | }); 92 | $("#all_forums").prop('disabled', disabled); 93 | }); 94 | 95 | // If all forums is unchecked and there are no checked forums, do not allow the form 96 | // to submit and display an error message. If bookmarked topics only is checked, then ignore. 97 | $("#phpbbservices_digests").submit(function(event) { 98 | if (!$("#all_forums").is(':checked') && !$("#bookmarks").is(':checked')) { 99 | var anyChecked = false; 100 | $("[name^=forums]").each(function() { 101 | if ($(this).prop('checked')) { 102 | anyChecked = true; 103 | } 104 | }); 105 | if (!anyChecked) { 106 | $("#dialog").text(noForumsChecked).dialog("open"); 107 | event.preventDefault(); 108 | } 109 | } 110 | }); 111 | 112 | function exclude_forum(forumId) { 113 | // Returns true if the forumId should be excluded 114 | return excludedForumsArray.indexOf(forumId) !== -1; 115 | } 116 | 117 | function ignore_forum(forumId) { 118 | // Returns true the forumId should be ignored 119 | return ignoredForumsArray.indexOf(forumId) !== -1; 120 | } 121 | 122 | }); 123 | -------------------------------------------------------------------------------- /styles/all/template/mail_digests_html.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/all/template/mail_digests_text.html: -------------------------------------------------------------------------------- 1 | 2 | {% if S_SHOW_PMS %} 3 | {{ lang('YOU_HAVE_PRIVATE_MESSAGES') }} 4 | 5 | {% for pm in loops.pm %} 6 | {{ pm.NEW_UNREAD }}{{ lang('PRIVATE_MESSAGE') }} 7 | {{ lang('SUBJECT') }}{{ lang('COLON') }} {{ pm.PRIVATE_MESSAGE_SUBJECT }} 8 | {{ lang('FROM') }}{{ lang('COLON') }} {{ pm.FROM }} {{ lang('DIGESTS_ON') }} {{ pm.DATE }} 9 | {{ pm.CONTENT }} 10 | 11 | {% else %} 12 | {{ lang('DIGESTS_NO_PRIVATE_MESSAGES') }} 13 | {% endfor %} 14 | ____________________________________________________________ 15 | {% endif %} 16 | 17 | {% for forum in loops.forum %} 18 | 19 | 20 | {{ lang('FORUM') }}{{ lang('COLON') }} {{ forum.FORUM }} 21 | 22 | {% for topic in forum.topic %} 23 | 24 | 25 | {{ lang('TOPIC') }}{{ lang('COLON') }} {{ topic.TOPIC }} 26 | 27 | {% for post in topic.post %} 28 | 29 | 30 | {% if not post.S_FIRST_POST %} 31 | {{ lang('DIGESTS_REPLY') }}{{ lang('COLON') }} {{ post.SUBJECT }} 32 | {% endif %} 33 | {{ lang('DIGESTS_BY') }}{{ lang('COLON') }} {{ post.FROM }} {{ lang('DIGESTS_ON') }} {{ post.DATE }} 34 | {% if S_SHOW_POST_TEXT %}{{ post.CONTENT }}{% endif %} 35 | 36 | {% endfor %} 37 | {% endfor %} 38 | {% else %} 39 | 40 | {{ lang('DIGESTS_NO_POSTS') }} 41 | {% endfor %} -------------------------------------------------------------------------------- /styles/all/template/ucp_digests.html: -------------------------------------------------------------------------------- 1 | {% INCLUDE 'ucp_header.html' %} 2 | {% if S_INCLUDE_DIGESTS_JS %} 3 | {# Includes ACP - JS #} 4 | {% if not definition.INCLUDED_DIGESTSJS %} 5 | {% INCLUDEJS '@phpbbservices_digests/js/jquery-ui-1.12.1.min.js' %} 6 | {% INCLUDEJS '@phpbbservices_digests/js/digests.js' %} 7 | {% DEFINE INCLUDED_DIGESTSJS = true %} 8 | {% endif %} 9 | {% endif %} 10 | 26 |
27 |

{{ lang('DIGESTS_MODE') }}

28 | 29 |
30 | {{ S_FORM_TOKEN }} 31 |
32 | 33 |
34 | 35 | {% if S_DIGESTS_NO_TIMEZONE %} 36 |

{{ lang('DIGESTS_NO_TIMEZONE') }}

37 | {% endif %} 38 | {% if S_DIGESTS_BASICS %} 39 |

{{ lang('DIGESTS_EXPLANATION') }}

40 | {% endif %} 41 |
42 | {% if S_DIGESTS_BASICS %} 43 |
44 |

{{ lang('DIGESTS_FREQUENCY_EXPLAIN') }}
45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |

{{ lang('DIGESTS_FORMAT_STYLING_EXPLAIN') }}
54 |
55 |
56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 |

{{ lang('DIGESTS_SEND_HOUR_EXPLAIN') }}
64 |
65 | 70 |
71 |
72 | {% endif %} 73 | {% if S_DIGESTS_FORUMS_SELECTION %} 74 | {{ lang('DIGESTS_DISABLED_MESSAGE') }} 75 | {% if S_DIGESTS_NO_FORUMS %} 76 |

{{ lang('DIGESTS_NO_FORUMS_AVAILABLE') }}

77 | {% endif %} 78 | {% if not S_DIGESTS_NO_FORUMS %} 79 |
80 |
81 |
82 | 83 | 84 | 85 |
86 |
87 |
88 |

{{ lang('DIGESTS_SELECT_FORUMS_EXPLAIN') }}
89 |
90 |

91 |
92 | {% for forums in loops.forums %} 93 | {% if forums.S_DIGESTS_DIV_OPEN %} 94 |
95 | {% endif %} 96 | {% if forums.S_DIGESTS_PRINT %} 97 | {% if forums.S_DIGESTS_IS_FORUM %} 98 |
99 | {% endif %} 100 | {% if not forums.S_DIGESTS_IS_FORUM %} 101 | {{ forums.FORUM_LABEL }}
102 | {% endif %} 103 | {% endif %} 104 | {% if forums.S_DIGESTS_DIV_CLOSE %} 105 |
106 | {% endif %} 107 | {% endfor %} 108 |
109 |
110 |
111 | {% endif %} 112 | {% endif %} 113 | {% if S_DIGESTS_POST_FILTERS %} 114 | {{ DISABLED_MESSAGE }} 115 |
116 |

{{ lang('DIGESTS_COUNT_LIMIT_EXPLAIN') }}
117 |
118 |
119 |
120 |
121 |

{{ lang('DIGESTS_MIN_SIZE_EXPLAIN') }}
122 |
123 |
124 |
125 |
126 |

{{ lang('DIGESTS_NEW_POSTS_ONLY_EXPLAIN') }}
127 |
128 | 129 | 130 |
131 |
132 |
133 |
134 |
135 | 136 | 137 |
138 |
139 |
140 |

{{ lang('DIGESTS_MIN_POPULARITY_VALUE_EXPLAIN') }}
141 |
142 |
143 |
144 |
145 |
146 |
147 | 148 | 149 |
150 |
151 |
152 |
153 |
154 | 155 | 156 |
157 |
158 |
159 |
160 |
161 | 162 |   163 |
164 |
165 | {% endif %} 166 | {% if S_DIGESTS_ADDITIONAL_CRITERIA %} 167 | {{ DISABLED_MESSAGE }} 168 |
169 |

{{ lang('DIGESTS_SORT_BY_EXPLAIN') }}
170 |
171 | 178 |
179 |
180 |
181 |

{{ lang('DIGESTS_MAX_DISPLAY_WORDS_EXPLAIN') }}
182 |
  183 |
184 |
185 |
186 |
187 |
188 | 189 | 190 |
191 |
192 |
193 |

{{ lang('DIGESTS_LASTVISIT_RESET_EXPLAIN') }}
194 |
195 | 196 | 197 |
198 |
199 |
200 |

{{ lang('DIGESTS_SHOW_ATTACHMENTS_EXPLAIN') }}
201 |
202 | 203 | 204 |
205 |
206 | {% if not S_DIGESTS_BLOCK_IMAGES %} 207 |
208 |

{{ lang('DIGESTS_BLOCK_IMAGES_EXPLAIN') }}
209 |
210 | 211 | 212 |
213 |
214 | {% endif %} 215 |
216 |

{{ lang('DIGESTS_TOC_EXPLAIN') }}
217 |
218 | 219 | 220 |
221 |
222 | {% endif %} 223 | 224 |
225 |
226 | 227 |
228 | 229 | {% if S_DIGESTS_SHOW_BUTTONS %} 230 |
231 |   232 | 233 | {{ S_DIGESTS_FORM_TOKEN }} 234 |
235 | {% endif %} 236 | 237 | 240 | 241 |
242 | {% INCLUDE 'ucp_footer.html' %} -------------------------------------------------------------------------------- /styles/all/theme/digests.css: -------------------------------------------------------------------------------- 1 | input[type="number"] 2 | { 3 | text-align: right; 4 | } 5 | .autowidth { 6 | width: 5em !important; 7 | } 8 | .ui-dialog { 9 | font-family: Verdana, Helvetica, Arial, sans-serif; 10 | text-align: center; 11 | width: 100%; 12 | height: auto; 13 | background-color: white; 14 | border: 1px solid #CADCEB; 15 | float: none; 16 | margin: inherit; 17 | } 18 | .ui-dialog-content { 19 | text-align: center; 20 | padding: 5px; 21 | } 22 | .ui-dialog-titlebar { 23 | background-color: #CADCEB; 24 | font-weight: 800; 25 | padding: 5px; 26 | border-top-left-radius: 5px; 27 | border-top-right-radius: 5px; 28 | } 29 | .ui-dialog-title{ 30 | width: 100%; 31 | } 32 | .ui-dialog-titlebar-close { 33 | display: none; 34 | } 35 | .ui-dialog .ui-dialog-title { 36 | width: 100%; 37 | } 38 | .ui-button { 39 | width: auto; 40 | background-color: #CADCEB; 41 | font-weight: 800; 42 | padding: 5px; 43 | border: 1px solid #CADCEB; 44 | margin: 5px; 45 | } 46 | .ui-widget { 47 | border-radius: 5px; 48 | } 49 | .ui-dialog-buttonpane { 50 | float:none; 51 | padding: 0; 52 | } 53 | .ui-dialog-buttonset { 54 | float: none; 55 | text-align: center; 56 | width: 100%; 57 | } 58 | .top-3px { 59 | margin-top: 3px; 60 | } 61 | .position-relative { 62 | position:relative; 63 | left: 20px; 64 | } -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /styles/all/theme/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkDHamill/digests/1efc79bc6c4d372f5bf671fa66aff53f4bd6b844/styles/all/theme/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /ucp/main_info.php: -------------------------------------------------------------------------------- 1 | '\phpbbservices\digests\ucp\main_module', 18 | 'title' => 'UCP_DIGESTS', 19 | 'version' => '3.2.12', 20 | 'modes' => array( 21 | 'basics' => array('title' => 'UCP_DIGESTS_BASICS', 'auth' => 'ext_phpbbservices/digests', 'cat' => array('UCP_DIGESTS')), 22 | 'forums_selection' => array('title' => 'UCP_DIGESTS_FORUMS_SELECTION', 'auth' => 'ext_phpbbservices/digests', 'cat' => array('UCP_DIGESTS')), 23 | 'post_filters' => array('title' => 'UCP_DIGESTS_POST_FILTERS', 'auth' => 'ext_phpbbservices/digests', 'cat' => array('UCP_DIGESTS')), 24 | 'additional_criteria' => array('title' => 'UCP_DIGESTS_ADDITIONAL_CRITERIA', 'auth' => 'ext_phpbbservices/digests', 'cat' => array('UCP_DIGESTS')), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ucp/main_module.php: -------------------------------------------------------------------------------- 1 | phpbb_container = $phpbb_container; 35 | 36 | /** @var \phpbbservices\digests\controller\ucp_controller $ucp_controller */ 37 | $ucp_controller = $phpbb_container->get('phpbbservices.digests.controller.ucp'); 38 | 39 | /** @var \phpbb\language\language $language */ 40 | $this->language = $phpbb_container->get('language'); 41 | $this->language->add_lang('common', 'phpbbservices/digests'); 42 | 43 | // Load a template from adm/style for our ACP pages 44 | $this->tpl_name = 'ucp_digests'; 45 | 46 | $this->language->add_lang(array('common', 'acp/common'), 'phpbbservices/digests'); 47 | 48 | // Set the page title for our ACP pages (modules) 49 | switch ($mode) 50 | { 51 | 52 | case 'basics': 53 | default: 54 | $this->page_title = $this->language->lang('UCP_DIGESTS_BASICS'); 55 | break; 56 | 57 | case 'forums_selection': 58 | $this->page_title = $this->language->lang('UCP_DIGESTS_FORUMS_SELECTION'); 59 | break; 60 | 61 | case 'post_filters': 62 | $this->page_title = $this->language->lang('UCP_DIGESTS_POST_FILTERS'); 63 | break; 64 | 65 | case 'additional_criteria': 66 | $this->page_title = $this->language->lang('UCP_DIGESTS_ADDITIONAL_CRITERIA'); 67 | break; 68 | 69 | } 70 | 71 | // Load the display options handle in our ACP controller, passing the mode 72 | $ucp_controller->display_options($mode, $this->u_action); 73 | 74 | } 75 | 76 | } 77 | --------------------------------------------------------------------------------