1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file contains core of the code for Mentions |
4
|
|
|
* |
5
|
|
|
* Simple Machines Forum (SMF) |
6
|
|
|
* |
7
|
|
|
* @package SMF |
8
|
|
|
* @author Simple Machines http://www.simplemachines.org |
9
|
|
|
* @copyright 2019 Simple Machines and individual contributors |
10
|
|
|
* @license http://www.simplemachines.org/about/smf/license.php BSD |
11
|
|
|
* |
12
|
|
|
* @version 2.1 RC2 |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* This really is a pseudo class, I couldn't justify having instance of it |
17
|
|
|
* while mentioning so I just made every method static |
18
|
|
|
*/ |
19
|
|
|
class Mentions |
20
|
|
|
{ |
21
|
|
|
protected static $char = '@'; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Returns mentions for a specific content |
25
|
|
|
* |
26
|
|
|
* @static |
27
|
|
|
* @access public |
28
|
|
|
* @param string $content_type The content type |
29
|
|
|
* @param int $content_id The ID of the desired content |
30
|
|
|
* @param array $members Whether to limit to a specific sect of members |
31
|
|
|
* @return array An array of arrays containing info about each member mentioned |
32
|
|
|
*/ |
33
|
|
|
public static function getMentionsByContent($content_type, $content_id, array $members = array()) |
34
|
|
|
{ |
35
|
|
|
global $smcFunc; |
36
|
|
|
|
37
|
|
|
$request = $smcFunc['db_query']('', ' |
38
|
|
|
SELECT mem.id_member, mem.real_name, mem.email_address, mem.id_group, mem.id_post_group, mem.additional_groups, |
39
|
|
|
mem.lngfile, ment.id_member AS id_mentioned_by, ment.real_name AS mentioned_by_name |
40
|
|
|
FROM {db_prefix}mentions AS m |
41
|
|
|
INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_mentioned) |
42
|
|
|
INNER JOIN {db_prefix}members AS ment ON (ment.id_member = m.id_member) |
43
|
|
|
WHERE content_type = {string:type} |
44
|
|
|
AND content_id = {int:id}' . (!empty($members) ? ' |
45
|
|
|
AND mem.id_member IN ({array_int:members})' : ''), |
46
|
|
|
array( |
47
|
|
|
'type' => $content_type, |
48
|
|
|
'id' => $content_id, |
49
|
|
|
'members' => (array) $members, |
50
|
|
|
) |
51
|
|
|
); |
52
|
|
|
$members = array(); |
53
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
54
|
|
|
$members[$row['id_member']] = array( |
55
|
|
|
'id' => $row['id_member'], |
56
|
|
|
'real_name' => $row['real_name'], |
57
|
|
|
'email_address' => $row['email_address'], |
58
|
|
|
'groups' => array_unique(array_merge(array($row['id_group'], $row['id_post_group']), explode(',', $row['additional_groups']))), |
59
|
|
|
'mentioned_by' => array( |
60
|
|
|
'id' => $row['id_mentioned_by'], |
61
|
|
|
'name' => $row['mentioned_by_name'], |
62
|
|
|
), |
63
|
|
|
'lngfile' => $row['lngfile'], |
64
|
|
|
); |
65
|
|
|
$smcFunc['db_free_result']($request); |
66
|
|
|
|
67
|
|
|
return $members; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Inserts mentioned members |
72
|
|
|
* |
73
|
|
|
* @static |
74
|
|
|
* @access public |
75
|
|
|
* @param string $content_type The content type |
76
|
|
|
* @param int $content_id The ID of the specified content |
77
|
|
|
* @param array $members An array of members who have been mentioned |
78
|
|
|
* @param int $id_member The ID of the member who mentioned them |
79
|
|
|
*/ |
80
|
|
|
public static function insertMentions($content_type, $content_id, array $members, $id_member) |
81
|
|
|
{ |
82
|
|
|
global $smcFunc; |
83
|
|
|
|
84
|
|
|
call_integration_hook('mention_insert_' . $content_type, array($content_id, &$members)); |
85
|
|
|
|
86
|
|
|
foreach ($members as $member) |
87
|
|
|
$smcFunc['db_insert']('ignore', |
88
|
|
|
'{db_prefix}mentions', |
89
|
|
|
array('content_id' => 'int', 'content_type' => 'string', 'id_member' => 'int', 'id_mentioned' => 'int', 'time' => 'int'), |
90
|
|
|
array((int) $content_id, $content_type, $id_member, $member['id'], time()), |
91
|
|
|
array('content_id', 'content_type', 'id_mentioned') |
92
|
|
|
); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Gets appropriate mentions replaced in the body |
97
|
|
|
* |
98
|
|
|
* @static |
99
|
|
|
* @access public |
100
|
|
|
* @param string $body The text to look for mentions in |
101
|
|
|
* @param array $members An array of arrays containing info about members (each should have 'id' and 'member') |
102
|
|
|
* @return string The body with mentions replaced |
103
|
|
|
*/ |
104
|
|
|
public static function getBody($body, array $members) |
105
|
|
|
{ |
106
|
|
|
foreach ($members as $member) |
107
|
|
|
$body = str_ireplace(static::$char . $member['real_name'], '[member=' . $member['id'] . ']' . $member['real_name'] . '[/member]', $body); |
108
|
|
|
|
109
|
|
|
return $body; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Takes a piece of text and finds all the mentioned members in it |
114
|
|
|
* |
115
|
|
|
* @static |
116
|
|
|
* @access public |
117
|
|
|
* @param string $body The body to get mentions from |
118
|
|
|
* @return array An array of arrays containing members who were mentioned (each has 'id_member' and 'real_name') |
119
|
|
|
*/ |
120
|
|
|
public static function getMentionedMembers($body) |
121
|
|
|
{ |
122
|
|
|
global $smcFunc; |
123
|
|
|
|
124
|
|
|
$possible_names = self::getPossibleMentions($body); |
125
|
|
|
|
126
|
|
|
if (empty($possible_names) || !allowedTo('mention')) |
127
|
|
|
return array(); |
128
|
|
|
|
129
|
|
|
$request = $smcFunc['db_query']('', ' |
130
|
|
|
SELECT id_member, real_name |
131
|
|
|
FROM {db_prefix}members |
132
|
|
|
WHERE real_name IN ({array_string:names}) |
133
|
|
|
ORDER BY LENGTH(real_name) DESC |
134
|
|
|
LIMIT {int:count}', |
135
|
|
|
array( |
136
|
|
|
'names' => $possible_names, |
137
|
|
|
'count' => count($possible_names), |
138
|
|
|
) |
139
|
|
|
); |
140
|
|
|
$members = array(); |
141
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
142
|
|
|
{ |
143
|
|
|
if (stripos($body, static::$char . $row['real_name']) === false) |
144
|
|
|
continue; |
145
|
|
|
|
146
|
|
|
$members[$row['id_member']] = array( |
147
|
|
|
'id' => $row['id_member'], |
148
|
|
|
'real_name' => $row['real_name'], |
149
|
|
|
); |
150
|
|
|
} |
151
|
|
|
$smcFunc['db_free_result']($request); |
152
|
|
|
|
153
|
|
|
return $members; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Parses a body in order to see if there are any mentions, returns possible mention names |
158
|
|
|
* |
159
|
|
|
* Names are tagged by "@<username>" format in post, but they can contain |
160
|
|
|
* any type of character up to 60 characters length. So we extract, starting from @ |
161
|
|
|
* up to 60 characters in length (or if we encounter a line break) and make |
162
|
|
|
* several combination of strings after splitting it by anything that's not a word and join |
163
|
|
|
* by having the first word, first and second word, first, second and third word and so on and |
164
|
|
|
* search every name. |
165
|
|
|
* |
166
|
|
|
* One potential problem with this is something like "@Admin Space" can match |
167
|
|
|
* "Admin Space" as well as "Admin", so we sort by length in descending order. |
168
|
|
|
* One disadvantage of this is that we can only match by one column, hence I've chosen |
169
|
|
|
* real_name since it's the most obvious. |
170
|
|
|
* |
171
|
|
|
* If there's an @ symbol within the name, it is counted in the ongoing string and a new |
172
|
|
|
* combination string is started from it as well in order to account for all the possibilities. |
173
|
|
|
* This makes the @ symbol to not be required to be escaped |
174
|
|
|
* |
175
|
|
|
* @static |
176
|
|
|
* @access protected |
177
|
|
|
* @param string $body The text to look for mentions in |
178
|
|
|
* @return array An array of names of members who have been mentioned |
179
|
|
|
*/ |
180
|
|
|
protected static function getPossibleMentions($body) |
181
|
|
|
{ |
182
|
|
|
global $smcFunc; |
183
|
|
|
|
184
|
|
|
// preparse code does a few things which might mess with our parsing |
185
|
|
|
$body = htmlspecialchars_decode(preg_replace('~<br\s*/?\>~', "\n", str_replace(' ', ' ', $body)), ENT_QUOTES); |
186
|
|
|
|
187
|
|
|
// Remove quotes, we don't want to get double mentions. |
188
|
|
|
while (preg_match('~\[quote[^\]]*\](.+?)\[\/quote\]~s', $body)) |
189
|
|
|
$body = preg_replace('~\[quote[^\]]*\](.+?)\[\/quote\]~s', '', $body); |
190
|
|
|
|
191
|
|
|
$matches = array(); |
192
|
|
|
$string = str_split($body); |
193
|
|
|
$depth = 0; |
194
|
|
|
foreach ($string as $k => $char) |
195
|
|
|
{ |
196
|
|
|
if ($char == static::$char && ($k == 0 || trim($string[$k - 1]) == '')) |
197
|
|
|
{ |
198
|
|
|
$depth++; |
199
|
|
|
$matches[] = array(); |
200
|
|
|
} |
201
|
|
|
elseif ($char == "\n") |
202
|
|
|
$depth = 0; |
203
|
|
|
|
204
|
|
|
for ($i = $depth; $i > 0; $i--) |
205
|
|
|
{ |
206
|
|
|
if (count($matches[count($matches) - $i]) > 60) |
207
|
|
|
{ |
208
|
|
|
$depth--; |
209
|
|
|
continue; |
210
|
|
|
} |
211
|
|
|
$matches[count($matches) - $i][] = $char; |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
foreach ($matches as $k => $match) |
216
|
|
|
$matches[$k] = substr(implode('', $match), 1); |
217
|
|
|
|
218
|
|
|
// Names can have spaces, other breaks, or they can't...we try to match every possible |
219
|
|
|
// combination. |
220
|
|
|
$names = array(); |
221
|
|
|
foreach ($matches as $match) |
222
|
|
|
{ |
223
|
|
|
$match = preg_split('/([^\w])/', $match, -1, PREG_SPLIT_DELIM_CAPTURE); |
224
|
|
|
$count = count($match); |
|
|
|
|
225
|
|
|
|
226
|
|
|
for ($i = 1; $i <= $count; $i++) |
227
|
|
|
$names[] = $smcFunc['htmlspecialchars']($smcFunc['htmltrim'](implode('', array_slice($match, 0, $i)))); |
|
|
|
|
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$names = array_unique($names); |
231
|
|
|
|
232
|
|
|
return $names; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
?> |