Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
80.60% |
241 / 299 |
|
37.50% |
12 / 32 |
CRAP | |
0.00% |
0 / 1 |
| manager | |
80.60% |
241 / 299 |
|
37.50% |
12 / 32 |
193.14 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
| load_notifications | |
40.00% |
4 / 10 |
|
0.00% |
0 / 1 |
4.94 | |||
| mark_notifications_read | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| mark_notifications | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
5.26 | |||
| mark_notifications_read_by_parent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| mark_notifications_by_parent | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
| mark_notifications_by_id | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| add_notifications | |
96.77% |
30 / 31 |
|
0.00% |
0 / 1 |
4 | |||
| add_notifications_for_users | |
92.11% |
35 / 38 |
|
0.00% |
0 / 1 |
11.06 | |||
| update_notifications | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| update_notification | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
| delete_notifications | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| delete_notifications_by_types | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| get_subscription_types | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
7 | |||
| get_subscription_methods | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| get_subscription_methods_instances | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| get_available_subscription_methods | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| get_user_notifications | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
| get_global_subscriptions | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
10.01 | |||
| add_subscription | |
61.29% |
19 / 31 |
|
0.00% |
0 / 1 |
8.09 | |||
| delete_subscription | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
5 | |||
| disable_notifications | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| purge_notifications | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| enable_notifications | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| prune_notifications | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| get_default_methods | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
| get_item_type_class | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
| get_method_class | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| load_object | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
4.07 | |||
| get_notification_type_id | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
5 | |||
| get_notification_type_ids | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
| get_notified_users | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * |
| 4 | * This file is part of the phpBB Forum Software package. |
| 5 | * |
| 6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
| 7 | * @license GNU General Public License, version 2 (GPL-2.0) |
| 8 | * |
| 9 | * For full copyright and license information, please see |
| 10 | * the docs/CREDITS.txt file. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | namespace phpbb\notification; |
| 15 | |
| 16 | use phpbb\exception\runtime_exception; |
| 17 | use Symfony\Component\DependencyInjection\ContainerInterface; |
| 18 | |
| 19 | /** |
| 20 | * Notifications service class |
| 21 | */ |
| 22 | class manager |
| 23 | { |
| 24 | /** @var array */ |
| 25 | protected $notification_types; |
| 26 | |
| 27 | /** @var array */ |
| 28 | protected $subscription_types; |
| 29 | |
| 30 | /** @var method\method_interface[] */ |
| 31 | protected $notification_methods; |
| 32 | |
| 33 | /** @var ContainerInterface */ |
| 34 | protected $phpbb_container; |
| 35 | |
| 36 | /** @var \phpbb\user_loader */ |
| 37 | protected $user_loader; |
| 38 | |
| 39 | /** @var \phpbb\event\dispatcher_interface */ |
| 40 | protected $phpbb_dispatcher; |
| 41 | |
| 42 | /** @var \phpbb\db\driver\driver_interface */ |
| 43 | protected $db; |
| 44 | |
| 45 | /** @var \phpbb\cache\service */ |
| 46 | protected $cache; |
| 47 | |
| 48 | /** @var \phpbb\language\language */ |
| 49 | protected $language; |
| 50 | |
| 51 | /** @var \phpbb\user */ |
| 52 | protected $user; |
| 53 | |
| 54 | /** @var string */ |
| 55 | protected $notification_types_table; |
| 56 | |
| 57 | /** @var string */ |
| 58 | protected $user_notifications_table; |
| 59 | |
| 60 | /** |
| 61 | * Notification Constructor |
| 62 | * |
| 63 | * @param array $notification_types |
| 64 | * @param array $notification_methods |
| 65 | * @param ContainerInterface $phpbb_container |
| 66 | * @param \phpbb\user_loader $user_loader |
| 67 | * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher |
| 68 | * @param \phpbb\db\driver\driver_interface $db |
| 69 | * @param \phpbb\cache\service $cache |
| 70 | * @param \phpbb\language\language $language |
| 71 | * @param \phpbb\user $user |
| 72 | * @param string $notification_types_table |
| 73 | * @param string $user_notifications_table |
| 74 | */ |
| 75 | public function __construct($notification_types, $notification_methods, ContainerInterface $phpbb_container, \phpbb\user_loader $user_loader, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\language\language $language, \phpbb\user $user, $notification_types_table, $user_notifications_table) |
| 76 | { |
| 77 | $this->notification_types = $notification_types; |
| 78 | $this->notification_methods = $notification_methods; |
| 79 | $this->phpbb_container = $phpbb_container; |
| 80 | |
| 81 | $this->user_loader = $user_loader; |
| 82 | $this->phpbb_dispatcher = $phpbb_dispatcher; |
| 83 | $this->db = $db; |
| 84 | $this->cache = $cache; |
| 85 | $this->language = $language; |
| 86 | $this->user = $user; |
| 87 | |
| 88 | $this->notification_types_table = $notification_types_table; |
| 89 | $this->user_notifications_table = $user_notifications_table; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Load the user's notifications for a given method |
| 94 | * |
| 95 | * @param string $method_name |
| 96 | * @param array $options Optional options to control what notifications are loaded |
| 97 | * notification_id Notification id to load (or array of notification ids) |
| 98 | * user_id User id to load notifications for (Default: $user->data['user_id']) |
| 99 | * order_by Order by (Default: notification_time) |
| 100 | * order_dir Order direction (Default: DESC) |
| 101 | * limit Number of notifications to load (Default: 5) |
| 102 | * start Notifications offset (Default: 0) |
| 103 | * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) |
| 104 | * count_unread Count all unread notifications? (Default: false) |
| 105 | * count_total Count all notifications? (Default: false) |
| 106 | * @return array Array of information based on the request with keys: |
| 107 | * 'notifications' array of notification type objects |
| 108 | * 'unread_count' number of unread notifications the user has if count_unread is true in the options |
| 109 | * 'total_count' number of notifications the user has if count_total is true in the options |
| 110 | * @throws \phpbb\notification\exception when the method doesn't refer to a class extending \phpbb\notification\method\method_interface |
| 111 | */ |
| 112 | public function load_notifications($method_name, array $options = array()) |
| 113 | { |
| 114 | $method = $this->get_method_class($method_name); |
| 115 | |
| 116 | if (! $method instanceof \phpbb\notification\method\method_interface) |
| 117 | { |
| 118 | throw new \phpbb\notification\exception($this->language->lang('NOTIFICATION_METHOD_INVALID', $method_name)); |
| 119 | } |
| 120 | else if ($method->is_available()) |
| 121 | { |
| 122 | return $method->load_notifications($options); |
| 123 | } |
| 124 | else |
| 125 | { |
| 126 | return array( |
| 127 | 'notifications' => array(), |
| 128 | 'unread_count' => 0, |
| 129 | 'total_count' => 0, |
| 130 | ); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Mark notifications read or unread for all available methods |
| 136 | * |
| 137 | * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types |
| 138 | * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids |
| 139 | * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids |
| 140 | * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) |
| 141 | * |
| 142 | * @deprecated since 3.2 |
| 143 | */ |
| 144 | public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) |
| 145 | { |
| 146 | $this->mark_notifications($notification_type_name, $item_id, $user_id, $time); |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Mark notifications read or unread for all available methods |
| 151 | * |
| 152 | * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types |
| 153 | * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids |
| 154 | * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids |
| 155 | * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) |
| 156 | * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) |
| 157 | */ |
| 158 | public function mark_notifications($notification_type_name, $item_id, $user_id, $time = false, $mark_read = true) |
| 159 | { |
| 160 | if (is_array($notification_type_name)) |
| 161 | { |
| 162 | $notification_type_id = $this->get_notification_type_ids($notification_type_name); |
| 163 | } |
| 164 | else if ($notification_type_name !== false) |
| 165 | { |
| 166 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 167 | } |
| 168 | else |
| 169 | { |
| 170 | $notification_type_id = false; |
| 171 | } |
| 172 | |
| 173 | /** @var method\method_interface $method */ |
| 174 | foreach ($this->get_available_subscription_methods() as $method) |
| 175 | { |
| 176 | $method->mark_notifications($notification_type_id, $item_id, $user_id, $time, $mark_read); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Mark notifications read or unread from a parent identifier for all available methods |
| 182 | * |
| 183 | * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) |
| 184 | * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids |
| 185 | * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids |
| 186 | * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) |
| 187 | * |
| 188 | * @deprecated since 3.2 |
| 189 | */ |
| 190 | public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false) |
| 191 | { |
| 192 | $this->mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time); |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * Mark notifications read or unread from a parent identifier for all available methods |
| 197 | * |
| 198 | * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) |
| 199 | * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids |
| 200 | * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids |
| 201 | * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) |
| 202 | * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) |
| 203 | */ |
| 204 | public function mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false, $mark_read = true) |
| 205 | { |
| 206 | if (is_array($notification_type_name)) |
| 207 | { |
| 208 | $notification_type_id = $this->get_notification_type_ids($notification_type_name); |
| 209 | } |
| 210 | else |
| 211 | { |
| 212 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 213 | } |
| 214 | |
| 215 | /** @var method\method_interface $method */ |
| 216 | foreach ($this->get_available_subscription_methods() as $method) |
| 217 | { |
| 218 | $method->mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time, $mark_read); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * Mark notifications read or unread for a given method |
| 224 | * |
| 225 | * @param string $method_name |
| 226 | * @param array $notification_id Notification id or array of notification ids. |
| 227 | * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) |
| 228 | * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) |
| 229 | */ |
| 230 | public function mark_notifications_by_id($method_name, $notification_id, $time = false, $mark_read = true) |
| 231 | { |
| 232 | $method = $this->get_method_class($method_name); |
| 233 | |
| 234 | if ($method instanceof \phpbb\notification\method\method_interface && $method->is_available()) |
| 235 | { |
| 236 | $method->mark_notifications_by_id($notification_id, $time, $mark_read); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | /** |
| 241 | * Add a notification |
| 242 | * |
| 243 | * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) |
| 244 | * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive |
| 245 | * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array |
| 246 | * @param array $data Data specific for this type that will be inserted |
| 247 | * @param array $options Optional options to control what notifications are loaded |
| 248 | * ignore_users array of data to specify which users should not receive certain types of notifications |
| 249 | * @return array Information about what users were notified and how they were notified |
| 250 | */ |
| 251 | public function add_notifications($notification_type_name, $data, array $options = array()) |
| 252 | { |
| 253 | $options = array_merge(array( |
| 254 | 'ignore_users' => array(), |
| 255 | ), $options); |
| 256 | |
| 257 | $notified_users = []; |
| 258 | $add_notifications_override = false; |
| 259 | |
| 260 | /** |
| 261 | * Get notification data before find_users_for_notification() execute |
| 262 | * |
| 263 | * @event core.notification_manager_add_notifications_before |
| 264 | * @var bool add_notifications_override Flag indicating whether function should return after event |
| 265 | * @var array|string notification_type_name Type identifier or array of item types |
| 266 | * @var string data Data specific for this notification type that will be inserted |
| 267 | * @var array notified_users Array of notified users |
| 268 | * @var string options Optional options to control what notifications are loaded |
| 269 | * @since 3.3.6-RC1 |
| 270 | */ |
| 271 | $vars = [ |
| 272 | 'add_notifications_override', |
| 273 | 'notification_type_name', |
| 274 | 'data', |
| 275 | 'notified_users', |
| 276 | 'options', |
| 277 | ]; |
| 278 | extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications_before', compact($vars))); |
| 279 | |
| 280 | if ($add_notifications_override) |
| 281 | { |
| 282 | return $notified_users; |
| 283 | } |
| 284 | |
| 285 | if (is_array($notification_type_name)) |
| 286 | { |
| 287 | $temp_options = $options; |
| 288 | |
| 289 | foreach ($notification_type_name as $type) |
| 290 | { |
| 291 | $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users; |
| 292 | $notified_users += $this->add_notifications($type, $data, $temp_options); |
| 293 | } |
| 294 | |
| 295 | return $notified_users; |
| 296 | } |
| 297 | |
| 298 | // find out which users want to receive this type of notification |
| 299 | $notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options); |
| 300 | |
| 301 | /** |
| 302 | * Allow filtering the notify_users array for a notification that is about to be sent. |
| 303 | * Here, $notify_users is already filtered by f_read and the ignored list included in the options variable |
| 304 | * |
| 305 | * @event core.notification_manager_add_notifications |
| 306 | * @var string notification_type_name The notification type identifier |
| 307 | * @var array data Data specific for the notification_type_name used will be inserted |
| 308 | * @var array notify_users The array of userid that are going to be notified for this notification. Set to array() to cancel. |
| 309 | * @var array options The options that were used when this method was called (read only) |
| 310 | * |
| 311 | * @since 3.1.3-RC1 |
| 312 | */ |
| 313 | $vars = array( |
| 314 | 'notification_type_name', |
| 315 | 'data', |
| 316 | 'notify_users', |
| 317 | 'options', |
| 318 | ); |
| 319 | extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications', compact($vars))); |
| 320 | |
| 321 | $this->add_notifications_for_users($notification_type_name, $data, $notify_users); |
| 322 | |
| 323 | return $notify_users; |
| 324 | } |
| 325 | |
| 326 | /** |
| 327 | * Add a notification for specific users |
| 328 | * |
| 329 | * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) |
| 330 | * @param array $data Data specific for this type that will be inserted |
| 331 | * @param array $notify_users User list to notify |
| 332 | */ |
| 333 | public function add_notifications_for_users($notification_type_name, $data, $notify_users) |
| 334 | { |
| 335 | if (is_array($notification_type_name)) |
| 336 | { |
| 337 | foreach ($notification_type_name as $type) |
| 338 | { |
| 339 | $this->add_notifications_for_users($type, $data, $notify_users); |
| 340 | } |
| 341 | |
| 342 | return; |
| 343 | } |
| 344 | |
| 345 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 346 | |
| 347 | $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); |
| 348 | |
| 349 | $user_ids = array(); |
| 350 | $notification_methods = array(); |
| 351 | |
| 352 | // Never send notifications to the anonymous user! |
| 353 | unset($notify_users[ANONYMOUS]); |
| 354 | |
| 355 | // Make sure not to send new notifications to users who've already been notified about this item |
| 356 | // This may happen when an item was added, but now new users are able to see the item |
| 357 | // We remove each user which was already notified by at least one method. |
| 358 | /** @var method\method_interface $method */ |
| 359 | foreach ($this->get_subscription_methods_instances() as $method) |
| 360 | { |
| 361 | $notified_users = $method->get_notified_users($notification_type_id, array('item_id' => $item_id)); |
| 362 | |
| 363 | foreach ($notified_users as $user => $notifications) |
| 364 | { |
| 365 | unset($notify_users[$user]); |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Allow filtering the $notify_users array by $notification_type_name for a notification that is about to be sent. |
| 371 | * Here, $notify_users is already filtered from users who've already been notified. |
| 372 | * |
| 373 | * @event core.notification_manager_add_notifications_for_users_modify_data |
| 374 | * @var string notification_type_name The notification type identifier |
| 375 | * @var array data Data specific for this type that will be inserted |
| 376 | * @var array notify_users User list to notify |
| 377 | * |
| 378 | * @since 3.2.10-RC1 |
| 379 | * @since 3.3.1-RC1 |
| 380 | */ |
| 381 | $vars = [ |
| 382 | 'notification_type_name', |
| 383 | 'data', |
| 384 | 'notify_users', |
| 385 | ]; |
| 386 | extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications_for_users_modify_data', compact($vars))); |
| 387 | |
| 388 | if (!count($notify_users)) |
| 389 | { |
| 390 | return; |
| 391 | } |
| 392 | |
| 393 | // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications) |
| 394 | $notification = $this->get_item_type_class($notification_type_name); |
| 395 | $pre_create_data = $notification->pre_create_insert_array($data, $notify_users); |
| 396 | unset($notification); |
| 397 | |
| 398 | // Go through each user so we can insert a row in the DB and then notify them by their desired means |
| 399 | foreach ($notify_users as $user => $methods) |
| 400 | { |
| 401 | $notification = $this->get_item_type_class($notification_type_name); |
| 402 | |
| 403 | $notification->user_id = (int) $user; |
| 404 | |
| 405 | // Generate the insert_array |
| 406 | $notification->create_insert_array($data, $pre_create_data); |
| 407 | |
| 408 | // Users are needed to send notifications |
| 409 | $user_ids = array_merge($user_ids, $notification->users_to_query()); |
| 410 | |
| 411 | foreach ($methods as $method) |
| 412 | { |
| 413 | // Do not load non-existent notification methods |
| 414 | if (!isset($this->notification_methods[$method])) |
| 415 | { |
| 416 | continue; |
| 417 | } |
| 418 | |
| 419 | // Setup the notification methods and add the notification to the queue |
| 420 | if (!isset($notification_methods[$method])) |
| 421 | { |
| 422 | $notification_methods[$method] = $this->get_method_class($method); |
| 423 | } |
| 424 | $notification_methods[$method]->add_to_queue($notification); |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | // We need to load all of the users to send notifications |
| 429 | $this->user_loader->load_users($user_ids); |
| 430 | |
| 431 | // run the queue for each method to send notifications |
| 432 | foreach ($notification_methods as $method) |
| 433 | { |
| 434 | $method->notify(); |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | /** |
| 439 | * Update notification |
| 440 | * |
| 441 | * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) |
| 442 | * @param array $data Data specific for this type that will be updated |
| 443 | * @param array $options |
| 444 | */ |
| 445 | public function update_notifications($notification_type_name, array $data, array $options = array()) |
| 446 | { |
| 447 | if (is_array($notification_type_name)) |
| 448 | { |
| 449 | foreach ($notification_type_name as $type) |
| 450 | { |
| 451 | $this->update_notifications($type, $data); |
| 452 | } |
| 453 | |
| 454 | return; |
| 455 | } |
| 456 | |
| 457 | $this->update_notification($this->get_item_type_class($notification_type_name), $data, $options); |
| 458 | } |
| 459 | |
| 460 | /** |
| 461 | * Update a notification |
| 462 | * |
| 463 | * @param \phpbb\notification\type\type_interface $notification The notification |
| 464 | * @param array $data Data specific for this type that will be updated |
| 465 | * @param array $options |
| 466 | */ |
| 467 | public function update_notification(\phpbb\notification\type\type_interface $notification, array $data, array $options = array()) |
| 468 | { |
| 469 | if (empty($options)) |
| 470 | { |
| 471 | $options['item_id'] = $notification->get_item_id($data); |
| 472 | } |
| 473 | |
| 474 | /** @var method\method_interface $method */ |
| 475 | foreach ($this->get_available_subscription_methods() as $method) |
| 476 | { |
| 477 | $method->update_notification($notification, $data, $options); |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | /** |
| 482 | * Delete notifications of specified type |
| 483 | * |
| 484 | * @param string $notification_type_name Type identifier |
| 485 | * @param int|array $item_id Identifier within the type (or array of ids) |
| 486 | * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) |
| 487 | * @param mixed $user_id User id (Default: false; not checked) |
| 488 | * |
| 489 | * @return void |
| 490 | */ |
| 491 | public function delete_notifications(string $notification_type_name, $item_id, $parent_id = false, $user_id = false): void |
| 492 | { |
| 493 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 494 | |
| 495 | /** @var method\method_interface $method */ |
| 496 | foreach ($this->get_available_subscription_methods() as $method) |
| 497 | { |
| 498 | $method->delete_notifications($notification_type_id, $item_id, $parent_id, $user_id); |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | /** |
| 503 | * Delete notifications specified by multiple types |
| 504 | * |
| 505 | * @param array $notification_type_names Array of item types (only acceptable if the $item_id is identical for the specified types) |
| 506 | * @param int|array $item_id Identifier within the type (or array of ids) |
| 507 | * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) |
| 508 | * @param mixed $user_id User id (Default: false; not checked) |
| 509 | * |
| 510 | * @return void |
| 511 | */ |
| 512 | public function delete_notifications_by_types(array $notification_type_names, $item_id, $parent_id = false, $user_id = false): void |
| 513 | { |
| 514 | foreach ($notification_type_names as $type) |
| 515 | { |
| 516 | $this->delete_notifications($type, $item_id, $parent_id, $user_id); |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * Get all of the subscription types |
| 522 | * |
| 523 | * @return array Array of item types |
| 524 | */ |
| 525 | public function get_subscription_types() |
| 526 | { |
| 527 | if ($this->subscription_types === null) |
| 528 | { |
| 529 | $this->subscription_types = array(); |
| 530 | |
| 531 | foreach ($this->notification_types as $type_name => $data) |
| 532 | { |
| 533 | /** @var type\base $type */ |
| 534 | $type = $this->get_item_type_class($type_name); |
| 535 | |
| 536 | if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available()) |
| 537 | { |
| 538 | $options = array_merge(array( |
| 539 | 'type' => $type, |
| 540 | 'id' => $type->get_type(), |
| 541 | 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()), |
| 542 | 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS', |
| 543 | ), (($type::$notification_option !== false) ? $type::$notification_option : array())); |
| 544 | |
| 545 | $this->subscription_types[$options['group']][$options['id']] = $options; |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | // Move Miscellaneous to the very last section |
| 550 | if (isset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'])) |
| 551 | { |
| 552 | $miscellaneous = $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']; |
| 553 | unset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']); |
| 554 | $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous; |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | return $this->subscription_types; |
| 559 | } |
| 560 | |
| 561 | /** |
| 562 | * Get all of the subscription methods |
| 563 | * |
| 564 | * @return array Array of methods |
| 565 | */ |
| 566 | public function get_subscription_methods() |
| 567 | { |
| 568 | $subscription_methods = array(); |
| 569 | |
| 570 | /** @var method\method_interface $method */ |
| 571 | foreach ($this->get_available_subscription_methods() as $method_name => $method) |
| 572 | { |
| 573 | $subscription_methods[$method_name] = array( |
| 574 | 'method' => $method, |
| 575 | 'id' => $method->get_type(), |
| 576 | 'lang' => str_replace('.', '_', strtoupper($method->get_type())), |
| 577 | ); |
| 578 | } |
| 579 | |
| 580 | return $subscription_methods; |
| 581 | } |
| 582 | |
| 583 | /** |
| 584 | * Get all of the subscription methods |
| 585 | * |
| 586 | * @return array Array of method's instances |
| 587 | */ |
| 588 | private function get_subscription_methods_instances() |
| 589 | { |
| 590 | $subscription_methods = array(); |
| 591 | |
| 592 | foreach ($this->notification_methods as $method_name => $data) |
| 593 | { |
| 594 | $method = $this->get_method_class($method_name); |
| 595 | |
| 596 | if ($method instanceof \phpbb\notification\method\method_interface) |
| 597 | { |
| 598 | $subscription_methods[$method_name] = $method; |
| 599 | } |
| 600 | } |
| 601 | |
| 602 | return $subscription_methods; |
| 603 | } |
| 604 | |
| 605 | /** |
| 606 | * Get all of the available subscription methods |
| 607 | * |
| 608 | * @return array Array of method's instances |
| 609 | */ |
| 610 | private function get_available_subscription_methods() |
| 611 | { |
| 612 | $subscription_methods = array(); |
| 613 | |
| 614 | /** @var method\method_interface $method */ |
| 615 | foreach ($this->get_subscription_methods_instances() as $method_name => $method) |
| 616 | { |
| 617 | if ($method->is_available()) |
| 618 | { |
| 619 | $subscription_methods[$method_name] = $method; |
| 620 | } |
| 621 | } |
| 622 | |
| 623 | return $subscription_methods; |
| 624 | } |
| 625 | |
| 626 | |
| 627 | /** |
| 628 | * Get user's notification data |
| 629 | * |
| 630 | * @param int $user_id The user_id of the user to get the notifications for |
| 631 | * |
| 632 | * @return array User's notification |
| 633 | */ |
| 634 | protected function get_user_notifications($user_id) |
| 635 | { |
| 636 | $sql = 'SELECT method, notify, item_type |
| 637 | FROM ' . $this->user_notifications_table . ' |
| 638 | WHERE user_id = ' . (int) $user_id . ' |
| 639 | AND item_id = 0'; |
| 640 | |
| 641 | $result = $this->db->sql_query($sql); |
| 642 | $user_notifications = array(); |
| 643 | |
| 644 | while ($row = $this->db->sql_fetchrow($result)) |
| 645 | { |
| 646 | $user_notifications[$row['item_type']][] = $row; |
| 647 | } |
| 648 | |
| 649 | $this->db->sql_freeresult($result); |
| 650 | |
| 651 | return $user_notifications; |
| 652 | } |
| 653 | |
| 654 | /** |
| 655 | * Get global subscriptions (item_id = 0) |
| 656 | * |
| 657 | * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) |
| 658 | * |
| 659 | * @return array Subscriptions |
| 660 | */ |
| 661 | public function get_global_subscriptions($user_id = false) |
| 662 | { |
| 663 | $user_id = $user_id ?: $this->user->data['user_id']; |
| 664 | |
| 665 | $subscriptions = array(); |
| 666 | $default_methods = $this->get_default_methods(); |
| 667 | |
| 668 | $user_notifications = $this->get_user_notifications($user_id); |
| 669 | |
| 670 | foreach ($this->get_subscription_types() as $types) |
| 671 | { |
| 672 | foreach ($types as $id => $type) |
| 673 | { |
| 674 | $type_subscriptions = $default_methods; |
| 675 | if (!empty($user_notifications[$id])) |
| 676 | { |
| 677 | foreach ($user_notifications[$id] as $user_notification) |
| 678 | { |
| 679 | $key = array_search($user_notification['method'], $type_subscriptions, true); |
| 680 | if (!$user_notification['notify']) |
| 681 | { |
| 682 | if ($key !== false) |
| 683 | { |
| 684 | unset($type_subscriptions[$key]); |
| 685 | } |
| 686 | |
| 687 | continue; |
| 688 | } |
| 689 | else if ($key === false) |
| 690 | { |
| 691 | $type_subscriptions[] = $user_notification['method']; |
| 692 | } |
| 693 | } |
| 694 | } |
| 695 | |
| 696 | if (!empty($type_subscriptions)) |
| 697 | { |
| 698 | $subscriptions[$id] = $type_subscriptions; |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | return $subscriptions; |
| 704 | } |
| 705 | |
| 706 | /** |
| 707 | * Add a subscription |
| 708 | * |
| 709 | * @param string $item_type Type identifier of the subscription |
| 710 | * @param int $item_id The id of the item |
| 711 | * @param string $method The method of the notification e.g. 'board', 'email' |
| 712 | * (if null a subscription will be added for all the defaults methods) |
| 713 | * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) |
| 714 | */ |
| 715 | public function add_subscription($item_type, $item_id = 0, $method = null, $user_id = false) |
| 716 | { |
| 717 | if ($method === null) |
| 718 | { |
| 719 | foreach ($this->get_default_methods() as $method_name) |
| 720 | { |
| 721 | $this->add_subscription($item_type, $item_id, $method_name, $user_id); |
| 722 | } |
| 723 | |
| 724 | return; |
| 725 | } |
| 726 | |
| 727 | $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; |
| 728 | |
| 729 | $sql = 'SELECT notify |
| 730 | FROM ' . $this->user_notifications_table . " |
| 731 | WHERE item_type = '" . $this->db->sql_escape($item_type) . "' |
| 732 | AND item_id = " . (int) $item_id . ' |
| 733 | AND user_id = ' .(int) $user_id . " |
| 734 | AND method = '" . $this->db->sql_escape($method) . "'"; |
| 735 | $this->db->sql_query($sql); |
| 736 | $current = $this->db->sql_fetchfield('notify'); |
| 737 | $this->db->sql_freeresult(); |
| 738 | |
| 739 | if ($current === false) |
| 740 | { |
| 741 | $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . |
| 742 | $this->db->sql_build_array('INSERT', array( |
| 743 | 'item_type' => $item_type, |
| 744 | 'item_id' => (int) $item_id, |
| 745 | 'user_id' => (int) $user_id, |
| 746 | 'method' => $method, |
| 747 | 'notify' => 1, |
| 748 | )); |
| 749 | $this->db->sql_query($sql); |
| 750 | } |
| 751 | else if (!$current) |
| 752 | { |
| 753 | $sql = 'UPDATE ' . $this->user_notifications_table . " |
| 754 | SET notify = 1 |
| 755 | WHERE item_type = '" . $this->db->sql_escape($item_type) . "' |
| 756 | AND item_id = " . (int) $item_id . ' |
| 757 | AND user_id = ' .(int) $user_id . " |
| 758 | AND method = '" . $this->db->sql_escape($method) . "'"; |
| 759 | $this->db->sql_query($sql); |
| 760 | } |
| 761 | } |
| 762 | |
| 763 | /** |
| 764 | * Delete a subscription |
| 765 | * |
| 766 | * @param string $item_type Type identifier of the subscription |
| 767 | * @param int $item_id The id of the item |
| 768 | * @param string $method The method of the notification e.g. 'board', 'email' |
| 769 | * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) |
| 770 | */ |
| 771 | public function delete_subscription($item_type, $item_id = 0, $method = null, $user_id = false) |
| 772 | { |
| 773 | if ($method === null) |
| 774 | { |
| 775 | foreach ($this->get_default_methods() as $method_name) |
| 776 | { |
| 777 | $this->delete_subscription($item_type, $item_id, $method_name, $user_id); |
| 778 | } |
| 779 | |
| 780 | return; |
| 781 | } |
| 782 | |
| 783 | $user_id = $user_id ?: $this->user->data['user_id']; |
| 784 | |
| 785 | $sql = 'UPDATE ' . $this->user_notifications_table . " |
| 786 | SET notify = 0 |
| 787 | WHERE item_type = '" . $this->db->sql_escape($item_type) . "' |
| 788 | AND item_id = " . (int) $item_id . ' |
| 789 | AND user_id = ' .(int) $user_id . " |
| 790 | AND method = '" . $this->db->sql_escape($method) . "'"; |
| 791 | $this->db->sql_query($sql); |
| 792 | |
| 793 | if (!$this->db->sql_affectedrows()) |
| 794 | { |
| 795 | $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . |
| 796 | $this->db->sql_build_array('INSERT', array( |
| 797 | 'item_type' => $item_type, |
| 798 | 'item_id' => (int) $item_id, |
| 799 | 'user_id' => (int) $user_id, |
| 800 | 'method' => $method, |
| 801 | 'notify' => 0, |
| 802 | )); |
| 803 | $this->db->sql_query($sql); |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | /** |
| 808 | * Disable all notifications of a certain type |
| 809 | * |
| 810 | * This should be called when an extension which has notification types |
| 811 | * is disabled so that all those notifications are hidden and do not |
| 812 | * cause errors |
| 813 | * |
| 814 | * @param string $notification_type_name Type identifier of the subscription |
| 815 | */ |
| 816 | public function disable_notifications($notification_type_name) |
| 817 | { |
| 818 | $sql = 'UPDATE ' . $this->notification_types_table . " |
| 819 | SET notification_type_enabled = 0 |
| 820 | WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; |
| 821 | $this->db->sql_query($sql); |
| 822 | } |
| 823 | |
| 824 | /** |
| 825 | * Purge all notifications of a certain type |
| 826 | * |
| 827 | * This should be called when an extension which has notification types |
| 828 | * is purged so that all those notifications are removed |
| 829 | * |
| 830 | * @param string $notification_type_name Type identifier of the subscription |
| 831 | */ |
| 832 | public function purge_notifications($notification_type_name) |
| 833 | { |
| 834 | // If a notification is never used, its type will not be added to the database |
| 835 | // nor its id cached. If this method is called by an extension during the |
| 836 | // purge step, and that extension never used its notifications, |
| 837 | // get_notification_type_id() will throw an exception. However, |
| 838 | // because no notification type was added to the database, |
| 839 | // there is nothing to delete, so we can silently drop the exception. |
| 840 | try |
| 841 | { |
| 842 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 843 | |
| 844 | /** @var method\method_interface $method */ |
| 845 | foreach ($this->get_available_subscription_methods() as $method) |
| 846 | { |
| 847 | $method->purge_notifications($notification_type_id); |
| 848 | } |
| 849 | |
| 850 | } |
| 851 | catch (\phpbb\notification\exception $e) |
| 852 | { |
| 853 | // Continue |
| 854 | } |
| 855 | } |
| 856 | |
| 857 | /** |
| 858 | * Enable all notifications of a certain type |
| 859 | * |
| 860 | * This should be called when an extension which has notification types |
| 861 | * that was disabled is re-enabled so that all those notifications that |
| 862 | * were hidden are shown again |
| 863 | * |
| 864 | * @param string $notification_type_name Type identifier of the subscription |
| 865 | */ |
| 866 | public function enable_notifications($notification_type_name) |
| 867 | { |
| 868 | $sql = 'UPDATE ' . $this->notification_types_table . " |
| 869 | SET notification_type_enabled = 1 |
| 870 | WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; |
| 871 | $this->db->sql_query($sql); |
| 872 | } |
| 873 | |
| 874 | /** |
| 875 | * Delete all notifications older than a certain time |
| 876 | * |
| 877 | * @param int $timestamp Unix timestamp to delete all notifications that were created before |
| 878 | * @param bool $only_read True (default) to only prune read notifications |
| 879 | */ |
| 880 | public function prune_notifications($timestamp, $only_read = true) |
| 881 | { |
| 882 | /** @var method\method_interface $method */ |
| 883 | foreach ($this->get_available_subscription_methods() as $method) |
| 884 | { |
| 885 | $method->prune_notifications($timestamp, $only_read); |
| 886 | } |
| 887 | } |
| 888 | |
| 889 | /** |
| 890 | * Helper to get the list of methods enabled by default |
| 891 | * |
| 892 | * @return string[] Default method types |
| 893 | */ |
| 894 | public function get_default_methods(): array |
| 895 | { |
| 896 | $default_methods = array(); |
| 897 | |
| 898 | foreach ($this->notification_methods as $method) |
| 899 | { |
| 900 | if ($method->is_enabled_by_default() && $method->is_available()) |
| 901 | { |
| 902 | $default_methods[] = $method->get_type(); |
| 903 | } |
| 904 | } |
| 905 | |
| 906 | return $default_methods; |
| 907 | } |
| 908 | |
| 909 | /** |
| 910 | * Helper to get the notifications item type class and set it up |
| 911 | * |
| 912 | * @param string $notification_type_name |
| 913 | * @param array $data |
| 914 | * |
| 915 | * @return type\type_interface |
| 916 | * @throws runtime_exception When type name is not o notification type |
| 917 | */ |
| 918 | public function get_item_type_class($notification_type_name, $data = array()) |
| 919 | { |
| 920 | $item = $this->load_object($notification_type_name); |
| 921 | |
| 922 | if (!$item instanceof type\type_interface) |
| 923 | { |
| 924 | throw new runtime_exception('Supplied type name returned invalid service: ' . $notification_type_name); |
| 925 | } |
| 926 | |
| 927 | $item->set_initial_data($data); |
| 928 | |
| 929 | return $item; |
| 930 | } |
| 931 | |
| 932 | /** |
| 933 | * Helper to get the notifications method class and set it up |
| 934 | * |
| 935 | * @param string $method_name |
| 936 | * |
| 937 | * @return method\method_interface |
| 938 | * @throws runtime_exception When object name is not o notification method |
| 939 | */ |
| 940 | public function get_method_class($method_name) |
| 941 | { |
| 942 | $object = $this->load_object($method_name); |
| 943 | |
| 944 | if (!$object instanceof method\method_interface) |
| 945 | { |
| 946 | throw new runtime_exception('Supplied method name returned invalid service: ' . $method_name); |
| 947 | } |
| 948 | |
| 949 | return $object; |
| 950 | } |
| 951 | |
| 952 | /** |
| 953 | * Helper to load objects (notification types/methods) |
| 954 | * |
| 955 | * @param string $object_name |
| 956 | * |
| 957 | * @return method\method_interface|type\type_interface |
| 958 | * @psalm-suppress NullableReturnStatement Invalid service will result in exception |
| 959 | * @throws runtime_exception When object name is not o notification method or type |
| 960 | */ |
| 961 | protected function load_object($object_name) |
| 962 | { |
| 963 | $object = $this->phpbb_container->get($object_name); |
| 964 | |
| 965 | if (method_exists($object, 'set_notification_manager')) |
| 966 | { |
| 967 | $object->set_notification_manager($this); |
| 968 | } |
| 969 | |
| 970 | if (!$object instanceof method\method_interface && !$object instanceof type\type_interface) |
| 971 | { |
| 972 | throw new runtime_exception('Supplied object name returned invalid service: ' . $object_name); |
| 973 | } |
| 974 | |
| 975 | return $object; |
| 976 | } |
| 977 | |
| 978 | /** |
| 979 | * Get the notification type id from the name |
| 980 | * |
| 981 | * @param string $notification_type_name The name |
| 982 | * @return int the notification_type_id |
| 983 | * @throws \phpbb\notification\exception |
| 984 | */ |
| 985 | public function get_notification_type_id($notification_type_name) |
| 986 | { |
| 987 | $notification_type_ids = []; |
| 988 | |
| 989 | $sql = 'SELECT notification_type_id, notification_type_name |
| 990 | FROM ' . $this->notification_types_table; |
| 991 | $result = $this->db->sql_query($sql, 604800); // cache for one week |
| 992 | while ($row = $this->db->sql_fetchrow($result)) |
| 993 | { |
| 994 | $notification_type_ids[$row['notification_type_name']] = (int) $row['notification_type_id']; |
| 995 | } |
| 996 | $this->db->sql_freeresult($result); |
| 997 | |
| 998 | if (!isset($notification_type_ids[$notification_type_name])) |
| 999 | { |
| 1000 | if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name])) |
| 1001 | { |
| 1002 | throw new \phpbb\notification\exception('NOTIFICATION_TYPE_NOT_EXIST', array($notification_type_name)); |
| 1003 | } |
| 1004 | |
| 1005 | $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( |
| 1006 | 'notification_type_name' => $notification_type_name, |
| 1007 | 'notification_type_enabled' => 1, |
| 1008 | )); |
| 1009 | $this->db->sql_query($sql); |
| 1010 | |
| 1011 | // expose new notification type ID for this request |
| 1012 | $notification_type_ids[$notification_type_name] = (int) $this->db->sql_nextid(); |
| 1013 | |
| 1014 | // destroy cache, we have a new addition which we have to to load next time |
| 1015 | $this->cache->destroy('sql', $this->notification_types_table); |
| 1016 | } |
| 1017 | |
| 1018 | return $notification_type_ids[$notification_type_name]; |
| 1019 | } |
| 1020 | |
| 1021 | /** |
| 1022 | * Get notification type ids (as an array) |
| 1023 | * |
| 1024 | * @param string|array $notification_type_names Notification type names |
| 1025 | * @return array Array of integers |
| 1026 | */ |
| 1027 | public function get_notification_type_ids($notification_type_names) |
| 1028 | { |
| 1029 | if (!is_array($notification_type_names)) |
| 1030 | { |
| 1031 | $notification_type_names = array($notification_type_names); |
| 1032 | } |
| 1033 | |
| 1034 | $notification_type_ids = array(); |
| 1035 | |
| 1036 | foreach ($notification_type_names as $name) |
| 1037 | { |
| 1038 | $notification_type_ids[$name] = $this->get_notification_type_id($name); |
| 1039 | } |
| 1040 | |
| 1041 | return $notification_type_ids; |
| 1042 | } |
| 1043 | |
| 1044 | /** |
| 1045 | * Find the users which are already notified |
| 1046 | * |
| 1047 | * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to retrieve all item types |
| 1048 | * @param array $options |
| 1049 | * @return array The list of the notified users |
| 1050 | */ |
| 1051 | public function get_notified_users($notification_type_name, array $options) |
| 1052 | { |
| 1053 | $notification_type_id = $this->get_notification_type_id($notification_type_name); |
| 1054 | |
| 1055 | $notified_users = array(); |
| 1056 | |
| 1057 | /** @var method\method_interface $method */ |
| 1058 | foreach ($this->get_available_subscription_methods() as $method) |
| 1059 | { |
| 1060 | $notified_users = $notified_users + $method->get_notified_users($notification_type_id, $options); |
| 1061 | } |
| 1062 | |
| 1063 | return $notified_users; |
| 1064 | } |
| 1065 | } |