albertlast /
SMF2.1
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 2017 Simple Machines and individual contributors |
||
| 10 | * @license http://www.simplemachines.org/about/smf/license.php BSD |
||
| 11 | * |
||
| 12 | * @version 2.1 Beta 4 |
||
| 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 | ?> |
||
|
0 ignored issues
–
show
|
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.
A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.