1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file has all the main functions in it that relate to, well, everything. |
5
|
|
|
* |
6
|
|
|
* Simple Machines Forum (SMF) |
7
|
|
|
* |
8
|
|
|
* @package SMF |
9
|
|
|
* @author Simple Machines http://www.simplemachines.org |
10
|
|
|
* @copyright 2017 Simple Machines and individual contributors |
11
|
|
|
* @license http://www.simplemachines.org/about/smf/license.php BSD |
12
|
|
|
* |
13
|
|
|
* @version 2.1 Beta 4 |
14
|
|
|
*/ |
15
|
|
|
|
16
|
|
|
if (!defined('SMF')) |
17
|
|
|
die('No direct access...'); |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Update some basic statistics. |
21
|
|
|
* |
22
|
|
|
* 'member' statistic updates the latest member, the total member |
23
|
|
|
* count, and the number of unapproved members. |
24
|
|
|
* 'member' also only counts approved members when approval is on, but |
25
|
|
|
* is much more efficient with it off. |
26
|
|
|
* |
27
|
|
|
* 'message' changes the total number of messages, and the |
28
|
|
|
* highest message id by id_msg - which can be parameters 1 and 2, |
29
|
|
|
* respectively. |
30
|
|
|
* |
31
|
|
|
* 'topic' updates the total number of topics, or if parameter1 is true |
32
|
|
|
* simply increments them. |
33
|
|
|
* |
34
|
|
|
* 'subject' updates the log_search_subjects in the event of a topic being |
35
|
|
|
* moved, removed or split. parameter1 is the topicid, parameter2 is the new subject |
36
|
|
|
* |
37
|
|
|
* 'postgroups' case updates those members who match condition's |
38
|
|
|
* post-based membergroups in the database (restricted by parameter1). |
39
|
|
|
* |
40
|
|
|
* @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups' |
41
|
|
|
* @param mixed $parameter1 A parameter for updating the stats |
42
|
|
|
* @param mixed $parameter2 A 2nd parameter for updating the stats |
43
|
|
|
*/ |
44
|
|
|
function updateStats($type, $parameter1 = null, $parameter2 = null) |
45
|
|
|
{ |
46
|
|
|
global $modSettings, $smcFunc; |
|
|
|
|
47
|
|
|
|
48
|
|
|
switch ($type) |
49
|
|
|
{ |
50
|
|
|
case 'member': |
51
|
|
|
$changes = array( |
52
|
|
|
'memberlist_updated' => time(), |
53
|
|
|
); |
54
|
|
|
|
55
|
|
|
// #1 latest member ID, #2 the real name for a new registration. |
56
|
|
|
if (is_numeric($parameter1)) |
57
|
|
|
{ |
58
|
|
|
$changes['latestMember'] = $parameter1; |
59
|
|
|
$changes['latestRealName'] = $parameter2; |
60
|
|
|
|
61
|
|
|
updateSettings(array('totalMembers' => true), true); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
// We need to calculate the totals. |
65
|
|
|
else |
66
|
|
|
{ |
67
|
|
|
// Update the latest activated member (highest id_member) and count. |
68
|
|
|
$result = $smcFunc['db_query']('', ' |
69
|
|
|
SELECT COUNT(*), MAX(id_member) |
70
|
|
|
FROM {db_prefix}members |
71
|
|
|
WHERE is_activated = {int:is_activated}', |
72
|
|
|
array( |
73
|
|
|
'is_activated' => 1, |
74
|
|
|
) |
75
|
|
|
); |
76
|
|
|
list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result); |
77
|
|
|
$smcFunc['db_free_result']($result); |
78
|
|
|
|
79
|
|
|
// Get the latest activated member's display name. |
80
|
|
|
$result = $smcFunc['db_query']('', ' |
81
|
|
|
SELECT real_name |
82
|
|
|
FROM {db_prefix}members |
83
|
|
|
WHERE id_member = {int:id_member} |
84
|
|
|
LIMIT 1', |
85
|
|
|
array( |
86
|
|
|
'id_member' => (int) $changes['latestMember'], |
87
|
|
|
) |
88
|
|
|
); |
89
|
|
|
list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result); |
90
|
|
|
$smcFunc['db_free_result']($result); |
91
|
|
|
|
92
|
|
|
if (!empty($modSettings['registration_method'])) |
93
|
|
|
{ |
94
|
|
|
// Are we using registration approval? |
95
|
|
|
if ($modSettings['registration_method'] == 2 || !empty($modSettings['approveAccountDeletion'])) |
96
|
|
|
{ |
97
|
|
|
// Update the amount of members awaiting approval |
98
|
|
|
$result = $smcFunc['db_query']('', ' |
99
|
|
|
SELECT COUNT(*) |
100
|
|
|
FROM {db_prefix}members |
101
|
|
|
WHERE is_activated IN ({array_int:activation_status})', |
102
|
|
|
array( |
103
|
|
|
'activation_status' => array(3, 4), |
104
|
|
|
) |
105
|
|
|
); |
106
|
|
|
list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result); |
107
|
|
|
$smcFunc['db_free_result']($result); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
// What about unapproved COPPA registrations? |
111
|
|
View Code Duplication |
if (!empty($modSettings['coppaType']) && $modSettings['coppaType'] != 1) |
112
|
|
|
{ |
113
|
|
|
$result = $smcFunc['db_query']('', ' |
114
|
|
|
SELECT COUNT(*) |
115
|
|
|
FROM {db_prefix}members |
116
|
|
|
WHERE is_activated = {int:coppa_approval}', |
117
|
|
|
array( |
118
|
|
|
'coppa_approval' => 5, |
119
|
|
|
) |
120
|
|
|
); |
121
|
|
|
list ($coppa_approvals) = $smcFunc['db_fetch_row']($result); |
122
|
|
|
$smcFunc['db_free_result']($result); |
123
|
|
|
|
124
|
|
|
// Add this to the number of unapproved members |
125
|
|
|
if (!empty($changes['unapprovedMembers'])) |
126
|
|
|
$changes['unapprovedMembers'] += $coppa_approvals; |
127
|
|
|
else |
128
|
|
|
$changes['unapprovedMembers'] = $coppa_approvals; |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
updateSettings($changes); |
133
|
|
|
break; |
134
|
|
|
|
135
|
|
|
case 'message': |
136
|
|
|
if ($parameter1 === true && $parameter2 !== null) |
137
|
|
|
updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true); |
138
|
|
|
else |
139
|
|
|
{ |
140
|
|
|
// SUM and MAX on a smaller table is better for InnoDB tables. |
141
|
|
|
$result = $smcFunc['db_query']('', ' |
142
|
|
|
SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id |
143
|
|
|
FROM {db_prefix}boards |
144
|
|
|
WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' |
145
|
|
|
AND id_board != {int:recycle_board}' : ''), |
146
|
|
|
array( |
147
|
|
|
'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, |
148
|
|
|
'blank_redirect' => '', |
149
|
|
|
) |
150
|
|
|
); |
151
|
|
|
$row = $smcFunc['db_fetch_assoc']($result); |
152
|
|
|
$smcFunc['db_free_result']($result); |
153
|
|
|
|
154
|
|
|
updateSettings(array( |
155
|
|
|
'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'], |
156
|
|
|
'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id'] |
157
|
|
|
)); |
158
|
|
|
} |
159
|
|
|
break; |
160
|
|
|
|
161
|
|
|
case 'subject': |
162
|
|
|
// Remove the previous subject (if any). |
163
|
|
|
$smcFunc['db_query']('', ' |
164
|
|
|
DELETE FROM {db_prefix}log_search_subjects |
165
|
|
|
WHERE id_topic = {int:id_topic}', |
166
|
|
|
array( |
167
|
|
|
'id_topic' => (int) $parameter1, |
168
|
|
|
) |
169
|
|
|
); |
170
|
|
|
|
171
|
|
|
// Insert the new subject. |
172
|
|
|
if ($parameter2 !== null) |
173
|
|
|
{ |
174
|
|
|
$parameter1 = (int) $parameter1; |
175
|
|
|
$parameter2 = text2words($parameter2); |
176
|
|
|
|
177
|
|
|
$inserts = array(); |
178
|
|
|
foreach ($parameter2 as $word) |
179
|
|
|
$inserts[] = array($word, $parameter1); |
180
|
|
|
|
181
|
|
View Code Duplication |
if (!empty($inserts)) |
182
|
|
|
$smcFunc['db_insert']('ignore', |
183
|
|
|
'{db_prefix}log_search_subjects', |
184
|
|
|
array('word' => 'string', 'id_topic' => 'int'), |
185
|
|
|
$inserts, |
186
|
|
|
array('word', 'id_topic') |
187
|
|
|
); |
188
|
|
|
} |
189
|
|
|
break; |
190
|
|
|
|
191
|
|
|
case 'topic': |
192
|
|
|
if ($parameter1 === true) |
193
|
|
|
updateSettings(array('totalTopics' => true), true); |
194
|
|
|
else |
195
|
|
|
{ |
196
|
|
|
// Get the number of topics - a SUM is better for InnoDB tables. |
197
|
|
|
// We also ignore the recycle bin here because there will probably be a bunch of one-post topics there. |
198
|
|
|
$result = $smcFunc['db_query']('', ' |
199
|
|
|
SELECT SUM(num_topics + unapproved_topics) AS total_topics |
200
|
|
|
FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? ' |
201
|
|
|
WHERE id_board != {int:recycle_board}' : ''), |
202
|
|
|
array( |
203
|
|
|
'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0, |
204
|
|
|
) |
205
|
|
|
); |
206
|
|
|
$row = $smcFunc['db_fetch_assoc']($result); |
207
|
|
|
$smcFunc['db_free_result']($result); |
208
|
|
|
|
209
|
|
|
updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics'])); |
210
|
|
|
} |
211
|
|
|
break; |
212
|
|
|
|
213
|
|
|
case 'postgroups': |
214
|
|
|
// Parameter two is the updated columns: we should check to see if we base groups off any of these. |
215
|
|
|
if ($parameter2 !== null && !in_array('posts', $parameter2)) |
216
|
|
|
return; |
217
|
|
|
|
218
|
|
|
$postgroups = cache_get_data('updateStats:postgroups', 360); |
219
|
|
|
if ($postgroups == null || $parameter1 == null) |
220
|
|
|
{ |
221
|
|
|
// Fetch the postgroups! |
222
|
|
|
$request = $smcFunc['db_query']('', ' |
223
|
|
|
SELECT id_group, min_posts |
224
|
|
|
FROM {db_prefix}membergroups |
225
|
|
|
WHERE min_posts != {int:min_posts}', |
226
|
|
|
array( |
227
|
|
|
'min_posts' => -1, |
228
|
|
|
) |
229
|
|
|
); |
230
|
|
|
$postgroups = array(); |
231
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
232
|
|
|
$postgroups[$row['id_group']] = $row['min_posts']; |
233
|
|
|
$smcFunc['db_free_result']($request); |
234
|
|
|
|
235
|
|
|
// Sort them this way because if it's done with MySQL it causes a filesort :(. |
236
|
|
|
arsort($postgroups); |
237
|
|
|
|
238
|
|
|
cache_put_data('updateStats:postgroups', $postgroups, 360); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
// Oh great, they've screwed their post groups. |
242
|
|
|
if (empty($postgroups)) |
243
|
|
|
return; |
244
|
|
|
|
245
|
|
|
// Set all membergroups from most posts to least posts. |
246
|
|
|
$conditions = ''; |
247
|
|
|
$lastMin = 0; |
248
|
|
|
foreach ($postgroups as $id => $min_posts) |
|
|
|
|
249
|
|
|
{ |
250
|
|
|
$conditions .= ' |
251
|
|
|
WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id; |
252
|
|
|
$lastMin = $min_posts; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;). |
256
|
|
|
$smcFunc['db_query']('', ' |
257
|
|
|
UPDATE {db_prefix}members |
258
|
|
|
SET id_post_group = CASE ' . $conditions . ' |
259
|
|
|
ELSE 0 |
260
|
|
|
END' . ($parameter1 != null ? ' |
261
|
|
|
WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''), |
262
|
|
|
array( |
263
|
|
|
'members' => $parameter1, |
264
|
|
|
) |
265
|
|
|
); |
266
|
|
|
break; |
267
|
|
|
|
268
|
|
|
default: |
269
|
|
|
trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Updates the columns in the members table. |
275
|
|
|
* Assumes the data has been htmlspecialchar'd. |
276
|
|
|
* this function should be used whenever member data needs to be |
277
|
|
|
* updated in place of an UPDATE query. |
278
|
|
|
* |
279
|
|
|
* id_member is either an int or an array of ints to be updated. |
280
|
|
|
* |
281
|
|
|
* data is an associative array of the columns to be updated and their respective values. |
282
|
|
|
* any string values updated should be quoted and slashed. |
283
|
|
|
* |
284
|
|
|
* the value of any column can be '+' or '-', which mean 'increment' |
285
|
|
|
* and decrement, respectively. |
286
|
|
|
* |
287
|
|
|
* if the member's post number is updated, updates their post groups. |
288
|
|
|
* |
289
|
|
|
* @param mixed $members An array of member IDs, null to update this for all members or the ID of a single member |
290
|
|
|
* @param array $data The info to update for the members |
291
|
|
|
*/ |
292
|
|
|
function updateMemberData($members, $data) |
293
|
|
|
{ |
294
|
|
|
global $modSettings, $user_info, $smcFunc, $sourcedir; |
|
|
|
|
295
|
|
|
|
296
|
|
|
$parameters = array(); |
297
|
|
|
if (is_array($members)) |
298
|
|
|
{ |
299
|
|
|
$condition = 'id_member IN ({array_int:members})'; |
300
|
|
|
$parameters['members'] = $members; |
301
|
|
|
} |
302
|
|
|
elseif ($members === null) |
303
|
|
|
$condition = '1=1'; |
304
|
|
|
else |
305
|
|
|
{ |
306
|
|
|
$condition = 'id_member = {int:member}'; |
307
|
|
|
$parameters['member'] = $members; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
// Everything is assumed to be a string unless it's in the below. |
311
|
|
|
$knownInts = array( |
312
|
|
|
'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', |
313
|
|
|
'new_pm', 'pm_prefs', 'gender', 'show_online', 'pm_receive_from', 'alerts', |
314
|
|
|
'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', |
315
|
|
|
); |
316
|
|
|
$knownFloats = array( |
317
|
|
|
'time_offset', |
318
|
|
|
); |
319
|
|
|
|
320
|
|
|
if (!empty($modSettings['integrate_change_member_data'])) |
321
|
|
|
{ |
322
|
|
|
// Only a few member variables are really interesting for integration. |
323
|
|
|
$integration_vars = array( |
324
|
|
|
'member_name', |
325
|
|
|
'real_name', |
326
|
|
|
'email_address', |
327
|
|
|
'id_group', |
328
|
|
|
'gender', |
329
|
|
|
'birthdate', |
330
|
|
|
'website_title', |
331
|
|
|
'website_url', |
332
|
|
|
'location', |
333
|
|
|
'time_format', |
334
|
|
|
'time_offset', |
335
|
|
|
'avatar', |
336
|
|
|
'lngfile', |
337
|
|
|
); |
338
|
|
|
$vars_to_integrate = array_intersect($integration_vars, array_keys($data)); |
339
|
|
|
|
340
|
|
|
// Only proceed if there are any variables left to call the integration function. |
341
|
|
|
if (count($vars_to_integrate) != 0) |
342
|
|
|
{ |
343
|
|
|
// Fetch a list of member_names if necessary |
344
|
|
|
if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members))) |
345
|
|
|
$member_names = array($user_info['username']); |
346
|
|
View Code Duplication |
else |
347
|
|
|
{ |
348
|
|
|
$member_names = array(); |
349
|
|
|
$request = $smcFunc['db_query']('', ' |
350
|
|
|
SELECT member_name |
351
|
|
|
FROM {db_prefix}members |
352
|
|
|
WHERE ' . $condition, |
353
|
|
|
$parameters |
354
|
|
|
); |
355
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
356
|
|
|
$member_names[] = $row['member_name']; |
357
|
|
|
$smcFunc['db_free_result']($request); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
if (!empty($member_names)) |
361
|
|
|
foreach ($vars_to_integrate as $var) |
362
|
|
|
call_integration_hook('integrate_change_member_data', array($member_names, $var, &$data[$var], &$knownInts, &$knownFloats)); |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
$setString = ''; |
367
|
|
|
foreach ($data as $var => $val) |
368
|
|
|
{ |
369
|
|
|
$type = 'string'; |
370
|
|
|
if (in_array($var, $knownInts)) |
371
|
|
|
$type = 'int'; |
372
|
|
|
elseif (in_array($var, $knownFloats)) |
373
|
|
|
$type = 'float'; |
374
|
|
|
elseif ($var == 'birthdate') |
375
|
|
|
$type = 'date'; |
376
|
|
|
elseif ($var == 'member_ip') |
377
|
|
|
$type = 'inet'; |
378
|
|
|
elseif ($var == 'member_ip2') |
379
|
|
|
$type = 'inet'; |
380
|
|
|
|
381
|
|
|
// Doing an increment? |
382
|
|
|
if ($var == 'alerts' && ($val === '+' || $val === '-')) |
383
|
|
|
{ |
384
|
|
|
include_once($sourcedir . '/Profile-View.php'); |
385
|
|
|
if (is_array($members)) |
386
|
|
|
{ |
387
|
|
|
$val = 'CASE '; |
388
|
|
|
foreach ($members as $k => $v) |
389
|
|
|
$val .= 'WHEN id_member = ' . $v . ' THEN '. count(fetch_alerts($v, false, 0, array(), false)) . ' '; |
390
|
|
|
$val = $val . ' END'; |
391
|
|
|
$type = 'raw'; |
392
|
|
|
} |
393
|
|
|
else |
394
|
|
|
{ |
395
|
|
|
$blub = fetch_alerts($members, false, 0, array(), false); |
396
|
|
|
$val = count($blub); |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
else if ($type == 'int' && ($val === '+' || $val === '-')) |
400
|
|
|
{ |
401
|
|
|
$val = $var . ' ' . $val . ' 1'; |
402
|
|
|
$type = 'raw'; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
// Ensure posts, instant_messages, and unread_messages don't overflow or underflow. |
406
|
|
|
if (in_array($var, array('posts', 'instant_messages', 'unread_messages'))) |
407
|
|
|
{ |
408
|
|
|
if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match)) |
409
|
|
|
{ |
410
|
|
|
if ($match[1] != '+ ') |
411
|
|
|
$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END'; |
412
|
|
|
$type = 'raw'; |
413
|
|
|
} |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},'; |
417
|
|
|
$parameters['p_' . $var] = $val; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
$smcFunc['db_query']('', ' |
421
|
|
|
UPDATE {db_prefix}members |
422
|
|
|
SET' . substr($setString, 0, -1) . ' |
423
|
|
|
WHERE ' . $condition, |
424
|
|
|
$parameters |
425
|
|
|
); |
426
|
|
|
|
427
|
|
|
updateStats('postgroups', $members, array_keys($data)); |
428
|
|
|
|
429
|
|
|
// Clear any caching? |
430
|
|
|
if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members)) |
431
|
|
|
{ |
432
|
|
|
if (!is_array($members)) |
433
|
|
|
$members = array($members); |
434
|
|
|
|
435
|
|
|
foreach ($members as $member) |
436
|
|
|
{ |
437
|
|
|
if ($modSettings['cache_enable'] >= 3) |
438
|
|
|
{ |
439
|
|
|
cache_put_data('member_data-profile-' . $member, null, 120); |
440
|
|
|
cache_put_data('member_data-normal-' . $member, null, 120); |
441
|
|
|
cache_put_data('member_data-minimal-' . $member, null, 120); |
442
|
|
|
} |
443
|
|
|
cache_put_data('user_settings-' . $member, null, 60); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Updates the settings table as well as $modSettings... only does one at a time if $update is true. |
450
|
|
|
* |
451
|
|
|
* - updates both the settings table and $modSettings array. |
452
|
|
|
* - all of changeArray's indexes and values are assumed to have escaped apostrophes (')! |
453
|
|
|
* - if a variable is already set to what you want to change it to, that |
454
|
|
|
* variable will be skipped over; it would be unnecessary to reset. |
455
|
|
|
* - When use_update is true, UPDATEs will be used instead of REPLACE. |
456
|
|
|
* - when use_update is true, the value can be true or false to increment |
457
|
|
|
* or decrement it, respectively. |
458
|
|
|
* |
459
|
|
|
* @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format |
460
|
|
|
* @param bool $update Whether to use an UPDATE query instead of a REPLACE query |
461
|
|
|
*/ |
462
|
|
|
function updateSettings($changeArray, $update = false) |
463
|
|
|
{ |
464
|
|
|
global $modSettings, $smcFunc; |
|
|
|
|
465
|
|
|
|
466
|
|
|
if (empty($changeArray) || !is_array($changeArray)) |
467
|
|
|
return; |
468
|
|
|
|
469
|
|
|
$toRemove = array(); |
470
|
|
|
|
471
|
|
|
// Go check if there is any setting to be removed. |
472
|
|
|
foreach ($changeArray as $k => $v) |
473
|
|
|
if ($v === null) |
474
|
|
|
{ |
475
|
|
|
// Found some, remove them from the original array and add them to ours. |
476
|
|
|
unset($changeArray[$k]); |
477
|
|
|
$toRemove[] = $k; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
// Proceed with the deletion. |
481
|
|
|
if (!empty($toRemove)) |
482
|
|
|
$smcFunc['db_query']('', ' |
483
|
|
|
DELETE FROM {db_prefix}settings |
484
|
|
|
WHERE variable IN ({array_string:remove})', |
485
|
|
|
array( |
486
|
|
|
'remove' => $toRemove, |
487
|
|
|
) |
488
|
|
|
); |
489
|
|
|
|
490
|
|
|
// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs. |
491
|
|
|
if ($update) |
492
|
|
|
{ |
493
|
|
|
foreach ($changeArray as $variable => $value) |
494
|
|
|
{ |
495
|
|
|
$smcFunc['db_query']('', ' |
496
|
|
|
UPDATE {db_prefix}settings |
497
|
|
|
SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value} |
498
|
|
|
WHERE variable = {string:variable}', |
499
|
|
|
array( |
500
|
|
|
'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value), |
501
|
|
|
'variable' => $variable, |
502
|
|
|
) |
503
|
|
|
); |
504
|
|
|
$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value); |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
// Clean out the cache and make sure the cobwebs are gone too. |
508
|
|
|
cache_put_data('modSettings', null, 90); |
509
|
|
|
|
510
|
|
|
return; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
$replaceArray = array(); |
514
|
|
|
foreach ($changeArray as $variable => $value) |
515
|
|
|
{ |
516
|
|
|
// Don't bother if it's already like that ;). |
517
|
|
|
if (isset($modSettings[$variable]) && $modSettings[$variable] == $value) |
518
|
|
|
continue; |
519
|
|
|
// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it. |
520
|
|
|
elseif (!isset($modSettings[$variable]) && empty($value)) |
521
|
|
|
continue; |
522
|
|
|
|
523
|
|
|
$replaceArray[] = array($variable, $value); |
524
|
|
|
|
525
|
|
|
$modSettings[$variable] = $value; |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
if (empty($replaceArray)) |
529
|
|
|
return; |
530
|
|
|
|
531
|
|
|
$smcFunc['db_insert']('replace', |
532
|
|
|
'{db_prefix}settings', |
533
|
|
|
array('variable' => 'string-255', 'value' => 'string-65534'), |
534
|
|
|
$replaceArray, |
535
|
|
|
array('variable') |
536
|
|
|
); |
537
|
|
|
|
538
|
|
|
// Kill the cache - it needs redoing now, but we won't bother ourselves with that here. |
539
|
|
|
cache_put_data('modSettings', null, 90); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Constructs a page list. |
544
|
|
|
* |
545
|
|
|
* - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15. |
546
|
|
|
* - flexible_start causes it to use "url.page" instead of "url;start=page". |
547
|
|
|
* - very importantly, cleans up the start value passed, and forces it to |
548
|
|
|
* be a multiple of num_per_page. |
549
|
|
|
* - checks that start is not more than max_value. |
550
|
|
|
* - base_url should be the URL without any start parameter on it. |
551
|
|
|
* - uses the compactTopicPagesEnable and compactTopicPagesContiguous |
552
|
|
|
* settings to decide how to display the menu. |
553
|
|
|
* |
554
|
|
|
* an example is available near the function definition. |
555
|
|
|
* $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true); |
556
|
|
|
* |
557
|
|
|
* @param string $base_url The basic URL to be used for each link. |
558
|
|
|
* @param int &$start The start position, by reference. If this is not a multiple of the number of items per page, it is sanitized to be so and the value will persist upon the function's return. |
559
|
|
|
* @param int $max_value The total number of items you are paginating for. |
560
|
|
|
* @param int $num_per_page The number of items to be displayed on a given page. $start will be forced to be a multiple of this value. |
561
|
|
|
* @param bool $flexible_start Whether a ;start=x component should be introduced into the URL automatically (see above) |
562
|
|
|
* @param bool $show_prevnext Whether the Previous and Next links should be shown (should be on only when navigating the list) |
563
|
|
|
* |
564
|
|
|
* @return string The complete HTML of the page index that was requested, formatted by the template. |
565
|
|
|
*/ |
566
|
|
|
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true) |
567
|
|
|
{ |
568
|
|
|
global $modSettings, $context, $smcFunc, $settings, $txt; |
|
|
|
|
569
|
|
|
|
570
|
|
|
// Save whether $start was less than 0 or not. |
571
|
|
|
$start = (int) $start; |
572
|
|
|
$start_invalid = $start < 0; |
573
|
|
|
|
574
|
|
|
// Make sure $start is a proper variable - not less than 0. |
575
|
|
|
if ($start_invalid) |
576
|
|
|
$start = 0; |
577
|
|
|
// Not greater than the upper bound. |
578
|
|
|
elseif ($start >= $max_value) |
579
|
|
|
$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page))); |
580
|
|
|
// And it has to be a multiple of $num_per_page! |
581
|
|
|
else |
582
|
|
|
$start = max(0, (int) $start - ((int) $start % (int) $num_per_page)); |
583
|
|
|
|
584
|
|
|
$context['current_page'] = $start / $num_per_page; |
585
|
|
|
|
586
|
|
|
// Define some default page index settings if we don't already have it... |
587
|
|
|
if (!isset($settings['page_index'])) |
588
|
|
|
{ |
589
|
|
|
// This defines the formatting for the page indexes used throughout the forum. |
590
|
|
|
$settings['page_index'] = array( |
591
|
|
|
'extra_before' => '<span class="pages">' . $txt['pages'] . '</span>', |
592
|
|
|
'previous_page' => '<span class="generic_icons previous_page"></span>', |
593
|
|
|
'current_page' => '<span class="current_page">%1$d</span> ', |
594
|
|
|
'page' => '<a class="navPages" href="{URL}">%2$s</a> ', |
595
|
|
|
'expand_pages' => '<span class="expand_pages" onclick="expandPages(this, {LINK}, {FIRST_PAGE}, {LAST_PAGE}, {PER_PAGE});"> ... </span>', |
596
|
|
|
'next_page' => '<span class="generic_icons next_page"></span>', |
597
|
|
|
'extra_after' => '', |
598
|
|
|
); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
$base_link = strtr($settings['page_index']['page'], array('{URL}' => $flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')); |
602
|
|
|
$pageindex = $settings['page_index']['extra_before']; |
603
|
|
|
|
604
|
|
|
// Compact pages is off or on? |
605
|
|
|
if (empty($modSettings['compactTopicPagesEnable'])) |
606
|
|
|
{ |
607
|
|
|
// Show the left arrow. |
608
|
|
|
$pageindex .= $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']); |
609
|
|
|
|
610
|
|
|
// Show all the pages. |
611
|
|
|
$display_page = 1; |
612
|
|
|
for ($counter = 0; $counter < $max_value; $counter += $num_per_page) |
613
|
|
|
$pageindex .= $start == $counter && !$start_invalid ? sprintf($settings['page_index']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++); |
614
|
|
|
|
615
|
|
|
// Show the right arrow. |
616
|
|
|
$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page); |
617
|
|
|
if ($start != $counter - $max_value && !$start_invalid) |
618
|
|
|
$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, $settings['page_index']['next_page']); |
619
|
|
|
} |
620
|
|
|
else |
621
|
|
|
{ |
622
|
|
|
// If they didn't enter an odd value, pretend they did. |
623
|
|
|
$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2; |
624
|
|
|
|
625
|
|
|
// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page) |
626
|
|
|
if (!empty($start) && $show_prevnext) |
627
|
|
|
$pageindex .= sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']); |
628
|
|
|
else |
629
|
|
|
$pageindex .= ''; |
630
|
|
|
|
631
|
|
|
// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15) |
|
|
|
|
632
|
|
|
if ($start > $num_per_page * $PageContiguous) |
633
|
|
|
$pageindex .= sprintf($base_link, 0, '1'); |
634
|
|
|
|
635
|
|
|
// Show the ... after the first page. (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page) |
636
|
|
|
if ($start > $num_per_page * ($PageContiguous + 1)) |
637
|
|
|
$pageindex .= strtr($settings['page_index']['expand_pages'], array( |
638
|
|
|
'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)), |
639
|
|
|
'{FIRST_PAGE}' => $num_per_page, |
640
|
|
|
'{LAST_PAGE}' => $start - $num_per_page * $PageContiguous, |
641
|
|
|
'{PER_PAGE}' => $num_per_page, |
642
|
|
|
)); |
643
|
|
|
|
644
|
|
|
// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page) |
645
|
|
|
for ($nCont = $PageContiguous; $nCont >= 1; $nCont--) |
646
|
|
View Code Duplication |
if ($start >= $num_per_page * $nCont) |
647
|
|
|
{ |
648
|
|
|
$tmpStart = $start - $num_per_page * $nCont; |
649
|
|
|
$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page) |
653
|
|
|
if (!$start_invalid) |
654
|
|
|
$pageindex .= sprintf($settings['page_index']['current_page'], $start / $num_per_page + 1); |
655
|
|
|
else |
656
|
|
|
$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1); |
657
|
|
|
|
658
|
|
|
// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page) |
659
|
|
|
$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page; |
660
|
|
|
for ($nCont = 1; $nCont <= $PageContiguous; $nCont++) |
661
|
|
View Code Duplication |
if ($start + $num_per_page * $nCont <= $tmpMaxPages) |
662
|
|
|
{ |
663
|
|
|
$tmpStart = $start + $num_per_page * $nCont; |
664
|
|
|
$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1); |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page) |
668
|
|
|
if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages) |
669
|
|
|
$pageindex .= strtr($settings['page_index']['expand_pages'], array( |
670
|
|
|
'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)), |
671
|
|
|
'{FIRST_PAGE}' => $start + $num_per_page * ($PageContiguous + 1), |
672
|
|
|
'{LAST_PAGE}' => $tmpMaxPages, |
673
|
|
|
'{PER_PAGE}' => $num_per_page, |
674
|
|
|
)); |
675
|
|
|
|
676
|
|
|
// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15< next page) |
677
|
|
|
if ($start + $num_per_page * $PageContiguous < $tmpMaxPages) |
678
|
|
|
$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1); |
679
|
|
|
|
680
|
|
|
// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<) |
681
|
|
|
if ($start != $tmpMaxPages && $show_prevnext) |
682
|
|
|
$pageindex .= sprintf($base_link, $start + $num_per_page, $settings['page_index']['next_page']); |
683
|
|
|
} |
684
|
|
|
$pageindex .= $settings['page_index']['extra_after']; |
685
|
|
|
|
686
|
|
|
return $pageindex; |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
/** |
690
|
|
|
* - Formats a number. |
691
|
|
|
* - uses the format of number_format to decide how to format the number. |
692
|
|
|
* for example, it might display "1 234,50". |
693
|
|
|
* - caches the formatting data from the setting for optimization. |
694
|
|
|
* |
695
|
|
|
* @param float $number A number |
696
|
|
|
* @param bool|int $override_decimal_count If set, will use the specified number of decimal places. Otherwise it's automatically determined |
697
|
|
|
* @return string A formatted number |
|
|
|
|
698
|
|
|
*/ |
699
|
|
|
function comma_format($number, $override_decimal_count = false) |
700
|
|
|
{ |
701
|
|
|
global $txt; |
|
|
|
|
702
|
|
|
static $thousands_separator = null, $decimal_separator = null, $decimal_count = null; |
703
|
|
|
|
704
|
|
|
// Cache these values... |
705
|
|
|
if ($decimal_separator === null) |
706
|
|
|
{ |
707
|
|
|
// Not set for whatever reason? |
708
|
|
|
if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1) |
709
|
|
|
return $number; |
710
|
|
|
|
711
|
|
|
// Cache these each load... |
712
|
|
|
$thousands_separator = $matches[1]; |
713
|
|
|
$decimal_separator = $matches[2]; |
714
|
|
|
$decimal_count = strlen($matches[3]); |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
// Format the string with our friend, number_format. |
718
|
|
|
return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator); |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
/** |
722
|
|
|
* Format a time to make it look purdy. |
723
|
|
|
* |
724
|
|
|
* - returns a pretty formatted version of time based on the user's format in $user_info['time_format']. |
725
|
|
|
* - applies all necessary time offsets to the timestamp, unless offset_type is set. |
726
|
|
|
* - if todayMod is set and show_today was not not specified or true, an |
727
|
|
|
* alternate format string is used to show the date with something to show it is "today" or "yesterday". |
728
|
|
|
* - performs localization (more than just strftime would do alone.) |
729
|
|
|
* |
730
|
|
|
* @param int $log_time A timestamp |
731
|
|
|
* @param bool $show_today Whether to show "Today"/"Yesterday" or just a date |
732
|
|
|
* @param bool|string $offset_type If false, uses both user time offset and forum offset. If 'forum', uses only the forum offset. Otherwise no offset is applied. |
733
|
|
|
* @param bool $process_safe Activate setlocale check for changes at runtime. Slower, but safer. |
734
|
|
|
* @return string A formatted timestamp |
735
|
|
|
*/ |
736
|
|
|
function timeformat($log_time, $show_today = true, $offset_type = false, $process_safe = false) |
737
|
|
|
{ |
738
|
|
|
global $context, $user_info, $txt, $modSettings; |
|
|
|
|
739
|
|
|
static $non_twelve_hour, $locale_cache; |
740
|
|
|
static $unsupportedFormats, $finalizedFormats; |
741
|
|
|
|
742
|
|
|
// Offset the time. |
743
|
|
|
if (!$offset_type) |
744
|
|
|
$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600; |
745
|
|
|
// Just the forum offset? |
746
|
|
|
elseif ($offset_type == 'forum') |
747
|
|
|
$time = $log_time + $modSettings['time_offset'] * 3600; |
748
|
|
|
else |
749
|
|
|
$time = $log_time; |
750
|
|
|
|
751
|
|
|
// We can't have a negative date (on Windows, at least.) |
752
|
|
|
if ($log_time < 0) |
753
|
|
|
$log_time = 0; |
754
|
|
|
|
755
|
|
|
// Today and Yesterday? |
756
|
|
|
if ($modSettings['todayMod'] >= 1 && $show_today === true) |
757
|
|
|
{ |
758
|
|
|
// Get the current time. |
759
|
|
|
$nowtime = forum_time(); |
760
|
|
|
|
761
|
|
|
$then = @getdate($time); |
762
|
|
|
$now = @getdate($nowtime); |
763
|
|
|
|
764
|
|
|
// Try to make something of a time format string... |
765
|
|
|
$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S'; |
|
|
|
|
766
|
|
|
if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false) |
767
|
|
|
{ |
768
|
|
|
$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l'; |
|
|
|
|
769
|
|
|
$today_fmt = $h . ':%M' . $s . ' %p'; |
770
|
|
|
} |
771
|
|
|
else |
772
|
|
|
$today_fmt = '%H:%M' . $s; |
773
|
|
|
|
774
|
|
|
// Same day of the year, same year.... Today! |
775
|
|
|
if ($then['yday'] == $now['yday'] && $then['year'] == $now['year']) |
776
|
|
|
return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type); |
|
|
|
|
777
|
|
|
|
778
|
|
|
// Day-of-year is one less and same year, or it's the first of the year and that's the last of the year... |
779
|
|
|
if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31)) |
780
|
|
|
return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type); |
|
|
|
|
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
$str = !is_bool($show_today) ? $show_today : $user_info['time_format']; |
784
|
|
|
|
785
|
|
|
// Use the cached formats if available |
786
|
|
|
if (is_null($finalizedFormats)) |
787
|
|
|
$finalizedFormats = (array) cache_get_data('timeformatstrings', 86400); |
788
|
|
|
|
789
|
|
|
// Make a supported version for this format if we don't already have one |
790
|
|
|
if (empty($finalizedFormats[$str])) |
791
|
|
|
{ |
792
|
|
|
$timeformat = $str; |
793
|
|
|
|
794
|
|
|
// Not all systems support all formats, and Windows fails altogether if unsupported ones are |
795
|
|
|
// used, so let's prevent that. Some substitutions go to the nearest reasonable fallback, some |
796
|
|
|
// turn into static strings, some (i.e. %a, %A, $b, %B, %p) have special handling below. |
797
|
|
|
$strftimeFormatSubstitutions = array( |
798
|
|
|
// Day |
799
|
|
|
'a' => '%a', 'A' => '%A', 'e' => '%d', 'd' => '%d', 'j' => '%j', 'u' => '%w', 'w' => '%w', |
800
|
|
|
// Week |
801
|
|
|
'U' => '%U', 'V' => '%U', 'W' => '%U', |
802
|
|
|
// Month |
803
|
|
|
'b' => '%b', 'B' => '%B', 'h' => '%b', 'm' => '%b', |
804
|
|
|
// Year |
805
|
|
|
'C' => '%C', 'g' => '%y', 'G' => '%Y', 'y' => '%y', 'Y' => '%Y', |
806
|
|
|
// Time |
807
|
|
|
'H' => '%H', 'k' => '%H', 'I' => '%H', 'l' => '%I', 'M' => '%M', 'p' => '%p', 'P' => '%p', |
808
|
|
|
'r' => '%I:%M:%S %p', 'R' => '%H:%M', 'S' => '%S', 'T' => '%H:%M:%S', 'X' => '%T', 'z' => '%z', 'Z' => '%Z', |
809
|
|
|
// Time and Date Stamps |
810
|
|
|
'c' => '%F %T', 'D' => '%m/%d/%y', 'F' => '%Y-%m-%d', 's' => '%s', 'x' => '%F', |
811
|
|
|
// Miscellaneous |
812
|
|
|
'n' => "\n", 't' => "\t", '%' => '%', |
813
|
|
|
); |
814
|
|
|
|
815
|
|
|
// No need to do this part again if we already did it once |
816
|
|
|
if (is_null($unsupportedFormats)) |
817
|
|
|
$unsupportedFormats = (array) cache_get_data('unsupportedtimeformats', 86400); |
818
|
|
|
if (empty($unsupportedFormats)) |
819
|
|
|
{ |
820
|
|
|
foreach($strftimeFormatSubstitutions as $format => $substitution) |
821
|
|
|
{ |
822
|
|
|
$value = @strftime('%' . $format); |
823
|
|
|
|
824
|
|
|
// Windows will return false for unsupported formats |
825
|
|
|
// Other operating systems return the format string as a literal |
826
|
|
|
if ($value === false || $value === $format) |
827
|
|
|
$unsupportedFormats[] = $format; |
828
|
|
|
} |
829
|
|
|
cache_put_data('unsupportedtimeformats', $unsupportedFormats, 86400); |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
// Windows needs extra help if $timeformat contains something completely invalid, e.g. '%Q' |
833
|
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') |
834
|
|
|
$timeformat = preg_replace('~%(?!' . implode('|', array_keys($strftimeFormatSubstitutions)) . ')~', '%', $timeformat); |
835
|
|
|
|
836
|
|
|
// Substitute unsupported formats with supported ones |
837
|
|
|
if (!empty($unsupportedFormats)) |
838
|
|
|
while (preg_match('~%(' . implode('|', $unsupportedFormats) . ')~', $timeformat, $matches)) |
839
|
|
|
$timeformat = str_replace($matches[0], $strftimeFormatSubstitutions[$matches[1]], $timeformat); |
840
|
|
|
|
841
|
|
|
// Remember this so we don't need to do it again |
842
|
|
|
$finalizedFormats[$str] = $timeformat; |
843
|
|
|
cache_put_data('timeformatstrings', $finalizedFormats, 86400); |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
$str = $finalizedFormats[$str]; |
847
|
|
|
|
848
|
|
|
if (!isset($locale_cache)) |
849
|
|
|
$locale_cache = setlocale(LC_TIME, $txt['lang_locale']); |
850
|
|
|
|
851
|
|
|
if ($locale_cache !== false) |
852
|
|
|
{ |
853
|
|
|
// Check if another process changed the locale |
854
|
|
|
if ($process_safe === true && setlocale(LC_TIME, '0') != $locale_cache) |
855
|
|
|
setlocale(LC_TIME, $txt['lang_locale']); |
856
|
|
|
|
857
|
|
|
if (!isset($non_twelve_hour)) |
858
|
|
|
$non_twelve_hour = trim(strftime('%p')) === ''; |
859
|
|
View Code Duplication |
if ($non_twelve_hour && strpos($str, '%p') !== false) |
860
|
|
|
$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); |
861
|
|
|
|
862
|
|
|
foreach (array('%a', '%A', '%b', '%B') as $token) |
863
|
|
|
if (strpos($str, $token) !== false) |
864
|
|
|
$str = str_replace($token, strftime($token, $time), $str); |
865
|
|
|
} |
866
|
|
|
else |
867
|
|
|
{ |
868
|
|
|
// Do-it-yourself time localization. Fun. |
869
|
|
|
foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label) |
870
|
|
|
if (strpos($str, $token) !== false) |
871
|
|
|
$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str); |
872
|
|
|
|
873
|
|
View Code Duplication |
if (strpos($str, '%p') !== false) |
874
|
|
|
$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str); |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
// Format the time and then restore any literal percent characters |
878
|
|
|
return str_replace('%', '%', strftime($str, $time)); |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
/** |
882
|
|
|
* Removes special entities from strings. Compatibility... |
883
|
|
|
* Should be used instead of html_entity_decode for PHP version compatibility reasons. |
884
|
|
|
* |
885
|
|
|
* - removes the base entities (<, ", etc.) from text. |
886
|
|
|
* - additionally converts and '. |
887
|
|
|
* |
888
|
|
|
* @param string $string A string |
889
|
|
|
* @return string The string without entities |
890
|
|
|
*/ |
891
|
|
|
function un_htmlspecialchars($string) |
892
|
|
|
{ |
893
|
|
|
global $context; |
|
|
|
|
894
|
|
|
static $translation = array(); |
895
|
|
|
|
896
|
|
|
// Determine the character set... Default to UTF-8 |
897
|
|
|
if (empty($context['character_set'])) |
898
|
|
|
$charset = 'UTF-8'; |
899
|
|
|
// Use ISO-8859-1 in place of non-supported ISO-8859 charsets... |
900
|
|
|
elseif (strpos($context['character_set'], 'ISO-8859-') !== false && !in_array($context['character_set'], array('ISO-8859-5', 'ISO-8859-15'))) |
901
|
|
|
$charset = 'ISO-8859-1'; |
902
|
|
|
else |
903
|
|
|
$charset = $context['character_set']; |
904
|
|
|
|
905
|
|
|
if (empty($translation)) |
906
|
|
|
$translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES, $charset)) + array(''' => '\'', ''' => '\'', ' ' => ' '); |
907
|
|
|
|
908
|
|
|
return strtr($string, $translation); |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* Shorten a subject + internationalization concerns. |
913
|
|
|
* |
914
|
|
|
* - shortens a subject so that it is either shorter than length, or that length plus an ellipsis. |
915
|
|
|
* - respects internationalization characters and entities as one character. |
916
|
|
|
* - avoids trailing entities. |
917
|
|
|
* - returns the shortened string. |
918
|
|
|
* |
919
|
|
|
* @param string $subject The subject |
920
|
|
|
* @param int $len How many characters to limit it to |
921
|
|
|
* @return string The shortened subject - either the entire subject (if it's <= $len) or the subject shortened to $len characters with "..." appended |
922
|
|
|
*/ |
923
|
|
|
function shorten_subject($subject, $len) |
924
|
|
|
{ |
925
|
|
|
global $smcFunc; |
|
|
|
|
926
|
|
|
|
927
|
|
|
// It was already short enough! |
928
|
|
|
if ($smcFunc['strlen']($subject) <= $len) |
929
|
|
|
return $subject; |
930
|
|
|
|
931
|
|
|
// Shorten it by the length it was too long, and strip off junk from the end. |
932
|
|
|
return $smcFunc['substr']($subject, 0, $len) . '...'; |
933
|
|
|
} |
934
|
|
|
|
935
|
|
|
/** |
936
|
|
|
* Gets the current time with offset. |
937
|
|
|
* |
938
|
|
|
* - always applies the offset in the time_offset setting. |
939
|
|
|
* |
940
|
|
|
* @param bool $use_user_offset Whether to apply the user's offset as well |
941
|
|
|
* @param int $timestamp A timestamp (null to use current time) |
|
|
|
|
942
|
|
|
* @return int Seconds since the unix epoch, with forum time offset and (optionally) user time offset applied |
|
|
|
|
943
|
|
|
*/ |
944
|
|
|
function forum_time($use_user_offset = true, $timestamp = null) |
945
|
|
|
{ |
946
|
|
|
global $user_info, $modSettings; |
|
|
|
|
947
|
|
|
|
948
|
|
|
if ($timestamp === null) |
949
|
|
|
$timestamp = time(); |
950
|
|
|
elseif ($timestamp == 0) |
951
|
|
|
return 0; |
952
|
|
|
|
953
|
|
|
return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600; |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
/** |
957
|
|
|
* Calculates all the possible permutations (orders) of array. |
958
|
|
|
* should not be called on huge arrays (bigger than like 10 elements.) |
959
|
|
|
* returns an array containing each permutation. |
960
|
|
|
* |
961
|
|
|
* @deprecated since 2.1 |
962
|
|
|
* @param array $array An array |
963
|
|
|
* @return array An array containing each permutation |
|
|
|
|
964
|
|
|
*/ |
965
|
|
|
function permute($array) |
966
|
|
|
{ |
967
|
|
|
$orders = array($array); |
968
|
|
|
|
969
|
|
|
$n = count($array); |
|
|
|
|
970
|
|
|
$p = range(0, $n); |
|
|
|
|
971
|
|
|
for ($i = 1; $i < $n; null) |
972
|
|
|
{ |
973
|
|
|
$p[$i]--; |
974
|
|
|
$j = $i % 2 != 0 ? $p[$i] : 0; |
|
|
|
|
975
|
|
|
|
976
|
|
|
$temp = $array[$i]; |
977
|
|
|
$array[$i] = $array[$j]; |
978
|
|
|
$array[$j] = $temp; |
979
|
|
|
|
980
|
|
|
for ($i = 1; $p[$i] == 0; $i++) |
981
|
|
|
$p[$i] = 1; |
982
|
|
|
|
983
|
|
|
$orders[] = $array; |
984
|
|
|
} |
985
|
|
|
|
986
|
|
|
return $orders; |
987
|
|
|
} |
988
|
|
|
|
989
|
|
|
/** |
990
|
|
|
* Parse bulletin board code in a string, as well as smileys optionally. |
991
|
|
|
* |
992
|
|
|
* - only parses bbc tags which are not disabled in disabledBBC. |
993
|
|
|
* - handles basic HTML, if enablePostHTML is on. |
994
|
|
|
* - caches the from/to replace regular expressions so as not to reload them every time a string is parsed. |
995
|
|
|
* - only parses smileys if smileys is true. |
996
|
|
|
* - does nothing if the enableBBC setting is off. |
997
|
|
|
* - uses the cache_id as a unique identifier to facilitate any caching it may do. |
998
|
|
|
* -returns the modified message. |
999
|
|
|
* |
1000
|
|
|
* @param string $message The message |
1001
|
|
|
* @param bool $smileys Whether to parse smileys as well |
1002
|
|
|
* @param string $cache_id The cache ID |
1003
|
|
|
* @param array $parse_tags If set, only parses these tags rather than all of them |
1004
|
|
|
* @return string The parsed message |
1005
|
|
|
*/ |
1006
|
|
|
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array()) |
1007
|
|
|
{ |
1008
|
|
|
global $smcFunc, $txt, $scripturl, $context, $modSettings, $user_info, $sourcedir; |
|
|
|
|
1009
|
|
|
static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array(); |
1010
|
|
|
static $disabled; |
1011
|
|
|
|
1012
|
|
|
// Don't waste cycles |
1013
|
|
|
if ($message === '') |
1014
|
|
|
return ''; |
1015
|
|
|
|
1016
|
|
|
// Just in case it wasn't determined yet whether UTF-8 is enabled. |
1017
|
|
|
if (!isset($context['utf8'])) |
1018
|
|
|
$context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8'; |
1019
|
|
|
|
1020
|
|
|
// Clean up any cut/paste issues we may have |
1021
|
|
|
$message = sanitizeMSCutPaste($message); |
1022
|
|
|
|
1023
|
|
|
// If the load average is too high, don't parse the BBC. |
1024
|
|
|
if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc']) |
1025
|
|
|
{ |
1026
|
|
|
$context['disabled_parse_bbc'] = true; |
1027
|
|
|
return $message; |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
if ($smileys !== null && ($smileys == '1' || $smileys == '0')) |
1031
|
|
|
$smileys = (bool) $smileys; |
1032
|
|
|
|
1033
|
|
|
if (empty($modSettings['enableBBC']) && $message !== false) |
1034
|
|
|
{ |
1035
|
|
|
if ($smileys === true) |
1036
|
|
|
parsesmileys($message); |
1037
|
|
|
|
1038
|
|
|
return $message; |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
// If we are not doing every tag then we don't cache this run. |
1042
|
|
|
if (!empty($parse_tags) && !empty($bbc_codes)) |
1043
|
|
|
{ |
1044
|
|
|
$temp_bbc = $bbc_codes; |
1045
|
|
|
$bbc_codes = array(); |
1046
|
|
|
} |
1047
|
|
|
|
1048
|
|
|
// Ensure $modSettings['tld_regex'] contains a valid regex for the autolinker |
1049
|
|
|
if (!empty($modSettings['autoLinkUrls'])) |
1050
|
|
|
set_tld_regex(); |
1051
|
|
|
|
1052
|
|
|
// Allow mods access before entering the main parse_bbc loop |
1053
|
|
|
call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags)); |
1054
|
|
|
|
1055
|
|
|
// Sift out the bbc for a performance improvement. |
1056
|
|
|
if (empty($bbc_codes) || $message === false || !empty($parse_tags)) |
1057
|
|
|
{ |
1058
|
|
|
if (!empty($modSettings['disabledBBC'])) |
1059
|
|
|
{ |
1060
|
|
|
$disabled = array(); |
1061
|
|
|
|
1062
|
|
|
$temp = explode(',', strtolower($modSettings['disabledBBC'])); |
1063
|
|
|
|
1064
|
|
|
foreach ($temp as $tag) |
1065
|
|
|
$disabled[trim($tag)] = true; |
1066
|
|
|
} |
1067
|
|
|
|
1068
|
|
|
if (empty($modSettings['enableEmbeddedFlash'])) |
1069
|
|
|
$disabled['flash'] = true; |
1070
|
|
|
|
1071
|
|
|
/* The following bbc are formatted as an array, with keys as follows: |
1072
|
|
|
|
1073
|
|
|
tag: the tag's name - should be lowercase! |
1074
|
|
|
|
1075
|
|
|
type: one of... |
1076
|
|
|
- (missing): [tag]parsed content[/tag] |
1077
|
|
|
- unparsed_equals: [tag=xyz]parsed content[/tag] |
1078
|
|
|
- parsed_equals: [tag=parsed data]parsed content[/tag] |
1079
|
|
|
- unparsed_content: [tag]unparsed content[/tag] |
1080
|
|
|
- closed: [tag], [tag/], [tag /] |
1081
|
|
|
- unparsed_commas: [tag=1,2,3]parsed content[/tag] |
1082
|
|
|
- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag] |
1083
|
|
|
- unparsed_equals_content: [tag=...]unparsed content[/tag] |
1084
|
|
|
|
1085
|
|
|
parameters: an optional array of parameters, for the form |
1086
|
|
|
[tag abc=123]content[/tag]. The array is an associative array |
1087
|
|
|
where the keys are the parameter names, and the values are an |
1088
|
|
|
array which may contain the following: |
1089
|
|
|
- match: a regular expression to validate and match the value. |
1090
|
|
|
- quoted: true if the value should be quoted. |
1091
|
|
|
- validate: callback to evaluate on the data, which is $data. |
1092
|
|
|
- value: a string in which to replace $1 with the data. |
1093
|
|
|
either it or validate may be used, not both. |
1094
|
|
|
- optional: true if the parameter is optional. |
1095
|
|
|
|
1096
|
|
|
test: a regular expression to test immediately after the tag's |
1097
|
|
|
'=', ' ' or ']'. Typically, should have a \] at the end. |
1098
|
|
|
Optional. |
1099
|
|
|
|
1100
|
|
|
content: only available for unparsed_content, closed, |
1101
|
|
|
unparsed_commas_content, and unparsed_equals_content. |
1102
|
|
|
$1 is replaced with the content of the tag. Parameters |
1103
|
|
|
are replaced in the form {param}. For unparsed_commas_content, |
1104
|
|
|
$2, $3, ..., $n are replaced. |
1105
|
|
|
|
1106
|
|
|
before: only when content is not used, to go before any |
1107
|
|
|
content. For unparsed_equals, $1 is replaced with the value. |
1108
|
|
|
For unparsed_commas, $1, $2, ..., $n are replaced. |
1109
|
|
|
|
1110
|
|
|
after: similar to before in every way, except that it is used |
1111
|
|
|
when the tag is closed. |
1112
|
|
|
|
1113
|
|
|
disabled_content: used in place of content when the tag is |
1114
|
|
|
disabled. For closed, default is '', otherwise it is '$1' if |
1115
|
|
|
block_level is false, '<div>$1</div>' elsewise. |
1116
|
|
|
|
1117
|
|
|
disabled_before: used in place of before when disabled. Defaults |
1118
|
|
|
to '<div>' if block_level, '' if not. |
1119
|
|
|
|
1120
|
|
|
disabled_after: used in place of after when disabled. Defaults |
1121
|
|
|
to '</div>' if block_level, '' if not. |
1122
|
|
|
|
1123
|
|
|
block_level: set to true the tag is a "block level" tag, similar |
1124
|
|
|
to HTML. Block level tags cannot be nested inside tags that are |
1125
|
|
|
not block level, and will not be implicitly closed as easily. |
1126
|
|
|
One break following a block level tag may also be removed. |
1127
|
|
|
|
1128
|
|
|
trim: if set, and 'inside' whitespace after the begin tag will be |
1129
|
|
|
removed. If set to 'outside', whitespace after the end tag will |
1130
|
|
|
meet the same fate. |
1131
|
|
|
|
1132
|
|
|
validate: except when type is missing or 'closed', a callback to |
1133
|
|
|
validate the data as $data. Depending on the tag's type, $data |
1134
|
|
|
may be a string or an array of strings (corresponding to the |
1135
|
|
|
replacement.) |
1136
|
|
|
|
1137
|
|
|
quoted: when type is 'unparsed_equals' or 'parsed_equals' only, |
1138
|
|
|
may be not set, 'optional', or 'required' corresponding to if |
1139
|
|
|
the content may be quoted. This allows the parser to read |
1140
|
|
|
[tag="abc]def[esdf]"] properly. |
1141
|
|
|
|
1142
|
|
|
require_parents: an array of tag names, or not set. If set, the |
1143
|
|
|
enclosing tag *must* be one of the listed tags, or parsing won't |
1144
|
|
|
occur. |
1145
|
|
|
|
1146
|
|
|
require_children: similar to require_parents, if set children |
1147
|
|
|
won't be parsed if they are not in the list. |
1148
|
|
|
|
1149
|
|
|
disallow_children: similar to, but very different from, |
1150
|
|
|
require_children, if it is set the listed tags will not be |
1151
|
|
|
parsed inside the tag. |
1152
|
|
|
|
1153
|
|
|
parsed_tags_allowed: an array restricting what BBC can be in the |
1154
|
|
|
parsed_equals parameter, if desired. |
1155
|
|
|
*/ |
1156
|
|
|
|
1157
|
|
|
$codes = array( |
1158
|
|
|
array( |
1159
|
|
|
'tag' => 'abbr', |
1160
|
|
|
'type' => 'unparsed_equals', |
1161
|
|
|
'before' => '<abbr title="$1">', |
1162
|
|
|
'after' => '</abbr>', |
1163
|
|
|
'quoted' => 'optional', |
1164
|
|
|
'disabled_after' => ' ($1)', |
1165
|
|
|
), |
1166
|
|
|
array( |
1167
|
|
|
'tag' => 'anchor', |
1168
|
|
|
'type' => 'unparsed_equals', |
1169
|
|
|
'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]', |
1170
|
|
|
'before' => '<span id="post_$1">', |
1171
|
|
|
'after' => '</span>', |
1172
|
|
|
), |
1173
|
|
|
array( |
1174
|
|
|
'tag' => 'attach', |
1175
|
|
|
'type' => 'unparsed_content', |
1176
|
|
|
'parameters' => array( |
1177
|
|
|
'name' => array('optional' => true), |
1178
|
|
|
'type' => array('optional' => true), |
1179
|
|
|
'alt' => array('optional' => true), |
1180
|
|
|
'title' => array('optional' => true), |
1181
|
|
|
'width' => array('optional' => true, 'match' => '(\d+)'), |
1182
|
|
|
'height' => array('optional' => true, 'match' => '(\d+)'), |
1183
|
|
|
), |
1184
|
|
|
'content' => '$1', |
1185
|
|
|
'validate' => function (&$tag, &$data, $disabled, $params) use ($modSettings, $context, $sourcedir, $txt) |
1186
|
|
|
{ |
1187
|
|
|
$returnContext = ''; |
1188
|
|
|
|
1189
|
|
|
// BBC or the entire attachments feature is disabled |
1190
|
|
|
if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach'])) |
1191
|
|
|
return $data; |
1192
|
|
|
|
1193
|
|
|
// Save the attach ID. |
1194
|
|
|
$attachID = $data; |
1195
|
|
|
|
1196
|
|
|
// Kinda need this. |
1197
|
|
|
require_once($sourcedir . '/Subs-Attachments.php'); |
1198
|
|
|
|
1199
|
|
|
$currentAttachment = parseAttachBBC($attachID); |
1200
|
|
|
|
1201
|
|
|
// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do. |
1202
|
|
|
if (is_string($currentAttachment)) |
1203
|
|
|
return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment; |
1204
|
|
|
|
1205
|
|
|
if (!empty($currentAttachment['is_image'])) |
1206
|
|
|
{ |
1207
|
|
|
$alt = ' alt="' . (!empty($params['{alt}']) ? $params['{alt}'] : $currentAttachment['name']) . '"'; |
1208
|
|
|
$title = !empty($params['{title}']) ? ' title="' . $params['{title}'] . '"' : ''; |
1209
|
|
|
|
1210
|
|
|
$width = !empty($params['{width}']) ? ' width="' . $params['{width}'] . '"' : ''; |
1211
|
|
|
$height = !empty($params['{height}']) ? ' height="' . $params['{height}'] . '"' : ''; |
1212
|
|
|
|
1213
|
|
|
if (empty($width) && empty($height)) |
1214
|
|
|
{ |
1215
|
|
|
$width = ' width="' . $currentAttachment['width'] . '"'; |
1216
|
|
|
$height = ' height="' . $currentAttachment['height'] . '"'; |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
if ($currentAttachment['thumbnail']['has_thumb'] && empty($params['{width}']) && empty($params['{height}'])) |
1220
|
|
|
$returnContext .= '<a href="'. $currentAttachment['href']. ';image" id="link_'. $currentAttachment['id']. '" onclick="'. $currentAttachment['thumbnail']['javascript']. '"><img src="'. $currentAttachment['thumbnail']['href']. '"' . $alt . $title . ' id="thumb_'. $currentAttachment['id']. '" class="atc_img"></a>'; |
1221
|
|
|
else |
1222
|
|
|
$returnContext .= '<img src="' . $currentAttachment['href'] . ';image"' . $alt . $title . $width . $height . ' class="bbc_img"/>'; |
1223
|
|
|
} |
1224
|
|
|
|
1225
|
|
|
// No image. Show a link. |
1226
|
|
|
else |
1227
|
|
|
$returnContext .= $currentAttachment['link']; |
1228
|
|
|
|
1229
|
|
|
// Gotta append what we just did. |
1230
|
|
|
$data = $returnContext; |
1231
|
|
|
}, |
1232
|
|
|
), |
1233
|
|
|
array( |
1234
|
|
|
'tag' => 'b', |
1235
|
|
|
'before' => '<b>', |
1236
|
|
|
'after' => '</b>', |
1237
|
|
|
), |
1238
|
|
|
array( |
1239
|
|
|
'tag' => 'center', |
1240
|
|
|
'before' => '<div class="centertext">', |
1241
|
|
|
'after' => '</div>', |
1242
|
|
|
'block_level' => true, |
1243
|
|
|
), |
1244
|
|
|
array( |
1245
|
|
|
'tag' => 'code', |
1246
|
|
|
'type' => 'unparsed_content', |
1247
|
|
|
'content' => '<div class="codeheader"><span class="code floatleft">' . $txt['code'] . '</span> <a class="codeoperation smf_select_text">' . $txt['code_select'] . '</a></div><code class="bbc_code">$1</code>', |
1248
|
|
|
// @todo Maybe this can be simplified? |
|
|
|
|
1249
|
|
|
'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context) |
1250
|
|
|
{ |
1251
|
|
|
if (!isset($disabled['code'])) |
1252
|
|
|
{ |
1253
|
|
|
$php_parts = preg_split('~(<\?php|\?>)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE); |
1254
|
|
|
|
1255
|
|
View Code Duplication |
for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) |
1256
|
|
|
{ |
1257
|
|
|
// Do PHP code coloring? |
1258
|
|
|
if ($php_parts[$php_i] != '<?php') |
1259
|
|
|
continue; |
1260
|
|
|
|
1261
|
|
|
$php_string = ''; |
1262
|
|
|
while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?>') |
1263
|
|
|
{ |
1264
|
|
|
$php_string .= $php_parts[$php_i]; |
1265
|
|
|
$php_parts[$php_i++] = ''; |
1266
|
|
|
} |
1267
|
|
|
$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
// Fix the PHP code stuff... |
1271
|
|
|
$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts)); |
1272
|
|
|
$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data); |
1273
|
|
|
|
1274
|
|
|
// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection. |
1275
|
|
|
if ($context['browser']['is_opera']) |
1276
|
|
|
$data .= ' '; |
1277
|
|
|
} |
1278
|
|
|
}, |
1279
|
|
|
'block_level' => true, |
1280
|
|
|
), |
1281
|
|
|
array( |
1282
|
|
|
'tag' => 'code', |
1283
|
|
|
'type' => 'unparsed_equals_content', |
1284
|
|
|
'content' => '<div class="codeheader"><span class="code floatleft">' . $txt['code'] . '</span> ($2) <a class="codeoperation smf_select_text">' . $txt['code_select'] . '</a></div><code class="bbc_code">$1</code>', |
1285
|
|
|
// @todo Maybe this can be simplified? |
|
|
|
|
1286
|
|
|
'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context) |
1287
|
|
|
{ |
1288
|
|
|
if (!isset($disabled['code'])) |
1289
|
|
|
{ |
1290
|
|
|
$php_parts = preg_split('~(<\?php|\?>)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE); |
1291
|
|
|
|
1292
|
|
View Code Duplication |
for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) |
1293
|
|
|
{ |
1294
|
|
|
// Do PHP code coloring? |
1295
|
|
|
if ($php_parts[$php_i] != '<?php') |
1296
|
|
|
continue; |
1297
|
|
|
|
1298
|
|
|
$php_string = ''; |
1299
|
|
|
while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?>') |
1300
|
|
|
{ |
1301
|
|
|
$php_string .= $php_parts[$php_i]; |
1302
|
|
|
$php_parts[$php_i++] = ''; |
1303
|
|
|
} |
1304
|
|
|
$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); |
1305
|
|
|
} |
1306
|
|
|
|
1307
|
|
|
// Fix the PHP code stuff... |
1308
|
|
|
$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts)); |
1309
|
|
|
$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]); |
1310
|
|
|
|
1311
|
|
|
// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection. |
1312
|
|
|
if ($context['browser']['is_opera']) |
1313
|
|
|
$data[0] .= ' '; |
1314
|
|
|
} |
1315
|
|
|
}, |
1316
|
|
|
'block_level' => true, |
1317
|
|
|
), |
1318
|
|
|
array( |
1319
|
|
|
'tag' => 'color', |
1320
|
|
|
'type' => 'unparsed_equals', |
1321
|
|
|
'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s?,\s?){2}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\))\]', |
1322
|
|
|
'before' => '<span style="color: $1;" class="bbc_color">', |
1323
|
|
|
'after' => '</span>', |
1324
|
|
|
), |
1325
|
|
|
array( |
1326
|
|
|
'tag' => 'email', |
1327
|
|
|
'type' => 'unparsed_content', |
1328
|
|
|
'content' => '<a href="mailto:$1" class="bbc_email">$1</a>', |
1329
|
|
|
// @todo Should this respect guest_hideContacts? |
|
|
|
|
1330
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1331
|
|
|
{ |
1332
|
|
|
$data = strtr($data, array('<br>' => '')); |
1333
|
|
|
}, |
1334
|
|
|
), |
1335
|
|
|
array( |
1336
|
|
|
'tag' => 'email', |
1337
|
|
|
'type' => 'unparsed_equals', |
1338
|
|
|
'before' => '<a href="mailto:$1" class="bbc_email">', |
1339
|
|
|
'after' => '</a>', |
1340
|
|
|
// @todo Should this respect guest_hideContacts? |
|
|
|
|
1341
|
|
|
'disallow_children' => array('email', 'ftp', 'url', 'iurl'), |
1342
|
|
|
'disabled_after' => ' ($1)', |
1343
|
|
|
), |
1344
|
|
|
array( |
1345
|
|
|
'tag' => 'flash', |
1346
|
|
|
'type' => 'unparsed_commas_content', |
1347
|
|
|
'test' => '\d+,\d+\]', |
1348
|
|
|
'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">', |
1349
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
1350
|
|
|
{ |
1351
|
|
|
if (isset($disabled['url'])) |
1352
|
|
|
$tag['content'] = '$1'; |
1353
|
|
|
$scheme = parse_url($data[0], PHP_URL_SCHEME); |
1354
|
|
|
if (empty($scheme)) |
1355
|
|
|
$data[0] = '//' . ltrim($data[0], ':/'); |
1356
|
|
|
}, |
1357
|
|
|
'disabled_content' => '<a href="$1" target="_blank">$1</a>', |
1358
|
|
|
), |
1359
|
|
|
array( |
1360
|
|
|
'tag' => 'float', |
1361
|
|
|
'type' => 'unparsed_equals', |
1362
|
|
|
'test' => '(left|right)(\s+max=\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)?\]', |
1363
|
|
|
'before' => '<div $1>', |
1364
|
|
|
'after' => '</div>', |
1365
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1366
|
|
|
{ |
1367
|
|
|
$class = 'class="bbc_float float' . (strpos($data, 'left') === 0 ? 'left' : 'right') . '"'; |
1368
|
|
|
|
1369
|
|
|
if (preg_match('~\bmax=(\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)~', $data, $matches)) |
1370
|
|
|
$css = ' style="max-width:' . $matches[1] . (is_numeric($matches[1]) ? 'px' : '') . '"'; |
1371
|
|
|
else |
1372
|
|
|
$css = ''; |
1373
|
|
|
|
1374
|
|
|
$data = $class . $css; |
1375
|
|
|
}, |
1376
|
|
|
'trim' => 'outside', |
1377
|
|
|
'block_level' => true, |
1378
|
|
|
), |
1379
|
|
|
array( |
1380
|
|
|
'tag' => 'font', |
1381
|
|
|
'type' => 'unparsed_equals', |
1382
|
|
|
'test' => '[A-Za-z0-9_,\-\s]+?\]', |
1383
|
|
|
'before' => '<span style="font-family: $1;" class="bbc_font">', |
1384
|
|
|
'after' => '</span>', |
1385
|
|
|
), |
1386
|
|
|
array( |
1387
|
|
|
'tag' => 'html', |
1388
|
|
|
'type' => 'unparsed_content', |
1389
|
|
|
'content' => '<div>$1</div>', |
1390
|
|
|
'block_level' => true, |
1391
|
|
|
'disabled_content' => '$1', |
1392
|
|
|
), |
1393
|
|
|
array( |
1394
|
|
|
'tag' => 'hr', |
1395
|
|
|
'type' => 'closed', |
1396
|
|
|
'content' => '<hr>', |
1397
|
|
|
'block_level' => true, |
1398
|
|
|
), |
1399
|
|
|
array( |
1400
|
|
|
'tag' => 'i', |
1401
|
|
|
'before' => '<i>', |
1402
|
|
|
'after' => '</i>', |
1403
|
|
|
), |
1404
|
|
|
array( |
1405
|
|
|
'tag' => 'img', |
1406
|
|
|
'type' => 'unparsed_content', |
1407
|
|
|
'parameters' => array( |
1408
|
|
|
'alt' => array('optional' => true), |
1409
|
|
|
'title' => array('optional' => true), |
1410
|
|
|
'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'), |
1411
|
|
|
'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'), |
1412
|
|
|
), |
1413
|
|
|
'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">', |
1414
|
|
View Code Duplication |
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1415
|
|
|
{ |
1416
|
|
|
global $image_proxy_enabled, $image_proxy_secret, $boardurl; |
|
|
|
|
1417
|
|
|
|
1418
|
|
|
$data = strtr($data, array('<br>' => '')); |
1419
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1420
|
|
|
if ($image_proxy_enabled) |
1421
|
|
|
{ |
1422
|
|
|
if (empty($scheme)) |
1423
|
|
|
$data = 'http://' . ltrim($data, ':/'); |
1424
|
|
|
|
1425
|
|
|
if ($scheme != 'https') |
1426
|
|
|
$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret); |
1427
|
|
|
} |
1428
|
|
|
elseif (empty($scheme)) |
1429
|
|
|
$data = '//' . ltrim($data, ':/'); |
1430
|
|
|
}, |
1431
|
|
|
'disabled_content' => '($1)', |
1432
|
|
|
), |
1433
|
|
|
array( |
1434
|
|
|
'tag' => 'img', |
1435
|
|
|
'type' => 'unparsed_content', |
1436
|
|
|
'content' => '<img src="$1" alt="" class="bbc_img">', |
1437
|
|
View Code Duplication |
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1438
|
|
|
{ |
1439
|
|
|
global $image_proxy_enabled, $image_proxy_secret, $boardurl; |
|
|
|
|
1440
|
|
|
|
1441
|
|
|
$data = strtr($data, array('<br>' => '')); |
1442
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1443
|
|
|
if ($image_proxy_enabled) |
1444
|
|
|
{ |
1445
|
|
|
if (empty($scheme)) |
1446
|
|
|
$data = 'http://' . ltrim($data, ':/'); |
1447
|
|
|
|
1448
|
|
|
if ($scheme != 'https') |
1449
|
|
|
$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret); |
1450
|
|
|
} |
1451
|
|
|
elseif (empty($scheme)) |
1452
|
|
|
$data = '//' . ltrim($data, ':/'); |
1453
|
|
|
}, |
1454
|
|
|
'disabled_content' => '($1)', |
1455
|
|
|
), |
1456
|
|
|
array( |
1457
|
|
|
'tag' => 'iurl', |
1458
|
|
|
'type' => 'unparsed_content', |
1459
|
|
|
'content' => '<a href="$1" class="bbc_link">$1</a>', |
1460
|
|
View Code Duplication |
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1461
|
|
|
{ |
1462
|
|
|
$data = strtr($data, array('<br>' => '')); |
1463
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1464
|
|
|
if (empty($scheme)) |
1465
|
|
|
$data = '//' . ltrim($data, ':/'); |
1466
|
|
|
}, |
1467
|
|
|
), |
1468
|
|
|
array( |
1469
|
|
|
'tag' => 'iurl', |
1470
|
|
|
'type' => 'unparsed_equals', |
1471
|
|
|
'quoted' => 'optional', |
1472
|
|
|
'before' => '<a href="$1" class="bbc_link">', |
1473
|
|
|
'after' => '</a>', |
1474
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1475
|
|
|
{ |
1476
|
|
|
if (substr($data, 0, 1) == '#') |
1477
|
|
|
$data = '#post_' . substr($data, 1); |
1478
|
|
|
else |
1479
|
|
|
{ |
1480
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1481
|
|
|
if (empty($scheme)) |
1482
|
|
|
$data = '//' . ltrim($data, ':/'); |
1483
|
|
|
} |
1484
|
|
|
}, |
1485
|
|
|
'disallow_children' => array('email', 'ftp', 'url', 'iurl'), |
1486
|
|
|
'disabled_after' => ' ($1)', |
1487
|
|
|
), |
1488
|
|
|
array( |
1489
|
|
|
'tag' => 'left', |
1490
|
|
|
'before' => '<div style="text-align: left;">', |
1491
|
|
|
'after' => '</div>', |
1492
|
|
|
'block_level' => true, |
1493
|
|
|
), |
1494
|
|
|
array( |
1495
|
|
|
'tag' => 'li', |
1496
|
|
|
'before' => '<li>', |
1497
|
|
|
'after' => '</li>', |
1498
|
|
|
'trim' => 'outside', |
1499
|
|
|
'require_parents' => array('list'), |
1500
|
|
|
'block_level' => true, |
1501
|
|
|
'disabled_before' => '', |
1502
|
|
|
'disabled_after' => '<br>', |
1503
|
|
|
), |
1504
|
|
|
array( |
1505
|
|
|
'tag' => 'list', |
1506
|
|
|
'before' => '<ul class="bbc_list">', |
1507
|
|
|
'after' => '</ul>', |
1508
|
|
|
'trim' => 'inside', |
1509
|
|
|
'require_children' => array('li', 'list'), |
1510
|
|
|
'block_level' => true, |
1511
|
|
|
), |
1512
|
|
|
array( |
1513
|
|
|
'tag' => 'list', |
1514
|
|
|
'parameters' => array( |
1515
|
|
|
'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|upper-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'), |
1516
|
|
|
), |
1517
|
|
|
'before' => '<ul class="bbc_list" style="list-style-type: {type};">', |
1518
|
|
|
'after' => '</ul>', |
1519
|
|
|
'trim' => 'inside', |
1520
|
|
|
'require_children' => array('li'), |
1521
|
|
|
'block_level' => true, |
1522
|
|
|
), |
1523
|
|
|
array( |
1524
|
|
|
'tag' => 'ltr', |
1525
|
|
|
'before' => '<bdo dir="ltr">', |
1526
|
|
|
'after' => '</bdo>', |
1527
|
|
|
'block_level' => true, |
1528
|
|
|
), |
1529
|
|
|
array( |
1530
|
|
|
'tag' => 'me', |
1531
|
|
|
'type' => 'unparsed_equals', |
1532
|
|
|
'before' => '<div class="meaction">* $1 ', |
1533
|
|
|
'after' => '</div>', |
1534
|
|
|
'quoted' => 'optional', |
1535
|
|
|
'block_level' => true, |
1536
|
|
|
'disabled_before' => '/me ', |
1537
|
|
|
'disabled_after' => '<br>', |
1538
|
|
|
), |
1539
|
|
|
array( |
1540
|
|
|
'tag' => 'member', |
1541
|
|
|
'type' => 'unparsed_equals', |
1542
|
|
|
'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention" data-mention="$1">@', |
1543
|
|
|
'after' => '</a>', |
1544
|
|
|
), |
1545
|
|
|
array( |
1546
|
|
|
'tag' => 'nobbc', |
1547
|
|
|
'type' => 'unparsed_content', |
1548
|
|
|
'content' => '$1', |
1549
|
|
|
), |
1550
|
|
|
array( |
1551
|
|
|
'tag' => 'php', |
1552
|
|
|
'type' => 'unparsed_content', |
1553
|
|
|
'content' => '<span class="phpcode">$1</span>', |
1554
|
|
|
'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled) |
1555
|
|
|
{ |
1556
|
|
|
if (!isset($disabled['php'])) |
1557
|
|
|
{ |
1558
|
|
|
$add_begin = substr(trim($data), 0, 5) != '<?'; |
1559
|
|
|
$data = highlight_php_code($add_begin ? '<?php ' . $data . '?>' : $data); |
1560
|
|
|
if ($add_begin) |
1561
|
|
|
$data = preg_replace(array('~^(.+?)<\?.{0,40}?php(?: |\s)~', '~\?>((?:</(font|span)>)*)$~'), '$1', $data, 2); |
1562
|
|
|
} |
1563
|
|
|
}, |
1564
|
|
|
'block_level' => false, |
1565
|
|
|
'disabled_content' => '$1', |
1566
|
|
|
), |
1567
|
|
|
array( |
1568
|
|
|
'tag' => 'pre', |
1569
|
|
|
'before' => '<pre>', |
1570
|
|
|
'after' => '</pre>', |
1571
|
|
|
), |
1572
|
|
|
array( |
1573
|
|
|
'tag' => 'quote', |
1574
|
|
|
'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>', |
1575
|
|
|
'after' => '</blockquote>', |
1576
|
|
|
'trim' => 'both', |
1577
|
|
|
'block_level' => true, |
1578
|
|
|
), |
1579
|
|
|
array( |
1580
|
|
|
'tag' => 'quote', |
1581
|
|
|
'parameters' => array( |
1582
|
|
|
'author' => array('match' => '(.{1,192}?)', 'quoted' => true), |
1583
|
|
|
), |
1584
|
|
|
'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>', |
1585
|
|
|
'after' => '</blockquote>', |
1586
|
|
|
'trim' => 'both', |
1587
|
|
|
'block_level' => true, |
1588
|
|
|
), |
1589
|
|
|
array( |
1590
|
|
|
'tag' => 'quote', |
1591
|
|
|
'type' => 'parsed_equals', |
1592
|
|
|
'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>', |
1593
|
|
|
'after' => '</blockquote>', |
1594
|
|
|
'trim' => 'both', |
1595
|
|
|
'quoted' => 'optional', |
1596
|
|
|
// Don't allow everything to be embedded with the author name. |
1597
|
|
|
'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), |
1598
|
|
|
'block_level' => true, |
1599
|
|
|
), |
1600
|
|
|
array( |
1601
|
|
|
'tag' => 'quote', |
1602
|
|
|
'parameters' => array( |
1603
|
|
|
'author' => array('match' => '([^<>]{1,192}?)'), |
1604
|
|
|
'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'), |
1605
|
|
|
'date' => array('match' => '(\d+)', 'validate' => 'timeformat'), |
1606
|
|
|
), |
1607
|
|
|
'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>', |
1608
|
|
|
'after' => '</blockquote>', |
1609
|
|
|
'trim' => 'both', |
1610
|
|
|
'block_level' => true, |
1611
|
|
|
), |
1612
|
|
|
array( |
1613
|
|
|
'tag' => 'quote', |
1614
|
|
|
'parameters' => array( |
1615
|
|
|
'author' => array('match' => '(.{1,192}?)'), |
1616
|
|
|
), |
1617
|
|
|
'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>', |
1618
|
|
|
'after' => '</blockquote>', |
1619
|
|
|
'trim' => 'both', |
1620
|
|
|
'block_level' => true, |
1621
|
|
|
), |
1622
|
|
|
array( |
1623
|
|
|
'tag' => 'right', |
1624
|
|
|
'before' => '<div style="text-align: right;">', |
1625
|
|
|
'after' => '</div>', |
1626
|
|
|
'block_level' => true, |
1627
|
|
|
), |
1628
|
|
|
array( |
1629
|
|
|
'tag' => 'rtl', |
1630
|
|
|
'before' => '<bdo dir="rtl">', |
1631
|
|
|
'after' => '</bdo>', |
1632
|
|
|
'block_level' => true, |
1633
|
|
|
), |
1634
|
|
|
array( |
1635
|
|
|
'tag' => 's', |
1636
|
|
|
'before' => '<s>', |
1637
|
|
|
'after' => '</s>', |
1638
|
|
|
), |
1639
|
|
|
array( |
1640
|
|
|
'tag' => 'size', |
1641
|
|
|
'type' => 'unparsed_equals', |
1642
|
|
|
'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]', |
1643
|
|
|
'before' => '<span style="font-size: $1;" class="bbc_size">', |
1644
|
|
|
'after' => '</span>', |
1645
|
|
|
), |
1646
|
|
|
array( |
1647
|
|
|
'tag' => 'size', |
1648
|
|
|
'type' => 'unparsed_equals', |
1649
|
|
|
'test' => '[1-7]\]', |
1650
|
|
|
'before' => '<span style="font-size: $1;" class="bbc_size">', |
1651
|
|
|
'after' => '</span>', |
1652
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1653
|
|
|
{ |
1654
|
|
|
$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95); |
1655
|
|
|
$data = $sizes[$data] . 'em'; |
1656
|
|
|
}, |
1657
|
|
|
), |
1658
|
|
|
array( |
1659
|
|
|
'tag' => 'sub', |
1660
|
|
|
'before' => '<sub>', |
1661
|
|
|
'after' => '</sub>', |
1662
|
|
|
), |
1663
|
|
|
array( |
1664
|
|
|
'tag' => 'sup', |
1665
|
|
|
'before' => '<sup>', |
1666
|
|
|
'after' => '</sup>', |
1667
|
|
|
), |
1668
|
|
|
array( |
1669
|
|
|
'tag' => 'table', |
1670
|
|
|
'before' => '<table class="bbc_table">', |
1671
|
|
|
'after' => '</table>', |
1672
|
|
|
'trim' => 'inside', |
1673
|
|
|
'require_children' => array('tr'), |
1674
|
|
|
'block_level' => true, |
1675
|
|
|
), |
1676
|
|
|
array( |
1677
|
|
|
'tag' => 'td', |
1678
|
|
|
'before' => '<td>', |
1679
|
|
|
'after' => '</td>', |
1680
|
|
|
'require_parents' => array('tr'), |
1681
|
|
|
'trim' => 'outside', |
1682
|
|
|
'block_level' => true, |
1683
|
|
|
'disabled_before' => '', |
1684
|
|
|
'disabled_after' => '', |
1685
|
|
|
), |
1686
|
|
|
array( |
1687
|
|
|
'tag' => 'time', |
1688
|
|
|
'type' => 'unparsed_content', |
1689
|
|
|
'content' => '$1', |
1690
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1691
|
|
|
{ |
1692
|
|
|
if (is_numeric($data)) |
1693
|
|
|
$data = timeformat($data); |
1694
|
|
|
else |
1695
|
|
|
$tag['content'] = '[time]$1[/time]'; |
1696
|
|
|
}, |
1697
|
|
|
), |
1698
|
|
|
array( |
1699
|
|
|
'tag' => 'tr', |
1700
|
|
|
'before' => '<tr>', |
1701
|
|
|
'after' => '</tr>', |
1702
|
|
|
'require_parents' => array('table'), |
1703
|
|
|
'require_children' => array('td'), |
1704
|
|
|
'trim' => 'both', |
1705
|
|
|
'block_level' => true, |
1706
|
|
|
'disabled_before' => '', |
1707
|
|
|
'disabled_after' => '', |
1708
|
|
|
), |
1709
|
|
|
array( |
1710
|
|
|
'tag' => 'u', |
1711
|
|
|
'before' => '<u>', |
1712
|
|
|
'after' => '</u>', |
1713
|
|
|
), |
1714
|
|
|
array( |
1715
|
|
|
'tag' => 'url', |
1716
|
|
|
'type' => 'unparsed_content', |
1717
|
|
|
'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>', |
1718
|
|
View Code Duplication |
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1719
|
|
|
{ |
1720
|
|
|
$data = strtr($data, array('<br>' => '')); |
1721
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1722
|
|
|
if (empty($scheme)) |
1723
|
|
|
$data = '//' . ltrim($data, ':/'); |
1724
|
|
|
}, |
1725
|
|
|
), |
1726
|
|
|
array( |
1727
|
|
|
'tag' => 'url', |
1728
|
|
|
'type' => 'unparsed_equals', |
1729
|
|
|
'quoted' => 'optional', |
1730
|
|
|
'before' => '<a href="$1" class="bbc_link" target="_blank">', |
1731
|
|
|
'after' => '</a>', |
1732
|
|
|
'validate' => function (&$tag, &$data, $disabled) |
|
|
|
|
1733
|
|
|
{ |
1734
|
|
|
$scheme = parse_url($data, PHP_URL_SCHEME); |
1735
|
|
|
if (empty($scheme)) |
1736
|
|
|
$data = '//' . ltrim($data, ':/'); |
1737
|
|
|
}, |
1738
|
|
|
'disallow_children' => array('email', 'ftp', 'url', 'iurl'), |
1739
|
|
|
'disabled_after' => ' ($1)', |
1740
|
|
|
), |
1741
|
|
|
); |
1742
|
|
|
|
1743
|
|
|
// Inside these tags autolink is not recommendable. |
1744
|
|
|
$no_autolink_tags = array( |
1745
|
|
|
'url', |
1746
|
|
|
'iurl', |
1747
|
|
|
'email', |
1748
|
|
|
); |
1749
|
|
|
|
1750
|
|
|
// Let mods add new BBC without hassle. |
1751
|
|
|
call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags)); |
1752
|
|
|
|
1753
|
|
|
// This is mainly for the bbc manager, so it's easy to add tags above. Custom BBC should be added above this line. |
1754
|
|
|
if ($message === false) |
1755
|
|
|
{ |
1756
|
|
|
if (isset($temp_bbc)) |
1757
|
|
|
$bbc_codes = $temp_bbc; |
1758
|
|
|
usort($codes, function ($a, $b) { |
|
|
|
|
1759
|
|
|
return strcmp($a['tag'], $b['tag']); |
1760
|
|
|
}); |
1761
|
|
|
return $codes; |
1762
|
|
|
} |
1763
|
|
|
|
1764
|
|
|
// So the parser won't skip them. |
1765
|
|
|
$itemcodes = array( |
1766
|
|
|
'*' => 'disc', |
1767
|
|
|
'@' => 'disc', |
1768
|
|
|
'+' => 'square', |
1769
|
|
|
'x' => 'square', |
1770
|
|
|
'#' => 'square', |
1771
|
|
|
'o' => 'circle', |
1772
|
|
|
'O' => 'circle', |
1773
|
|
|
'0' => 'circle', |
1774
|
|
|
); |
1775
|
|
|
if (!isset($disabled['li']) && !isset($disabled['list'])) |
1776
|
|
|
{ |
1777
|
|
|
foreach ($itemcodes as $c => $dummy) |
1778
|
|
|
$bbc_codes[$c] = array(); |
1779
|
|
|
} |
1780
|
|
|
|
1781
|
|
|
// Shhhh! |
1782
|
|
|
if (!isset($disabled['color'])) |
1783
|
|
|
{ |
1784
|
|
|
$codes[] = array( |
1785
|
|
|
'tag' => 'chrissy', |
1786
|
|
|
'before' => '<span style="color: #cc0099;">', |
1787
|
|
|
'after' => ' :-*</span>', |
1788
|
|
|
); |
1789
|
|
|
$codes[] = array( |
1790
|
|
|
'tag' => 'kissy', |
1791
|
|
|
'before' => '<span style="color: #cc0099;">', |
1792
|
|
|
'after' => ' :-*</span>', |
1793
|
|
|
); |
1794
|
|
|
} |
1795
|
|
|
|
1796
|
|
|
foreach ($codes as $code) |
1797
|
|
|
{ |
1798
|
|
|
// Make it easier to process parameters later |
1799
|
|
|
if (!empty($code['parameters'])) |
1800
|
|
|
ksort($code['parameters'], SORT_STRING); |
1801
|
|
|
|
1802
|
|
|
// If we are not doing every tag only do ones we are interested in. |
1803
|
|
|
if (empty($parse_tags) || in_array($code['tag'], $parse_tags)) |
1804
|
|
|
$bbc_codes[substr($code['tag'], 0, 1)][] = $code; |
1805
|
|
|
} |
1806
|
|
|
$codes = null; |
|
|
|
|
1807
|
|
|
} |
1808
|
|
|
|
1809
|
|
|
// Shall we take the time to cache this? |
1810
|
|
|
if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags)) |
1811
|
|
|
{ |
1812
|
|
|
// It's likely this will change if the message is modified. |
1813
|
|
|
$cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . $smcFunc['json_encode']($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']); |
1814
|
|
|
|
1815
|
|
|
if (($temp = cache_get_data($cache_key, 240)) != null) |
1816
|
|
|
return $temp; |
1817
|
|
|
|
1818
|
|
|
$cache_t = microtime(); |
1819
|
|
|
} |
1820
|
|
|
|
1821
|
|
|
if ($smileys === 'print') |
1822
|
|
|
{ |
1823
|
|
|
// [glow], [shadow], and [move] can't really be printed. |
1824
|
|
|
$disabled['glow'] = true; |
1825
|
|
|
$disabled['shadow'] = true; |
1826
|
|
|
$disabled['move'] = true; |
1827
|
|
|
|
1828
|
|
|
// Colors can't well be displayed... supposed to be black and white. |
1829
|
|
|
$disabled['color'] = true; |
1830
|
|
|
$disabled['black'] = true; |
1831
|
|
|
$disabled['blue'] = true; |
1832
|
|
|
$disabled['white'] = true; |
1833
|
|
|
$disabled['red'] = true; |
1834
|
|
|
$disabled['green'] = true; |
1835
|
|
|
$disabled['me'] = true; |
1836
|
|
|
|
1837
|
|
|
// Color coding doesn't make sense. |
1838
|
|
|
$disabled['php'] = true; |
1839
|
|
|
|
1840
|
|
|
// Links are useless on paper... just show the link. |
1841
|
|
|
$disabled['ftp'] = true; |
1842
|
|
|
$disabled['url'] = true; |
1843
|
|
|
$disabled['iurl'] = true; |
1844
|
|
|
$disabled['email'] = true; |
1845
|
|
|
$disabled['flash'] = true; |
1846
|
|
|
|
1847
|
|
|
// @todo Change maybe? |
|
|
|
|
1848
|
|
|
if (!isset($_GET['images'])) |
1849
|
|
|
$disabled['img'] = true; |
1850
|
|
|
|
1851
|
|
|
// @todo Interface/setting to add more? |
|
|
|
|
1852
|
|
|
} |
1853
|
|
|
|
1854
|
|
|
$open_tags = array(); |
1855
|
|
|
$message = strtr($message, array("\n" => '<br>')); |
1856
|
|
|
|
1857
|
|
|
$alltags = array(); |
1858
|
|
|
foreach ($bbc_codes as $section) { |
1859
|
|
|
foreach ($section as $code) { |
1860
|
|
|
$alltags[] = $code['tag']; |
1861
|
|
|
} |
1862
|
|
|
} |
1863
|
|
|
$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b'; |
1864
|
|
|
|
1865
|
|
|
$pos = -1; |
1866
|
|
|
while ($pos !== false) |
1867
|
|
|
{ |
1868
|
|
|
$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos; |
1869
|
|
|
preg_match('~\[/?(?=' . $alltags_regex . ')~i', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1); |
1870
|
|
|
$pos = isset($matches[0][1]) ? $matches[0][1] : false; |
1871
|
|
|
|
1872
|
|
|
// Failsafe. |
1873
|
|
|
if ($pos === false || $last_pos > $pos) |
1874
|
|
|
$pos = strlen($message) + 1; |
1875
|
|
|
|
1876
|
|
|
// Can't have a one letter smiley, URL, or email! (sorry.) |
1877
|
|
|
if ($last_pos < $pos - 1) |
1878
|
|
|
{ |
1879
|
|
|
// Make sure the $last_pos is not negative. |
1880
|
|
|
$last_pos = max($last_pos, 0); |
1881
|
|
|
|
1882
|
|
|
// Pick a block of data to do some raw fixing on. |
1883
|
|
|
$data = substr($message, $last_pos, $pos - $last_pos); |
1884
|
|
|
|
1885
|
|
|
// Take care of some HTML! |
1886
|
|
|
if (!empty($modSettings['enablePostHTML']) && strpos($data, '<') !== false) |
1887
|
|
|
{ |
1888
|
|
|
$data = preg_replace('~<a\s+href=((?:")?)((?:https?://|ftps?://|mailto:|tel:)\S+?)\\1>(.*?)</a>~i', '[url="$2"]$3[/url]', $data); |
1889
|
|
|
|
1890
|
|
|
// <br> should be empty. |
1891
|
|
|
$empty_tags = array('br', 'hr'); |
1892
|
|
|
foreach ($empty_tags as $tag) |
1893
|
|
|
$data = str_replace(array('<' . $tag . '>', '<' . $tag . '/>', '<' . $tag . ' />'), '<' . $tag . '>', $data); |
1894
|
|
|
|
1895
|
|
|
// b, u, i, s, pre... basic tags. |
1896
|
|
|
$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote', 'strong'); |
1897
|
|
|
foreach ($closable_tags as $tag) |
1898
|
|
|
{ |
1899
|
|
|
$diff = substr_count($data, '<' . $tag . '>') - substr_count($data, '</' . $tag . '>'); |
1900
|
|
|
$data = strtr($data, array('<' . $tag . '>' => '<' . $tag . '>', '</' . $tag . '>' => '</' . $tag . '>')); |
1901
|
|
|
|
1902
|
|
|
if ($diff > 0) |
1903
|
|
|
$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1); |
1904
|
|
|
} |
1905
|
|
|
|
1906
|
|
|
// Do <img ...> - with security... action= -> action-. |
1907
|
|
|
preg_match_all('~<img\s+src=((?:")?)((?:https?://|ftps?://)\S+?)\\1(?:\s+alt=(".*?"|\S*?))?(?:\s?/)?>~i', $data, $matches, PREG_PATTERN_ORDER); |
1908
|
|
|
if (!empty($matches[0])) |
1909
|
|
|
{ |
1910
|
|
|
$replaces = array(); |
1911
|
|
|
foreach ($matches[2] as $match => $imgtag) |
1912
|
|
|
{ |
1913
|
|
|
$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^"|"$~', '', $matches[3][$match]); |
1914
|
|
|
|
1915
|
|
|
// Remove action= from the URL - no funny business, now. |
1916
|
|
|
if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) |
1917
|
|
|
$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag); |
1918
|
|
|
|
1919
|
|
|
// Check if the image is larger than allowed. |
1920
|
|
|
if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) |
1921
|
|
|
{ |
1922
|
|
|
list ($width, $height) = url_image_size($imgtag); |
1923
|
|
|
|
1924
|
|
View Code Duplication |
if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) |
1925
|
|
|
{ |
1926
|
|
|
$height = (int) (($modSettings['max_image_width'] * $height) / $width); |
1927
|
|
|
$width = $modSettings['max_image_width']; |
1928
|
|
|
} |
1929
|
|
|
|
1930
|
|
View Code Duplication |
if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) |
1931
|
|
|
{ |
1932
|
|
|
$width = (int) (($modSettings['max_image_height'] * $width) / $height); |
1933
|
|
|
$height = $modSettings['max_image_height']; |
1934
|
|
|
} |
1935
|
|
|
|
1936
|
|
|
// Set the new image tag. |
1937
|
|
|
$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]'; |
1938
|
|
|
} |
1939
|
|
|
else |
1940
|
|
|
$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]'; |
1941
|
|
|
} |
1942
|
|
|
|
1943
|
|
|
$data = strtr($data, $replaces); |
1944
|
|
|
} |
1945
|
|
|
} |
1946
|
|
|
|
1947
|
|
|
if (!empty($modSettings['autoLinkUrls'])) |
1948
|
|
|
{ |
1949
|
|
|
// Are we inside tags that should be auto linked? |
1950
|
|
|
$no_autolink_area = false; |
1951
|
|
|
if (!empty($open_tags)) |
1952
|
|
|
{ |
1953
|
|
|
foreach ($open_tags as $open_tag) |
1954
|
|
|
if (in_array($open_tag['tag'], $no_autolink_tags)) |
1955
|
|
|
$no_autolink_area = true; |
1956
|
|
|
} |
1957
|
|
|
|
1958
|
|
|
// Don't go backwards. |
1959
|
|
|
// @todo Don't think is the real solution.... |
|
|
|
|
1960
|
|
|
$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0; |
1961
|
|
|
if ($pos < $lastAutoPos) |
1962
|
|
|
$no_autolink_area = true; |
1963
|
|
|
$lastAutoPos = $pos; |
1964
|
|
|
|
1965
|
|
|
if (!$no_autolink_area) |
1966
|
|
|
{ |
1967
|
|
|
// Parse any URLs |
1968
|
|
|
if (!isset($disabled['url']) && strpos($data, '[url') === false) |
1969
|
|
|
{ |
1970
|
|
|
$url_regex = ' |
1971
|
|
|
(?: |
1972
|
|
|
# IRIs with a scheme (or at least an opening "//") |
1973
|
|
|
(?: |
1974
|
|
|
# URI scheme (or lack thereof for schemeless URLs) |
1975
|
|
|
(?: |
1976
|
|
|
# URL scheme and colon |
1977
|
|
|
\b[a-z][\w\-]+: |
1978
|
|
|
| # or |
1979
|
|
|
# A boundary followed by two slashes for schemeless URLs |
1980
|
|
|
(?<=^|\W)(?=//) |
1981
|
|
|
) |
1982
|
|
|
|
1983
|
|
|
# IRI "authority" chunk |
1984
|
|
|
(?: |
1985
|
|
|
# 2 slashes for IRIs with an "authority" |
1986
|
|
|
// |
1987
|
|
|
# then a domain name |
1988
|
|
|
(?: |
1989
|
|
|
# Either the reserved "localhost" domain name |
1990
|
|
|
localhost |
1991
|
|
|
| # or |
1992
|
|
|
# a run of Unicode domain name characters and a dot |
1993
|
|
|
[\p{L}\p{M}\p{N}\-.:@]+\. |
1994
|
|
|
# and then a TLD valid in the DNS or the reserved "local" TLD |
1995
|
|
|
(?:'. $modSettings['tld_regex'] .'|local) |
1996
|
|
|
) |
1997
|
|
|
# followed by a non-domain character or end of line |
1998
|
|
|
(?=[^\p{L}\p{N}\-.]|$) |
1999
|
|
|
|
2000
|
|
|
| # Or, if there is no "authority" per se (e.g. mailto: URLs) ... |
2001
|
|
|
|
2002
|
|
|
# a run of IRI characters |
2003
|
|
|
[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.:@]+[\p{L}\p{M}\p{N}] |
2004
|
|
|
# and then a dot and a closing IRI label |
2005
|
|
|
\.[\p{L}\p{M}\p{N}\-]+ |
2006
|
|
|
) |
2007
|
|
|
) |
2008
|
|
|
|
2009
|
|
|
| # or |
2010
|
|
|
|
2011
|
|
|
# Naked domains (e.g. "example.com" in "Go to example.com for an example.") |
2012
|
|
|
(?: |
2013
|
|
|
# Preceded by start of line or a non-domain character |
2014
|
|
|
(?<=^|[^\p{L}\p{M}\p{N}\-:@]) |
2015
|
|
|
|
2016
|
|
|
# A run of Unicode domain name characters (excluding [:@]) |
2017
|
|
|
[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.]+[\p{L}\p{M}\p{N}] |
2018
|
|
|
# and then a dot and a valid TLD |
2019
|
|
|
\.' . $modSettings['tld_regex'] . ' |
2020
|
|
|
|
2021
|
|
|
# Followed by either: |
2022
|
|
|
(?= |
2023
|
|
|
# end of line or a non-domain character (excluding [.:@]) |
2024
|
|
|
$|[^\p{L}\p{N}\-] |
2025
|
|
|
| # or |
2026
|
|
|
# a dot followed by end of line or a non-domain character (excluding [.:@]) |
2027
|
|
|
\.(?=$|[^\p{L}\p{N}\-]) |
2028
|
|
|
) |
2029
|
|
|
) |
2030
|
|
|
) |
2031
|
|
|
|
2032
|
|
|
# IRI path, query, and fragment (if present) |
2033
|
|
|
(?: |
2034
|
|
|
# If any of these parts exist, must start with a single / |
2035
|
|
|
/ |
2036
|
|
|
|
2037
|
|
|
# And then optionally: |
2038
|
|
|
(?: |
2039
|
|
|
# One or more of: |
2040
|
|
|
(?: |
2041
|
|
|
# a run of non-space, non-()<> |
2042
|
|
|
[^\s()<>]+ |
2043
|
|
|
| # or |
2044
|
|
|
# balanced parens, up to 2 levels |
2045
|
|
|
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) |
2046
|
|
|
)+ |
2047
|
|
|
|
2048
|
|
|
# End with: |
2049
|
|
|
(?: |
2050
|
|
|
# balanced parens, up to 2 levels |
2051
|
|
|
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) |
2052
|
|
|
| # or |
2053
|
|
|
# not a space or one of these punct char |
2054
|
|
|
[^\s`!()\[\]{};:\'".,<>?«»“”‘’/] |
2055
|
|
|
| # or |
2056
|
|
|
# a trailing slash (but not two in a row) |
2057
|
|
|
(?<!/)/ |
2058
|
|
|
) |
2059
|
|
|
)? |
2060
|
|
|
)? |
2061
|
|
|
'; |
2062
|
|
|
|
2063
|
|
|
$data = preg_replace_callback('~' . $url_regex . '~xi' . ($context['utf8'] ? 'u' : ''), function ($matches) { |
2064
|
|
|
$url = array_shift($matches); |
2065
|
|
|
|
2066
|
|
|
$scheme = parse_url($url, PHP_URL_SCHEME); |
2067
|
|
|
|
2068
|
|
|
if ($scheme == 'mailto') |
2069
|
|
|
{ |
2070
|
|
|
$email_address = str_replace('mailto:', '', $url); |
2071
|
|
|
if (!isset($disabled['email']) && filter_var($email_address, FILTER_VALIDATE_EMAIL) !== false) |
|
|
|
|
2072
|
|
|
return '[email=' . $email_address . ']' . $url . '[/email]'; |
2073
|
|
|
else |
2074
|
|
|
return $url; |
2075
|
|
|
} |
2076
|
|
|
|
2077
|
|
|
// Are we linking a schemeless URL or naked domain name (e.g. "example.com")? |
2078
|
|
|
if (empty($scheme)) |
2079
|
|
|
$fullUrl = '//' . ltrim($url, ':/'); |
2080
|
|
|
else |
2081
|
|
|
$fullUrl = $url; |
2082
|
|
|
|
2083
|
|
|
return '[url="' . str_replace(array('[', ']'), array('[', ']'), $fullUrl) . '"]' . $url . '[/url]'; |
2084
|
|
|
}, $data); |
2085
|
|
|
} |
2086
|
|
|
|
2087
|
|
|
// Next, emails... Must be careful not to step on enablePostHTML logic above... |
2088
|
|
|
if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false && stripos($data, 'mailto:') === false) |
2089
|
|
|
{ |
2090
|
|
|
$email_regex = ' |
2091
|
|
|
# Preceded by a non-domain character or start of line |
2092
|
|
|
(?<=^|[^\p{L}\p{M}\p{N}\-\.]) |
2093
|
|
|
|
2094
|
|
|
# An email address |
2095
|
|
|
[\p{L}\p{M}\p{N}_\-.]{1,80} |
2096
|
|
|
@ |
2097
|
|
|
[\p{L}\p{M}\p{N}\-.]+ |
2098
|
|
|
\. |
2099
|
|
|
'. $modSettings['tld_regex'] . ' |
2100
|
|
|
|
2101
|
|
|
# Followed by either: |
2102
|
|
|
(?= |
2103
|
|
|
# end of line or a non-domain character (excluding the dot) |
2104
|
|
|
$|[^\p{L}\p{M}\p{N}\-] |
2105
|
|
|
| # or |
2106
|
|
|
# a dot followed by end of line or a non-domain character |
2107
|
|
|
\.(?=$|[^\p{L}\p{M}\p{N}\-]) |
2108
|
|
|
)'; |
2109
|
|
|
|
2110
|
|
|
$data = preg_replace('~' . $email_regex . '~xi' . ($context['utf8'] ? 'u' : ''), '[email]$0[/email]', $data); |
2111
|
|
|
} |
2112
|
|
|
} |
2113
|
|
|
} |
2114
|
|
|
|
2115
|
|
|
$data = strtr($data, array("\t" => ' ')); |
2116
|
|
|
|
2117
|
|
|
// If it wasn't changed, no copying or other boring stuff has to happen! |
2118
|
|
|
if ($data != substr($message, $last_pos, $pos - $last_pos)) |
2119
|
|
|
{ |
2120
|
|
|
$message = substr($message, 0, $last_pos) . $data . substr($message, $pos); |
2121
|
|
|
|
2122
|
|
|
// Since we changed it, look again in case we added or removed a tag. But we don't want to skip any. |
2123
|
|
|
$old_pos = strlen($data) + $last_pos; |
2124
|
|
|
$pos = strpos($message, '[', $last_pos); |
2125
|
|
|
$pos = $pos === false ? $old_pos : min($pos, $old_pos); |
2126
|
|
|
} |
2127
|
|
|
} |
2128
|
|
|
|
2129
|
|
|
// Are we there yet? Are we there yet? |
2130
|
|
|
if ($pos >= strlen($message) - 1) |
2131
|
|
|
break; |
2132
|
|
|
|
2133
|
|
|
$tags = strtolower($message[$pos + 1]); |
2134
|
|
|
|
2135
|
|
|
if ($tags == '/' && !empty($open_tags)) |
2136
|
|
|
{ |
2137
|
|
|
$pos2 = strpos($message, ']', $pos + 1); |
2138
|
|
|
if ($pos2 == $pos + 2) |
2139
|
|
|
continue; |
2140
|
|
|
|
2141
|
|
|
$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2)); |
2142
|
|
|
|
2143
|
|
|
$to_close = array(); |
2144
|
|
|
$block_level = null; |
2145
|
|
|
|
2146
|
|
|
do |
2147
|
|
|
{ |
2148
|
|
|
$tag = array_pop($open_tags); |
2149
|
|
|
if (!$tag) |
2150
|
|
|
break; |
2151
|
|
|
|
2152
|
|
|
if (!empty($tag['block_level'])) |
2153
|
|
|
{ |
2154
|
|
|
// Only find out if we need to. |
2155
|
|
|
if ($block_level === false) |
2156
|
|
|
{ |
2157
|
|
|
array_push($open_tags, $tag); |
2158
|
|
|
break; |
2159
|
|
|
} |
2160
|
|
|
|
2161
|
|
|
// The idea is, if we are LOOKING for a block level tag, we can close them on the way. |
2162
|
|
View Code Duplication |
if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) |
2163
|
|
|
{ |
2164
|
|
|
foreach ($bbc_codes[$look_for[0]] as $temp) |
2165
|
|
|
if ($temp['tag'] == $look_for) |
2166
|
|
|
{ |
2167
|
|
|
$block_level = !empty($temp['block_level']); |
2168
|
|
|
break; |
2169
|
|
|
} |
2170
|
|
|
} |
2171
|
|
|
|
2172
|
|
|
if ($block_level !== true) |
2173
|
|
|
{ |
2174
|
|
|
$block_level = false; |
2175
|
|
|
array_push($open_tags, $tag); |
2176
|
|
|
break; |
2177
|
|
|
} |
2178
|
|
|
} |
2179
|
|
|
|
2180
|
|
|
$to_close[] = $tag; |
2181
|
|
|
} |
2182
|
|
|
while ($tag['tag'] != $look_for); |
2183
|
|
|
|
2184
|
|
|
// Did we just eat through everything and not find it? |
2185
|
|
|
if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for))) |
2186
|
|
|
{ |
2187
|
|
|
$open_tags = $to_close; |
2188
|
|
|
continue; |
2189
|
|
|
} |
2190
|
|
|
elseif (!empty($to_close) && $tag['tag'] != $look_for) |
2191
|
|
|
{ |
2192
|
|
View Code Duplication |
if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) |
2193
|
|
|
{ |
2194
|
|
|
foreach ($bbc_codes[$look_for[0]] as $temp) |
2195
|
|
|
if ($temp['tag'] == $look_for) |
2196
|
|
|
{ |
2197
|
|
|
$block_level = !empty($temp['block_level']); |
2198
|
|
|
break; |
2199
|
|
|
} |
2200
|
|
|
} |
2201
|
|
|
|
2202
|
|
|
// We're not looking for a block level tag (or maybe even a tag that exists...) |
2203
|
|
|
if (!$block_level) |
|
|
|
|
2204
|
|
|
{ |
2205
|
|
|
foreach ($to_close as $tag) |
2206
|
|
|
array_push($open_tags, $tag); |
2207
|
|
|
continue; |
2208
|
|
|
} |
2209
|
|
|
} |
2210
|
|
|
|
2211
|
|
|
foreach ($to_close as $tag) |
2212
|
|
|
{ |
2213
|
|
|
$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1); |
2214
|
|
|
$pos += strlen($tag['after']) + 2; |
2215
|
|
|
$pos2 = $pos - 1; |
2216
|
|
|
|
2217
|
|
|
// See the comment at the end of the big loop - just eating whitespace ;). |
2218
|
|
|
$whitespace_regex = ''; |
2219
|
|
|
if (!empty($tag['block_level'])) |
2220
|
|
|
$whitespace_regex .= '( |\s)*(<br>)?'; |
2221
|
|
|
// Trim one line of whitespace after unnested tags, but all of it after nested ones |
2222
|
|
View Code Duplication |
if (!empty($tag['trim']) && $tag['trim'] != 'inside') |
2223
|
|
|
$whitespace_regex .= empty($tag['require_parents']) ? '( |\s)*' : '(<br>| |\s)*'; |
2224
|
|
|
|
2225
|
|
View Code Duplication |
if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0) |
2226
|
|
|
$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); |
2227
|
|
|
} |
2228
|
|
|
|
2229
|
|
|
if (!empty($to_close)) |
2230
|
|
|
{ |
2231
|
|
|
$to_close = array(); |
|
|
|
|
2232
|
|
|
$pos--; |
2233
|
|
|
} |
2234
|
|
|
|
2235
|
|
|
continue; |
2236
|
|
|
} |
2237
|
|
|
|
2238
|
|
|
// No tags for this character, so just keep going (fastest possible course.) |
2239
|
|
|
if (!isset($bbc_codes[$tags])) |
2240
|
|
|
continue; |
2241
|
|
|
|
2242
|
|
|
$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1]; |
2243
|
|
|
$tag = null; |
2244
|
|
|
foreach ($bbc_codes[$tags] as $possible) |
2245
|
|
|
{ |
2246
|
|
|
$pt_strlen = strlen($possible['tag']); |
2247
|
|
|
|
2248
|
|
|
// Not a match? |
2249
|
|
|
if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag']) |
2250
|
|
|
continue; |
2251
|
|
|
|
2252
|
|
|
$next_c = $message[$pos + 1 + $pt_strlen]; |
2253
|
|
|
|
2254
|
|
|
// A test validation? |
2255
|
|
|
if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0) |
2256
|
|
|
continue; |
2257
|
|
|
// Do we want parameters? |
2258
|
|
|
elseif (!empty($possible['parameters'])) |
2259
|
|
|
{ |
2260
|
|
|
if ($next_c != ' ') |
2261
|
|
|
continue; |
2262
|
|
|
} |
2263
|
|
|
elseif (isset($possible['type'])) |
2264
|
|
|
{ |
2265
|
|
|
// Do we need an equal sign? |
2266
|
|
|
if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') |
2267
|
|
|
continue; |
2268
|
|
|
// Maybe we just want a /... |
2269
|
|
|
if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]') |
2270
|
|
|
continue; |
2271
|
|
|
// An immediate ]? |
2272
|
|
|
if ($possible['type'] == 'unparsed_content' && $next_c != ']') |
2273
|
|
|
continue; |
2274
|
|
|
} |
2275
|
|
|
// No type means 'parsed_content', which demands an immediate ] without parameters! |
2276
|
|
|
elseif ($next_c != ']') |
2277
|
|
|
continue; |
2278
|
|
|
|
2279
|
|
|
// Check allowed tree? |
2280
|
|
|
if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) |
2281
|
|
|
continue; |
2282
|
|
|
elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) |
2283
|
|
|
continue; |
2284
|
|
|
// If this is in the list of disallowed child tags, don't parse it. |
2285
|
|
|
elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) |
2286
|
|
|
continue; |
2287
|
|
|
|
2288
|
|
|
$pos1 = $pos + 1 + $pt_strlen + 1; |
2289
|
|
|
|
2290
|
|
|
// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes. |
2291
|
|
|
if ($possible['tag'] == 'quote') |
2292
|
|
|
{ |
2293
|
|
|
// Start with standard |
2294
|
|
|
$quote_alt = false; |
2295
|
|
|
foreach ($open_tags as $open_quote) |
2296
|
|
|
{ |
2297
|
|
|
// Every parent quote this quote has flips the styling |
2298
|
|
|
if ($open_quote['tag'] == 'quote') |
2299
|
|
|
$quote_alt = !$quote_alt; |
2300
|
|
|
} |
2301
|
|
|
// Add a class to the quote to style alternating blockquotes |
2302
|
|
|
$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">')); |
2303
|
|
|
} |
2304
|
|
|
|
2305
|
|
|
// This is long, but it makes things much easier and cleaner. |
2306
|
|
|
if (!empty($possible['parameters'])) |
2307
|
|
|
{ |
2308
|
|
|
// Build a regular expression for each parameter for the current tag. |
2309
|
|
|
$preg = array(); |
2310
|
|
|
foreach ($possible['parameters'] as $p => $info) |
2311
|
|
|
$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '"') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '"') . '\s*)' . (empty($info['optional']) ? '' : '?'); |
2312
|
|
|
|
2313
|
|
|
// Extract the string that potentially holds our parameters. |
2314
|
|
|
$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos)); |
2315
|
|
|
$blobs = preg_split('~\]~i', $blob[1]); |
2316
|
|
|
|
2317
|
|
|
$splitters = implode('=|', array_keys($possible['parameters'])) . '='; |
2318
|
|
|
|
2319
|
|
|
// Progressively append more blobs until we find our parameters or run out of blobs |
2320
|
|
|
$blob_counter = 1; |
2321
|
|
|
while ($blob_counter <= count($blobs)) |
2322
|
|
|
{ |
2323
|
|
|
|
2324
|
|
|
$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++)); |
2325
|
|
|
|
2326
|
|
|
$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string); |
2327
|
|
|
sort($given_params, SORT_STRING); |
2328
|
|
|
|
2329
|
|
|
$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0; |
2330
|
|
|
|
2331
|
|
|
if ($match) |
2332
|
|
|
$blob_counter = count($blobs) + 1; |
2333
|
|
|
} |
2334
|
|
|
|
2335
|
|
|
// Didn't match our parameter list, try the next possible. |
2336
|
|
|
if (!$match) |
|
|
|
|
2337
|
|
|
continue; |
2338
|
|
|
|
2339
|
|
|
$params = array(); |
2340
|
|
|
for ($i = 1, $n = count($matches); $i < $n; $i += 2) |
2341
|
|
|
{ |
2342
|
|
|
$key = strtok(ltrim($matches[$i]), '='); |
2343
|
|
|
if (isset($possible['parameters'][$key]['value'])) |
2344
|
|
|
$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1])); |
2345
|
|
|
elseif (isset($possible['parameters'][$key]['validate'])) |
2346
|
|
|
$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]); |
2347
|
|
|
else |
2348
|
|
|
$params['{' . $key . '}'] = $matches[$i + 1]; |
2349
|
|
|
|
2350
|
|
|
// Just to make sure: replace any $ or { so they can't interpolate wrongly. |
2351
|
|
|
$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '$', '{' => '{')); |
2352
|
|
|
} |
2353
|
|
|
|
2354
|
|
|
foreach ($possible['parameters'] as $p => $info) |
2355
|
|
|
{ |
2356
|
|
|
if (!isset($params['{' . $p . '}'])) |
2357
|
|
|
$params['{' . $p . '}'] = ''; |
2358
|
|
|
} |
2359
|
|
|
|
2360
|
|
|
$tag = $possible; |
2361
|
|
|
|
2362
|
|
|
// Put the parameters into the string. |
2363
|
|
|
if (isset($tag['before'])) |
2364
|
|
|
$tag['before'] = strtr($tag['before'], $params); |
2365
|
|
|
if (isset($tag['after'])) |
2366
|
|
|
$tag['after'] = strtr($tag['after'], $params); |
2367
|
|
|
if (isset($tag['content'])) |
2368
|
|
|
$tag['content'] = strtr($tag['content'], $params); |
2369
|
|
|
|
2370
|
|
|
$pos1 += strlen($given_param_string); |
|
|
|
|
2371
|
|
|
} |
2372
|
|
|
else |
2373
|
|
|
{ |
2374
|
|
|
$tag = $possible; |
2375
|
|
|
$params = array(); |
2376
|
|
|
} |
2377
|
|
|
break; |
2378
|
|
|
} |
2379
|
|
|
|
2380
|
|
|
// Item codes are complicated buggers... they are implicit [li]s and can make [list]s! |
2381
|
|
|
if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li'])) |
2382
|
|
|
{ |
2383
|
|
|
if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>'))) |
2384
|
|
|
continue; |
2385
|
|
|
|
2386
|
|
|
$tag = $itemcodes[$message[$pos + 1]]; |
2387
|
|
|
|
2388
|
|
|
// First let's set up the tree: it needs to be in a list, or after an li. |
2389
|
|
|
if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li')) |
2390
|
|
|
{ |
2391
|
|
|
$open_tags[] = array( |
2392
|
|
|
'tag' => 'list', |
2393
|
|
|
'after' => '</ul>', |
2394
|
|
|
'block_level' => true, |
2395
|
|
|
'require_children' => array('li'), |
2396
|
|
|
'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, |
2397
|
|
|
); |
2398
|
|
|
$code = '<ul class="bbc_list">'; |
2399
|
|
|
} |
2400
|
|
|
// We're in a list item already: another itemcode? Close it first. |
2401
|
|
|
elseif ($inside['tag'] == 'li') |
2402
|
|
|
{ |
2403
|
|
|
array_pop($open_tags); |
2404
|
|
|
$code = '</li>'; |
2405
|
|
|
} |
2406
|
|
|
else |
2407
|
|
|
$code = ''; |
2408
|
|
|
|
2409
|
|
|
// Now we open a new tag. |
2410
|
|
|
$open_tags[] = array( |
2411
|
|
|
'tag' => 'li', |
2412
|
|
|
'after' => '</li>', |
2413
|
|
|
'trim' => 'outside', |
2414
|
|
|
'block_level' => true, |
2415
|
|
|
'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, |
2416
|
|
|
); |
2417
|
|
|
|
2418
|
|
|
// First, open the tag... |
2419
|
|
|
$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>'; |
2420
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3); |
2421
|
|
|
$pos += strlen($code) - 1 + 2; |
2422
|
|
|
|
2423
|
|
|
// Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! |
2424
|
|
|
$pos2 = strpos($message, '<br>', $pos); |
2425
|
|
|
$pos3 = strpos($message, '[/', $pos); |
2426
|
|
|
if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) |
2427
|
|
|
{ |
2428
|
|
|
preg_match('~^(<br>| |\s|\[)+~', substr($message, $pos2 + 4), $matches); |
2429
|
|
|
$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2); |
2430
|
|
|
|
2431
|
|
|
$open_tags[count($open_tags) - 2]['after'] = '</ul>'; |
2432
|
|
|
} |
2433
|
|
|
// Tell the [list] that it needs to close specially. |
2434
|
|
|
else |
2435
|
|
|
{ |
2436
|
|
|
// Move the li over, because we're not sure what we'll hit. |
2437
|
|
|
$open_tags[count($open_tags) - 1]['after'] = ''; |
2438
|
|
|
$open_tags[count($open_tags) - 2]['after'] = '</li></ul>'; |
2439
|
|
|
} |
2440
|
|
|
|
2441
|
|
|
continue; |
2442
|
|
|
} |
2443
|
|
|
|
2444
|
|
|
// Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. |
2445
|
|
|
if ($tag === null && $inside !== null && !empty($inside['require_children'])) |
2446
|
|
|
{ |
2447
|
|
|
array_pop($open_tags); |
2448
|
|
|
|
2449
|
|
|
$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos); |
2450
|
|
|
$pos += strlen($inside['after']) - 1 + 2; |
2451
|
|
|
} |
2452
|
|
|
|
2453
|
|
|
// No tag? Keep looking, then. Silly people using brackets without actual tags. |
2454
|
|
|
if ($tag === null) |
2455
|
|
|
continue; |
2456
|
|
|
|
2457
|
|
|
// Propagate the list to the child (so wrapping the disallowed tag won't work either.) |
2458
|
|
|
if (isset($inside['disallow_children'])) |
2459
|
|
|
$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children']; |
2460
|
|
|
|
2461
|
|
|
// Is this tag disabled? |
2462
|
|
|
if (isset($disabled[$tag['tag']])) |
2463
|
|
|
{ |
2464
|
|
|
if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) |
2465
|
|
|
{ |
2466
|
|
|
$tag['before'] = !empty($tag['block_level']) ? '<div>' : ''; |
2467
|
|
|
$tag['after'] = !empty($tag['block_level']) ? '</div>' : ''; |
2468
|
|
|
$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1'); |
2469
|
|
|
} |
2470
|
|
|
elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) |
2471
|
|
|
{ |
2472
|
|
|
$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : ''); |
2473
|
|
|
$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : ''); |
2474
|
|
|
} |
2475
|
|
|
else |
2476
|
|
|
$tag['content'] = $tag['disabled_content']; |
2477
|
|
|
} |
2478
|
|
|
|
2479
|
|
|
// we use this a lot |
2480
|
|
|
$tag_strlen = strlen($tag['tag']); |
2481
|
|
|
|
2482
|
|
|
// The only special case is 'html', which doesn't need to close things. |
2483
|
|
|
if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) |
2484
|
|
|
{ |
2485
|
|
|
$n = count($open_tags) - 1; |
2486
|
|
|
while (empty($open_tags[$n]['block_level']) && $n >= 0) |
2487
|
|
|
$n--; |
2488
|
|
|
|
2489
|
|
|
// Close all the non block level tags so this tag isn't surrounded by them. |
2490
|
|
|
for ($i = count($open_tags) - 1; $i > $n; $i--) |
2491
|
|
|
{ |
2492
|
|
|
$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos); |
2493
|
|
|
$ot_strlen = strlen($open_tags[$i]['after']); |
2494
|
|
|
$pos += $ot_strlen + 2; |
2495
|
|
|
$pos1 += $ot_strlen + 2; |
|
|
|
|
2496
|
|
|
|
2497
|
|
|
// Trim or eat trailing stuff... see comment at the end of the big loop. |
2498
|
|
|
$whitespace_regex = ''; |
2499
|
|
|
if (!empty($tag['block_level'])) |
2500
|
|
|
$whitespace_regex .= '( |\s)*(<br>)?'; |
2501
|
|
View Code Duplication |
if (!empty($tag['trim']) && $tag['trim'] != 'inside') |
2502
|
|
|
$whitespace_regex .= empty($tag['require_parents']) ? '( |\s)*' : '(<br>| |\s)*'; |
2503
|
|
View Code Duplication |
if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0) |
2504
|
|
|
$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); |
2505
|
|
|
|
2506
|
|
|
array_pop($open_tags); |
2507
|
|
|
} |
2508
|
|
|
} |
2509
|
|
|
|
2510
|
|
|
// Can't read past the end of the message |
2511
|
|
|
$pos1 = min(strlen($message), $pos1); |
2512
|
|
|
|
2513
|
|
|
// No type means 'parsed_content'. |
2514
|
|
|
if (!isset($tag['type'])) |
2515
|
|
|
{ |
2516
|
|
|
// @todo Check for end tag first, so people can say "I like that [i] tag"? |
|
|
|
|
2517
|
|
|
$open_tags[] = $tag; |
2518
|
|
|
$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1); |
2519
|
|
|
$pos += strlen($tag['before']) - 1 + 2; |
2520
|
|
|
} |
2521
|
|
|
// Don't parse the content, just skip it. |
2522
|
|
|
elseif ($tag['type'] == 'unparsed_content') |
2523
|
|
|
{ |
2524
|
|
|
$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1); |
2525
|
|
|
if ($pos2 === false) |
2526
|
|
|
continue; |
2527
|
|
|
|
2528
|
|
|
$data = substr($message, $pos1, $pos2 - $pos1); |
2529
|
|
|
|
2530
|
|
|
if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>') |
2531
|
|
|
$data = substr($data, 4); |
2532
|
|
|
|
2533
|
|
|
if (isset($tag['validate'])) |
2534
|
|
|
$tag['validate']($tag, $data, $disabled, $params); |
|
|
|
|
2535
|
|
|
|
2536
|
|
|
$code = strtr($tag['content'], array('$1' => $data)); |
2537
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen); |
2538
|
|
|
|
2539
|
|
|
$pos += strlen($code) - 1 + 2; |
2540
|
|
|
$last_pos = $pos + 1; |
2541
|
|
|
|
2542
|
|
|
} |
2543
|
|
|
// Don't parse the content, just skip it. |
2544
|
|
|
elseif ($tag['type'] == 'unparsed_equals_content') |
2545
|
|
|
{ |
2546
|
|
|
// The value may be quoted for some tags - check. |
2547
|
|
View Code Duplication |
if (isset($tag['quoted'])) |
2548
|
|
|
{ |
2549
|
|
|
$quoted = substr($message, $pos1, 6) == '"'; |
2550
|
|
|
if ($tag['quoted'] != 'optional' && !$quoted) |
2551
|
|
|
continue; |
2552
|
|
|
|
2553
|
|
|
if ($quoted) |
2554
|
|
|
$pos1 += 6; |
2555
|
|
|
} |
2556
|
|
|
else |
2557
|
|
|
$quoted = false; |
2558
|
|
|
|
2559
|
|
|
$pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); |
|
|
|
|
2560
|
|
|
if ($pos2 === false) |
2561
|
|
|
continue; |
2562
|
|
|
|
2563
|
|
|
$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2); |
2564
|
|
|
if ($pos3 === false) |
2565
|
|
|
continue; |
2566
|
|
|
|
2567
|
|
|
$data = array( |
2568
|
|
|
substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), |
|
|
|
|
2569
|
|
|
substr($message, $pos1, $pos2 - $pos1) |
2570
|
|
|
); |
2571
|
|
|
|
2572
|
|
|
if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>') |
2573
|
|
|
$data[0] = substr($data[0], 4); |
2574
|
|
|
|
2575
|
|
|
// Validation for my parking, please! |
2576
|
|
|
if (isset($tag['validate'])) |
2577
|
|
|
$tag['validate']($tag, $data, $disabled, $params); |
2578
|
|
|
|
2579
|
|
|
$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1])); |
2580
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen); |
2581
|
|
|
$pos += strlen($code) - 1 + 2; |
2582
|
|
|
} |
2583
|
|
|
// A closed tag, with no content or value. |
2584
|
|
|
elseif ($tag['type'] == 'closed') |
2585
|
|
|
{ |
2586
|
|
|
$pos2 = strpos($message, ']', $pos); |
2587
|
|
|
$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1); |
2588
|
|
|
$pos += strlen($tag['content']) - 1 + 2; |
2589
|
|
|
} |
2590
|
|
|
// This one is sorta ugly... :/. Unfortunately, it's needed for flash. |
2591
|
|
|
elseif ($tag['type'] == 'unparsed_commas_content') |
2592
|
|
|
{ |
2593
|
|
|
$pos2 = strpos($message, ']', $pos1); |
2594
|
|
|
if ($pos2 === false) |
2595
|
|
|
continue; |
2596
|
|
|
|
2597
|
|
|
$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2); |
2598
|
|
|
if ($pos3 === false) |
2599
|
|
|
continue; |
2600
|
|
|
|
2601
|
|
|
// We want $1 to be the content, and the rest to be csv. |
2602
|
|
|
$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1)); |
2603
|
|
|
$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1); |
2604
|
|
|
|
2605
|
|
|
if (isset($tag['validate'])) |
2606
|
|
|
$tag['validate']($tag, $data, $disabled, $params); |
2607
|
|
|
|
2608
|
|
|
$code = $tag['content']; |
2609
|
|
View Code Duplication |
foreach ($data as $k => $d) |
2610
|
|
|
$code = strtr($code, array('$' . ($k + 1) => trim($d))); |
2611
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen); |
2612
|
|
|
$pos += strlen($code) - 1 + 2; |
2613
|
|
|
} |
2614
|
|
|
// This has parsed content, and a csv value which is unparsed. |
2615
|
|
|
elseif ($tag['type'] == 'unparsed_commas') |
2616
|
|
|
{ |
2617
|
|
|
$pos2 = strpos($message, ']', $pos1); |
2618
|
|
|
if ($pos2 === false) |
2619
|
|
|
continue; |
2620
|
|
|
|
2621
|
|
|
$data = explode(',', substr($message, $pos1, $pos2 - $pos1)); |
2622
|
|
|
|
2623
|
|
|
if (isset($tag['validate'])) |
2624
|
|
|
$tag['validate']($tag, $data, $disabled, $params); |
2625
|
|
|
|
2626
|
|
|
// Fix after, for disabled code mainly. |
2627
|
|
View Code Duplication |
foreach ($data as $k => $d) |
2628
|
|
|
$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d))); |
2629
|
|
|
|
2630
|
|
|
$open_tags[] = $tag; |
2631
|
|
|
|
2632
|
|
|
// Replace them out, $1, $2, $3, $4, etc. |
|
|
|
|
2633
|
|
|
$code = $tag['before']; |
2634
|
|
View Code Duplication |
foreach ($data as $k => $d) |
2635
|
|
|
$code = strtr($code, array('$' . ($k + 1) => trim($d))); |
2636
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1); |
2637
|
|
|
$pos += strlen($code) - 1 + 2; |
2638
|
|
|
} |
2639
|
|
|
// A tag set to a value, parsed or not. |
2640
|
|
|
elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') |
2641
|
|
|
{ |
2642
|
|
|
// The value may be quoted for some tags - check. |
2643
|
|
View Code Duplication |
if (isset($tag['quoted'])) |
2644
|
|
|
{ |
2645
|
|
|
$quoted = substr($message, $pos1, 6) == '"'; |
2646
|
|
|
if ($tag['quoted'] != 'optional' && !$quoted) |
2647
|
|
|
continue; |
2648
|
|
|
|
2649
|
|
|
if ($quoted) |
2650
|
|
|
$pos1 += 6; |
2651
|
|
|
} |
2652
|
|
|
else |
2653
|
|
|
$quoted = false; |
2654
|
|
|
|
2655
|
|
|
$pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); |
|
|
|
|
2656
|
|
|
if ($pos2 === false) |
2657
|
|
|
continue; |
2658
|
|
|
|
2659
|
|
|
$data = substr($message, $pos1, $pos2 - $pos1); |
2660
|
|
|
|
2661
|
|
|
// Validation for my parking, please! |
2662
|
|
|
if (isset($tag['validate'])) |
2663
|
|
|
$tag['validate']($tag, $data, $disabled, $params); |
2664
|
|
|
|
2665
|
|
|
// For parsed content, we must recurse to avoid security problems. |
2666
|
|
|
if ($tag['type'] != 'unparsed_equals') |
2667
|
|
|
$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array()); |
2668
|
|
|
|
2669
|
|
|
$tag['after'] = strtr($tag['after'], array('$1' => $data)); |
2670
|
|
|
|
2671
|
|
|
$open_tags[] = $tag; |
2672
|
|
|
|
2673
|
|
|
$code = strtr($tag['before'], array('$1' => $data)); |
2674
|
|
|
$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7)); |
|
|
|
|
2675
|
|
|
$pos += strlen($code) - 1 + 2; |
2676
|
|
|
} |
2677
|
|
|
|
2678
|
|
|
// If this is block level, eat any breaks after it. |
2679
|
|
|
if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>') |
2680
|
|
|
$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5); |
2681
|
|
|
|
2682
|
|
|
// Are we trimming outside this tag? |
2683
|
|
|
if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>| |\s)*~', substr($message, $pos + 1), $matches) != 0) |
2684
|
|
|
$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0])); |
2685
|
|
|
} |
2686
|
|
|
|
2687
|
|
|
// Close any remaining tags. |
2688
|
|
|
while ($tag = array_pop($open_tags)) |
2689
|
|
|
$message .= "\n" . $tag['after'] . "\n"; |
2690
|
|
|
|
2691
|
|
|
// Parse the smileys within the parts where it can be done safely. |
2692
|
|
|
if ($smileys === true) |
2693
|
|
|
{ |
2694
|
|
|
$message_parts = explode("\n", $message); |
2695
|
|
|
for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) |
2696
|
|
|
parsesmileys($message_parts[$i]); |
2697
|
|
|
|
2698
|
|
|
$message = implode('', $message_parts); |
2699
|
|
|
} |
2700
|
|
|
|
2701
|
|
|
// No smileys, just get rid of the markers. |
2702
|
|
|
else |
2703
|
|
|
$message = strtr($message, array("\n" => '')); |
2704
|
|
|
|
2705
|
|
|
if ($message !== '' && $message[0] === ' ') |
2706
|
|
|
$message = ' ' . substr($message, 1); |
2707
|
|
|
|
2708
|
|
|
// Cleanup whitespace. |
2709
|
|
|
$message = strtr($message, array(' ' => ' ', "\r" => '', "\n" => '<br>', '<br> ' => '<br> ', ' ' => "\n")); |
2710
|
|
|
|
2711
|
|
|
// Allow mods access to what parse_bbc created |
2712
|
|
|
call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags)); |
2713
|
|
|
|
2714
|
|
|
// Cache the output if it took some time... |
2715
|
|
|
if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) |
2716
|
|
|
cache_put_data($cache_key, $message, 240); |
2717
|
|
|
|
2718
|
|
|
// If this was a force parse revert if needed. |
2719
|
|
|
if (!empty($parse_tags)) |
2720
|
|
|
{ |
2721
|
|
|
if (empty($temp_bbc)) |
2722
|
|
|
$bbc_codes = array(); |
2723
|
|
|
else |
2724
|
|
|
{ |
2725
|
|
|
$bbc_codes = $temp_bbc; |
2726
|
|
|
unset($temp_bbc); |
2727
|
|
|
} |
2728
|
|
|
} |
2729
|
|
|
|
2730
|
|
|
return $message; |
2731
|
|
|
} |
2732
|
|
|
|
2733
|
|
|
/** |
2734
|
|
|
* Parse smileys in the passed message. |
2735
|
|
|
* |
2736
|
|
|
* The smiley parsing function which makes pretty faces appear :). |
2737
|
|
|
* If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used. |
2738
|
|
|
* These are specifically not parsed in code tags [url=mailto:[email protected]] |
2739
|
|
|
* Caches the smileys from the database or array in memory. |
2740
|
|
|
* Doesn't return anything, but rather modifies message directly. |
2741
|
|
|
* |
2742
|
|
|
* @param string &$message The message to parse smileys in |
2743
|
|
|
*/ |
2744
|
|
|
function parsesmileys(&$message) |
2745
|
|
|
{ |
2746
|
|
|
global $modSettings, $txt, $user_info, $context, $smcFunc; |
|
|
|
|
2747
|
|
|
static $smileyPregSearch = null, $smileyPregReplacements = array(); |
2748
|
|
|
|
2749
|
|
|
// No smiley set at all?! |
2750
|
|
|
if ($user_info['smiley_set'] == 'none' || trim($message) == '') |
2751
|
|
|
return; |
2752
|
|
|
|
2753
|
|
|
// If smileyPregSearch hasn't been set, do it now. |
2754
|
|
|
if (empty($smileyPregSearch)) |
2755
|
|
|
{ |
2756
|
|
|
// Use the default smileys if it is disabled. (better for "portability" of smileys.) |
2757
|
|
|
if (empty($modSettings['smiley_enable'])) |
2758
|
|
|
{ |
2759
|
|
|
$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); |
2760
|
|
|
$smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif'); |
2761
|
|
|
$smileysdescs = array('', $txt['icon_cheesy'], $txt['icon_rolleyes'], $txt['icon_angry'], '', $txt['icon_smiley'], $txt['icon_wink'], $txt['icon_grin'], $txt['icon_sad'], $txt['icon_shocked'], $txt['icon_cool'], $txt['icon_tongue'], $txt['icon_huh'], $txt['icon_embarrassed'], $txt['icon_lips'], $txt['icon_kiss'], $txt['icon_cry'], $txt['icon_undecided'], '', '', '', ''); |
2762
|
|
|
} |
2763
|
|
|
else |
2764
|
|
|
{ |
2765
|
|
|
// Load the smileys in reverse order by length so they don't get parsed wrong. |
2766
|
|
|
if (($temp = cache_get_data('parsing_smileys', 480)) == null) |
2767
|
|
|
{ |
2768
|
|
|
$result = $smcFunc['db_query']('', ' |
2769
|
|
|
SELECT code, filename, description |
2770
|
|
|
FROM {db_prefix}smileys |
2771
|
|
|
ORDER BY LENGTH(code) DESC', |
2772
|
|
|
array( |
2773
|
|
|
) |
2774
|
|
|
); |
2775
|
|
|
$smileysfrom = array(); |
2776
|
|
|
$smileysto = array(); |
2777
|
|
|
$smileysdescs = array(); |
2778
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($result)) |
2779
|
|
|
{ |
2780
|
|
|
$smileysfrom[] = $row['code']; |
2781
|
|
|
$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']); |
2782
|
|
|
$smileysdescs[] = $row['description']; |
2783
|
|
|
} |
2784
|
|
|
$smcFunc['db_free_result']($result); |
2785
|
|
|
|
2786
|
|
|
cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480); |
2787
|
|
|
} |
2788
|
|
|
else |
2789
|
|
|
list ($smileysfrom, $smileysto, $smileysdescs) = $temp; |
2790
|
|
|
} |
2791
|
|
|
|
2792
|
|
|
// The non-breaking-space is a complex thing... |
2793
|
|
|
$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0'; |
2794
|
|
|
|
2795
|
|
|
// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley) |
2796
|
|
|
$smileyPregReplacements = array(); |
2797
|
|
|
$searchParts = array(); |
2798
|
|
|
$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/'); |
2799
|
|
|
|
2800
|
|
|
for ($i = 0, $n = count($smileysfrom); $i < $n; $i++) |
2801
|
|
|
{ |
2802
|
|
|
$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES); |
2803
|
|
|
$smileyCode = '<img src="' . $smileys_path . $smileysto[$i] . '" alt="' . strtr($specialChars, array(':' => ':', '(' => '(', ')' => ')', '$' => '$', '[' => '[')). '" title="' . strtr($smcFunc['htmlspecialchars']($smileysdescs[$i]), array(':' => ':', '(' => '(', ')' => ')', '$' => '$', '[' => '[')) . '" class="smiley">'; |
2804
|
|
|
|
2805
|
|
|
$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode; |
2806
|
|
|
|
2807
|
|
|
$searchParts[] = preg_quote($smileysfrom[$i], '~'); |
2808
|
|
|
if ($smileysfrom[$i] != $specialChars) |
2809
|
|
|
{ |
2810
|
|
|
$smileyPregReplacements[$specialChars] = $smileyCode; |
2811
|
|
|
$searchParts[] = preg_quote($specialChars, '~'); |
2812
|
|
|
} |
2813
|
|
|
} |
2814
|
|
|
|
2815
|
|
|
$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : ''); |
2816
|
|
|
} |
2817
|
|
|
|
2818
|
|
|
// Replace away! |
2819
|
|
|
$message = preg_replace_callback($smileyPregSearch, |
2820
|
|
|
function ($matches) use ($smileyPregReplacements) |
2821
|
|
|
{ |
2822
|
|
|
return $smileyPregReplacements[$matches[1]]; |
2823
|
|
|
}, $message); |
2824
|
|
|
} |
2825
|
|
|
|
2826
|
|
|
/** |
2827
|
|
|
* Highlight any code. |
2828
|
|
|
* |
2829
|
|
|
* Uses PHP's highlight_string() to highlight PHP syntax |
2830
|
|
|
* does special handling to keep the tabs in the code available. |
2831
|
|
|
* used to parse PHP code from inside [code] and [php] tags. |
2832
|
|
|
* |
2833
|
|
|
* @param string $code The code |
2834
|
|
|
* @return string The code with highlighted HTML. |
2835
|
|
|
*/ |
2836
|
|
|
function highlight_php_code($code) |
2837
|
|
|
{ |
2838
|
|
|
// Remove special characters. |
2839
|
|
|
$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '[' => '['))); |
2840
|
|
|
|
2841
|
|
|
$oldlevel = error_reporting(0); |
2842
|
|
|
|
2843
|
|
|
$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true)); |
2844
|
|
|
|
2845
|
|
|
error_reporting($oldlevel); |
2846
|
|
|
|
2847
|
|
|
// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. |
2848
|
|
|
$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer); |
2849
|
|
|
|
2850
|
|
|
return strtr($buffer, array('\'' => ''', '<code>' => '', '</code>' => '')); |
2851
|
|
|
} |
2852
|
|
|
|
2853
|
|
|
/** |
2854
|
|
|
* Make sure the browser doesn't come back and repost the form data. |
2855
|
|
|
* Should be used whenever anything is posted. |
2856
|
|
|
* |
2857
|
|
|
* @param string $setLocation The URL to redirect them to |
2858
|
|
|
* @param bool $refresh Whether to use a meta refresh instead |
2859
|
|
|
* @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily |
2860
|
|
|
*/ |
2861
|
|
|
function redirectexit($setLocation = '', $refresh = false, $permanent = false) |
2862
|
|
|
{ |
2863
|
|
|
global $scripturl, $context, $modSettings, $db_show_debug, $db_cache; |
|
|
|
|
2864
|
|
|
|
2865
|
|
|
// In case we have mail to send, better do that - as obExit doesn't always quite make it... |
2866
|
|
|
if (!empty($context['flush_mail'])) |
2867
|
|
|
// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\ |
|
|
|
|
2868
|
|
|
AddMailQueue(true); |
2869
|
|
|
|
2870
|
|
|
$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:'; |
2871
|
|
|
|
2872
|
|
|
if ($add) |
2873
|
|
|
$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : ''); |
2874
|
|
|
|
2875
|
|
|
// Put the session ID in. |
2876
|
|
|
if (defined('SID') && SID != '') |
2877
|
|
|
$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation); |
2878
|
|
|
// Keep that debug in their for template debugging! |
2879
|
|
View Code Duplication |
elseif (isset($_GET['debug'])) |
2880
|
|
|
$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation); |
2881
|
|
|
|
2882
|
|
|
if (!empty($modSettings['queryless_urls']) && (empty($context['server']['is_cgi']) || ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && (!empty($context['server']['is_apache']) || !empty($context['server']['is_lighttpd']) || !empty($context['server']['is_litespeed']))) |
2883
|
|
|
{ |
2884
|
|
|
if (defined('SID') && SID != '') |
2885
|
|
|
$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', |
2886
|
|
|
function ($m) use ($scripturl) |
|
|
|
|
2887
|
|
|
{ |
2888
|
|
|
return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : ""); |
|
|
|
|
2889
|
|
|
}, $setLocation); |
2890
|
|
View Code Duplication |
else |
2891
|
|
|
$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', |
2892
|
|
|
function ($m) use ($scripturl) |
2893
|
|
|
{ |
2894
|
|
|
return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : ""); |
|
|
|
|
2895
|
|
|
}, $setLocation); |
2896
|
|
|
} |
2897
|
|
|
|
2898
|
|
|
// Maybe integrations want to change where we are heading? |
2899
|
|
|
call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent)); |
2900
|
|
|
|
2901
|
|
|
// Set the header. |
2902
|
|
|
header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302); |
|
|
|
|
2903
|
|
|
|
2904
|
|
|
// Debugging. |
2905
|
|
|
if (isset($db_show_debug) && $db_show_debug === true) |
2906
|
|
|
$_SESSION['debug_redirect'] = $db_cache; |
2907
|
|
|
|
2908
|
|
|
obExit(false); |
2909
|
|
|
} |
2910
|
|
|
|
2911
|
|
|
/** |
2912
|
|
|
* Ends execution. Takes care of template loading and remembering the previous URL. |
2913
|
|
|
* @param bool $header Whether to do the header |
|
|
|
|
2914
|
|
|
* @param bool $do_footer Whether to do the footer |
|
|
|
|
2915
|
|
|
* @param bool $from_index Whether we're coming from the board index |
2916
|
|
|
* @param bool $from_fatal_error Whether we're coming from a fatal error |
2917
|
|
|
*/ |
2918
|
|
|
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false) |
2919
|
|
|
{ |
2920
|
|
|
global $context, $settings, $modSettings, $txt, $smcFunc; |
|
|
|
|
2921
|
|
|
static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false; |
2922
|
|
|
|
2923
|
|
|
// Attempt to prevent a recursive loop. |
2924
|
|
|
++$level; |
2925
|
|
|
if ($level > 1 && !$from_fatal_error && !$has_fatal_error) |
2926
|
|
|
exit; |
2927
|
|
|
if ($from_fatal_error) |
2928
|
|
|
$has_fatal_error = true; |
2929
|
|
|
|
2930
|
|
|
// Clear out the stat cache. |
2931
|
|
|
trackStats(); |
2932
|
|
|
|
2933
|
|
|
// If we have mail to send, send it. |
2934
|
|
|
if (!empty($context['flush_mail'])) |
2935
|
|
|
// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\ |
|
|
|
|
2936
|
|
|
AddMailQueue(true); |
2937
|
|
|
|
2938
|
|
|
$do_header = $header === null ? !$header_done : $header; |
2939
|
|
|
if ($do_footer === null) |
2940
|
|
|
$do_footer = $do_header; |
2941
|
|
|
|
2942
|
|
|
// Has the template/header been done yet? |
2943
|
|
|
if ($do_header) |
2944
|
|
|
{ |
2945
|
|
|
// Was the page title set last minute? Also update the HTML safe one. |
2946
|
|
|
if (!empty($context['page_title']) && empty($context['page_title_html_safe'])) |
2947
|
|
|
$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : ''); |
2948
|
|
|
|
2949
|
|
|
// Start up the session URL fixer. |
2950
|
|
|
ob_start('ob_sessrewrite'); |
2951
|
|
|
|
2952
|
|
|
if (!empty($settings['output_buffers']) && is_string($settings['output_buffers'])) |
2953
|
|
|
$buffers = explode(',', $settings['output_buffers']); |
2954
|
|
|
elseif (!empty($settings['output_buffers'])) |
2955
|
|
|
$buffers = $settings['output_buffers']; |
2956
|
|
|
else |
2957
|
|
|
$buffers = array(); |
2958
|
|
|
|
2959
|
|
|
if (isset($modSettings['integrate_buffer'])) |
2960
|
|
|
$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers); |
2961
|
|
|
|
2962
|
|
|
if (!empty($buffers)) |
2963
|
|
|
foreach ($buffers as $function) |
2964
|
|
|
{ |
2965
|
|
|
$call = call_helper($function, true); |
2966
|
|
|
|
2967
|
|
|
// Is it valid? |
2968
|
|
|
if (!empty($call)) |
2969
|
|
|
ob_start($call); |
2970
|
|
|
} |
2971
|
|
|
|
2972
|
|
|
// Display the screen in the logical order. |
2973
|
|
|
template_header(); |
2974
|
|
|
$header_done = true; |
2975
|
|
|
} |
2976
|
|
|
if ($do_footer) |
2977
|
|
|
{ |
2978
|
|
|
loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main'); |
2979
|
|
|
|
2980
|
|
|
// Anything special to put out? |
2981
|
|
|
if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml'])) |
2982
|
|
|
echo $context['insert_after_template']; |
2983
|
|
|
|
2984
|
|
|
// Just so we don't get caught in an endless loop of errors from the footer... |
2985
|
|
|
if (!$footer_done) |
2986
|
|
|
{ |
2987
|
|
|
$footer_done = true; |
2988
|
|
|
template_footer(); |
2989
|
|
|
|
2990
|
|
|
// (since this is just debugging... it's okay that it's after </html>.) |
2991
|
|
|
if (!isset($_REQUEST['xml'])) |
2992
|
|
|
displayDebug(); |
2993
|
|
|
} |
2994
|
|
|
} |
2995
|
|
|
|
2996
|
|
|
// Remember this URL in case someone doesn't like sending HTTP_REFERER. |
2997
|
|
|
if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false) |
2998
|
|
|
$_SESSION['old_url'] = $_SERVER['REQUEST_URL']; |
2999
|
|
|
|
3000
|
|
|
// For session check verification.... don't switch browsers... |
3001
|
|
|
$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT']; |
3002
|
|
|
|
3003
|
|
|
// Hand off the output to the portal, etc. we're integrated with. |
3004
|
|
|
call_integration_hook('integrate_exit', array($do_footer)); |
3005
|
|
|
|
3006
|
|
|
// Don't exit if we're coming from index.php; that will pass through normally. |
3007
|
|
|
if (!$from_index) |
3008
|
|
|
exit; |
3009
|
|
|
} |
3010
|
|
|
|
3011
|
|
|
/** |
3012
|
|
|
* Get the size of a specified image with better error handling. |
3013
|
|
|
* @todo see if it's better in Subs-Graphics, but one step at the time. |
|
|
|
|
3014
|
|
|
* Uses getimagesize() to determine the size of a file. |
3015
|
|
|
* Attempts to connect to the server first so it won't time out. |
3016
|
|
|
* |
3017
|
|
|
* @param string $url The URL of the image |
3018
|
|
|
* @return array|false The image size as array (width, height), or false on failure |
3019
|
|
|
*/ |
3020
|
|
|
function url_image_size($url) |
3021
|
|
|
{ |
3022
|
|
|
global $sourcedir; |
|
|
|
|
3023
|
|
|
|
3024
|
|
|
// Make sure it is a proper URL. |
3025
|
|
|
$url = str_replace(' ', '%20', $url); |
3026
|
|
|
|
3027
|
|
|
// Can we pull this from the cache... please please? |
3028
|
|
|
if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null) |
3029
|
|
|
return $temp; |
3030
|
|
|
$t = microtime(); |
|
|
|
|
3031
|
|
|
|
3032
|
|
|
// Get the host to pester... |
3033
|
|
|
preg_match('~^\w+://(.+?)/(.*)$~', $url, $match); |
3034
|
|
|
|
3035
|
|
|
// Can't figure it out, just try the image size. |
3036
|
|
|
if ($url == '' || $url == 'http://' || $url == 'https://') |
3037
|
|
|
{ |
3038
|
|
|
return false; |
3039
|
|
|
} |
3040
|
|
|
elseif (!isset($match[1])) |
3041
|
|
|
{ |
3042
|
|
|
$size = @getimagesize($url); |
3043
|
|
|
} |
3044
|
|
|
else |
3045
|
|
|
{ |
3046
|
|
|
// Try to connect to the server... give it half a second. |
3047
|
|
|
$temp = 0; |
3048
|
|
|
$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5); |
|
|
|
|
3049
|
|
|
|
3050
|
|
|
// Successful? Continue... |
|
|
|
|
3051
|
|
|
if ($fp != false) |
3052
|
|
|
{ |
3053
|
|
|
// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.) |
3054
|
|
|
fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/SMF' . "\r\n" . 'Connection: close' . "\r\n\r\n"); |
3055
|
|
|
|
3056
|
|
|
// Read in the HTTP/1.1 or whatever. |
3057
|
|
|
$test = substr(fgets($fp, 11), -1); |
3058
|
|
|
fclose($fp); |
3059
|
|
|
|
3060
|
|
|
// See if it returned a 404/403 or something. |
3061
|
|
|
if ($test < 4) |
3062
|
|
|
{ |
3063
|
|
|
$size = @getimagesize($url); |
3064
|
|
|
|
3065
|
|
|
// This probably means allow_url_fopen is off, let's try GD. |
3066
|
|
|
if ($size === false && function_exists('imagecreatefromstring')) |
3067
|
|
|
{ |
3068
|
|
|
include_once($sourcedir . '/Subs-Package.php'); |
3069
|
|
|
|
3070
|
|
|
// It's going to hate us for doing this, but another request... |
3071
|
|
|
$image = @imagecreatefromstring(fetch_web_data($url)); |
3072
|
|
|
if ($image !== false) |
3073
|
|
|
{ |
3074
|
|
|
$size = array(imagesx($image), imagesy($image)); |
3075
|
|
|
imagedestroy($image); |
3076
|
|
|
} |
3077
|
|
|
} |
3078
|
|
|
} |
3079
|
|
|
} |
3080
|
|
|
} |
3081
|
|
|
|
3082
|
|
|
// If we didn't get it, we failed. |
3083
|
|
|
if (!isset($size)) |
3084
|
|
|
$size = false; |
3085
|
|
|
|
3086
|
|
|
// If this took a long time, we may never have to do it again, but then again we might... |
3087
|
|
View Code Duplication |
if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8) |
3088
|
|
|
cache_put_data('url_image_size-' . md5($url), $size, 240); |
3089
|
|
|
|
3090
|
|
|
// Didn't work. |
3091
|
|
|
return $size; |
3092
|
|
|
} |
3093
|
|
|
|
3094
|
|
|
/** |
3095
|
|
|
* Sets up the basic theme context stuff. |
3096
|
|
|
* @param bool $forceload Whether to load the theme even if it's already loaded |
3097
|
|
|
*/ |
3098
|
|
|
function setupThemeContext($forceload = false) |
3099
|
|
|
{ |
3100
|
|
|
global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance; |
|
|
|
|
3101
|
|
|
global $smcFunc; |
|
|
|
|
3102
|
|
|
static $loaded = false; |
3103
|
|
|
|
3104
|
|
|
// Under SSI this function can be called more then once. That can cause some problems. |
3105
|
|
|
// So only run the function once unless we are forced to run it again. |
3106
|
|
|
if ($loaded && !$forceload) |
3107
|
|
|
return; |
3108
|
|
|
|
3109
|
|
|
$loaded = true; |
3110
|
|
|
|
3111
|
|
|
$context['in_maintenance'] = !empty($maintenance); |
3112
|
|
|
$context['current_time'] = timeformat(time(), false); |
3113
|
|
|
$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : ''; |
3114
|
|
|
|
3115
|
|
|
// Get some news... |
3116
|
|
|
$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news']))))); |
3117
|
|
|
for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++) |
3118
|
|
|
{ |
3119
|
|
|
if (trim($context['news_lines'][$i]) == '') |
3120
|
|
|
continue; |
3121
|
|
|
|
3122
|
|
|
// Clean it up for presentation ;). |
3123
|
|
|
$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i); |
3124
|
|
|
} |
3125
|
|
|
if (!empty($context['news_lines'])) |
3126
|
|
|
$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)]; |
3127
|
|
|
|
3128
|
|
|
if (!$user_info['is_guest']) |
3129
|
|
|
{ |
3130
|
|
|
$context['user']['messages'] = &$user_info['messages']; |
3131
|
|
|
$context['user']['unread_messages'] = &$user_info['unread_messages']; |
3132
|
|
|
$context['user']['alerts'] = &$user_info['alerts']; |
3133
|
|
|
|
3134
|
|
|
// Personal message popup... |
3135
|
|
|
if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0)) |
3136
|
|
|
$context['user']['popup_messages'] = true; |
3137
|
|
|
else |
3138
|
|
|
$context['user']['popup_messages'] = false; |
3139
|
|
|
$_SESSION['unread_messages'] = $user_info['unread_messages']; |
3140
|
|
|
|
3141
|
|
|
if (allowedTo('moderate_forum')) |
3142
|
|
|
$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0; |
3143
|
|
|
|
3144
|
|
|
$context['user']['avatar'] = array(); |
3145
|
|
|
|
3146
|
|
|
// Check for gravatar first since we might be forcing them... |
3147
|
|
|
if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride'])) |
3148
|
|
|
{ |
3149
|
|
|
if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11) |
3150
|
|
|
$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11)); |
3151
|
|
|
else |
3152
|
|
|
$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']); |
3153
|
|
|
} |
3154
|
|
|
// Uploaded? |
3155
|
|
|
elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach'])) |
3156
|
|
|
$context['user']['avatar']['href'] = $user_info['avatar']['custom_dir'] ? $modSettings['custom_avatar_url'] . '/' . $user_info['avatar']['filename'] : $scripturl . '?action=dlattach;attach=' . $user_info['avatar']['id_attach'] . ';type=avatar'; |
3157
|
|
|
// Full URL? |
3158
|
|
|
elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0) |
3159
|
|
|
$context['user']['avatar']['href'] = $user_info['avatar']['url']; |
3160
|
|
|
// Otherwise we assume it's server stored. |
3161
|
|
|
elseif ($user_info['avatar']['url'] != '') |
3162
|
|
|
$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']); |
3163
|
|
|
// No avatar at all? Fine, we have a big fat default avatar ;) |
3164
|
|
|
else |
3165
|
|
|
$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png'; |
3166
|
|
|
|
3167
|
|
|
if (!empty($context['user']['avatar'])) |
3168
|
|
|
$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">'; |
3169
|
|
|
|
3170
|
|
|
// Figure out how long they've been logged in. |
3171
|
|
|
$context['user']['total_time_logged_in'] = array( |
3172
|
|
|
'days' => floor($user_info['total_time_logged_in'] / 86400), |
3173
|
|
|
'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600), |
3174
|
|
|
'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60) |
3175
|
|
|
); |
3176
|
|
|
} |
3177
|
|
|
else |
3178
|
|
|
{ |
3179
|
|
|
$context['user']['messages'] = 0; |
3180
|
|
|
$context['user']['unread_messages'] = 0; |
3181
|
|
|
$context['user']['avatar'] = array(); |
3182
|
|
|
$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0); |
3183
|
|
|
$context['user']['popup_messages'] = false; |
3184
|
|
|
|
3185
|
|
|
if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1) |
3186
|
|
|
$txt['welcome_guest'] .= $txt['welcome_guest_activate']; |
3187
|
|
|
|
3188
|
|
|
// If we've upgraded recently, go easy on the passwords. |
3189
|
|
|
if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime'])) |
3190
|
|
|
$context['disable_login_hashing'] = true; |
3191
|
|
|
} |
3192
|
|
|
|
3193
|
|
|
// Setup the main menu items. |
3194
|
|
|
setupMenuContext(); |
3195
|
|
|
|
3196
|
|
|
// This is here because old index templates might still use it. |
3197
|
|
|
$context['show_news'] = !empty($settings['enable_news']); |
3198
|
|
|
|
3199
|
|
|
// This is done to allow theme authors to customize it as they want. |
3200
|
|
|
$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm'); |
3201
|
|
|
|
3202
|
|
|
// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array. |
3203
|
|
|
if ($context['show_pm_popup']) |
3204
|
|
|
addInlineJavaScript(' |
3205
|
|
|
jQuery(document).ready(function($) { |
3206
|
|
|
new smc_Popup({ |
3207
|
|
|
heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ', |
3208
|
|
|
content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ', |
3209
|
|
|
icon_class: \'generic_icons mail_new\' |
3210
|
|
|
}); |
3211
|
|
|
});'); |
3212
|
|
|
|
3213
|
|
|
// Add a generic "Are you sure?" confirmation message. |
3214
|
|
|
addInlineJavaScript(' |
3215
|
|
|
var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';'); |
3216
|
|
|
|
3217
|
|
|
// Now add the capping code for avatars. |
3218
|
|
|
if (!empty($modSettings['avatar_max_width_external']) && !empty($modSettings['avatar_max_height_external']) && !empty($modSettings['avatar_action_too_large']) && $modSettings['avatar_action_too_large'] == 'option_css_resize') |
3219
|
|
|
addInlineCss(' |
3220
|
|
|
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }'); |
3221
|
|
|
|
3222
|
|
|
// This looks weird, but it's because BoardIndex.php references the variable. |
3223
|
|
|
$context['common_stats']['latest_member'] = array( |
3224
|
|
|
'id' => $modSettings['latestMember'], |
3225
|
|
|
'name' => $modSettings['latestRealName'], |
3226
|
|
|
'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'], |
3227
|
|
|
'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>', |
3228
|
|
|
); |
3229
|
|
|
$context['common_stats'] = array( |
3230
|
|
|
'total_posts' => comma_format($modSettings['totalMessages']), |
3231
|
|
|
'total_topics' => comma_format($modSettings['totalTopics']), |
3232
|
|
|
'total_members' => comma_format($modSettings['totalMembers']), |
3233
|
|
|
'latest_member' => $context['common_stats']['latest_member'], |
3234
|
|
|
); |
3235
|
|
|
$context['common_stats']['boardindex_total_posts'] = sprintf($txt['boardindex_total_posts'], $context['common_stats']['total_posts'], $context['common_stats']['total_topics'], $context['common_stats']['total_members']); |
3236
|
|
|
|
3237
|
|
|
if (empty($settings['theme_version'])) |
3238
|
|
|
addJavaScriptVar('smf_scripturl', $scripturl); |
3239
|
|
|
|
3240
|
|
|
if (!isset($context['page_title'])) |
3241
|
|
|
$context['page_title'] = ''; |
3242
|
|
|
|
3243
|
|
|
// Set some specific vars. |
3244
|
|
|
$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : ''); |
3245
|
|
|
$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : ''; |
3246
|
|
|
|
3247
|
|
|
// Content related meta tags, including Open Graph |
3248
|
|
|
$context['meta_tags'][] = array('property' => 'og:site_name', 'content' => $context['forum_name']); |
3249
|
|
|
$context['meta_tags'][] = array('property' => 'og:title', 'content' => $context['page_title_html_safe']); |
3250
|
|
|
|
3251
|
|
|
if (!empty($context['meta_keywords'])) |
3252
|
|
|
$context['meta_tags'][] = array('name' => 'keywords', 'content' => $context['meta_keywords']); |
3253
|
|
|
|
3254
|
|
|
if (!empty($context['canonical_url'])) |
3255
|
|
|
$context['meta_tags'][] = array('property' => 'og:url', 'content' => $context['canonical_url']); |
3256
|
|
|
|
3257
|
|
|
if (!empty($settings['og_image'])) |
3258
|
|
|
$context['meta_tags'][] = array('property' => 'og:image', 'content' => $settings['og_image']); |
3259
|
|
|
|
3260
|
|
|
if (!empty($context['meta_description'])) |
3261
|
|
|
{ |
3262
|
|
|
$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['meta_description']); |
3263
|
|
|
$context['meta_tags'][] = array('name' => 'description', 'content' => $context['meta_description']); |
3264
|
|
|
} |
3265
|
|
|
else |
3266
|
|
|
{ |
3267
|
|
|
$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['page_title_html_safe']); |
3268
|
|
|
$context['meta_tags'][] = array('name' => 'description', 'content' => $context['page_title_html_safe']); |
3269
|
|
|
} |
3270
|
|
|
|
3271
|
|
|
call_integration_hook('integrate_theme_context'); |
3272
|
|
|
} |
3273
|
|
|
|
3274
|
|
|
/** |
3275
|
|
|
* Helper function to set the system memory to a needed value |
3276
|
|
|
* - If the needed memory is greater than current, will attempt to get more |
3277
|
|
|
* - if in_use is set to true, will also try to take the current memory usage in to account |
3278
|
|
|
* |
3279
|
|
|
* @param string $needed The amount of memory to request, if needed, like 256M |
3280
|
|
|
* @param bool $in_use Set to true to account for current memory usage of the script |
3281
|
|
|
* @return boolean True if we have at least the needed memory |
3282
|
|
|
*/ |
3283
|
|
|
function setMemoryLimit($needed, $in_use = false) |
3284
|
|
|
{ |
3285
|
|
|
// everything in bytes |
3286
|
|
|
$memory_current = memoryReturnBytes(ini_get('memory_limit')); |
3287
|
|
|
$memory_needed = memoryReturnBytes($needed); |
3288
|
|
|
|
3289
|
|
|
// should we account for how much is currently being used? |
3290
|
|
|
if ($in_use) |
3291
|
|
|
$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576); |
3292
|
|
|
|
3293
|
|
|
// if more is needed, request it |
3294
|
|
|
if ($memory_current < $memory_needed) |
3295
|
|
|
{ |
3296
|
|
|
@ini_set('memory_limit', ceil($memory_needed / 1048576) . 'M'); |
|
|
|
|
3297
|
|
|
$memory_current = memoryReturnBytes(ini_get('memory_limit')); |
3298
|
|
|
} |
3299
|
|
|
|
3300
|
|
|
$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit'))); |
3301
|
|
|
|
3302
|
|
|
// return success or not |
3303
|
|
|
return (bool) ($memory_current >= $memory_needed); |
3304
|
|
|
} |
3305
|
|
|
|
3306
|
|
|
/** |
3307
|
|
|
* Helper function to convert memory string settings to bytes |
3308
|
|
|
* |
3309
|
|
|
* @param string $val The byte string, like 256M or 1G |
3310
|
|
|
* @return integer The string converted to a proper integer in bytes |
3311
|
|
|
*/ |
3312
|
|
|
function memoryReturnBytes($val) |
3313
|
|
|
{ |
3314
|
|
|
if (is_integer($val)) |
3315
|
|
|
return $val; |
3316
|
|
|
|
3317
|
|
|
// Separate the number from the designator |
3318
|
|
|
$val = trim($val); |
3319
|
|
|
$num = intval(substr($val, 0, strlen($val) - 1)); |
3320
|
|
|
$last = strtolower(substr($val, -1)); |
3321
|
|
|
|
3322
|
|
|
// convert to bytes |
3323
|
|
|
switch ($last) |
3324
|
|
|
{ |
3325
|
|
|
case 'g': |
3326
|
|
|
$num *= 1024; |
3327
|
|
|
case 'm': |
3328
|
|
|
$num *= 1024; |
3329
|
|
|
case 'k': |
3330
|
|
|
$num *= 1024; |
3331
|
|
|
} |
3332
|
|
|
return $num; |
3333
|
|
|
} |
3334
|
|
|
|
3335
|
|
|
/** |
3336
|
|
|
* The header template |
3337
|
|
|
*/ |
3338
|
|
|
function template_header() |
3339
|
|
|
{ |
3340
|
|
|
global $txt, $modSettings, $context, $user_info, $boarddir, $cachedir; |
|
|
|
|
3341
|
|
|
|
3342
|
|
|
setupThemeContext(); |
3343
|
|
|
|
3344
|
|
|
// Print stuff to prevent caching of pages (except on attachment errors, etc.) |
3345
|
|
|
if (empty($context['no_last_modified'])) |
3346
|
|
|
{ |
3347
|
|
|
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); |
3348
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); |
3349
|
|
|
|
3350
|
|
|
// Are we debugging the template/html content? |
3351
|
|
|
if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie')) |
3352
|
|
|
header('Content-Type: application/xhtml+xml'); |
3353
|
|
View Code Duplication |
elseif (!isset($_REQUEST['xml'])) |
3354
|
|
|
header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); |
3355
|
|
|
} |
3356
|
|
|
|
3357
|
|
|
header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); |
3358
|
|
|
|
3359
|
|
|
// We need to splice this in after the body layer, or after the main layer for older stuff. |
3360
|
|
|
if ($context['in_maintenance'] && $context['user']['is_admin']) |
3361
|
|
|
{ |
3362
|
|
|
$position = array_search('body', $context['template_layers']); |
3363
|
|
|
if ($position === false) |
3364
|
|
|
$position = array_search('main', $context['template_layers']); |
3365
|
|
|
|
3366
|
|
|
if ($position !== false) |
3367
|
|
|
{ |
3368
|
|
|
$before = array_slice($context['template_layers'], 0, $position + 1); |
3369
|
|
|
$after = array_slice($context['template_layers'], $position + 1); |
3370
|
|
|
$context['template_layers'] = array_merge($before, array('maint_warning'), $after); |
3371
|
|
|
} |
3372
|
|
|
} |
3373
|
|
|
|
3374
|
|
|
$checked_securityFiles = false; |
3375
|
|
|
$showed_banned = false; |
3376
|
|
|
foreach ($context['template_layers'] as $layer) |
3377
|
|
|
{ |
3378
|
|
|
loadSubTemplate($layer . '_above', true); |
3379
|
|
|
|
3380
|
|
|
// May seem contrived, but this is done in case the body and main layer aren't there... |
3381
|
|
|
if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles) |
3382
|
|
|
{ |
3383
|
|
|
$checked_securityFiles = true; |
3384
|
|
|
|
3385
|
|
|
$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~'); |
3386
|
|
|
|
3387
|
|
|
// Add your own files. |
3388
|
|
|
call_integration_hook('integrate_security_files', array(&$securityFiles)); |
3389
|
|
|
|
3390
|
|
|
foreach ($securityFiles as $i => $securityFile) |
3391
|
|
|
{ |
3392
|
|
|
if (!file_exists($boarddir . '/' . $securityFile)) |
3393
|
|
|
unset($securityFiles[$i]); |
3394
|
|
|
} |
3395
|
|
|
|
3396
|
|
|
// We are already checking so many files...just few more doesn't make any difference! :P |
3397
|
|
|
if (!empty($modSettings['currentAttachmentUploadDir'])) |
3398
|
|
|
$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; |
3399
|
|
|
|
3400
|
|
|
else |
3401
|
|
|
$path = $modSettings['attachmentUploadDir']; |
3402
|
|
|
|
3403
|
|
|
secureDirectory($path, true); |
3404
|
|
|
secureDirectory($cachedir); |
3405
|
|
|
|
3406
|
|
|
// If agreement is enabled, at least the english version shall exists |
3407
|
|
|
if ($modSettings['requireAgreement']) |
3408
|
|
|
$agreement = !file_exists($boarddir . '/agreement.txt'); |
3409
|
|
|
|
3410
|
|
|
if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement)) |
3411
|
|
|
{ |
3412
|
|
|
echo ' |
3413
|
|
|
<div class="errorbox"> |
3414
|
|
|
<p class="alert">!!</p> |
3415
|
|
|
<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3> |
3416
|
|
|
<p>'; |
3417
|
|
|
|
3418
|
|
|
foreach ($securityFiles as $securityFile) |
3419
|
|
|
{ |
3420
|
|
|
echo ' |
3421
|
|
|
', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>'; |
3422
|
|
|
|
3423
|
|
|
if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~') |
3424
|
|
|
echo ' |
3425
|
|
|
', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>'; |
3426
|
|
|
} |
3427
|
|
|
|
3428
|
|
|
if (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) |
3429
|
|
|
echo ' |
3430
|
|
|
<strong>', $txt['cache_writable'], '</strong><br>'; |
3431
|
|
|
|
3432
|
|
|
if (!empty($agreement)) |
3433
|
|
|
echo ' |
3434
|
|
|
<strong>', $txt['agreement_missing'], '</strong><br>'; |
3435
|
|
|
|
3436
|
|
|
echo ' |
3437
|
|
|
</p> |
3438
|
|
|
</div>'; |
3439
|
|
|
} |
3440
|
|
|
} |
3441
|
|
|
// If the user is banned from posting inform them of it. |
3442
|
|
|
elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned) |
3443
|
|
|
{ |
3444
|
|
|
$showed_banned = true; |
3445
|
|
|
echo ' |
3446
|
|
|
<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;"> |
3447
|
|
|
', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']); |
3448
|
|
|
|
3449
|
|
|
if (!empty($_SESSION['ban']['cannot_post']['reason'])) |
3450
|
|
|
echo ' |
3451
|
|
|
<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>'; |
3452
|
|
|
|
3453
|
|
|
if (!empty($_SESSION['ban']['expire_time'])) |
3454
|
|
|
echo ' |
3455
|
|
|
<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>'; |
3456
|
|
|
else |
3457
|
|
|
echo ' |
3458
|
|
|
<div>', $txt['your_ban_expires_never'], '</div>'; |
3459
|
|
|
|
3460
|
|
|
echo ' |
3461
|
|
|
</div>'; |
3462
|
|
|
} |
3463
|
|
|
} |
3464
|
|
|
} |
3465
|
|
|
|
3466
|
|
|
/** |
3467
|
|
|
* Show the copyright. |
3468
|
|
|
*/ |
3469
|
|
|
function theme_copyright() |
3470
|
|
|
{ |
3471
|
|
|
global $forum_copyright, $software_year, $forum_version; |
|
|
|
|
3472
|
|
|
|
3473
|
|
|
// Don't display copyright for things like SSI. |
3474
|
|
|
if (!isset($forum_version) || !isset($software_year)) |
3475
|
|
|
return; |
3476
|
|
|
|
3477
|
|
|
// Put in the version... |
3478
|
|
|
printf($forum_copyright, $forum_version, $software_year); |
3479
|
|
|
} |
3480
|
|
|
|
3481
|
|
|
/** |
3482
|
|
|
* The template footer |
3483
|
|
|
*/ |
3484
|
|
|
function template_footer() |
3485
|
|
|
{ |
3486
|
|
|
global $context, $modSettings, $time_start, $db_count; |
|
|
|
|
3487
|
|
|
|
3488
|
|
|
// Show the load time? (only makes sense for the footer.) |
3489
|
|
|
$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']); |
3490
|
|
|
$context['load_time'] = round(microtime(true) - $time_start, 3); |
3491
|
|
|
$context['load_queries'] = $db_count; |
3492
|
|
|
|
3493
|
|
|
foreach (array_reverse($context['template_layers']) as $layer) |
3494
|
|
|
loadSubTemplate($layer . '_below', true); |
3495
|
|
|
} |
3496
|
|
|
|
3497
|
|
|
/** |
3498
|
|
|
* Output the Javascript files |
3499
|
|
|
* - tabbing in this function is to make the HTML source look good proper |
3500
|
|
|
* - if defered is set function will output all JS (source & inline) set to load at page end |
3501
|
|
|
* |
3502
|
|
|
* @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag) |
3503
|
|
|
*/ |
3504
|
|
|
function template_javascript($do_deferred = false) |
3505
|
|
|
{ |
3506
|
|
|
global $context, $modSettings, $settings; |
|
|
|
|
3507
|
|
|
|
3508
|
|
|
// Use this hook to minify/optimize Javascript files and vars |
3509
|
|
|
call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred)); |
3510
|
|
|
|
3511
|
|
|
$toMinify = array(); |
3512
|
|
|
$toMinifyDefer = array(); |
3513
|
|
|
|
3514
|
|
|
// Ouput the declared Javascript variables. |
3515
|
|
|
if (!empty($context['javascript_vars']) && !$do_deferred) |
3516
|
|
|
{ |
3517
|
|
|
echo ' |
3518
|
|
|
<script>'; |
3519
|
|
|
|
3520
|
|
|
foreach ($context['javascript_vars'] as $key => $value) |
3521
|
|
|
{ |
3522
|
|
|
if (empty($value)) |
3523
|
|
|
{ |
3524
|
|
|
echo ' |
3525
|
|
|
var ', $key, ';'; |
3526
|
|
|
} |
3527
|
|
|
else |
3528
|
|
|
{ |
3529
|
|
|
echo ' |
3530
|
|
|
var ', $key, ' = ', $value, ';'; |
3531
|
|
|
} |
3532
|
|
|
} |
3533
|
|
|
|
3534
|
|
|
echo ' |
3535
|
|
|
</script>'; |
3536
|
|
|
} |
3537
|
|
|
|
3538
|
|
|
// While we have JavaScript files to place in the template. |
3539
|
|
|
foreach ($context['javascript_files'] as $id => $js_file) |
3540
|
|
|
{ |
3541
|
|
|
// Last minute call! allow theme authors to disable single files. |
3542
|
|
|
if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files'])) |
3543
|
|
|
continue; |
3544
|
|
|
|
3545
|
|
|
// By default all files don't get minimized unless the file explicitly says so! |
3546
|
|
|
if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files'])) |
3547
|
|
|
{ |
3548
|
|
|
if ($do_deferred && !empty($js_file['options']['defer'])) |
3549
|
|
|
$toMinifyDefer[] = $js_file; |
3550
|
|
|
|
3551
|
|
|
elseif (!$do_deferred && empty($js_file['options']['defer'])) |
3552
|
|
|
$toMinify[] = $js_file; |
3553
|
|
|
|
3554
|
|
|
// Grab a random seed. |
3555
|
|
|
if (!isset($minSeed)) |
3556
|
|
|
$minSeed = $js_file['options']['seed']; |
3557
|
|
|
} |
3558
|
|
|
|
3559
|
|
|
elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer']))) |
3560
|
|
|
echo ' |
3561
|
|
|
<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>'; |
3562
|
|
|
} |
3563
|
|
|
|
3564
|
|
|
if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer))) |
3565
|
|
|
{ |
3566
|
|
|
$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred); |
3567
|
|
|
|
3568
|
|
|
// Minify process couldn't work, print each individual files. |
3569
|
|
|
if (!empty($result) && is_array($result)) |
3570
|
|
|
foreach ($result as $minFailedFile) |
3571
|
|
|
echo ' |
3572
|
|
|
<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>'; |
3573
|
|
|
|
3574
|
|
|
else |
3575
|
|
|
echo ' |
3576
|
|
|
<script src="', $settings['theme_url'] ,'/scripts/minified', ($do_deferred ? '_deferred' : '') ,'.js', $minSeed ,'"></script>'; |
|
|
|
|
3577
|
|
|
} |
3578
|
|
|
|
3579
|
|
|
// Inline JavaScript - Actually useful some times! |
3580
|
|
|
if (!empty($context['javascript_inline'])) |
3581
|
|
|
{ |
3582
|
|
View Code Duplication |
if (!empty($context['javascript_inline']['defer']) && $do_deferred) |
3583
|
|
|
{ |
3584
|
|
|
echo ' |
3585
|
|
|
<script>'; |
3586
|
|
|
|
3587
|
|
|
foreach ($context['javascript_inline']['defer'] as $js_code) |
3588
|
|
|
echo $js_code; |
3589
|
|
|
|
3590
|
|
|
echo ' |
3591
|
|
|
</script>'; |
3592
|
|
|
} |
3593
|
|
|
|
3594
|
|
View Code Duplication |
if (!empty($context['javascript_inline']['standard']) && !$do_deferred) |
3595
|
|
|
{ |
3596
|
|
|
echo ' |
3597
|
|
|
<script>'; |
3598
|
|
|
|
3599
|
|
|
foreach ($context['javascript_inline']['standard'] as $js_code) |
3600
|
|
|
echo $js_code; |
3601
|
|
|
|
3602
|
|
|
echo ' |
3603
|
|
|
</script>'; |
3604
|
|
|
} |
3605
|
|
|
} |
3606
|
|
|
} |
3607
|
|
|
|
3608
|
|
|
/** |
3609
|
|
|
* Output the CSS files |
3610
|
|
|
* |
3611
|
|
|
*/ |
3612
|
|
|
function template_css() |
3613
|
|
|
{ |
3614
|
|
|
global $context, $db_show_debug, $boardurl, $settings, $modSettings; |
|
|
|
|
3615
|
|
|
|
3616
|
|
|
// Use this hook to minify/optimize CSS files |
3617
|
|
|
call_integration_hook('integrate_pre_css_output'); |
3618
|
|
|
|
3619
|
|
|
$toMinify = array(); |
3620
|
|
|
$normal = array(); |
3621
|
|
|
|
3622
|
|
|
foreach ($context['css_files'] as $id => $file) |
3623
|
|
|
{ |
3624
|
|
|
// Last minute call! allow theme authors to disable single files. |
3625
|
|
|
if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files'])) |
3626
|
|
|
continue; |
3627
|
|
|
|
3628
|
|
|
// By default all files don't get minimized unless the file explicitly says so! |
3629
|
|
|
if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files'])) |
3630
|
|
|
{ |
3631
|
|
|
$toMinify[] = $file; |
3632
|
|
|
|
3633
|
|
|
// Grab a random seed. |
3634
|
|
|
if (!isset($minSeed)) |
3635
|
|
|
$minSeed = $file['options']['seed']; |
3636
|
|
|
} |
3637
|
|
|
|
3638
|
|
|
else |
3639
|
|
|
$normal[] = $file['fileUrl']; |
3640
|
|
|
} |
3641
|
|
|
|
3642
|
|
|
if (!empty($toMinify)) |
3643
|
|
|
{ |
3644
|
|
|
$result = custMinify($toMinify, 'css'); |
3645
|
|
|
|
3646
|
|
|
// Minify process couldn't work, print each individual files. |
3647
|
|
|
if (!empty($result) && is_array($result)) |
3648
|
|
|
foreach ($result as $minFailedFile) |
3649
|
|
|
echo ' |
3650
|
|
|
<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">'; |
3651
|
|
|
|
3652
|
|
|
else |
3653
|
|
|
echo ' |
3654
|
|
|
<link rel="stylesheet" href="', $settings['theme_url'] ,'/css/minified.css', $minSeed ,'">'; |
|
|
|
|
3655
|
|
|
} |
3656
|
|
|
|
3657
|
|
|
// Print the rest after the minified files. |
3658
|
|
|
if (!empty($normal)) |
3659
|
|
|
foreach ($normal as $nf) |
3660
|
|
|
echo ' |
3661
|
|
|
<link rel="stylesheet" href="', $nf ,'">'; |
3662
|
|
|
|
3663
|
|
|
if ($db_show_debug === true) |
3664
|
|
|
{ |
3665
|
|
|
// Try to keep only what's useful. |
3666
|
|
|
$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => ''); |
3667
|
|
|
foreach ($context['css_files'] as $file) |
3668
|
|
|
$context['debug']['sheets'][] = strtr($file['fileName'], $repl); |
3669
|
|
|
} |
3670
|
|
|
|
3671
|
|
|
if (!empty($context['css_header'])) |
3672
|
|
|
{ |
3673
|
|
|
echo ' |
3674
|
|
|
<style>'; |
3675
|
|
|
|
3676
|
|
|
foreach ($context['css_header'] as $css) |
3677
|
|
|
echo $css .' |
3678
|
|
|
'; |
3679
|
|
|
|
3680
|
|
|
echo' |
3681
|
|
|
</style>'; |
3682
|
|
|
} |
3683
|
|
|
} |
3684
|
|
|
|
3685
|
|
|
/** |
3686
|
|
|
* Get an array of previously defined files and adds them to our main minified file. |
3687
|
|
|
* Sets a one day cache to avoid re-creating a file on every request. |
3688
|
|
|
* |
3689
|
|
|
* @param array $data The files to minify. |
3690
|
|
|
* @param string $type either css or js. |
3691
|
|
|
* @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag. |
3692
|
|
|
* @return bool|array If an array the minify process failed and the data is returned intact. |
3693
|
|
|
*/ |
3694
|
|
|
function custMinify($data, $type, $do_deferred = false) |
3695
|
|
|
{ |
3696
|
|
|
global $settings, $txt; |
|
|
|
|
3697
|
|
|
|
3698
|
|
|
$types = array('css', 'js'); |
3699
|
|
|
$type = !empty($type) && in_array($type, $types) ? $type : false; |
3700
|
|
|
$data = !empty($data) ? $data : false; |
3701
|
|
|
|
3702
|
|
|
if (empty($type) || empty($data)) |
3703
|
|
|
return false; |
3704
|
|
|
|
3705
|
|
|
// Did we already did this? |
3706
|
|
|
$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400); |
3707
|
|
|
|
3708
|
|
|
// Already done? |
3709
|
|
|
if (!empty($toCache)) |
3710
|
|
|
return true; |
3711
|
|
|
|
3712
|
|
|
// No namespaces, sorry! |
3713
|
|
|
$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type); |
3714
|
|
|
|
3715
|
|
|
// Temp path. |
3716
|
|
|
$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/'; |
3717
|
|
|
|
3718
|
|
|
// What kind of file are we going to create? |
3719
|
|
|
$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type; |
3720
|
|
|
|
3721
|
|
|
// File has to exists, if it isn't try to create it. |
3722
|
|
|
if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate)) |
3723
|
|
|
{ |
3724
|
|
|
loadLanguage('Errors'); |
3725
|
|
|
log_error(sprintf($txt['file_not_created'], $toCreate), 'general'); |
3726
|
|
|
cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null); |
3727
|
|
|
|
3728
|
|
|
// The process failed so roll back to print each individual file. |
3729
|
|
|
return $data; |
3730
|
|
|
} |
3731
|
|
|
|
3732
|
|
|
$minifier = new $classType(); |
3733
|
|
|
|
3734
|
|
|
foreach ($data as $file) |
3735
|
|
|
{ |
3736
|
|
|
$tempFile = str_replace($file['options']['seed'], '', $file['filePath']); |
3737
|
|
|
$toAdd = file_exists($tempFile) ? $tempFile : false; |
3738
|
|
|
|
3739
|
|
|
// The file couldn't be located so it won't be added, log this error. |
3740
|
|
|
if (empty($toAdd)) |
3741
|
|
|
{ |
3742
|
|
|
loadLanguage('Errors'); |
3743
|
|
|
log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general'); |
3744
|
|
|
continue; |
3745
|
|
|
} |
3746
|
|
|
|
3747
|
|
|
// Add this file to the list. |
3748
|
|
|
$minifier->add($toAdd); |
3749
|
|
|
} |
3750
|
|
|
|
3751
|
|
|
// Create the file. |
3752
|
|
|
$minifier->minify($toCreate); |
3753
|
|
|
unset($minifier); |
3754
|
|
|
clearstatcache(); |
3755
|
|
|
|
3756
|
|
|
// Minify process failed. |
3757
|
|
|
if (!filesize($toCreate)) |
3758
|
|
|
{ |
3759
|
|
|
loadLanguage('Errors'); |
3760
|
|
|
log_error(sprintf($txt['file_not_created'], $toCreate), 'general'); |
3761
|
|
|
cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null); |
3762
|
|
|
|
3763
|
|
|
// The process failed so roll back to print each individual file. |
3764
|
|
|
return $data; |
3765
|
|
|
} |
3766
|
|
|
|
3767
|
|
|
// And create a long lived cache entry. |
3768
|
|
|
cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400); |
3769
|
|
|
|
3770
|
|
|
return true; |
3771
|
|
|
} |
3772
|
|
|
|
3773
|
|
|
/** |
3774
|
|
|
* Get an attachment's encrypted filename. If $new is true, won't check for file existence. |
3775
|
|
|
* @todo this currently returns the hash if new, and the full filename otherwise. |
|
|
|
|
3776
|
|
|
* Something messy like that. |
3777
|
|
|
* @todo and of course everything relies on this behavior and work around it. :P. |
|
|
|
|
3778
|
|
|
* Converters included. |
3779
|
|
|
* |
3780
|
|
|
* @param string $filename The name of the file |
3781
|
|
|
* @param int $attachment_id The ID of the attachment |
3782
|
|
|
* @param string $dir Which directory it should be in (null to use current one) |
|
|
|
|
3783
|
|
|
* @param bool $new Whether this is a new attachment |
3784
|
|
|
* @param string $file_hash The file hash |
3785
|
|
|
* @return string The path to the file |
|
|
|
|
3786
|
|
|
*/ |
3787
|
|
|
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '') |
3788
|
|
|
{ |
3789
|
|
|
global $modSettings, $smcFunc; |
|
|
|
|
3790
|
|
|
|
3791
|
|
|
// Just make up a nice hash... |
3792
|
|
|
if ($new) |
3793
|
|
|
return sha1(md5($filename . time()) . mt_rand()); |
3794
|
|
|
|
3795
|
|
|
// Just make sure that attachment id is only a int |
3796
|
|
|
$attachment_id = (int) $attachment_id; |
3797
|
|
|
|
3798
|
|
|
// Grab the file hash if it wasn't added. |
3799
|
|
|
// Left this for legacy. |
3800
|
|
|
if ($file_hash === '') |
3801
|
|
|
{ |
3802
|
|
|
$request = $smcFunc['db_query']('', ' |
3803
|
|
|
SELECT file_hash |
3804
|
|
|
FROM {db_prefix}attachments |
3805
|
|
|
WHERE id_attach = {int:id_attach}', |
3806
|
|
|
array( |
3807
|
|
|
'id_attach' => $attachment_id, |
3808
|
|
|
)); |
3809
|
|
|
|
3810
|
|
|
if ($smcFunc['db_num_rows']($request) === 0) |
3811
|
|
|
return false; |
3812
|
|
|
|
3813
|
|
|
list ($file_hash) = $smcFunc['db_fetch_row']($request); |
3814
|
|
|
$smcFunc['db_free_result']($request); |
3815
|
|
|
} |
3816
|
|
|
|
3817
|
|
|
// Still no hash? mmm... |
3818
|
|
|
if (empty($file_hash)) |
3819
|
|
|
$file_hash = sha1(md5($filename . time()) . mt_rand()); |
3820
|
|
|
|
3821
|
|
|
// Are we using multiple directories? |
3822
|
|
|
if (is_array($modSettings['attachmentUploadDir'])) |
3823
|
|
|
$path = $modSettings['attachmentUploadDir'][$dir]; |
3824
|
|
|
|
3825
|
|
|
else |
3826
|
|
|
$path = $modSettings['attachmentUploadDir']; |
3827
|
|
|
|
3828
|
|
|
return $path . '/' . $attachment_id . '_' . $file_hash .'.dat'; |
3829
|
|
|
} |
3830
|
|
|
|
3831
|
|
|
/** |
3832
|
|
|
* Convert a single IP to a ranged IP. |
3833
|
|
|
* internal function used to convert a user-readable format to a format suitable for the database. |
3834
|
|
|
* |
3835
|
|
|
* @param string $fullip The full IP |
3836
|
|
|
* @return array An array of IP parts |
3837
|
|
|
*/ |
3838
|
|
|
function ip2range($fullip) |
3839
|
|
|
{ |
3840
|
|
|
// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.) |
3841
|
|
|
if ($fullip == 'unknown') |
3842
|
|
|
$fullip = '255.255.255.255'; |
3843
|
|
|
|
3844
|
|
|
$ip_parts = explode('-', $fullip); |
3845
|
|
|
$ip_array = array(); |
3846
|
|
|
|
3847
|
|
|
// if ip 22.12.31.21 |
|
|
|
|
3848
|
|
|
if (count($ip_parts) == 1 && isValidIP($fullip)) |
3849
|
|
|
{ |
3850
|
|
|
$ip_array['low'] = $fullip; |
3851
|
|
|
$ip_array['high'] = $fullip; |
3852
|
|
|
return $ip_array; |
3853
|
|
|
} // if ip 22.12.* -> 22.12.* - 22.12.* |
3854
|
|
|
elseif (count($ip_parts) == 1) |
3855
|
|
|
{ |
3856
|
|
|
$ip_parts[0] = $fullip; |
3857
|
|
|
$ip_parts[1] = $fullip; |
3858
|
|
|
} |
3859
|
|
|
|
3860
|
|
|
// if ip 22.12.31.21-12.21.31.21 |
|
|
|
|
3861
|
|
|
if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1])) |
3862
|
|
|
{ |
3863
|
|
|
$ip_array['low'] = $ip_parts[0]; |
3864
|
|
|
$ip_array['high'] = $ip_parts[1]; |
3865
|
|
|
return $ip_array; |
3866
|
|
|
} |
3867
|
|
|
elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.* |
3868
|
|
|
{ |
3869
|
|
|
$valid_low = isValidIP($ip_parts[0]); |
3870
|
|
|
$valid_high = isValidIP($ip_parts[1]); |
3871
|
|
|
$count = 0; |
3872
|
|
|
$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.'); |
3873
|
|
|
$max = ($mode == ':' ? 'ffff' : '255'); |
3874
|
|
|
$min = 0; |
3875
|
|
View Code Duplication |
if(!$valid_low) |
3876
|
|
|
{ |
3877
|
|
|
$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]); |
3878
|
|
|
$valid_low = isValidIP($ip_parts[0]); |
3879
|
|
|
while (!$valid_low) |
3880
|
|
|
{ |
3881
|
|
|
$ip_parts[0] .= $mode . $min; |
3882
|
|
|
$valid_low = isValidIP($ip_parts[0]); |
3883
|
|
|
$count++; |
3884
|
|
|
if ($count > 9) break; |
3885
|
|
|
} |
3886
|
|
|
} |
3887
|
|
|
|
3888
|
|
|
$count = 0; |
3889
|
|
View Code Duplication |
if(!$valid_high) |
3890
|
|
|
{ |
3891
|
|
|
$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]); |
3892
|
|
|
$valid_high = isValidIP($ip_parts[1]); |
3893
|
|
|
while (!$valid_high) |
3894
|
|
|
{ |
3895
|
|
|
$ip_parts[1] .= $mode . $max; |
3896
|
|
|
$valid_high = isValidIP($ip_parts[1]); |
3897
|
|
|
$count++; |
3898
|
|
|
if ($count > 9) break; |
3899
|
|
|
} |
3900
|
|
|
} |
3901
|
|
|
|
3902
|
|
|
if($valid_high && $valid_low) |
3903
|
|
|
{ |
3904
|
|
|
$ip_array['low'] = $ip_parts[0]; |
3905
|
|
|
$ip_array['high'] = $ip_parts[1]; |
3906
|
|
|
} |
3907
|
|
|
|
3908
|
|
|
} |
3909
|
|
|
|
3910
|
|
|
return $ip_array; |
3911
|
|
|
} |
3912
|
|
|
|
3913
|
|
|
/** |
3914
|
|
|
* Lookup an IP; try shell_exec first because we can do a timeout on it. |
3915
|
|
|
* |
3916
|
|
|
* @param string $ip The IP to get the hostname from |
3917
|
|
|
* @return string The hostname |
3918
|
|
|
*/ |
3919
|
|
|
function host_from_ip($ip) |
|
|
|
|
3920
|
|
|
{ |
3921
|
|
|
global $modSettings; |
|
|
|
|
3922
|
|
|
|
3923
|
|
|
if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null) |
3924
|
|
|
return $host; |
3925
|
|
|
$t = microtime(); |
|
|
|
|
3926
|
|
|
|
3927
|
|
|
// Try the Linux host command, perhaps? |
3928
|
|
|
if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1) |
3929
|
|
|
{ |
3930
|
|
|
if (!isset($modSettings['host_to_dis'])) |
3931
|
|
|
$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip)); |
3932
|
|
|
else |
3933
|
|
|
$test = @shell_exec('host ' . @escapeshellarg($ip)); |
3934
|
|
|
|
3935
|
|
|
// Did host say it didn't find anything? |
3936
|
|
|
if (strpos($test, 'not found') !== false) |
3937
|
|
|
$host = ''; |
3938
|
|
|
// Invalid server option? |
3939
|
|
|
elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis'])) |
3940
|
|
|
updateSettings(array('host_to_dis' => 1)); |
3941
|
|
|
// Maybe it found something, after all? |
3942
|
|
|
elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1) |
3943
|
|
|
$host = $match[1]; |
3944
|
|
|
} |
3945
|
|
|
|
3946
|
|
|
// This is nslookup; usually only Windows, but possibly some Unix? |
3947
|
|
|
if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1) |
3948
|
|
|
{ |
3949
|
|
|
$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip)); |
3950
|
|
|
if (strpos($test, 'Non-existent domain') !== false) |
3951
|
|
|
$host = ''; |
3952
|
|
|
elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1) |
3953
|
|
|
$host = $match[1]; |
3954
|
|
|
} |
3955
|
|
|
|
3956
|
|
|
// This is the last try :/. |
3957
|
|
|
if (!isset($host) || $host === false) |
3958
|
|
|
$host = @gethostbyaddr($ip); |
3959
|
|
|
|
3960
|
|
|
// It took a long time, so let's cache it! |
3961
|
|
View Code Duplication |
if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5) |
3962
|
|
|
cache_put_data('hostlookup-' . $ip, $host, 600); |
3963
|
|
|
|
3964
|
|
|
return $host; |
3965
|
|
|
} |
3966
|
|
|
|
3967
|
|
|
/** |
3968
|
|
|
* Chops a string into words and prepares them to be inserted into (or searched from) the database. |
3969
|
|
|
* |
3970
|
|
|
* @param string $text The text to split into words |
3971
|
|
|
* @param int $max_chars The maximum number of characters per word |
3972
|
|
|
* @param bool $encrypt Whether to encrypt the results |
3973
|
|
|
* @return array An array of ints or words depending on $encrypt |
3974
|
|
|
*/ |
3975
|
|
|
function text2words($text, $max_chars = 20, $encrypt = false) |
3976
|
|
|
{ |
3977
|
|
|
global $smcFunc, $context; |
|
|
|
|
3978
|
|
|
|
3979
|
|
|
// Step 1: Remove entities/things we don't consider words: |
3980
|
|
|
$words = preg_replace('~(?:[\x0B\0' . ($context['utf8'] ? '\x{A0}' : '\xA0') . '\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~' . ($context['utf8'] ? 'u' : ''), ' ', strtr($text, array('<br>' => ' '))); |
3981
|
|
|
|
3982
|
|
|
// Step 2: Entities we left to letters, where applicable, lowercase. |
3983
|
|
|
$words = un_htmlspecialchars($smcFunc['strtolower']($words)); |
3984
|
|
|
|
3985
|
|
|
// Step 3: Ready to split apart and index! |
3986
|
|
|
$words = explode(' ', $words); |
3987
|
|
|
|
3988
|
|
|
if ($encrypt) |
3989
|
|
|
{ |
3990
|
|
|
$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122))); |
3991
|
|
|
$returned_ints = array(); |
3992
|
|
|
foreach ($words as $word) |
3993
|
|
|
{ |
3994
|
|
|
if (($word = trim($word, '-_\'')) !== '') |
3995
|
|
|
{ |
3996
|
|
|
$encrypted = substr(crypt($word, 'uk'), 2, $max_chars); |
3997
|
|
|
$total = 0; |
3998
|
|
|
for ($i = 0; $i < $max_chars; $i++) |
3999
|
|
|
$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i); |
4000
|
|
|
$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total; |
4001
|
|
|
} |
4002
|
|
|
} |
4003
|
|
|
return array_unique($returned_ints); |
4004
|
|
|
} |
4005
|
|
|
else |
4006
|
|
|
{ |
4007
|
|
|
// Trim characters before and after and add slashes for database insertion. |
4008
|
|
|
$returned_words = array(); |
4009
|
|
|
foreach ($words as $word) |
4010
|
|
|
if (($word = trim($word, '-_\'')) !== '') |
4011
|
|
|
$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars); |
4012
|
|
|
|
4013
|
|
|
// Filter out all words that occur more than once. |
4014
|
|
|
return array_unique($returned_words); |
4015
|
|
|
} |
4016
|
|
|
} |
4017
|
|
|
|
4018
|
|
|
/** |
4019
|
|
|
* Creates an image/text button |
4020
|
|
|
* |
4021
|
|
|
* @param string $name The name of the button (should be a generic_icons class or the name of an image) |
4022
|
|
|
* @param string $alt The alt text |
4023
|
|
|
* @param string $label The $txt string to use as the label |
4024
|
|
|
* @param string $custom Custom text/html to add to the img tag (only when using an actual image) |
4025
|
|
|
* @param boolean $force_use Whether to force use of this when template_create_button is available |
4026
|
|
|
* @return string The HTML to display the button |
4027
|
|
|
*/ |
4028
|
|
|
function create_button($name, $alt, $label = '', $custom = '', $force_use = false) |
4029
|
|
|
{ |
4030
|
|
|
global $settings, $txt; |
|
|
|
|
4031
|
|
|
|
4032
|
|
|
// Does the current loaded theme have this and we are not forcing the usage of this function? |
4033
|
|
|
if (function_exists('template_create_button') && !$force_use) |
4034
|
|
|
return template_create_button($name, $alt, $label = '', $custom = ''); |
4035
|
|
|
|
4036
|
|
|
if (!$settings['use_image_buttons']) |
4037
|
|
|
return $txt[$alt]; |
4038
|
|
|
elseif (!empty($settings['use_buttons'])) |
4039
|
|
|
return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? ' <strong>' . $txt[$label] . '</strong>' : ''); |
4040
|
|
|
else |
4041
|
|
|
return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>'; |
4042
|
|
|
} |
4043
|
|
|
|
4044
|
|
|
/** |
4045
|
|
|
* Sets up all of the top menu buttons |
4046
|
|
|
* Saves them in the cache if it is available and on |
4047
|
|
|
* Places the results in $context |
4048
|
|
|
* |
4049
|
|
|
*/ |
4050
|
|
|
function setupMenuContext() |
4051
|
|
|
{ |
4052
|
|
|
global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings; |
|
|
|
|
4053
|
|
|
|
4054
|
|
|
// Set up the menu privileges. |
4055
|
|
|
$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts')); |
4056
|
|
|
$context['allow_admin'] = allowedTo(array('admin_forum', 'manage_boards', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_attachments', 'manage_smileys')); |
4057
|
|
|
|
4058
|
|
|
$context['allow_memberlist'] = allowedTo('view_mlist'); |
4059
|
|
|
$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']); |
4060
|
|
|
$context['allow_moderation_center'] = $context['user']['can_mod']; |
4061
|
|
|
$context['allow_pm'] = allowedTo('pm_read'); |
4062
|
|
|
|
4063
|
|
|
$cacheTime = $modSettings['lastActive'] * 60; |
4064
|
|
|
|
4065
|
|
|
// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already. |
4066
|
|
|
if (!isset($context['allow_calendar_event'])) |
4067
|
|
|
{ |
4068
|
|
|
$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post'); |
4069
|
|
|
|
4070
|
|
|
// If you don't allow events not linked to posts and you're not an admin, we have more work to do... |
4071
|
|
View Code Duplication |
if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin']) |
4072
|
|
|
{ |
4073
|
|
|
$boards_can_post = boardsAllowedTo('post_new'); |
4074
|
|
|
$context['allow_calendar_event'] &= !empty($boards_can_post); |
4075
|
|
|
} |
4076
|
|
|
} |
4077
|
|
|
|
4078
|
|
|
// There is some menu stuff we need to do if we're coming at this from a non-guest perspective. |
4079
|
|
|
if (!$context['user']['is_guest']) |
4080
|
|
|
{ |
4081
|
|
|
addInlineJavaScript(' |
4082
|
|
|
var user_menus = new smc_PopupMenu(); |
4083
|
|
|
user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup"); |
4084
|
|
|
user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true); |
4085
|
|
|
if ($context['allow_pm']) |
4086
|
|
|
addInlineJavaScript(' |
4087
|
|
|
user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true); |
4088
|
|
|
|
4089
|
|
|
if (!empty($modSettings['enable_ajax_alerts'])) |
4090
|
|
|
{ |
4091
|
|
|
require_once($sourcedir . '/Subs-Notify.php'); |
4092
|
|
|
|
4093
|
|
|
$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true); |
4094
|
|
|
$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000; |
4095
|
|
|
|
4096
|
|
|
addInlineJavaScript(' |
4097
|
|
|
var new_alert_title = "' . $context['forum_name'] . '"; |
4098
|
|
|
var alert_timeout = ' . $timeout . ';'); |
4099
|
|
|
loadJavaScriptFile('alerts.js', array(), 'smf_alerts'); |
4100
|
|
|
} |
4101
|
|
|
} |
4102
|
|
|
|
4103
|
|
|
// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first. |
4104
|
|
|
if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated']) |
4105
|
|
|
{ |
4106
|
|
|
$buttons = array( |
4107
|
|
|
'home' => array( |
4108
|
|
|
'title' => $txt['home'], |
4109
|
|
|
'href' => $scripturl, |
4110
|
|
|
'show' => true, |
4111
|
|
|
'sub_buttons' => array( |
4112
|
|
|
), |
4113
|
|
|
'is_last' => $context['right_to_left'], |
4114
|
|
|
), |
4115
|
|
|
'search' => array( |
4116
|
|
|
'title' => $txt['search'], |
4117
|
|
|
'href' => $scripturl . '?action=search', |
4118
|
|
|
'show' => $context['allow_search'], |
4119
|
|
|
'sub_buttons' => array( |
4120
|
|
|
), |
4121
|
|
|
), |
4122
|
|
|
'admin' => array( |
4123
|
|
|
'title' => $txt['admin'], |
4124
|
|
|
'href' => $scripturl . '?action=admin', |
4125
|
|
|
'show' => $context['allow_admin'], |
4126
|
|
|
'sub_buttons' => array( |
4127
|
|
|
'featuresettings' => array( |
4128
|
|
|
'title' => $txt['modSettings_title'], |
4129
|
|
|
'href' => $scripturl . '?action=admin;area=featuresettings', |
4130
|
|
|
'show' => allowedTo('admin_forum'), |
4131
|
|
|
), |
4132
|
|
|
'packages' => array( |
4133
|
|
|
'title' => $txt['package'], |
4134
|
|
|
'href' => $scripturl . '?action=admin;area=packages', |
4135
|
|
|
'show' => allowedTo('admin_forum'), |
4136
|
|
|
), |
4137
|
|
|
'errorlog' => array( |
4138
|
|
|
'title' => $txt['errlog'], |
4139
|
|
|
'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc', |
4140
|
|
|
'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']), |
4141
|
|
|
), |
4142
|
|
|
'permissions' => array( |
4143
|
|
|
'title' => $txt['edit_permissions'], |
4144
|
|
|
'href' => $scripturl . '?action=admin;area=permissions', |
4145
|
|
|
'show' => allowedTo('manage_permissions'), |
4146
|
|
|
), |
4147
|
|
|
'memberapprove' => array( |
4148
|
|
|
'title' => $txt['approve_members_waiting'], |
4149
|
|
|
'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve', |
4150
|
|
|
'show' => !empty($context['unapproved_members']), |
4151
|
|
|
'is_last' => true, |
4152
|
|
|
), |
4153
|
|
|
), |
4154
|
|
|
), |
4155
|
|
|
'moderate' => array( |
4156
|
|
|
'title' => $txt['moderate'], |
4157
|
|
|
'href' => $scripturl . '?action=moderate', |
4158
|
|
|
'show' => $context['allow_moderation_center'], |
4159
|
|
|
'sub_buttons' => array( |
4160
|
|
|
'modlog' => array( |
4161
|
|
|
'title' => $txt['modlog_view'], |
4162
|
|
|
'href' => $scripturl . '?action=moderate;area=modlog', |
4163
|
|
|
'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', |
4164
|
|
|
), |
4165
|
|
|
'poststopics' => array( |
4166
|
|
|
'title' => $txt['mc_unapproved_poststopics'], |
4167
|
|
|
'href' => $scripturl . '?action=moderate;area=postmod;sa=posts', |
4168
|
|
|
'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), |
4169
|
|
|
), |
4170
|
|
|
'attachments' => array( |
4171
|
|
|
'title' => $txt['mc_unapproved_attachments'], |
4172
|
|
|
'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments', |
4173
|
|
|
'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']), |
4174
|
|
|
), |
4175
|
|
|
'reports' => array( |
4176
|
|
|
'title' => $txt['mc_reported_posts'], |
4177
|
|
|
'href' => $scripturl . '?action=moderate;area=reportedposts', |
4178
|
|
|
'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1', |
4179
|
|
|
), |
4180
|
|
|
'reported_members' => array( |
4181
|
|
|
'title' => $txt['mc_reported_members'], |
4182
|
|
|
'href' => $scripturl . '?action=moderate;area=reportedmembers', |
4183
|
|
|
'show' => allowedTo('moderate_forum'), |
4184
|
|
|
'is_last' => true, |
4185
|
|
|
) |
4186
|
|
|
), |
4187
|
|
|
), |
4188
|
|
|
'calendar' => array( |
4189
|
|
|
'title' => $txt['calendar'], |
4190
|
|
|
'href' => $scripturl . '?action=calendar', |
4191
|
|
|
'show' => $context['allow_calendar'], |
4192
|
|
|
'sub_buttons' => array( |
4193
|
|
|
'view' => array( |
4194
|
|
|
'title' => $txt['calendar_menu'], |
4195
|
|
|
'href' => $scripturl . '?action=calendar', |
4196
|
|
|
'show' => $context['allow_calendar_event'], |
4197
|
|
|
), |
4198
|
|
|
'post' => array( |
4199
|
|
|
'title' => $txt['calendar_post_event'], |
4200
|
|
|
'href' => $scripturl . '?action=calendar;sa=post', |
4201
|
|
|
'show' => $context['allow_calendar_event'], |
4202
|
|
|
'is_last' => true, |
4203
|
|
|
), |
4204
|
|
|
), |
4205
|
|
|
), |
4206
|
|
|
'mlist' => array( |
4207
|
|
|
'title' => $txt['members_title'], |
4208
|
|
|
'href' => $scripturl . '?action=mlist', |
4209
|
|
|
'show' => $context['allow_memberlist'], |
4210
|
|
|
'sub_buttons' => array( |
4211
|
|
|
'mlist_view' => array( |
4212
|
|
|
'title' => $txt['mlist_menu_view'], |
4213
|
|
|
'href' => $scripturl . '?action=mlist', |
4214
|
|
|
'show' => true, |
4215
|
|
|
), |
4216
|
|
|
'mlist_search' => array( |
4217
|
|
|
'title' => $txt['mlist_search'], |
4218
|
|
|
'href' => $scripturl . '?action=mlist;sa=search', |
4219
|
|
|
'show' => true, |
4220
|
|
|
'is_last' => true, |
4221
|
|
|
), |
4222
|
|
|
), |
4223
|
|
|
), |
4224
|
|
|
'signup' => array( |
4225
|
|
|
'title' => $txt['register'], |
4226
|
|
|
'href' => $scripturl . '?action=signup', |
4227
|
|
|
'show' => $user_info['is_guest'] && $context['can_register'], |
4228
|
|
|
'sub_buttons' => array( |
4229
|
|
|
), |
4230
|
|
|
'is_last' => !$context['right_to_left'], |
4231
|
|
|
), |
4232
|
|
|
'logout' => array( |
4233
|
|
|
'title' => $txt['logout'], |
4234
|
|
|
'href' => $scripturl . '?action=logout;%1$s=%2$s', |
4235
|
|
|
'show' => !$user_info['is_guest'], |
4236
|
|
|
'sub_buttons' => array( |
4237
|
|
|
), |
4238
|
|
|
'is_last' => !$context['right_to_left'], |
4239
|
|
|
), |
4240
|
|
|
); |
4241
|
|
|
|
4242
|
|
|
// Allow editing menu buttons easily. |
4243
|
|
|
call_integration_hook('integrate_menu_buttons', array(&$buttons)); |
4244
|
|
|
|
4245
|
|
|
// Now we put the buttons in the context so the theme can use them. |
4246
|
|
|
$menu_buttons = array(); |
4247
|
|
|
foreach ($buttons as $act => $button) |
4248
|
|
|
if (!empty($button['show'])) |
4249
|
|
|
{ |
4250
|
|
|
$button['active_button'] = false; |
4251
|
|
|
|
4252
|
|
|
// This button needs some action. |
4253
|
|
|
if (isset($button['action_hook'])) |
4254
|
|
|
$needs_action_hook = true; |
4255
|
|
|
|
4256
|
|
|
// Make sure the last button truly is the last button. |
4257
|
|
|
if (!empty($button['is_last'])) |
4258
|
|
|
{ |
4259
|
|
|
if (isset($last_button)) |
4260
|
|
|
unset($menu_buttons[$last_button]['is_last']); |
4261
|
|
|
$last_button = $act; |
4262
|
|
|
} |
4263
|
|
|
|
4264
|
|
|
// Go through the sub buttons if there are any. |
4265
|
|
|
if (!empty($button['sub_buttons'])) |
4266
|
|
|
foreach ($button['sub_buttons'] as $key => $subbutton) |
4267
|
|
|
{ |
4268
|
|
|
if (empty($subbutton['show'])) |
4269
|
|
|
unset($button['sub_buttons'][$key]); |
4270
|
|
|
|
4271
|
|
|
// 2nd level sub buttons next... |
4272
|
|
|
if (!empty($subbutton['sub_buttons'])) |
4273
|
|
|
{ |
4274
|
|
|
foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2) |
4275
|
|
|
{ |
4276
|
|
|
if (empty($sub_button2['show'])) |
4277
|
|
|
unset($button['sub_buttons'][$key]['sub_buttons'][$key2]); |
4278
|
|
|
} |
4279
|
|
|
} |
4280
|
|
|
} |
4281
|
|
|
|
4282
|
|
|
// Does this button have its own icon? |
4283
|
|
|
if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon'])) |
4284
|
|
|
$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">'; |
4285
|
|
|
elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon'])) |
4286
|
|
|
$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">'; |
4287
|
|
|
elseif (isset($button['icon'])) |
4288
|
|
|
$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>'; |
4289
|
|
|
else |
4290
|
|
|
$button['icon'] = '<span class="generic_icons ' . $act . '"></span>'; |
4291
|
|
|
|
4292
|
|
|
$menu_buttons[$act] = $button; |
4293
|
|
|
} |
4294
|
|
|
|
4295
|
|
|
if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) |
4296
|
|
|
cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime); |
4297
|
|
|
} |
4298
|
|
|
|
4299
|
|
|
$context['menu_buttons'] = $menu_buttons; |
4300
|
|
|
|
4301
|
|
|
// Logging out requires the session id in the url. |
4302
|
|
|
if (isset($context['menu_buttons']['logout'])) |
4303
|
|
|
$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']); |
4304
|
|
|
|
4305
|
|
|
// Figure out which action we are doing so we can set the active tab. |
4306
|
|
|
// Default to home. |
4307
|
|
|
$current_action = 'home'; |
4308
|
|
|
|
4309
|
|
|
if (isset($context['menu_buttons'][$context['current_action']])) |
4310
|
|
|
$current_action = $context['current_action']; |
4311
|
|
|
elseif ($context['current_action'] == 'search2') |
4312
|
|
|
$current_action = 'search'; |
4313
|
|
|
elseif ($context['current_action'] == 'theme') |
4314
|
|
|
$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin'; |
4315
|
|
|
elseif ($context['current_action'] == 'register2') |
4316
|
|
|
$current_action = 'register'; |
4317
|
|
|
elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder')) |
4318
|
|
|
$current_action = 'login'; |
4319
|
|
|
elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center']) |
4320
|
|
|
$current_action = 'moderate'; |
4321
|
|
|
|
4322
|
|
|
// There are certain exceptions to the above where we don't want anything on the menu highlighted. |
4323
|
|
|
if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner'])) |
4324
|
|
|
{ |
4325
|
|
|
$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile'; |
4326
|
|
|
$context[$current_action] = true; |
4327
|
|
|
} |
4328
|
|
|
elseif ($context['current_action'] == 'pm') |
4329
|
|
|
{ |
4330
|
|
|
$current_action = 'self_pm'; |
4331
|
|
|
$context['self_pm'] = true; |
4332
|
|
|
} |
4333
|
|
|
|
4334
|
|
|
$total_mod_reports = 0; |
4335
|
|
|
|
4336
|
|
|
if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports'])) |
4337
|
|
|
{ |
4338
|
|
|
$total_mod_reports = $context['open_mod_reports']; |
4339
|
|
|
$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>'; |
4340
|
|
|
} |
4341
|
|
|
|
4342
|
|
|
// Show how many errors there are |
4343
|
|
View Code Duplication |
if (!empty($context['num_errors']) && allowedTo('admin_forum')) |
4344
|
|
|
{ |
4345
|
|
|
$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>'; |
4346
|
|
|
$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>'; |
4347
|
|
|
} |
4348
|
|
|
|
4349
|
|
|
// Show number of reported members |
4350
|
|
|
if (!empty($context['open_member_reports']) && allowedTo('moderate_forum')) |
4351
|
|
|
{ |
4352
|
|
|
$total_mod_reports += $context['open_member_reports']; |
4353
|
|
|
$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>'; |
4354
|
|
|
} |
4355
|
|
|
|
4356
|
|
View Code Duplication |
if (!empty($context['unapproved_members'])) |
4357
|
|
|
{ |
4358
|
|
|
$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>'; |
4359
|
|
|
$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>'; |
4360
|
|
|
} |
4361
|
|
|
|
4362
|
|
|
// Do we have any open reports? |
4363
|
|
|
if ($total_mod_reports > 0) |
4364
|
|
|
{ |
4365
|
|
|
$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>'; |
4366
|
|
|
} |
4367
|
|
|
|
4368
|
|
|
// Not all actions are simple. |
4369
|
|
|
if (!empty($needs_action_hook)) |
4370
|
|
|
call_integration_hook('integrate_current_action', array(&$current_action)); |
4371
|
|
|
|
4372
|
|
|
if (isset($context['menu_buttons'][$current_action])) |
4373
|
|
|
$context['menu_buttons'][$current_action]['active_button'] = true; |
4374
|
|
|
} |
4375
|
|
|
|
4376
|
|
|
/** |
4377
|
|
|
* Generate a random seed and ensure it's stored in settings. |
4378
|
|
|
*/ |
4379
|
|
|
function smf_seed_generator() |
4380
|
|
|
{ |
4381
|
|
|
updateSettings(array('rand_seed' => microtime() * 1000000)); |
4382
|
|
|
} |
4383
|
|
|
|
4384
|
|
|
/** |
4385
|
|
|
* Process functions of an integration hook. |
4386
|
|
|
* calls all functions of the given hook. |
4387
|
|
|
* supports static class method calls. |
4388
|
|
|
* |
4389
|
|
|
* @param string $hook The hook name |
4390
|
|
|
* @param array $parameters An array of parameters this hook implements |
4391
|
|
|
* @return array The results of the functions |
4392
|
|
|
*/ |
4393
|
|
|
function call_integration_hook($hook, $parameters = array()) |
4394
|
|
|
{ |
4395
|
|
|
global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug; |
|
|
|
|
4396
|
|
|
global $context, $txt; |
|
|
|
|
4397
|
|
|
|
4398
|
|
|
if ($db_show_debug === true) |
4399
|
|
|
$context['debug']['hooks'][] = $hook; |
4400
|
|
|
|
4401
|
|
|
// Need to have some control. |
4402
|
|
|
if (!isset($context['instances'])) |
4403
|
|
|
$context['instances'] = array(); |
4404
|
|
|
|
4405
|
|
|
$results = array(); |
4406
|
|
|
if (empty($modSettings[$hook])) |
4407
|
|
|
return $results; |
4408
|
|
|
|
4409
|
|
|
$functions = explode(',', $modSettings[$hook]); |
4410
|
|
|
// Loop through each function. |
4411
|
|
|
foreach ($functions as $function) |
4412
|
|
|
{ |
4413
|
|
|
// Hook has been marked as "disabled". Skip it! |
4414
|
|
|
if (strpos($function, '!') !== false) |
4415
|
|
|
continue; |
4416
|
|
|
|
4417
|
|
|
$call = call_helper($function, true); |
4418
|
|
|
|
4419
|
|
|
// Is it valid? |
4420
|
|
|
if (!empty($call)) |
4421
|
|
|
$results[$function] = call_user_func_array($call, $parameters); |
4422
|
|
|
|
4423
|
|
|
// Whatever it was suppose to call, it failed :( |
4424
|
|
|
elseif (!empty($function)) |
4425
|
|
|
{ |
4426
|
|
|
loadLanguage('Errors'); |
4427
|
|
|
|
4428
|
|
|
// Get a full path to show on error. |
4429
|
|
|
if (strpos($function, '|') !== false) |
4430
|
|
|
{ |
4431
|
|
|
list ($file, $string) = explode('|', $function); |
4432
|
|
|
$absPath = empty($settings['theme_dir']) ? (strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir))) : (strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']))); |
4433
|
|
|
log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general'); |
4434
|
|
|
} |
4435
|
|
|
|
4436
|
|
|
// "Assume" the file resides on $boarddir somewhere... |
4437
|
|
|
else |
4438
|
|
|
log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general'); |
4439
|
|
|
} |
4440
|
|
|
} |
4441
|
|
|
|
4442
|
|
|
return $results; |
4443
|
|
|
} |
4444
|
|
|
|
4445
|
|
|
/** |
4446
|
|
|
* Add a function for integration hook. |
4447
|
|
|
* does nothing if the function is already added. |
4448
|
|
|
* |
4449
|
|
|
* @param string $hook The complete hook name. |
4450
|
|
|
* @param string $function The function name. Can be a call to a method via Class::method. |
4451
|
|
|
* @param bool $permanent If true, updates the value in settings table. |
4452
|
|
|
* @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php |
4453
|
|
|
* @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method. |
4454
|
|
|
*/ |
4455
|
|
|
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false) |
4456
|
|
|
{ |
4457
|
|
|
global $smcFunc, $modSettings; |
|
|
|
|
4458
|
|
|
|
4459
|
|
|
// Any objects? |
4460
|
|
|
if ($object) |
4461
|
|
|
$function = $function . '#'; |
4462
|
|
|
|
4463
|
|
|
// Any files to load? |
4464
|
|
|
if (!empty($file) && is_string($file)) |
4465
|
|
|
$function = $file . (!empty($function) ? '|' . $function : ''); |
4466
|
|
|
|
4467
|
|
|
// Get the correct string. |
4468
|
|
|
$integration_call = $function; |
4469
|
|
|
|
4470
|
|
|
// Is it going to be permanent? |
4471
|
|
|
if ($permanent) |
4472
|
|
|
{ |
4473
|
|
|
$request = $smcFunc['db_query']('', ' |
4474
|
|
|
SELECT value |
4475
|
|
|
FROM {db_prefix}settings |
4476
|
|
|
WHERE variable = {string:variable}', |
4477
|
|
|
array( |
4478
|
|
|
'variable' => $hook, |
4479
|
|
|
) |
4480
|
|
|
); |
4481
|
|
|
list ($current_functions) = $smcFunc['db_fetch_row']($request); |
4482
|
|
|
$smcFunc['db_free_result']($request); |
4483
|
|
|
|
4484
|
|
|
if (!empty($current_functions)) |
4485
|
|
|
{ |
4486
|
|
|
$current_functions = explode(',', $current_functions); |
4487
|
|
|
if (in_array($integration_call, $current_functions)) |
4488
|
|
|
return; |
4489
|
|
|
|
4490
|
|
|
$permanent_functions = array_merge($current_functions, array($integration_call)); |
4491
|
|
|
} |
4492
|
|
|
else |
4493
|
|
|
$permanent_functions = array($integration_call); |
4494
|
|
|
|
4495
|
|
|
updateSettings(array($hook => implode(',', $permanent_functions))); |
4496
|
|
|
} |
4497
|
|
|
|
4498
|
|
|
// Make current function list usable. |
4499
|
|
|
$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); |
4500
|
|
|
|
4501
|
|
|
// Do nothing, if it's already there. |
4502
|
|
|
if (in_array($integration_call, $functions)) |
4503
|
|
|
return; |
4504
|
|
|
|
4505
|
|
|
$functions[] = $integration_call; |
4506
|
|
|
$modSettings[$hook] = implode(',', $functions); |
4507
|
|
|
} |
4508
|
|
|
|
4509
|
|
|
/** |
4510
|
|
|
* Remove an integration hook function. |
4511
|
|
|
* Removes the given function from the given hook. |
4512
|
|
|
* Does nothing if the function is not available. |
4513
|
|
|
* |
4514
|
|
|
* @param string $hook The complete hook name. |
4515
|
|
|
* @param string $function The function name. Can be a call to a method via Class::method. |
4516
|
|
|
* @param boolean $permanent Irrelevant for the function itself but need to declare it to match |
4517
|
|
|
* @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php |
4518
|
|
|
* @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method. |
4519
|
|
|
* @see add_integration_function |
4520
|
|
|
*/ |
4521
|
|
|
function remove_integration_function($hook, $function, $permanent = true, $file = '', $object = false) |
|
|
|
|
4522
|
|
|
{ |
4523
|
|
|
global $smcFunc, $modSettings; |
|
|
|
|
4524
|
|
|
|
4525
|
|
|
// Any objects? |
4526
|
|
|
if ($object) |
4527
|
|
|
$function = $function . '#'; |
4528
|
|
|
|
4529
|
|
|
// Any files to load? |
4530
|
|
|
if (!empty($file) && is_string($file)) |
4531
|
|
|
$function = $file . '|' . $function; |
4532
|
|
|
|
4533
|
|
|
// Get the correct string. |
4534
|
|
|
$integration_call = $function; |
4535
|
|
|
|
4536
|
|
|
// Get the permanent functions. |
4537
|
|
|
$request = $smcFunc['db_query']('', ' |
4538
|
|
|
SELECT value |
4539
|
|
|
FROM {db_prefix}settings |
4540
|
|
|
WHERE variable = {string:variable}', |
4541
|
|
|
array( |
4542
|
|
|
'variable' => $hook, |
4543
|
|
|
) |
4544
|
|
|
); |
4545
|
|
|
list ($current_functions) = $smcFunc['db_fetch_row']($request); |
4546
|
|
|
$smcFunc['db_free_result']($request); |
4547
|
|
|
|
4548
|
|
|
if (!empty($current_functions)) |
4549
|
|
|
{ |
4550
|
|
|
$current_functions = explode(',', $current_functions); |
4551
|
|
|
|
4552
|
|
|
if (in_array($integration_call, $current_functions)) |
4553
|
|
|
updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call))))); |
4554
|
|
|
} |
4555
|
|
|
|
4556
|
|
|
// Turn the function list into something usable. |
4557
|
|
|
$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]); |
4558
|
|
|
|
4559
|
|
|
// You can only remove it if it's available. |
4560
|
|
|
if (!in_array($integration_call, $functions)) |
4561
|
|
|
return; |
4562
|
|
|
|
4563
|
|
|
$functions = array_diff($functions, array($integration_call)); |
4564
|
|
|
$modSettings[$hook] = implode(',', $functions); |
4565
|
|
|
} |
4566
|
|
|
|
4567
|
|
|
/** |
4568
|
|
|
* Receives a string and tries to figure it out if its a method or a function. |
4569
|
|
|
* If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class. |
4570
|
|
|
* Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array. |
4571
|
|
|
* Prepare and returns a callable depending on the type of method/function found. |
4572
|
|
|
* |
4573
|
|
|
* @param mixed $string The string containing a function name or a static call. The function can also accept a closure, object or a callable array (object/class, valid_callable) |
4574
|
|
|
* @param boolean $return If true, the function will not call the function/method but instead will return the formatted string. |
4575
|
|
|
* @return string|array|boolean Either a string or an array that contains a callable function name or an array with a class and method to call. Boolean false if the given string cannot produce a callable var. |
4576
|
|
|
*/ |
4577
|
|
|
function call_helper($string, $return = false) |
4578
|
|
|
{ |
4579
|
|
|
global $context, $smcFunc, $txt, $db_show_debug; |
|
|
|
|
4580
|
|
|
|
4581
|
|
|
// Really? |
4582
|
|
|
if (empty($string)) |
4583
|
|
|
return false; |
4584
|
|
|
|
4585
|
|
|
// An array? should be a "callable" array IE array(object/class, valid_callable). |
4586
|
|
|
// A closure? should be a callable one. |
4587
|
|
|
if (is_array($string) || $string instanceof Closure) |
4588
|
|
|
return $return ? $string : (is_callable($string) ? call_user_func($string) : false); |
4589
|
|
|
|
4590
|
|
|
// No full objects, sorry! pass a method or a property instead! |
4591
|
|
|
if (is_object($string)) |
4592
|
|
|
return false; |
4593
|
|
|
|
4594
|
|
|
// Stay vitaminized my friends... |
4595
|
|
|
$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string)); |
4596
|
|
|
|
4597
|
|
|
// Is there a file to load? |
4598
|
|
|
$string = load_file($string); |
4599
|
|
|
|
4600
|
|
|
// Loaded file failed |
4601
|
|
|
if (empty($string)) |
4602
|
|
|
return false; |
4603
|
|
|
|
4604
|
|
|
// Found a method. |
4605
|
|
|
if (strpos($string, '::') !== false) |
4606
|
|
|
{ |
4607
|
|
|
list ($class, $method) = explode('::', $string); |
4608
|
|
|
|
4609
|
|
|
// Check if a new object will be created. |
4610
|
|
|
if (strpos($method, '#') !== false) |
4611
|
|
|
{ |
4612
|
|
|
// Need to remove the # thing. |
4613
|
|
|
$method = str_replace('#', '', $method); |
4614
|
|
|
|
4615
|
|
|
// Don't need to create a new instance for every method. |
4616
|
|
|
if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class)) |
4617
|
|
|
{ |
4618
|
|
|
$context['instances'][$class] = new $class; |
4619
|
|
|
|
4620
|
|
|
// Add another one to the list. |
4621
|
|
|
if ($db_show_debug === true) |
4622
|
|
|
{ |
4623
|
|
|
if (!isset($context['debug']['instances'])) |
4624
|
|
|
$context['debug']['instances'] = array(); |
4625
|
|
|
|
4626
|
|
|
$context['debug']['instances'][$class] = $class; |
4627
|
|
|
} |
4628
|
|
|
} |
4629
|
|
|
|
4630
|
|
|
$func = array($context['instances'][$class], $method); |
4631
|
|
|
} |
4632
|
|
|
|
4633
|
|
|
// Right then. This is a call to a static method. |
4634
|
|
|
else |
4635
|
|
|
$func = array($class, $method); |
4636
|
|
|
} |
4637
|
|
|
|
4638
|
|
|
// Nope! just a plain regular function. |
4639
|
|
|
else |
4640
|
|
|
$func = $string; |
4641
|
|
|
|
4642
|
|
|
// Right, we got what we need, time to do some checks. |
4643
|
|
|
if (!is_callable($func, false, $callable_name)) |
4644
|
|
|
{ |
4645
|
|
|
loadLanguage('Errors'); |
4646
|
|
|
log_error(sprintf($txt['subAction_fail'], $callable_name), 'general'); |
4647
|
|
|
|
4648
|
|
|
// Gotta tell everybody. |
4649
|
|
|
return false; |
4650
|
|
|
} |
4651
|
|
|
|
4652
|
|
|
// Everything went better than expected. |
4653
|
|
|
else |
4654
|
|
|
{ |
4655
|
|
|
// What are we gonna do about it? |
4656
|
|
|
if ($return) |
4657
|
|
|
return $func; |
4658
|
|
|
|
4659
|
|
|
// If this is a plain function, avoid the heat of calling call_user_func(). |
4660
|
|
|
else |
4661
|
|
|
{ |
4662
|
|
|
if (is_array($func)) |
4663
|
|
|
call_user_func($func); |
4664
|
|
|
|
4665
|
|
|
else |
4666
|
|
|
$func(); |
4667
|
|
|
} |
4668
|
|
|
} |
4669
|
|
|
} |
4670
|
|
|
|
4671
|
|
|
/** |
4672
|
|
|
* Receives a string and tries to figure it out if it contains info to load a file. |
4673
|
|
|
* Checks for a | (pipe) symbol and tries to load a file with the info given. |
4674
|
|
|
* The string should be format as follows File.php|. You can use the following wildcards: $boarddir, $sourcedir and if available at the moment of execution, $themedir. |
4675
|
|
|
* |
4676
|
|
|
* @param string $string The string containing a valid format. |
4677
|
|
|
* @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded. |
4678
|
|
|
*/ |
4679
|
|
|
function load_file($string) |
4680
|
|
|
{ |
4681
|
|
|
global $sourcedir, $txt, $boarddir, $settings; |
|
|
|
|
4682
|
|
|
|
4683
|
|
|
if (empty($string)) |
4684
|
|
|
return false; |
4685
|
|
|
|
4686
|
|
|
if (strpos($string, '|') !== false) |
4687
|
|
|
{ |
4688
|
|
|
list ($file, $string) = explode('|', $string); |
4689
|
|
|
|
4690
|
|
|
// Match the wildcards to their regular vars. |
4691
|
|
|
if (empty($settings['theme_dir'])) |
4692
|
|
|
$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir)); |
4693
|
|
|
|
4694
|
|
|
else |
4695
|
|
|
$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])); |
4696
|
|
|
|
4697
|
|
|
// Load the file if it can be loaded. |
4698
|
|
|
if (file_exists($absPath)) |
4699
|
|
|
require_once($absPath); |
4700
|
|
|
|
4701
|
|
|
// No? try a fallback to $sourcedir |
4702
|
|
|
else |
4703
|
|
|
{ |
4704
|
|
|
$absPath = $sourcedir .'/'. $file; |
4705
|
|
|
|
4706
|
|
|
if (file_exists($absPath)) |
4707
|
|
|
require_once($absPath); |
4708
|
|
|
|
4709
|
|
|
// Sorry, can't do much for you at this point. |
4710
|
|
|
else |
4711
|
|
|
{ |
4712
|
|
|
loadLanguage('Errors'); |
4713
|
|
|
log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general'); |
4714
|
|
|
|
4715
|
|
|
// File couldn't be loaded. |
4716
|
|
|
return false; |
4717
|
|
|
} |
4718
|
|
|
} |
4719
|
|
|
} |
4720
|
|
|
|
4721
|
|
|
return $string; |
4722
|
|
|
} |
4723
|
|
|
|
4724
|
|
|
/** |
4725
|
|
|
* Prepares an array of "likes" info for the topic specified by $topic |
4726
|
|
|
* @param integer $topic The topic ID to fetch the info from. |
4727
|
|
|
* @return array An array of IDs of messages in the specified topic that the current user likes |
|
|
|
|
4728
|
|
|
*/ |
4729
|
|
|
function prepareLikesContext($topic) |
4730
|
|
|
{ |
4731
|
|
|
global $user_info, $smcFunc; |
|
|
|
|
4732
|
|
|
|
4733
|
|
|
// Make sure we have something to work with. |
4734
|
|
|
if (empty($topic)) |
4735
|
|
|
return array(); |
4736
|
|
|
|
4737
|
|
|
|
4738
|
|
|
// We already know the number of likes per message, we just want to know whether the current user liked it or not. |
4739
|
|
|
$user = $user_info['id']; |
4740
|
|
|
$cache_key = 'likes_topic_' . $topic . '_' . $user; |
4741
|
|
|
$ttl = 180; |
4742
|
|
|
|
4743
|
|
|
if (($temp = cache_get_data($cache_key, $ttl)) === null) |
4744
|
|
|
{ |
4745
|
|
|
$temp = array(); |
4746
|
|
|
$request = $smcFunc['db_query']('', ' |
4747
|
|
|
SELECT content_id |
4748
|
|
|
FROM {db_prefix}user_likes AS l |
4749
|
|
|
INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg) |
4750
|
|
|
WHERE l.id_member = {int:current_user} |
4751
|
|
|
AND l.content_type = {literal:msg} |
4752
|
|
|
AND m.id_topic = {int:topic}', |
4753
|
|
|
array( |
4754
|
|
|
'current_user' => $user, |
4755
|
|
|
'topic' => $topic, |
4756
|
|
|
) |
4757
|
|
|
); |
4758
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
4759
|
|
|
$temp[] = (int) $row['content_id']; |
4760
|
|
|
|
4761
|
|
|
cache_put_data($cache_key, $temp, $ttl); |
4762
|
|
|
} |
4763
|
|
|
|
4764
|
|
|
return $temp; |
4765
|
|
|
} |
4766
|
|
|
|
4767
|
|
|
/** |
4768
|
|
|
* Microsoft uses their own character set Code Page 1252 (CP1252), which is a |
4769
|
|
|
* superset of ISO 8859-1, defining several characters between DEC 128 and 159 |
4770
|
|
|
* that are not normally displayable. This converts the popular ones that |
4771
|
|
|
* appear from a cut and paste from windows. |
4772
|
|
|
* |
4773
|
|
|
* @param string $string The string |
4774
|
|
|
* @return string The sanitized string |
4775
|
|
|
*/ |
4776
|
|
|
function sanitizeMSCutPaste($string) |
4777
|
|
|
{ |
4778
|
|
|
global $context; |
|
|
|
|
4779
|
|
|
|
4780
|
|
|
if (empty($string)) |
4781
|
|
|
return $string; |
4782
|
|
|
|
4783
|
|
|
// UTF-8 occurences of MS special characters |
4784
|
|
|
$findchars_utf8 = array( |
4785
|
|
|
"\xe2\x80\x9a", // single low-9 quotation mark |
4786
|
|
|
"\xe2\x80\x9e", // double low-9 quotation mark |
4787
|
|
|
"\xe2\x80\xa6", // horizontal ellipsis |
4788
|
|
|
"\xe2\x80\x98", // left single curly quote |
4789
|
|
|
"\xe2\x80\x99", // right single curly quote |
4790
|
|
|
"\xe2\x80\x9c", // left double curly quote |
4791
|
|
|
"\xe2\x80\x9d", // right double curly quote |
4792
|
|
|
"\xe2\x80\x93", // en dash |
4793
|
|
|
"\xe2\x80\x94", // em dash |
4794
|
|
|
); |
4795
|
|
|
|
4796
|
|
|
// windows 1252 / iso equivalents |
4797
|
|
|
$findchars_iso = array( |
4798
|
|
|
chr(130), |
4799
|
|
|
chr(132), |
4800
|
|
|
chr(133), |
4801
|
|
|
chr(145), |
4802
|
|
|
chr(146), |
4803
|
|
|
chr(147), |
4804
|
|
|
chr(148), |
4805
|
|
|
chr(150), |
4806
|
|
|
chr(151), |
4807
|
|
|
); |
4808
|
|
|
|
4809
|
|
|
// safe replacements |
4810
|
|
|
$replacechars = array( |
4811
|
|
|
',', // ‚ |
4812
|
|
|
',,', // „ |
4813
|
|
|
'...', // … |
4814
|
|
|
"'", // ‘ |
4815
|
|
|
"'", // ’ |
4816
|
|
|
'"', // “ |
4817
|
|
|
'"', // ” |
4818
|
|
|
'-', // – |
4819
|
|
|
'--', // — |
4820
|
|
|
); |
4821
|
|
|
|
4822
|
|
|
if ($context['utf8']) |
4823
|
|
|
$string = str_replace($findchars_utf8, $replacechars, $string); |
4824
|
|
|
else |
4825
|
|
|
$string = str_replace($findchars_iso, $replacechars, $string); |
4826
|
|
|
|
4827
|
|
|
return $string; |
4828
|
|
|
} |
4829
|
|
|
|
4830
|
|
|
/** |
4831
|
|
|
* Decode numeric html entities to their ascii or UTF8 equivalent character. |
4832
|
|
|
* |
4833
|
|
|
* Callback function for preg_replace_callback in subs-members |
4834
|
|
|
* Uses capture group 2 in the supplied array |
4835
|
|
|
* Does basic scan to ensure characters are inside a valid range |
4836
|
|
|
* |
4837
|
|
|
* @param array $matches An array of matches (relevant info should be the 3rd item) |
4838
|
|
|
* @return string A fixed string |
4839
|
|
|
*/ |
4840
|
|
|
function replaceEntities__callback($matches) |
4841
|
|
|
{ |
4842
|
|
|
global $context; |
|
|
|
|
4843
|
|
|
|
4844
|
|
|
if (!isset($matches[2])) |
4845
|
|
|
return ''; |
4846
|
|
|
|
4847
|
|
|
$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2]; |
4848
|
|
|
|
4849
|
|
|
// remove left to right / right to left overrides |
4850
|
|
|
if ($num === 0x202D || $num === 0x202E) |
4851
|
|
|
return ''; |
4852
|
|
|
|
4853
|
|
|
// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced |
4854
|
|
|
if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E))) |
4855
|
|
|
return '&#' . $num . ';'; |
4856
|
|
|
|
4857
|
|
|
if (empty($context['utf8'])) |
4858
|
|
|
{ |
4859
|
|
|
// no control characters |
4860
|
|
|
if ($num < 0x20) |
4861
|
|
|
return ''; |
4862
|
|
|
// text is text |
4863
|
|
|
elseif ($num < 0x80) |
4864
|
|
|
return chr($num); |
4865
|
|
|
// all others get html-ised |
4866
|
|
|
else |
4867
|
|
|
return '&#' . $matches[2] . ';'; |
4868
|
|
|
} |
4869
|
|
|
else |
4870
|
|
|
{ |
4871
|
|
|
// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set |
4872
|
|
|
// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text) |
4873
|
|
|
if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF)) |
4874
|
|
|
return ''; |
4875
|
|
|
// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation |
4876
|
|
|
elseif ($num < 0x80) |
4877
|
|
|
return chr($num); |
4878
|
|
|
// <0x800 (2048) |
|
|
|
|
4879
|
|
View Code Duplication |
elseif ($num < 0x800) |
4880
|
|
|
return chr(($num >> 6) + 192) . chr(($num & 63) + 128); |
4881
|
|
|
// < 0x10000 (65536) |
|
|
|
|
4882
|
|
View Code Duplication |
elseif ($num < 0x10000) |
4883
|
|
|
return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); |
4884
|
|
|
// <= 0x10FFFF (1114111) |
|
|
|
|
4885
|
|
View Code Duplication |
else |
4886
|
|
|
return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); |
4887
|
|
|
} |
4888
|
|
|
} |
4889
|
|
|
|
4890
|
|
|
/** |
4891
|
|
|
* Converts html entities to utf8 equivalents |
4892
|
|
|
* |
4893
|
|
|
* Callback function for preg_replace_callback |
4894
|
|
|
* Uses capture group 1 in the supplied array |
4895
|
|
|
* Does basic checks to keep characters inside a viewable range. |
4896
|
|
|
* |
4897
|
|
|
* @param array $matches An array of matches (relevant info should be the 2nd item in the array) |
4898
|
|
|
* @return string The fixed string |
4899
|
|
|
*/ |
4900
|
|
|
function fixchar__callback($matches) |
4901
|
|
|
{ |
4902
|
|
|
if (!isset($matches[1])) |
4903
|
|
|
return ''; |
4904
|
|
|
|
4905
|
|
|
$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1]; |
4906
|
|
|
|
4907
|
|
|
// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set |
4908
|
|
|
// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides |
4909
|
|
|
if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E) |
4910
|
|
|
return ''; |
4911
|
|
|
// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation |
4912
|
|
|
elseif ($num < 0x80) |
4913
|
|
|
return chr($num); |
4914
|
|
|
// <0x800 (2048) |
|
|
|
|
4915
|
|
View Code Duplication |
elseif ($num < 0x800) |
4916
|
|
|
return chr(($num >> 6) + 192) . chr(($num & 63) + 128); |
4917
|
|
|
// < 0x10000 (65536) |
|
|
|
|
4918
|
|
View Code Duplication |
elseif ($num < 0x10000) |
4919
|
|
|
return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); |
4920
|
|
|
// <= 0x10FFFF (1114111) |
|
|
|
|
4921
|
|
View Code Duplication |
else |
4922
|
|
|
return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); |
4923
|
|
|
} |
4924
|
|
|
|
4925
|
|
|
/** |
4926
|
|
|
* Strips out invalid html entities, replaces others with html style { codes |
4927
|
|
|
* |
4928
|
|
|
* Callback function used of preg_replace_callback in smcFunc $ent_checks, for example |
4929
|
|
|
* strpos, strlen, substr etc |
4930
|
|
|
* |
4931
|
|
|
* @param array $matches An array of matches (relevant info should be the 3rd item in the array) |
4932
|
|
|
* @return string The fixed string |
4933
|
|
|
*/ |
4934
|
|
|
function entity_fix__callback($matches) |
4935
|
|
|
{ |
4936
|
|
|
if (!isset($matches[2])) |
4937
|
|
|
return ''; |
4938
|
|
|
|
4939
|
|
|
$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2]; |
4940
|
|
|
|
4941
|
|
|
// we don't allow control characters, characters out of range, byte markers, etc |
4942
|
|
|
if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E) |
4943
|
|
|
return ''; |
4944
|
|
|
else |
4945
|
|
|
return '&#' . $num . ';'; |
4946
|
|
|
} |
4947
|
|
|
|
4948
|
|
|
/** |
4949
|
|
|
* Return a Gravatar URL based on |
4950
|
|
|
* - the supplied email address, |
4951
|
|
|
* - the global maximum rating, |
4952
|
|
|
* - the global default fallback, |
4953
|
|
|
* - maximum sizes as set in the admin panel. |
4954
|
|
|
* |
4955
|
|
|
* It is SSL aware, and caches most of the parameters. |
4956
|
|
|
* |
4957
|
|
|
* @param string $email_address The user's email address |
4958
|
|
|
* @return string The gravatar URL |
4959
|
|
|
*/ |
4960
|
|
|
function get_gravatar_url($email_address) |
4961
|
|
|
{ |
4962
|
|
|
global $modSettings, $smcFunc; |
|
|
|
|
4963
|
|
|
static $url_params = null; |
4964
|
|
|
|
4965
|
|
|
if ($url_params === null) |
4966
|
|
|
{ |
4967
|
|
|
$ratings = array('G', 'PG', 'R', 'X'); |
4968
|
|
|
$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank'); |
4969
|
|
|
$url_params = array(); |
4970
|
|
View Code Duplication |
if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings)) |
4971
|
|
|
$url_params[] = 'rating=' . $modSettings['gravatarMaxRating']; |
4972
|
|
View Code Duplication |
if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults)) |
4973
|
|
|
$url_params[] = 'default=' . $modSettings['gravatarDefault']; |
4974
|
|
|
if (!empty($modSettings['avatar_max_width_external'])) |
4975
|
|
|
$size_string = (int) $modSettings['avatar_max_width_external']; |
4976
|
|
|
if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string)) |
4977
|
|
|
if ((int) $modSettings['avatar_max_height_external'] < $size_string) |
4978
|
|
|
$size_string = $modSettings['avatar_max_height_external']; |
4979
|
|
|
|
4980
|
|
|
if (!empty($size_string)) |
4981
|
|
|
$url_params[] = 's=' . $size_string; |
4982
|
|
|
} |
4983
|
|
|
$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www'; |
4984
|
|
|
|
4985
|
|
|
return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params); |
4986
|
|
|
} |
4987
|
|
|
|
4988
|
|
|
/** |
4989
|
|
|
* Get a list of timezones. |
4990
|
|
|
* |
4991
|
|
|
* @param string $when An optional date or time for which to calculate the timezone offset values. May be a Unix timestamp or any string that strtotime() can understand. Defaults to 'now'. |
4992
|
|
|
* @return array An array of timezone info. |
4993
|
|
|
*/ |
4994
|
|
|
function smf_list_timezones($when = 'now') |
4995
|
|
|
{ |
4996
|
|
|
global $smcFunc, $modSettings; |
|
|
|
|
4997
|
|
|
static $timezones = null, $lastwhen = null; |
4998
|
|
|
|
4999
|
|
|
// No point doing this over if we already did it once |
5000
|
|
|
if (!empty($timezones) && $when == $lastwhen) |
5001
|
|
|
return $timezones; |
5002
|
|
|
else |
5003
|
|
|
$lastwhen = $when; |
5004
|
|
|
|
5005
|
|
|
// Parseable datetime string? |
5006
|
|
|
if (is_int($timestamp = strtotime($when))) |
5007
|
|
|
$when = $timestamp; |
5008
|
|
|
|
5009
|
|
|
// A Unix timestamp? |
5010
|
|
|
elseif (is_numeric($when)) |
5011
|
|
|
$when = intval($when); |
5012
|
|
|
|
5013
|
|
|
// Invalid value? Just get current Unix timestamp. |
5014
|
|
|
else |
5015
|
|
|
$when = time(); |
5016
|
|
|
|
5017
|
|
|
// We'll need these too |
5018
|
|
|
$date_when = date_create('@' . $when); |
5019
|
|
|
$later = (int) date_format(date_add($date_when, date_interval_create_from_date_string('1 year')), 'U'); |
5020
|
|
|
|
5021
|
|
|
// Prefer and give custom descriptions for these time zones |
5022
|
|
|
// If the description is left empty, it will be filled in with the names of matching cities |
5023
|
|
|
$timezone_descriptions = array( |
5024
|
|
|
'America/Adak' => 'Aleutian Islands', |
5025
|
|
|
'Pacific/Marquesas' => 'Marquesas Islands', |
5026
|
|
|
'Pacific/Gambier' => 'Gambier Islands', |
5027
|
|
|
'America/Anchorage' => 'Alaska', |
5028
|
|
|
'Pacific/Pitcairn' => 'Pitcairn Islands', |
5029
|
|
|
'America/Los_Angeles' => 'Pacific Time (USA, Canada)', |
5030
|
|
|
'America/Denver' => 'Mountain Time (USA, Canada)', |
5031
|
|
|
'America/Phoenix' => 'Mountain Time (no DST)', |
5032
|
|
|
'America/Chicago' => 'Central Time (USA, Canada)', |
5033
|
|
|
'America/Belize' => 'Central Time (no DST)', |
5034
|
|
|
'America/New_York' => 'Eastern Time (USA, Canada)', |
5035
|
|
|
'America/Atikokan' => 'Eastern Time (no DST)', |
5036
|
|
|
'America/Halifax' => 'Atlantic Time (Canada)', |
5037
|
|
|
'America/Anguilla' => 'Atlantic Time (no DST)', |
5038
|
|
|
'America/St_Johns' => 'Newfoundland', |
5039
|
|
|
'America/Chihuahua' => 'Chihuahua, Mazatlan', |
5040
|
|
|
'Pacific/Easter' => 'Easter Island', |
5041
|
|
|
'Atlantic/Stanley' => 'Falkland Islands', |
5042
|
|
|
'America/Miquelon' => 'Saint Pierre and Miquelon', |
5043
|
|
|
'America/Argentina/Buenos_Aires' => 'Buenos Aires', |
5044
|
|
|
'America/Sao_Paulo' => 'Brasilia Time', |
5045
|
|
|
'America/Araguaina' => 'Brasilia Time (no DST)', |
5046
|
|
|
'America/Godthab' => 'Greenland', |
5047
|
|
|
'America/Noronha' => 'Fernando de Noronha', |
5048
|
|
|
'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)', |
5049
|
|
|
'Europe/London' => '', |
5050
|
|
|
'Europe/Berlin' => 'Central European Time', |
5051
|
|
|
'Europe/Helsinki' => 'Eastern European Time', |
5052
|
|
|
'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo', |
5053
|
|
|
'Asia/Jerusalem' => 'Jerusalem', |
5054
|
|
|
'Europe/Moscow' => '', |
5055
|
|
|
'Africa/Khartoum' => 'Eastern Africa Time', |
5056
|
|
|
'Asia/Riyadh' => 'Arabia Time', |
5057
|
|
|
'Asia/Kolkata' => 'India, Sri Lanka', |
5058
|
|
|
'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen', |
5059
|
|
|
'Asia/Dhaka' => 'Astana, Dhaka', |
5060
|
|
|
'Asia/Rangoon' => 'Yangon/Rangoon', |
5061
|
|
|
'Indian/Christmas' => 'Christmas Island', |
5062
|
|
|
'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station', |
5063
|
|
|
'Antarctica/Vostok' => 'Vostok Station', |
5064
|
|
|
'Australia/Lord_Howe' => 'Lord Howe Island', |
5065
|
|
|
'Pacific/Guadalcanal' => 'Solomon Islands', |
5066
|
|
|
'Pacific/Norfolk' => 'Norfolk Island', |
5067
|
|
|
'Pacific/Noumea' => 'New Caledonia', |
5068
|
|
|
'Pacific/Auckland' => 'Auckland, McMurdo Station', |
5069
|
|
|
'Pacific/Kwajalein' => 'Marshall Islands', |
5070
|
|
|
'Pacific/Chatham' => 'Chatham Islands', |
5071
|
|
|
); |
5072
|
|
|
|
5073
|
|
|
// Should we put time zones from certain countries at the top of the list? |
5074
|
|
|
$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array(); |
5075
|
|
|
$priority_tzids = array(); |
5076
|
|
|
foreach ($priority_countries as $country) |
5077
|
|
|
{ |
5078
|
|
|
$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country))); |
5079
|
|
|
if (!empty($country_tzids)) |
5080
|
|
|
$priority_tzids = array_merge($priority_tzids, $country_tzids); |
5081
|
|
|
} |
5082
|
|
|
|
5083
|
|
|
// Process the preferred timezones first, then the rest. |
5084
|
|
|
$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions)); |
5085
|
|
|
|
5086
|
|
|
// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules. |
5087
|
|
|
foreach ($tzids as $tzid) |
5088
|
|
|
{ |
5089
|
|
|
// We don't want UTC right now |
5090
|
|
|
if ($tzid == 'UTC') |
5091
|
|
|
continue; |
5092
|
|
|
|
5093
|
|
|
$tz = timezone_open($tzid); |
5094
|
|
|
|
5095
|
|
|
// First, get the set of transition rules for this tzid |
5096
|
|
|
$tzinfo = timezone_transitions_get($tz, $when, $later); |
5097
|
|
|
|
5098
|
|
|
// Use the entire set of transition rules as the array *key* so we can avoid duplicates |
5099
|
|
|
$tzkey = serialize($tzinfo); |
5100
|
|
|
|
5101
|
|
|
// Next, get the geographic info for this tzid |
5102
|
|
|
$tzgeo = timezone_location_get($tz); |
5103
|
|
|
|
5104
|
|
|
// Don't overwrite our preferred tzids |
5105
|
|
|
if (empty($zones[$tzkey]['tzid'])) |
5106
|
|
|
{ |
5107
|
|
|
$zones[$tzkey]['tzid'] = $tzid; |
|
|
|
|
5108
|
|
|
$zones[$tzkey]['abbr'] = fix_tz_abbrev($tzid, $tzinfo[0]['abbr']); |
|
|
|
|
5109
|
|
|
} |
5110
|
|
|
|
5111
|
|
|
// A time zone from a prioritized country? |
5112
|
|
|
if (in_array($tzid, $priority_tzids)) |
5113
|
|
|
$priority_zones[$tzkey] = true; |
|
|
|
|
5114
|
|
|
|
5115
|
|
|
// Keep track of the location and offset for this tzid |
5116
|
|
|
$tzid_parts = explode('/', $tzid); |
5117
|
|
|
$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop($tzid_parts)); |
5118
|
|
|
$offsets[$tzkey] = $tzinfo[0]['offset']; |
|
|
|
|
5119
|
|
|
$longitudes[$tzkey] = empty($longitudes[$tzkey]) ? $tzgeo['longitude'] : $longitudes[$tzkey]; |
|
|
|
|
5120
|
|
|
} |
5121
|
|
|
|
5122
|
|
|
// Sort by offset then longitude |
5123
|
|
|
array_multisort($offsets, SORT_ASC, SORT_NUMERIC, $longitudes, SORT_ASC, SORT_NUMERIC, $zones); |
5124
|
|
|
|
5125
|
|
|
// Build the final array of formatted values |
5126
|
|
|
$priority_timezones = array(); |
5127
|
|
|
$timezones = array(); |
5128
|
|
|
foreach ($zones as $tzkey => $tzvalue) |
5129
|
|
|
{ |
5130
|
|
|
date_timezone_set($date_when, timezone_open($tzvalue['tzid'])); |
5131
|
|
|
|
5132
|
|
|
if (!empty($timezone_descriptions[$tzvalue['tzid']])) |
5133
|
|
|
$desc = $timezone_descriptions[$tzvalue['tzid']]; |
5134
|
|
|
else |
5135
|
|
|
$desc = implode(', ', array_unique($tzvalue['locations'])); |
5136
|
|
|
|
5137
|
|
|
if (isset($priority_zones[$tzkey])) |
5138
|
|
|
$priority_timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']'; |
5139
|
|
|
else |
5140
|
|
|
$timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']'; |
5141
|
|
|
} |
5142
|
|
|
|
5143
|
|
|
$timezones = array_merge( |
5144
|
|
|
$priority_timezones, |
5145
|
|
|
array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'), |
5146
|
|
|
$timezones |
5147
|
|
|
); |
5148
|
|
|
|
5149
|
|
|
return $timezones; |
5150
|
|
|
} |
5151
|
|
|
|
5152
|
|
|
/** |
5153
|
|
|
* Reformats certain time zone abbreviations to look better. |
5154
|
|
|
* |
5155
|
|
|
* Some of PHP's time zone abbreviations are just numerical offsets from UTC, e.g. '+04' |
5156
|
|
|
* These look weird and are kind of useless, so we make them look better. |
5157
|
|
|
* |
5158
|
|
|
* @param string $tzid The Olsen time zome identifier for a time zone. |
5159
|
|
|
* @param string $tz_abbrev The abbreviation PHP provided for this time zone. |
5160
|
|
|
* @return string The fixed version of $tz_abbrev. |
5161
|
|
|
*/ |
5162
|
|
|
function fix_tz_abbrev($tzid, $tz_abbrev) |
5163
|
|
|
{ |
5164
|
|
|
// Is this abbreviation just a numerical offset? |
5165
|
|
|
if (strspn($tz_abbrev, '+-') > 0) |
5166
|
|
|
{ |
5167
|
|
|
// To get on this list, a time zone must be historically stable and must not observe daylight saving time |
5168
|
|
|
$missing_tz_abbrs = array( |
5169
|
|
|
'Antarctica/Casey' => 'CAST', |
5170
|
|
|
'Antarctica/Davis' => 'DAVT', |
5171
|
|
|
'Antarctica/DumontDUrville' => 'DDUT', |
5172
|
|
|
'Antarctica/Mawson' => 'MAWT', |
5173
|
|
|
'Antarctica/Rothera' => 'ART', |
5174
|
|
|
'Antarctica/Syowa' => 'SYOT', |
5175
|
|
|
'Antarctica/Vostok' => 'VOST', |
5176
|
|
|
'Asia/Almaty' => 'ALMT', |
5177
|
|
|
'Asia/Aqtau' => 'ORAT', |
5178
|
|
|
'Asia/Aqtobe' => 'AQTT', |
5179
|
|
|
'Asia/Ashgabat' => 'TMT', |
5180
|
|
|
'Asia/Bishkek' => 'KGT', |
5181
|
|
|
'Asia/Colombo' => 'IST', |
5182
|
|
|
'Asia/Dushanbe' => 'TJT', |
5183
|
|
|
'Asia/Oral' => 'ORAT', |
5184
|
|
|
'Asia/Qyzylorda' => 'QYZT', |
5185
|
|
|
'Asia/Samarkand' => 'UZT', |
5186
|
|
|
'Asia/Tashkent' => 'UZT', |
5187
|
|
|
'Asia/Tbilisi' => 'GET', |
5188
|
|
|
'Asia/Yerevan' => 'AMT', |
5189
|
|
|
'Europe/Istanbul' => 'TRT', |
5190
|
|
|
'Europe/Minsk' => 'MSK', |
5191
|
|
|
'Indian/Kerguelen' => 'TFT', |
5192
|
|
|
); |
5193
|
|
|
|
5194
|
|
|
if (!empty($missing_tz_abbrs[$tzid])) |
5195
|
|
|
$tz_abbrev = $missing_tz_abbrs[$tzid]; |
5196
|
|
|
else |
5197
|
|
|
{ |
5198
|
|
|
// Russia likes to experiment with time zones often, and names them as offsets from Moscow |
5199
|
|
|
$tz_location = timezone_location_get(timezone_open($tzid)); |
5200
|
|
|
if ($tz_location['country_code'] == 'RU') |
5201
|
|
|
{ |
5202
|
|
|
$msk_offset = intval($tz_abbrev) - 3; |
5203
|
|
|
$tz_abbrev = 'MSK' . (!empty($msk_offset) ? sprintf('%+0d', $msk_offset) : ''); |
5204
|
|
|
} |
5205
|
|
|
} |
5206
|
|
|
|
5207
|
|
|
// Still no good? We'll just mark it as a UTC offset |
5208
|
|
|
if (strspn($tz_abbrev, '+-') > 0) |
5209
|
|
|
{ |
5210
|
|
|
$tz_abbrev = intval($tz_abbrev); |
5211
|
|
|
$tz_abbrev = 'UTC' . (!empty($tz_abbrev) ? sprintf('%+0d', $tz_abbrev) : ''); |
5212
|
|
|
} |
5213
|
|
|
} |
5214
|
|
|
|
5215
|
|
|
return $tz_abbrev; |
5216
|
|
|
} |
5217
|
|
|
|
5218
|
|
|
/** |
5219
|
|
|
* @param string $ip_address An IP address in IPv4, IPv6 or decimal notation |
5220
|
|
|
* @return string|false The IP address in binary or false |
5221
|
|
|
*/ |
5222
|
|
|
function inet_ptod($ip_address) |
5223
|
|
|
{ |
5224
|
|
|
if (!isValidIP($ip_address)) |
5225
|
|
|
return $ip_address; |
5226
|
|
|
|
5227
|
|
|
$bin = inet_pton($ip_address); |
5228
|
|
|
return $bin; |
5229
|
|
|
} |
5230
|
|
|
|
5231
|
|
|
/** |
5232
|
|
|
* @param string $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases)) |
5233
|
|
|
* @return string|false The IP address in presentation format or false on error |
5234
|
|
|
*/ |
5235
|
|
|
function inet_dtop($bin) |
5236
|
|
|
{ |
5237
|
|
|
if(empty($bin)) |
5238
|
|
|
return ''; |
5239
|
|
|
|
5240
|
|
|
global $db_type; |
|
|
|
|
5241
|
|
|
|
5242
|
|
|
if ($db_type == 'postgresql') |
5243
|
|
|
return $bin; |
5244
|
|
|
|
5245
|
|
|
$ip_address = inet_ntop($bin); |
5246
|
|
|
|
5247
|
|
|
return $ip_address; |
5248
|
|
|
} |
5249
|
|
|
|
5250
|
|
|
/** |
5251
|
|
|
* Safe serialize() and unserialize() replacements |
5252
|
|
|
* |
5253
|
|
|
* @license Public Domain |
5254
|
|
|
* |
5255
|
|
|
* @author anthon (dot) pang (at) gmail (dot) com |
5256
|
|
|
*/ |
5257
|
|
|
|
5258
|
|
|
/** |
5259
|
|
|
* Safe serialize() replacement. Recursive |
5260
|
|
|
* - output a strict subset of PHP's native serialized representation |
5261
|
|
|
* - does not serialize objects |
5262
|
|
|
* |
5263
|
|
|
* @param mixed $value |
5264
|
|
|
* @return string |
|
|
|
|
5265
|
|
|
*/ |
5266
|
|
|
function _safe_serialize($value) |
5267
|
|
|
{ |
5268
|
|
|
if(is_null($value)) |
5269
|
|
|
return 'N;'; |
5270
|
|
|
|
5271
|
|
|
if(is_bool($value)) |
5272
|
|
|
return 'b:'. (int) $value .';'; |
5273
|
|
|
|
5274
|
|
|
if(is_int($value)) |
5275
|
|
|
return 'i:'. $value .';'; |
5276
|
|
|
|
5277
|
|
|
if(is_float($value)) |
5278
|
|
|
return 'd:'. str_replace(',', '.', $value) .';'; |
5279
|
|
|
|
5280
|
|
|
if(is_string($value)) |
5281
|
|
|
return 's:'. strlen($value) .':"'. $value .'";'; |
5282
|
|
|
|
5283
|
|
|
if(is_array($value)) |
5284
|
|
|
{ |
5285
|
|
|
$out = ''; |
5286
|
|
|
foreach($value as $k => $v) |
5287
|
|
|
$out .= _safe_serialize($k) . _safe_serialize($v); |
5288
|
|
|
|
5289
|
|
|
return 'a:'. count($value) .':{'. $out .'}'; |
5290
|
|
|
} |
5291
|
|
|
|
5292
|
|
|
// safe_serialize cannot serialize resources or objects. |
5293
|
|
|
return false; |
5294
|
|
|
} |
5295
|
|
|
/** |
5296
|
|
|
* Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues. |
5297
|
|
|
* |
5298
|
|
|
* @param mixed $value |
5299
|
|
|
* @return string |
|
|
|
|
5300
|
|
|
*/ |
5301
|
|
View Code Duplication |
function safe_serialize($value) |
|
|
|
|
5302
|
|
|
{ |
5303
|
|
|
// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen() |
5304
|
|
|
if (function_exists('mb_internal_encoding') && |
5305
|
|
|
(((int) ini_get('mbstring.func_overload')) & 2)) |
5306
|
|
|
{ |
5307
|
|
|
$mbIntEnc = mb_internal_encoding(); |
5308
|
|
|
mb_internal_encoding('ASCII'); |
5309
|
|
|
} |
5310
|
|
|
|
5311
|
|
|
$out = _safe_serialize($value); |
5312
|
|
|
|
5313
|
|
|
if (isset($mbIntEnc)) |
5314
|
|
|
mb_internal_encoding($mbIntEnc); |
5315
|
|
|
|
5316
|
|
|
return $out; |
5317
|
|
|
} |
5318
|
|
|
|
5319
|
|
|
/** |
5320
|
|
|
* Safe unserialize() replacement |
5321
|
|
|
* - accepts a strict subset of PHP's native serialized representation |
5322
|
|
|
* - does not unserialize objects |
5323
|
|
|
* |
5324
|
|
|
* @param string $str |
5325
|
|
|
* @return mixed |
|
|
|
|
5326
|
|
|
* @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects) |
5327
|
|
|
*/ |
5328
|
|
|
function _safe_unserialize($str) |
5329
|
|
|
{ |
5330
|
|
|
// Input is not a string. |
5331
|
|
|
if(empty($str) || !is_string($str)) |
5332
|
|
|
return false; |
5333
|
|
|
|
5334
|
|
|
$stack = array(); |
5335
|
|
|
$expected = array(); |
5336
|
|
|
|
5337
|
|
|
/* |
5338
|
|
|
* states: |
5339
|
|
|
* 0 - initial state, expecting a single value or array |
5340
|
|
|
* 1 - terminal state |
5341
|
|
|
* 2 - in array, expecting end of array or a key |
5342
|
|
|
* 3 - in array, expecting value or another array |
5343
|
|
|
*/ |
5344
|
|
|
$state = 0; |
5345
|
|
|
while($state != 1) |
5346
|
|
|
{ |
5347
|
|
|
$type = isset($str[0]) ? $str[0] : ''; |
5348
|
|
|
if($type == '}') |
5349
|
|
|
$str = substr($str, 1); |
5350
|
|
|
|
5351
|
|
|
else if($type == 'N' && $str[1] == ';') |
5352
|
|
|
{ |
5353
|
|
|
$value = null; |
5354
|
|
|
$str = substr($str, 2); |
5355
|
|
|
} |
5356
|
|
|
else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches)) |
5357
|
|
|
{ |
5358
|
|
|
$value = $matches[1] == '1' ? true : false; |
5359
|
|
|
$str = substr($str, 4); |
5360
|
|
|
} |
5361
|
|
|
else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches)) |
5362
|
|
|
{ |
5363
|
|
|
$value = (int)$matches[1]; |
5364
|
|
|
$str = $matches[2]; |
5365
|
|
|
} |
5366
|
|
|
else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches)) |
5367
|
|
|
{ |
5368
|
|
|
$value = (float)$matches[1]; |
5369
|
|
|
$str = $matches[3]; |
5370
|
|
|
} |
5371
|
|
|
else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";') |
5372
|
|
|
{ |
5373
|
|
|
$value = substr($matches[2], 0, (int)$matches[1]); |
5374
|
|
|
$str = substr($matches[2], (int)$matches[1] + 2); |
5375
|
|
|
} |
5376
|
|
|
else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches)) |
5377
|
|
|
{ |
5378
|
|
|
$expectedLength = (int)$matches[1]; |
5379
|
|
|
$str = $matches[2]; |
5380
|
|
|
} |
5381
|
|
|
|
5382
|
|
|
// Object or unknown/malformed type. |
5383
|
|
|
else |
5384
|
|
|
return false; |
5385
|
|
|
|
5386
|
|
|
switch($state) |
5387
|
|
|
{ |
5388
|
|
|
case 3: // In array, expecting value or another array. |
5389
|
|
|
if($type == 'a') |
5390
|
|
|
{ |
5391
|
|
|
$stack[] = &$list; |
|
|
|
|
5392
|
|
|
$list[$key] = array(); |
|
|
|
|
5393
|
|
|
$list = &$list[$key]; |
5394
|
|
|
$expected[] = $expectedLength; |
|
|
|
|
5395
|
|
|
$state = 2; |
5396
|
|
|
break; |
5397
|
|
|
} |
5398
|
|
|
if($type != '}') |
5399
|
|
|
{ |
5400
|
|
|
$list[$key] = $value; |
|
|
|
|
5401
|
|
|
$state = 2; |
5402
|
|
|
break; |
5403
|
|
|
} |
5404
|
|
|
|
5405
|
|
|
// Missing array value. |
5406
|
|
|
return false; |
5407
|
|
|
|
5408
|
|
|
case 2: // in array, expecting end of array or a key |
5409
|
|
|
if($type == '}') |
5410
|
|
|
{ |
5411
|
|
|
// Array size is less than expected. |
5412
|
|
|
if(count($list) < end($expected)) |
5413
|
|
|
return false; |
5414
|
|
|
|
5415
|
|
|
unset($list); |
5416
|
|
|
$list = &$stack[count($stack)-1]; |
5417
|
|
|
array_pop($stack); |
5418
|
|
|
|
5419
|
|
|
// Go to terminal state if we're at the end of the root array. |
5420
|
|
|
array_pop($expected); |
5421
|
|
|
|
5422
|
|
|
if(count($expected) == 0) |
5423
|
|
|
$state = 1; |
5424
|
|
|
|
5425
|
|
|
break; |
5426
|
|
|
} |
5427
|
|
|
|
5428
|
|
|
if($type == 'i' || $type == 's') |
5429
|
|
|
{ |
5430
|
|
|
// Array size exceeds expected length. |
5431
|
|
|
if(count($list) >= end($expected)) |
5432
|
|
|
return false; |
5433
|
|
|
|
5434
|
|
|
$key = $value; |
5435
|
|
|
$state = 3; |
5436
|
|
|
break; |
5437
|
|
|
} |
5438
|
|
|
|
5439
|
|
|
// Illegal array index type. |
5440
|
|
|
return false; |
5441
|
|
|
|
5442
|
|
|
// Expecting array or value. |
5443
|
|
|
case 0: |
5444
|
|
|
if($type == 'a') |
5445
|
|
|
{ |
5446
|
|
|
$data = array(); |
5447
|
|
|
$list = &$data; |
5448
|
|
|
$expected[] = $expectedLength; |
5449
|
|
|
$state = 2; |
5450
|
|
|
break; |
5451
|
|
|
} |
5452
|
|
|
|
5453
|
|
|
if($type != '}') |
5454
|
|
|
{ |
5455
|
|
|
$data = $value; |
5456
|
|
|
$state = 1; |
5457
|
|
|
break; |
5458
|
|
|
} |
5459
|
|
|
|
5460
|
|
|
// Not in array. |
5461
|
|
|
return false; |
5462
|
|
|
} |
5463
|
|
|
} |
5464
|
|
|
|
5465
|
|
|
// Trailing data in input. |
5466
|
|
|
if(!empty($str)) |
5467
|
|
|
return false; |
5468
|
|
|
|
5469
|
|
|
return $data; |
|
|
|
|
5470
|
|
|
} |
5471
|
|
|
|
5472
|
|
|
/** |
5473
|
|
|
* Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue |
5474
|
|
|
* |
5475
|
|
|
* @param string $str |
5476
|
|
|
* @return mixed |
|
|
|
|
5477
|
|
|
*/ |
5478
|
|
View Code Duplication |
function safe_unserialize($str) |
|
|
|
|
5479
|
|
|
{ |
5480
|
|
|
// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen() |
5481
|
|
|
if (function_exists('mb_internal_encoding') && |
5482
|
|
|
(((int) ini_get('mbstring.func_overload')) & 0x02)) |
5483
|
|
|
{ |
5484
|
|
|
$mbIntEnc = mb_internal_encoding(); |
5485
|
|
|
mb_internal_encoding('ASCII'); |
5486
|
|
|
} |
5487
|
|
|
|
5488
|
|
|
$out = _safe_unserialize($str); |
5489
|
|
|
|
5490
|
|
|
if (isset($mbIntEnc)) |
5491
|
|
|
mb_internal_encoding($mbIntEnc); |
5492
|
|
|
|
5493
|
|
|
return $out; |
5494
|
|
|
} |
5495
|
|
|
|
5496
|
|
|
/** |
5497
|
|
|
* Tries different modes to make file/dirs writable. Wrapper function for chmod() |
5498
|
|
|
|
5499
|
|
|
* @param string $file The file/dir full path. |
5500
|
|
|
* @param int $value Not needed, added for legacy reasons. |
5501
|
|
|
* @return boolean true if the file/dir is already writable or the function was able to make it writable, false if the function couldn't make the file/dir writable. |
5502
|
|
|
*/ |
5503
|
|
|
function smf_chmod($file, $value = 0) |
|
|
|
|
5504
|
|
|
{ |
5505
|
|
|
// No file? no checks! |
5506
|
|
|
if (empty($file)) |
5507
|
|
|
return false; |
5508
|
|
|
|
5509
|
|
|
// Already writable? |
5510
|
|
|
if (is_writable($file)) |
5511
|
|
|
return true; |
5512
|
|
|
|
5513
|
|
|
// Do we have a file or a dir? |
5514
|
|
|
$isDir = is_dir($file); |
5515
|
|
|
$isWritable = false; |
5516
|
|
|
|
5517
|
|
|
// Set different modes. |
5518
|
|
|
$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666); |
5519
|
|
|
|
5520
|
|
|
foreach($chmodValues as $val) |
5521
|
|
|
{ |
5522
|
|
|
// If it's writable, break out of the loop. |
5523
|
|
|
if (is_writable($file)) |
5524
|
|
|
{ |
5525
|
|
|
$isWritable = true; |
5526
|
|
|
break; |
5527
|
|
|
} |
5528
|
|
|
|
5529
|
|
|
else |
5530
|
|
|
@chmod($file, $val); |
|
|
|
|
5531
|
|
|
} |
5532
|
|
|
|
5533
|
|
|
return $isWritable; |
5534
|
|
|
} |
5535
|
|
|
|
5536
|
|
|
/** |
5537
|
|
|
* Wrapper function for json_decode() with error handling. |
5538
|
|
|
|
5539
|
|
|
* @param string $json The string to decode. |
5540
|
|
|
* @param bool $returnAsArray To return the decoded string as an array or an object, SMF only uses Arrays but to keep on compatibility with json_decode its set to false as default. |
5541
|
|
|
* @param bool $logIt To specify if the error will be logged if theres any. |
5542
|
|
|
* @return array Either an empty array or the decoded data as an array. |
5543
|
|
|
*/ |
5544
|
|
|
function smf_json_decode($json, $returnAsArray = false, $logIt = true) |
5545
|
|
|
{ |
5546
|
|
|
global $txt; |
|
|
|
|
5547
|
|
|
|
5548
|
|
|
// Come on... |
5549
|
|
|
if (empty($json) || !is_string($json)) |
5550
|
|
|
return array(); |
5551
|
|
|
|
5552
|
|
|
$returnArray = @json_decode($json, $returnAsArray); |
5553
|
|
|
|
5554
|
|
|
// PHP 5.3 so no json_last_error_msg() |
5555
|
|
|
switch(json_last_error()) |
5556
|
|
|
{ |
5557
|
|
|
case JSON_ERROR_NONE: |
5558
|
|
|
$jsonError = false; |
5559
|
|
|
break; |
5560
|
|
|
case JSON_ERROR_DEPTH: |
5561
|
|
|
$jsonError = 'JSON_ERROR_DEPTH'; |
5562
|
|
|
break; |
5563
|
|
|
case JSON_ERROR_STATE_MISMATCH: |
5564
|
|
|
$jsonError = 'JSON_ERROR_STATE_MISMATCH'; |
5565
|
|
|
break; |
5566
|
|
|
case JSON_ERROR_CTRL_CHAR: |
5567
|
|
|
$jsonError = 'JSON_ERROR_CTRL_CHAR'; |
5568
|
|
|
break; |
5569
|
|
|
case JSON_ERROR_SYNTAX: |
5570
|
|
|
$jsonError = 'JSON_ERROR_SYNTAX'; |
5571
|
|
|
break; |
5572
|
|
|
case JSON_ERROR_UTF8: |
5573
|
|
|
$jsonError = 'JSON_ERROR_UTF8'; |
5574
|
|
|
break; |
5575
|
|
|
default: |
5576
|
|
|
$jsonError = 'unknown'; |
5577
|
|
|
break; |
5578
|
|
|
} |
5579
|
|
|
|
5580
|
|
|
// Something went wrong! |
5581
|
|
|
if (!empty($jsonError) && $logIt) |
5582
|
|
|
{ |
5583
|
|
|
// Being a wrapper means we lost our smf_error_handler() privileges :( |
5584
|
|
|
$jsonDebug = debug_backtrace(); |
5585
|
|
|
$jsonDebug = $jsonDebug[0]; |
5586
|
|
|
loadLanguage('Errors'); |
5587
|
|
|
|
5588
|
|
|
if (!empty($jsonDebug)) |
5589
|
|
|
log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']); |
5590
|
|
|
|
5591
|
|
|
else |
5592
|
|
|
log_error($txt['json_'. $jsonError], 'critical'); |
5593
|
|
|
|
5594
|
|
|
// Everyone expects an array. |
5595
|
|
|
return array(); |
5596
|
|
|
} |
5597
|
|
|
|
5598
|
|
|
return $returnArray; |
5599
|
|
|
} |
5600
|
|
|
|
5601
|
|
|
/** |
5602
|
|
|
* Check the given String if he is a valid IPv4 or IPv6 |
5603
|
|
|
* return true or false |
5604
|
|
|
* |
5605
|
|
|
* @param string $IPString |
5606
|
|
|
* |
5607
|
|
|
* @return bool |
5608
|
|
|
*/ |
5609
|
|
|
function isValidIP($IPString) |
5610
|
|
|
{ |
5611
|
|
|
return filter_var($IPString, FILTER_VALIDATE_IP) !== false; |
5612
|
|
|
} |
5613
|
|
|
|
5614
|
|
|
/** |
5615
|
|
|
* Outputs a response. |
5616
|
|
|
* It assumes the data is already a string. |
5617
|
|
|
* @param string $data The data to print |
5618
|
|
|
* @param string $type The content type. Defaults to Json. |
5619
|
|
|
* @return void |
|
|
|
|
5620
|
|
|
*/ |
5621
|
|
|
function smf_serverResponse($data = '', $type = 'Content-Type: application/json') |
5622
|
|
|
{ |
5623
|
|
|
global $db_show_debug, $modSettings; |
|
|
|
|
5624
|
|
|
|
5625
|
|
|
// Defensive programming anyone? |
5626
|
|
|
if (empty($data)) |
5627
|
|
|
return false; |
5628
|
|
|
|
5629
|
|
|
// Don't need extra stuff... |
5630
|
|
|
$db_show_debug = false; |
5631
|
|
|
|
5632
|
|
|
// Kill anything else. |
5633
|
|
|
ob_end_clean(); |
5634
|
|
|
|
5635
|
|
|
if (!empty($modSettings['CompressedOutput'])) |
5636
|
|
|
@ob_start('ob_gzhandler'); |
|
|
|
|
5637
|
|
|
|
5638
|
|
|
else |
5639
|
|
|
ob_start(); |
5640
|
|
|
|
5641
|
|
|
// Set the header. |
5642
|
|
|
header($type); |
5643
|
|
|
|
5644
|
|
|
// Echo! |
5645
|
|
|
echo $data; |
5646
|
|
|
|
5647
|
|
|
// Done. |
5648
|
|
|
obExit(false); |
5649
|
|
|
} |
5650
|
|
|
|
5651
|
|
|
/** |
5652
|
|
|
* Creates an optimized regex to match all known top level domains. |
5653
|
|
|
* |
5654
|
|
|
* The optimized regex is stored in $modSettings['tld_regex']. |
5655
|
|
|
* |
5656
|
|
|
* To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set |
5657
|
|
|
* the $update parameter to true. Updating can take some time, based on network connectivity, so it |
5658
|
|
|
* should normally only be done by calling this function from a background or scheduled task. |
5659
|
|
|
* |
5660
|
|
|
* If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a |
5661
|
|
|
* hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update. |
5662
|
|
|
* |
5663
|
|
|
* @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org. |
5664
|
|
|
*/ |
5665
|
|
|
function set_tld_regex($update = false) |
5666
|
|
|
{ |
5667
|
|
|
global $sourcedir, $smcFunc, $modSettings; |
|
|
|
|
5668
|
|
|
static $done = false; |
5669
|
|
|
|
5670
|
|
|
// If we don't need to do anything, don't |
5671
|
|
|
if (!$update && $done) |
5672
|
|
|
return; |
5673
|
|
|
|
5674
|
|
|
// Should we get a new copy of the official list of TLDs? |
5675
|
|
|
if ($update) |
5676
|
|
|
{ |
5677
|
|
|
require_once($sourcedir . '/Subs-Package.php'); |
5678
|
|
|
$tlds = fetch_web_data('https://data.iana.org/TLD/tlds-alpha-by-domain.txt'); |
5679
|
|
|
|
5680
|
|
|
// If the Internet Assigned Numbers Authority can't be reached, the Internet is gone. We're probably running on a server hidden in a bunker deep underground to protect it from marauding bandits roaming on the surface. We don't want to waste precious electricity on pointlessly repeating background tasks, so we'll wait until the next regularly scheduled update to see if civilization has been restored. |
5681
|
|
|
if ($tlds === false) |
5682
|
|
|
$postapocalypticNightmare = true; |
5683
|
|
|
} |
5684
|
|
|
// If we aren't updating and the regex is valid, we're done |
5685
|
|
|
elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false) |
5686
|
|
|
{ |
5687
|
|
|
$done = true; |
5688
|
|
|
return; |
5689
|
|
|
} |
5690
|
|
|
|
5691
|
|
|
// If we successfully got an update, process the list into an array |
5692
|
|
|
if (!empty($tlds)) |
5693
|
|
|
{ |
5694
|
|
|
// Clean $tlds and convert it to an array |
5695
|
|
|
$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) { |
5696
|
|
|
$line = trim($line); |
5697
|
|
|
if (empty($line) || strpos($line, '#') !== false || strpos($line, ' ') !== false) |
|
|
|
|
5698
|
|
|
return false; |
5699
|
|
|
else |
5700
|
|
|
return true; |
5701
|
|
|
}); |
5702
|
|
|
|
5703
|
|
|
// Convert Punycode to Unicode |
5704
|
|
|
$tlds = array_map(function ($input) { |
5705
|
|
|
$prefix = 'xn--'; |
5706
|
|
|
$safe_char = 0xFFFC; |
5707
|
|
|
$base = 36; |
5708
|
|
|
$tmin = 1; |
5709
|
|
|
$tmax = 26; |
5710
|
|
|
$skew = 38; |
5711
|
|
|
$damp = 700; |
5712
|
|
|
$output_parts = array(); |
5713
|
|
|
|
5714
|
|
|
$input = str_replace(strtoupper($prefix), $prefix, $input); |
5715
|
|
|
|
5716
|
|
|
$enco_parts = (array) explode('.', $input); |
5717
|
|
|
|
5718
|
|
|
foreach ($enco_parts as $encoded) |
5719
|
|
|
{ |
5720
|
|
|
if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0) |
5721
|
|
|
{ |
5722
|
|
|
$output_parts[] = $encoded; |
5723
|
|
|
continue; |
5724
|
|
|
} |
5725
|
|
|
|
5726
|
|
|
$is_first = true; |
5727
|
|
|
$bias = 72; |
5728
|
|
|
$idx = 0; |
5729
|
|
|
$char = 0x80; |
5730
|
|
|
$decoded = array(); |
5731
|
|
|
$output=''; |
5732
|
|
|
$delim_pos = strrpos($encoded, '-'); |
5733
|
|
|
|
5734
|
|
|
if ($delim_pos > strlen($prefix)) |
5735
|
|
|
{ |
5736
|
|
|
for ($k = strlen($prefix); $k < $delim_pos; ++$k) |
5737
|
|
|
{ |
5738
|
|
|
$decoded[] = ord($encoded{$k}); |
5739
|
|
|
} |
5740
|
|
|
} |
5741
|
|
|
|
5742
|
|
|
$deco_len = count($decoded); |
5743
|
|
|
$enco_len = strlen($encoded); |
5744
|
|
|
|
5745
|
|
|
for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) |
5746
|
|
|
{ |
5747
|
|
|
for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base) |
5748
|
|
|
{ |
5749
|
|
|
$cp = ord($encoded{$enco_idx++}); |
5750
|
|
|
$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base)); |
5751
|
|
|
$idx += $digit * $w; |
5752
|
|
|
$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias)); |
5753
|
|
|
|
5754
|
|
|
if ($digit < $t) |
5755
|
|
|
break; |
5756
|
|
|
|
5757
|
|
|
$w = (int) ($w * ($base - $t)); |
5758
|
|
|
} |
5759
|
|
|
|
5760
|
|
|
$delta = $idx - $old_idx; |
5761
|
|
|
$delta = intval($is_first ? ($delta / $damp) : ($delta / 2)); |
5762
|
|
|
$delta += intval($delta / ($deco_len + 1)); |
5763
|
|
|
|
5764
|
|
|
for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base) |
5765
|
|
|
$delta = intval($delta / ($base - $tmin)); |
5766
|
|
|
|
5767
|
|
|
$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew)); |
5768
|
|
|
$is_first = false; |
5769
|
|
|
$char += (int) ($idx / ($deco_len + 1)); |
5770
|
|
|
$idx %= ($deco_len + 1); |
5771
|
|
|
|
5772
|
|
|
if ($deco_len > 0) |
5773
|
|
|
{ |
5774
|
|
|
for ($i = $deco_len; $i > $idx; $i--) |
5775
|
|
|
$decoded[$i] = $decoded[($i - 1)]; |
5776
|
|
|
} |
5777
|
|
|
$decoded[$idx++] = $char; |
5778
|
|
|
} |
5779
|
|
|
|
5780
|
|
|
foreach ($decoded as $k => $v) |
5781
|
|
|
{ |
5782
|
|
|
// 7bit are transferred literally |
5783
|
|
|
if ($v < 128) |
5784
|
|
|
$output .= chr($v); |
5785
|
|
|
|
5786
|
|
|
// 2 bytes |
5787
|
|
|
elseif ($v < (1 << 11)) |
5788
|
|
|
$output .= chr(192+($v >> 6)) . chr(128+($v & 63)); |
5789
|
|
|
|
5790
|
|
|
// 3 bytes |
5791
|
|
|
elseif ($v < (1 << 16)) |
5792
|
|
|
$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63)); |
5793
|
|
|
|
5794
|
|
|
// 4 bytes |
5795
|
|
|
elseif ($v < (1 << 21)) |
5796
|
|
|
$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63)); |
5797
|
|
|
|
5798
|
|
|
// 'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k |
5799
|
|
|
else |
5800
|
|
|
$output .= $safe_char; |
5801
|
|
|
} |
5802
|
|
|
|
5803
|
|
|
$output_parts[] = $output; |
5804
|
|
|
} |
5805
|
|
|
|
5806
|
|
|
return implode('.', $output_parts); |
5807
|
|
|
}, $tlds); |
5808
|
|
|
} |
5809
|
|
|
// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update |
5810
|
|
|
else |
5811
|
|
|
{ |
5812
|
|
|
$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat', |
5813
|
|
|
'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel', |
5814
|
|
|
'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', |
5815
|
|
|
'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', |
5816
|
|
|
'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc', |
5817
|
|
|
'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv', |
5818
|
|
|
'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh', |
5819
|
|
|
'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', |
5820
|
|
|
'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', |
5821
|
|
|
'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', |
5822
|
|
|
'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', |
5823
|
|
|
'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', |
5824
|
|
|
'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp', |
5825
|
|
|
'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf', |
5826
|
|
|
'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', |
5827
|
|
|
'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', |
5828
|
|
|
'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', |
5829
|
|
|
'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', |
5830
|
|
|
'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', |
5831
|
|
|
'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', |
5832
|
|
|
'yt', 'yu', 'za', 'zm', 'zw'); |
5833
|
|
|
|
5834
|
|
|
// Schedule a background update, unless civilization has collapsed and/or we are having connectivity issues. |
5835
|
|
|
$schedule_update = empty($postapocalypticNightmare); |
5836
|
|
|
} |
5837
|
|
|
|
5838
|
|
|
// Get an optimized regex to match all the TLDs |
5839
|
|
|
$tld_regex = build_regex($tlds); |
5840
|
|
|
|
5841
|
|
|
// Remember the new regex in $modSettings |
5842
|
|
|
updateSettings(array('tld_regex' => $tld_regex)); |
5843
|
|
|
|
5844
|
|
|
// Schedule a background update if we need one |
5845
|
|
View Code Duplication |
if (!empty($schedule_update)) |
5846
|
|
|
{ |
5847
|
|
|
$smcFunc['db_insert']('insert', '{db_prefix}background_tasks', |
5848
|
|
|
array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'), |
5849
|
|
|
array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array() |
5850
|
|
|
); |
5851
|
|
|
} |
5852
|
|
|
|
5853
|
|
|
// Redundant repetition is redundant |
5854
|
|
|
$done = true; |
5855
|
|
|
} |
5856
|
|
|
|
5857
|
|
|
/** |
5858
|
|
|
* Creates optimized regular expressions from an array of strings. |
5859
|
|
|
* |
5860
|
|
|
* An optimized regex built using this function will be much faster than a simple regex built using |
5861
|
|
|
* `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster. |
5862
|
|
|
* |
5863
|
|
|
* However, the time required to build the optimized regex is approximately equal to the time it |
5864
|
|
|
* takes to execute the simple regex. Therefore, it is only worth calling this function if the |
5865
|
|
|
* resulting regex will be used more than once. |
5866
|
|
|
* |
5867
|
|
|
* Because PHP places an upper limit on the allowed length of a regex, very large arrays of $strings |
5868
|
|
|
* may not fit in a single regex. Normally, the excess strings will simply be dropped. However, if |
5869
|
|
|
* the $returnArray parameter is set to true, this function will build as many regexes as necessary |
5870
|
|
|
* to accomodate everything in $strings and return them in an array. You will need to iterate |
5871
|
|
|
* through all elements of the returned array in order to test all possible matches. |
5872
|
|
|
* |
5873
|
|
|
* @param array $strings An array of strings to make a regex for. |
5874
|
|
|
* @param string $delim An optional delimiter character to pass to preg_quote(). |
|
|
|
|
5875
|
|
|
* @param bool $returnArray If true, returns an array of regexes. |
5876
|
|
|
* @return string|array One or more regular expressions to match any of the input strings. |
5877
|
|
|
*/ |
5878
|
|
|
function build_regex($strings, $delim = null, $returnArray = false) |
5879
|
|
|
{ |
5880
|
|
|
global $smcFunc; |
|
|
|
|
5881
|
|
|
|
5882
|
|
|
// The mb_* functions are faster than the $smcFunc ones, but may not be available |
5883
|
|
|
if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr')) |
5884
|
|
|
{ |
5885
|
|
|
if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false) |
5886
|
|
|
{ |
5887
|
|
|
$current_encoding = mb_internal_encoding(); |
5888
|
|
|
mb_internal_encoding($string_encoding); |
5889
|
|
|
} |
5890
|
|
|
|
5891
|
|
|
$strlen = 'mb_strlen'; |
5892
|
|
|
$substr = 'mb_substr'; |
5893
|
|
|
} |
5894
|
|
|
else |
5895
|
|
|
{ |
5896
|
|
|
$strlen = $smcFunc['strlen']; |
5897
|
|
|
$substr = $smcFunc['substr']; |
5898
|
|
|
} |
5899
|
|
|
|
5900
|
|
|
// This recursive function creates the index array from the strings |
5901
|
|
|
$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index) |
5902
|
|
|
{ |
5903
|
|
|
static $depth = 0; |
5904
|
|
|
$depth++; |
5905
|
|
|
|
5906
|
|
|
$first = $substr($string, 0, 1); |
5907
|
|
|
|
5908
|
|
|
if (empty($index[$first])) |
5909
|
|
|
$index[$first] = array(); |
5910
|
|
|
|
5911
|
|
|
if ($strlen($string) > 1) |
5912
|
|
|
{ |
5913
|
|
|
// Sanity check on recursion |
5914
|
|
|
if ($depth > 99) |
5915
|
|
|
$index[$first][$substr($string, 1)] = ''; |
5916
|
|
|
|
5917
|
|
|
else |
5918
|
|
|
$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]); |
5919
|
|
|
} |
5920
|
|
|
else |
5921
|
|
|
$index[$first][''] = ''; |
5922
|
|
|
|
5923
|
|
|
$depth--; |
5924
|
|
|
return $index; |
5925
|
|
|
}; |
5926
|
|
|
|
5927
|
|
|
// This recursive function turns the index array into a regular expression |
5928
|
|
|
$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex) |
5929
|
|
|
{ |
5930
|
|
|
static $depth = 0; |
5931
|
|
|
$depth++; |
5932
|
|
|
|
5933
|
|
|
// Absolute max length for a regex is 32768, but we might need wiggle room |
5934
|
|
|
$max_length = 30000; |
5935
|
|
|
|
5936
|
|
|
$regex = array(); |
5937
|
|
|
$length = 0; |
5938
|
|
|
|
5939
|
|
|
foreach ($index as $key => $value) |
5940
|
|
|
{ |
5941
|
|
|
$key_regex = preg_quote($key, $delim); |
5942
|
|
|
$new_key = $key; |
5943
|
|
|
|
5944
|
|
|
if (empty($value)) |
5945
|
|
|
$sub_regex = ''; |
5946
|
|
|
else |
5947
|
|
|
{ |
5948
|
|
|
$sub_regex = $index_to_regex($value, $delim); |
5949
|
|
|
|
5950
|
|
|
if (count(array_keys($value)) == 1) |
5951
|
|
|
{ |
5952
|
|
|
$new_key_array = explode('(?'.'>', $sub_regex); |
5953
|
|
|
$new_key .= $new_key_array[0]; |
5954
|
|
|
} |
5955
|
|
|
else |
5956
|
|
|
$sub_regex = '(?'.'>' . $sub_regex . ')'; |
5957
|
|
|
} |
5958
|
|
|
|
5959
|
|
|
if ($depth > 1) |
5960
|
|
|
$regex[$new_key] = $key_regex . $sub_regex; |
5961
|
|
|
else |
5962
|
|
|
{ |
5963
|
|
|
if (($length += strlen($key_regex) + 1) < $max_length || empty($regex)) |
5964
|
|
|
{ |
5965
|
|
|
$regex[$new_key] = $key_regex . $sub_regex; |
5966
|
|
|
unset($index[$key]); |
5967
|
|
|
} |
5968
|
|
|
else |
5969
|
|
|
break; |
5970
|
|
|
} |
5971
|
|
|
} |
5972
|
|
|
|
5973
|
|
|
// Sort by key length and then alphabetically |
5974
|
|
|
uksort($regex, function($k1, $k2) use (&$strlen) { |
|
|
|
|
5975
|
|
|
$l1 = $strlen($k1); |
|
|
|
|
5976
|
|
|
$l2 = $strlen($k2); |
|
|
|
|
5977
|
|
|
|
5978
|
|
|
if ($l1 == $l2) |
5979
|
|
|
return strcmp($k1, $k2) > 0 ? 1 : -1; |
5980
|
|
|
else |
5981
|
|
|
return $l1 > $l2 ? -1 : 1; |
5982
|
|
|
}); |
5983
|
|
|
|
5984
|
|
|
$depth--; |
5985
|
|
|
return implode('|', $regex); |
5986
|
|
|
}; |
5987
|
|
|
|
5988
|
|
|
// Now that the functions are defined, let's do this thing |
5989
|
|
|
$index = array(); |
5990
|
|
|
$regex = ''; |
|
|
|
|
5991
|
|
|
|
5992
|
|
|
foreach ($strings as $string) |
5993
|
|
|
$index = $add_string_to_index($string, $index); |
5994
|
|
|
|
5995
|
|
|
if ($returnArray === true) |
5996
|
|
|
{ |
5997
|
|
|
$regex = array(); |
5998
|
|
|
while (!empty($index)) |
5999
|
|
|
$regex[] = '(?'.'>' . $index_to_regex($index, $delim) . ')'; |
6000
|
|
|
} |
6001
|
|
|
else |
6002
|
|
|
$regex = '(?'.'>' . $index_to_regex($index, $delim) . ')'; |
6003
|
|
|
|
6004
|
|
|
// Restore PHP's internal character encoding to whatever it was originally |
6005
|
|
|
if (!empty($current_encoding)) |
6006
|
|
|
mb_internal_encoding($current_encoding); |
6007
|
|
|
|
6008
|
|
|
return $regex; |
6009
|
|
|
} |
6010
|
|
|
|
6011
|
|
|
/** |
6012
|
|
|
* Check if the passed url has an SSL certificate. |
6013
|
|
|
* |
6014
|
|
|
* Returns true if a cert was found & false if not. |
6015
|
|
|
* @param string $url to check, in $boardurl format (no trailing slash). |
6016
|
|
|
*/ |
6017
|
|
|
function ssl_cert_found($url) { |
6018
|
|
|
|
6019
|
|
|
// Ask for the headers for the passed url, but via https... |
6020
|
|
|
$url = str_ireplace('http://', 'https://', $url) . '/'; |
6021
|
|
|
|
6022
|
|
|
$result = false; |
6023
|
|
|
$stream = stream_context_create (array("ssl" => array("capture_peer_cert" => true))); |
|
|
|
|
6024
|
|
|
$read = @fopen($url, "rb", false, $stream); |
|
|
|
|
6025
|
|
|
if ($read !== false) { |
6026
|
|
|
$cont = stream_context_get_params($read); |
6027
|
|
|
$result = isset($cont["options"]["ssl"]["peer_certificate"]) ? true : false; |
|
|
|
|
6028
|
|
|
} |
6029
|
|
|
return $result; |
6030
|
|
|
} |
6031
|
|
|
|
6032
|
|
|
/** |
6033
|
|
|
* Check if the passed url has a redirect to https:// by querying headers. |
6034
|
|
|
* |
6035
|
|
|
* Returns true if a redirect was found & false if not. |
6036
|
|
|
* Note that when force_ssl = 2, SMF issues its own redirect... So if this |
6037
|
|
|
* returns true, it may be caused by SMF, not necessarily an .htaccess redirect. |
6038
|
|
|
* @param string $url to check, in $boardurl format (no trailing slash). |
6039
|
|
|
*/ |
6040
|
|
|
function https_redirect_active($url) { |
6041
|
|
|
|
6042
|
|
|
// Ask for the headers for the passed url, but via http... |
6043
|
|
|
// Need to add the trailing slash, or it puts it there & thinks there's a redirect when there isn't... |
6044
|
|
|
$url = str_ireplace('https://', 'http://', $url) . '/'; |
6045
|
|
|
$headers = @get_headers($url); |
|
|
|
|
6046
|
|
|
if ($headers === false) |
6047
|
|
|
return false; |
6048
|
|
|
|
6049
|
|
|
// Now to see if it came back https... |
6050
|
|
|
// First check for a redirect status code in first row (301, 302, 307) |
6051
|
|
|
if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false) |
6052
|
|
|
return false; |
6053
|
|
|
|
6054
|
|
|
// Search for the location entry to confirm https |
6055
|
|
|
$result = false; |
6056
|
|
|
foreach ($headers as $header) { |
6057
|
|
|
if (stristr($header, 'Location: https://') !== false) { |
6058
|
|
|
$result = true; |
6059
|
|
|
break; |
6060
|
|
|
} |
6061
|
|
|
} |
6062
|
|
|
return $result; |
6063
|
|
|
} |
6064
|
|
|
|
6065
|
|
|
/** |
6066
|
|
|
* Build query_wanna_see_board and query_see_board for a userid |
6067
|
|
|
* |
6068
|
|
|
* Returns array with keys query_wanna_see_board and query_see_board |
6069
|
|
|
* @param int $userid of the user |
6070
|
|
|
*/ |
6071
|
|
|
function build_query_board($userid) |
6072
|
|
|
{ |
6073
|
|
|
global $user_info, $modSettings, $smcFunc; |
|
|
|
|
6074
|
|
|
|
6075
|
|
|
$query_part = array(); |
6076
|
|
|
$groups = array(); |
|
|
|
|
6077
|
|
|
$is_admin = false; |
|
|
|
|
6078
|
|
|
$deny_boards_access = !empty($modSettings['deny_boards_access']) ? $modSettings['deny_boards_access'] : null; |
6079
|
|
|
$mod_cache; |
|
|
|
|
6080
|
|
|
$ignoreboards; |
|
|
|
|
6081
|
|
|
|
6082
|
|
|
if ($user_info['id'] == $userid) |
6083
|
|
|
{ |
6084
|
|
|
$groups = $user_info['groups']; |
6085
|
|
|
$is_admin = $user_info['is_admin']; |
6086
|
|
|
$mod_cache = !empty($user_info['mod_cache']) ? $user_info['mod_cache'] : null; |
6087
|
|
|
$ignoreboards = !empty($user_info['ignoreboards']) ? $user_info['ignoreboards'] : null; |
6088
|
|
|
} |
6089
|
|
|
else |
6090
|
|
|
{ |
6091
|
|
|
$request = $smcFunc['db_query']('', ' |
6092
|
|
|
SELECT mem.ignore_boards, mem.id_group, mem.additional_groups, mem.id_post_group |
6093
|
|
|
FROM {db_prefix}members AS mem |
6094
|
|
|
WHERE mem.id_member = {int:id_member} |
6095
|
|
|
LIMIT 1', |
6096
|
|
|
array( |
6097
|
|
|
'id_member' => $userid, |
6098
|
|
|
) |
6099
|
|
|
); |
6100
|
|
|
|
6101
|
|
|
$row = $smcFunc['db_fetch_assoc']($request); |
6102
|
|
|
|
6103
|
|
View Code Duplication |
if (empty($row['additional_groups'])) |
6104
|
|
|
$groups = array($row['id_group'], $user_settings['id_post_group']); |
|
|
|
|
6105
|
|
|
else |
6106
|
|
|
$groups = array_merge( |
6107
|
|
|
array($row['id_group'], $user_settings['id_post_group']), |
6108
|
|
|
explode(',', $row['additional_groups']) |
6109
|
|
|
); |
6110
|
|
|
|
6111
|
|
|
// Because history has proven that it is possible for groups to go bad - clean up in case. |
6112
|
|
|
foreach ($groups as $k => $v) |
6113
|
|
|
$groups[$k] = (int) $v; |
6114
|
|
|
|
6115
|
|
|
$is_admin = in_array(1, $groups); |
6116
|
|
|
|
6117
|
|
|
$ignoreboards = !empty($row['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $row['ignore_boards']) : array(); |
6118
|
|
|
|
6119
|
|
|
// What boards are they the moderator of? |
6120
|
|
|
$boards_mod = array(); |
6121
|
|
|
|
6122
|
|
|
$request = $smcFunc['db_query']('', ' |
6123
|
|
|
SELECT id_board |
6124
|
|
|
FROM {db_prefix}moderators |
6125
|
|
|
WHERE id_member = {int:current_member}', |
6126
|
|
|
array( |
6127
|
|
|
'current_member' => $userid, |
6128
|
|
|
) |
6129
|
|
|
); |
6130
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
6131
|
|
|
$boards_mod[] = $row['id_board']; |
6132
|
|
|
$smcFunc['db_free_result']($request); |
6133
|
|
|
|
6134
|
|
|
// Can any of the groups they're in moderate any of the boards? |
6135
|
|
|
$request = $smcFunc['db_query']('', ' |
6136
|
|
|
SELECT id_board |
6137
|
|
|
FROM {db_prefix}moderator_groups |
6138
|
|
|
WHERE id_group IN({array_int:groups})', |
6139
|
|
|
array( |
6140
|
|
|
'groups' => $groups, |
6141
|
|
|
) |
6142
|
|
|
); |
6143
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
6144
|
|
|
$boards_mod[] = $row['id_board']; |
6145
|
|
|
$smcFunc['db_free_result']($request); |
6146
|
|
|
|
6147
|
|
|
// Just in case we've got duplicates here... |
6148
|
|
|
$boards_mod = array_unique($boards_mod); |
6149
|
|
|
|
6150
|
|
|
$mod_cache['mq'] = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; |
|
|
|
|
6151
|
|
|
} |
6152
|
|
|
|
6153
|
|
|
// Just build this here, it makes it easier to change/use - administrators can see all boards. |
6154
|
|
|
if ($is_admin) |
6155
|
|
|
$query_part['query_see_board'] = '1=1'; |
6156
|
|
|
// Otherwise just the groups in $user_info['groups']. |
6157
|
|
|
else |
6158
|
|
|
$query_part['query_see_board'] = '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $groups) . ', b.member_groups) != 0)' . (!empty($deny_boards_access) ? ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $groups) . ', b.deny_member_groups) = 0)' : '') . (isset($mod_cache) ? ' OR ' . $mod_cache['mq'] : '') . ')'; |
6159
|
|
|
|
6160
|
|
|
// Build the list of boards they WANT to see. |
6161
|
|
|
// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also |
6162
|
|
|
|
6163
|
|
|
// If they aren't ignoring any boards then they want to see all the boards they can see |
6164
|
|
|
if (empty($ignoreboards)) |
6165
|
|
|
$query_part['query_wanna_see_board'] = $query_part['query_see_board']; |
6166
|
|
|
// Ok I guess they don't want to see all the boards |
6167
|
|
|
else |
6168
|
|
|
$query_part['query_wanna_see_board'] = '(' . $query_part['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $ignoreboards) . '))'; |
6169
|
|
|
|
6170
|
|
|
return $query_part; |
6171
|
|
|
} |
6172
|
|
|
|
6173
|
|
|
?> |
Instead of relying on
global
state, we recommend one of these alternatives:1. Pass all data via parameters
2. Create a class that maintains your state