├── .gitignore ├── README.md ├── acp ├── discord_notifications_info.php └── discord_notifications_module.php ├── adm └── style │ └── acp_discord_notifications.html ├── build.xml ├── composer.json ├── config ├── parameters.yml └── services.yml ├── event └── notification_event_listener.php ├── language ├── en │ ├── acp_discord_notifications.php │ ├── discord_notification_messages.php │ └── info_acp_discord_notifications.php └── fr │ ├── acp_discord_notifications.php │ ├── discord_notification_messages.php │ └── info_acp_discord_notifications.php ├── license.txt ├── migrations └── extension_installation.php ├── notification_service.php ├── phpunit.xml.dist ├── tests ├── dbal │ ├── fixtures │ │ └── config.xml │ └── simple_test.php └── functional │ └── demo_test.php └── travis └── prepare-phpbb.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phpbb-discord-notifications 2 | 3 | A phpBB extension that publishes notification messages to a Discord channel when certain events occur on a phpBB board. The intent of this extension is meant to announce content changes on a forum to a community residing on a Discord server. It is not intended as a compliment to the announcements found within the phpBB admin or moderator control panels. See the [wiki](https://github.com/rootslinux/phpbb-discord-notifications/wiki) for additional information. 4 | 5 | ## Installation 6 | 7 | Copy the extension to phpBB/ext/roots/discordnotifications 8 | 9 | Go to "ACP" > "Customise" > "Extensions" and enable the "Discord Notifications" extension. 10 | 11 | ## Tests and Continuous Integration 12 | 13 | We use Travis-CI as a continuous integration server and phpunit for our unit testing. See more information on the [phpBB Developer Docs](https://area51.phpbb.com/docs/dev/31x/testing/index.html). 14 | To run the tests locally, you need to install phpBB from its Git repository. Afterwards run the following command from the phpBB Git repository's root: 15 | 16 | Windows: 17 | 18 | phpBB\vendor\bin\phpunit.bat -c phpBB\ext\roots\discordnotifications\phpunit.xml.dist 19 | 20 | Other Systems: 21 | 22 | phpBB/vendor/bin/phpunit -c phpBB/ext/roots/discordnotifications/phpunit.xml.dist 23 | 24 | ## License 25 | 26 | [GPLv2](license.txt) 27 | -------------------------------------------------------------------------------- /acp/discord_notifications_info.php: -------------------------------------------------------------------------------- 1 | '\roots\discordnotifications\acp\discord_notifications_module', 20 | 'title' => 'ACP_DISCORD_NOTIFICATIONS', 21 | 'modes' => array( 22 | 'settings' => array( 23 | 'title' => 'ACP_DISCORD_NOTIFICATIONS_TITLE', 24 | 'auth' => 'ext_roots/discordnotifications && acl_a_board', 25 | 'cat' => array('ACP_DISCORD_NOTIFICATIONS') 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /acp/discord_notifications_module.php: -------------------------------------------------------------------------------- 1 | cache = $phpbb_container->get('cache.driver'); 71 | $this->config = $phpbb_container->get('config'); 72 | $this->db = $phpbb_container->get('dbal.conn'); 73 | $this->language = $phpbb_container->get('language'); 74 | $this->log = $phpbb_container->get('log'); 75 | $this->request = $phpbb_container->get('request'); 76 | $this->template = $phpbb_container->get('template'); 77 | $this->user = $phpbb_container->get('user'); 78 | // Used for sending test messages to Discord 79 | $this->notification_service = $phpbb_container->get('roots.discordnotifications.notification_service'); 80 | 81 | $this->user->add_lang_ext('roots/discordnotifications', 'acp_discord_notifications'); 82 | $this->tpl_name = 'acp_discord_notifications'; 83 | $this->page_title = $this->user->lang('ACP_DISCORD_NOTIFICATIONS_TITLE'); 84 | 85 | add_form_key(self::PAGE_FORM_NAME); 86 | 87 | // Process submit actions 88 | if ($this->request->is_set_post('action_send_test_message')) 89 | { 90 | $this->process_send_test_message(); 91 | } 92 | elseif ($this->request->is_set_post('submit')) 93 | { 94 | $this->process_form_submit(); 95 | } 96 | 97 | // Generate the dynamic HTML content for enabling/disabling forum notifications 98 | $this->generate_forum_section(); 99 | 100 | // Assign template values so that the page reflects the state of the extension settings 101 | $this->template->assign_vars(array( 102 | 'DN_MASTER_ENABLE' => $this->config['discord_notifications_enabled'], 103 | 'DN_WEBHOOK_URL' => $this->config['discord_notifications_webhook_url'], 104 | 'DN_POST_PREVIEW_LENGTH' => $this->config['discord_notifications_post_preview_length'], 105 | 'DN_TEST_MESSAGE_TEXT' => $this->user->lang('DN_TEST_MESSAGE_TEXT'), 106 | 107 | 'DN_POST_CREATE' => $this->config['discord_notification_type_post_create'], 108 | 'DN_POST_UPDATE' => $this->config['discord_notification_type_post_update'], 109 | 'DN_POST_DELETE' => $this->config['discord_notification_type_post_delete'], 110 | 'DN_POST_LOCK' => $this->config['discord_notification_type_post_lock'], 111 | 'DN_POST_UNLOCK' => $this->config['discord_notification_type_post_unlock'], 112 | 113 | 'DN_TOPIC_CREATE' => $this->config['discord_notification_type_topic_create'], 114 | 'DN_TOPIC_UPDATE' => $this->config['discord_notification_type_topic_update'], 115 | 'DN_TOPIC_DELETE' => $this->config['discord_notification_type_topic_delete'], 116 | 'DN_TOPIC_LOCK' => $this->config['discord_notification_type_topic_lock'], 117 | 'DN_TOPIC_UNLOCK' => $this->config['discord_notification_type_topic_unlock'], 118 | 119 | 'DN_USER_CREATE' => $this->config['discord_notification_type_user_create'], 120 | 'DN_USER_DELETE' => $this->config['discord_notification_type_user_delete'], 121 | 122 | 'U_ACTION' => $this->u_action, 123 | )); 124 | } 125 | 126 | /* 127 | * Called when the user clicks the "Send Test Message" button on the page. Sends the content in the 128 | * Text Message input to Discord. 129 | */ 130 | private function process_send_test_message() 131 | { 132 | $webhook_url = $this->request->variable('dn_webhook_url', ''); 133 | $test_message = $this->request->variable('dn_test_message', ''); 134 | 135 | // Check user inputs before attempting to send the message 136 | if ($test_message == '') 137 | { 138 | trigger_error($this->user->lang('DN_TEST_BAD_MESSAGE') . adm_back_link($this->u_action), E_USER_WARNING); 139 | } 140 | if ($webhook_url == '' || !filter_var($webhook_url, FILTER_VALIDATE_URL)) 141 | { 142 | trigger_error($this->user->lang('DN_TEST_BAD_WEBHOOK') . adm_back_link($this->u_action), E_USER_WARNING); 143 | } 144 | 145 | $result = $this->notification_service->force_send_discord_notification($webhook_url, $test_message); 146 | if ($result == true) 147 | { 148 | trigger_error($this->user->lang('DN_TEST_SUCCESS') . adm_back_link($this->u_action)); 149 | } 150 | else 151 | { 152 | trigger_error($this->user->lang('DN_TEST_FAILURE') . adm_back_link($this->u_action), E_USER_WARNING); 153 | } 154 | } 155 | 156 | /* 157 | * Handles all error checking and database changes when the user hits the submit button on the ACP page. 158 | */ 159 | private function process_form_submit() 160 | { 161 | if (!check_form_key(self::PAGE_FORM_NAME)) 162 | { 163 | trigger_error('FORM_INVALID', E_USER_WARNING); 164 | } 165 | 166 | // Get form values for the main settings 167 | $master_enable = $this->request->variable('dn_master_enable', 0); 168 | $webhook_url = $this->request->variable('dn_webhook_url', ''); 169 | $preview_length = $this->request->variable('dn_post_preview_length', ''); 170 | 171 | // If the master enable is set to on, a webhook URL is required 172 | if ($master_enable == 1 && $webhook_url == '') 173 | { 174 | trigger_error($this->user->lang('DN_MASTER_WEBHOOK_REQUIRED') . adm_back_link($this->u_action), E_USER_WARNING); 175 | } 176 | // Check that the webhook URL is a valid URL string if it is not empty 177 | if ($webhook_url != '' && !filter_var($webhook_url, FILTER_VALIDATE_URL)) 178 | { 179 | trigger_error($this->user->lang('DN_WEBHOOK_URL_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); 180 | } 181 | // Verify that the post preview length is a numeric string, convert to an int and check the valid range 182 | if (is_numeric($preview_length) == false) 183 | { 184 | trigger_error($this->user->lang('DN_POST_PREVIEW_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); 185 | } 186 | $preview_length = (int)$preview_length; 187 | if ($preview_length != 0 && ($preview_length < self::MIN_POST_PREVIEW_LENGTH || $preview_length > self::MAX_POST_PREVIEW_LENGTH)) 188 | { 189 | trigger_error($this->user->lang('DN_POST_PREVIEW_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); 190 | } 191 | 192 | $this->config->set('discord_notifications_enabled', $master_enable); 193 | $this->config->set('discord_notifications_webhook_url', $webhook_url); 194 | $this->config->set('discord_notifications_post_preview_length', $preview_length); 195 | 196 | $this->config->set('discord_notification_type_post_create', $this->request->variable('dn_post_create', 0)); 197 | $this->config->set('discord_notification_type_post_update', $this->request->variable('dn_post_update', 0)); 198 | $this->config->set('discord_notification_type_post_delete', $this->request->variable('dn_post_delete', 0)); 199 | $this->config->set('discord_notification_type_post_lock', $this->request->variable('dn_post_lock', 0)); 200 | $this->config->set('discord_notification_type_post_unlock', $this->request->variable('dn_post_unlock', 0)); 201 | 202 | $this->config->set('discord_notification_type_topic_create', $this->request->variable('dn_topic_create', 0)); 203 | $this->config->set('discord_notification_type_topic_update', $this->request->variable('dn_topic_update', 0)); 204 | $this->config->set('discord_notification_type_topic_delete', $this->request->variable('dn_topic_delete', 0)); 205 | $this->config->set('discord_notification_type_topic_lock', $this->request->variable('dn_topic_lock', 0)); 206 | $this->config->set('discord_notification_type_topic_unlock', $this->request->variable('dn_topic_unlock', 0)); 207 | 208 | $this->config->set('discord_notification_type_user_create', $this->request->variable('dn_user_create', 0)); 209 | $this->config->set('discord_notification_type_user_delete', $this->request->variable('dn_user_delete', 0)); 210 | 211 | // Set the discord_notifications_enabled in the forum table. 212 | $forum_id_names = array(); // This array will be built up to contain {forum_id} => {input_name} 213 | 214 | // First grab all variables in the submit request and match each against a regex to find the ones that are tied to a forum enabled setting. 215 | $form_inputs = $this->request->variable_names(); 216 | foreach ($form_inputs as $input) 217 | { 218 | $matches = array(); 219 | $match = preg_match('/^' . self::FORUM_INPUT_PREFIX . '(\d+)$/', $input, $matches); 220 | if ($match === 1) 221 | { 222 | $forum_id_names[$matches[1]] = $input; 223 | } 224 | } 225 | 226 | // Grab all of the values for all of the forum inputs and update the row in the forum table 227 | foreach ($forum_id_names as $id => $input_name) 228 | { 229 | $enabled = (int)$this->request->variable($input_name, 0); 230 | $sql = "UPDATE " . FORUMS_TABLE . " SET discord_notifications_enabled = $enabled WHERE forum_id = $id"; 231 | $this->db->sql_query($sql); 232 | // TODO: It would be better to do this update in a single operation instead of once for each input inside this loop 233 | } 234 | 235 | // Log the settings change 236 | $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'ACP_DISCORD_NOTIFICATIONS_LOG_UPDATE'); 237 | // Destroy any cached discord notification data 238 | $this->cache->destroy('roots_discord_notifications'); 239 | 240 | trigger_error($this->user->lang('DN_SETTINGS_SAVED') . adm_back_link($this->u_action)); 241 | } 242 | 243 | /** 244 | * Generates the section of the ACP page listing all of the forums, in order, with the radio button option 245 | * that allows the user to enable or disable discord notifications for that forum. 246 | */ 247 | private function generate_forum_section() 248 | { 249 | $sql = "SELECT forum_id, forum_type, forum_name, discord_notifications_enabled FROM " . FORUMS_TABLE . " ORDER BY left_id ASC"; 250 | $result = $this->db->sql_query($sql); 251 | 252 | while ($row = $this->db->sql_fetchrow($result)) 253 | { 254 | // Category forums are displayed for organizational purposes, but have no configuration 255 | if ($row['forum_type'] == FORUM_CAT) 256 | { 257 | $tpl_row = array( 258 | 'S_IS_CAT' => true, 259 | 'FORUM_NAME' => $row['forum_name'], 260 | ); 261 | $this->template->assign_block_vars('forumrow', $tpl_row); 262 | } 263 | // Normal forums have a radio input with the value selected based on the value of the discord_notifications_enabled setting 264 | else if ($row['forum_type'] == FORUM_POST) 265 | { 266 | // The labels for all the inputs are constructed based on the forum IDs to make it easy to know which 267 | $tpl_row = array( 268 | 'S_IS_CAT' => false, 269 | 'FORUM_NAME' => $row['forum_name'], 270 | 'FORUM_ID' => self::FORUM_INPUT_PREFIX . $row['forum_id'], 271 | 'FORUM_DN_ENABLED' => $row['discord_notifications_enabled'], 272 | ); 273 | $this->template->assign_block_vars('forumrow', $tpl_row); 274 | } 275 | // Other forum types (links) are ignored 276 | } 277 | $this->db->sql_freeresult($result); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /adm/style/acp_discord_notifications.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{L_ACP_DISCORD_NOTIFICATIONS}

4 | 5 |

{DN_ACP_DESCRIPTION}

6 | 7 |
8 | 9 |
10 | {L_DN_MAIN_SETTINGS} 11 |
12 |
13 |
14 |
15 |
16 |
17 |

{L_DN_WEBHOOK_DESCRIPTION}
18 |
19 | 20 |
21 |
22 |
23 |

{L_DN_POST_PREVIEW_DESCRIPTION}
24 |
25 | 26 |
27 |
28 |
29 |

{L_DN_TEST_DESCRIPTION}
30 |
31 | 32 |
33 |
34 |
35 |

{L_DN_SEND_TEST_DESCRIPTION}
36 |
37 |
38 |
39 | 40 |
41 | {L_DN_TYPE_SETTINGS} 42 |

{L_DN_TYPE_DESCRIPTION}

43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 105 |
106 | {L_DN_FORUM_SETTINGS} 107 |

{L_DN_FORUM_DESCRIPTION}

108 | 109 | 110 | 111 |
112 |
113 |
114 | 115 |
116 |
117 |
118 | 119 | 120 |
121 |
122 | 123 | 124 | {L_NO_FORUMS} 125 | 126 | 127 |
128 | 129 |
130 |   131 | {S_FORM_TOKEN} 132 |
133 | 134 |
135 | 136 | 137 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roots/discordnotifications", 3 | "type": "phpbb-extension", 4 | "description": "Publishes forum event notifications to a Discord server channel", 5 | "homepage": "https://github.com/rootslinux/phpbb-discord-notifications", 6 | "version": "1.0.0", 7 | "keywords": ["phpbb", "extension", "discord"], 8 | "time": "2018-10-14", 9 | "license": "GPL-2.0-only", 10 | "authors": [ 11 | { 12 | "name": "Tyler Olsen", 13 | "homepage": "https://github.com/rootslinux", 14 | "role": "Lead Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.3.3", 19 | "composer/installers": "~1.0" 20 | }, 21 | "extra": { 22 | "display-name": "Discord Notifications", 23 | "soft-require": { 24 | "phpbb/phpbb": ">=3.1.4,<3.2.0@dev" 25 | } 26 | }, 27 | "require-dev": { 28 | "phing/phing": "2.4.*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /config/parameters.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | roots.discordnotifications.tables.notifications: '%core.table_prefix%notifications' 3 | -------------------------------------------------------------------------------- /config/services.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: parameters.yml } 3 | 4 | services: 5 | roots.discordnotifications.notification_service: 6 | class: roots\discordnotifications\notification_service 7 | arguments: 8 | - '@config' 9 | - '@dbal.conn' 10 | 11 | roots.discordnotifications.listener: 12 | class: roots\discordnotifications\event\notification_event_listener 13 | arguments: 14 | - '@language' 15 | - '@roots.discordnotifications.notification_service' 16 | tags: 17 | - { name: event.listener } 18 | -------------------------------------------------------------------------------- /event/notification_event_listener.php: -------------------------------------------------------------------------------- 1 | language = $language; 53 | $this->notification_service = $notification_service; 54 | 55 | // Add notifications text from the langauge file 56 | $this->language->add_lang('discord_notification_messages', 'roots/discordnotifications'); 57 | } 58 | 59 | /** 60 | * Assigns functions defined in this class to event listeners 61 | * @return array 62 | * @static 63 | * @access public 64 | * 65 | * For description of the event types, refer to https://wiki.phpbb.com/Event_List 66 | */ 67 | static public function getSubscribedEvents() 68 | { 69 | return array( 70 | // This event is used for performing actions directly after a post or topic has been submitted. 71 | 'core.submit_post_end' => 'handle_post_submit_action', 72 | // This event is used for performing actions directly after a post or topic has been deleted. 73 | 'core.delete_post_after' => 'handle_post_delete_action', 74 | // Perform additional actions after locking/unlocking posts and topics 75 | 'core.mcp_lock_unlock_after' => 'handle_lock_action', 76 | // Perform additional actions before topic(s) deletion 77 | 'core.delete_topics_before_query' => 'handle_topic_delete_action', 78 | // Event that returns user id, user details and user CPF of newly registered user 79 | 'core.user_add_after' => 'handle_user_add_action', 80 | // This event can be used to modify data after user account's activation 81 | 'core.ucp_activate_after' => 'handle_user_activate_action', 82 | // Perform additional actions after the users have been activated/deactivated 83 | 'core.user_active_flip_after' => 'handle_user_activate_action', 84 | // Event after a user is deleted 85 | 'core.delete_user_after' => 'handle_user_delete_action', 86 | ); 87 | } 88 | 89 | // ---------------------------------------------------------------------------- 90 | // -------------------------- Event Handler Functions ------------------------- 91 | // ---------------------------------------------------------------------------- 92 | 93 | /** 94 | * Handles events generated by submitting a post. This could result in a notification of several different types. 95 | * @param \phpbb\event\data $event Event object -- [data, mode, poll, post_visibility, subject, topic_type, update_message, update_search_index, url, username] 96 | * 97 | * The possible notifications that can be generated as a result of these events include: 98 | * - New post created 99 | * - Post updated 100 | * - New topic created 101 | * - Topic updated 102 | */ 103 | public function handle_post_submit_action($event) 104 | { 105 | // Check for visibility of the post/topic. We don't send notifications for content that are hidden from normal users. 106 | // Note that there are three visibility settings here. The first is the post visibility when it is generated. For example, 107 | // users may require moderator approval before their posts appear. The other two are the existing visibility status of the topic and post. 108 | if ($event['post_visibility'] === 0 || $event['data']['topic_visibility'] === 0 || $event['data']['post_visibility'] === 0) 109 | { 110 | return; 111 | } 112 | 113 | // Verify that the forum that the post submit action happened in has notifications enabled. If not, we have nothing further to do. 114 | if ($this->notification_service->is_notification_forum_enabled($event['data']['forum_id']) == false) 115 | { 116 | return; 117 | } 118 | 119 | // Build an array of the event data that we may need to pass along to the function that will construct the notification message 120 | $post_data = array( 121 | 'user_id' => $event['data']['poster_id'], 122 | 'user_name' => $event['username'], 123 | 'forum_id' => $event['data']['forum_id'], 124 | 'forum_name' => $event['data']['forum_name'], 125 | 'topic_id' => $event['data']['topic_id'], 126 | 'topic_title' => $event['data']['topic_title'], 127 | 'post_id' => $event['data']['post_id'], 128 | 'post_title' => $event['subject'], 129 | 'edit_user_id' => $event['data']['post_edit_user'], 130 | 'edit_user_name' => $this->language->lang('UNKNOWN_USER'), 131 | 'edit_reason' => $event['data']['post_edit_reason'], 132 | 'content' => $event['data']['message'], 133 | ); 134 | 135 | if ($post_data['edit_user_id'] == $post_data['user_id']) 136 | { 137 | $post_data['edit_user_name'] = $post_data['user_name']; 138 | } 139 | else 140 | { 141 | $post_data['edit_user_name'] = $this->language->lang('UNKNOWN_USER'); 142 | $edit_name = $this->notification_service->query_user_name($post_data['edit_user_id']); 143 | if ($edit_name != null) 144 | { 145 | $post_data['edit_user_name'] = $edit_name; 146 | } 147 | } 148 | 149 | // Finally, based on the event characteristics we determine which kind of notification we need to send 150 | if ($event['mode'] == 'post') // New topic 151 | { 152 | $this->notify_topic_created($post_data); 153 | } 154 | elseif ($event['mode'] == 'reply' || $event['mode'] == 'quote') // New post 155 | { 156 | $this->notify_post_created($post_data); 157 | } 158 | elseif ($event['mode'] == 'edit' || $event['mode'] == 'edit_topic' || $event['mode'] == 'edit_first_post' || $event['mode'] == 'edit_last_post') // Edit existing post 159 | { 160 | // If the post that was edited is the first one in the topic, we consider this a topic update event. 161 | if ($event['data']['post_id'] == $event['data']['topic_first_post_id']) 162 | { 163 | $this->notify_topic_updated($post_data); 164 | } 165 | // Otherwise we treat this as a post update event. 166 | else 167 | { 168 | $this->notify_post_updated($post_data); 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * Handles events generated by deleting a post. 175 | * @param \phpbb\event\data $event Event object -- [data, forum_id, is_soft, next_post_id, post_id, post_mode, softdelete_reason, topic_id] 176 | */ 177 | public function handle_post_delete_action($event) 178 | { 179 | // Check for visibility of the post/topic. We don't send notifications for content that is hidden from normal users. 180 | if ($event['data']['topic_visibility'] == 0 || $event['data']['post_visibility'] == 0) 181 | { 182 | return; 183 | } 184 | 185 | // Verify that the forum that the post delete action happened in has notifications enabled. If not, we have nothing further to do. 186 | if ($this->notification_service->is_notification_forum_enabled($event['forum_id']) == false) 187 | { 188 | return; 189 | } 190 | 191 | // Build an array of the event data that we may need to pass along to the function that will construct the notification message. 192 | // Note that unfortunately, the event data does not give us any information indicating which user deleted the post. 193 | $post_data = array( 194 | 'user_id' => $event['data']['poster_id'], 195 | 'user_name' => $this->language->lang('UNKNOWN_USER'), 196 | 'forum_id' => $event['forum_id'], 197 | 'forum_name' => $this->language->lang('UNKNOWN_FORUM'), 198 | 'topic_id' => $event['topic_id'], 199 | 'topic_title' => $this->language->lang('UNKNOWN_TOPIC'), 200 | 'post_id' => $event['post_id'], 201 | 'delete_reason' => $event['softdelete_reason'], 202 | ); 203 | 204 | // Fetch the forum name, topic title, and user name using the respective IDs. 205 | $forum_name = $this->notification_service->query_forum_name($post_data['forum_id']); 206 | if ($forum_name != null) 207 | { 208 | $post_data['forum_name'] = $forum_name; 209 | } 210 | $topic_title = $this->notification_service->query_topic_title($post_data['topic_id']); 211 | if ($topic_title != null) 212 | { 213 | $post_data['topic_title'] = $topic_title; 214 | } 215 | $user_name = $this->notification_service->query_user_name($post_data['user_id']); 216 | if ($user_name != null) 217 | { 218 | $post_data['user_name'] = $user_name; 219 | } 220 | 221 | $this->notify_post_deleted($post_data); 222 | } 223 | 224 | /** 225 | * Handles events generated by deleting a topic. 226 | * @param \phpbb\event\data $event Event object -- [table_ary, topic_ids] 227 | */ 228 | public function handle_topic_delete_action($event) 229 | { 230 | // Notification messages can get complicated when more than one topic is deleted in a transaction. We choose to not generate 231 | // a notification in this case. 232 | if (count($event['topic_ids']) > 1) 233 | { 234 | return; 235 | } 236 | 237 | // Unfortunately the only useful data we get from this event is the topic ID. We have to run a custom query to retrieve the 238 | // rest of the data that we are interested in. 239 | $topic_id = (int)array_pop($event['topic_ids']); 240 | $query_data = $this->notification_service->query_topic_details($topic_id); 241 | 242 | // Check for visibility of the topic. We don't send notifications for content that is hidden from normal users. 243 | if ($query_data['topic_visibility'] == 0) 244 | { 245 | return; 246 | } 247 | 248 | // Verify that the forum that the topic delete action happened in has notifications enabled. If not, we have nothing further to do. 249 | if ($this->notification_service->is_notification_forum_enabled($query_data['forum_id']) == false) 250 | { 251 | return; 252 | } 253 | 254 | // Copy over the data necessary to generate the notification into a new array 255 | $delete_data = array(); 256 | $delete_data['forum_id'] = $query_data['forum_id']; 257 | $delete_data['forum_name'] = $query_data['forum_name']; 258 | $delete_data['topic_title'] = $query_data['topic_title']; 259 | $delete_data['topic_post_count'] = $query_data['topic_posts_approved']; 260 | $delete_data['user_id'] = $query_data['topic_poster']; 261 | $delete_data['user_name'] = $query_data['topic_first_poster_name']; 262 | 263 | $this->notify_topic_deleted($delete_data); 264 | } 265 | 266 | /** 267 | * Handles events generated by changing the lock status on a topic or post. 268 | * @param \phpbb\event\data $event Event object -- [action, data, ids] 269 | * 270 | * The possible notifications that can be generated as a result of these events include: 271 | * - Post locked 272 | * - Post unlocked 273 | * - Topic locked 274 | * - Topic unlocked 275 | */ 276 | public function handle_lock_action($event) 277 | { 278 | // Notification messages can get complicated if we have multiple topics or posts that are locked/unlocked in a single transaction. 279 | // Presently we choose not to take any action on such operations. 280 | if (count($event['ids']) > 1) 281 | { 282 | return; 283 | } 284 | 285 | // Get the ID needed to access $event['data'], then extract all relevant data from the event that we need to generate the notification 286 | $id = array_pop($event['ids']); 287 | 288 | $lock_data = array(); 289 | $lock_data['forum_id'] = $event['data'][$id]['forum_id']; 290 | $lock_data['forum_name'] = $event['data'][$id]['forum_name']; 291 | $lock_data['post_id'] = $event['data'][$id]['post_id']; 292 | $lock_data['post_subject'] = $event['data'][$id]['post_subject']; 293 | $lock_data['topic_id'] = $event['data'][$id]['topic_id']; 294 | $lock_data['topic_title'] = $event['data'][$id]['topic_title']; 295 | // Two sets of user data captured: one for the post (if applicable) and one for the user that started the topic 296 | $lock_data['post_user_id'] = $event['data'][$id]['poster_id']; 297 | $lock_data['post_user_name'] = $event['data'][$id]['username']; 298 | $lock_data['topic_user_id'] = $event['data'][$id]['topic_poster']; 299 | $lock_data['topic_user_name'] = $event['data'][$id]['topic_first_poster_name']; 300 | 301 | // If the forum the post was made in does not have notifications enabled or the topic/poar is not visible, do nothing more. 302 | $topic_visibile = $event['data'][$id]['topic_visibility'] == 1 ? true : false; 303 | if ($this->notification_service->is_notification_forum_enabled($lock_data['forum_id']) == false || $topic_visibile == false) 304 | { 305 | return; 306 | } 307 | 308 | // The action determines whether the action was a lock or unlock event and thus which notification to generate 309 | switch ($event['action']) 310 | { 311 | case 'lock_post': 312 | $this->notify_post_locked($lock_data); 313 | break; 314 | case 'unlock_post': 315 | $this->notify_post_unlocked($lock_data); 316 | break; 317 | case 'lock': 318 | $this->notify_topic_locked($lock_data); 319 | break; 320 | case 'unlock': 321 | $this->notify_topic_unlocked($lock_data); 322 | break; 323 | } 324 | } 325 | 326 | /** 327 | * Handles events generated by the creation of a new user account. 328 | * @param \phpbb\event\data $event Event object -- [cp_data, user_id, user_row] 329 | * 330 | * If the user account that was created is initially inactive or a bot, we don't generate a user created 331 | * notification. Instead, we wait until the user activates their account. 332 | */ 333 | public function handle_user_add_action($event) 334 | { 335 | // Only generate a notification if the user starts off as a normal, activated user. 336 | if ($event['user_row']['user_type'] != USER_NORMAL) 337 | { 338 | return; 339 | } 340 | 341 | // Notifications are only generated if user activation is disabled and this is a normal user 342 | $user_data['user_id'] = $event['user_id']; 343 | $user_data['user_name'] = $event['user_row']['username']; 344 | 345 | $this->notify_user_created($user_data); 346 | } 347 | 348 | /** 349 | * Handles events generated by the activation or deactivation of a user account. 350 | * @param \phpbb\event\data $event Event object -- [message, user_row] -or- [activated, deactivated, mode, reason, sql_statements, user_id_ary] 351 | * 352 | * There are two different types of events that can trigger this function to be called, and each one provides different types of event data. 353 | * The first type of event, "core.ucp_activate_after" occurs when a user activates their own account. The second event, "core.user_active_flip_after" 354 | * occurs when an adminstrator either activates or deactivates one or more user accounts. We look at the event data to figure out which type 355 | * of event occurred and from there pass along the appropriate data to the notify_user_created() function. 356 | 357 | */ 358 | public function handle_user_activate_action($event) 359 | { 360 | $user_data = array(); 361 | if ($event['user_id']) 362 | { 363 | $user_data['user_id'] = $event['user_id']; 364 | $user_data['user_name'] = $event['user_row']['username']; 365 | } 366 | elseif ($event['activated'] == 1) 367 | { 368 | $user_data['user_id'] = array_pop($event['user_id_ary']); 369 | $user_name = $this->notification_service->query_user_name($user_data['user_id']); 370 | $user_data['user_name'] = ($user_name != null) ? $user_name : $this->language->lang('UNKNOWN_USER'); 371 | } 372 | // Ignore deactivated user case 373 | else 374 | { 375 | return; 376 | } 377 | 378 | $this->notify_user_created($user_data); 379 | } 380 | 381 | /** 382 | * Handles events generated by the deletion of one or more user account. 383 | * @param \phpbb\event\data $event Event object -- [mode, retain_username, user_ids, user_rows] 384 | */ 385 | public function handle_user_delete_action($event) 386 | { 387 | // Extract the IDs and names of all deleted users to pass along in an array of (id => name) 388 | $user_data = array(); 389 | foreach ($event['user_ids'] as $id) 390 | { 391 | $user_data[$id] = $event['user_rows'][$id]['username']; 392 | } 393 | 394 | $this->notify_users_deleted($user_data); 395 | } 396 | 397 | // ---------------------------------------------------------------------------- 398 | // --------------------- Notification Generation Functions -------------------- 399 | // ---------------------------------------------------------------------------- 400 | 401 | /** 402 | * Sends a notification to Discord when a new post is created. 403 | * @param $data Array of attributes for the new post 404 | */ 405 | private function notify_post_created($data) 406 | { 407 | // Constant properties for this notification type 408 | $notification_type_config_name = 'discord_notification_type_post_create'; 409 | $color = self::COLOR_BRIGHT_GREEN; 410 | $emoji = self::EMOJI_CREATE; 411 | 412 | // Verify that this notification type is enabled. If not, we have nothing further to do. 413 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 414 | return; 415 | } 416 | 417 | // Construct the notification message using the post data 418 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 419 | $post_link = $this->generate_post_link($data['topic_id'], $data['post_id'], $this->language->lang('POST')); 420 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 421 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 422 | $message = sprintf($this->language->lang('CREATE_POST'), 423 | $emoji, $user_link, $post_link, $topic_link, $forum_link 424 | ); 425 | 426 | // Generate a post preview if necessary 427 | $footer = $this->generate_footer_text($this->language->lang('PREVIEW'), $data['content']); 428 | 429 | $this->notification_service->send_discord_notification($color, $message, $footer); 430 | } 431 | 432 | /** 433 | * Sends a notification to Discord when a post is updated. 434 | * @param $data Array of attributes for the updated post 435 | */ 436 | private function notify_post_updated($data) 437 | { 438 | // Constant properties for this notification type 439 | $notification_type_config_name = 'discord_notification_type_post_update'; 440 | $color = self::COLOR_BRIGHT_BLUE; 441 | $emoji = self::EMOJI_UPDATE; 442 | 443 | // Verify that this notification type is enabled. If not, we have nothing further to do. 444 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 445 | return; 446 | } 447 | 448 | // Construct the notification message using the post data 449 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 450 | $post_link = $this->generate_post_link($data['topic_id'], $data['post_id'], $this->language->lang('POST')); 451 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 452 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 453 | 454 | // The notification is slightly different depending on whether the user edited their own post, or another user made the edit. 455 | $message = ''; 456 | if ($data['user_id'] == $data['edit_user_id']) 457 | { 458 | $message = sprintf($this->language->lang('UPDATE_POST_SELF'), 459 | $emoji, $user_link, $post_link, $topic_link, $forum_link 460 | ); 461 | } 462 | else 463 | { 464 | $edit_user_link = $this->generate_user_link($data['edit_user_id'], $data['edit_user_name']); 465 | $message = sprintf($this->language->lang('UPDATE_POST_OTHER'), 466 | $emoji, $edit_user_link, $post_link, $user_link, $topic_link, $forum_link 467 | ); 468 | } 469 | 470 | // If we allow previews and an edit reason was given, add that information in the footer 471 | $footer = null; 472 | if (isset($data['edit_reason']) && $data['edit_reason'] !== '') 473 | { 474 | $footer = $this->generate_footer_text($this->language->lang('REASON'), $data['edit_reason']); 475 | } 476 | 477 | $this->notification_service->send_discord_notification($color, $message, $footer); 478 | } 479 | 480 | /** 481 | * Sends a notification to Discord when a post is deleted. 482 | * @param $data Array of attributes for the deleted post 483 | */ 484 | private function notify_post_deleted($data) 485 | { 486 | // Constant properties for this notification type 487 | $notification_type_config_name = 'discord_notification_type_post_delete'; 488 | $color = self::COLOR_BRIGHT_RED; 489 | $emoji = self::EMOJI_DELETE; 490 | 491 | // Verify that this notification type is enabled. If not, we have nothing further to do. 492 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 493 | return; 494 | } 495 | 496 | // Construct the notification message using the post data. 497 | $user_name = $data['user_name'] !== '' ? $data['user_name'] : $this->language->lang('UNKNOWN_USER'); 498 | $user_link = $this->generate_user_link($data['user_id'], $user_name); 499 | $topic_title = $data['topic_title'] !== '' ? $data['topic_title'] : $this->language->lang('UNKNOWN_TOPIC'); 500 | $topic_link = $this->generate_topic_link($data['topic_id'], $topic_title); 501 | $forum_name = $data['forum_name'] !== '' ? $data['forum_name'] : $this->language->lang('UNKNOWN_FORUM'); 502 | $forum_link = $this->generate_forum_link($data['forum_id'], $forum_name); 503 | 504 | $message = sprintf($this->language->lang('DELETE_POST'), 505 | $emoji, $user_link, $topic_link, $forum_link 506 | ); 507 | 508 | // If there was a reason specified for the delete, include that in the message footer. 509 | $footer = null; 510 | if (is_string($data['delete_reason']) && $data['delete_reason'] !== '') 511 | { 512 | $footer = $this->generate_footer_text($this->language->lang('REASON'), $data['delete_reason']); 513 | } 514 | 515 | $this->notification_service->send_discord_notification($color, $message, $footer); 516 | } 517 | 518 | /** 519 | * Sends a notification to Discord when a post is locked. 520 | * @param $data Array of attributes for the locked post 521 | */ 522 | private function notify_post_locked($data) 523 | { 524 | // Constant properties for this notification type 525 | $notification_type_config_name = 'discord_notification_type_post_lock'; 526 | $color = self::COLOR_BRIGHT_ORANGE; 527 | $emoji = self::EMOJI_LOCK; 528 | 529 | // Verify that this notification type is enabled. If not, we have nothing further to do. 530 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 531 | return; 532 | } 533 | 534 | // Construct the notification message using the argument data 535 | $user_link = $this->generate_user_link($data['post_user_id'], $data['post_user_name']); 536 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 537 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 538 | $post_link = $this->generate_post_link($data['topic_id'], $data['post_id'], $this->language->lang('POST')); 539 | $message = sprintf($this->language->lang('LOCK_POST'), 540 | $emoji, $post_link, $user_link, $topic_link, $forum_link 541 | ); 542 | 543 | $this->notification_service->send_discord_notification($color, $message); 544 | } 545 | 546 | /** 547 | * Sends a notification to Discord when a post is unlocked. 548 | * @param $data Array of attributes for the unlocked post 549 | */ 550 | private function notify_post_unlocked($data) 551 | { 552 | // Constant properties for this notification type 553 | $notification_type_config_name = 'discord_notification_type_post_unlock'; 554 | $color = self::COLOR_BRIGHT_ORANGE; 555 | $emoji = self::EMOJI_UNLOCK; 556 | 557 | // Verify that this notification type is enabled. If not, we have nothing further to do. 558 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 559 | return; 560 | } 561 | 562 | // Construct the notification message using the argument data 563 | $user_link = $this->generate_user_link($data['post_user_id'], $data['post_user_name']); 564 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 565 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 566 | $post_link = $this->generate_post_link($data['topic_id'], $data['post_id'], $this->language->lang('POST')); 567 | $message = sprintf($this->language->lang('UNLOCK_POST'), 568 | $emoji, $post_link, $user_link, $topic_link, $forum_link 569 | ); 570 | 571 | $this->notification_service->send_discord_notification($color, $message); 572 | } 573 | 574 | /** 575 | * Sends a notification to Discord when a new topic is created. 576 | * @param $data Array of attributes for the new topic 577 | */ 578 | private function notify_topic_created($data) 579 | { 580 | // Constant properties for this notification type 581 | $notification_type_config_name = 'discord_notification_type_topic_create'; 582 | $color = self::COLOR_BRIGHT_GREEN; 583 | $emoji = self::EMOJI_CREATE; 584 | 585 | // Verify that this notification type is enabled. If not, we have nothing further to do. 586 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 587 | return; 588 | } 589 | 590 | // Construct the notification message using the argument data 591 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 592 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 593 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 594 | $message = sprintf($this->language->lang('CREATE_TOPIC'), 595 | $emoji, $user_link, $topic_link, $forum_link 596 | ); 597 | 598 | // Generates a topic preview if necessary 599 | $footer = $this->generate_footer_text($this->language->lang('PREVIEW'), $data['content']); 600 | 601 | $this->notification_service->send_discord_notification($color, $message, $footer); 602 | } 603 | 604 | /** 605 | * Sends a notification to Discord when a topic is updated. 606 | * @param $data Array of attributes for the updated topic 607 | */ 608 | private function notify_topic_updated($data) 609 | { 610 | // Constant properties for this notification type 611 | $notification_type_config_name = 'discord_notification_type_topic_update'; 612 | $color = self::COLOR_BRIGHT_BLUE; 613 | $emoji = self::EMOJI_UPDATE; 614 | 615 | // Verify that this notification type is enabled. If not, we have nothing further to do. 616 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 617 | return; 618 | } 619 | 620 | // Construct the notification message using the topic data 621 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 622 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 623 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 624 | 625 | // The notification is slightly different depending on whether the user edited their own topic, or another user made the edit 626 | $message = ''; 627 | if ($data['user_id'] == $data['edit_user_id']) 628 | { 629 | $message = sprintf($this->language->lang('UPDATE_TOPIC_SELF'), 630 | $emoji, $user_link, $topic_link, $forum_link 631 | ); 632 | } 633 | else 634 | { 635 | $edit_user_link = $this->generate_user_link($data['edit_user_id'], $data['edit_user_name']); 636 | $message = sprintf($this->language->lang('UPDATE_TOPIC_OTHER'), 637 | $emoji, $edit_user_link, $topic_link, $user_link, $forum_link 638 | ); 639 | } 640 | 641 | // If an edit reason was given, add that information in the footer 642 | $footer = null; 643 | if (isset($data['edit_reason']) && $data['edit_reason'] !== '') 644 | { 645 | $footer = $this->generate_footer_text($this->language->lang('REASON'), $data['edit_reason']); 646 | } 647 | 648 | $this->notification_service->send_discord_notification($color, $message, $footer); 649 | } 650 | 651 | /** 652 | * Sends a notification to Discord when a topic is deleted. 653 | * @param $data Array of attributes for the deleted topic 654 | */ 655 | private function notify_topic_deleted($data) 656 | { 657 | // Constant properties for this notification type 658 | $notification_type_config_name = 'discord_notification_type_topic_delete'; 659 | $color = self::COLOR_BRIGHT_RED; 660 | $emoji = self::EMOJI_DELETE; 661 | 662 | // Verify that this notification type is enabled. If not, we have nothing further to do. 663 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 664 | return; 665 | } 666 | 667 | // Construct the notification message using the argument data 668 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 669 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 670 | $message = sprintf($this->language->lang('DELETE_TOPIC'), 671 | $emoji, $user_link, $data['topic_title'], $data['topic_post_count'], $forum_link 672 | ); 673 | 674 | $this->notification_service->send_discord_notification($color, $message); 675 | } 676 | 677 | /** 678 | * Sends a notification to Discord when a topic is locked. 679 | * @param $data Array of attributes for the locked topic 680 | */ 681 | private function notify_topic_locked($data) 682 | { 683 | // Constant properties for this notification type 684 | $notification_type_config_name = 'discord_notification_type_topic_lock'; 685 | $color = self::COLOR_BRIGHT_ORANGE; 686 | $emoji = self::EMOJI_LOCK; 687 | 688 | // Verify that this notification type is enabled. If not, we have nothing further to do. 689 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 690 | return; 691 | } 692 | 693 | // Construct the notification message using the argument data 694 | $user_link = $this->generate_user_link($data['topic_user_id'], $data['topic_user_name']); 695 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 696 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 697 | $message = sprintf($this->language->lang('LOCK_TOPIC'), 698 | $emoji, $topic_link, $forum_link, $user_link 699 | ); 700 | 701 | $this->notification_service->send_discord_notification($color, $message); 702 | } 703 | 704 | /** 705 | * Sends a notification to Discord when a topic is unlocked. 706 | * @param $data Array of attributes for the unlocked topic 707 | */ 708 | private function notify_topic_unlocked($data) 709 | { 710 | // Constant properties for this notification type 711 | $notification_type_config_name = 'discord_notification_type_topic_unlock'; 712 | $color = self::COLOR_BRIGHT_ORANGE; 713 | $emoji = self::EMOJI_UNLOCK; 714 | 715 | // Verify that this notification type is enabled. If not, we have nothing further to do. 716 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 717 | return; 718 | } 719 | 720 | // Construct the notification message using the argument data 721 | $user_link = $this->generate_user_link($data['topic_user_id'], $data['topic_user_name']); 722 | $forum_link = $this->generate_forum_link($data['forum_id'], $data['forum_name']); 723 | $topic_link = $this->generate_topic_link($data['topic_id'], $data['topic_title']); 724 | $message = sprintf($this->language->lang('UNLOCK_TOPIC'), 725 | $emoji, $topic_link, $forum_link, $user_link 726 | ); 727 | 728 | $this->notification_service->send_discord_notification($color, $message); 729 | } 730 | 731 | /** 732 | * Sends a notification to Discord when a new user account is created. 733 | * @param $data Array of attributes for the new user 734 | * 735 | * Notification details include the user name and a link to the user's profile page. 736 | */ 737 | private function notify_user_created($data) 738 | { 739 | // Constant properties for this notification type 740 | $notification_type_config_name = 'discord_notification_type_user_create'; 741 | $color = self::COLOR_BRIGHT_PURPLE; 742 | $emoji = self::EMOJI_USER; 743 | 744 | // Verify that this notification type is enabled. If not, we have nothing further to do. 745 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 746 | return; 747 | } 748 | 749 | // Construct the notification message using the argument data. 750 | $user_link = $this->generate_user_link($data['user_id'], $data['user_name']); 751 | $message = sprintf($this->language->lang('CREATE_USER'), 752 | $emoji, $user_link 753 | ); 754 | 755 | $this->notification_service->send_discord_notification($color, $message); 756 | } 757 | 758 | /** 759 | * Sends a notification to Discord when one or more user accounts are deleted. 760 | * @param $data Array of attributes for the deleted users (id => username) 761 | * 762 | * The notification lists the usernames of those deleted but provides no links or additional information such as deletion reason. 763 | */ 764 | private function notify_users_deleted($data) 765 | { 766 | // Constant properties for this notification type 767 | $notification_type_config_name = 'discord_notification_type_user_delete'; 768 | $color = self::COLOR_BRIGHT_PURPLE; 769 | $emoji = self::EMOJI_USER; 770 | 771 | // Verify that this notification type is enabled. If not, we have nothing further to do. 772 | if ($this->notification_service->is_notification_type_enabled($notification_type_config_name) == false) { 773 | return; 774 | } 775 | 776 | // Construct the notification message using the argument data. 777 | $message = ''; 778 | // The message format is slightly different depending on how many users were deleted. 779 | if (count($data) == 1) 780 | { 781 | $user_name = array_pop($data); 782 | $message = sprintf($this->language->lang('DELETE_USER'), 783 | $emoji, $user_name 784 | ); 785 | } 786 | else { 787 | $deleted_users_text = ''; 788 | $and = $this->language->lang('AND'); 789 | $comma = $this->language->lang('CONJ'); 790 | if (count($data) == 2) 791 | { 792 | $deleted_users_text = array_pop($data) . " $and " . array_pop($data); 793 | } 794 | elseif (count($data) == 3) 795 | { 796 | $deleted_users_text = array_pop($data) . "$comma " . array_pop($data) . "$comma $and " . array_pop($data); 797 | } 798 | // If more than three users were deleted, we display three user names and then the number of additional deletions 799 | elseif (count($data) > 3) 800 | { 801 | $deleted_users_text = array_pop($data) . "$comma " . array_pop($data) . "$comma " . array_pop($data) . "$comma $and " . count($data); 802 | // Singular vs plural case check 803 | if (count($data) == 1) 804 | { 805 | $deleted_users_text .= " " . $this->language->lang('OTHER'); 806 | } 807 | else 808 | { 809 | $deleted_users_text .= " " . $this->language->lang('OTHERS'); 810 | } 811 | } 812 | 813 | $message = sprintf($this->language->lang('DELETE_MULTI_USER'), 814 | $emoji, $deleted_users_text 815 | ); 816 | } 817 | 818 | $this->notification_service->send_discord_notification($color, $message); 819 | } 820 | 821 | // ---------------------------------------------------------------------------- 822 | // ----------------------------- Helper Functions ----------------------------- 823 | // ---------------------------------------------------------------------------- 824 | 825 | /** 826 | * The Discord webhook api does not accept urlencoded text. This function replaces problematic characters. 827 | * @param $url 828 | * @return Formatted URL text 829 | */ 830 | private function reformat_link_url($url) 831 | { 832 | $url = str_replace(" ", "%20", $url); 833 | $url = str_replace("(", "%28", $url); 834 | $url = str_replace(")", "%29", $url); 835 | return $url; 836 | } 837 | 838 | /** 839 | * Discord link text must be surrounded by []. This function replaces problematic characters 840 | * @param $text Text link 841 | * @return Formatted link-safe text 842 | */ 843 | private function reformat_link_text($text) 844 | { 845 | $text = str_replace("[", "(", $text); 846 | $text = str_replace("]", ")", $text); 847 | return $text; 848 | } 849 | 850 | /** 851 | * Removes all HTML and BBcode formatting tags from a string 852 | * @param $text Text link 853 | * @return Formatted text 854 | * 855 | * Note that there is some risk here of the text not coming out exactly as we may like. The user text 856 | * may include characters that look like pseudo-HTML and get picked up by the regex used. 857 | */ 858 | private function remove_formatting($text) 859 | { 860 | $text = strip_tags($text); 861 | $text = preg_replace('|[[\/\!]*?[^\[\]]*?]|si', '', $text); 862 | return $text; 863 | } 864 | 865 | /** 866 | * Given the ID of a forum, returns text that contains a link to view the forum 867 | * @param $topic_id The ID of the topic 868 | * @param $post_id The ID of the post 869 | * @param $text The text to display for the post link 870 | * @return Text formatted in the notation that Discord would interpret. 871 | */ 872 | private function generate_forum_link($forum_id, $text) 873 | { 874 | $url = generate_board_url() . '/viewforum.php?f=' . $forum_id; 875 | $url = $this->reformat_link_url($url); 876 | $text = $this->reformat_link_text($text); 877 | return sprintf('[%s](%s)', $text, $url); 878 | } 879 | 880 | /** 881 | * Given the ID of a valid post, returns text that contains the post title with a link to the post. 882 | * @param $topic_id The ID of the topic 883 | * @param $post_id The ID of the post 884 | * @param $text The text to display for the post link 885 | * @return Text formatted in the notation that Discord would interpret. 886 | */ 887 | private function generate_post_link($topic_id, $post_id, $text) 888 | { 889 | $url = generate_board_url() . '/viewtopic.php?t=' . $topic_id . '&p=' . $post_id . '#p' . $post_id; 890 | $url = $this->reformat_link_url($url); 891 | $text = $this->reformat_link_text($text); 892 | return sprintf('[%s](%s)', $text, $url); 893 | } 894 | 895 | /** 896 | * Given the ID of a valid topic, returns text that contains the topic title with a link to the topic. 897 | * @param $topic_id The ID of the topic 898 | * @param $text The text to display for the topic link 899 | * @return Text formatted in the notation that Discord would interpret. 900 | */ 901 | private function generate_topic_link($topic_id, $text) 902 | { 903 | $url = generate_board_url() . '/viewtopic.php?t=' . $topic_id; 904 | $url = $this->reformat_link_url($url); 905 | $text = $this->reformat_link_text($text); 906 | return sprintf('[%s](%s)', $text, $url); 907 | } 908 | 909 | /** 910 | * Given the ID of a valid user, returns text that contains the user name with a link to their user profile. 911 | * @param $user_id The ID of the user 912 | * @param $text The text to display for the user link 913 | * @return Text formatted in the notation that Discord would interpret. 914 | */ 915 | private function generate_user_link($user_id, $text) 916 | { 917 | $url = generate_board_url() . '/memberlist.php?mode=viewprofile&u=' . $user_id; 918 | $url = $this->reformat_link_url($url); 919 | $text = $this->reformat_link_text($text); 920 | return sprintf('[%s](%s)', $text, $url); 921 | } 922 | 923 | /** 924 | * Formats and prepares text to be placed in the footer of a notification message. 925 | * @param $prepend_text Text to add before the content 926 | * @param $content The raw text to place in the footer 927 | * @return A string meeting the configuration requirements for a footer, or NULL if a footer should not be generated at all. 928 | */ 929 | private function generate_footer_text($prepend_text, $content) 930 | { 931 | $preview_length = $this->notification_service->get_post_preview_length(); 932 | if ($preview_length == 0) 933 | { 934 | return null; 935 | } 936 | 937 | $footer = $this->remove_formatting($content); 938 | // Truncate the content if it is too long and add '...' for the last three characters. The preview length will 939 | // always be at least 10 characters so we don't need to worry about really short strings. 940 | if (strlen($footer) > $preview_length) 941 | { 942 | $footer = substr($footer, 0, $preview_length - 3) . '...'; 943 | } 944 | 945 | // Prepend text to the footer so that it's clear what content we are sharing in the footer 946 | $footer = $prepend_text . $footer; 947 | return $footer; 948 | } 949 | } 950 | -------------------------------------------------------------------------------- /language/en/acp_discord_notifications.php: -------------------------------------------------------------------------------- 1 | 'These settings allow for various forum events to send notification messages to a Discord server.', 24 | 25 | 'DN_MAIN_SETTINGS' => 'Main Settings', 26 | 'DN_MASTER_ENABLE' => 'Enable Discord Notifications', 27 | 'DN_WEBHOOK_URL' => 'Discord Webhook URL', 28 | 'DN_WEBHOOK_DESCRIPTION' => 'The URL of the webhook generated by the Discord server. See this article to learn how to create a new webhook.', 29 | 'DN_POST_PREVIEW_LENGTH' => 'Post Preview Length', 30 | 'DN_POST_PREVIEW_DESCRIPTION' => 'Specify the number of characters to display in a post preview (10 - 2000). A zero value disables post preview.', 31 | 'DN_TEST_MESSAGE' => 'Test Message', 32 | 'DN_TEST_MESSAGE_TEXT' => 'This is a test: Hello Discord!', 33 | 'DN_TEST_DESCRIPTION' => 'A message to send to Discord to verify that the connection with phpBB is functioning.', 34 | 'DN_SEND_TEST' => 'Send Test Message', 35 | 'DN_SEND_TEST_DESCRIPTION' => 'Sends the contents of the test message to the Discord webhook. Use this to verify that your webhook is properly functioning.', 36 | 37 | 'DN_TYPE_SETTINGS' => 'Notification Types', 38 | 'DN_TYPE_DESCRIPTION' => 'Select which types of notifications should generate messages that get sent to Discord', 39 | 'DN_POST_CREATE' => 'Post created', 40 | 'DN_POST_UPDATE' => 'Post updated', 41 | 'DN_POST_DELETE' => 'Post deleted', 42 | 'DN_POST_LOCK' => 'Post locked', 43 | 'DN_POST_UNLOCK' => 'Post unlocked', 44 | 'DN_TOPIC_CREATE' => 'Topic created', 45 | 'DN_TOPIC_UPDATE' => 'Topic updated', 46 | 'DN_TOPIC_DELETE' => 'Topic deleted', 47 | 'DN_TOPIC_LOCK' => 'Topic locked', 48 | 'DN_TOPIC_UNLOCK' => 'Topic unlocked', 49 | 'DN_USER_CREATE' => 'User created', 50 | 'DN_USER_DELETE' => 'User deleted', 51 | 52 | 'DN_FORUM_SETTINGS' => 'Notification Forums', 53 | 'DN_FORUM_DESCRIPTION' => 'Select which forums should generate notifications for forum-specific events, such as those related to posts and topics', 54 | 55 | // Messages that appear after a user tries to send a test message 56 | 'DN_TEST_SUCCESS' => 'Success! Check your Discord server and you should see the test message displayed there.', 57 | 'DN_TEST_FAILURE' => 'There was a problem with sending the test message. Check your webhook URL and verify your server meets the extension requirements.', 58 | 'DN_TEST_BAD_MESSAGE' => 'Please enter at least one character for the test message', 59 | 'DN_TEST_BAD_WEBHOOK' => 'The text entered for the webhook URL is not a valid URL. Please check this setting and try again.', 60 | 61 | // Success/Failure messages that can be generated once the user saves 62 | 'DN_SETTINGS_SAVED' => 'Discord Notification settings modified successfully.', 63 | 'DN_MASTER_WEBHOOK_REQUIRED' => 'A valid Discord webhook URL is required to enable notifications.', 64 | 'DN_WEBHOOK_URL_INVALID' => 'Discord webhook URL must be a full and valid URL.', 65 | 'DN_POST_PREVIEW_INVALID' => 'Post preview length must be a number between 10 and 2000, or 0 to disable previews.', 66 | )); 67 | -------------------------------------------------------------------------------- /language/en/discord_notification_messages.php: -------------------------------------------------------------------------------- 1 | '%s %s created a new %s in the topic %s located in the forum %s', // %s == emoji, user, post, topic, forum 29 | 'UPDATE_POST_SELF' => '%s %s edited their %s in the topic %s located in the forum %s', // %s == emoji, user, post, topic, forum 30 | 'UPDATE_POST_OTHER' => '%s %s edited the %s written by %s in the topic %s located in the forum %s', // %s == emoji, edit user, post, user, topic, forum 31 | 'DELETE_POST' => '%s Deleted post by user %s in the topic %s located in the forum %s', // %s == emoji, user, topic, forum 32 | 'LOCK_POST' => '%s The %s written by user %s in the topic titled %s in the %s forum has been locked', // %s == emoji, post, user, topic, forum 33 | 'UNLOCK_POST' => '%s The %s written by user %s in the topic titled %s in the %s forum has been unlocked', // %s == emoji, post, user, topic, forum 34 | 35 | // Topic Notifications 36 | 'CREATE_TOPIC' => '%s %s created a new topic titled %s in the %s forum', // %s == emoji, user, topic, forum 37 | 'UPDATE_TOPIC_SELF' => '%s %s edited their topic %s located in the forum %s', // %s == emoji, user, topic, forum 38 | 'UPDATE_TOPIC_OTHER' => '%s %s edited the the topic %s written by %s located in the forum %s', // %s == emoji, edit user, topic, user, forum 39 | 'DELETE_TOPIC' => '%s Deleted the topic started by user %s titled \'%s\' containing %d posts in the forum %s', // %s/d == emoji, user, topic title, post count, forum 40 | 'LOCK_TOPIC' => '%s The topic titled %s in the %s forum started by user %s has been locked', // %s == emoji, topic, forum, user 41 | 'UNLOCK_TOPIC' => '%s The topic titled %s in the %s forum started by user %s has been unlocked', // %s == emoji, topic, forum, user 42 | 43 | // User Notifications 44 | 'CREATE_USER' => '%s New user account created for %s', // %s == emoji, user 45 | 'DELETE_USER' => '%s Deleted account for user %s', // %s == emoji, user 46 | 'DELETE_MULTI_USER' => '%s Deleted accounts for users %s', // %s == emoji, list of users 47 | 48 | // Additional Text 49 | 'PREVIEW' => 'Preview: ', 50 | 'REASON' => 'Reason: ', 51 | 'POST' => 'post', 52 | 'AND' => 'and', 53 | 'CONJ' => ',', // short for "conjunction character" 54 | 'OTHER' => 'other', 55 | 'OTHERS' => 'others', 56 | 'UNKNOWN_USER' => '{user}', 57 | 'UNKNOWN_FORUM' => '{forum}', 58 | 'UNKNOWN_TOPIC' => '{topic}', 59 | )); 60 | -------------------------------------------------------------------------------- /language/en/info_acp_discord_notifications.php: -------------------------------------------------------------------------------- 1 | 'Discord Notifications', 22 | 'ACP_DISCORD_NOTIFICATIONS_TITLE' => 'Discord Notification Settings', 23 | 24 | // ACP Logs 25 | 'ACP_DISCORD_NOTIFICATIONS_LOG_UPDATE' => 'Modified Discord notification settings', 26 | )); 27 | -------------------------------------------------------------------------------- /language/fr/acp_discord_notifications.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0-only) 9 | * 10 | * This file contains the language strings for the ACP settings page for this extension. 11 | */ 12 | 13 | /** 14 | * DO NOT CHANGE 15 | */ 16 | if (!defined('IN_PHPBB')) 17 | { 18 | exit; 19 | } 20 | 21 | if (empty($lang) || !is_array($lang)) 22 | { 23 | $lang = array(); 24 | } 25 | 26 | // DEVELOPERS PLEASE NOTE 27 | // 28 | // All language files should use UTF-8 as their encoding and the files must not contain a BOM. 29 | // 30 | // Placeholders can now contain order information, e.g. instead of 31 | // 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows 32 | // translators to re-order the output of data while ensuring it remains correct 33 | // 34 | // You do not need this where single placeholders are used, e.g. 'Message %d' is fine 35 | // equally where a string contains only two placeholders which are used to wrap text 36 | // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine 37 | // 38 | // Some characters you may want to copy&paste: 39 | // ’ « » “ ” … 40 | // 41 | 42 | $lang = array_merge($lang, array( 43 | // ACP Extension Settings Page 44 | 'DN_ACP_DESCRIPTION' => 'Depuis cette page il est possible de définir les paramètres permettant à différents évènements du forum d’être envoyés comme notification sur un serveur Discord.', 45 | 46 | 'DN_MAIN_SETTINGS' => 'Paramètres généraux', 47 | 'DN_MASTER_ENABLE' => 'Activer les notifications Discord', 48 | 'DN_WEBHOOK_URL' => 'URL Webhook de Discord', 49 | 'DN_WEBHOOK_DESCRIPTION' => 'Permet de saisir l’adresse URL Webhook du serveur Discord généré par le serveur Discord. Voir cet article pour apprendre comment créer un nouveau Webhook.', 50 | 'DN_POST_PREVIEW_LENGTH' => 'Longueur de l’aperçu des messages', 51 | 'DN_POST_PREVIEW_DESCRIPTION' => 'Permet de saisir le nombre de caractères à afficher dans l’aperçu des messages (10 - 2000). Définir sur la valeur 0 pour désactiver l’aperçu des messages.', 52 | 'DN_TEST_MESSAGE' => 'Message de test', 53 | 'DN_TEST_MESSAGE_TEXT' => 'Texte du message de test : Hello Discord!', 54 | 'DN_TEST_DESCRIPTION' => 'Permet d’envoyer un message sur le serveur Discord afin de vérifier que la connexion avec phpBB est fonctionnelle.', 55 | 'DN_SEND_TEST' => 'Envoyer un message de test', 56 | 'DN_SEND_TEST_DESCRIPTION' => 'Permet d’envoyer le contenu du message de test au Webhook du serveur Discord. À utiliser pour vérifier que le Webhook est fonctionnel.', 57 | 58 | 'DN_TYPE_SETTINGS' => 'Types de notification', 59 | 'DN_TYPE_DESCRIPTION' => 'Permet de sélectionner quels types de notification devraient être générés sous forme de messages envoyés sur le serveur Discord.', 60 | 'DN_POST_CREATE' => 'Message créé', 61 | 'DN_POST_UPDATE' => 'Message modifié', 62 | 'DN_POST_DELETE' => 'Message supprimé', 63 | 'DN_POST_LOCK' => 'Message verrouillé', 64 | 'DN_POST_UNLOCK' => 'Message déverrouillé', 65 | 'DN_TOPIC_CREATE' => 'Sujet créé', 66 | 'DN_TOPIC_UPDATE' => 'Sujet modifié', 67 | 'DN_TOPIC_DELETE' => 'Sujet supprimé', 68 | 'DN_TOPIC_LOCK' => 'Sujet verrouillé', 69 | 'DN_TOPIC_UNLOCK' => 'Sujet déverrouillé', 70 | 'DN_USER_CREATE' => 'Compte utilisateur créé', 71 | 'DN_USER_DELETE' => 'Compte utilisateur supprimé', 72 | 73 | 'DN_FORUM_SETTINGS' => 'Forums concernés par les notifications', 74 | 'DN_FORUM_DESCRIPTION' => 'Permet de sélectionner les forums pour lesquels des évènements seront notifiés sur le serveur Discord, tels que ceux liés aux messages et aux sujets.', 75 | 76 | // Messages that appear after a user tries to send a test message 77 | 'DN_TEST_SUCCESS' => 'Test effectué avec succès ! Merci de vérifier sur le serveur Discord que le message de test apparait.', 78 | 'DN_TEST_FAILURE' => 'Un problème a été rencontré lors de l’envoi du message de test. Merci de vérifier l’adresse URL du Webhook du serveur Discord.', 79 | 'DN_TEST_BAD_MESSAGE' => 'Merci de saisir au moins un caractère pour le message de test.', 80 | 'DN_TEST_BAD_WEBHOOK' => 'Le texte saisi pour l’adresse URL Webhook du serveur Discord est incorrecte. Merci de vérifier ce paramètre puis d’essayer l’envoi à nouveau.', 81 | 82 | // Success/Failure messages that can be generated once the user saves 83 | 'DN_SETTINGS_SAVED' => 'Les paramètres de notification Discord ont été modifiés avec succès !', 84 | 'DN_MASTER_WEBHOOK_REQUIRED' => 'Une adresse URL Webhook du serveur Discord est requise pour activer les notifications.', 85 | 'DN_WEBHOOK_URL_INVALID' => 'L’adresse URL Webhook du serveur Discord doit être complète et correspondre à une adresse URL valide.', 86 | 'DN_POST_PREVIEW_INVALID' => 'La longueur de l’aperçu des messages doit être comprise entre 10 et 2000 ou définie sur 0 pour la désactivation de l’aperçu.', 87 | )); 88 | -------------------------------------------------------------------------------- /language/fr/discord_notification_messages.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0-only) 9 | * 10 | * This file contains the language strings for the ACP settings page for this extension. 11 | */ 12 | 13 | /** 14 | * DO NOT CHANGE 15 | */ 16 | if (!defined('IN_PHPBB')) 17 | { 18 | exit; 19 | } 20 | 21 | if (empty($lang) || !is_array($lang)) 22 | { 23 | $lang = array(); 24 | } 25 | 26 | // DEVELOPERS PLEASE NOTE 27 | // 28 | // All language files should use UTF-8 as their encoding and the files must not contain a BOM. 29 | // 30 | // Placeholders can now contain order information, e.g. instead of 31 | // 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows 32 | // translators to re-order the output of data while ensuring it remains correct 33 | // 34 | // You do not need this where single placeholders are used, e.g. 'Message %d' is fine 35 | // equally where a string contains only two placeholders which are used to wrap text 36 | // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine 37 | // 38 | // Some characters you may want to copy&paste: 39 | // ’ « » “ ” … 40 | // 41 | 42 | // These messages are used by the event/notification_event_listener class. The notifications naturally generate dynamic content, 43 | // and this is done using formatted strings passed to sprintf(). Each notification message below has a comment indicating what each 44 | // %s string argument should represent (typically this is a hyperlink with text describing a user, topic, post, or forum). 45 | // Note that the order of what gets populated in the %s arguments is unfortunately fixed, meaning that this could make good 46 | // translations into other difficult. 47 | $lang = array_merge($lang, array( 48 | // Post Notifications 49 | 'CREATE_POST' => '%s %s a publié un %s dans le sujet « %s » du forum « %s ».', // %s == emoji, user, post, topic, forum 50 | 'UPDATE_POST_SELF' => '%s %s a modifié son « %s dans le sujet « %s » du forum « %s ».', // %s == emoji, user, post, topic, forum 51 | 'UPDATE_POST_OTHER' => '%s %s a modifié le message « %s » publié par « %s » dans le sujet « %s » du forum « %s ».', // %s == emoji, edit user, post, user, topic, forum 52 | 'DELETE_POST' => '%s Message supprimé de l’auteur « %s » dans le sujet « %s » du forum « %s ».', // %s == emoji, user, topic, forum 53 | 'LOCK_POST' => '%s Le message « %s » publié par le membre « %s » dans le sujet intitulé « %s » du forum « %s » a été verrouillé.', // %s == emoji, post, user, topic, forum 54 | 'UNLOCK_POST' => '%s Le message « %s » publié par le membre « %s » dans le sujet intitulé « %s » du forum « %s » a été déverrouillé.', // %s == emoji, post, user, topic, forum 55 | 56 | // Topic Notifications 57 | 'CREATE_TOPIC' => '%s %s a publié un nouveau sujet intitulé « %s » dans le forum « %s ».', // %s == emoji, user, topic, forum 58 | 'UPDATE_TOPIC_SELF' => '%s %s a modifié son sujet « %s » dans le forum « %s ».', // %s == emoji, user, topic, forum 59 | 'UPDATE_TOPIC_OTHER' => '%s %s a modifié le sujet « %s », dont l’auteur est « %s » dans le forum « %s ».', // %s == emoji, edit user, topic, user, forum 60 | 'DELETE_TOPIC' => '%s Sujet supprimé de l’auteur « %s », intitulé « %s », contenant %d messages dans le forum « %s ».', // %s/d == emoji, user, topic title, post count, forum 61 | 'LOCK_TOPIC' => '%s Le sujet intitulé « %s » dans le forum « %s » et dont l’auteur est « %s » a été verrouillé.', // %s == emoji, topic, forum, user 62 | 'UNLOCK_TOPIC' => '%s Le sujet intitulé « %s » dans le forum « %s » et dont l’auteur est « %s » a été déverrouillé.', // %s == emoji, topic, forum, user 63 | 64 | // User Notifications 65 | 'CREATE_USER' => '%s Nouveau compte utilisateur créé pour le membre « %s ».', // %s == emoji, user 66 | 'DELETE_USER' => '%s Compte utilisateur supprimé pour le membre « %s ».', // %s == emoji, user 67 | 'DELETE_MULTI_USER' => '%s Comptes utilisateurs supprimés pour les membres : « %s ».', // %s == emoji, list of users 68 | 69 | // Additional Text 70 | 'PREVIEW' => 'Aperçu : ', 71 | 'REASON' => 'Raison : ', 72 | 'POST' => 'message', 73 | 'AND' => 'et', 74 | 'CONJ' => ',', // short for "conjunction character" 75 | 'OTHER' => 'autre', 76 | 'OTHERS' => 'autres', 77 | 'UNKNOWN_USER' => '{user}', 78 | 'UNKNOWN_FORUM' => '{forum}', 79 | 'UNKNOWN_TOPIC' => '{topic}', 80 | )); 81 | -------------------------------------------------------------------------------- /language/fr/info_acp_discord_notifications.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0-only) 9 | * 10 | * This file contains the language strings for the ACP settings page for this extension. 11 | */ 12 | 13 | /** 14 | * DO NOT CHANGE 15 | */ 16 | if (!defined('IN_PHPBB')) 17 | { 18 | exit; 19 | } 20 | 21 | if (empty($lang) || !is_array($lang)) 22 | { 23 | $lang = array(); 24 | } 25 | 26 | // DEVELOPERS PLEASE NOTE 27 | // 28 | // All language files should use UTF-8 as their encoding and the files must not contain a BOM. 29 | // 30 | // Placeholders can now contain order information, e.g. instead of 31 | // 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows 32 | // translators to re-order the output of data while ensuring it remains correct 33 | // 34 | // You do not need this where single placeholders are used, e.g. 'Message %d' is fine 35 | // equally where a string contains only two placeholders which are used to wrap text 36 | // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine 37 | // 38 | // Some characters you may want to copy&paste: 39 | // ’ « » “ ” … 40 | // 41 | 42 | $lang = array_merge($lang, array( 43 | // ACP Module 44 | 'ACP_DISCORD_NOTIFICATIONS' => 'Notifications Discord', 45 | 'ACP_DISCORD_NOTIFICATIONS_TITLE' => 'Paramètres', 46 | 47 | // ACP Logs 48 | 'ACP_DISCORD_NOTIFICATIONS_LOG_UPDATE' => 'Paramètres des notifications Discord modifiées', 49 | )); 50 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, 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 Library 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 | -------------------------------------------------------------------------------- /migrations/extension_installation.php: -------------------------------------------------------------------------------- 1 | config['discord_notifications_enabled']); 29 | } 30 | 31 | /** 32 | * Add the discord notification enabled column to the forums table. 33 | * This setting determines whether or not activity on a specific forum will generate a 34 | * notification transmitted to Discord. 35 | * 36 | * @return array Array of table schema 37 | * @access public 38 | */ 39 | public function update_schema() 40 | { 41 | return array( 42 | 'add_columns' => array( 43 | $this->table_prefix . 'forums' => array( 44 | 'discord_notifications_enabled' => array('BOOL', 0), 45 | ), 46 | ), 47 | ); 48 | } 49 | 50 | /** 51 | * Drop the discord notification enabled column from the users table. 52 | * 53 | * @return array Array of table schema 54 | * @access public 55 | */ 56 | public function revert_schema() 57 | { 58 | return array( 59 | 'drop_columns' => array( 60 | $this->table_prefix . 'forums' => array( 61 | 'discord_notifications_enabled', 62 | ), 63 | ), 64 | ); 65 | } 66 | 67 | /** 68 | * Add Discord notification data to the database. 69 | * Note: these changes will automatically get reverted by phpbb if the extension is uninstalled. 70 | * Hence the reason why no corresponding revert_data() function exists. 71 | * 72 | * @return array Array of table data to set 73 | * @access public 74 | */ 75 | public function update_data() 76 | { 77 | return array( 78 | // The "master switch" that enables notifications to be sent. This can only be set to true if the webhook URL is also valid 79 | array('config.add', array('discord_notifications_enabled', 0)), 80 | // The webhook generated by Discord to send the notifications to. Must be a valid URL. 81 | array('config.add', array('discord_notifications_webhook_url', '')), 82 | // The maximum number of characters permitted in a discord notification message 83 | array('config.add', array('discord_notifications_post_preview_length', 200)), 84 | 85 | // These configurations represent the various types of notifications that can be sent, which can be individually enabled or disabled. 86 | // Upon installation, every notification type is enabled by default. 87 | 88 | // Post notifications 89 | array('config.add', array('discord_notification_type_post_create', 1)), 90 | array('config.add', array('discord_notification_type_post_update', 1)), 91 | array('config.add', array('discord_notification_type_post_delete', 1)), 92 | array('config.add', array('discord_notification_type_post_lock', 1)), 93 | array('config.add', array('discord_notification_type_post_unlock', 1)), 94 | 95 | // Topic notifications 96 | array('config.add', array('discord_notification_type_topic_create', 1)), 97 | array('config.add', array('discord_notification_type_topic_update', 1)), 98 | array('config.add', array('discord_notification_type_topic_delete', 1)), 99 | array('config.add', array('discord_notification_type_topic_lock', 1)), 100 | array('config.add', array('discord_notification_type_topic_unlock', 1)), 101 | 102 | // User notifications 103 | array('config.add', array('discord_notification_type_user_create', 1)), 104 | array('config.add', array('discord_notification_type_user_delete', 1)), 105 | 106 | // Standard ACP module data 107 | array('module.add', array( 108 | 'acp', 109 | 'ACP_CAT_DOT_MODS', 110 | 'ACP_DISCORD_NOTIFICATIONS' 111 | )), 112 | array('module.add', array( 113 | 'acp', 114 | 'ACP_DISCORD_NOTIFICATIONS', 115 | array( 116 | 'module_basename' => '\roots\discordnotifications\acp\discord_notifications_module', 117 | 'modes' => array('settings'), 118 | ), 119 | )), 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /notification_service.php: -------------------------------------------------------------------------------- 1 | config = $config; 43 | $this->db = $db; 44 | } 45 | 46 | /** 47 | * Check whether notifications are enabled for a certain type 48 | * @param $notification_type The name of the notification type to check 49 | * @return False if the global notification setting is disabled or this notification type is disabled 50 | */ 51 | public function is_notification_type_enabled($notification_type) 52 | { 53 | // Also check the global extension enabled setting. We don't generate any notifications if this is disabled 54 | if ($this->config['discord_notifications_enabled'] == 1 && $this->config[$notification_type] == 1) 55 | { 56 | return true; 57 | } 58 | 59 | return false; 60 | } 61 | 62 | /** 63 | * Check whether notifications that occur on a specific forum should be generated 64 | * @param $forum_id The ID of the forum to check 65 | * @return False if the global notification setting is disabled, notifications are disabled for the forum, or no forum exists with this ID 66 | */ 67 | public function is_notification_forum_enabled($forum_id) 68 | { 69 | if (is_numeric($forum_id) == false) 70 | { 71 | return false; 72 | } 73 | 74 | if ($this->config['discord_notifications_enabled'] == 0) 75 | { 76 | return false; 77 | } 78 | 79 | // Query the forum table where forum notification settings are stored 80 | $sql = "SELECT discord_notifications_enabled FROM " . FORUMS_TABLE . " WHERE forum_id = $forum_id"; 81 | $result = $this->db->sql_query($sql); 82 | $data = $this->db->sql_fetchrow($result); 83 | $enabled = $data['discord_notifications_enabled'] == 1 ? true : false; 84 | $this->db->sql_freeresult($result); 85 | 86 | return $enabled; 87 | } 88 | 89 | /** 90 | * Retrieve the value for the ACP settings configuration related to post preview length 91 | * @return The number of characters to display in the post preview. A zero value indicates that no preview should be displayed 92 | */ 93 | public function get_post_preview_length() 94 | { 95 | return $this->config['discord_notifications_post_preview_length']; 96 | } 97 | 98 | /** 99 | * Retrieves the name of a forum from the database when given an ID 100 | * @param $forum_id The ID of the forum to query 101 | * @return The name of the forum, or NULL if not found 102 | */ 103 | public function query_forum_name($forum_id) 104 | { 105 | if (is_numeric($forum_id) == false) 106 | { 107 | return null; 108 | } 109 | 110 | $sql = "SELECT forum_name from " . FORUMS_TABLE . " WHERE forum_id = $forum_id"; 111 | $result = $this->db->sql_query($sql); 112 | $data = $this->db->sql_fetchrow($result); 113 | $name = $data['forum_name']; 114 | $this->db->sql_freeresult($result); 115 | return $name; 116 | } 117 | 118 | /** 119 | * Retrieves the subject of a post from the database when given an ID 120 | * @param $post_id The ID of the post to query 121 | * @return The subject of the post, or NULL if not found 122 | */ 123 | public function query_post_subject($post_id) 124 | { 125 | if (is_numeric($post_id) == false) 126 | { 127 | return null; 128 | } 129 | 130 | $sql = "SELECT post_subject from " . POSTS_TABLE . " WHERE post_id = $post_id"; 131 | $result = $this->db->sql_query($sql); 132 | $data = $this->db->sql_fetchrow($result); 133 | $subject = $data['post_subject']; 134 | $this->db->sql_freeresult($result); 135 | return $subject; 136 | } 137 | 138 | /** 139 | * Retrieves the title of a topic from the database when given an ID 140 | * @param $topic_id The ID of the topic to query 141 | * @return The name of the topic, or NULL if not found 142 | */ 143 | public function query_topic_title($topic_id) 144 | { 145 | if (is_numeric($topic_id) == false) 146 | { 147 | return null; 148 | } 149 | 150 | $sql = "SELECT topic_title from " . TOPICS_TABLE . " WHERE topic_id = $topic_id"; 151 | $result = $this->db->sql_query($sql); 152 | $data = $this->db->sql_fetchrow($result); 153 | $title = $data['topic_title']; 154 | $this->db->sql_freeresult($result); 155 | return $title; 156 | } 157 | 158 | /** 159 | * Runs a query to fetch useful data about a specific forum topic. The return data includes information on the first poster, number of posts, 160 | * which forum contains the topic, and more. 161 | * @param $topic_id The ID of the topic to query 162 | * @return Array containing data about the topic and the forum it is contained in 163 | */ 164 | public function query_topic_details($topic_id) 165 | { 166 | if (is_numeric($topic_id) == false) 167 | { 168 | return array(); 169 | } 170 | 171 | $topic_table = TOPICS_TABLE; 172 | $forum_table = FORUMS_TABLE; 173 | $sql = "SELECT 174 | f.forum_id, f.forum_name, 175 | t.topic_id, t.topic_title, t.topic_poster, t.topic_first_post_id, t.topic_first_poster_name, t.topic_posts_approved, t.topic_visibility 176 | FROM 177 | $forum_table f, $topic_table t 178 | WHERE 179 | t.forum_id = f.forum_id and t.topic_id = $topic_id"; 180 | $result = $this->db->sql_query($sql); 181 | $data = $this->db->sql_fetchrow($result); 182 | $this->db->sql_freeresult($result); 183 | 184 | return $data; 185 | } 186 | 187 | /** 188 | * Retrieves the name of a user from the database when given an ID 189 | * @param $user_id The ID of the user to query 190 | * @return The name of the user, or NULL if not found 191 | */ 192 | public function query_user_name($user_id) 193 | { 194 | if (is_numeric($user_id) == false) 195 | { 196 | return null; 197 | } 198 | 199 | $sql = "SELECT username from " . USERS_TABLE . " WHERE user_id = $user_id"; 200 | $result = $this->db->sql_query($sql); 201 | $data = $this->db->sql_fetchrow($result); 202 | $name = $data['username']; 203 | $this->db->sql_freeresult($result); 204 | return $name; 205 | } 206 | 207 | /** 208 | * Sends a notification message to Discord. This function checks the master switch configuration for the extension, but does 209 | * no further checks. The caller is responsible for performing full validation of the notification prior to calling this function. 210 | * @param $color The color to use in the notification (decimal value of a hexadecimal RGB code) 211 | * @param $message The message text to send. 212 | * @param $footer Text to place in the footer of the message. Optional. 213 | */ 214 | public function send_discord_notification($color, $message, $footer = NULL) 215 | { 216 | if ($this->config['discord_notifications_enabled'] == 0 || isset($message) == false) 217 | { 218 | return; 219 | } 220 | 221 | // Note that the value stored in the config table will always be a valid URL when discord_notifications_enabled is set 222 | $discord_webhook_url = $this->config['discord_notifications_webhook_url']; 223 | 224 | $this->execute_discord_webhook($discord_webhook_url, $color, $message, $footer); 225 | } 226 | 227 | /** 228 | * Sends a message to Discord, disregarding any configurations that are currently set. This method is primarily used by users 229 | * to test their notifications from the ACP. 230 | * @param $discord_webhook_url The URL of the Discord webhook to transmit the message to. If this is an invalid URL, no message will be sent. 231 | * @param $message The message text to send. Must be a non-empty string. 232 | * @return Boolean indicating whether the message transmission resulted in success or failure. 233 | */ 234 | public function force_send_discord_notification($discord_webhook_url, $message) 235 | { 236 | if (!filter_var($discord_webhook_url, FILTER_VALIDATE_URL) || is_string($message) == false || $message == '') 237 | { 238 | return false; 239 | } 240 | 241 | return $this->execute_discord_webhook($discord_webhook_url, self::DEFAULT_COLOR, $message, NULL); 242 | } 243 | 244 | /** 245 | * Helper function that performs the message transmission. This method checks the inputs to prevent any problematic characters in 246 | * strings. Note that this function checks that the message and footer do not exceed the maximum allowable limits by the Discord 247 | * API, but it does -not- check configuration settings such as the post_preview_length. The code invoking this method is responsible 248 | * for checking those settings. 249 | * 250 | * @param $discord_webhook_url The URL of the Discord webhook to transmit the message to. 251 | * @param $color Color to set for the message. Should be a positive non-zero integer representing a hex color code. 252 | * @param $message The message text to send. Must be a non-empty string. 253 | * @param $footer The text to place in the footer. Optional. Must be a non-empty string. 254 | * @return Boolean indicating whether the message transmission resulted in success or failure. 255 | * @see https://discordapp.com/developers/docs/resources/webhook#execute-webhook 256 | */ 257 | private function execute_discord_webhook($discord_webhook_url, $color, $message, $footer = NULL) 258 | { 259 | if (isset($discord_webhook_url) == false || $discord_webhook_url === '') 260 | { 261 | return false; 262 | } 263 | if (is_integer($color) == false || $color < 0) 264 | { 265 | // Use the default color if we did not receive a valid color value 266 | $color = self::DEFAULT_COLOR; 267 | } 268 | if (is_string($message) == false || $message == '') 269 | { 270 | return false; 271 | } 272 | if (isset($footer) == true && (is_string($footer) == false || $footer == '')) 273 | { 274 | return false; 275 | } 276 | 277 | // Clean up the message and footer text before sending by trimming whitespace from the front and end of the message and footer strings. 278 | $message = trim($message); 279 | $message = str_replace('"', "'", $message); // Replace " characters that would break the JSON encoding that our message must be wrapped in. 280 | if (isset($footer)) 281 | { 282 | $footer = trim($footer); 283 | $footer = str_replace('"', "'", $footer); 284 | // Discord does not appear to allow newline characters in the footer. In fact, the presence of them causes the message POST 285 | // to fail. Hence why we replace all newlines with a space here. 286 | $footer = str_replace(array("\r", "\n"), ' ', $footer); 287 | } 288 | 289 | // Abort if we find that either of our text fields are now empty strings 290 | if ($message === '') 291 | { 292 | return false; 293 | } 294 | if (isset($footer) && $footer === '') 295 | { 296 | return false; 297 | } 298 | 299 | // Verify that the message and footer size is within the allowable limit and truncate if necessary. We add "..." as the last three characters 300 | // when we require truncation. 301 | if (strlen($message) > self::MAX_MESSAGE_SIZE) 302 | { 303 | $message = substr($message, 0, self::MAX_MESSAGE_SIZE - 3) . '...'; 304 | } 305 | if (isset($footer)) 306 | { 307 | if (strlen($footer) > self::MAX_FOOTER_SIZE) 308 | { 309 | $footer = substr($footer, 0, self::MAX_FOOTER_SIZE - 3) . '...'; 310 | } 311 | } 312 | 313 | // Place the message inside the JSON structure that Discord expects to receive at the REST endpoint. 314 | $post = ''; 315 | if (isset($footer)) 316 | { 317 | $post = sprintf('{"embeds": [{"color": "%d", "description" : "%s", "footer": {"text": "%s"}}]}', $color, $message, $footer); 318 | } 319 | else { 320 | $post = sprintf('{"embeds": [{"color": "%d", "description" : "%s"}]}', $color, $message); 321 | } 322 | 323 | // Use the CURL library to transmit the message via a POST operation to the webhook URL. 324 | $h = curl_init(); 325 | curl_setopt($h, CURLOPT_URL, $discord_webhook_url); 326 | curl_setopt($h, CURLOPT_POST, 1); 327 | curl_setopt($h, CURLOPT_POSTFIELDS, $post); 328 | $response = curl_exec($h); 329 | curl_close($h); 330 | 331 | // Check if the response was not successful 332 | if (is_array($response) && $response['message']) 333 | { 334 | // TODO: If the response includes a message then an error has occurred. Determine whether we want to log it, queue it up to try again, etc. 335 | return false; 336 | } 337 | 338 | return true; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | ./tests 18 | ./tests/functional 19 | 20 | 21 | ./tests/functional/ 22 | 23 | 24 | 25 | 26 | 27 | ./tests/ 28 | 29 | 30 | ./ 31 | 32 | ./language/ 33 | ./migrations/ 34 | ./tests/ 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/dbal/fixtures/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | config_name 5 | config_value 6 | is_dynamic 7 | 8 | config1 9 | foo 10 | 0 11 | 12 | 13 | config2 14 | bar 15 | 1 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /tests/dbal/simple_test.php: -------------------------------------------------------------------------------- 1 | createXMLDataSet(__DIR__ . '/fixtures/config.xml'); 29 | } 30 | 31 | public function test_column() 32 | { 33 | $this->db = $this->new_dbal(); 34 | 35 | if (phpbb_version_compare(PHPBB_VERSION, '3.2.0-dev', '<')) 36 | { 37 | // This is how to instantiate db_tools in phpBB 3.1 38 | $db_tools = new \phpbb\db\tools($this->db); 39 | } 40 | else 41 | { 42 | // This is how to instantiate db_tools in phpBB 3.2 43 | $factory = new \phpbb\db\tools\factory(); 44 | $db_tools = $factory->get($this->db); 45 | } 46 | 47 | $this->assertTrue($db_tools->sql_column_exists(FORUMS_TABLE, 'discord_notifications_enabled'), 'Asserting that column "discord_notifications_enabled" exists on forums table'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/functional/demo_test.php: -------------------------------------------------------------------------------- 1 | 6 | # @license GNU General Public License, version 2 (GPL-2.0) 7 | # 8 | # For full copyright and license information, please see 9 | # the docs/CREDITS.txt file. 10 | # 11 | set -e 12 | set -x 13 | 14 | EXTNAME=$1 15 | BRANCH=$2 16 | EXTPATH_TEMP=$3 17 | 18 | # Copy extension to a temp folder 19 | mkdir ../../tmp 20 | cp -R . ../../tmp 21 | cd ../../ 22 | 23 | # Clone phpBB 24 | git clone --depth=1 "git://github.com/phpbb/phpbb.git" "phpBB3" --branch=$BRANCH 25 | --------------------------------------------------------------------------------