Change list :: cBB Reactions v1.0.4

Changes in Javascript files and external plugins are not included here.

acp/main_info.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -16,7 +16,7 @@
16
16
return [
17
17
'filename' => '\canidev\reactions\acp\main_module',
18
18
'title' => 'ACP_REACTIONS',
19
'version' => '1.0.3',
19
'version' => '1.0.4',
20
20
'modes' => [
21
21
'config' => ['title' => 'ACP_REACTIONS_CONFIG', 'auth' => 'ext_canidev/reactions && acl_a_board',  'cat' => ['ACP_REACTIONS']],
22
22
'manage' => ['title' => 'ACP_REACTIONS_MANAGE', 'auth' => 'ext_canidev/reactions && acl_a_board',  'cat' => ['ACP_REACTIONS']],
acp/main_module.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -19,7 +19,7 @@
19
19
public function main($id, $mode)
20
20
{
21
21
global $phpbb_container;
22
22
23
23
$controller = $phpbb_container->get('canidev.reactions.controller.admin.' . $mode);
24
24
25
25
$controller
adm/style/acp_reactions_manage.html
@@ -23,11 +23,11 @@
23
23
<a href="{{ U_REACTION_ADD }}" class="cbb-btn default-btn">{{ lang('REACTION_ADD') }}</a>
24
24
</div>
25
25
26
<div id="no-reactions-label" class="successbox{% if reactions|length > 0 %} cbb-helper-hidden {% endif %}">
26
<div id="no-reactions-label" class="successbox{{ reactions|length > 0 ? ' cbb-helper-hidden' : '' }}">
27
27
<p>{{ lang('NO_REACTIONS') }}</p>
28
28
</div>
29
29
30
<div class="cbb-table-acp acp-reactions-table{% if reactions|length == 0 %} cbb-helper-hidden{% endif %}">
30
<div class="cbb-table-acp acp-reactions-table{{ reactions|length == 0 ? ' cbb-helper-hidden' : '' }}">
31
31
{% for reaction in reactions %}
32
32
<div id="reaction-{{ reaction.ID }}">
33
33
<div class="handle"><i class="fa fa-sort"></i></div>
@@ -40,6 +40,7 @@
40
40
</label>
41
41
</div>
42
42
<div class="column-actions">
43
<a href="{{ reaction.U_DEFAULT }}" title="{{ lang('SET_AS_DEFAULT') }}" data-request="ajax" class="js-set-default {{ reaction.IS_DEFAULT ? ' active' : '' }}"><i class="fa fa-thumb-tack"></i></a>
43
44
<a href="{{ reaction.U_EDIT }}" title="{{ lang('EDIT') }}"><i class="fa fa-pencil"></i></a>
44
45
<a href="{{ reaction.U_DELETE }}" title="{{ lang('DELETE') }}" data-request="ajax"><i class="fa fa-trash"></i></a>
45
46
{{ reaction.S_HIDDEN_FIELDS }}
adm/style/event/acp_overall_footer_after.html
@@ -1,3 +1,3 @@
1
{% if REACTIONS_IN_ADMIN and CANIDEV_CORE_STARTED %}
1
{% if IN_EXT_REACTIONS and CANIDEV_CORE_STARTED %}
2
2
{% include '@canidev_core/event/acp_overall_footer_after.html' %}
3
3
{% endif %}
4
4
 No hay ningún carácter de nueva línea al final del fichero
adm/style/event/acp_overall_header_head_append.html
@@ -1,3 +1,3 @@
1
{% if REACTIONS_IN_ADMIN and CANIDEV_CORE_STARTED %}
1
{% if IN_EXT_REACTIONS and CANIDEV_CORE_STARTED %}
2
2
{% include '@canidev_core/event/overall_header_head_append.html' %}
3
3
{% endif %}
4
4
 No hay ningún carácter de nueva línea al final del fichero
adm/style/reactions-acp.css
@@ -1,7 +1,7 @@
1
1
/*  cBB Reactions StyleSheet
2
2
    --------------------------------------------------------------
3
3
Style: ACP
4
Copyright (c) 2024 CaniDev ( https://www.canidev.com )
4
Copyright (c) 2025 CaniDev ( https://www.canidev.com )
5
5
    --------------------------------------------------------------
6
6
*/
7
7
@@ -26,6 +26,10 @@
26
26
white-space: nowrap;
27
27
}
28
28
29
.acp-reactions-table .column-actions a.active {
30
color: #BC2A4D;
31
}
32
29
33
.reaction-score-selector {
30
34
list-style-type: none;
31
35
}
composer.json
@@ -3,7 +3,8 @@
3
3
"type": "phpbb-extension",
4
4
"description": "",
5
5
"homepage": "https://www.canidev.com",
6
"version": "1.0.3",
6
"version": "1.0.4",
7
"time": "2025-04-01",
7
8
"keywords": ["phpbb", "extension", "canidev", "reactions"],
8
9
"license": "CreativeCommons Attribution-NonCommercial v4.0",
9
10
"authors": [
config/services.yml
@@ -36,7 +36,9 @@
36
36
    canidev.reactions.manager:
37
37
        class: canidev\reactions\libraries\reaction_manager
38
38
        arguments:
39
            - '@auth'
39
40
            - '@cache.driver'
41
            - '@config'
40
42
            - '@service_container'
41
43
            - '@dbal.conn'
42
44
            - '@dispatcher'
controller/admin_config.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -65,12 +65,14 @@
65
65
'legend1' => '',
66
66
'reactions_allow_myself' => ['lang' => 'REACTIONS_ALLOW_MYSELF', 'validate' => 'bool', 'type' => 'radio:yes_no'],
67
67
'reactions_allow_change' => ['lang' => 'REACTIONS_ALLOW_CHANGE', 'validate' => 'bool', 'type' => 'radio:yes_no'],
68
'reactions_anonymous' => ['lang' => 'REACTIONS_ANONYMOUS', 'validate' => 'bool', 'type' => 'radio:yes_no'],
68
69
'reactions_force_reply' => ['lang' => 'REACTIONS_FORCE_REPLY', 'validate' => 'bool', 'type' => 'radio:yes_no'],
69
70
'reactions_force_attach' => ['lang' => 'REACTIONS_FORCE_ATTACH', 'validate' => 'bool', 'type' => 'radio:yes_no'],
70
71
'reactions_score_on_profile' => ['lang' => 'REACTIONS_SCORE_ON_PROFILE', 'validate' => 'bool', 'type' => 'radio:yes_no'],
71
'reactions_zones' => ['lang' => 'REACTIONS_ZONES', 'type' => 'custom', 'function' => ['tools', 'make_select'], 'params' => [$zones_options, '{KEY}', '{CONFIG_VALUE}']],
72
'reactions_button_position' => ['lang' => 'REACTIONS_BUTTON_POSITION', 'type' => 'custom', 'function' => ['tools', 'make_select'], 'params' => [$position_options, '{KEY}', '{CONFIG_VALUE}']],
73
'reactions_list_order' => ['lang' => 'REACTIONS_LIST_ORDER', 'type' => 'custom', 'function' => ['tools', 'make_select'], 'params' => [$order_options, '{KEY}', '{CONFIG_VALUE}']],
72
'reactions_zones' => ['lang' => 'REACTIONS_ZONES', 'type' => 'select', 'function' => ['tools', 'make_select'], 'params' => [$zones_options, false, '{CONFIG_VALUE}']],
73
'reactions_forums' => ['lang' => 'REACTIONS_FORUMS', 'type' => 'custom', 'function' => ['tools', 'forums_select'], 'params' => ['forums', '{CONFIG_VALUE}', 1]],
74
'reactions_button_position' => ['lang' => 'REACTIONS_BUTTON_POSITION', 'type' => 'select', 'function' => ['tools', 'make_select'], 'params' => [$position_options, false, '{CONFIG_VALUE}']],
75
'reactions_list_order' => ['lang' => 'REACTIONS_LIST_ORDER', 'type' => 'select', 'function' => ['tools', 'make_select'], 'params' => [$order_options, false, '{CONFIG_VALUE}']],
74
76
];
75
77
76
78
$cfg_array = $this->config;
@@ -78,6 +80,7 @@
78
80
if($submit)
79
81
{
80
82
$cfg_array = $this->request->variable('config', ['' => ''], true);
83
$cfg_array['reactions_forums'] = implode(',', $this->request->variable('forums', [0]));
81
84
82
85
if($cfg_array['reactions_zones'] == constants::ZONE_ONLY_REPLIES)
83
86
{
@@ -93,7 +96,7 @@
93
96
/**
94
97
 * @event reactions.acp_config_before
95
98
 * @var array display_vars Array of config values to display and process
96
 * @var boolean submit Do we display the form or process the submission
99
 * @var bool submit Do we display the form or process the submission
97
100
 * @var array cfg_array  Array with data
98
101
 * @var array  error  Array with data errors 
99
102
 * @since 1.0.0
@@ -105,7 +108,7 @@
105
108
validate_config_vars($display_vars, $cfg_array, $error);
106
109
107
110
// Do not write values if there is an error
108
if(sizeof($error))
111
if(count($error))
109
112
{
110
113
$submit = false;
111
114
}
@@ -141,11 +144,10 @@
141
144
trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($this->form_action));
142
145
}
143
146
144
$this->template->assign_vars(array(
145
'REACTIONS_IN_ADMIN' => true,
146
'S_ERROR' => (sizeof($error)) ? true : false,
147
'ERROR_MSG' => implode('<br />', $error),
148
));
147
$this->template->assign_vars([
148
'S_ERROR' => count($error) > 0,
149
'ERROR_MSG' => implode('<br />', $error),
150
]);
149
151
150
152
$this->display_vars($display_vars);
151
153
}
controller/admin_manage.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -135,7 +135,7 @@
135
135
extract($this->dispatcher->trigger_event('reactions.acp_manage_edit_before', compact($vars)));
136
136
137
137
// Do not write values if there is an error
138
if(sizeof($error))
138
if(count($error))
139
139
{
140
140
$submit = false;
141
141
}
@@ -177,7 +177,7 @@
177
177
178
178
$this->template->assign_vars([
179
179
'IN_REACTION' => true,
180
'S_ERROR' => sizeof($error) ? implode('<br />', $error) : '',
180
'S_ERROR' => count($error) ? implode('<br />', $error) : '',
181
181
'S_HIDDEN_FIELDS' => build_hidden_fields([
182
182
'action' => $action,
183
183
'id' => $reaction_id,
@@ -274,6 +274,18 @@
274
274
275
275
$this->json->send();
276
276
break;
277
278
case 'set_default':
279
if($this->config['reactions_default'] == $reaction_id)
280
{
281
$reaction_id = 0;
282
}
283
284
$this->config->set('reactions_default', $reaction_id);
285
$this->json->send([
286
'reactionID'  => $reaction_id
287
]);
288
break;
277
289
}
278
290
279
291
$sql = 'SELECT *
@@ -302,6 +314,7 @@
302
314
303
315
$this->template->assign_block_vars('reactions', [
304
316
'ID' => $row['reaction_id'],
317
'IS_DEFAULT' => ($row['reaction_id'] == $this->config['reactions_default']),
305
318
'IS_ENABLED' => (bool)$row['reaction_enabled'],
306
319
'S_HIDDEN_FIELDS' => build_hidden_fields([
307
320
'item[]' => $row['reaction_id'],
@@ -310,6 +323,7 @@
310
323
'S_SCORE' => $score_label,
311
324
'S_TITLE' => $title,
312
325
'S_TITLE_RAW' => $this->language->lang($row['reaction_title']),
326
'U_DEFAULT' => $this->form_action . '&amp;action=set_default&amp;id=' . $row['reaction_id'],
313
327
'U_DELETE' => $this->form_action . '&amp;action=delete&amp;id=' . $row['reaction_id'],
314
328
'U_EDIT' => $this->form_action . '&amp;action=edit&amp;id=' . $row['reaction_id'],
315
329
'U_STATUS' => $this->form_action . '&amp;action=change_status&amp;id=' . $row['reaction_id'],
@@ -318,8 +332,7 @@
318
332
$this->db->sql_freeresult($result);
319
333
320
334
$this->template->assign_vars([
321
'REACTIONS_IN_ADMIN' => true,
322
'U_REACTION_ADD' => $this->form_action . '&amp;action=add',
335
'U_REACTION_ADD' => $this->form_action . '&amp;action=add',
323
336
]);
324
337
325
338
// Assets
controller/main.php
@@ -1,14 +1,15 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
10
10
namespace canidev\reactions\controller;
11
11
12
use canidev\core\Dom;
12
13
use \canidev\reactions\libraries\constants;
13
14
14
15
/**
@@ -107,6 +108,7 @@
107
108
$post_id  = $this->request->variable('post', 0);
108
109
$user_id  = $this->request->variable('user', 0);
109
110
$score_amount  = 0;
111
$score_row  = [];
110
112
111
113
if(!(($user_id == $this->user->data['user_id'] && $this->auth->acl_get('u_reactions'))
112
114
|| $this->auth->acl_get('m_reactions')))
@@ -116,9 +118,10 @@
116
118
117
119
// Get post data
118
120
$sql = 'SELECT p.post_id, p.topic_id, p.poster_id, p.post_text, p.bbcode_uid, p.bbcode_bitfield,
119
t.topic_first_post_id
121
t.topic_first_post_id, u.user_reaction_score AS current_score
120
122
FROM ' . POSTS_TABLE . ' p
121
123
LEFT JOIN ' . TOPICS_TABLE . ' t ON(t.topic_id = p.topic_id)
124
LEFT JOIN ' . USERS_TABLE . ' u on(u.user_id = p.poster_id)
122
125
WHERE p.post_id = ' . $post_id;
123
126
$result = $this->db->sql_query($sql);
124
127
$post_data = $this->db->sql_fetchrow($result);
@@ -204,10 +207,10 @@
204
207
}
205
208
}
206
209
207
$this->template->assign_vars([
210
$score_row += [
208
211
'REACTION_HAS_MINE' => true,
209
212
'REACTION_LAUNCHER_ICON'  => $reaction->get_launcher_html(),
210
]);
213
];
211
214
212
215
// Add the new score to post/topic
213
216
$score_amount += $reaction->get_score();
@@ -224,9 +227,9 @@
224
227
AND user_id = ' . $user_id;
225
228
$this->db->sql_query($sql);
226
229
227
$this->template->assign_vars([
228
'REACTION_LAUNCHER_ICON'  => $this->reactions->get(false)->get_launcher_html(),
229
]);
230
$score_row += [
231
'REACTION_LAUNCHER_ICON'  => $this->reactions->get_launcher_html(false),
232
];
230
233
231
234
$this->response->add([
232
235
'reactionID' => $reaction_data['reaction_id'],
@@ -238,6 +241,15 @@
238
241
$this->notifications->delete_notification('canidev.reactions.notification.post', $post_id, $post_data['topic_id'], $user_id);
239
242
}
240
243
244
// Update user score
245
if(!is_null($post_data['current_score']))
246
{
247
$sql = 'UPDATE ' . USERS_TABLE . '
248
SET user_reaction_score = user_reaction_score + ' . $score_amount . '
249
WHERE user_id = ' . (int)$post_data['poster_id'];
250
$this->db->sql_query($sql);
251
}
252
241
253
// Update post score
242
254
$sql = 'UPDATE ' . POSTS_TABLE . '
243
255
SET post_reaction_score = post_reaction_score + ' . $score_amount . '
@@ -249,7 +261,7 @@
249
261
{
250
262
$sql = 'UPDATE ' . TOPICS_TABLE . '
251
263
SET topic_reaction_score = topic_reaction_score + ' . $score_amount . '
252
WHERE topic_id = ' . $post_data['topic_id'];
264
WHERE topic_id = ' . (int)$post_data['topic_id'];
253
265
$this->db->sql_query($sql);
254
266
}
255
267
@@ -257,18 +269,22 @@
257
269
 * @event reactions.add_remove_after
258
270
 * @var string  mode add_reaction / remove_reaction
259
271
 * @var int post_id  Post ID
260
 * @var  int  user_id  User ID
261
 * @var  int  reaction_id Reaction ID (only for "add_reaction" mode) 
272
 * @var int  user_id  User ID
273
 * @var int  reaction_id Reaction ID (only for "add_reaction" mode) 
262
274
 * @since 1.0.0
263
275
 */
264
276
$vars = ['mode', 'post_id', 'user_id', 'reaction_id'];
265
277
extract($this->dispatcher->trigger_event('reactions.add_remove_after', compact($vars)));
266
278
267
$this->template->assign_vars([
268
'CAN_USE_REACTIONS' => $this->auth->acl_get('u_reactions'),
269
'POST_ID' => $post_id,
270
'U_REACTION'  => $this->reactions->get_user_link($post_id,  $user_id),
271
]);
279
$score_row += [
280
'CAN_USE_REACTIONS' => $this->auth->acl_get('u_reactions'),
281
'POST_ID' => $post_id,
282
'REACTION_LIST_ATTR' => Dom::arrayToAttr([
283
'data-post-id' => $post_id,
284
'data-title' => $this->language->lang('REACTIONS'),
285
]),
286
'U_REACTION'  => $this->reactions->get_user_link($post_id,  $user_id),
287
];
272
288
273
289
// Put reactions on template
274
290
foreach($this->reactions->get_all() as $reaction)
@@ -284,13 +300,16 @@
284
300
if($this->auth->acl_get('u_reactions_view'))
285
301
{
286
302
$reaction_list = $this->reactions->get_posts_reactions($post_id);
287
$this->template->assign_vars($this->reactions->parse_post_score_list($reaction_list[$post_id]));
303
304
$score_row = array_merge($score_row, $this->reactions->parse_post_score_list($reaction_list[$post_id]));
288
305
}
289
306
290
$this->template->set_filenames(array(
307
$this->template->assign_block_vars('score_rows', $score_row);
308
309
$this->template->set_filenames([
291
310
'launcher' => '@canidev_reactions/launcher_button.html',
292
311
'score_list' => '@canidev_reactions/score_list.html',
293
));
312
]);
294
313
295
314
$this->response->add([
296
315
'postID' => $post_id,
@@ -399,7 +418,7 @@
399
418
400
419
foreach($tabs as $reaction_id => $data)
401
420
{
402
$item_count = sizeof($data['items']);
421
$item_count = count($data['items']);
403
422
404
423
if(!$item_count)
405
424
{
@@ -418,7 +437,7 @@
418
437
}
419
438
420
439
$this->template->set_filenames([
421
'dialog' => '@canidev_reactions/reactions_list.html'
440
'dialog' => $this->reactions->can_view_usernames() ? '@canidev_reactions/reactions_list.html' : '@canidev_reactions/reactions_list_simple.html'
422
441
]);
423
442
424
443
$this->response->addHtml($this->template->assign_display('dialog'));
event/listener.php
@@ -1,14 +1,15 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
10
10
namespace canidev\reactions\event;
11
11
12
use canidev\core\Dom;
12
13
use canidev\reactions\libraries\constants;
13
14
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14
15
@@ -39,6 +40,7 @@
39
40
 * @param \phpbb\language\language  $language Language object
40
41
 * @param \canidev\reactions\libraries\notification_manager  $notification_manager  Notification Manager
41
42
 * @param \canidev\reactions\libraries\reaction_manager  $reactions  Reaction Manager
43
 * @param \canidev\core\template  $template  Canidev Template Object
42
44
 * @param \phpbb\user $user User object
43
45
 * @param string  $reactions_table  Reactions Table
44
46
 * @param string  $reactions_data_table  Reactions Data Table
@@ -84,6 +86,7 @@
84
86
'core.permissions' => 'add_permissions',
85
87
'core.memberlist_view_profile'  => 'memberlist_view_profile',
86
88
'core.modify_posting_auth' => 'posting_auth',
89
'core.viewtopic_cache_user_data'  => 'viewtopic_user_data',
87
90
'core.viewforum_modify_topic_ordering' => 'viewforum_gen_sort_selects', // phpBB >= 3.2.5
88
91
'core.viewtopic_gen_sort_selects_before' => 'viewtopic_gen_sort_selects', // phpBB >= 3.2.8
89
92
'core.viewtopic_modify_post_data' => 'viewtopic_modify_data',
@@ -96,9 +99,9 @@
96
99
/**
97
100
 * Add Permissions to ACP
98
101
 * 
99
 * @param array $event Event data
102
 * @param \phpbb\event\data $event Event data
100
103
 */
101
public function add_permissions($event)
104
public function add_permissions(\phpbb\event\data $event)
102
105
{
103
106
$event['permissions'] = array_merge($event['permissions'], [
104
107
'm_reactions' => ['lang' => 'ACL_M_REACTIONS', 'cat' => 'post_actions'],
@@ -110,9 +113,9 @@
110
113
/**
111
114
 * Delete reaction's data on post delete
112
115
 * 
113
 * @param array $event Event data
116
 * @param \phpbb\event\data $event Event data
114
117
 */
115
public function delete_posts($event)
118
public function delete_posts(\phpbb\event\data $event)
116
119
{
117
120
$table_ary = $event['table_ary'];
118
121
$delete_notifications_types = $event['delete_notifications_types'];
@@ -127,9 +130,9 @@
127
130
/**
128
131
 * Delete reaction's data on user delete
129
132
 * 
130
 * @param array $event Event data
133
 * @param \phpbb\event\data $event Event data
131
134
 */
132
public function delete_user($event)
135
public function delete_user(\phpbb\event\data $event)
133
136
{
134
137
$post_ids = [];
135
138
@@ -162,9 +165,9 @@
162
165
/**
163
166
 * Load Reactions language
164
167
 * 
165
 * @param array $event Event data
168
 * @param \phpbb\event\data $event Event data
166
169
 */
167
public function load_language($event)
170
public function load_language(\phpbb\event\data $event)
168
171
{
169
172
$lang_set_ext = $event['lang_set_ext'];
170
173
$lang_set_ext[] = [
@@ -177,9 +180,9 @@
177
180
/**
178
181
 * Load profile stats
179
182
 * 
180
 * @param array $event Event data
183
 * @param \phpbb\event\data $event Event data
181
184
 */
182
public function memberlist_view_profile($event)
185
public function memberlist_view_profile(\phpbb\event\data $event)
183
186
{
184
187
$user_id = (int)$event['member']['user_id'];
185
188
@@ -194,19 +197,39 @@
194
197
195
198
if($this->config['reactions_score_on_profile'])
196
199
{
197
$score_ary = $this->reactions->get_users_score([$user_id]);
198
$this->template->assign_var('REACTIONS_USER_SCORE', $score_ary[$user_id]);
200
$score = $event['member']['user_reaction_score'];
201
202
/**
203
 * Adjust score count for user.
204
 * Correction of the error with the creation of the index in phpbb posts in large forums.
205
 */
206
if(is_null($score))
207
{
208
$score_ary = $this->reactions->get_users_score([$user_id]);
209
210
$score = $score_ary[$user_id];
211
212
$sql = 'UPDATE ' . USERS_TABLE . '
213
SET user_reaction_score = ' . $score . '
214
WHERE user_id = ' . $user_id;
215
$this->db->sql_query($sql);
216
}
217
218
$this->template->assign_var('REACTIONS_USER_SCORE', $score);
199
219
}
200
220
}
201
221
202
222
/**
203
223
 * Determine if user can posting a reply
204
224
 * 
205
 * @param array $event Event data
225
 * @param \phpbb\event\data $event Event data
206
226
 */
207
public function posting_auth($event)
227
public function posting_auth(\phpbb\event\data $event)
208
228
{
209
if($event['mode'] == 'reply' && $this->config['reactions_force_reply'] && $event['post_data']['topic_poster'] != $this->user->data['user_id'])
229
if($event['mode'] == 'reply' &&
230
$this->config['reactions_force_reply'] &&
231
$event['post_data']['topic_poster'] != $this->user->data['user_id'] &&
232
$this->is_valid_forum($event['forum_id']))
210
233
{
211
234
$sql = 'SELECT reaction_id
212
235
FROM ' . $this->reactions_data_table . '
@@ -226,9 +249,9 @@
226
249
/**
227
250
 * Add sort option to Viewforum
228
251
 * 
229
 * @param array $event Event data
252
 * @param \phpbb\event\data $event Event data
230
253
 */
231
public function viewforum_gen_sort_selects($event)
254
public function viewforum_gen_sort_selects(\phpbb\event\data $event)
232
255
{
233
256
$sort_by_text  = $event['sort_by_text'];
234
257
$sort_by_sql  = $event['sort_by_sql'];
@@ -243,9 +266,9 @@
243
266
/**
244
267
 * Add sort option to Viewtopic
245
268
 * 
246
 * @param array $event Event data
269
 * @param \phpbb\event\data $event Event data
247
270
 */
248
public function viewtopic_gen_sort_selects($event)
271
public function viewtopic_gen_sort_selects(\phpbb\event\data $event)
249
272
{
250
273
$sort_by_text  = $event['sort_by_text'];
251
274
$sort_by_sql  = $event['sort_by_sql'];
@@ -263,11 +286,11 @@
263
286
/**
264
287
 * Assign variables on viewtopic page
265
288
 * 
266
 * @param array $event Event data
289
 * @param \phpbb\event\data $event Event data
267
290
 */
268
public function viewtopic_modify_data($event)
291
public function viewtopic_modify_data(\phpbb\event\data $event)
269
292
{
270
if(!sizeof($event['post_list']))
293
if(!count($event['post_list']) || !$this->is_valid_forum($event['forum_id']))
271
294
{
272
295
return;
273
296
}
@@ -286,34 +309,71 @@
286
309
// Load post's reactions
287
310
$this->cache_ary = $this->reactions->get_posts_reactions($event['post_list']);
288
311
289
// Load user's scores
312
/**
313
 * Adjust score count for users.
314
 * Correction of the error with the creation of the index in phpbb posts in large forums.
315
 */
290
316
if($this->config['reactions_score_on_profile'])
291
317
{
292
318
$user_cache = $event['user_cache'];
293
$score_ary  = $this->reactions->get_users_score(array_keys($user_cache));
319
$user_ary  = [];
294
320
295
foreach($score_ary as $user_id => $user_score)
321
foreach($user_cache as $user_id => $row)
296
322
{
297
$user_cache[$user_id]['reactions_score'] = $user_score;
323
if(array_key_exists('reactions_score', $row) && is_null($row['reactions_score']))
324
{
325
$user_ary[$user_id] = $user_id;
326
}
298
327
}
299
328
300
$event['user_cache'] = $user_cache;
329
if(count($user_ary))
330
{
331
$score_ary = $this->reactions->get_users_score($user_ary);
332
$user_ary  = [];
333
334
foreach($score_ary as $user_id => $user_score)
335
{
336
$user_cache[$user_id]['reactions_score'] = $user_score;
337
338
$user_ary[$user_id] = [
339
'user_reaction_score' => $user_score,
340
];
341
}
342
343
foreach($user_ary as $user_id => $update_ary)
344
{
345
$sql = 'UPDATE ' . USERS_TABLE . '
346
SET ' . $this->db->sql_build_array('UPDATE', $update_ary) . '
347
WHERE user_id = ' . $user_id;
348
$this->db->sql_query($sql);
349
}
350
351
$event['user_cache'] = $user_cache;
352
}
301
353
}
302
354
303
$this->template->assign_var('REACTIONS_BTN_POSITION', $this->config['reactions_button_position']);
355
// Template variables and assets
356
$service_options = [
357
'allowChange' => (bool)$this->config['reactions_allow_change'],
358
'defaultID' => (int)$this->config['reactions_default'],
359
'simpleList' => !$this->reactions->can_view_usernames(),
360
];
361
362
$this->template->assign_vars([
363
'REACTIONS_BTN_POSITION' => $this->config['reactions_button_position'],
364
'REACTIONS_OPTIONS' => json_encode($service_options),
365
]);
304
366
305
// Assets
306
367
$this->template->append_asset('css', '@canidev_reactions/reactions.css');
307
$this->template->append_asset('javascript', 'window.reactionsAllowChange=' . (int)$this->config['reactions_allow_change'] . ';');
308
368
$this->template->append_asset('js', '@canidev_reactions/reactions.min.js');
309
369
}
310
370
311
371
/**
312
372
 * Add reaction variables to post rows in viewtopic
313
373
 * 
314
 * @param array $event Event data
374
 * @param \phpbb\event\data $event Event data
315
375
 */
316
public function viewtopic_modify_row($event)
376
public function viewtopic_modify_row(\phpbb\event\data $event)
317
377
{
318
378
$postrow  = $event['post_row'];
319
379
$row  = $event['row'];
@@ -321,6 +381,11 @@
321
381
$post_id  = (int)$row['post_id'];
322
382
$url_params = [];
323
383
384
if(!$this->is_valid_forum($event['row']['forum_id']))
385
{
386
return;
387
}
388
324
389
$postrow['REACTION_HAS_MINE'] = false;
325
390
326
391
if($this->reactions->get($this->cache_ary[$post_id]['mine'])->is_enabled())
@@ -328,7 +393,7 @@
328
393
$postrow['REACTION_HAS_MINE'] = true;
329
394
}
330
395
331
$postrow['REACTION_LAUNCHER_ICON']  = $this->reactions->get($this->cache_ary[$post_id]['mine'])->get_launcher_html();
396
$postrow['REACTION_LAUNCHER_ICON']  = $this->reactions->get_launcher_html($this->cache_ary[$post_id]['mine']);
332
397
$postrow['CAN_USE_REACTIONS']  = $this->auth->acl_get('u_reactions');
333
398
334
399
// Current user is the author and can't react to their own post
@@ -389,6 +454,11 @@
389
454
390
455
$postrow['U_REACTION'] = $this->reactions->get_user_link($post_id,  $this->user->data['user_id'], $url_params);
391
456
457
$postrow['REACTION_LIST_ATTR'] = Dom::arrayToAttr([
458
'data-post-id' => $post_id,
459
'data-title' => $this->language->lang('REACTIONS'),
460
]);
461
392
462
if($this->cache_ary[$post_id]['length'] && $this->auth->acl_get('u_reactions_view'))
393
463
{
394
464
$postrow = array_merge($postrow, $this->reactions->parse_post_score_list($this->cache_ary[$post_id]));
@@ -398,15 +468,46 @@
398
468
}
399
469
400
470
/**
471
 * Assign data to user profile in viewtopic
472
 * 
473
 * @param \phpbb\event\data  $event Event data
474
 */
475
public function viewtopic_user_data(\phpbb\event\data $event)
476
{
477
$user_cache_data = $event['user_cache_data'];
478
$user_cache_data['reactions_score'] = $event['row']['user_reaction_score'];
479
$event['user_cache_data'] = $user_cache_data;
480
}
481
482
/**
401
483
 * Update reactions username when some username is changed
402
484
 * 
403
 * @param array $event Event data
485
 * @param \phpbb\event\data $event Event data
404
486
 */
405
public function update_username($event)
487
public function update_username(\phpbb\event\data $event)
406
488
{
407
489
$sql = 'UPDATE ' . $this->reactions_data_table . "
408
490
SET username = '" . $this->db->sql_escape($event['new_name']) . "'
409
491
WHERE username = '" . $this->db->sql_escape($event['old_name']) . "'";
410
492
$this->db->sql_query($sql);
411
493
}
494
495
/**
496
 * Check if reactions can be used in selected forum
497
 * 
498
 * @param int  $forum_id
499
 * @return bool
500
 * @access private
501
 */
502
protected function is_valid_forum($forum_id)
503
{
504
if(!$this->config['reactions_forums'])
505
{
506
return true;
507
}
508
509
$forum_ary = explode(',', $this->config['reactions_forums']);
510
511
return in_array($forum_id, $forum_ary);
512
}
412
513
}
event/media.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
ext.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -12,7 +12,7 @@
12
12
class ext extends \phpbb\extension\base
13
13
{
14
14
const REQUIRED_PHPBB = '3.2.0';
15
const REQUIRED_CORE = '1.1.4';
15
const REQUIRED_CORE = '1.1.10';
16
16
17
17
/**
18
18
 * {@inheritDoc}
@@ -51,22 +51,30 @@
51
51
 */
52
52
public function enable_step($old_state)
53
53
{
54
switch ($old_state)
54
if($old_state === false)
55
55
{
56
case '': // Empty means nothing has run yet
57
// Enable notifications
58
$phpbb_notifications = $this->container->get('notification_manager');
59
$phpbb_notifications->enable_notifications('canidev.reactions.notification.post');
60
61
return 'notifications';
62
63
default:
64
// Create upload folder
65
\canidev\core\tools::mkdir($this->container->getParameter('core.root_path') . 'files/reactions/', true);
66
67
// Run parent enable step method
68
return parent::enable_step($old_state);
56
/** @var \phpbb\config\db */
57
$config = $this->container->get('config');
58
59
// Fresh install
60
if(!$config->offsetExists('reactions_allow_change'))
61
{
62
define('REACTIONS_FRESH_INSTALL', true);
63
}
64
65
// Enable notifications
66
/** @var \phpbb\notification\manager */
67
$phpbb_notifications = $this->container->get('notification_manager');
68
$phpbb_notifications->enable_notifications('canidev.reactions.notification.post');
69
70
// Create upload folder
71
\canidev\core\tools::mkdir($this->container->getParameter('core.root_path') . 'files/reactions/', true);
72
73
return true;
69
74
}
75
76
// Run parent enable step method
77
return parent::enable_step($old_state);
70
78
}
71
79
72
80
/**
language/en/acp.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [English [En]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
@@ -44,12 +44,19 @@
44
44
'REACTIONS_ALLOW_CHANGE_EXPLAIN' => 'Defines whether users will be able to modify their reaction on messages.',
45
45
'REACTIONS_ALLOW_MYSELF' => 'Allow self reactions',
46
46
'REACTIONS_ALLOW_MYSELF_EXPLAIN' => 'If enabled, users will be able to react to their own messages.',
47
'REACTIONS_ANONYMOUS' => 'Anonymous reactions',
48
'REACTIONS_ANONYMOUS_EXPLAIN' => 'Enable this option if you want user names not to appear in reaction listings.<br />
49
<em>This option does not affect users with permission to moderate reactions.</em>',
47
50
'REACTIONS_BUTTON_POSITION' => 'Button position',
48
51
'REACTIONS_BUTTON_POSITION_EXPLAIN' => 'Defines where the button to react will appear in the message.',
49
52
'REACTIONS_FORCE_ATTACH' => 'Force reaction to view attachments',
50
53
'REACTIONS_FORCE_ATTACH_EXPLAIN' => 'If enabled, users will have to react to a message in order to see its attachments.',
51
54
'REACTIONS_FORCE_REPLY' => 'Force reaction to reply',
52
55
'REACTIONS_FORCE_REPLY_EXPLAIN' => 'If enabled, users will have to react to the first post in a topic in order to post replies to it.',
56
'REACTIONS_FORUMS' => 'Forums where users can react',
57
'REACTIONS_FORUMS_EXPLAIN' => 'Defines the forums in which reactions will be displayed.<br />
58
If you don\'t select any, reactions will be displayed on all forums.<br />
59
You can select as many as you like using the <em>Ctrl</em> key on your keyboard.',
53
60
'REACTIONS_LIST_ORDER' => 'List order',
54
61
'REACTIONS_LIST_ORDER_EXPLAIN' => 'Defines the criteria that will be used to sort the users in the reaction list.',
55
62
'REACTIONS_ORDER_TIME' => 'Reaction date',
@@ -66,6 +73,7 @@
66
73
'SCORE_CUSTOM' => 'Custom',
67
74
'SCORE_CUSTOM_VALUE' => 'Custom (%s)',
68
75
'SELECT_COLOUR' => 'Select color',
76
'SET_AS_DEFAULT' => 'Set as default',
69
77
70
78
'scores'  => [
71
79
1  => 'Positive (+1)',
language/en/info_acp_reactions.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [English [En]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
language/en/main.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [English [En]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
@@ -30,9 +30,17 @@
30
30
31
31
'REACTIONS_NOTIFICATION_TYPE_POST' => 'Someone reacted to a message you posted',
32
32
33
'REACTION_SCORE_LABEL_SIMPLE' => '%1$s',
34
'REACTION_SCORE_LABEL_COUNT_ONE' => '%1$s and another user',
35
'REACTION_SCORE_LABEL_COUNT_MULTIPLE' => '%1$s and another %2$d users',
33
'REACTION_SCORE_LABEL_ANONYMOUS' => [
34
1 => '1 user has reacted',
35
2 => '%d users have reacted',
36
],
37
38
'REACTION_SCORE_LABEL_SIMPLE' => '%1$s',
39
40
'REACTION_SCORE_LABEL_COUNT' => [
41
1 => '%1$s and another user',
42
2 => '%1$s and another %2$d users',
43
],
36
44
37
45
'REACTIONS' => 'Reactions',
38
46
'REACTIONS_ALL' => 'All',
language/en/permissions_reactions.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [English [En]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
language/es/acp.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [Spanish [Es]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
@@ -44,12 +44,19 @@
44
44
'REACTIONS_ALLOW_CHANGE_EXPLAIN' => 'Define si los usuarios podrán modificar su reacción en los mensajes.',
45
45
'REACTIONS_ALLOW_MYSELF' => 'Permitir reacciones de uno mismo',
46
46
'REACTIONS_ALLOW_MYSELF_EXPLAIN' => 'Si se habilita, los usuarios podrán reaccionar a sus propios mensajes.',
47
'REACTIONS_ANONYMOUS' => 'Reacciones anónimas',
48
'REACTIONS_ANONYMOUS_EXPLAIN' => 'Habilite esta opción si desea que los nombres de los usuarios no aparezcan en los listados de reacciones.<br />
49
<em>Esta opción no afecta a los usuarios con permiso para moderar las reacciones.</em>',
47
50
'REACTIONS_BUTTON_POSITION' => 'Posición del botón',
48
51
'REACTIONS_BUTTON_POSITION_EXPLAIN' => 'Define en que lugar del mensaje aparecerá el botón para reaccionar.',
49
52
'REACTIONS_FORCE_ATTACH' => 'Forzar reacción para ver adjuntos',
50
53
'REACTIONS_FORCE_ATTACH_EXPLAIN' => 'Si se habilita, los usuarios tendrán que reaccionar a un mensaje para poder ver sus archivos adjuntos.',
51
54
'REACTIONS_FORCE_REPLY' => 'Forzar reacción para responder',
52
55
'REACTIONS_FORCE_REPLY_EXPLAIN' => 'Si se habilita, los usuarios tendrán que reaccionar al primer mensaje de un tema para poder publicar respuestas en el mismo.',
56
'REACTIONS_FORUMS' => 'Foros en los que se puede reaccionar',
57
'REACTIONS_FORUMS_EXPLAIN' => 'Define los foros en los que se mostrarán las reacciones.<br />
58
Si no selecciona ninguno, las reacciones se mostrarán en todos los foros.<br />
59
Puede seleccionar tantos como quiera usando la tecla <em>Ctrl</em> de su teclado.',
53
60
'REACTIONS_LIST_ORDER' => 'Orden de la lista',
54
61
'REACTIONS_LIST_ORDER_EXPLAIN' => 'Define el criterio que se usará para ordenar los usuarios en la lista de reacciones.',
55
62
'REACTIONS_ORDER_TIME' => 'Fecha de reacción',
@@ -66,6 +73,7 @@
66
73
'SCORE_CUSTOM' => 'Personalizado',
67
74
'SCORE_CUSTOM_VALUE' => 'Personalizado (%s)',
68
75
'SELECT_COLOUR' => 'Seleccionar color',
76
'SET_AS_DEFAULT' => 'Definir como predeterminada',
69
77
70
78
'scores'  => [
71
79
1  => 'Positivo (+1)',
language/es/info_acp_reactions.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [Spanish [Es]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
language/es/main.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [Spanish [Es]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
@@ -30,9 +30,17 @@
30
30
31
31
'REACTIONS_NOTIFICATION_TYPE_POST' => 'Alguien reaccionó a un mensaje que ha publicado',
32
32
33
'REACTION_SCORE_LABEL_SIMPLE' => '%1$s',
34
'REACTION_SCORE_LABEL_COUNT_ONE' => '%1$s y otro usuario',
35
'REACTION_SCORE_LABEL_COUNT_MULTIPLE' => '%1$s y otros %2$d usuarios',
33
'REACTION_SCORE_LABEL_ANONYMOUS' => [
34
1 => '1 usuario ha reaccionado',
35
2 => '%d usuarios han reaccionado',
36
],
37
38
'REACTION_SCORE_LABEL_SIMPLE' => '%1$s',
39
40
'REACTION_SCORE_LABEL_COUNT' => [
41
1 => '%1$s y otro usuario',
42
2 => '%1$s y otros %2$d usuarios',
43
],
36
44
37
45
'REACTIONS' => 'Reacciones',
38
46
'REACTIONS_ALL' => 'Todas',
language/es/permissions_reactions.php
@@ -2,9 +2,9 @@
2
2
/**
3
3
 * [Spanish [Es]]
4
4
 * @package cBB Reactions
5
 * @version 1.0.3 26/02/2024
5
 * @version 1.0.4 01/04/2025
6
6
 *
7
 * @copyright (c) 2024 CaniDev
7
 * @copyright (c) 2025 CaniDev
8
8
 * @license https://creativecommons.org/licenses/by-nc/4.0/
9
9
 */
10
10
libraries/constants.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
libraries/notification_manager.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
@@ -48,8 +48,10 @@
48
48
public function add_notification($notification_type_name, $notification_data)
49
49
{
50
50
$notification_type_id  = $this->manager->get_notification_type_id($notification_type_name);
51
$notification = $this->manager->get_item_type_class($notification_type_name);
52
51
$item_parent_id = isset($notification_data['item_parent_id']) ? $notification_data['item_parent_id'] : 0;
52
53
/** @var \canidev\reactions\notification\post */
54
$notification = $this->manager->get_item_type_class($notification_type_name);
53
55
54
56
$time = $this->user->create_datetime();
55
57
$time->setTime(0, 0, 0);
@@ -58,9 +60,9 @@
58
60
$sql = 'SELECT *
59
61
FROM ' . $this->notifications_table . '
60
62
WHERE notification_type_id = ' . (int)$notification_type_id . '
61
AND item_id = ' . (int)$notification_data['item_id'] . '
62
AND item_parent_id = ' . $item_parent_id . '
63
AND notification_time >= ' . $time->getTimestamp();
63
AND item_id = ' . (int)$notification_data['item_id'] . '
64
AND item_parent_id = ' . $item_parent_id . '
65
AND notification_time >= ' . $time->getTimestamp();
64
66
$result = $this->db->sql_query($sql);
65
67
$notification_row = $this->db->sql_fetchrow($result);
66
68
$this->db->sql_freeresult($result);
@@ -100,9 +102,9 @@
100
102
$sql = 'SELECT *
101
103
FROM ' . $this->notifications_table . '
102
104
WHERE notification_type_id = ' . (int)$notification_type_id . '
103
AND item_id = ' . (int)$item_id . '
104
AND item_parent_id = ' . (int)$item_parent_id . '
105
ORDER BY notification_time DESC';
105
AND item_id = ' . (int)$item_id . '
106
AND item_parent_id = ' . (int)$item_parent_id . '
107
ORDER BY notification_time DESC';
106
108
$result = $this->db->sql_query($sql);
107
109
$notification_row = $this->db->sql_fetchrow($result);
108
110
$this->db->sql_freeresult($result);
@@ -127,7 +129,7 @@
127
129
unset($user_ary[$key]);
128
130
129
131
// If there are still users, update the notification
130
if(sizeof($user_ary))
132
if(count($user_ary))
131
133
{
132
134
$data['user_ids'] = array_values($user_ary);
133
135
@@ -202,4 +204,4 @@
202
204
203
205
return $message;
204
206
}
205
}
206
207
 No hay ningún carácter de nueva línea al final del fichero
208
}
libraries/reaction_manager.php
@@ -1,19 +1,22 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
10
10
namespace canidev\reactions\libraries;
11
11
12
use canidev\core\Dom;
12
13
use Symfony\Component\DependencyInjection\ContainerInterface;
13
14
14
15
class reaction_manager
15
16
{
17
protected $auth;
16
18
protected $cache;
19
protected $config;
17
20
protected $container;
18
21
protected $db;
19
22
protected $dispatcher;
@@ -31,7 +34,9 @@
31
34
/**
32
35
 * Constructor
33
36
 *
37
 * @param \phpbb\auth\auth  $auth  Auth instance 
34
38
 * @param \phpbb\cache\driver\driver_interface  $cache  Cache instance
39
 * @param \phpbb\config\db  $config  Config object
35
40
 * @param ContainerInterface  $container Service container interface
36
41
 * @param \phpbb\db\driver\driver_interface $db DB Object
37
42
 * @param \phpbb\event\dispatcher_interface $dispatcher Event dispatcher
@@ -44,7 +49,9 @@
44
49
 * @access public
45
50
 */
46
51
public function __construct(
52
\phpbb\auth\auth $auth,
47
53
\phpbb\cache\driver\driver_interface $cache,
54
\phpbb\config\db $config,
48
55
ContainerInterface $container,
49
56
\phpbb\db\driver\driver_interface $db,
50
57
\phpbb\event\dispatcher_interface $dispatcher,
@@ -55,7 +62,9 @@
55
62
$reactions_data_table
56
63
)
57
64
{
65
$this->auth  = $auth;
58
66
$this->cache  = $cache;
67
$this->config  = $config;
59
68
$this->container  = $container;
60
69
$this->db  = $db;
61
70
$this->dispatcher = $dispatcher;
@@ -69,6 +78,16 @@
69
78
}
70
79
71
80
/**
81
 * Check if user can view usernames
82
 * 
83
 * @return bool
84
 */
85
public function can_view_usernames()
86
{
87
return !$this->config['reactions_anonymous'] || $this->auth->acl_get('m_reactions');
88
}
89
90
/**
72
91
 * Get singular reaction
73
92
 * 
74
93
 * @param int  $reaction_id  Reaction ID
@@ -76,11 +95,6 @@
76
95
 */
77
96
public function get($reaction_id = null)
78
97
{
79
if($reaction_id === null)
80
{
81
return $this->data;
82
}
83
84
98
if(is_numeric($reaction_id) && isset($this->data[$reaction_id]))
85
99
{
86
100
return $this->data[$reaction_id];
@@ -99,6 +113,22 @@
99
113
}
100
114
101
115
/**
116
 * Get default launcher icon
117
 * 
118
 * @param int|false  $reaction_id
119
 * @return string
120
 */
121
public function get_launcher_html($reaction_id)
122
{
123
if(!$reaction_id && $this->config['reactions_default'])
124
{
125
return '<img src="' . $this->get($this->config['reactions_default'])->get_image_url() . '" alt="reaction" />';
126
}
127
128
return $this->get($reaction_id)->get_launcher_html();
129
}
130
131
/**
102
132
 * Get Reactions of posts
103
133
 * 
104
134
 * @param array  $post_ary  Array with post IDs
@@ -258,7 +288,7 @@
258
288
$output['reaction_scores'][] = [
259
289
'S_REACTION_IMAGE' => $reaction->get_image_url(),
260
290
'S_REACTION_TITLE' => $reaction->get_title(),
261
'S_TITLE' => $reaction->get_title() . ' (' . sizeof($reactions) . ')',
291
'S_TITLE' => $reaction->get_title() . ' (' . count($reactions) . ')',
262
292
'U_VIEW' => $this->routing->route('canidev_reactions_controller', [
263
293
'mode' => 'view',
264
294
'post' => $reactions_data['post_id'],
@@ -266,11 +296,11 @@
266
296
]),
267
297
];
268
298
269
if(sizeof($scores_usernames) < 2)
299
if(count($scores_usernames) < 2)
270
300
{
271
301
foreach($reactions as $row)
272
302
{
273
if(sizeof($scores_usernames) >= 2)
303
if(count($scores_usernames) >= 2)
274
304
{
275
305
break;
276
306
}
@@ -284,25 +314,32 @@
284
314
}
285
315
}
286
316
287
if($reactions_data['length'] < 4)
317
if(!$this->can_view_usernames())
288
318
{
289
$key = ($reactions_data['length'] == 3) ? 'REACTION_SCORE_LABEL_COUNT_ONE' : 'REACTION_SCORE_LABEL_SIMPLE';
290
291
$output['REACTION_SCORE_LABEL'] = $this->language->lang($key, implode(', ', $scores_usernames));
319
$output['REACTION_SCORE_LABEL'] = $this->language->lang('REACTION_SCORE_LABEL_ANONYMOUS', $reactions_data['length']);
292
320
}
293
321
else
294
322
{
295
$output['REACTION_SCORE_LABEL'] = $this->language->lang(
296
'REACTION_SCORE_LABEL_COUNT_MULTIPLE',
297
implode(', ', $scores_usernames),
298
$reactions_data['length'] - sizeof($scores_usernames)
299
);
300
}
323
if($reactions_data['length'] < 4)
324
{
325
$key = ($reactions_data['length'] == 3) ? 'REACTION_SCORE_LABEL_COUNT' : 'REACTION_SCORE_LABEL_SIMPLE';
326
327
$output['REACTION_SCORE_LABEL'] = $this->language->lang($key, implode(', ', $scores_usernames), 1);
328
}
329
else
330
{
331
$output['REACTION_SCORE_LABEL'] = $this->language->lang(
332
'REACTION_SCORE_LABEL_COUNT',
333
implode(', ', $scores_usernames),
334
$reactions_data['length'] - count($scores_usernames)
335
);
336
}
301
337
302
$output['U_REACTION_SCORE_ALL'] = $this->routing->route('canidev_reactions_controller', [
303
'mode' => 'view',
304
'post' => $reactions_data['post_id'],
305
]);
338
$output['U_REACTION_SCORE_ALL'] = $this->routing->route('canidev_reactions_controller', [
339
'mode' => 'view',
340
'post' => $reactions_data['post_id'],
341
]);
342
}
306
343
307
344
return $output;
308
345
}
@@ -342,7 +379,7 @@
342
379
$post_ids = [$post_ids];
343
380
}
344
381
345
if(!sizeof($post_ids))
382
if(!count($post_ids))
346
383
{
347
384
return;
348
385
}
libraries/reaction.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
migrations/v103.php
@@ -5,6 +5,8 @@
5
5
 *
6
6
 * @copyright (c) 2024 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
 * 
9
 * Migration deactivated because problem with table index on big forums
8
10
 */
9
11
10
12
namespace canidev\reactions\migrations;
@@ -16,7 +18,7 @@
16
18
 */
17
19
public function effectively_installed()
18
20
{
19
return $this->db_tools->sql_index_exists($this->table_prefix . 'posts', 'reaction_score');
21
return true;
20
22
}
21
23
22
24
/**
@@ -28,32 +30,4 @@
28
30
'\canidev\reactions\migrations\v102',
29
31
];
30
32
}
31
32
/**
33
 * {@inheritDoc}
34
 */
35
public function update_schema()
36
{
37
return [
38
'add_index' => [
39
$this->table_prefix . 'posts' => [
40
'reaction_score' => ['poster_id', 'post_reaction_score'],
41
],
42
],
43
];
44
}
45
46
/**
47
 * {@inheritDoc}
48
 */
49
public function revert_schema()
50
{
51
return [
52
'drop_keys' => [
53
$this->table_prefix . 'posts' => [
54
'reaction_score',
55
],
56
],
57
];
58
}
59
33
}
migrations/v104.php
@@ -0,0 +1,88 @@
1
<?php
2
/**
3
 * @package cBB Reactions
4
 * @version 1.0.4 01/04/2025
5
 *
6
 * @copyright (c) 2025 CaniDev
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
 * 
9
 */
10
11
namespace canidev\reactions\migrations;
12
13
class v104 extends \phpbb\db\migration\migration
14
{
15
/**
16
 * {@inheritDoc}
17
 */
18
public function effectively_installed()
19
{
20
return $this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_reaction_score');
21
}
22
23
/**
24
 * {@inheritDoc}
25
 */
26
static public function depends_on()
27
{
28
return [
29
'\canidev\reactions\migrations\v102',
30
];
31
}
32
33
/**
34
 * {@inheritDoc}
35
 */
36
public function update_schema()
37
{
38
return [
39
'add_columns' => [
40
$this->table_prefix . 'users' => [
41
'user_reaction_score' => ['INT:4', null],
42
],
43
],
44
];
45
}
46
47
/**
48
 * {@inheritDoc}
49
 */
50
public function revert_schema()
51
{
52
return [
53
'drop_columns' => [
54
$this->table_prefix . 'users' => [
55
'user_reaction_score',
56
],
57
],
58
];
59
}
60
61
/**
62
 * {@inheritDoc}
63
 */
64
public function update_data()
65
{
66
return [
67
['config.add', ['reactions_forums', '']],
68
['config.add', ['reactions_default', 0]],
69
['config.add', ['reactions_anonymous', 0]],
70
71
['custom', [[$this, 'adjust_first_score']]],
72
];
73
}
74
75
/**
76
 * Set first user score. 0 on fresh install, null on update. (Fix phpbb_posts index problem)
77
 */
78
public function adjust_first_score()
79
{
80
if(defined('REACTIONS_FRESH_INSTALL'))
81
{
82
$sql = 'UPDATE ' . USERS_TABLE . '
83
SET user_reaction_score = 0
84
WHERE user_reaction_score IS NULL';
85
$this->db->sql_query($sql);
86
}
87
}
88
}
notification/post.php
@@ -1,9 +1,9 @@
1
1
<?php
2
2
/**
3
3
 * @package cBB Reactions
4
 * @version 1.0.3 26/02/2024
4
 * @version 1.0.4 01/04/2025
5
5
 *
6
 * @copyright (c) 2024 CaniDev
6
 * @copyright (c) 2025 CaniDev
7
7
 * @license https://creativecommons.org/licenses/by-nc/4.0/
8
8
 */
9
9
styles/all/template/event/overall_footer_body_after.html
@@ -0,0 +1,7 @@
1
{% if CANIDEV_CORE_STARTED %}
2
<script>
3
(function($) {
4
cbbCore.initService('reactions', {{ REACTIONS_OPTIONS }});
5
})(jQuery);
6
</script>
7
{% endif %}
styles/all/template/event/viewtopic_body_postrow_post_notices_after.html
@@ -1,6 +1,8 @@
1
{% if REACTIONS_BTN_POSITION == 'below' %}
2
{% set launcher_need_wrapper = true %}
3
{% include "@canidev_reactions/launcher_button.html" %}
4
{% endif %}
1
<div class="reaction-post-bar">
2
{% include "@canidev_reactions/score_list.html" %}
5
3
6
{% include "@canidev_reactions/score_list.html" %}
7
4
 No hay ningún carácter de nueva línea al final del fichero
5
{% if REACTIONS_BTN_POSITION == 'below' %}
6
{% set launcher_need_wrapper = true %}
7
{% include "@canidev_reactions/launcher_button.html" %}
8
{% endif %}
9
</div>
styles/all/template/launcher_button.html
@@ -1,37 +1,27 @@
1
1
{% if launcher_need_wrapper %}<ul class="reaction-launcher-wrapper">{% endif %}
2
2
3
{% if postrow is defined %}
4
{% if postrow.CAN_USE_REACTIONS %}
3
{% if not postrow is defined %}
4
{% set postrow = score_rows|first %}
5
{% endif %}
6
7
{% if postrow.CAN_USE_REACTIONS %}
5
8
<li class="reactions-launcher" data-skip-responsive="true" data-post-id="{{ postrow.POST_ID }}">
6
<a href="{{ postrow.U_REACTION }}" title="" rel="nofollow" class="reaction-button{% if not postrow.REACTION_HAS_MINE %} default-icon{% endif %} button button-icon-only">
9
<a href="{{ postrow.U_REACTION }}" title="" rel="nofollow" class="reaction-button{{ not postrow.REACTION_HAS_MINE ? ' default-icon' : '' }} button button-icon-only">
7
10
{{ postrow.REACTION_LAUNCHER_ICON }}
8
11
</a>
9
12
10
13
<div class="cbb-helper-hidden">
11
14
<ul class="reaction-selector">
12
15
{% for reaction in reactions %}
13
<li><a href="#" data-reaction-id="{{ reaction.ID }}" title="{{ reaction.S_TITLE }}" rel="nofollow"><img src="{{ reaction.S_IMAGE_URL }}" alt="{{ reaction.S_TITLE }}" /></a></li>
14
{% endfor %}
15
</ul>
16
</div>
17
</li>
18
{% endif %}
19
{% else %}
20
{% if CAN_USE_REACTIONS %}
21
<li class="reactions-launcher" data-skip-responsive="true" data-post-id="{{ POST_ID }}">
22
<a href="{{ U_REACTION }}" title="" rel="nofollow" class="reaction-button{% if not REACTION_HAS_MINE %} default-icon{% endif %} button button-icon-only">
23
{{ REACTION_LAUNCHER_ICON }}
24
</a>
25
26
<div class="cbb-helper-hidden">
27
<ul class="reaction-selector">
28
{% for reaction in reactions %}
29
<li><a href="#" data-reaction-id="{{ reaction.ID }}" title="{{ reaction.S_TITLE }}" rel="nofollow"><img src="{{ reaction.S_IMAGE_URL }}" alt="{{ reaction.S_TITLE }}" /></a></li>
16
<li>
17
<a href="#" data-reaction-id="{{ reaction.ID }}" title="{{ reaction.S_TITLE }}" rel="nofollow">
18
<img src="{{ reaction.S_IMAGE_URL }}" alt="{{ reaction.S_TITLE }}" />
19
</a>
20
</li>
30
21
{% endfor %}
31
22
</ul>
32
23
</div>
33
24
</li>
34
{% endif %}
35
25
{% endif %}
36
26
37
27
{% if launcher_need_wrapper %}</ul>{% endif %}
38
28
 No hay ningún carácter de nueva línea al final del fichero
styles/all/template/reactions_list.html
@@ -30,7 +30,7 @@
30
30
</div>
31
31
<div class="reaction-row-time">{{ item.S_REACTION_TIME }}</div>
32
32
<div class="reaction-row-actions">
33
{% if item.U_DELETE %}<a href="{{ item.U_DELETE }}" class="cbb-btn icon-only" data-ajaxify="true"><i class="fa fa-close"></i></a>{% endif %}
33
{% if item.U_DELETE %}<a href="{{ item.U_DELETE }}" class="cbb-btn icon-only" data-ajaxify="custom"><i class="fa fa-close"></i></a>{% endif %}
34
34
{{ item.CUSTOM_ACTIONS }}
35
35
</div>
36
36
</li>
styles/all/template/reactions_list_simple.html
@@ -0,0 +1,11 @@
1
<ul class="cbb-menu">
2
{% for tab in loops.tab %}
3
{% if tab.S_ID <> 0 %}
4
<li>
5
{% if tab.S_IMAGE %}<img src="{{ tab.S_IMAGE }}" alt="{{ tab.S_TITLE }}" />{% endif %}
6
<span>{{ tab.S_TITLE }}</span>
7
<span class="tab-counter">{{ tab.S_COUNT }}</span>
8
</li>
9
{% endif %}
10
{% endfor %}
11
</ul>
styles/all/template/score_list.html
@@ -1,19 +1,18 @@
1
{% if postrow is defined %}
2
<div class="reaction-score-list{% if not postrow.reaction_scores|length %} empty{% endif %}" data-post-id="{{ postrow.POST_ID }}" data-title="{{ lang('REACTIONS') }}">
3
<div class="list-label"><a href="{{ postrow.U_REACTION_SCORE_ALL }}">{{ postrow.REACTION_SCORE_LABEL }}</a></div>
4
<div class="list-scores">
5
{% for score in postrow.reaction_scores %}
6
<a href="{{ score.U_VIEW }}" title="{{ score.S_TITLE }}"><img src="{{ score.S_REACTION_IMAGE }}" alt="{{ score.S_REACTION_TITLE }}" /></a>
7
{% endfor %}
8
</div>
1
{% if not postrow is defined %}
2
{% set postrow = score_rows|first %}
3
{% endif %}
4
5
<div class="reaction-score-list{{ not postrow.reaction_scores|length ? ' empty' : '' }}" {{ postrow.REACTION_LIST_ATTR }}>
6
<div class="list-label">
7
{% if postrow.U_REACTION_SCORE_ALL %}
8
<a href="{{ postrow.U_REACTION_SCORE_ALL }}">{{ postrow.REACTION_SCORE_LABEL }}</a>
9
{% else %}
10
<span>{{ postrow.REACTION_SCORE_LABEL }}</span>
11
{% endif %}
9
12
</div>
10
{% else %}
11
<div class="reaction-score-list{% if not reaction_scores|length %} empty{% endif %}" data-post-id="{{ POST_ID }}" data-title="{{ lang('REACTIONS') }}">
12
<div class="list-label"><a href="{{ U_REACTION_SCORE_ALL }}">{{ REACTION_SCORE_LABEL }}</a></div>
13
<div class="list-scores">
14
{% for score in reaction_scores %}
15
<a href="{{ score.U_VIEW }}" title="{{ score.S_TITLE }}"><img src="{{ score.S_REACTION_IMAGE }}" alt="{{ score.S_REACTION_TITLE }}" /></a>
16
{% endfor %}
17
</div>
13
<div class="list-scores">
14
{% for score in postrow.reaction_scores %}
15
<a href="{{ score.U_VIEW }}" title="{{ score.S_TITLE }}"><img src="{{ score.S_REACTION_IMAGE }}" alt="{{ score.S_REACTION_TITLE }}" /></a>
16
{% endfor %}
18
17
</div>
19
{% endif %}
20
18
 No hay ningún carácter de nueva línea al final del fichero
19
</div>
styles/all/theme/reactions.css
@@ -1,7 +1,7 @@
1
1
/*  cBB Reactions StyleSheet
2
2
    --------------------------------------------------------------
3
3
Style: All
4
Copyright (c) 2024 CaniDev ( https://www.canidev.com )
4
Copyright (c) 2025 CaniDev ( https://www.canidev.com )
5
5
    --------------------------------------------------------------
6
6
*/
7
7
@@ -32,16 +32,26 @@
32
32
-webkit-touch-callout: none;
33
33
}
34
34
35
.reactions-launcher > .reaction-button.default-icon {
36
width: 24px;
37
}
38
39
35
.reactions-launcher > .reaction-button:hover {
40
36
background: none;
41
37
    box-shadow: none;
42
38
text-shadow: none;
43
39
}
44
40
41
.reactions-launcher > .reaction-button.default-icon {
42
width: 24px;
43
}
44
45
.reactions-launcher > .default-icon img {
46
filter: grayscale(1);
47
opacity: .7;
48
}
49
50
.reactions-launcher > .default-icon:hover img {
51
filter: grayscale(.3);
52
opacity: .8;
53
}
54
45
55
.reactions-launcher > .reaction-button span {
46
56
font-size: 0.85em;
47
57
font-weight: bold;
@@ -55,10 +65,28 @@
55
65
}
56
66
57
67
.reaction-launcher-wrapper {
58
clear: both;
68
display: flex;
59
69
list-style: none;
60
float: right;
61
margin-bottom: 8px;
70
position: relative;
71
}
72
73
.reaction-launcher-wrapper .reactions-launcher {
74
float: none;
75
}
76
77
.reaction-score-list:not(.empty) + .reaction-launcher-wrapper {
78
margin-left: 8px;
79
padding-left: 6px;
80
}
81
82
.reaction-score-list:not(.empty) + .reaction-launcher-wrapper::before {
83
background-color: #ccc;
84
bottom: 5px;
85
content: "";
86
left: 0;
87
position: absolute;
88
top: 5px;
89
width: 1px;
62
90
}
63
91
64
92
.reaction-button img {
@@ -87,14 +115,25 @@
87
115
transform: scale(1.3);
88
116
}
89
117
90
.reaction-score-list {
118
.reaction-post-bar {
91
119
align-items: center;
92
120
clear: both;
93
121
display: flex;
94
    float: right;
95
height: 16px;
96
margin: 0 0 5px 5px;
97
padding: 4px;
122
justify-content: flex-end;
123
margin: 12px 4px;
124
}
125
126
.reaction-post-bar::after {
127
    content: "";
128
    clear: both;
129
    display: block;
130
}
131
132
.reaction-score-list {
133
align-items: center;
134
display: flex;
135
font-size: 1em;
136
padding: 0;
98
137
}
99
138
100
139
.reaction-score-list.empty {
@@ -111,6 +150,7 @@
111
150
border: 2px solid transparent;
112
151
border-radius: 50%;
113
152
float: left;
153
line-height: 1px;
114
154
    position: relative;
115
155
}
116
156
@@ -155,10 +195,38 @@
155
195
transform: translate(-50%);
156
196
}
157
197
198
.cbb-dropdown .reactions-loading {
199
margin: 20px auto;
200
}
201
202
.cbb-dropdown .reactions-loading,
203
.cbb-dropdown .reactions-loading > span {
204
height: 35px;
205
width: 35px;
206
}
207
158
208
.reactions-view-dialog .cbb-dialog-content {
159
209
display: flex;
160
210
}
161
211
212
.reactions-view-list li {
213
align-items: center;
214
display: flex;
215
font-size: 11px;
216
gap: 10px;
217
padding: 6px 8px;
218
}
219
220
.reactions-view-list img {
221
height: 24px;
222
max-width: 24px;
223
}
224
225
.reactions-view-list .tab-counter {
226
font-weight: bold;
227
margin-left: auto;
228
}
229
162
230
.reactions-list {
163
231
display: flex;
164
232
    flex-direction: column;
@@ -264,7 +332,7 @@
264
332
265
333
.reaction-row-info .username,
266
334
.reaction-row-info .username-coloured {
267
font-size: 1.2em;
335
font-size: 1.1em;
268
336
    font-weight: bold;
269
337
}
270
338
@@ -321,6 +389,10 @@
321
389
border-color: #444;
322
390
}
323
391
392
.dark-theme .reaction-launcher-wrapper:not(:first-child)::before {
393
background-color: #3f3f3f;
394
}
395
324
396
/* Responsive
325
397
------------------------------ */
326
398
@media only screen and (max-width: 500px) {