annotate forum/Sources/Subs.php @ 85:6d7b61434be7 website

Add a copy of this here, just in case!
author Chris Cannam
date Mon, 20 Jan 2014 11:02:36 +0000
parents e3e11437ecea
children
rev   line source
Chris@76 1 <?php
Chris@76 2
Chris@76 3 /**
Chris@76 4 * Simple Machines Forum (SMF)
Chris@76 5 *
Chris@76 6 * @package SMF
Chris@76 7 * @author Simple Machines http://www.simplemachines.org
Chris@76 8 * @copyright 2011 Simple Machines
Chris@76 9 * @license http://www.simplemachines.org/about/smf/license.php BSD
Chris@76 10 *
Chris@76 11 * @version 2.0
Chris@76 12 */
Chris@76 13
Chris@76 14 if (!defined('SMF'))
Chris@76 15 die('Hacking attempt...');
Chris@76 16
Chris@76 17 /* This file has all the main functions in it that relate to, well,
Chris@76 18 everything. It provides all of the following functions:
Chris@76 19
Chris@76 20 void updateStats(string statistic, string condition = '1')
Chris@76 21 - statistic can be 'member', 'message', 'topic', or 'postgroups'.
Chris@76 22 - parameter1 and parameter2 are optional, and are used to update only
Chris@76 23 those stats that need updating.
Chris@76 24 - the 'member' statistic updates the latest member, the total member
Chris@76 25 count, and the number of unapproved members.
Chris@76 26 - 'member' also only counts approved members when approval is on, but
Chris@76 27 is much more efficient with it off.
Chris@76 28 - updating 'message' changes the total number of messages, and the
Chris@76 29 highest message id by id_msg - which can be parameters 1 and 2,
Chris@76 30 respectively.
Chris@76 31 - 'topic' updates the total number of topics, or if parameter1 is true
Chris@76 32 simply increments them.
Chris@76 33 - the 'postgroups' case updates those members who match condition's
Chris@76 34 post-based membergroups in the database (restricted by parameter1).
Chris@76 35
Chris@76 36 void updateMemberData(int id_member, array data)
Chris@76 37 - updates the columns in the members table.
Chris@76 38 - id_member is either an int or an array of ints to be updated.
Chris@76 39 - data is an associative array of the columns to be updated and their
Chris@76 40 respective values.
Chris@76 41 - any string values updated should be quoted and slashed.
Chris@76 42 - the value of any column can be '+' or '-', which mean 'increment'
Chris@76 43 and decrement, respectively.
Chris@76 44 - if the member's post number is updated, updates their post groups.
Chris@76 45 - this function should be used whenever member data needs to be
Chris@76 46 updated in place of an UPDATE query.
Chris@76 47
Chris@76 48 void updateSettings(array changeArray, use_update = false)
Chris@76 49 - updates both the settings table and $modSettings array.
Chris@76 50 - all of changeArray's indexes and values are assumed to have escaped
Chris@76 51 apostrophes (')!
Chris@76 52 - if a variable is already set to what you want to change it to, that
Chris@76 53 variable will be skipped over; it would be unnecessary to reset.
Chris@76 54 - if use_update is true, UPDATEs will be used instead of REPLACE.
Chris@76 55 - when use_update is true, the value can be true or false to increment
Chris@76 56 or decrement it, respectively.
Chris@76 57
Chris@76 58 string constructPageIndex(string base_url, int &start, int max_value,
Chris@76 59 int num_per_page, bool compact_start = false)
Chris@76 60 - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
Chris@76 61 - compact_start caused it to use "url.page" instead of
Chris@76 62 "url;start=page".
Chris@76 63 - handles any wireless settings (adding special things to URLs.)
Chris@76 64 - very importantly, cleans up the start value passed, and forces it to
Chris@76 65 be a multiple of num_per_page.
Chris@76 66 - also checks that start is not more than max_value.
Chris@76 67 - base_url should be the URL without any start parameter on it.
Chris@76 68 - uses the compactTopicPagesEnable and compactTopicPagesContiguous
Chris@76 69 settings to decide how to display the menu.
Chris@76 70 - an example is available near the function definition.
Chris@76 71
Chris@76 72 string comma_format(float number)
Chris@76 73 - formats a number to display in the style of the admins' choosing.
Chris@76 74 - uses the format of number_format to decide how to format the number.
Chris@76 75 - for example, it might display "1 234,50".
Chris@76 76 - caches the formatting data from the setting for optimization.
Chris@76 77
Chris@76 78 string timeformat(int time, bool show_today = true, string offset_type = false)
Chris@76 79 - returns a pretty formated version of time based on the user's format
Chris@76 80 in $user_info['time_format'].
Chris@76 81 - applies all necessary time offsets to the timestamp, unless offset_type
Chris@76 82 is set.
Chris@76 83 - if todayMod is set and show_today was not not specified or true, an
Chris@76 84 alternate format string is used to show the date with something to
Chris@76 85 show it is "today" or "yesterday".
Chris@76 86 - performs localization (more than just strftime would do alone.)
Chris@76 87
Chris@76 88 string un_htmlspecialchars(string text)
Chris@76 89 - removes the base entities (&lt;, &quot;, etc.) from text.
Chris@76 90 - should be used instead of html_entity_decode for PHP version
Chris@76 91 compatibility reasons.
Chris@76 92 - additionally converts &nbsp; and &#039;.
Chris@76 93 - returns the string without entities.
Chris@76 94
Chris@76 95 string shorten_subject(string regular_subject, int length)
Chris@76 96 - shortens a subject so that it is either shorter than length, or that
Chris@76 97 length plus an ellipsis.
Chris@76 98 - respects internationalization characters and entities as one character.
Chris@76 99 - avoids trailing entities.
Chris@76 100 - returns the shortened string.
Chris@76 101
Chris@76 102 int forum_time(bool use_user_offset = true)
Chris@76 103 - returns the current time with offsets.
Chris@76 104 - always applies the offset in the time_offset setting.
Chris@76 105 - if use_user_offset is true, applies the user's offset as well.
Chris@76 106 - returns seconds since the unix epoch.
Chris@76 107
Chris@76 108 array permute(array input)
Chris@76 109 - calculates all the possible permutations (orders) of array.
Chris@76 110 - should not be called on huge arrays (bigger than like 10 elements.)
Chris@76 111 - returns an array containing each permutation.
Chris@76 112
Chris@76 113 string parse_bbc(string message, bool smileys = true, string cache_id = '', array parse_tags = null)
Chris@76 114 - this very hefty function parses bbc in message.
Chris@76 115 - only parses bbc tags which are not disabled in disabledBBC.
Chris@76 116 - also handles basic HTML, if enablePostHTML is on.
Chris@76 117 - caches the from/to replace regular expressions so as not to reload
Chris@76 118 them every time a string is parsed.
Chris@76 119 - only parses smileys if smileys is true.
Chris@76 120 - does nothing if the enableBBC setting is off.
Chris@76 121 - applies the fixLongWords magic if the setting is set to on.
Chris@76 122 - uses the cache_id as a unique identifier to facilitate any caching
Chris@76 123 it may do.
Chris@76 124 - returns the modified message.
Chris@76 125
Chris@76 126 void parsesmileys(string &message)
Chris@76 127 - the smiley parsing function which makes pretty faces appear :).
Chris@76 128 - if custom smiley sets are turned off by smiley_enable, the default
Chris@76 129 set of smileys will be used.
Chris@76 130 - these are specifically not parsed in code tags [url=mailto:Dad@blah.com]
Chris@76 131 - caches the smileys from the database or array in memory.
Chris@76 132 - doesn't return anything, but rather modifies message directly.
Chris@76 133
Chris@76 134 string highlight_php_code(string code)
Chris@76 135 - Uses PHP's highlight_string() to highlight PHP syntax
Chris@76 136 - does special handling to keep the tabs in the code available.
Chris@76 137 - used to parse PHP code from inside [code] and [php] tags.
Chris@76 138 - returns the code with highlighted HTML.
Chris@76 139
Chris@76 140 void writeLog(bool force = false)
Chris@76 141 // !!!
Chris@76 142
Chris@76 143 void redirectexit(string setLocation = '', bool use_refresh = false)
Chris@76 144 // !!!
Chris@76 145
Chris@76 146 void obExit(bool do_header = true, bool do_footer = do_header)
Chris@76 147 // !!!
Chris@76 148
Chris@76 149 int logAction($action, $extra = array())
Chris@76 150 // !!!
Chris@76 151
Chris@76 152 void trackStats($stats = array())
Chris@76 153 - caches statistics changes, and flushes them if you pass nothing.
Chris@76 154 - if '+' is used as a value, it will be incremented.
Chris@76 155 - does not actually commit the changes until the end of the page view.
Chris@76 156 - depends on the trackStats setting.
Chris@76 157
Chris@76 158 void spamProtection(string error_type)
Chris@76 159 - attempts to protect from spammed messages and the like.
Chris@76 160 - takes a $txt index. (not an actual string.)
Chris@76 161 - time taken depends on error_type - generally uses the modSetting.
Chris@76 162
Chris@76 163 array url_image_size(string url)
Chris@76 164 - uses getimagesize() to determine the size of a file.
Chris@76 165 - attempts to connect to the server first so it won't time out.
Chris@76 166 - returns false on failure, otherwise the output of getimagesize().
Chris@76 167
Chris@76 168 void determineTopicClass(array &topic_context)
Chris@76 169 // !!!
Chris@76 170
Chris@76 171 void setupThemeContext(bool force_reload = false)
Chris@76 172 // !!!
Chris@76 173
Chris@76 174 void template_rawdata()
Chris@76 175 // !!!
Chris@76 176
Chris@76 177 void template_header()
Chris@76 178 // !!!
Chris@76 179
Chris@76 180 void theme_copyright(bool get_it = false)
Chris@76 181 // !!!
Chris@76 182
Chris@76 183 void template_footer()
Chris@76 184 // !!!
Chris@76 185
Chris@76 186 void db_debug_junk()
Chris@76 187 // !!!
Chris@76 188
Chris@76 189 void getAttachmentFilename(string filename, int id_attach, bool new = true)
Chris@76 190 // !!!
Chris@76 191
Chris@76 192 array ip2range(string $fullip)
Chris@76 193 - converts a given IP string to an array.
Chris@76 194 - internal function used to convert a user-readable format to
Chris@76 195 a format suitable for the database.
Chris@76 196 - returns 'unknown' if the ip in the input was '255.255.255.255'.
Chris@76 197
Chris@76 198 string host_from_ip(string ip_address)
Chris@76 199 // !!!
Chris@76 200
Chris@76 201 string create_button(string filename, string alt, string label, bool custom = '')
Chris@76 202 // !!!
Chris@76 203
Chris@76 204 void clean_cache(type = '')
Chris@76 205 - clean the cache directory ($cachedir, if any and in use)
Chris@76 206 - it may only remove the files of a certain type
Chris@76 207 (if the $type parameter is given)
Chris@76 208
Chris@76 209 array call_integration_hook(string hook, array parameters = array())
Chris@76 210 - calls all functions of the given hook.
Chris@76 211 - supports static class method calls.
Chris@76 212 - returns the results of the functions as an array.
Chris@76 213
Chris@76 214 void add_integration_function(string hook, string function, bool permanent = true)
Chris@76 215 - adds the given function to the given hook.
Chris@76 216 - does nothing if the functions is already added.
Chris@76 217 - if permanent parameter is true, updates the value in settings table.
Chris@76 218
Chris@76 219 void remove_integration_function(string hook, string function)
Chris@76 220 - removes the given function from the given hook.
Chris@76 221 - does nothing if the functions is not available.
Chris@76 222 */
Chris@76 223
Chris@76 224 // Update some basic statistics...
Chris@76 225 function updateStats($type, $parameter1 = null, $parameter2 = null)
Chris@76 226 {
Chris@76 227 global $sourcedir, $modSettings, $smcFunc;
Chris@76 228
Chris@76 229 switch ($type)
Chris@76 230 {
Chris@76 231 case 'member':
Chris@76 232 $changes = array(
Chris@76 233 'memberlist_updated' => time(),
Chris@76 234 );
Chris@76 235
Chris@76 236 // #1 latest member ID, #2 the real name for a new registration.
Chris@76 237 if (is_numeric($parameter1))
Chris@76 238 {
Chris@76 239 $changes['latestMember'] = $parameter1;
Chris@76 240 $changes['latestRealName'] = $parameter2;
Chris@76 241
Chris@76 242 updateSettings(array('totalMembers' => true), true);
Chris@76 243 }
Chris@76 244
Chris@76 245 // We need to calculate the totals.
Chris@76 246 else
Chris@76 247 {
Chris@76 248 // Update the latest activated member (highest id_member) and count.
Chris@76 249 $result = $smcFunc['db_query']('', '
Chris@76 250 SELECT COUNT(*), MAX(id_member)
Chris@76 251 FROM {db_prefix}members
Chris@76 252 WHERE is_activated = {int:is_activated}',
Chris@76 253 array(
Chris@76 254 'is_activated' => 1,
Chris@76 255 )
Chris@76 256 );
Chris@76 257 list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result);
Chris@76 258 $smcFunc['db_free_result']($result);
Chris@76 259
Chris@76 260 // Get the latest activated member's display name.
Chris@76 261 $result = $smcFunc['db_query']('', '
Chris@76 262 SELECT real_name
Chris@76 263 FROM {db_prefix}members
Chris@76 264 WHERE id_member = {int:id_member}
Chris@76 265 LIMIT 1',
Chris@76 266 array(
Chris@76 267 'id_member' => (int) $changes['latestMember'],
Chris@76 268 )
Chris@76 269 );
Chris@76 270 list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result);
Chris@76 271 $smcFunc['db_free_result']($result);
Chris@76 272
Chris@76 273 // Are we using registration approval?
Chris@76 274 if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']))
Chris@76 275 {
Chris@76 276 // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission.
Chris@76 277 $result = $smcFunc['db_query']('', '
Chris@76 278 SELECT COUNT(*)
Chris@76 279 FROM {db_prefix}members
Chris@76 280 WHERE is_activated IN ({array_int:activation_status})',
Chris@76 281 array(
Chris@76 282 'activation_status' => array(3, 4),
Chris@76 283 )
Chris@76 284 );
Chris@76 285 list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result);
Chris@76 286 $smcFunc['db_free_result']($result);
Chris@76 287 }
Chris@76 288 }
Chris@76 289
Chris@76 290 updateSettings($changes);
Chris@76 291 break;
Chris@76 292
Chris@76 293 case 'message':
Chris@76 294 if ($parameter1 === true && $parameter2 !== null)
Chris@76 295 updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true);
Chris@76 296 else
Chris@76 297 {
Chris@76 298 // SUM and MAX on a smaller table is better for InnoDB tables.
Chris@76 299 $result = $smcFunc['db_query']('', '
Chris@76 300 SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
Chris@76 301 FROM {db_prefix}boards
Chris@76 302 WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
Chris@76 303 AND id_board != {int:recycle_board}' : ''),
Chris@76 304 array(
Chris@76 305 'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
Chris@76 306 'blank_redirect' => '',
Chris@76 307 )
Chris@76 308 );
Chris@76 309 $row = $smcFunc['db_fetch_assoc']($result);
Chris@76 310 $smcFunc['db_free_result']($result);
Chris@76 311
Chris@76 312 updateSettings(array(
Chris@76 313 'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'],
Chris@76 314 'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id']
Chris@76 315 ));
Chris@76 316 }
Chris@76 317 break;
Chris@76 318
Chris@76 319 case 'subject':
Chris@76 320 // Remove the previous subject (if any).
Chris@76 321 $smcFunc['db_query']('', '
Chris@76 322 DELETE FROM {db_prefix}log_search_subjects
Chris@76 323 WHERE id_topic = {int:id_topic}',
Chris@76 324 array(
Chris@76 325 'id_topic' => (int) $parameter1,
Chris@76 326 )
Chris@76 327 );
Chris@76 328
Chris@76 329 // Insert the new subject.
Chris@76 330 if ($parameter2 !== null)
Chris@76 331 {
Chris@76 332 $parameter1 = (int) $parameter1;
Chris@76 333 $parameter2 = text2words($parameter2);
Chris@76 334
Chris@76 335 $inserts = array();
Chris@76 336 foreach ($parameter2 as $word)
Chris@76 337 $inserts[] = array($word, $parameter1);
Chris@76 338
Chris@76 339 if (!empty($inserts))
Chris@76 340 $smcFunc['db_insert']('ignore',
Chris@76 341 '{db_prefix}log_search_subjects',
Chris@76 342 array('word' => 'string', 'id_topic' => 'int'),
Chris@76 343 $inserts,
Chris@76 344 array('word', 'id_topic')
Chris@76 345 );
Chris@76 346 }
Chris@76 347 break;
Chris@76 348
Chris@76 349 case 'topic':
Chris@76 350 if ($parameter1 === true)
Chris@76 351 updateSettings(array('totalTopics' => true), true);
Chris@76 352 else
Chris@76 353 {
Chris@76 354 // Get the number of topics - a SUM is better for InnoDB tables.
Chris@76 355 // We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
Chris@76 356 $result = $smcFunc['db_query']('', '
Chris@76 357 SELECT SUM(num_topics + unapproved_topics) AS total_topics
Chris@76 358 FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
Chris@76 359 WHERE id_board != {int:recycle_board}' : ''),
Chris@76 360 array(
Chris@76 361 'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
Chris@76 362 )
Chris@76 363 );
Chris@76 364 $row = $smcFunc['db_fetch_assoc']($result);
Chris@76 365 $smcFunc['db_free_result']($result);
Chris@76 366
Chris@76 367 updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
Chris@76 368 }
Chris@76 369 break;
Chris@76 370
Chris@76 371 case 'postgroups':
Chris@76 372 // Parameter two is the updated columns: we should check to see if we base groups off any of these.
Chris@76 373 if ($parameter2 !== null && !in_array('posts', $parameter2))
Chris@76 374 return;
Chris@76 375
Chris@76 376 if (($postgroups = cache_get_data('updateStats:postgroups', 360)) == null)
Chris@76 377 {
Chris@76 378 // Fetch the postgroups!
Chris@76 379 $request = $smcFunc['db_query']('', '
Chris@76 380 SELECT id_group, min_posts
Chris@76 381 FROM {db_prefix}membergroups
Chris@76 382 WHERE min_posts != {int:min_posts}',
Chris@76 383 array(
Chris@76 384 'min_posts' => -1,
Chris@76 385 )
Chris@76 386 );
Chris@76 387 $postgroups = array();
Chris@76 388 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 389 $postgroups[$row['id_group']] = $row['min_posts'];
Chris@76 390 $smcFunc['db_free_result']($request);
Chris@76 391
Chris@76 392 // Sort them this way because if it's done with MySQL it causes a filesort :(.
Chris@76 393 arsort($postgroups);
Chris@76 394
Chris@76 395 cache_put_data('updateStats:postgroups', $postgroups, 360);
Chris@76 396 }
Chris@76 397
Chris@76 398 // Oh great, they've screwed their post groups.
Chris@76 399 if (empty($postgroups))
Chris@76 400 return;
Chris@76 401
Chris@76 402 // Set all membergroups from most posts to least posts.
Chris@76 403 $conditions = '';
Chris@76 404 foreach ($postgroups as $id => $min_posts)
Chris@76 405 {
Chris@76 406 $conditions .= '
Chris@76 407 WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
Chris@76 408 $lastMin = $min_posts;
Chris@76 409 }
Chris@76 410
Chris@76 411 // A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
Chris@76 412 $smcFunc['db_query']('', '
Chris@76 413 UPDATE {db_prefix}members
Chris@76 414 SET id_post_group = CASE ' . $conditions . '
Chris@76 415 ELSE 0
Chris@76 416 END' . ($parameter1 != null ? '
Chris@76 417 WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''),
Chris@76 418 array(
Chris@76 419 'members' => $parameter1,
Chris@76 420 )
Chris@76 421 );
Chris@76 422 break;
Chris@76 423
Chris@76 424 default:
Chris@76 425 trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
Chris@76 426 }
Chris@76 427 }
Chris@76 428
Chris@76 429 // Assumes the data has been htmlspecialchar'd.
Chris@76 430 function updateMemberData($members, $data)
Chris@76 431 {
Chris@76 432 global $modSettings, $user_info, $smcFunc;
Chris@76 433
Chris@76 434 $parameters = array();
Chris@76 435 if (is_array($members))
Chris@76 436 {
Chris@76 437 $condition = 'id_member IN ({array_int:members})';
Chris@76 438 $parameters['members'] = $members;
Chris@76 439 }
Chris@76 440 elseif ($members === null)
Chris@76 441 $condition = '1=1';
Chris@76 442 else
Chris@76 443 {
Chris@76 444 $condition = 'id_member = {int:member}';
Chris@76 445 $parameters['member'] = $members;
Chris@76 446 }
Chris@76 447
Chris@76 448 if (!empty($modSettings['integrate_change_member_data']))
Chris@76 449 {
Chris@76 450 // Only a few member variables are really interesting for integration.
Chris@76 451 $integration_vars = array(
Chris@76 452 'member_name',
Chris@76 453 'real_name',
Chris@76 454 'email_address',
Chris@76 455 'id_group',
Chris@76 456 'gender',
Chris@76 457 'birthdate',
Chris@76 458 'website_title',
Chris@76 459 'website_url',
Chris@76 460 'location',
Chris@76 461 'hide_email',
Chris@76 462 'time_format',
Chris@76 463 'time_offset',
Chris@76 464 'avatar',
Chris@76 465 'lngfile',
Chris@76 466 );
Chris@76 467 $vars_to_integrate = array_intersect($integration_vars, array_keys($data));
Chris@76 468
Chris@76 469 // Only proceed if there are any variables left to call the integration function.
Chris@76 470 if (count($vars_to_integrate) != 0)
Chris@76 471 {
Chris@76 472 // Fetch a list of member_names if necessary
Chris@76 473 if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
Chris@76 474 $member_names = array($user_info['username']);
Chris@76 475 else
Chris@76 476 {
Chris@76 477 $member_names = array();
Chris@76 478 $request = $smcFunc['db_query']('', '
Chris@76 479 SELECT member_name
Chris@76 480 FROM {db_prefix}members
Chris@76 481 WHERE ' . $condition,
Chris@76 482 $parameters
Chris@76 483 );
Chris@76 484 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 485 $member_names[] = $row['member_name'];
Chris@76 486 $smcFunc['db_free_result']($request);
Chris@76 487 }
Chris@76 488
Chris@76 489 if (!empty($member_names))
Chris@76 490 foreach ($vars_to_integrate as $var)
Chris@76 491 call_integration_hook('integrate_change_member_data', array($member_names, $var, $data[$var]));
Chris@76 492 }
Chris@76 493 }
Chris@76 494
Chris@76 495 // Everything is assumed to be a string unless it's in the below.
Chris@76 496 $knownInts = array(
Chris@76 497 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
Chris@76 498 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad',
Chris@76 499 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
Chris@76 500 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
Chris@76 501 );
Chris@76 502 $knownFloats = array(
Chris@76 503 'time_offset',
Chris@76 504 );
Chris@76 505
Chris@76 506 $setString = '';
Chris@76 507 foreach ($data as $var => $val)
Chris@76 508 {
Chris@76 509 $type = 'string';
Chris@76 510 if (in_array($var, $knownInts))
Chris@76 511 $type = 'int';
Chris@76 512 elseif (in_array($var, $knownFloats))
Chris@76 513 $type = 'float';
Chris@76 514 elseif ($var == 'birthdate')
Chris@76 515 $type = 'date';
Chris@76 516
Chris@76 517 // Doing an increment?
Chris@76 518 if ($type == 'int' && ($val === '+' || $val === '-'))
Chris@76 519 {
Chris@76 520 $val = $var . ' ' . $val . ' 1';
Chris@76 521 $type = 'raw';
Chris@76 522 }
Chris@76 523
Chris@76 524 // Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
Chris@76 525 if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
Chris@76 526 {
Chris@76 527 if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
Chris@76 528 {
Chris@76 529 if ($match[1] != '+ ')
Chris@76 530 $val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
Chris@76 531 $type = 'raw';
Chris@76 532 }
Chris@76 533 }
Chris@76 534
Chris@76 535 $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
Chris@76 536 $parameters['p_' . $var] = $val;
Chris@76 537 }
Chris@76 538
Chris@76 539 $smcFunc['db_query']('', '
Chris@76 540 UPDATE {db_prefix}members
Chris@76 541 SET' . substr($setString, 0, -1) . '
Chris@76 542 WHERE ' . $condition,
Chris@76 543 $parameters
Chris@76 544 );
Chris@76 545
Chris@76 546 updateStats('postgroups', $members, array_keys($data));
Chris@76 547
Chris@76 548 // Clear any caching?
Chris@76 549 if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
Chris@76 550 {
Chris@76 551 if (!is_array($members))
Chris@76 552 $members = array($members);
Chris@76 553
Chris@76 554 foreach ($members as $member)
Chris@76 555 {
Chris@76 556 if ($modSettings['cache_enable'] >= 3)
Chris@76 557 {
Chris@76 558 cache_put_data('member_data-profile-' . $member, null, 120);
Chris@76 559 cache_put_data('member_data-normal-' . $member, null, 120);
Chris@76 560 cache_put_data('member_data-minimal-' . $member, null, 120);
Chris@76 561 }
Chris@76 562 cache_put_data('user_settings-' . $member, null, 60);
Chris@76 563 }
Chris@76 564 }
Chris@76 565 }
Chris@76 566
Chris@76 567 // Updates the settings table as well as $modSettings... only does one at a time if $update is true.
Chris@76 568 function updateSettings($changeArray, $update = false, $debug = false)
Chris@76 569 {
Chris@76 570 global $modSettings, $smcFunc;
Chris@76 571
Chris@76 572 if (empty($changeArray) || !is_array($changeArray))
Chris@76 573 return;
Chris@76 574
Chris@76 575 // In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
Chris@76 576 if ($update)
Chris@76 577 {
Chris@76 578 foreach ($changeArray as $variable => $value)
Chris@76 579 {
Chris@76 580 $smcFunc['db_query']('', '
Chris@76 581 UPDATE {db_prefix}settings
Chris@76 582 SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
Chris@76 583 WHERE variable = {string:variable}',
Chris@76 584 array(
Chris@76 585 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
Chris@76 586 'variable' => $variable,
Chris@76 587 )
Chris@76 588 );
Chris@76 589 $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
Chris@76 590 }
Chris@76 591
Chris@76 592 // Clean out the cache and make sure the cobwebs are gone too.
Chris@76 593 cache_put_data('modSettings', null, 90);
Chris@76 594
Chris@76 595 return;
Chris@76 596 }
Chris@76 597
Chris@76 598 $replaceArray = array();
Chris@76 599 foreach ($changeArray as $variable => $value)
Chris@76 600 {
Chris@76 601 // Don't bother if it's already like that ;).
Chris@76 602 if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
Chris@76 603 continue;
Chris@76 604 // If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
Chris@76 605 elseif (!isset($modSettings[$variable]) && empty($value))
Chris@76 606 continue;
Chris@76 607
Chris@76 608 $replaceArray[] = array($variable, $value);
Chris@76 609
Chris@76 610 $modSettings[$variable] = $value;
Chris@76 611 }
Chris@76 612
Chris@76 613 if (empty($replaceArray))
Chris@76 614 return;
Chris@76 615
Chris@76 616 $smcFunc['db_insert']('replace',
Chris@76 617 '{db_prefix}settings',
Chris@76 618 array('variable' => 'string-255', 'value' => 'string-65534'),
Chris@76 619 $replaceArray,
Chris@76 620 array('variable')
Chris@76 621 );
Chris@76 622
Chris@76 623 // Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
Chris@76 624 cache_put_data('modSettings', null, 90);
Chris@76 625 }
Chris@76 626
Chris@76 627 // Constructs a page list.
Chris@76 628 // $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
Chris@76 629 function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false)
Chris@76 630 {
Chris@76 631 global $modSettings;
Chris@76 632
Chris@76 633 // Save whether $start was less than 0 or not.
Chris@76 634 $start = (int) $start;
Chris@76 635 $start_invalid = $start < 0;
Chris@76 636
Chris@76 637 // Make sure $start is a proper variable - not less than 0.
Chris@76 638 if ($start_invalid)
Chris@76 639 $start = 0;
Chris@76 640 // Not greater than the upper bound.
Chris@76 641 elseif ($start >= $max_value)
Chris@76 642 $start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
Chris@76 643 // And it has to be a multiple of $num_per_page!
Chris@76 644 else
Chris@76 645 $start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
Chris@76 646
Chris@76 647 // Wireless will need the protocol on the URL somewhere.
Chris@76 648 if (WIRELESS)
Chris@76 649 $base_url .= ';' . WIRELESS_PROTOCOL;
Chris@76 650
Chris@76 651 $base_link = '<a class="navPages" href="' . ($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d') . '">%2$s</a> ';
Chris@76 652
Chris@76 653 // Compact pages is off or on?
Chris@76 654 if (empty($modSettings['compactTopicPagesEnable']))
Chris@76 655 {
Chris@76 656 // Show the left arrow.
Chris@76 657 $pageindex = $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, '&#171;');
Chris@76 658
Chris@76 659 // Show all the pages.
Chris@76 660 $display_page = 1;
Chris@76 661 for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
Chris@76 662 $pageindex .= $start == $counter && !$start_invalid ? '<strong>' . $display_page++ . '</strong> ' : sprintf($base_link, $counter, $display_page++);
Chris@76 663
Chris@76 664 // Show the right arrow.
Chris@76 665 $display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
Chris@76 666 if ($start != $counter - $max_value && !$start_invalid)
Chris@76 667 $pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, '&#187;');
Chris@76 668 }
Chris@76 669 else
Chris@76 670 {
Chris@76 671 // If they didn't enter an odd value, pretend they did.
Chris@76 672 $PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
Chris@76 673
Chris@76 674 // Show the first page. (>1< ... 6 7 [8] 9 10 ... 15)
Chris@76 675 if ($start > $num_per_page * $PageContiguous)
Chris@76 676 $pageindex = sprintf($base_link, 0, '1');
Chris@76 677 else
Chris@76 678 $pageindex = '';
Chris@76 679
Chris@76 680 // Show the ... after the first page. (1 >...< 6 7 [8] 9 10 ... 15)
Chris@76 681 if ($start > $num_per_page * ($PageContiguous + 1))
Chris@76 682 $pageindex .= '<span style="font-weight: bold;" onclick="' . htmlspecialchars('expandPages(this, ' . JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')) . ', ' . $num_per_page . ', ' . ($start - $num_per_page * $PageContiguous) . ', ' . $num_per_page . ');') . '" onmouseover="this.style.cursor = \'pointer\';"> ... </span>';
Chris@76 683
Chris@76 684 // Show the pages before the current one. (1 ... >6 7< [8] 9 10 ... 15)
Chris@76 685 for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
Chris@76 686 if ($start >= $num_per_page * $nCont)
Chris@76 687 {
Chris@76 688 $tmpStart = $start - $num_per_page * $nCont;
Chris@76 689 $pageindex.= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
Chris@76 690 }
Chris@76 691
Chris@76 692 // Show the current page. (1 ... 6 7 >[8]< 9 10 ... 15)
Chris@76 693 if (!$start_invalid)
Chris@76 694 $pageindex .= '[<strong>' . ($start / $num_per_page + 1) . '</strong>] ';
Chris@76 695 else
Chris@76 696 $pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
Chris@76 697
Chris@76 698 // Show the pages after the current one... (1 ... 6 7 [8] >9 10< ... 15)
Chris@76 699 $tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
Chris@76 700 for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
Chris@76 701 if ($start + $num_per_page * $nCont <= $tmpMaxPages)
Chris@76 702 {
Chris@76 703 $tmpStart = $start + $num_per_page * $nCont;
Chris@76 704 $pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
Chris@76 705 }
Chris@76 706
Chris@76 707 // Show the '...' part near the end. (1 ... 6 7 [8] 9 10 >...< 15)
Chris@76 708 if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
Chris@76 709 $pageindex .= '<span style="font-weight: bold;" onclick="expandPages(this, \'' . ($flexible_start ? strtr($base_url, array('\'' => '\\\'')) : strtr($base_url, array('%' => '%%', '\'' => '\\\'')) . ';start=%1$d') . '\', ' . ($start + $num_per_page * ($PageContiguous + 1)) . ', ' . $tmpMaxPages . ', ' . $num_per_page . ');" onmouseover="this.style.cursor=\'pointer\';"> ... </span>';
Chris@76 710
Chris@76 711 // Show the last number in the list. (1 ... 6 7 [8] 9 10 ... >15<)
Chris@76 712 if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
Chris@76 713 $pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
Chris@76 714 }
Chris@76 715
Chris@76 716 return $pageindex;
Chris@76 717 }
Chris@76 718
Chris@76 719 // Formats a number to display in the style of the admin's choosing.
Chris@76 720 function comma_format($number, $override_decimal_count = false)
Chris@76 721 {
Chris@76 722 global $txt;
Chris@76 723 static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
Chris@76 724
Chris@76 725 // !!! Should, perhaps, this just be handled in the language files, and not a mod setting?
Chris@76 726 // (French uses 1 234,00 for example... what about a multilingual forum?)
Chris@76 727
Chris@76 728 // Cache these values...
Chris@76 729 if ($decimal_separator === null)
Chris@76 730 {
Chris@76 731 // Not set for whatever reason?
Chris@76 732 if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
Chris@76 733 return $number;
Chris@76 734
Chris@76 735 // Cache these each load...
Chris@76 736 $thousands_separator = $matches[1];
Chris@76 737 $decimal_separator = $matches[2];
Chris@76 738 $decimal_count = strlen($matches[3]);
Chris@76 739 }
Chris@76 740
Chris@76 741 // Format the string with our friend, number_format.
Chris@76 742 return number_format($number, is_float($number) ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
Chris@76 743 }
Chris@76 744
Chris@76 745 // Format a time to make it look purdy.
Chris@76 746 function timeformat($log_time, $show_today = true, $offset_type = false)
Chris@76 747 {
Chris@76 748 global $context, $user_info, $txt, $modSettings, $smcFunc;
Chris@76 749 static $non_twelve_hour;
Chris@76 750
Chris@76 751 // Offset the time.
Chris@76 752 if (!$offset_type)
Chris@76 753 $time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
Chris@76 754 // Just the forum offset?
Chris@76 755 elseif ($offset_type == 'forum')
Chris@76 756 $time = $log_time + $modSettings['time_offset'] * 3600;
Chris@76 757 else
Chris@76 758 $time = $log_time;
Chris@76 759
Chris@76 760 // We can't have a negative date (on Windows, at least.)
Chris@76 761 if ($log_time < 0)
Chris@76 762 $log_time = 0;
Chris@76 763
Chris@76 764 // Today and Yesterday?
Chris@76 765 if ($modSettings['todayMod'] >= 1 && $show_today === true)
Chris@76 766 {
Chris@76 767 // Get the current time.
Chris@76 768 $nowtime = forum_time();
Chris@76 769
Chris@76 770 $then = @getdate($time);
Chris@76 771 $now = @getdate($nowtime);
Chris@76 772
Chris@76 773 // Try to make something of a time format string...
Chris@76 774 $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
Chris@76 775 if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
Chris@76 776 {
Chris@76 777 $h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
Chris@76 778 $today_fmt = $h . ':%M' . $s . ' %p';
Chris@76 779 }
Chris@76 780 else
Chris@76 781 $today_fmt = '%H:%M' . $s;
Chris@76 782
Chris@76 783 // Same day of the year, same year.... Today!
Chris@76 784 if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
Chris@76 785 return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type);
Chris@76 786
Chris@76 787 // 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...
Chris@76 788 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))
Chris@76 789 return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type);
Chris@76 790 }
Chris@76 791
Chris@76 792 $str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
Chris@76 793
Chris@76 794 if (setlocale(LC_TIME, $txt['lang_locale']))
Chris@76 795 {
Chris@76 796 if (!isset($non_twelve_hour))
Chris@76 797 $non_twelve_hour = trim(strftime('%p')) === '';
Chris@76 798 if ($non_twelve_hour && strpos($str, '%p') !== false)
Chris@76 799 $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
Chris@76 800
Chris@76 801 foreach (array('%a', '%A', '%b', '%B') as $token)
Chris@76 802 if (strpos($str, $token) !== false)
Chris@76 803 $str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? $smcFunc['ucwords'](strftime($token, $time)) : strftime($token, $time), $str);
Chris@76 804 }
Chris@76 805 else
Chris@76 806 {
Chris@76 807 // Do-it-yourself time localization. Fun.
Chris@76 808 foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
Chris@76 809 if (strpos($str, $token) !== false)
Chris@76 810 $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
Chris@76 811
Chris@76 812 if (strpos($str, '%p') !== false)
Chris@76 813 $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
Chris@76 814 }
Chris@76 815
Chris@76 816 // Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
Chris@76 817 if ($context['server']['is_windows'] && strpos($str, '%e') !== false)
Chris@76 818 $str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
Chris@76 819
Chris@76 820 // Format any other characters..
Chris@76 821 return strftime($str, $time);
Chris@76 822 }
Chris@76 823
Chris@76 824 // Removes special entities from strings. Compatibility...
Chris@76 825 function un_htmlspecialchars($string)
Chris@76 826 {
Chris@76 827 static $translation;
Chris@76 828
Chris@76 829 if (!isset($translation))
Chris@76 830 $translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array('&#039;' => '\'', '&nbsp;' => ' ');
Chris@76 831
Chris@76 832 return strtr($string, $translation);
Chris@76 833 }
Chris@76 834
Chris@76 835 // Shorten a subject + internationalization concerns.
Chris@76 836 function shorten_subject($subject, $len)
Chris@76 837 {
Chris@76 838 global $smcFunc;
Chris@76 839
Chris@76 840 // It was already short enough!
Chris@76 841 if ($smcFunc['strlen']($subject) <= $len)
Chris@76 842 return $subject;
Chris@76 843
Chris@76 844 // Shorten it by the length it was too long, and strip off junk from the end.
Chris@76 845 return $smcFunc['substr']($subject, 0, $len) . '...';
Chris@76 846 }
Chris@76 847
Chris@76 848 // The current time with offset.
Chris@76 849 function forum_time($use_user_offset = true, $timestamp = null)
Chris@76 850 {
Chris@76 851 global $user_info, $modSettings;
Chris@76 852
Chris@76 853 if ($timestamp === null)
Chris@76 854 $timestamp = time();
Chris@76 855 elseif ($timestamp == 0)
Chris@76 856 return 0;
Chris@76 857
Chris@76 858 return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
Chris@76 859 }
Chris@76 860
Chris@76 861 // This gets all possible permutations of an array.
Chris@76 862 function permute($array)
Chris@76 863 {
Chris@76 864 $orders = array($array);
Chris@76 865
Chris@76 866 $n = count($array);
Chris@76 867 $p = range(0, $n);
Chris@76 868 for ($i = 1; $i < $n; null)
Chris@76 869 {
Chris@76 870 $p[$i]--;
Chris@76 871 $j = $i % 2 != 0 ? $p[$i] : 0;
Chris@76 872
Chris@76 873 $temp = $array[$i];
Chris@76 874 $array[$i] = $array[$j];
Chris@76 875 $array[$j] = $temp;
Chris@76 876
Chris@76 877 for ($i = 1; $p[$i] == 0; $i++)
Chris@76 878 $p[$i] = 1;
Chris@76 879
Chris@76 880 $orders[] = $array;
Chris@76 881 }
Chris@76 882
Chris@76 883 return $orders;
Chris@76 884 }
Chris@76 885
Chris@76 886 // Parse bulletin board code in a string, as well as smileys optionally.
Chris@76 887 function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
Chris@76 888 {
Chris@76 889 global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc;
Chris@76 890 static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
Chris@76 891 static $disabled;
Chris@76 892
Chris@76 893 // Don't waste cycles
Chris@76 894 if ($message === '')
Chris@76 895 return '';
Chris@76 896
Chris@76 897 // Never show smileys for wireless clients. More bytes, can't see it anyway :P.
Chris@76 898 if (WIRELESS)
Chris@76 899 $smileys = false;
Chris@76 900 elseif ($smileys !== null && ($smileys == '1' || $smileys == '0'))
Chris@76 901 $smileys = (bool) $smileys;
Chris@76 902
Chris@76 903 if (empty($modSettings['enableBBC']) && $message !== false)
Chris@76 904 {
Chris@76 905 if ($smileys === true)
Chris@76 906 parsesmileys($message);
Chris@76 907
Chris@76 908 return $message;
Chris@76 909 }
Chris@76 910
Chris@76 911 // Just in case it wasn't determined yet whether UTF-8 is enabled.
Chris@76 912 if (!isset($context['utf8']))
Chris@76 913 $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
Chris@76 914
Chris@76 915 // If we are not doing every tag then we don't cache this run.
Chris@76 916 if (!empty($parse_tags) && !empty($bbc_codes))
Chris@76 917 {
Chris@76 918 $temp_bbc = $bbc_codes;
Chris@76 919 $bbc_codes = array();
Chris@76 920 }
Chris@76 921
Chris@76 922 // Sift out the bbc for a performance improvement.
Chris@76 923 if (empty($bbc_codes) || $message === false || !empty($parse_tags))
Chris@76 924 {
Chris@76 925 if (!empty($modSettings['disabledBBC']))
Chris@76 926 {
Chris@76 927 $temp = explode(',', strtolower($modSettings['disabledBBC']));
Chris@76 928
Chris@76 929 foreach ($temp as $tag)
Chris@76 930 $disabled[trim($tag)] = true;
Chris@76 931 }
Chris@76 932
Chris@76 933 if (empty($modSettings['enableEmbeddedFlash']))
Chris@76 934 $disabled['flash'] = true;
Chris@76 935
Chris@76 936 /* The following bbc are formatted as an array, with keys as follows:
Chris@76 937
Chris@76 938 tag: the tag's name - should be lowercase!
Chris@76 939
Chris@76 940 type: one of...
Chris@76 941 - (missing): [tag]parsed content[/tag]
Chris@76 942 - unparsed_equals: [tag=xyz]parsed content[/tag]
Chris@76 943 - parsed_equals: [tag=parsed data]parsed content[/tag]
Chris@76 944 - unparsed_content: [tag]unparsed content[/tag]
Chris@76 945 - closed: [tag], [tag/], [tag /]
Chris@76 946 - unparsed_commas: [tag=1,2,3]parsed content[/tag]
Chris@76 947 - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
Chris@76 948 - unparsed_equals_content: [tag=...]unparsed content[/tag]
Chris@76 949
Chris@76 950 parameters: an optional array of parameters, for the form
Chris@76 951 [tag abc=123]content[/tag]. The array is an associative array
Chris@76 952 where the keys are the parameter names, and the values are an
Chris@76 953 array which may contain the following:
Chris@76 954 - match: a regular expression to validate and match the value.
Chris@76 955 - quoted: true if the value should be quoted.
Chris@76 956 - validate: callback to evaluate on the data, which is $data.
Chris@76 957 - value: a string in which to replace $1 with the data.
Chris@76 958 either it or validate may be used, not both.
Chris@76 959 - optional: true if the parameter is optional.
Chris@76 960
Chris@76 961 test: a regular expression to test immediately after the tag's
Chris@76 962 '=', ' ' or ']'. Typically, should have a \] at the end.
Chris@76 963 Optional.
Chris@76 964
Chris@76 965 content: only available for unparsed_content, closed,
Chris@76 966 unparsed_commas_content, and unparsed_equals_content.
Chris@76 967 $1 is replaced with the content of the tag. Parameters
Chris@76 968 are replaced in the form {param}. For unparsed_commas_content,
Chris@76 969 $2, $3, ..., $n are replaced.
Chris@76 970
Chris@76 971 before: only when content is not used, to go before any
Chris@76 972 content. For unparsed_equals, $1 is replaced with the value.
Chris@76 973 For unparsed_commas, $1, $2, ..., $n are replaced.
Chris@76 974
Chris@76 975 after: similar to before in every way, except that it is used
Chris@76 976 when the tag is closed.
Chris@76 977
Chris@76 978 disabled_content: used in place of content when the tag is
Chris@76 979 disabled. For closed, default is '', otherwise it is '$1' if
Chris@76 980 block_level is false, '<div>$1</div>' elsewise.
Chris@76 981
Chris@76 982 disabled_before: used in place of before when disabled. Defaults
Chris@76 983 to '<div>' if block_level, '' if not.
Chris@76 984
Chris@76 985 disabled_after: used in place of after when disabled. Defaults
Chris@76 986 to '</div>' if block_level, '' if not.
Chris@76 987
Chris@76 988 block_level: set to true the tag is a "block level" tag, similar
Chris@76 989 to HTML. Block level tags cannot be nested inside tags that are
Chris@76 990 not block level, and will not be implicitly closed as easily.
Chris@76 991 One break following a block level tag may also be removed.
Chris@76 992
Chris@76 993 trim: if set, and 'inside' whitespace after the begin tag will be
Chris@76 994 removed. If set to 'outside', whitespace after the end tag will
Chris@76 995 meet the same fate.
Chris@76 996
Chris@76 997 validate: except when type is missing or 'closed', a callback to
Chris@76 998 validate the data as $data. Depending on the tag's type, $data
Chris@76 999 may be a string or an array of strings (corresponding to the
Chris@76 1000 replacement.)
Chris@76 1001
Chris@76 1002 quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
Chris@76 1003 may be not set, 'optional', or 'required' corresponding to if
Chris@76 1004 the content may be quoted. This allows the parser to read
Chris@76 1005 [tag="abc]def[esdf]"] properly.
Chris@76 1006
Chris@76 1007 require_parents: an array of tag names, or not set. If set, the
Chris@76 1008 enclosing tag *must* be one of the listed tags, or parsing won't
Chris@76 1009 occur.
Chris@76 1010
Chris@76 1011 require_children: similar to require_parents, if set children
Chris@76 1012 won't be parsed if they are not in the list.
Chris@76 1013
Chris@76 1014 disallow_children: similar to, but very different from,
Chris@76 1015 require_children, if it is set the listed tags will not be
Chris@76 1016 parsed inside the tag.
Chris@76 1017
Chris@76 1018 parsed_tags_allowed: an array restricting what BBC can be in the
Chris@76 1019 parsed_equals parameter, if desired.
Chris@76 1020 */
Chris@76 1021
Chris@76 1022 $codes = array(
Chris@76 1023 array(
Chris@76 1024 'tag' => 'abbr',
Chris@76 1025 'type' => 'unparsed_equals',
Chris@76 1026 'before' => '<abbr title="$1">',
Chris@76 1027 'after' => '</abbr>',
Chris@76 1028 'quoted' => 'optional',
Chris@76 1029 'disabled_after' => ' ($1)',
Chris@76 1030 ),
Chris@76 1031 array(
Chris@76 1032 'tag' => 'acronym',
Chris@76 1033 'type' => 'unparsed_equals',
Chris@76 1034 'before' => '<acronym title="$1">',
Chris@76 1035 'after' => '</acronym>',
Chris@76 1036 'quoted' => 'optional',
Chris@76 1037 'disabled_after' => ' ($1)',
Chris@76 1038 ),
Chris@76 1039 array(
Chris@76 1040 'tag' => 'anchor',
Chris@76 1041 'type' => 'unparsed_equals',
Chris@76 1042 'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
Chris@76 1043 'before' => '<span id="post_$1">',
Chris@76 1044 'after' => '</span>',
Chris@76 1045 ),
Chris@76 1046 array(
Chris@76 1047 'tag' => 'b',
Chris@76 1048 'before' => '<strong>',
Chris@76 1049 'after' => '</strong>',
Chris@76 1050 ),
Chris@76 1051 array(
Chris@76 1052 'tag' => 'bdo',
Chris@76 1053 'type' => 'unparsed_equals',
Chris@76 1054 'before' => '<bdo dir="$1">',
Chris@76 1055 'after' => '</bdo>',
Chris@76 1056 'test' => '(rtl|ltr)\]',
Chris@76 1057 'block_level' => true,
Chris@76 1058 ),
Chris@76 1059 array(
Chris@76 1060 'tag' => 'black',
Chris@76 1061 'before' => '<span style="color: black;" class="bbc_color">',
Chris@76 1062 'after' => '</span>',
Chris@76 1063 ),
Chris@76 1064 array(
Chris@76 1065 'tag' => 'blue',
Chris@76 1066 'before' => '<span style="color: blue;" class="bbc_color">',
Chris@76 1067 'after' => '</span>',
Chris@76 1068 ),
Chris@76 1069 array(
Chris@76 1070 'tag' => 'br',
Chris@76 1071 'type' => 'closed',
Chris@76 1072 'content' => '<br />',
Chris@76 1073 ),
Chris@76 1074 array(
Chris@76 1075 'tag' => 'center',
Chris@76 1076 'before' => '<div align="center">',
Chris@76 1077 'after' => '</div>',
Chris@76 1078 'block_level' => true,
Chris@76 1079 ),
Chris@76 1080 array(
Chris@76 1081 'tag' => 'code',
Chris@76 1082 'type' => 'unparsed_content',
Chris@76 1083 'content' => '<div class="codeheader">' . $txt['code'] . ': <a href="javascript:void(0);" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''),
Chris@76 1084 // !!! Maybe this can be simplified?
Chris@76 1085 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
Chris@76 1086 global $context;
Chris@76 1087
Chris@76 1088 if (!isset($disabled[\'code\']))
Chris@76 1089 {
Chris@76 1090 $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
Chris@76 1091
Chris@76 1092 for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
Chris@76 1093 {
Chris@76 1094 // Do PHP code coloring?
Chris@76 1095 if ($php_parts[$php_i] != \'&lt;?php\')
Chris@76 1096 continue;
Chris@76 1097
Chris@76 1098 $php_string = \'\';
Chris@76 1099 while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
Chris@76 1100 {
Chris@76 1101 $php_string .= $php_parts[$php_i];
Chris@76 1102 $php_parts[$php_i++] = \'\';
Chris@76 1103 }
Chris@76 1104 $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
Chris@76 1105 }
Chris@76 1106
Chris@76 1107 // Fix the PHP code stuff...
Chris@76 1108 $data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));
Chris@76 1109
Chris@76 1110 // Older browsers are annoying, aren\'t they?
Chris@76 1111 if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
Chris@76 1112 $data = str_replace("\t", "<pre style=\"display: inline;\">\t</pre>", $data);
Chris@76 1113 else
Chris@76 1114 $data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
Chris@76 1115
Chris@76 1116 // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
Chris@76 1117 if ($context[\'browser\'][\'is_opera\'])
Chris@76 1118 $data .= \'&nbsp;\';
Chris@76 1119 }'),
Chris@76 1120 'block_level' => true,
Chris@76 1121 ),
Chris@76 1122 array(
Chris@76 1123 'tag' => 'code',
Chris@76 1124 'type' => 'unparsed_equals_content',
Chris@76 1125 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2) <a href="#" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''),
Chris@76 1126 // !!! Maybe this can be simplified?
Chris@76 1127 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
Chris@76 1128 global $context;
Chris@76 1129
Chris@76 1130 if (!isset($disabled[\'code\']))
Chris@76 1131 {
Chris@76 1132 $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
Chris@76 1133
Chris@76 1134 for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
Chris@76 1135 {
Chris@76 1136 // Do PHP code coloring?
Chris@76 1137 if ($php_parts[$php_i] != \'&lt;?php\')
Chris@76 1138 continue;
Chris@76 1139
Chris@76 1140 $php_string = \'\';
Chris@76 1141 while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
Chris@76 1142 {
Chris@76 1143 $php_string .= $php_parts[$php_i];
Chris@76 1144 $php_parts[$php_i++] = \'\';
Chris@76 1145 }
Chris@76 1146 $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
Chris@76 1147 }
Chris@76 1148
Chris@76 1149 // Fix the PHP code stuff...
Chris@76 1150 $data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));
Chris@76 1151
Chris@76 1152 // Older browsers are annoying, aren\'t they?
Chris@76 1153 if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
Chris@76 1154 $data[0] = str_replace("\t", "<pre style=\"display: inline;\">\t</pre>", $data[0]);
Chris@76 1155 else
Chris@76 1156 $data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
Chris@76 1157
Chris@76 1158 // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
Chris@76 1159 if ($context[\'browser\'][\'is_opera\'])
Chris@76 1160 $data[0] .= \'&nbsp;\';
Chris@76 1161 }'),
Chris@76 1162 'block_level' => true,
Chris@76 1163 ),
Chris@76 1164 array(
Chris@76 1165 'tag' => 'color',
Chris@76 1166 'type' => 'unparsed_equals',
Chris@76 1167 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]',
Chris@76 1168 'before' => '<span style="color: $1;" class="bbc_color">',
Chris@76 1169 'after' => '</span>',
Chris@76 1170 ),
Chris@76 1171 array(
Chris@76 1172 'tag' => 'email',
Chris@76 1173 'type' => 'unparsed_content',
Chris@76 1174 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
Chris@76 1175 // !!! Should this respect guest_hideContacts?
Chris@76 1176 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'<br />\' => \'\'));'),
Chris@76 1177 ),
Chris@76 1178 array(
Chris@76 1179 'tag' => 'email',
Chris@76 1180 'type' => 'unparsed_equals',
Chris@76 1181 'before' => '<a href="mailto:$1" class="bbc_email">',
Chris@76 1182 'after' => '</a>',
Chris@76 1183 // !!! Should this respect guest_hideContacts?
Chris@76 1184 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
Chris@76 1185 'disabled_after' => ' ($1)',
Chris@76 1186 ),
Chris@76 1187 array(
Chris@76 1188 'tag' => 'flash',
Chris@76 1189 'type' => 'unparsed_commas_content',
Chris@76 1190 'test' => '\d+,\d+\]',
Chris@76 1191 'content' => ($context['browser']['is_ie'] && !$context['browser']['is_mac_ie'] ? '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$2" height="$3"><param name="movie" value="$1" /><param name="play" value="true" /><param name="loop" value="true" /><param name="quality" value="high" /><param name="AllowScriptAccess" value="never" /><embed src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed></object>' : '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed>'),
Chris@76 1192 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1193 if (isset($disabled[\'url\']))
Chris@76 1194 $tag[\'content\'] = \'$1\';
Chris@76 1195 elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0)
Chris@76 1196 $data[0] = \'http://\' . $data[0];
Chris@76 1197 '),
Chris@76 1198 'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
Chris@76 1199 ),
Chris@76 1200 array(
Chris@76 1201 'tag' => 'font',
Chris@76 1202 'type' => 'unparsed_equals',
Chris@76 1203 'test' => '[A-Za-z0-9_,\-\s]+?\]',
Chris@76 1204 'before' => '<span style="font-family: $1;" class="bbc_font">',
Chris@76 1205 'after' => '</span>',
Chris@76 1206 ),
Chris@76 1207 array(
Chris@76 1208 'tag' => 'ftp',
Chris@76 1209 'type' => 'unparsed_content',
Chris@76 1210 'content' => '<a href="$1" class="bbc_ftp new_win" target="_blank">$1</a>',
Chris@76 1211 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1212 $data = strtr($data, array(\'<br />\' => \'\'));
Chris@76 1213 if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
Chris@76 1214 $data = \'ftp://\' . $data;
Chris@76 1215 '),
Chris@76 1216 ),
Chris@76 1217 array(
Chris@76 1218 'tag' => 'ftp',
Chris@76 1219 'type' => 'unparsed_equals',
Chris@76 1220 'before' => '<a href="$1" class="bbc_ftp new_win" target="_blank">',
Chris@76 1221 'after' => '</a>',
Chris@76 1222 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1223 if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
Chris@76 1224 $data = \'ftp://\' . $data;
Chris@76 1225 '),
Chris@76 1226 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
Chris@76 1227 'disabled_after' => ' ($1)',
Chris@76 1228 ),
Chris@76 1229 array(
Chris@76 1230 'tag' => 'glow',
Chris@76 1231 'type' => 'unparsed_commas',
Chris@76 1232 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]',
Chris@76 1233 'before' => $context['browser']['is_ie'] ? '<table border="0" cellpadding="0" cellspacing="0" style="display: inline; vertical-align: middle; font: inherit;"><tr><td style="filter: Glow(color=$1, strength=$2); font: inherit;">' : '<span style="text-shadow: $1 1px 1px 1px">',
Chris@76 1234 'after' => $context['browser']['is_ie'] ? '</td></tr></table> ' : '</span>',
Chris@76 1235 ),
Chris@76 1236 array(
Chris@76 1237 'tag' => 'green',
Chris@76 1238 'before' => '<span style="color: green;" class="bbc_color">',
Chris@76 1239 'after' => '</span>',
Chris@76 1240 ),
Chris@76 1241 array(
Chris@76 1242 'tag' => 'html',
Chris@76 1243 'type' => 'unparsed_content',
Chris@76 1244 'content' => '$1',
Chris@76 1245 'block_level' => true,
Chris@76 1246 'disabled_content' => '$1',
Chris@76 1247 ),
Chris@76 1248 array(
Chris@76 1249 'tag' => 'hr',
Chris@76 1250 'type' => 'closed',
Chris@76 1251 'content' => '<hr />',
Chris@76 1252 'block_level' => true,
Chris@76 1253 ),
Chris@76 1254 array(
Chris@76 1255 'tag' => 'i',
Chris@76 1256 'before' => '<em>',
Chris@76 1257 'after' => '</em>',
Chris@76 1258 ),
Chris@76 1259 array(
Chris@76 1260 'tag' => 'img',
Chris@76 1261 'type' => 'unparsed_content',
Chris@76 1262 'parameters' => array(
Chris@76 1263 'alt' => array('optional' => true),
Chris@76 1264 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
Chris@76 1265 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
Chris@76 1266 ),
Chris@76 1267 'content' => '<img src="$1" alt="{alt}"{width}{height} class="bbc_img resized" />',
Chris@76 1268 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1269 $data = strtr($data, array(\'<br />\' => \'\'));
Chris@76 1270 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1271 $data = \'http://\' . $data;
Chris@76 1272 '),
Chris@76 1273 'disabled_content' => '($1)',
Chris@76 1274 ),
Chris@76 1275 array(
Chris@76 1276 'tag' => 'img',
Chris@76 1277 'type' => 'unparsed_content',
Chris@76 1278 'content' => '<img src="$1" alt="" class="bbc_img" />',
Chris@76 1279 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1280 $data = strtr($data, array(\'<br />\' => \'\'));
Chris@76 1281 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1282 $data = \'http://\' . $data;
Chris@76 1283 '),
Chris@76 1284 'disabled_content' => '($1)',
Chris@76 1285 ),
Chris@76 1286 array(
Chris@76 1287 'tag' => 'iurl',
Chris@76 1288 'type' => 'unparsed_content',
Chris@76 1289 'content' => '<a href="$1" class="bbc_link">$1</a>',
Chris@76 1290 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1291 $data = strtr($data, array(\'<br />\' => \'\'));
Chris@76 1292 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1293 $data = \'http://\' . $data;
Chris@76 1294 '),
Chris@76 1295 ),
Chris@76 1296 array(
Chris@76 1297 'tag' => 'iurl',
Chris@76 1298 'type' => 'unparsed_equals',
Chris@76 1299 'before' => '<a href="$1" class="bbc_link">',
Chris@76 1300 'after' => '</a>',
Chris@76 1301 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1302 if (substr($data, 0, 1) == \'#\')
Chris@76 1303 $data = \'#post_\' . substr($data, 1);
Chris@76 1304 elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1305 $data = \'http://\' . $data;
Chris@76 1306 '),
Chris@76 1307 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
Chris@76 1308 'disabled_after' => ' ($1)',
Chris@76 1309 ),
Chris@76 1310 array(
Chris@76 1311 'tag' => 'left',
Chris@76 1312 'before' => '<div style="text-align: left;">',
Chris@76 1313 'after' => '</div>',
Chris@76 1314 'block_level' => true,
Chris@76 1315 ),
Chris@76 1316 array(
Chris@76 1317 'tag' => 'li',
Chris@76 1318 'before' => '<li>',
Chris@76 1319 'after' => '</li>',
Chris@76 1320 'trim' => 'outside',
Chris@76 1321 'require_parents' => array('list'),
Chris@76 1322 'block_level' => true,
Chris@76 1323 'disabled_before' => '',
Chris@76 1324 'disabled_after' => '<br />',
Chris@76 1325 ),
Chris@76 1326 array(
Chris@76 1327 'tag' => 'list',
Chris@76 1328 'before' => '<ul class="bbc_list">',
Chris@76 1329 'after' => '</ul>',
Chris@76 1330 'trim' => 'inside',
Chris@76 1331 'require_children' => array('li', 'list'),
Chris@76 1332 'block_level' => true,
Chris@76 1333 ),
Chris@76 1334 array(
Chris@76 1335 'tag' => 'list',
Chris@76 1336 'parameters' => array(
Chris@76 1337 'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
Chris@76 1338 ),
Chris@76 1339 'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
Chris@76 1340 'after' => '</ul>',
Chris@76 1341 'trim' => 'inside',
Chris@76 1342 'require_children' => array('li'),
Chris@76 1343 'block_level' => true,
Chris@76 1344 ),
Chris@76 1345 array(
Chris@76 1346 'tag' => 'ltr',
Chris@76 1347 'before' => '<div dir="ltr">',
Chris@76 1348 'after' => '</div>',
Chris@76 1349 'block_level' => true,
Chris@76 1350 ),
Chris@76 1351 array(
Chris@76 1352 'tag' => 'me',
Chris@76 1353 'type' => 'unparsed_equals',
Chris@76 1354 'before' => '<div class="meaction">* $1 ',
Chris@76 1355 'after' => '</div>',
Chris@76 1356 'quoted' => 'optional',
Chris@76 1357 'block_level' => true,
Chris@76 1358 'disabled_before' => '/me ',
Chris@76 1359 'disabled_after' => '<br />',
Chris@76 1360 ),
Chris@76 1361 array(
Chris@76 1362 'tag' => 'move',
Chris@76 1363 'before' => '<marquee>',
Chris@76 1364 'after' => '</marquee>',
Chris@76 1365 'block_level' => true,
Chris@76 1366 'disallow_children' => array('move'),
Chris@76 1367 ),
Chris@76 1368 array(
Chris@76 1369 'tag' => 'nobbc',
Chris@76 1370 'type' => 'unparsed_content',
Chris@76 1371 'content' => '$1',
Chris@76 1372 ),
Chris@76 1373 array(
Chris@76 1374 'tag' => 'php',
Chris@76 1375 'type' => 'unparsed_content',
Chris@76 1376 'content' => '<span class="phpcode">$1</span>',
Chris@76 1377 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', '
Chris@76 1378 if (!isset($disabled[\'php\']))
Chris@76 1379 {
Chris@76 1380 $add_begin = substr(trim($data), 0, 5) != \'&lt;?\';
Chris@76 1381 $data = highlight_php_code($add_begin ? \'&lt;?php \' . $data . \'?&gt;\' : $data);
Chris@76 1382 if ($add_begin)
Chris@76 1383 $data = preg_replace(array(\'~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~\', \'~\?&gt;((?:</(font|span)>)*)$~\'), \'$1\', $data, 2);
Chris@76 1384 }'),
Chris@76 1385 'block_level' => false,
Chris@76 1386 'disabled_content' => '$1',
Chris@76 1387 ),
Chris@76 1388 array(
Chris@76 1389 'tag' => 'pre',
Chris@76 1390 'before' => '<pre>',
Chris@76 1391 'after' => '</pre>',
Chris@76 1392 ),
Chris@76 1393 array(
Chris@76 1394 'tag' => 'quote',
Chris@76 1395 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote'] . '</div></div><blockquote>',
Chris@76 1396 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
Chris@76 1397 'block_level' => true,
Chris@76 1398 ),
Chris@76 1399 array(
Chris@76 1400 'tag' => 'quote',
Chris@76 1401 'parameters' => array(
Chris@76 1402 'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
Chris@76 1403 ),
Chris@76 1404 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
Chris@76 1405 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
Chris@76 1406 'block_level' => true,
Chris@76 1407 ),
Chris@76 1408 array(
Chris@76 1409 'tag' => 'quote',
Chris@76 1410 'type' => 'parsed_equals',
Chris@76 1411 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': $1</div></div><blockquote>',
Chris@76 1412 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
Chris@76 1413 'quoted' => 'optional',
Chris@76 1414 // Don't allow everything to be embedded with the author name.
Chris@76 1415 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
Chris@76 1416 'block_level' => true,
Chris@76 1417 ),
Chris@76 1418 array(
Chris@76 1419 'tag' => 'quote',
Chris@76 1420 'parameters' => array(
Chris@76 1421 'author' => array('match' => '([^<>]{1,192}?)'),
Chris@76 1422 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'),
Chris@76 1423 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
Chris@76 1424 ),
Chris@76 1425 'before' => '<div class="quoteheader"><div class="topslice_quote"><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></div></div><blockquote>',
Chris@76 1426 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
Chris@76 1427 'block_level' => true,
Chris@76 1428 ),
Chris@76 1429 array(
Chris@76 1430 'tag' => 'quote',
Chris@76 1431 'parameters' => array(
Chris@76 1432 'author' => array('match' => '(.{1,192}?)'),
Chris@76 1433 ),
Chris@76 1434 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
Chris@76 1435 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
Chris@76 1436 'block_level' => true,
Chris@76 1437 ),
Chris@76 1438 array(
Chris@76 1439 'tag' => 'red',
Chris@76 1440 'before' => '<span style="color: red;" class="bbc_color">',
Chris@76 1441 'after' => '</span>',
Chris@76 1442 ),
Chris@76 1443 array(
Chris@76 1444 'tag' => 'right',
Chris@76 1445 'before' => '<div style="text-align: right;">',
Chris@76 1446 'after' => '</div>',
Chris@76 1447 'block_level' => true,
Chris@76 1448 ),
Chris@76 1449 array(
Chris@76 1450 'tag' => 'rtl',
Chris@76 1451 'before' => '<div dir="rtl">',
Chris@76 1452 'after' => '</div>',
Chris@76 1453 'block_level' => true,
Chris@76 1454 ),
Chris@76 1455 array(
Chris@76 1456 'tag' => 's',
Chris@76 1457 'before' => '<del>',
Chris@76 1458 'after' => '</del>',
Chris@76 1459 ),
Chris@76 1460 array(
Chris@76 1461 'tag' => 'shadow',
Chris@76 1462 'type' => 'unparsed_commas',
Chris@76 1463 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[0123]\d{0,2})\]',
Chris@76 1464 'before' => $context['browser']['is_ie'] ? '<span style="display: inline-block; filter: Shadow(color=$1, direction=$2); height: 1.2em;">' : '<span style="text-shadow: $1 $2">',
Chris@76 1465 'after' => '</span>',
Chris@76 1466 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', '
Chris@76 1467 if ($data[1] == \'left\')
Chris@76 1468 $data[1] = 270;
Chris@76 1469 elseif ($data[1] == \'right\')
Chris@76 1470 $data[1] = 90;
Chris@76 1471 elseif ($data[1] == \'top\')
Chris@76 1472 $data[1] = 0;
Chris@76 1473 elseif ($data[1] == \'bottom\')
Chris@76 1474 $data[1] = 180;
Chris@76 1475 else
Chris@76 1476 $data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', '
Chris@76 1477 if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50))
Chris@76 1478 $data[1] = \'0 -2px 1px\';
Chris@76 1479 elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100))
Chris@76 1480 $data[1] = \'2px 0 1px\';
Chris@76 1481 elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190))
Chris@76 1482 $data[1] = \'0 2px 1px\';
Chris@76 1483 elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280))
Chris@76 1484 $data[1] = \'-2px 0 1px\';
Chris@76 1485 else
Chris@76 1486 $data[1] = \'1px 1px 1px\';'),
Chris@76 1487 ),
Chris@76 1488 array(
Chris@76 1489 'tag' => 'size',
Chris@76 1490 'type' => 'unparsed_equals',
Chris@76 1491 'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
Chris@76 1492 'before' => '<span style="font-size: $1;" class="bbc_size">',
Chris@76 1493 'after' => '</span>',
Chris@76 1494 ),
Chris@76 1495 array(
Chris@76 1496 'tag' => 'size',
Chris@76 1497 'type' => 'unparsed_equals',
Chris@76 1498 'test' => '[1-7]\]',
Chris@76 1499 'before' => '<span style="font-size: $1;" class="bbc_size">',
Chris@76 1500 'after' => '</span>',
Chris@76 1501 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1502 $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
Chris@76 1503 $data = $sizes[$data] . \'em\';'
Chris@76 1504 ),
Chris@76 1505 ),
Chris@76 1506 array(
Chris@76 1507 'tag' => 'sub',
Chris@76 1508 'before' => '<sub>',
Chris@76 1509 'after' => '</sub>',
Chris@76 1510 ),
Chris@76 1511 array(
Chris@76 1512 'tag' => 'sup',
Chris@76 1513 'before' => '<sup>',
Chris@76 1514 'after' => '</sup>',
Chris@76 1515 ),
Chris@76 1516 array(
Chris@76 1517 'tag' => 'table',
Chris@76 1518 'before' => '<table class="bbc_table">',
Chris@76 1519 'after' => '</table>',
Chris@76 1520 'trim' => 'inside',
Chris@76 1521 'require_children' => array('tr'),
Chris@76 1522 'block_level' => true,
Chris@76 1523 ),
Chris@76 1524 array(
Chris@76 1525 'tag' => 'td',
Chris@76 1526 'before' => '<td>',
Chris@76 1527 'after' => '</td>',
Chris@76 1528 'require_parents' => array('tr'),
Chris@76 1529 'trim' => 'outside',
Chris@76 1530 'block_level' => true,
Chris@76 1531 'disabled_before' => '',
Chris@76 1532 'disabled_after' => '',
Chris@76 1533 ),
Chris@76 1534 array(
Chris@76 1535 'tag' => 'time',
Chris@76 1536 'type' => 'unparsed_content',
Chris@76 1537 'content' => '$1',
Chris@76 1538 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1539 if (is_numeric($data))
Chris@76 1540 $data = timeformat($data);
Chris@76 1541 else
Chris@76 1542 $tag[\'content\'] = \'[time]$1[/time]\';'),
Chris@76 1543 ),
Chris@76 1544 array(
Chris@76 1545 'tag' => 'tr',
Chris@76 1546 'before' => '<tr>',
Chris@76 1547 'after' => '</tr>',
Chris@76 1548 'require_parents' => array('table'),
Chris@76 1549 'require_children' => array('td'),
Chris@76 1550 'trim' => 'both',
Chris@76 1551 'block_level' => true,
Chris@76 1552 'disabled_before' => '',
Chris@76 1553 'disabled_after' => '',
Chris@76 1554 ),
Chris@76 1555 array(
Chris@76 1556 'tag' => 'tt',
Chris@76 1557 'before' => '<tt class="bbc_tt">',
Chris@76 1558 'after' => '</tt>',
Chris@76 1559 ),
Chris@76 1560 array(
Chris@76 1561 'tag' => 'u',
Chris@76 1562 'before' => '<span class="bbc_u">',
Chris@76 1563 'after' => '</span>',
Chris@76 1564 ),
Chris@76 1565 array(
Chris@76 1566 'tag' => 'url',
Chris@76 1567 'type' => 'unparsed_content',
Chris@76 1568 'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
Chris@76 1569 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1570 $data = strtr($data, array(\'<br />\' => \'\'));
Chris@76 1571 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1572 $data = \'http://\' . $data;
Chris@76 1573 '),
Chris@76 1574 ),
Chris@76 1575 array(
Chris@76 1576 'tag' => 'url',
Chris@76 1577 'type' => 'unparsed_equals',
Chris@76 1578 'before' => '<a href="$1" class="bbc_link" target="_blank">',
Chris@76 1579 'after' => '</a>',
Chris@76 1580 'validate' => create_function('&$tag, &$data, $disabled', '
Chris@76 1581 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
Chris@76 1582 $data = \'http://\' . $data;
Chris@76 1583 '),
Chris@76 1584 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
Chris@76 1585 'disabled_after' => ' ($1)',
Chris@76 1586 ),
Chris@76 1587 array(
Chris@76 1588 'tag' => 'white',
Chris@76 1589 'before' => '<span style="color: white;" class="bbc_color">',
Chris@76 1590 'after' => '</span>',
Chris@76 1591 ),
Chris@76 1592 );
Chris@76 1593
Chris@76 1594 // Let mods add new BBC without hassle.
Chris@76 1595 call_integration_hook('integrate_bbc_codes', array(&$codes));
Chris@76 1596
Chris@76 1597 // This is mainly for the bbc manager, so it's easy to add tags above. Custom BBC should be added above this line.
Chris@76 1598 if ($message === false)
Chris@76 1599 {
Chris@76 1600 if (isset($temp_bbc))
Chris@76 1601 $bbc_codes = $temp_bbc;
Chris@76 1602 return $codes;
Chris@76 1603 }
Chris@76 1604
Chris@76 1605 // So the parser won't skip them.
Chris@76 1606 $itemcodes = array(
Chris@76 1607 '*' => 'disc',
Chris@76 1608 '@' => 'disc',
Chris@76 1609 '+' => 'square',
Chris@76 1610 'x' => 'square',
Chris@76 1611 '#' => 'square',
Chris@76 1612 'o' => 'circle',
Chris@76 1613 'O' => 'circle',
Chris@76 1614 '0' => 'circle',
Chris@76 1615 );
Chris@76 1616 if (!isset($disabled['li']) && !isset($disabled['list']))
Chris@76 1617 {
Chris@76 1618 foreach ($itemcodes as $c => $dummy)
Chris@76 1619 $bbc_codes[$c] = array();
Chris@76 1620 }
Chris@76 1621
Chris@76 1622 // Inside these tags autolink is not recommendable.
Chris@76 1623 $no_autolink_tags = array(
Chris@76 1624 'url',
Chris@76 1625 'iurl',
Chris@76 1626 'ftp',
Chris@76 1627 'email',
Chris@76 1628 );
Chris@76 1629
Chris@76 1630 // Shhhh!
Chris@76 1631 if (!isset($disabled['color']))
Chris@76 1632 {
Chris@76 1633 $codes[] = array(
Chris@76 1634 'tag' => 'chrissy',
Chris@76 1635 'before' => '<span style="color: #cc0099;">',
Chris@76 1636 'after' => ' :-*</span>',
Chris@76 1637 );
Chris@76 1638 $codes[] = array(
Chris@76 1639 'tag' => 'kissy',
Chris@76 1640 'before' => '<span style="color: #cc0099;">',
Chris@76 1641 'after' => ' :-*</span>',
Chris@76 1642 );
Chris@76 1643 }
Chris@76 1644
Chris@76 1645 foreach ($codes as $code)
Chris@76 1646 {
Chris@76 1647 // If we are not doing every tag only do ones we are interested in.
Chris@76 1648 if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
Chris@76 1649 $bbc_codes[substr($code['tag'], 0, 1)][] = $code;
Chris@76 1650 }
Chris@76 1651 $codes = null;
Chris@76 1652 }
Chris@76 1653
Chris@76 1654 // Shall we take the time to cache this?
Chris@76 1655 if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && strlen($message) > 1000) || strlen($message) > 2400) && empty($parse_tags))
Chris@76 1656 {
Chris@76 1657 // It's likely this will change if the message is modified.
Chris@76 1658 $cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . serialize($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
Chris@76 1659
Chris@76 1660 if (($temp = cache_get_data($cache_key, 240)) != null)
Chris@76 1661 return $temp;
Chris@76 1662
Chris@76 1663 $cache_t = microtime();
Chris@76 1664 }
Chris@76 1665
Chris@76 1666 if ($smileys === 'print')
Chris@76 1667 {
Chris@76 1668 // [glow], [shadow], and [move] can't really be printed.
Chris@76 1669 $disabled['glow'] = true;
Chris@76 1670 $disabled['shadow'] = true;
Chris@76 1671 $disabled['move'] = true;
Chris@76 1672
Chris@76 1673 // Colors can't well be displayed... supposed to be black and white.
Chris@76 1674 $disabled['color'] = true;
Chris@76 1675 $disabled['black'] = true;
Chris@76 1676 $disabled['blue'] = true;
Chris@76 1677 $disabled['white'] = true;
Chris@76 1678 $disabled['red'] = true;
Chris@76 1679 $disabled['green'] = true;
Chris@76 1680 $disabled['me'] = true;
Chris@76 1681
Chris@76 1682 // Color coding doesn't make sense.
Chris@76 1683 $disabled['php'] = true;
Chris@76 1684
Chris@76 1685 // Links are useless on paper... just show the link.
Chris@76 1686 $disabled['ftp'] = true;
Chris@76 1687 $disabled['url'] = true;
Chris@76 1688 $disabled['iurl'] = true;
Chris@76 1689 $disabled['email'] = true;
Chris@76 1690 $disabled['flash'] = true;
Chris@76 1691
Chris@76 1692 // !!! Change maybe?
Chris@76 1693 if (!isset($_GET['images']))
Chris@76 1694 $disabled['img'] = true;
Chris@76 1695
Chris@76 1696 // !!! Interface/setting to add more?
Chris@76 1697 }
Chris@76 1698
Chris@76 1699 $open_tags = array();
Chris@76 1700 $message = strtr($message, array("\n" => '<br />'));
Chris@76 1701
Chris@76 1702 // The non-breaking-space looks a bit different each time.
Chris@76 1703 $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0';
Chris@76 1704
Chris@76 1705 // This saves time by doing our break long words checks here.
Chris@76 1706 if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5)
Chris@76 1707 {
Chris@76 1708 if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror'])
Chris@76 1709 $breaker = '<span style="margin: 0 -0.5ex 0 0;"> </span>';
Chris@76 1710 // Opera...
Chris@76 1711 elseif ($context['browser']['is_opera'])
Chris@76 1712 $breaker = '<span style="margin: 0 -0.65ex 0 -1px;"> </span>';
Chris@76 1713 // Internet Explorer...
Chris@76 1714 else
Chris@76 1715 $breaker = '<span style="width: 0; margin: 0 -0.6ex 0 -1px;"> </span>';
Chris@76 1716
Chris@76 1717 // PCRE will not be happy if we don't give it a short.
Chris@76 1718 $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']);
Chris@76 1719 }
Chris@76 1720
Chris@76 1721 $pos = -1;
Chris@76 1722 while ($pos !== false)
Chris@76 1723 {
Chris@76 1724 $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
Chris@76 1725 $pos = strpos($message, '[', $pos + 1);
Chris@76 1726
Chris@76 1727 // Failsafe.
Chris@76 1728 if ($pos === false || $last_pos > $pos)
Chris@76 1729 $pos = strlen($message) + 1;
Chris@76 1730
Chris@76 1731 // Can't have a one letter smiley, URL, or email! (sorry.)
Chris@76 1732 if ($last_pos < $pos - 1)
Chris@76 1733 {
Chris@76 1734 // Make sure the $last_pos is not negative.
Chris@76 1735 $last_pos = max($last_pos, 0);
Chris@76 1736
Chris@76 1737 // Pick a block of data to do some raw fixing on.
Chris@76 1738 $data = substr($message, $last_pos, $pos - $last_pos);
Chris@76 1739
Chris@76 1740 // Take care of some HTML!
Chris@76 1741 if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
Chris@76 1742 {
Chris@76 1743 $data = preg_replace('~&lt;a\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\S+?)\\1&gt;~i', '[url=$2]', $data);
Chris@76 1744 $data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
Chris@76 1745
Chris@76 1746 // <br /> should be empty.
Chris@76 1747 $empty_tags = array('br', 'hr');
Chris@76 1748 foreach ($empty_tags as $tag)
Chris@76 1749 $data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
Chris@76 1750
Chris@76 1751 // b, u, i, s, pre... basic tags.
Chris@76 1752 $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
Chris@76 1753 foreach ($closable_tags as $tag)
Chris@76 1754 {
Chris@76 1755 $diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
Chris@76 1756 $data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
Chris@76 1757
Chris@76 1758 if ($diff > 0)
Chris@76 1759 $data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
Chris@76 1760 }
Chris@76 1761
Chris@76 1762 // Do <img ... /> - with security... action= -> action-.
Chris@76 1763 preg_match_all('~&lt;img\s+src=((?:&quot;)?)((?:https?://|ftps?://)\S+?)\\1(?:\s+alt=(&quot;.*?&quot;|\S*?))?(?:\s?/)?&gt;~i', $data, $matches, PREG_PATTERN_ORDER);
Chris@76 1764 if (!empty($matches[0]))
Chris@76 1765 {
Chris@76 1766 $replaces = array();
Chris@76 1767 foreach ($matches[2] as $match => $imgtag)
Chris@76 1768 {
Chris@76 1769 $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
Chris@76 1770
Chris@76 1771 // Remove action= from the URL - no funny business, now.
Chris@76 1772 if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
Chris@76 1773 $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
Chris@76 1774
Chris@76 1775 // Check if the image is larger than allowed.
Chris@76 1776 if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
Chris@76 1777 {
Chris@76 1778 list ($width, $height) = url_image_size($imgtag);
Chris@76 1779
Chris@76 1780 if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
Chris@76 1781 {
Chris@76 1782 $height = (int) (($modSettings['max_image_width'] * $height) / $width);
Chris@76 1783 $width = $modSettings['max_image_width'];
Chris@76 1784 }
Chris@76 1785
Chris@76 1786 if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
Chris@76 1787 {
Chris@76 1788 $width = (int) (($modSettings['max_image_height'] * $width) / $height);
Chris@76 1789 $height = $modSettings['max_image_height'];
Chris@76 1790 }
Chris@76 1791
Chris@76 1792 // Set the new image tag.
Chris@76 1793 $replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
Chris@76 1794 }
Chris@76 1795 else
Chris@76 1796 $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
Chris@76 1797 }
Chris@76 1798
Chris@76 1799 $data = strtr($data, $replaces);
Chris@76 1800 }
Chris@76 1801 }
Chris@76 1802
Chris@76 1803 if (!empty($modSettings['autoLinkUrls']))
Chris@76 1804 {
Chris@76 1805 // Are we inside tags that should be auto linked?
Chris@76 1806 $no_autolink_area = false;
Chris@76 1807 if (!empty($open_tags))
Chris@76 1808 {
Chris@76 1809 foreach ($open_tags as $open_tag)
Chris@76 1810 if (in_array($open_tag['tag'], $no_autolink_tags))
Chris@76 1811 $no_autolink_area = true;
Chris@76 1812 }
Chris@76 1813
Chris@76 1814 // Don't go backwards.
Chris@76 1815 //!!! Don't think is the real solution....
Chris@76 1816 $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
Chris@76 1817 if ($pos < $lastAutoPos)
Chris@76 1818 $no_autolink_area = true;
Chris@76 1819 $lastAutoPos = $pos;
Chris@76 1820
Chris@76 1821 if (!$no_autolink_area)
Chris@76 1822 {
Chris@76 1823 // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses.
Chris@76 1824 if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false)
Chris@76 1825 {
Chris@76 1826 // Switch out quotes really quick because they can cause problems.
Chris@76 1827 $data = strtr($data, array('&#039;' => '\'', '&nbsp;' => $context['utf8'] ? "\xC2\xA0" : "\xA0", '&quot;' => '>">', '"' => '<"<', '&lt;' => '<lt<'));
Chris@76 1828
Chris@76 1829 // Only do this if the preg survives.
Chris@76 1830 if (is_string($result = preg_replace(array(
Chris@76 1831 '~(?<=[\s>\.(;\'"]|^)((?:http|https)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i',
Chris@76 1832 '~(?<=[\s>\.(;\'"]|^)((?:ftp|ftps)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i',
Chris@76 1833 '~(?<=[\s>(\'<]|^)(www(?:\.[\w\-_]+)+(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i'
Chris@76 1834 ), array(
Chris@76 1835 '[url]$1[/url]',
Chris@76 1836 '[ftp]$1[/ftp]',
Chris@76 1837 '[url=http://$1]$1[/url]'
Chris@76 1838 ), $data)))
Chris@76 1839 $data = $result;
Chris@76 1840
Chris@76 1841 $data = strtr($data, array('\'' => '&#039;', $context['utf8'] ? "\xC2\xA0" : "\xA0" => '&nbsp;', '>">' => '&quot;', '<"<' => '"', '<lt<' => '&lt;'));
Chris@76 1842 }
Chris@76 1843
Chris@76 1844 // Next, emails...
Chris@76 1845 if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false)
Chris@76 1846 {
Chris@76 1847 $data = preg_replace('~(?<=[\?\s' . $non_breaking_space . '\[\]()*\\\;>]|^)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?,\s' . $non_breaking_space . '\[\]()*\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;|\.(?:\.|;|&nbsp;|\s|$|<br />))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
Chris@76 1848 $data = preg_replace('~(?<=<br />)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;)~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
Chris@76 1849 }
Chris@76 1850 }
Chris@76 1851 }
Chris@76 1852
Chris@76 1853 $data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
Chris@76 1854
Chris@76 1855 if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5)
Chris@76 1856 {
Chris@76 1857 // The idea is, find words xx long, and then replace them with xx + space + more.
Chris@76 1858 if ($smcFunc['strlen']($data) > $modSettings['fixLongWords'])
Chris@76 1859 {
Chris@76 1860 // This is done in a roundabout way because $breaker has "long words" :P.
Chris@76 1861 $data = strtr($data, array($breaker => '< >', '&nbsp;' => $context['utf8'] ? "\xC2\xA0" : "\xA0"));
Chris@76 1862 $data = preg_replace(
Chris@76 1863 '~(?<=[>;:!? ' . $non_breaking_space . '\]()]|^)([\w' . ($context['utf8'] ? '\pL' : '') . '\.]{' . $modSettings['fixLongWords'] . ',})~e' . ($context['utf8'] ? 'u' : ''),
Chris@76 1864 'preg_replace(\'/(.{' . ($modSettings['fixLongWords'] - 1) . '})/' . ($context['utf8'] ? 'u' : '') . '\', \'\\$1< >\', \'$1\')',
Chris@76 1865 $data);
Chris@76 1866 $data = strtr($data, array('< >' => $breaker, $context['utf8'] ? "\xC2\xA0" : "\xA0" => '&nbsp;'));
Chris@76 1867 }
Chris@76 1868 }
Chris@76 1869
Chris@76 1870 // If it wasn't changed, no copying or other boring stuff has to happen!
Chris@76 1871 if ($data != substr($message, $last_pos, $pos - $last_pos))
Chris@76 1872 {
Chris@76 1873 $message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
Chris@76 1874
Chris@76 1875 // Since we changed it, look again in case we added or removed a tag. But we don't want to skip any.
Chris@76 1876 $old_pos = strlen($data) + $last_pos;
Chris@76 1877 $pos = strpos($message, '[', $last_pos);
Chris@76 1878 $pos = $pos === false ? $old_pos : min($pos, $old_pos);
Chris@76 1879 }
Chris@76 1880 }
Chris@76 1881
Chris@76 1882 // Are we there yet? Are we there yet?
Chris@76 1883 if ($pos >= strlen($message) - 1)
Chris@76 1884 break;
Chris@76 1885
Chris@76 1886 $tags = strtolower(substr($message, $pos + 1, 1));
Chris@76 1887
Chris@76 1888 if ($tags == '/' && !empty($open_tags))
Chris@76 1889 {
Chris@76 1890 $pos2 = strpos($message, ']', $pos + 1);
Chris@76 1891 if ($pos2 == $pos + 2)
Chris@76 1892 continue;
Chris@76 1893 $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
Chris@76 1894
Chris@76 1895 $to_close = array();
Chris@76 1896 $block_level = null;
Chris@76 1897 do
Chris@76 1898 {
Chris@76 1899 $tag = array_pop($open_tags);
Chris@76 1900 if (!$tag)
Chris@76 1901 break;
Chris@76 1902
Chris@76 1903 if (!empty($tag['block_level']))
Chris@76 1904 {
Chris@76 1905 // Only find out if we need to.
Chris@76 1906 if ($block_level === false)
Chris@76 1907 {
Chris@76 1908 array_push($open_tags, $tag);
Chris@76 1909 break;
Chris@76 1910 }
Chris@76 1911
Chris@76 1912 // The idea is, if we are LOOKING for a block level tag, we can close them on the way.
Chris@76 1913 if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
Chris@76 1914 {
Chris@76 1915 foreach ($bbc_codes[$look_for[0]] as $temp)
Chris@76 1916 if ($temp['tag'] == $look_for)
Chris@76 1917 {
Chris@76 1918 $block_level = !empty($temp['block_level']);
Chris@76 1919 break;
Chris@76 1920 }
Chris@76 1921 }
Chris@76 1922
Chris@76 1923 if ($block_level !== true)
Chris@76 1924 {
Chris@76 1925 $block_level = false;
Chris@76 1926 array_push($open_tags, $tag);
Chris@76 1927 break;
Chris@76 1928 }
Chris@76 1929 }
Chris@76 1930
Chris@76 1931 $to_close[] = $tag;
Chris@76 1932 }
Chris@76 1933 while ($tag['tag'] != $look_for);
Chris@76 1934
Chris@76 1935 // Did we just eat through everything and not find it?
Chris@76 1936 if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
Chris@76 1937 {
Chris@76 1938 $open_tags = $to_close;
Chris@76 1939 continue;
Chris@76 1940 }
Chris@76 1941 elseif (!empty($to_close) && $tag['tag'] != $look_for)
Chris@76 1942 {
Chris@76 1943 if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
Chris@76 1944 {
Chris@76 1945 foreach ($bbc_codes[$look_for[0]] as $temp)
Chris@76 1946 if ($temp['tag'] == $look_for)
Chris@76 1947 {
Chris@76 1948 $block_level = !empty($temp['block_level']);
Chris@76 1949 break;
Chris@76 1950 }
Chris@76 1951 }
Chris@76 1952
Chris@76 1953 // We're not looking for a block level tag (or maybe even a tag that exists...)
Chris@76 1954 if (!$block_level)
Chris@76 1955 {
Chris@76 1956 foreach ($to_close as $tag)
Chris@76 1957 array_push($open_tags, $tag);
Chris@76 1958 continue;
Chris@76 1959 }
Chris@76 1960 }
Chris@76 1961
Chris@76 1962 foreach ($to_close as $tag)
Chris@76 1963 {
Chris@76 1964 $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
Chris@76 1965 $pos += strlen($tag['after']) + 2;
Chris@76 1966 $pos2 = $pos - 1;
Chris@76 1967
Chris@76 1968 // See the comment at the end of the big loop - just eating whitespace ;).
Chris@76 1969 if (!empty($tag['block_level']) && substr($message, $pos, 6) == '<br />')
Chris@76 1970 $message = substr($message, 0, $pos) . substr($message, $pos + 6);
Chris@76 1971 if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
Chris@76 1972 $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
Chris@76 1973 }
Chris@76 1974
Chris@76 1975 if (!empty($to_close))
Chris@76 1976 {
Chris@76 1977 $to_close = array();
Chris@76 1978 $pos--;
Chris@76 1979 }
Chris@76 1980
Chris@76 1981 continue;
Chris@76 1982 }
Chris@76 1983
Chris@76 1984 // No tags for this character, so just keep going (fastest possible course.)
Chris@76 1985 if (!isset($bbc_codes[$tags]))
Chris@76 1986 continue;
Chris@76 1987
Chris@76 1988 $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
Chris@76 1989 $tag = null;
Chris@76 1990 foreach ($bbc_codes[$tags] as $possible)
Chris@76 1991 {
Chris@76 1992 // Not a match?
Chris@76 1993 if (strtolower(substr($message, $pos + 1, strlen($possible['tag']))) != $possible['tag'])
Chris@76 1994 continue;
Chris@76 1995
Chris@76 1996 $next_c = substr($message, $pos + 1 + strlen($possible['tag']), 1);
Chris@76 1997
Chris@76 1998 // A test validation?
Chris@76 1999 if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + strlen($possible['tag']) + 1)) == 0)
Chris@76 2000 continue;
Chris@76 2001 // Do we want parameters?
Chris@76 2002 elseif (!empty($possible['parameters']))
Chris@76 2003 {
Chris@76 2004 if ($next_c != ' ')
Chris@76 2005 continue;
Chris@76 2006 }
Chris@76 2007 elseif (isset($possible['type']))
Chris@76 2008 {
Chris@76 2009 // Do we need an equal sign?
Chris@76 2010 if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
Chris@76 2011 continue;
Chris@76 2012 // Maybe we just want a /...
Chris@76 2013 if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + strlen($possible['tag']), 2) != '/]' && substr($message, $pos + 1 + strlen($possible['tag']), 3) != ' /]')
Chris@76 2014 continue;
Chris@76 2015 // An immediate ]?
Chris@76 2016 if ($possible['type'] == 'unparsed_content' && $next_c != ']')
Chris@76 2017 continue;
Chris@76 2018 }
Chris@76 2019 // No type means 'parsed_content', which demands an immediate ] without parameters!
Chris@76 2020 elseif ($next_c != ']')
Chris@76 2021 continue;
Chris@76 2022
Chris@76 2023 // Check allowed tree?
Chris@76 2024 if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
Chris@76 2025 continue;
Chris@76 2026 elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
Chris@76 2027 continue;
Chris@76 2028 // If this is in the list of disallowed child tags, don't parse it.
Chris@76 2029 elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
Chris@76 2030 continue;
Chris@76 2031
Chris@76 2032 $pos1 = $pos + 1 + strlen($possible['tag']) + 1;
Chris@76 2033
Chris@76 2034 // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
Chris@76 2035 if ($possible['tag'] == 'quote')
Chris@76 2036 {
Chris@76 2037 // Start with standard
Chris@76 2038 $quote_alt = false;
Chris@76 2039 foreach ($open_tags as $open_quote)
Chris@76 2040 {
Chris@76 2041 // Every parent quote this quote has flips the styling
Chris@76 2042 if ($open_quote['tag'] == 'quote')
Chris@76 2043 $quote_alt = !$quote_alt;
Chris@76 2044 }
Chris@76 2045 // Add a class to the quote to style alternating blockquotes
Chris@76 2046 $possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
Chris@76 2047 }
Chris@76 2048
Chris@76 2049 // This is long, but it makes things much easier and cleaner.
Chris@76 2050 if (!empty($possible['parameters']))
Chris@76 2051 {
Chris@76 2052 $preg = array();
Chris@76 2053 foreach ($possible['parameters'] as $p => $info)
Chris@76 2054 $preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . ')' . (empty($info['optional']) ? '' : '?');
Chris@76 2055
Chris@76 2056 // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right.
Chris@76 2057 $match = false;
Chris@76 2058 $orders = permute($preg);
Chris@76 2059 foreach ($orders as $p)
Chris@76 2060 if (preg_match('~^' . implode('', $p) . '\]~i', substr($message, $pos1 - 1), $matches) != 0)
Chris@76 2061 {
Chris@76 2062 $match = true;
Chris@76 2063 break;
Chris@76 2064 }
Chris@76 2065
Chris@76 2066 // Didn't match our parameter list, try the next possible.
Chris@76 2067 if (!$match)
Chris@76 2068 continue;
Chris@76 2069
Chris@76 2070 $params = array();
Chris@76 2071 for ($i = 1, $n = count($matches); $i < $n; $i += 2)
Chris@76 2072 {
Chris@76 2073 $key = strtok(ltrim($matches[$i]), '=');
Chris@76 2074 if (isset($possible['parameters'][$key]['value']))
Chris@76 2075 $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
Chris@76 2076 elseif (isset($possible['parameters'][$key]['validate']))
Chris@76 2077 $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
Chris@76 2078 else
Chris@76 2079 $params['{' . $key . '}'] = $matches[$i + 1];
Chris@76 2080
Chris@76 2081 // Just to make sure: replace any $ or { so they can't interpolate wrongly.
Chris@76 2082 $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
Chris@76 2083 }
Chris@76 2084
Chris@76 2085 foreach ($possible['parameters'] as $p => $info)
Chris@76 2086 {
Chris@76 2087 if (!isset($params['{' . $p . '}']))
Chris@76 2088 $params['{' . $p . '}'] = '';
Chris@76 2089 }
Chris@76 2090
Chris@76 2091 $tag = $possible;
Chris@76 2092
Chris@76 2093 // Put the parameters into the string.
Chris@76 2094 if (isset($tag['before']))
Chris@76 2095 $tag['before'] = strtr($tag['before'], $params);
Chris@76 2096 if (isset($tag['after']))
Chris@76 2097 $tag['after'] = strtr($tag['after'], $params);
Chris@76 2098 if (isset($tag['content']))
Chris@76 2099 $tag['content'] = strtr($tag['content'], $params);
Chris@76 2100
Chris@76 2101 $pos1 += strlen($matches[0]) - 1;
Chris@76 2102 }
Chris@76 2103 else
Chris@76 2104 $tag = $possible;
Chris@76 2105 break;
Chris@76 2106 }
Chris@76 2107
Chris@76 2108 // Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
Chris@76 2109 if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li']))
Chris@76 2110 {
Chris@76 2111 if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>')))
Chris@76 2112 continue;
Chris@76 2113 $tag = $itemcodes[substr($message, $pos + 1, 1)];
Chris@76 2114
Chris@76 2115 // First let's set up the tree: it needs to be in a list, or after an li.
Chris@76 2116 if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
Chris@76 2117 {
Chris@76 2118 $open_tags[] = array(
Chris@76 2119 'tag' => 'list',
Chris@76 2120 'after' => '</ul>',
Chris@76 2121 'block_level' => true,
Chris@76 2122 'require_children' => array('li'),
Chris@76 2123 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
Chris@76 2124 );
Chris@76 2125 $code = '<ul class="bbc_list">';
Chris@76 2126 }
Chris@76 2127 // We're in a list item already: another itemcode? Close it first.
Chris@76 2128 elseif ($inside['tag'] == 'li')
Chris@76 2129 {
Chris@76 2130 array_pop($open_tags);
Chris@76 2131 $code = '</li>';
Chris@76 2132 }
Chris@76 2133 else
Chris@76 2134 $code = '';
Chris@76 2135
Chris@76 2136 // Now we open a new tag.
Chris@76 2137 $open_tags[] = array(
Chris@76 2138 'tag' => 'li',
Chris@76 2139 'after' => '</li>',
Chris@76 2140 'trim' => 'outside',
Chris@76 2141 'block_level' => true,
Chris@76 2142 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
Chris@76 2143 );
Chris@76 2144
Chris@76 2145 // First, open the tag...
Chris@76 2146 $code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
Chris@76 2147 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
Chris@76 2148 $pos += strlen($code) - 1 + 2;
Chris@76 2149
Chris@76 2150 // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close!
Chris@76 2151 $pos2 = strpos($message, '<br />', $pos);
Chris@76 2152 $pos3 = strpos($message, '[/', $pos);
Chris@76 2153 if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
Chris@76 2154 {
Chris@76 2155 preg_match('~^(<br />|&nbsp;|\s|\[)+~', substr($message, $pos2 + 6), $matches);
Chris@76 2156 $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2);
Chris@76 2157
Chris@76 2158 $open_tags[count($open_tags) - 2]['after'] = '</ul>';
Chris@76 2159 }
Chris@76 2160 // Tell the [list] that it needs to close specially.
Chris@76 2161 else
Chris@76 2162 {
Chris@76 2163 // Move the li over, because we're not sure what we'll hit.
Chris@76 2164 $open_tags[count($open_tags) - 1]['after'] = '';
Chris@76 2165 $open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
Chris@76 2166 }
Chris@76 2167
Chris@76 2168 continue;
Chris@76 2169 }
Chris@76 2170
Chris@76 2171 // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode.
Chris@76 2172 if ($tag === null && $inside !== null && !empty($inside['require_children']))
Chris@76 2173 {
Chris@76 2174 array_pop($open_tags);
Chris@76 2175
Chris@76 2176 $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
Chris@76 2177 $pos += strlen($inside['after']) - 1 + 2;
Chris@76 2178 }
Chris@76 2179
Chris@76 2180 // No tag? Keep looking, then. Silly people using brackets without actual tags.
Chris@76 2181 if ($tag === null)
Chris@76 2182 continue;
Chris@76 2183
Chris@76 2184 // Propagate the list to the child (so wrapping the disallowed tag won't work either.)
Chris@76 2185 if (isset($inside['disallow_children']))
Chris@76 2186 $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
Chris@76 2187
Chris@76 2188 // Is this tag disabled?
Chris@76 2189 if (isset($disabled[$tag['tag']]))
Chris@76 2190 {
Chris@76 2191 if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
Chris@76 2192 {
Chris@76 2193 $tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
Chris@76 2194 $tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
Chris@76 2195 $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
Chris@76 2196 }
Chris@76 2197 elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
Chris@76 2198 {
Chris@76 2199 $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
Chris@76 2200 $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
Chris@76 2201 }
Chris@76 2202 else
Chris@76 2203 $tag['content'] = $tag['disabled_content'];
Chris@76 2204 }
Chris@76 2205
Chris@76 2206 // The only special case is 'html', which doesn't need to close things.
Chris@76 2207 if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
Chris@76 2208 {
Chris@76 2209 $n = count($open_tags) - 1;
Chris@76 2210 while (empty($open_tags[$n]['block_level']) && $n >= 0)
Chris@76 2211 $n--;
Chris@76 2212
Chris@76 2213 // Close all the non block level tags so this tag isn't surrounded by them.
Chris@76 2214 for ($i = count($open_tags) - 1; $i > $n; $i--)
Chris@76 2215 {
Chris@76 2216 $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
Chris@76 2217 $pos += strlen($open_tags[$i]['after']) + 2;
Chris@76 2218 $pos1 += strlen($open_tags[$i]['after']) + 2;
Chris@76 2219
Chris@76 2220 // Trim or eat trailing stuff... see comment at the end of the big loop.
Chris@76 2221 if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '<br />')
Chris@76 2222 $message = substr($message, 0, $pos) . substr($message, $pos + 6);
Chris@76 2223 if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
Chris@76 2224 $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
Chris@76 2225
Chris@76 2226 array_pop($open_tags);
Chris@76 2227 }
Chris@76 2228 }
Chris@76 2229
Chris@76 2230 // No type means 'parsed_content'.
Chris@76 2231 if (!isset($tag['type']))
Chris@76 2232 {
Chris@76 2233 // !!! Check for end tag first, so people can say "I like that [i] tag"?
Chris@76 2234 $open_tags[] = $tag;
Chris@76 2235 $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
Chris@76 2236 $pos += strlen($tag['before']) - 1 + 2;
Chris@76 2237 }
Chris@76 2238 // Don't parse the content, just skip it.
Chris@76 2239 elseif ($tag['type'] == 'unparsed_content')
Chris@76 2240 {
Chris@76 2241 $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1);
Chris@76 2242 if ($pos2 === false)
Chris@76 2243 continue;
Chris@76 2244
Chris@76 2245 $data = substr($message, $pos1, $pos2 - $pos1);
Chris@76 2246
Chris@76 2247 if (!empty($tag['block_level']) && substr($data, 0, 6) == '<br />')
Chris@76 2248 $data = substr($data, 6);
Chris@76 2249
Chris@76 2250 if (isset($tag['validate']))
Chris@76 2251 $tag['validate']($tag, $data, $disabled);
Chris@76 2252
Chris@76 2253 $code = strtr($tag['content'], array('$1' => $data));
Chris@76 2254 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag']));
Chris@76 2255
Chris@76 2256 $pos += strlen($code) - 1 + 2;
Chris@76 2257 $last_pos = $pos + 1;
Chris@76 2258
Chris@76 2259 }
Chris@76 2260 // Don't parse the content, just skip it.
Chris@76 2261 elseif ($tag['type'] == 'unparsed_equals_content')
Chris@76 2262 {
Chris@76 2263 // The value may be quoted for some tags - check.
Chris@76 2264 if (isset($tag['quoted']))
Chris@76 2265 {
Chris@76 2266 $quoted = substr($message, $pos1, 6) == '&quot;';
Chris@76 2267 if ($tag['quoted'] != 'optional' && !$quoted)
Chris@76 2268 continue;
Chris@76 2269
Chris@76 2270 if ($quoted)
Chris@76 2271 $pos1 += 6;
Chris@76 2272 }
Chris@76 2273 else
Chris@76 2274 $quoted = false;
Chris@76 2275
Chris@76 2276 $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
Chris@76 2277 if ($pos2 === false)
Chris@76 2278 continue;
Chris@76 2279 $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
Chris@76 2280 if ($pos3 === false)
Chris@76 2281 continue;
Chris@76 2282
Chris@76 2283 $data = array(
Chris@76 2284 substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))),
Chris@76 2285 substr($message, $pos1, $pos2 - $pos1)
Chris@76 2286 );
Chris@76 2287
Chris@76 2288 if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '<br />')
Chris@76 2289 $data[0] = substr($data[0], 6);
Chris@76 2290
Chris@76 2291 // Validation for my parking, please!
Chris@76 2292 if (isset($tag['validate']))
Chris@76 2293 $tag['validate']($tag, $data, $disabled);
Chris@76 2294
Chris@76 2295 $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
Chris@76 2296 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
Chris@76 2297 $pos += strlen($code) - 1 + 2;
Chris@76 2298 }
Chris@76 2299 // A closed tag, with no content or value.
Chris@76 2300 elseif ($tag['type'] == 'closed')
Chris@76 2301 {
Chris@76 2302 $pos2 = strpos($message, ']', $pos);
Chris@76 2303 $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
Chris@76 2304 $pos += strlen($tag['content']) - 1 + 2;
Chris@76 2305 }
Chris@76 2306 // This one is sorta ugly... :/. Unfortunately, it's needed for flash.
Chris@76 2307 elseif ($tag['type'] == 'unparsed_commas_content')
Chris@76 2308 {
Chris@76 2309 $pos2 = strpos($message, ']', $pos1);
Chris@76 2310 if ($pos2 === false)
Chris@76 2311 continue;
Chris@76 2312 $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
Chris@76 2313 if ($pos3 === false)
Chris@76 2314 continue;
Chris@76 2315
Chris@76 2316 // We want $1 to be the content, and the rest to be csv.
Chris@76 2317 $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
Chris@76 2318 $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
Chris@76 2319
Chris@76 2320 if (isset($tag['validate']))
Chris@76 2321 $tag['validate']($tag, $data, $disabled);
Chris@76 2322
Chris@76 2323 $code = $tag['content'];
Chris@76 2324 foreach ($data as $k => $d)
Chris@76 2325 $code = strtr($code, array('$' . ($k + 1) => trim($d)));
Chris@76 2326 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
Chris@76 2327 $pos += strlen($code) - 1 + 2;
Chris@76 2328 }
Chris@76 2329 // This has parsed content, and a csv value which is unparsed.
Chris@76 2330 elseif ($tag['type'] == 'unparsed_commas')
Chris@76 2331 {
Chris@76 2332 $pos2 = strpos($message, ']', $pos1);
Chris@76 2333 if ($pos2 === false)
Chris@76 2334 continue;
Chris@76 2335
Chris@76 2336 $data = explode(',', substr($message, $pos1, $pos2 - $pos1));
Chris@76 2337
Chris@76 2338 if (isset($tag['validate']))
Chris@76 2339 $tag['validate']($tag, $data, $disabled);
Chris@76 2340
Chris@76 2341 // Fix after, for disabled code mainly.
Chris@76 2342 foreach ($data as $k => $d)
Chris@76 2343 $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
Chris@76 2344
Chris@76 2345 $open_tags[] = $tag;
Chris@76 2346
Chris@76 2347 // Replace them out, $1, $2, $3, $4, etc.
Chris@76 2348 $code = $tag['before'];
Chris@76 2349 foreach ($data as $k => $d)
Chris@76 2350 $code = strtr($code, array('$' . ($k + 1) => trim($d)));
Chris@76 2351 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
Chris@76 2352 $pos += strlen($code) - 1 + 2;
Chris@76 2353 }
Chris@76 2354 // A tag set to a value, parsed or not.
Chris@76 2355 elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
Chris@76 2356 {
Chris@76 2357 // The value may be quoted for some tags - check.
Chris@76 2358 if (isset($tag['quoted']))
Chris@76 2359 {
Chris@76 2360 $quoted = substr($message, $pos1, 6) == '&quot;';
Chris@76 2361 if ($tag['quoted'] != 'optional' && !$quoted)
Chris@76 2362 continue;
Chris@76 2363
Chris@76 2364 if ($quoted)
Chris@76 2365 $pos1 += 6;
Chris@76 2366 }
Chris@76 2367 else
Chris@76 2368 $quoted = false;
Chris@76 2369
Chris@76 2370 $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
Chris@76 2371 if ($pos2 === false)
Chris@76 2372 continue;
Chris@76 2373
Chris@76 2374 $data = substr($message, $pos1, $pos2 - $pos1);
Chris@76 2375
Chris@76 2376 // Validation for my parking, please!
Chris@76 2377 if (isset($tag['validate']))
Chris@76 2378 $tag['validate']($tag, $data, $disabled);
Chris@76 2379
Chris@76 2380 // For parsed content, we must recurse to avoid security problems.
Chris@76 2381 if ($tag['type'] != 'unparsed_equals')
Chris@76 2382 $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
Chris@76 2383
Chris@76 2384 $tag['after'] = strtr($tag['after'], array('$1' => $data));
Chris@76 2385
Chris@76 2386 $open_tags[] = $tag;
Chris@76 2387
Chris@76 2388 $code = strtr($tag['before'], array('$1' => $data));
Chris@76 2389 $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7));
Chris@76 2390 $pos += strlen($code) - 1 + 2;
Chris@76 2391 }
Chris@76 2392
Chris@76 2393 // If this is block level, eat any breaks after it.
Chris@76 2394 if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '<br />')
Chris@76 2395 $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7);
Chris@76 2396
Chris@76 2397 // Are we trimming outside this tag?
Chris@76 2398 if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br />|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
Chris@76 2399 $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
Chris@76 2400 }
Chris@76 2401
Chris@76 2402 // Close any remaining tags.
Chris@76 2403 while ($tag = array_pop($open_tags))
Chris@76 2404 $message .= "\n" . $tag['after'] . "\n";
Chris@76 2405
Chris@76 2406 // Parse the smileys within the parts where it can be done safely.
Chris@76 2407 if ($smileys === true)
Chris@76 2408 {
Chris@76 2409 $message_parts = explode("\n", $message);
Chris@76 2410 for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
Chris@76 2411 parsesmileys($message_parts[$i]);
Chris@76 2412
Chris@76 2413 $message = implode('', $message_parts);
Chris@76 2414 }
Chris@76 2415
Chris@76 2416 // No smileys, just get rid of the markers.
Chris@76 2417 else
Chris@76 2418 $message = strtr($message, array("\n" => ''));
Chris@76 2419
Chris@76 2420 if (substr($message, 0, 1) == ' ')
Chris@76 2421 $message = '&nbsp;' . substr($message, 1);
Chris@76 2422
Chris@76 2423 // Cleanup whitespace.
Chris@76 2424 $message = strtr($message, array(' ' => ' &nbsp;', "\r" => '', "\n" => '<br />', '<br /> ' => '<br />&nbsp;', '&#13;' => "\n"));
Chris@76 2425
Chris@76 2426 // Cache the output if it took some time...
Chris@76 2427 if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
Chris@76 2428 cache_put_data($cache_key, $message, 240);
Chris@76 2429
Chris@76 2430 // If this was a force parse revert if needed.
Chris@76 2431 if (!empty($parse_tags))
Chris@76 2432 {
Chris@76 2433 if (empty($temp_bbc))
Chris@76 2434 $bbc_codes = array();
Chris@76 2435 else
Chris@76 2436 {
Chris@76 2437 $bbc_codes = $temp_bbc;
Chris@76 2438 unset($temp_bbc);
Chris@76 2439 }
Chris@76 2440 }
Chris@76 2441
Chris@76 2442 return $message;
Chris@76 2443 }
Chris@76 2444
Chris@76 2445 // Parse smileys in the passed message.
Chris@76 2446 function parsesmileys(&$message)
Chris@76 2447 {
Chris@76 2448 global $modSettings, $txt, $user_info, $context, $smcFunc;
Chris@76 2449 static $smileyPregSearch = array(), $smileyPregReplacements = array();
Chris@76 2450
Chris@76 2451 // No smiley set at all?!
Chris@76 2452 if ($user_info['smiley_set'] == 'none')
Chris@76 2453 return;
Chris@76 2454
Chris@76 2455 // If the smiley array hasn't been set, do it now.
Chris@76 2456 if (empty($smileyPregSearch))
Chris@76 2457 {
Chris@76 2458 // Use the default smileys if it is disabled. (better for "portability" of smileys.)
Chris@76 2459 if (empty($modSettings['smiley_enable']))
Chris@76 2460 {
Chris@76 2461 $smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
Chris@76 2462 $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');
Chris@76 2463 $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'], '', '', '', '');
Chris@76 2464 }
Chris@76 2465 else
Chris@76 2466 {
Chris@76 2467 // Load the smileys in reverse order by length so they don't get parsed wrong.
Chris@76 2468 if (($temp = cache_get_data('parsing_smileys', 480)) == null)
Chris@76 2469 {
Chris@76 2470 $result = $smcFunc['db_query']('', '
Chris@76 2471 SELECT code, filename, description
Chris@76 2472 FROM {db_prefix}smileys',
Chris@76 2473 array(
Chris@76 2474 )
Chris@76 2475 );
Chris@76 2476 $smileysfrom = array();
Chris@76 2477 $smileysto = array();
Chris@76 2478 $smileysdescs = array();
Chris@76 2479 while ($row = $smcFunc['db_fetch_assoc']($result))
Chris@76 2480 {
Chris@76 2481 $smileysfrom[] = $row['code'];
Chris@76 2482 $smileysto[] = $row['filename'];
Chris@76 2483 $smileysdescs[] = $row['description'];
Chris@76 2484 }
Chris@76 2485 $smcFunc['db_free_result']($result);
Chris@76 2486
Chris@76 2487 cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
Chris@76 2488 }
Chris@76 2489 else
Chris@76 2490 list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
Chris@76 2491 }
Chris@76 2492
Chris@76 2493 // The non-breaking-space is a complex thing...
Chris@76 2494 $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0';
Chris@76 2495
Chris@76 2496 // This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:David@bla.com] doesn't parse the :D smiley)
Chris@76 2497 $smileyPregReplacements = array();
Chris@76 2498 $searchParts = array();
Chris@76 2499 for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
Chris@76 2500 {
Chris@76 2501 $smileyCode = '<img src="' . htmlspecialchars($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/' . $smileysto[$i]) . '" alt="' . strtr(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), array(':' => '&#58;', '(' => '&#40;', ')' => '&#41;', '$' => '&#36;', '[' => '&#091;')). '" title="' . strtr(htmlspecialchars($smileysdescs[$i]), array(':' => '&#58;', '(' => '&#40;', ')' => '&#41;', '$' => '&#36;', '[' => '&#091;')) . '" class="smiley" />';
Chris@76 2502
Chris@76 2503 $smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
Chris@76 2504 $smileyPregReplacements[htmlspecialchars($smileysfrom[$i], ENT_QUOTES)] = $smileyCode;
Chris@76 2505 $searchParts[] = preg_quote($smileysfrom[$i], '~');
Chris@76 2506 $searchParts[] = preg_quote(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), '~');
Chris@76 2507 }
Chris@76 2508
Chris@76 2509 $smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~e' . ($context['utf8'] ? 'u' : '');
Chris@76 2510 }
Chris@76 2511
Chris@76 2512 // Replace away!
Chris@76 2513 $message = preg_replace($smileyPregSearch, 'isset($smileyPregReplacements[\'$1\']) ? $smileyPregReplacements[\'$1\'] : \'\'', $message);
Chris@76 2514 }
Chris@76 2515
Chris@76 2516 // Highlight any code...
Chris@76 2517 function highlight_php_code($code)
Chris@76 2518 {
Chris@76 2519 global $context;
Chris@76 2520
Chris@76 2521 // Remove special characters.
Chris@76 2522 $code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
Chris@76 2523
Chris@76 2524 $oldlevel = error_reporting(0);
Chris@76 2525
Chris@76 2526 // It's easier in 4.2.x+.
Chris@76 2527 if (@version_compare(PHP_VERSION, '4.2.0') == -1)
Chris@76 2528 {
Chris@76 2529 ob_start();
Chris@76 2530 @highlight_string($code);
Chris@76 2531 $buffer = str_replace(array("\n", "\r"), '', ob_get_contents());
Chris@76 2532 ob_end_clean();
Chris@76 2533 }
Chris@76 2534 else
Chris@76 2535 $buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
Chris@76 2536
Chris@76 2537 error_reporting($oldlevel);
Chris@76 2538
Chris@76 2539 // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
Chris@76 2540 $buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
Chris@76 2541
Chris@76 2542 return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
Chris@76 2543 }
Chris@76 2544
Chris@76 2545 // Put this user in the online log.
Chris@76 2546 function writeLog($force = false)
Chris@76 2547 {
Chris@76 2548 global $user_info, $user_settings, $context, $modSettings, $settings, $topic, $board, $smcFunc, $sourcedir;
Chris@76 2549
Chris@76 2550 // If we are showing who is viewing a topic, let's see if we are, and force an update if so - to make it accurate.
Chris@76 2551 if (!empty($settings['display_who_viewing']) && ($topic || $board))
Chris@76 2552 {
Chris@76 2553 // Take the opposite approach!
Chris@76 2554 $force = true;
Chris@76 2555 // Don't update for every page - this isn't wholly accurate but who cares.
Chris@76 2556 if ($topic)
Chris@76 2557 {
Chris@76 2558 if (isset($_SESSION['last_topic_id']) && $_SESSION['last_topic_id'] == $topic)
Chris@76 2559 $force = false;
Chris@76 2560 $_SESSION['last_topic_id'] = $topic;
Chris@76 2561 }
Chris@76 2562 }
Chris@76 2563
Chris@76 2564 // Are they a spider we should be tracking? Mode = 1 gets tracked on its spider check...
Chris@76 2565 if (!empty($user_info['possibly_robot']) && !empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1)
Chris@76 2566 {
Chris@76 2567 require_once($sourcedir . '/ManageSearchEngines.php');
Chris@76 2568 logSpider();
Chris@76 2569 }
Chris@76 2570
Chris@76 2571 // Don't mark them as online more than every so often.
Chris@76 2572 if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= (time() - 8) && !$force)
Chris@76 2573 return;
Chris@76 2574
Chris@76 2575 if (!empty($modSettings['who_enabled']))
Chris@76 2576 {
Chris@76 2577 $serialized = $_GET + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']);
Chris@76 2578
Chris@76 2579 // In the case of a dlattach action, session_var may not be set.
Chris@76 2580 if (!isset($context['session_var']))
Chris@76 2581 $context['session_var'] = $_SESSION['session_var'];
Chris@76 2582
Chris@76 2583 unset($serialized['sesc'], $serialized[$context['session_var']]);
Chris@76 2584 $serialized = serialize($serialized);
Chris@76 2585 }
Chris@76 2586 else
Chris@76 2587 $serialized = '';
Chris@76 2588
Chris@76 2589 // Guests use 0, members use their session ID.
Chris@76 2590 $session_id = $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id();
Chris@76 2591
Chris@76 2592 // Grab the last all-of-SMF-specific log_online deletion time.
Chris@76 2593 $do_delete = cache_get_data('log_online-update', 30) < time() - 30;
Chris@76 2594
Chris@76 2595 // If the last click wasn't a long time ago, and there was a last click...
Chris@76 2596 if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= time() - $modSettings['lastActive'] * 20)
Chris@76 2597 {
Chris@76 2598 if ($do_delete)
Chris@76 2599 {
Chris@76 2600 $smcFunc['db_query']('delete_log_online_interval', '
Chris@76 2601 DELETE FROM {db_prefix}log_online
Chris@76 2602 WHERE log_time < {int:log_time}
Chris@76 2603 AND session != {string:session}',
Chris@76 2604 array(
Chris@76 2605 'log_time' => time() - $modSettings['lastActive'] * 60,
Chris@76 2606 'session' => $session_id,
Chris@76 2607 )
Chris@76 2608 );
Chris@76 2609
Chris@76 2610 // Cache when we did it last.
Chris@76 2611 cache_put_data('log_online-update', time(), 30);
Chris@76 2612 }
Chris@76 2613
Chris@76 2614 $smcFunc['db_query']('', '
Chris@76 2615 UPDATE {db_prefix}log_online
Chris@76 2616 SET log_time = {int:log_time}, ip = IFNULL(INET_ATON({string:ip}), 0), url = {string:url}
Chris@76 2617 WHERE session = {string:session}',
Chris@76 2618 array(
Chris@76 2619 'log_time' => time(),
Chris@76 2620 'ip' => $user_info['ip'],
Chris@76 2621 'url' => $serialized,
Chris@76 2622 'session' => $session_id,
Chris@76 2623 )
Chris@76 2624 );
Chris@76 2625
Chris@76 2626 // Guess it got deleted.
Chris@76 2627 if ($smcFunc['db_affected_rows']() == 0)
Chris@76 2628 $_SESSION['log_time'] = 0;
Chris@76 2629 }
Chris@76 2630 else
Chris@76 2631 $_SESSION['log_time'] = 0;
Chris@76 2632
Chris@76 2633 // Otherwise, we have to delete and insert.
Chris@76 2634 if (empty($_SESSION['log_time']))
Chris@76 2635 {
Chris@76 2636 if ($do_delete || !empty($user_info['id']))
Chris@76 2637 $smcFunc['db_query']('', '
Chris@76 2638 DELETE FROM {db_prefix}log_online
Chris@76 2639 WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') . ($do_delete && !empty($user_info['id']) ? ' OR ' : '') . (empty($user_info['id']) ? '' : 'id_member = {int:current_member}'),
Chris@76 2640 array(
Chris@76 2641 'current_member' => $user_info['id'],
Chris@76 2642 'log_time' => time() - $modSettings['lastActive'] * 60,
Chris@76 2643 )
Chris@76 2644 );
Chris@76 2645
Chris@76 2646 $smcFunc['db_insert']($do_delete ? 'ignore' : 'replace',
Chris@76 2647 '{db_prefix}log_online',
Chris@76 2648 array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'raw', 'url' => 'string'),
Chris@76 2649 array($session_id, $user_info['id'], empty($_SESSION['id_robot']) ? 0 : $_SESSION['id_robot'], time(), 'IFNULL(INET_ATON(\'' . $user_info['ip'] . '\'), 0)', $serialized),
Chris@76 2650 array('session')
Chris@76 2651 );
Chris@76 2652 }
Chris@76 2653
Chris@76 2654 // Mark your session as being logged.
Chris@76 2655 $_SESSION['log_time'] = time();
Chris@76 2656
Chris@76 2657 // Well, they are online now.
Chris@76 2658 if (empty($_SESSION['timeOnlineUpdated']))
Chris@76 2659 $_SESSION['timeOnlineUpdated'] = time();
Chris@76 2660
Chris@76 2661 // Set their login time, if not already done within the last minute.
Chris@76 2662 if (SMF != 'SSI' && !empty($user_info['last_login']) && $user_info['last_login'] < time() - 60)
Chris@76 2663 {
Chris@76 2664 // Don't count longer than 15 minutes.
Chris@76 2665 if (time() - $_SESSION['timeOnlineUpdated'] > 60 * 15)
Chris@76 2666 $_SESSION['timeOnlineUpdated'] = time();
Chris@76 2667
Chris@76 2668 $user_settings['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated'];
Chris@76 2669 updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'], 'total_time_logged_in' => $user_settings['total_time_logged_in']));
Chris@76 2670
Chris@76 2671 if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
Chris@76 2672 cache_put_data('user_settings-' . $user_info['id'], $user_settings, 60);
Chris@76 2673
Chris@76 2674 $user_info['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated'];
Chris@76 2675 $_SESSION['timeOnlineUpdated'] = time();
Chris@76 2676 }
Chris@76 2677 }
Chris@76 2678
Chris@76 2679 // Make sure the browser doesn't come back and repost the form data. Should be used whenever anything is posted.
Chris@76 2680 function redirectexit($setLocation = '', $refresh = false)
Chris@76 2681 {
Chris@76 2682 global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
Chris@76 2683
Chris@76 2684 // In case we have mail to send, better do that - as obExit doesn't always quite make it...
Chris@76 2685 if (!empty($context['flush_mail']))
Chris@76 2686 AddMailQueue(true);
Chris@76 2687
Chris@76 2688 $add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
Chris@76 2689
Chris@76 2690 if (WIRELESS)
Chris@76 2691 {
Chris@76 2692 // Add the scripturl on if needed.
Chris@76 2693 if ($add)
Chris@76 2694 $setLocation = $scripturl . '?' . $setLocation;
Chris@76 2695
Chris@76 2696 $char = strpos($setLocation, '?') === false ? '?' : ';';
Chris@76 2697
Chris@76 2698 if (strpos($setLocation, '#') !== false)
Chris@76 2699 $setLocation = strtr($setLocation, array('#' => $char . WIRELESS_PROTOCOL . '#'));
Chris@76 2700 else
Chris@76 2701 $setLocation .= $char . WIRELESS_PROTOCOL;
Chris@76 2702 }
Chris@76 2703 elseif ($add)
Chris@76 2704 $setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
Chris@76 2705
Chris@76 2706 // Put the session ID in.
Chris@76 2707 if (defined('SID') && SID != '')
Chris@76 2708 $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
Chris@76 2709 // Keep that debug in their for template debugging!
Chris@76 2710 elseif (isset($_GET['debug']))
Chris@76 2711 $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
Chris@76 2712
Chris@76 2713 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'])))
Chris@76 2714 {
Chris@76 2715 if (defined('SID') && SID != '')
Chris@76 2716 $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2?' . SID", $setLocation);
Chris@76 2717 else
Chris@76 2718 $setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$/e', "\$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2'", $setLocation);
Chris@76 2719 }
Chris@76 2720
Chris@76 2721 // Maybe integrations want to change where we are heading?
Chris@76 2722 call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh));
Chris@76 2723
Chris@76 2724 // We send a Refresh header only in special cases because Location looks better. (and is quicker...)
Chris@76 2725 if ($refresh && !WIRELESS)
Chris@76 2726 header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20')));
Chris@76 2727 else
Chris@76 2728 header('Location: ' . str_replace(' ', '%20', $setLocation));
Chris@76 2729
Chris@76 2730 // Debugging.
Chris@76 2731 if (isset($db_show_debug) && $db_show_debug === true)
Chris@76 2732 $_SESSION['debug_redirect'] = $db_cache;
Chris@76 2733
Chris@76 2734 obExit(false);
Chris@76 2735 }
Chris@76 2736
Chris@76 2737 // Ends execution. Takes care of template loading and remembering the previous URL.
Chris@76 2738 function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
Chris@76 2739 {
Chris@76 2740 global $context, $settings, $modSettings, $txt, $smcFunc;
Chris@76 2741 static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
Chris@76 2742
Chris@76 2743 // Attempt to prevent a recursive loop.
Chris@76 2744 ++$level;
Chris@76 2745 if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
Chris@76 2746 exit;
Chris@76 2747 if ($from_fatal_error)
Chris@76 2748 $has_fatal_error = true;
Chris@76 2749
Chris@76 2750 // Clear out the stat cache.
Chris@76 2751 trackStats();
Chris@76 2752
Chris@76 2753 // If we have mail to send, send it.
Chris@76 2754 if (!empty($context['flush_mail']))
Chris@76 2755 AddMailQueue(true);
Chris@76 2756
Chris@76 2757 $do_header = $header === null ? !$header_done : $header;
Chris@76 2758 if ($do_footer === null)
Chris@76 2759 $do_footer = $do_header;
Chris@76 2760
Chris@76 2761 // Has the template/header been done yet?
Chris@76 2762 if ($do_header)
Chris@76 2763 {
Chris@76 2764 // Was the page title set last minute? Also update the HTML safe one.
Chris@76 2765 if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
Chris@76 2766 $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title']));
Chris@76 2767
Chris@76 2768 // Start up the session URL fixer.
Chris@76 2769 ob_start('ob_sessrewrite');
Chris@76 2770
Chris@76 2771 if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
Chris@76 2772 $buffers = explode(',', $settings['output_buffers']);
Chris@76 2773 elseif (!empty($settings['output_buffers']))
Chris@76 2774 $buffers = $settings['output_buffers'];
Chris@76 2775 else
Chris@76 2776 $buffers = array();
Chris@76 2777
Chris@76 2778 if (isset($modSettings['integrate_buffer']))
Chris@76 2779 $buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
Chris@76 2780
Chris@76 2781 if (!empty($buffers))
Chris@76 2782 foreach ($buffers as $function)
Chris@76 2783 {
Chris@76 2784 $function = trim($function);
Chris@76 2785 $call = strpos($function, '::') !== false ? explode('::', $function) : $function;
Chris@76 2786
Chris@76 2787 // Is it valid?
Chris@76 2788 if (is_callable($call))
Chris@76 2789 ob_start($call);
Chris@76 2790 }
Chris@76 2791
Chris@76 2792 // Display the screen in the logical order.
Chris@76 2793 template_header();
Chris@76 2794 $header_done = true;
Chris@76 2795 }
Chris@76 2796 if ($do_footer)
Chris@76 2797 {
Chris@76 2798 if (WIRELESS && !isset($context['sub_template']))
Chris@76 2799 fatal_lang_error('wireless_error_notyet', false);
Chris@76 2800
Chris@76 2801 // Just show the footer, then.
Chris@76 2802 loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
Chris@76 2803
Chris@76 2804 // Anything special to put out?
Chris@76 2805 if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
Chris@76 2806 echo $context['insert_after_template'];
Chris@76 2807
Chris@76 2808 // Just so we don't get caught in an endless loop of errors from the footer...
Chris@76 2809 if (!$footer_done)
Chris@76 2810 {
Chris@76 2811 $footer_done = true;
Chris@76 2812 template_footer();
Chris@76 2813
Chris@76 2814 // (since this is just debugging... it's okay that it's after </html>.)
Chris@76 2815 if (!isset($_REQUEST['xml']))
Chris@76 2816 db_debug_junk();
Chris@76 2817 }
Chris@76 2818 }
Chris@76 2819
Chris@76 2820 // Remember this URL in case someone doesn't like sending HTTP_REFERER.
Chris@76 2821 if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
Chris@76 2822 $_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
Chris@76 2823
Chris@76 2824 // For session check verfication.... don't switch browsers...
Chris@76 2825 $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
Chris@76 2826
Chris@76 2827 if (!empty($settings['strict_doctype']))
Chris@76 2828 {
Chris@76 2829 // The theme author wants to use the STRICT doctype (only God knows why).
Chris@76 2830 $temp = ob_get_contents();
Chris@76 2831 if (function_exists('ob_clean'))
Chris@76 2832 ob_clean();
Chris@76 2833 else
Chris@76 2834 {
Chris@76 2835 ob_end_clean();
Chris@76 2836 ob_start('ob_sessrewrite');
Chris@76 2837 }
Chris@76 2838
Chris@76 2839 echo strtr($temp, array(
Chris@76 2840 'var smf_iso_case_folding' => 'var target_blank = \'_blank\'; var smf_iso_case_folding',
Chris@76 2841 'target="_blank"' => 'onclick="this.target=target_blank"'));
Chris@76 2842 }
Chris@76 2843
Chris@76 2844 // Hand off the output to the portal, etc. we're integrated with.
Chris@76 2845 call_integration_hook('integrate_exit', array($do_footer && !WIRELESS));
Chris@76 2846
Chris@76 2847 // Don't exit if we're coming from index.php; that will pass through normally.
Chris@76 2848 if (!$from_index || WIRELESS)
Chris@76 2849 exit;
Chris@76 2850 }
Chris@76 2851
Chris@76 2852 // Usage: logAction('remove', array('starter' => $id_member_started));
Chris@76 2853 function logAction($action, $extra = array(), $log_type = 'moderate')
Chris@76 2854 {
Chris@76 2855 global $modSettings, $user_info, $smcFunc, $sourcedir;
Chris@76 2856
Chris@76 2857 $log_types = array(
Chris@76 2858 'moderate' => 1,
Chris@76 2859 'user' => 2,
Chris@76 2860 'admin' => 3,
Chris@76 2861 );
Chris@76 2862
Chris@76 2863 if (!is_array($extra))
Chris@76 2864 trigger_error('logAction(): data is not an array with action \'' . $action . '\'', E_USER_NOTICE);
Chris@76 2865
Chris@76 2866 // Pull out the parts we want to store separately, but also make sure that the data is proper
Chris@76 2867 if (isset($extra['topic']))
Chris@76 2868 {
Chris@76 2869 if (!is_numeric($extra['topic']))
Chris@76 2870 trigger_error('logAction(): data\'s topic is not a number', E_USER_NOTICE);
Chris@76 2871 $topic_id = empty($extra['topic']) ? '0' : (int)$extra['topic'];
Chris@76 2872 unset($extra['topic']);
Chris@76 2873 }
Chris@76 2874 else
Chris@76 2875 $topic_id = '0';
Chris@76 2876
Chris@76 2877 if (isset($extra['message']))
Chris@76 2878 {
Chris@76 2879 if (!is_numeric($extra['message']))
Chris@76 2880 trigger_error('logAction(): data\'s message is not a number', E_USER_NOTICE);
Chris@76 2881 $msg_id = empty($extra['message']) ? '0' : (int)$extra['message'];
Chris@76 2882 unset($extra['message']);
Chris@76 2883 }
Chris@76 2884 else
Chris@76 2885 $msg_id = '0';
Chris@76 2886
Chris@76 2887 // Is there an associated report on this?
Chris@76 2888 if (in_array($action, array('move', 'remove', 'split', 'merge')))
Chris@76 2889 {
Chris@76 2890 $request = $smcFunc['db_query']('', '
Chris@76 2891 SELECT id_report
Chris@76 2892 FROM {db_prefix}log_reported
Chris@76 2893 WHERE {raw:column_name} = {int:reported}
Chris@76 2894 LIMIT 1',
Chris@76 2895 array(
Chris@76 2896 'column_name' => !empty($msg_id) ? 'id_msg' : 'id_topic',
Chris@76 2897 'reported' => !empty($msg_id) ? $msg_id : $topic_id,
Chris@76 2898 ));
Chris@76 2899
Chris@76 2900 // Alright, if we get any result back, update open reports.
Chris@76 2901 if ($smcFunc['db_num_rows']($request) > 0)
Chris@76 2902 {
Chris@76 2903 require_once($sourcedir . '/ModerationCenter.php');
Chris@76 2904 updateSettings(array('last_mod_report_action' => time()));
Chris@76 2905 recountOpenReports();
Chris@76 2906 }
Chris@76 2907 $smcFunc['db_free_result']($request);
Chris@76 2908 }
Chris@76 2909
Chris@76 2910 // No point in doing anything else, if the log isn't even enabled.
Chris@76 2911 if (empty($modSettings['modlog_enabled']) || !isset($log_types[$log_type]))
Chris@76 2912 return false;
Chris@76 2913
Chris@76 2914 if (isset($extra['member']) && !is_numeric($extra['member']))
Chris@76 2915 trigger_error('logAction(): data\'s member is not a number', E_USER_NOTICE);
Chris@76 2916
Chris@76 2917 if (isset($extra['board']))
Chris@76 2918 {
Chris@76 2919 if (!is_numeric($extra['board']))
Chris@76 2920 trigger_error('logAction(): data\'s board is not a number', E_USER_NOTICE);
Chris@76 2921 $board_id = empty($extra['board']) ? '0' : (int)$extra['board'];
Chris@76 2922 unset($extra['board']);
Chris@76 2923 }
Chris@76 2924 else
Chris@76 2925 $board_id = '0';
Chris@76 2926
Chris@76 2927 if (isset($extra['board_to']))
Chris@76 2928 {
Chris@76 2929 if (!is_numeric($extra['board_to']))
Chris@76 2930 trigger_error('logAction(): data\'s board_to is not a number', E_USER_NOTICE);
Chris@76 2931 if (empty($board_id))
Chris@76 2932 {
Chris@76 2933 $board_id = empty($extra['board_to']) ? '0' : (int)$extra['board_to'];
Chris@76 2934 unset($extra['board_to']);
Chris@76 2935 }
Chris@76 2936 }
Chris@76 2937
Chris@76 2938 $smcFunc['db_insert']('',
Chris@76 2939 '{db_prefix}log_actions',
Chris@76 2940 array(
Chris@76 2941 'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'string-16', 'action' => 'string',
Chris@76 2942 'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
Chris@76 2943 ),
Chris@76 2944 array(
Chris@76 2945 time(), $log_types[$log_type], $user_info['id'], $user_info['ip'], $action,
Chris@76 2946 $board_id, $topic_id, $msg_id, serialize($extra),
Chris@76 2947 ),
Chris@76 2948 array('id_action')
Chris@76 2949 );
Chris@76 2950
Chris@76 2951 return $smcFunc['db_insert_id']('{db_prefix}log_actions', 'id_action');
Chris@76 2952 }
Chris@76 2953
Chris@76 2954 // Track Statistics.
Chris@76 2955 function trackStats($stats = array())
Chris@76 2956 {
Chris@76 2957 global $modSettings, $smcFunc;
Chris@76 2958 static $cache_stats = array();
Chris@76 2959
Chris@76 2960 if (empty($modSettings['trackStats']))
Chris@76 2961 return false;
Chris@76 2962 if (!empty($stats))
Chris@76 2963 return $cache_stats = array_merge($cache_stats, $stats);
Chris@76 2964 elseif (empty($cache_stats))
Chris@76 2965 return false;
Chris@76 2966
Chris@76 2967 $setStringUpdate = '';
Chris@76 2968 $insert_keys = array();
Chris@76 2969 $date = strftime('%Y-%m-%d', forum_time(false));
Chris@76 2970 $update_parameters = array(
Chris@76 2971 'current_date' => $date,
Chris@76 2972 );
Chris@76 2973 foreach ($cache_stats as $field => $change)
Chris@76 2974 {
Chris@76 2975 $setStringUpdate .= '
Chris@76 2976 ' . $field . ' = ' . ($change === '+' ? $field . ' + 1' : '{int:' . $field . '}') . ',';
Chris@76 2977
Chris@76 2978 if ($change === '+')
Chris@76 2979 $cache_stats[$field] = 1;
Chris@76 2980 else
Chris@76 2981 $update_parameters[$field] = $change;
Chris@76 2982 $insert_keys[$field] = 'int';
Chris@76 2983 }
Chris@76 2984
Chris@76 2985 $smcFunc['db_query']('', '
Chris@76 2986 UPDATE {db_prefix}log_activity
Chris@76 2987 SET' . substr($setStringUpdate, 0, -1) . '
Chris@76 2988 WHERE date = {date:current_date}',
Chris@76 2989 $update_parameters
Chris@76 2990 );
Chris@76 2991 if ($smcFunc['db_affected_rows']() == 0)
Chris@76 2992 {
Chris@76 2993 $smcFunc['db_insert']('ignore',
Chris@76 2994 '{db_prefix}log_activity',
Chris@76 2995 array_merge($insert_keys, array('date' => 'date')),
Chris@76 2996 array_merge($cache_stats, array($date)),
Chris@76 2997 array('date')
Chris@76 2998 );
Chris@76 2999 }
Chris@76 3000
Chris@76 3001 // Don't do this again.
Chris@76 3002 $cache_stats = array();
Chris@76 3003
Chris@76 3004 return true;
Chris@76 3005 }
Chris@76 3006
Chris@76 3007 // Make sure the user isn't posting over and over again.
Chris@76 3008 function spamProtection($error_type)
Chris@76 3009 {
Chris@76 3010 global $modSettings, $txt, $user_info, $smcFunc;
Chris@76 3011
Chris@76 3012 // Certain types take less/more time.
Chris@76 3013 $timeOverrides = array(
Chris@76 3014 'login' => 2,
Chris@76 3015 'register' => 2,
Chris@76 3016 'sendtopc' => $modSettings['spamWaitTime'] * 4,
Chris@76 3017 'sendmail' => $modSettings['spamWaitTime'] * 5,
Chris@76 3018 'reporttm' => $modSettings['spamWaitTime'] * 4,
Chris@76 3019 'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1,
Chris@76 3020 );
Chris@76 3021
Chris@76 3022 // Moderators are free...
Chris@76 3023 if (!allowedTo('moderate_board'))
Chris@76 3024 $timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime'];
Chris@76 3025 else
Chris@76 3026 $timeLimit = 2;
Chris@76 3027
Chris@76 3028 // Delete old entries...
Chris@76 3029 $smcFunc['db_query']('', '
Chris@76 3030 DELETE FROM {db_prefix}log_floodcontrol
Chris@76 3031 WHERE log_time < {int:log_time}
Chris@76 3032 AND log_type = {string:log_type}',
Chris@76 3033 array(
Chris@76 3034 'log_time' => time() - $timeLimit,
Chris@76 3035 'log_type' => $error_type,
Chris@76 3036 )
Chris@76 3037 );
Chris@76 3038
Chris@76 3039 // Add a new entry, deleting the old if necessary.
Chris@76 3040 $smcFunc['db_insert']('replace',
Chris@76 3041 '{db_prefix}log_floodcontrol',
Chris@76 3042 array('ip' => 'string-16', 'log_time' => 'int', 'log_type' => 'string'),
Chris@76 3043 array($user_info['ip'], time(), $error_type),
Chris@76 3044 array('ip', 'log_type')
Chris@76 3045 );
Chris@76 3046
Chris@76 3047 // If affected is 0 or 2, it was there already.
Chris@76 3048 if ($smcFunc['db_affected_rows']() != 1)
Chris@76 3049 {
Chris@76 3050 // Spammer! You only have to wait a *few* seconds!
Chris@76 3051 fatal_lang_error($error_type . 'WaitTime_broken', false, array($timeLimit));
Chris@76 3052 return true;
Chris@76 3053 }
Chris@76 3054
Chris@76 3055 // They haven't posted within the limit.
Chris@76 3056 return false;
Chris@76 3057 }
Chris@76 3058
Chris@76 3059 // Get the size of a specified image with better error handling.
Chris@76 3060 function url_image_size($url)
Chris@76 3061 {
Chris@76 3062 global $sourcedir;
Chris@76 3063
Chris@76 3064 // Make sure it is a proper URL.
Chris@76 3065 $url = str_replace(' ', '%20', $url);
Chris@76 3066
Chris@76 3067 // Can we pull this from the cache... please please?
Chris@76 3068 if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
Chris@76 3069 return $temp;
Chris@76 3070 $t = microtime();
Chris@76 3071
Chris@76 3072 // Get the host to pester...
Chris@76 3073 preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
Chris@76 3074
Chris@76 3075 // Can't figure it out, just try the image size.
Chris@76 3076 if ($url == '' || $url == 'http://' || $url == 'https://')
Chris@76 3077 {
Chris@76 3078 return false;
Chris@76 3079 }
Chris@76 3080 elseif (!isset($match[1]))
Chris@76 3081 {
Chris@76 3082 $size = @getimagesize($url);
Chris@76 3083 }
Chris@76 3084 else
Chris@76 3085 {
Chris@76 3086 // Try to connect to the server... give it half a second.
Chris@76 3087 $temp = 0;
Chris@76 3088 $fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
Chris@76 3089
Chris@76 3090 // Successful? Continue...
Chris@76 3091 if ($fp != false)
Chris@76 3092 {
Chris@76 3093 // Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
Chris@76 3094 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");
Chris@76 3095
Chris@76 3096 // Read in the HTTP/1.1 or whatever.
Chris@76 3097 $test = substr(fgets($fp, 11), -1);
Chris@76 3098 fclose($fp);
Chris@76 3099
Chris@76 3100 // See if it returned a 404/403 or something.
Chris@76 3101 if ($test < 4)
Chris@76 3102 {
Chris@76 3103 $size = @getimagesize($url);
Chris@76 3104
Chris@76 3105 // This probably means allow_url_fopen is off, let's try GD.
Chris@76 3106 if ($size === false && function_exists('imagecreatefromstring'))
Chris@76 3107 {
Chris@76 3108 include_once($sourcedir . '/Subs-Package.php');
Chris@76 3109
Chris@76 3110 // It's going to hate us for doing this, but another request...
Chris@76 3111 $image = @imagecreatefromstring(fetch_web_data($url));
Chris@76 3112 if ($image !== false)
Chris@76 3113 {
Chris@76 3114 $size = array(imagesx($image), imagesy($image));
Chris@76 3115 imagedestroy($image);
Chris@76 3116 }
Chris@76 3117 }
Chris@76 3118 }
Chris@76 3119 }
Chris@76 3120 }
Chris@76 3121
Chris@76 3122 // If we didn't get it, we failed.
Chris@76 3123 if (!isset($size))
Chris@76 3124 $size = false;
Chris@76 3125
Chris@76 3126 // If this took a long time, we may never have to do it again, but then again we might...
Chris@76 3127 if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8)
Chris@76 3128 cache_put_data('url_image_size-' . md5($url), $size, 240);
Chris@76 3129
Chris@76 3130 // Didn't work.
Chris@76 3131 return $size;
Chris@76 3132 }
Chris@76 3133
Chris@76 3134 function determineTopicClass(&$topic_context)
Chris@76 3135 {
Chris@76 3136 // Set topic class depending on locked status and number of replies.
Chris@76 3137 if ($topic_context['is_very_hot'])
Chris@76 3138 $topic_context['class'] = 'veryhot';
Chris@76 3139 elseif ($topic_context['is_hot'])
Chris@76 3140 $topic_context['class'] = 'hot';
Chris@76 3141 else
Chris@76 3142 $topic_context['class'] = 'normal';
Chris@76 3143
Chris@76 3144 $topic_context['class'] .= $topic_context['is_poll'] ? '_poll' : '_post';
Chris@76 3145
Chris@76 3146 if ($topic_context['is_locked'])
Chris@76 3147 $topic_context['class'] .= '_locked';
Chris@76 3148
Chris@76 3149 if ($topic_context['is_sticky'])
Chris@76 3150 $topic_context['class'] .= '_sticky';
Chris@76 3151
Chris@76 3152 // This is so old themes will still work.
Chris@76 3153 $topic_context['extended_class'] = &$topic_context['class'];
Chris@76 3154 }
Chris@76 3155
Chris@76 3156 // Sets up the basic theme context stuff.
Chris@76 3157 function setupThemeContext($forceload = false)
Chris@76 3158 {
Chris@76 3159 global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance;
Chris@76 3160 global $user_settings, $smcFunc;
Chris@76 3161 static $loaded = false;
Chris@76 3162
Chris@76 3163 // Under SSI this function can be called more then once. That can cause some problems.
Chris@76 3164 // So only run the function once unless we are forced to run it again.
Chris@76 3165 if ($loaded && !$forceload)
Chris@76 3166 return;
Chris@76 3167
Chris@76 3168 $loaded = true;
Chris@76 3169
Chris@76 3170 $context['in_maintenance'] = !empty($maintenance);
Chris@76 3171 $context['current_time'] = timeformat(time(), false);
Chris@76 3172 $context['current_action'] = isset($_GET['action']) ? $_GET['action'] : '';
Chris@76 3173 $context['show_quick_login'] = !empty($modSettings['enableVBStyleLogin']) && $user_info['is_guest'];
Chris@76 3174
Chris@76 3175 // Get some news...
Chris@76 3176 $context['news_lines'] = explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news']))));
Chris@76 3177 $context['fader_news_lines'] = array();
Chris@76 3178 for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
Chris@76 3179 {
Chris@76 3180 if (trim($context['news_lines'][$i]) == '')
Chris@76 3181 continue;
Chris@76 3182
Chris@76 3183 // Clean it up for presentation ;).
Chris@76 3184 $context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
Chris@76 3185
Chris@76 3186 // Gotta be special for the javascript.
Chris@76 3187 $context['fader_news_lines'][$i] = strtr(addslashes($context['news_lines'][$i]), array('/' => '\/', '<a href=' => '<a hre" + "f='));
Chris@76 3188 }
Chris@76 3189 $context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
Chris@76 3190
Chris@76 3191 if (!$user_info['is_guest'])
Chris@76 3192 {
Chris@76 3193 $context['user']['messages'] = &$user_info['messages'];
Chris@76 3194 $context['user']['unread_messages'] = &$user_info['unread_messages'];
Chris@76 3195
Chris@76 3196 // Personal message popup...
Chris@76 3197 if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
Chris@76 3198 $context['user']['popup_messages'] = true;
Chris@76 3199 else
Chris@76 3200 $context['user']['popup_messages'] = false;
Chris@76 3201 $_SESSION['unread_messages'] = $user_info['unread_messages'];
Chris@76 3202
Chris@76 3203 if (allowedTo('moderate_forum'))
Chris@76 3204 $context['unapproved_members'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
Chris@76 3205 $context['show_open_reports'] = empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1;
Chris@76 3206
Chris@76 3207 $context['user']['avatar'] = array();
Chris@76 3208
Chris@76 3209 // Figure out the avatar... uploaded?
Chris@76 3210 if ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
Chris@76 3211 $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';
Chris@76 3212 // Full URL?
Chris@76 3213 elseif (substr($user_info['avatar']['url'], 0, 7) == 'http://')
Chris@76 3214 {
Chris@76 3215 $context['user']['avatar']['href'] = $user_info['avatar']['url'];
Chris@76 3216
Chris@76 3217 if ($modSettings['avatar_action_too_large'] == 'option_html_resize' || $modSettings['avatar_action_too_large'] == 'option_js_resize')
Chris@76 3218 {
Chris@76 3219 if (!empty($modSettings['avatar_max_width_external']))
Chris@76 3220 $context['user']['avatar']['width'] = $modSettings['avatar_max_width_external'];
Chris@76 3221 if (!empty($modSettings['avatar_max_height_external']))
Chris@76 3222 $context['user']['avatar']['height'] = $modSettings['avatar_max_height_external'];
Chris@76 3223 }
Chris@76 3224 }
Chris@76 3225 // Otherwise we assume it's server stored?
Chris@76 3226 elseif ($user_info['avatar']['url'] != '')
Chris@76 3227 $context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . htmlspecialchars($user_info['avatar']['url']);
Chris@76 3228
Chris@76 3229 if (!empty($context['user']['avatar']))
Chris@76 3230 $context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '"' . (isset($context['user']['avatar']['width']) ? ' width="' . $context['user']['avatar']['width'] . '"' : '') . (isset($context['user']['avatar']['height']) ? ' height="' . $context['user']['avatar']['height'] . '"' : '') . ' alt="" class="avatar" />';
Chris@76 3231
Chris@76 3232 // Figure out how long they've been logged in.
Chris@76 3233 $context['user']['total_time_logged_in'] = array(
Chris@76 3234 'days' => floor($user_info['total_time_logged_in'] / 86400),
Chris@76 3235 'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
Chris@76 3236 'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
Chris@76 3237 );
Chris@76 3238 }
Chris@76 3239 else
Chris@76 3240 {
Chris@76 3241 $context['user']['messages'] = 0;
Chris@76 3242 $context['user']['unread_messages'] = 0;
Chris@76 3243 $context['user']['avatar'] = array();
Chris@76 3244 $context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
Chris@76 3245 $context['user']['popup_messages'] = false;
Chris@76 3246
Chris@76 3247 if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
Chris@76 3248 $txt['welcome_guest'] .= $txt['welcome_guest_activate'];
Chris@76 3249
Chris@76 3250 // If we've upgraded recently, go easy on the passwords.
Chris@76 3251 if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
Chris@76 3252 $context['disable_login_hashing'] = true;
Chris@76 3253 elseif ($context['browser']['is_ie5'] || $context['browser']['is_ie5.5'])
Chris@76 3254 $context['disable_login_hashing'] = true;
Chris@76 3255 }
Chris@76 3256
Chris@76 3257 // Setup the main menu items.
Chris@76 3258 setupMenuContext();
Chris@76 3259
Chris@76 3260 if (empty($settings['theme_version']))
Chris@76 3261 $context['show_vBlogin'] = $context['show_quick_login'];
Chris@76 3262
Chris@76 3263 // This is here because old index templates might still use it.
Chris@76 3264 $context['show_news'] = !empty($settings['enable_news']);
Chris@76 3265
Chris@76 3266 // This is done to allow theme authors to customize it as they want.
Chris@76 3267 $context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
Chris@76 3268
Chris@76 3269 // Resize avatars the fancy, but non-GD requiring way.
Chris@76 3270 if ($modSettings['avatar_action_too_large'] == 'option_js_resize' && (!empty($modSettings['avatar_max_width_external']) || !empty($modSettings['avatar_max_height_external'])))
Chris@76 3271 {
Chris@76 3272 $context['html_headers'] .= '
Chris@76 3273 <script type="text/javascript"><!-- // --><![CDATA[
Chris@76 3274 var smf_avatarMaxWidth = ' . (int) $modSettings['avatar_max_width_external'] . ';
Chris@76 3275 var smf_avatarMaxHeight = ' . (int) $modSettings['avatar_max_height_external'] . ';';
Chris@76 3276
Chris@76 3277 if (!$context['browser']['is_ie'] && !$context['browser']['is_mac_ie'])
Chris@76 3278 $context['html_headers'] .= '
Chris@76 3279 window.addEventListener("load", smf_avatarResize, false);';
Chris@76 3280 else
Chris@76 3281 $context['html_headers'] .= '
Chris@76 3282 var window_oldAvatarOnload = window.onload;
Chris@76 3283 window.onload = smf_avatarResize;';
Chris@76 3284
Chris@76 3285 // !!! Move this over to script.js?
Chris@76 3286 $context['html_headers'] .= '
Chris@76 3287 // ]]></script>';
Chris@76 3288 }
Chris@76 3289
Chris@76 3290 // This looks weird, but it's because BoardIndex.php references the variable.
Chris@76 3291 $context['common_stats']['latest_member'] = array(
Chris@76 3292 'id' => $modSettings['latestMember'],
Chris@76 3293 'name' => $modSettings['latestRealName'],
Chris@76 3294 'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
Chris@76 3295 'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
Chris@76 3296 );
Chris@76 3297 $context['common_stats'] = array(
Chris@76 3298 'total_posts' => comma_format($modSettings['totalMessages']),
Chris@76 3299 'total_topics' => comma_format($modSettings['totalTopics']),
Chris@76 3300 'total_members' => comma_format($modSettings['totalMembers']),
Chris@76 3301 'latest_member' => $context['common_stats']['latest_member'],
Chris@76 3302 );
Chris@76 3303
Chris@76 3304 if (empty($settings['theme_version']))
Chris@76 3305 $context['html_headers'] .= '
Chris@76 3306 <script type="text/javascript"><!-- // --><![CDATA[
Chris@76 3307 var smf_scripturl = "' . $scripturl . '";
Chris@76 3308 // ]]></script>';
Chris@76 3309
Chris@76 3310 if (!isset($context['page_title']))
Chris@76 3311 $context['page_title'] = '';
Chris@76 3312
Chris@76 3313 // Set some specific vars.
Chris@76 3314 $context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title']));
Chris@76 3315 $context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
Chris@76 3316 }
Chris@76 3317
Chris@76 3318 // This is the only template included in the sources...
Chris@76 3319 function template_rawdata()
Chris@76 3320 {
Chris@76 3321 global $context;
Chris@76 3322
Chris@76 3323 echo $context['raw_data'];
Chris@76 3324 }
Chris@76 3325
Chris@76 3326 function template_header()
Chris@76 3327 {
Chris@76 3328 global $txt, $modSettings, $context, $settings, $user_info, $boarddir, $cachedir;
Chris@76 3329
Chris@76 3330 setupThemeContext();
Chris@76 3331
Chris@76 3332 // Print stuff to prevent caching of pages (except on attachment errors, etc.)
Chris@76 3333 if (empty($context['no_last_modified']))
Chris@76 3334 {
Chris@76 3335 header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
Chris@76 3336 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
Chris@76 3337
Chris@76 3338 // Are we debugging the template/html content?
Chris@76 3339 if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !$context['browser']['is_ie'] && !WIRELESS)
Chris@76 3340 header('Content-Type: application/xhtml+xml');
Chris@76 3341 elseif (!isset($_REQUEST['xml']) && !WIRELESS)
Chris@76 3342 header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
Chris@76 3343 }
Chris@76 3344
Chris@76 3345 header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
Chris@76 3346
Chris@76 3347 $checked_securityFiles = false;
Chris@76 3348 $showed_banned = false;
Chris@76 3349 foreach ($context['template_layers'] as $layer)
Chris@76 3350 {
Chris@76 3351 loadSubTemplate($layer . '_above', true);
Chris@76 3352
Chris@76 3353 // May seem contrived, but this is done in case the body and main layer aren't there...
Chris@76 3354 if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
Chris@76 3355 {
Chris@76 3356 $checked_securityFiles = true;
Chris@76 3357 $securityFiles = array('install.php', 'webinstall.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
Chris@76 3358 foreach ($securityFiles as $i => $securityFile)
Chris@76 3359 {
Chris@76 3360 if (!file_exists($boarddir . '/' . $securityFile))
Chris@76 3361 unset($securityFiles[$i]);
Chris@76 3362 }
Chris@76 3363
Chris@76 3364 if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)))
Chris@76 3365 {
Chris@76 3366 echo '
Chris@76 3367 <div class="errorbox">
Chris@76 3368 <p class="alert">!!</p>
Chris@76 3369 <h3>', empty($securityFiles) ? $txt['cache_writable_head'] : $txt['security_risk'], '</h3>
Chris@76 3370 <p>';
Chris@76 3371
Chris@76 3372 foreach ($securityFiles as $securityFile)
Chris@76 3373 {
Chris@76 3374 echo '
Chris@76 3375 ', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br />';
Chris@76 3376
Chris@76 3377 if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
Chris@76 3378 echo '
Chris@76 3379 ', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br />';
Chris@76 3380 }
Chris@76 3381
Chris@76 3382 if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
Chris@76 3383 echo '
Chris@76 3384 <strong>', $txt['cache_writable'], '</strong><br />';
Chris@76 3385
Chris@76 3386 echo '
Chris@76 3387 </p>
Chris@76 3388 </div>';
Chris@76 3389 }
Chris@76 3390 }
Chris@76 3391 // If the user is banned from posting inform them of it.
Chris@76 3392 elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
Chris@76 3393 {
Chris@76 3394 $showed_banned = true;
Chris@76 3395 echo '
Chris@76 3396 <div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
Chris@76 3397 ', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
Chris@76 3398
Chris@76 3399 if (!empty($_SESSION['ban']['cannot_post']['reason']))
Chris@76 3400 echo '
Chris@76 3401 <div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
Chris@76 3402
Chris@76 3403 if (!empty($_SESSION['ban']['expire_time']))
Chris@76 3404 echo '
Chris@76 3405 <div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
Chris@76 3406 else
Chris@76 3407 echo '
Chris@76 3408 <div>', $txt['your_ban_expires_never'], '</div>';
Chris@76 3409
Chris@76 3410 echo '
Chris@76 3411 </div>';
Chris@76 3412 }
Chris@76 3413 }
Chris@76 3414
Chris@76 3415 if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template']))
Chris@76 3416 {
Chris@76 3417 $settings['theme_url'] = $settings['default_theme_url'];
Chris@76 3418 $settings['images_url'] = $settings['default_images_url'];
Chris@76 3419 $settings['theme_dir'] = $settings['default_theme_dir'];
Chris@76 3420 }
Chris@76 3421 }
Chris@76 3422
Chris@76 3423 // Show the copyright...
Chris@76 3424 function theme_copyright($get_it = false)
Chris@76 3425 {
Chris@76 3426 global $forum_copyright, $context, $boardurl, $forum_version, $txt, $modSettings;
Chris@76 3427
Chris@76 3428 // Don't display copyright for things like SSI.
Chris@76 3429 if (!isset($forum_version))
Chris@76 3430 return;
Chris@76 3431
Chris@76 3432 // Put in the version...
Chris@76 3433 $forum_copyright = sprintf($forum_copyright, $forum_version);
Chris@76 3434
Chris@76 3435 echo '
Chris@76 3436 <span class="smalltext" style="display: inline; visibility: visible; font-family: Verdana, Arial, sans-serif;">' . $forum_copyright . '
Chris@76 3437 </span>';
Chris@76 3438 }
Chris@76 3439
Chris@76 3440 function template_footer()
Chris@76 3441 {
Chris@76 3442 global $context, $settings, $modSettings, $time_start, $db_count;
Chris@76 3443
Chris@76 3444 // Show the load time? (only makes sense for the footer.)
Chris@76 3445 $context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
Chris@76 3446 $context['load_time'] = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3);
Chris@76 3447 $context['load_queries'] = $db_count;
Chris@76 3448
Chris@76 3449 if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template']))
Chris@76 3450 {
Chris@76 3451 $settings['theme_url'] = $settings['actual_theme_url'];
Chris@76 3452 $settings['images_url'] = $settings['actual_images_url'];
Chris@76 3453 $settings['theme_dir'] = $settings['actual_theme_dir'];
Chris@76 3454 }
Chris@76 3455
Chris@76 3456 foreach (array_reverse($context['template_layers']) as $layer)
Chris@76 3457 loadSubTemplate($layer . '_below', true);
Chris@76 3458
Chris@76 3459 }
Chris@76 3460
Chris@76 3461 // Debugging.
Chris@76 3462 function db_debug_junk()
Chris@76 3463 {
Chris@76 3464 global $context, $scripturl, $boarddir, $modSettings, $boarddir;
Chris@76 3465 global $db_cache, $db_count, $db_show_debug, $cache_count, $cache_hits, $txt;
Chris@76 3466
Chris@76 3467 // Add to Settings.php if you want to show the debugging information.
Chris@76 3468 if (!isset($db_show_debug) || $db_show_debug !== true || (isset($_GET['action']) && $_GET['action'] == 'viewquery') || WIRELESS)
Chris@76 3469 return;
Chris@76 3470
Chris@76 3471 if (empty($_SESSION['view_queries']))
Chris@76 3472 $_SESSION['view_queries'] = 0;
Chris@76 3473 if (empty($context['debug']['language_files']))
Chris@76 3474 $context['debug']['language_files'] = array();
Chris@76 3475 if (empty($context['debug']['sheets']))
Chris@76 3476 $context['debug']['sheets'] = array();
Chris@76 3477
Chris@76 3478 $files = get_included_files();
Chris@76 3479 $total_size = 0;
Chris@76 3480 for ($i = 0, $n = count($files); $i < $n; $i++)
Chris@76 3481 {
Chris@76 3482 if (file_exists($files[$i]))
Chris@76 3483 $total_size += filesize($files[$i]);
Chris@76 3484 $files[$i] = strtr($files[$i], array($boarddir => '.'));
Chris@76 3485 }
Chris@76 3486
Chris@76 3487 $warnings = 0;
Chris@76 3488 if (!empty($db_cache))
Chris@76 3489 {
Chris@76 3490 foreach ($db_cache as $q => $qq)
Chris@76 3491 {
Chris@76 3492 if (!empty($qq['w']))
Chris@76 3493 $warnings += count($qq['w']);
Chris@76 3494 }
Chris@76 3495
Chris@76 3496 $_SESSION['debug'] = &$db_cache;
Chris@76 3497 }
Chris@76 3498
Chris@76 3499 // Gotta have valid HTML ;).
Chris@76 3500 $temp = ob_get_contents();
Chris@76 3501 if (function_exists('ob_clean'))
Chris@76 3502 ob_clean();
Chris@76 3503 else
Chris@76 3504 {
Chris@76 3505 ob_end_clean();
Chris@76 3506 ob_start('ob_sessrewrite');
Chris@76 3507 }
Chris@76 3508
Chris@76 3509 echo preg_replace('~</body>\s*</html>~', '', $temp), '
Chris@76 3510 <div class="smalltext" style="text-align: left; margin: 1ex;">
Chris@76 3511 ', $txt['debug_templates'], count($context['debug']['templates']), ': <em>', implode('</em>, <em>', $context['debug']['templates']), '</em>.<br />
Chris@76 3512 ', $txt['debug_subtemplates'], count($context['debug']['sub_templates']), ': <em>', implode('</em>, <em>', $context['debug']['sub_templates']), '</em>.<br />
Chris@76 3513 ', $txt['debug_language_files'], count($context['debug']['language_files']), ': <em>', implode('</em>, <em>', $context['debug']['language_files']), '</em>.<br />
Chris@76 3514 ', $txt['debug_stylesheets'], count($context['debug']['sheets']), ': <em>', implode('</em>, <em>', $context['debug']['sheets']), '</em>.<br />
Chris@76 3515 ', $txt['debug_files_included'], count($files), ' - ', round($total_size / 1024), $txt['debug_kb'], ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_include_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_include_info" style="display: none;"><em>', implode('</em>, <em>', $files), '</em></span>)<br />';
Chris@76 3516
Chris@76 3517 if (!empty($modSettings['cache_enable']) && !empty($cache_hits))
Chris@76 3518 {
Chris@76 3519 $entries = array();
Chris@76 3520 $total_t = 0;
Chris@76 3521 $total_s = 0;
Chris@76 3522 foreach ($cache_hits as $cache_hit)
Chris@76 3523 {
Chris@76 3524 $entries[] = $cache_hit['d'] . ' ' . $cache_hit['k'] . ': ' . sprintf($txt['debug_cache_seconds_bytes'], comma_format($cache_hit['t'], 5), $cache_hit['s']);
Chris@76 3525 $total_t += $cache_hit['t'];
Chris@76 3526 $total_s += $cache_hit['s'];
Chris@76 3527 }
Chris@76 3528
Chris@76 3529 echo '
Chris@76 3530 ', $txt['debug_cache_hits'], $cache_count, ': ', sprintf($txt['debug_cache_seconds_bytes_total'], comma_format($total_t, 5), comma_format($total_s)), ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_cache_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_cache_info" style="display: none;"><em>', implode('</em>, <em>', $entries), '</em></span>)<br />';
Chris@76 3531 }
Chris@76 3532
Chris@76 3533 echo '
Chris@76 3534 <a href="', $scripturl, '?action=viewquery" target="_blank" class="new_win">', $warnings == 0 ? sprintf($txt['debug_queries_used'], (int) $db_count) : sprintf($txt['debug_queries_used_and_warnings'], (int) $db_count, $warnings), '</a><br />
Chris@76 3535 <br />';
Chris@76 3536
Chris@76 3537 if ($_SESSION['view_queries'] == 1 && !empty($db_cache))
Chris@76 3538 foreach ($db_cache as $q => $qq)
Chris@76 3539 {
Chris@76 3540 $is_select = substr(trim($qq['q']), 0, 6) == 'SELECT' || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($qq['q'])) != 0;
Chris@76 3541 // Temporary tables created in earlier queries are not explainable.
Chris@76 3542 if ($is_select)
Chris@76 3543 {
Chris@76 3544 foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp)
Chris@76 3545 if (strpos(trim($qq['q']), $tmp) !== false)
Chris@76 3546 {
Chris@76 3547 $is_select = false;
Chris@76 3548 break;
Chris@76 3549 }
Chris@76 3550 }
Chris@76 3551 // But actual creation of the temporary tables are.
Chris@76 3552 elseif (preg_match('~^CREATE TEMPORARY TABLE .+?SELECT .+$~s', trim($qq['q'])) != 0)
Chris@76 3553 $is_select = true;
Chris@76 3554
Chris@76 3555 // Make the filenames look a bit better.
Chris@76 3556 if (isset($qq['f']))
Chris@76 3557 $qq['f'] = preg_replace('~^' . preg_quote($boarddir, '~') . '~', '...', $qq['f']);
Chris@76 3558
Chris@76 3559 echo '
Chris@76 3560 <strong>', $is_select ? '<a href="' . $scripturl . '?action=viewquery;qq=' . ($q + 1) . '#qq' . $q . '" target="_blank" class="new_win" style="text-decoration: none;">' : '', nl2br(str_replace("\t", '&nbsp;&nbsp;&nbsp;', htmlspecialchars(ltrim($qq['q'], "\n\r")))) . ($is_select ? '</a></strong>' : '</strong>') . '<br />
Chris@76 3561 &nbsp;&nbsp;&nbsp;';
Chris@76 3562 if (!empty($qq['f']) && !empty($qq['l']))
Chris@76 3563 echo sprintf($txt['debug_query_in_line'], $qq['f'], $qq['l']);
Chris@76 3564
Chris@76 3565 if (isset($qq['s'], $qq['t']) && isset($txt['debug_query_which_took_at']))
Chris@76 3566 echo sprintf($txt['debug_query_which_took_at'], round($qq['t'], 8), round($qq['s'], 8)) . '<br />';
Chris@76 3567 elseif (isset($qq['t']))
Chris@76 3568 echo sprintf($txt['debug_query_which_took'], round($qq['t'], 8)) . '<br />';
Chris@76 3569 echo '
Chris@76 3570 <br />';
Chris@76 3571 }
Chris@76 3572
Chris@76 3573 echo '
Chris@76 3574 <a href="' . $scripturl . '?action=viewquery;sa=hide">', $txt['debug_' . (empty($_SESSION['view_queries']) ? 'show' : 'hide') . '_queries'], '</a>
Chris@76 3575 </div></body></html>';
Chris@76 3576 }
Chris@76 3577
Chris@76 3578 // Get an attachment's encrypted filename. If $new is true, won't check for file existence.
Chris@76 3579 function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
Chris@76 3580 {
Chris@76 3581 global $modSettings, $smcFunc;
Chris@76 3582
Chris@76 3583 // Just make up a nice hash...
Chris@76 3584 if ($new)
Chris@76 3585 return sha1(md5($filename . time()) . mt_rand());
Chris@76 3586
Chris@76 3587 // Grab the file hash if it wasn't added.
Chris@76 3588 if ($file_hash === '')
Chris@76 3589 {
Chris@76 3590 $request = $smcFunc['db_query']('', '
Chris@76 3591 SELECT file_hash
Chris@76 3592 FROM {db_prefix}attachments
Chris@76 3593 WHERE id_attach = {int:id_attach}',
Chris@76 3594 array(
Chris@76 3595 'id_attach' => $attachment_id,
Chris@76 3596 ));
Chris@76 3597
Chris@76 3598 if ($smcFunc['db_num_rows']($request) === 0)
Chris@76 3599 return false;
Chris@76 3600
Chris@76 3601 list ($file_hash) = $smcFunc['db_fetch_row']($request);
Chris@76 3602 $smcFunc['db_free_result']($request);
Chris@76 3603 }
Chris@76 3604
Chris@76 3605 // In case of files from the old system, do a legacy call.
Chris@76 3606 if (empty($file_hash))
Chris@76 3607 return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new);
Chris@76 3608
Chris@76 3609 // Are we using multiple directories?
Chris@76 3610 if (!empty($modSettings['currentAttachmentUploadDir']))
Chris@76 3611 {
Chris@76 3612 if (!is_array($modSettings['attachmentUploadDir']))
Chris@76 3613 $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
Chris@76 3614 $path = $modSettings['attachmentUploadDir'][$dir];
Chris@76 3615 }
Chris@76 3616 else
Chris@76 3617 $path = $modSettings['attachmentUploadDir'];
Chris@76 3618
Chris@76 3619 return $path . '/' . $attachment_id . '_' . $file_hash;
Chris@76 3620 }
Chris@76 3621
Chris@76 3622 // Older attachments may still use this function.
Chris@76 3623 function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false)
Chris@76 3624 {
Chris@76 3625 global $modSettings, $db_character_set;
Chris@76 3626
Chris@76 3627 $clean_name = $filename;
Chris@76 3628 // Remove international characters (windows-1252)
Chris@76 3629 // These lines should never be needed again. Still, behave.
Chris@76 3630 if (empty($db_character_set) || $db_character_set != 'utf8')
Chris@76 3631 {
Chris@76 3632 $clean_name = strtr($filename,
Chris@76 3633 "\x8a\x8e\x9a\x9e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xe0\xe1\xe2\xe3\xe4\xe5\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xff",
Chris@76 3634 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
Chris@76 3635 $clean_name = strtr($clean_name, array("\xde" => 'TH', "\xfe" =>
Chris@76 3636 'th', "\xd0" => 'DH', "\xf0" => 'dh', "\xdf" => 'ss', "\x8c" => 'OE',
Chris@76 3637 "\x9c" => 'oe', "\c6" => 'AE', "\xe6" => 'ae', "\xb5" => 'u'));
Chris@76 3638 }
Chris@76 3639 // Sorry, no spaces, dots, or anything else but letters allowed.
Chris@76 3640 $clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
Chris@76 3641
Chris@76 3642 $enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
Chris@76 3643 $clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
Chris@76 3644
Chris@76 3645 if ($attachment_id == false || ($new && empty($modSettings['attachmentEncryptFilenames'])))
Chris@76 3646 return $clean_name;
Chris@76 3647 elseif ($new)
Chris@76 3648 return $enc_name;
Chris@76 3649
Chris@76 3650 // Are we using multiple directories?
Chris@76 3651 if (!empty($modSettings['currentAttachmentUploadDir']))
Chris@76 3652 {
Chris@76 3653 if (!is_array($modSettings['attachmentUploadDir']))
Chris@76 3654 $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
Chris@76 3655 $path = $modSettings['attachmentUploadDir'][$dir];
Chris@76 3656 }
Chris@76 3657 else
Chris@76 3658 $path = $modSettings['attachmentUploadDir'];
Chris@76 3659
Chris@76 3660 if (file_exists($path . '/' . $enc_name))
Chris@76 3661 $filename = $path . '/' . $enc_name;
Chris@76 3662 else
Chris@76 3663 $filename = $path . '/' . $clean_name;
Chris@76 3664
Chris@76 3665 return $filename;
Chris@76 3666 }
Chris@76 3667
Chris@76 3668 // Convert a single IP to a ranged IP.
Chris@76 3669 function ip2range($fullip)
Chris@76 3670 {
Chris@76 3671 // Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
Chris@76 3672 if ($fullip == 'unknown')
Chris@76 3673 $fullip = '255.255.255.255';
Chris@76 3674
Chris@76 3675 $ip_parts = explode('.', $fullip);
Chris@76 3676 $ip_array = array();
Chris@76 3677
Chris@76 3678 if (count($ip_parts) != 4)
Chris@76 3679 return array();
Chris@76 3680
Chris@76 3681 for ($i = 0; $i < 4; $i++)
Chris@76 3682 {
Chris@76 3683 if ($ip_parts[$i] == '*')
Chris@76 3684 $ip_array[$i] = array('low' => '0', 'high' => '255');
Chris@76 3685 elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1)
Chris@76 3686 $ip_array[$i] = array('low' => $range[1], 'high' => $range[2]);
Chris@76 3687 elseif (is_numeric($ip_parts[$i]))
Chris@76 3688 $ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]);
Chris@76 3689 }
Chris@76 3690
Chris@76 3691 return $ip_array;
Chris@76 3692 }
Chris@76 3693
Chris@76 3694 // Lookup an IP; try shell_exec first because we can do a timeout on it.
Chris@76 3695 function host_from_ip($ip)
Chris@76 3696 {
Chris@76 3697 global $modSettings;
Chris@76 3698
Chris@76 3699 if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
Chris@76 3700 return $host;
Chris@76 3701 $t = microtime();
Chris@76 3702
Chris@76 3703 // If we can't access nslookup/host, PHP 4.1.x might just crash.
Chris@76 3704 if (@version_compare(PHP_VERSION, '4.2.0') == -1)
Chris@76 3705 $host = false;
Chris@76 3706
Chris@76 3707 // Try the Linux host command, perhaps?
Chris@76 3708 if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
Chris@76 3709 {
Chris@76 3710 if (!isset($modSettings['host_to_dis']))
Chris@76 3711 $test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
Chris@76 3712 else
Chris@76 3713 $test = @shell_exec('host ' . @escapeshellarg($ip));
Chris@76 3714
Chris@76 3715 // Did host say it didn't find anything?
Chris@76 3716 if (strpos($test, 'not found') !== false)
Chris@76 3717 $host = '';
Chris@76 3718 // Invalid server option?
Chris@76 3719 elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
Chris@76 3720 updateSettings(array('host_to_dis' => 1));
Chris@76 3721 // Maybe it found something, after all?
Chris@76 3722 elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
Chris@76 3723 $host = $match[1];
Chris@76 3724 }
Chris@76 3725
Chris@76 3726 // This is nslookup; usually only Windows, but possibly some Unix?
Chris@76 3727 if (!isset($host) && strpos(strtolower(PHP_OS), 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
Chris@76 3728 {
Chris@76 3729 $test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
Chris@76 3730 if (strpos($test, 'Non-existent domain') !== false)
Chris@76 3731 $host = '';
Chris@76 3732 elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
Chris@76 3733 $host = $match[1];
Chris@76 3734 }
Chris@76 3735
Chris@76 3736 // This is the last try :/.
Chris@76 3737 if (!isset($host) || $host === false)
Chris@76 3738 $host = @gethostbyaddr($ip);
Chris@76 3739
Chris@76 3740 // It took a long time, so let's cache it!
Chris@76 3741 if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
Chris@76 3742 cache_put_data('hostlookup-' . $ip, $host, 600);
Chris@76 3743
Chris@76 3744 return $host;
Chris@76 3745 }
Chris@76 3746
Chris@76 3747 // Chops a string into words and prepares them to be inserted into (or searched from) the database.
Chris@76 3748 function text2words($text, $max_chars = 20, $encrypt = false)
Chris@76 3749 {
Chris@76 3750 global $smcFunc, $context;
Chris@76 3751
Chris@76 3752 // Step 1: Remove entities/things we don't consider words:
Chris@76 3753 $words = preg_replace('~(?:[\x0B\0' . ($context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : "\xC2\xA0") : '\xA0') . '\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~' . ($context['utf8'] ? 'u' : ''), ' ', strtr($text, array('<br />' => ' ')));
Chris@76 3754
Chris@76 3755 // Step 2: Entities we left to letters, where applicable, lowercase.
Chris@76 3756 $words = un_htmlspecialchars($smcFunc['strtolower']($words));
Chris@76 3757
Chris@76 3758 // Step 3: Ready to split apart and index!
Chris@76 3759 $words = explode(' ', $words);
Chris@76 3760
Chris@76 3761 if ($encrypt)
Chris@76 3762 {
Chris@76 3763 $possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
Chris@76 3764 $returned_ints = array();
Chris@76 3765 foreach ($words as $word)
Chris@76 3766 {
Chris@76 3767 if (($word = trim($word, '-_\'')) !== '')
Chris@76 3768 {
Chris@76 3769 $encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
Chris@76 3770 $total = 0;
Chris@76 3771 for ($i = 0; $i < $max_chars; $i++)
Chris@76 3772 $total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
Chris@76 3773 $returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
Chris@76 3774 }
Chris@76 3775 }
Chris@76 3776 return array_unique($returned_ints);
Chris@76 3777 }
Chris@76 3778 else
Chris@76 3779 {
Chris@76 3780 // Trim characters before and after and add slashes for database insertion.
Chris@76 3781 $returned_words = array();
Chris@76 3782 foreach ($words as $word)
Chris@76 3783 if (($word = trim($word, '-_\'')) !== '')
Chris@76 3784 $returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
Chris@76 3785
Chris@76 3786 // Filter out all words that occur more than once.
Chris@76 3787 return array_unique($returned_words);
Chris@76 3788 }
Chris@76 3789 }
Chris@76 3790
Chris@76 3791 // Creates an image/text button
Chris@76 3792 function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
Chris@76 3793 {
Chris@76 3794 global $settings, $txt, $context;
Chris@76 3795
Chris@76 3796 // Does the current loaded theme have this and we are not forcing the usage of this function?
Chris@76 3797 if (function_exists('template_create_button') && !$force_use)
Chris@76 3798 return template_create_button($name, $alt, $label = '', $custom = '');
Chris@76 3799
Chris@76 3800 if (!$settings['use_image_buttons'])
Chris@76 3801 return $txt[$alt];
Chris@76 3802 elseif (!empty($settings['use_buttons']))
Chris@76 3803 return '<img src="' . $settings['images_url'] . '/buttons/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . ' />' . ($label != '' ? '<strong>' . $txt[$label] . '</strong>' : '');
Chris@76 3804 else
Chris@76 3805 return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . ' />';
Chris@76 3806 }
Chris@76 3807
Chris@76 3808 // Empty out the cache folder.
Chris@76 3809 function clean_cache($type = '')
Chris@76 3810 {
Chris@76 3811 global $cachedir, $sourcedir;
Chris@76 3812
Chris@76 3813 // No directory = no game.
Chris@76 3814 if (!is_dir($cachedir))
Chris@76 3815 return;
Chris@76 3816
Chris@76 3817 // Remove the files in SMF's own disk cache, if any
Chris@76 3818 $dh = opendir($cachedir);
Chris@76 3819 while ($file = readdir($dh))
Chris@76 3820 {
Chris@76 3821 if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
Chris@76 3822 @unlink($cachedir . '/' . $file);
Chris@76 3823 }
Chris@76 3824 closedir($dh);
Chris@76 3825
Chris@76 3826 // Invalidate cache, to be sure!
Chris@76 3827 // ... as long as Load.php can be modified, anyway.
Chris@76 3828 @touch($sourcedir . '/' . 'Load.php');
Chris@76 3829 clearstatcache();
Chris@76 3830 }
Chris@76 3831
Chris@76 3832 // Load classes that are both (E_STRICT) PHP 4 and PHP 5 compatible.
Chris@76 3833 function loadClassFile($filename)
Chris@76 3834 {
Chris@76 3835 global $sourcedir;
Chris@76 3836 static $files_included = array();
Chris@76 3837
Chris@76 3838 if (!file_exists($sourcedir . '/' . $filename))
Chris@76 3839 fatal_lang_error('error_bad_file', 'general', array($sourcedir . '/' . $filename));
Chris@76 3840
Chris@76 3841 // Using a version below PHP 5.0? Do a compatibility conversion.
Chris@76 3842 if (@version_compare(PHP_VERSION, '5.0.0') != 1)
Chris@76 3843 {
Chris@76 3844 // Check if it was included before.
Chris@76 3845 if (in_array($filename, $files_included))
Chris@76 3846 return;
Chris@76 3847
Chris@76 3848 // Make sure we don't include it again.
Chris@76 3849 $files_included[] = $filename;
Chris@76 3850
Chris@76 3851 // Do some replacements to make it PHP 4 compatible.
Chris@76 3852 eval('?' . '>' . preg_replace(array(
Chris@76 3853 '~class\s+([\w-_]+)([^}]+)function\s+__construct\s*\(~',
Chris@76 3854 '~([\s\t]+)public\s+\$~',
Chris@76 3855 '~([\s\t]+)private\s+\$~',
Chris@76 3856 '~([\s\t]+)protected\s+\$~',
Chris@76 3857 '~([\s\t]+)public\s+function\s+~',
Chris@76 3858 '~([\s\t]+)private\s+function\s+~',
Chris@76 3859 '~([\s\t]+)protected\s+function\s+~',
Chris@76 3860 ), array(
Chris@76 3861 'class $1$2function $1(',
Chris@76 3862 '$1var $',
Chris@76 3863 '$1var $',
Chris@76 3864 '$1var $',
Chris@76 3865 '$1function ',
Chris@76 3866 '$1function ',
Chris@76 3867 '$1function ',
Chris@76 3868 ), rtrim(file_get_contents($sourcedir . '/' . $filename))));
Chris@76 3869 }
Chris@76 3870 else
Chris@76 3871 require_once($sourcedir . '/' . $filename);
Chris@76 3872 }
Chris@76 3873
Chris@76 3874 function setupMenuContext()
Chris@76 3875 {
Chris@76 3876 global $context, $modSettings, $user_info, $txt, $scripturl;
Chris@76 3877
Chris@76 3878 // Set up the menu privileges.
Chris@76 3879 $context['allow_search'] = allowedTo('search_posts');
Chris@76 3880 $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'));
Chris@76 3881 $context['allow_edit_profile'] = !$user_info['is_guest'] && allowedTo(array('profile_view_own', 'profile_view_any', 'profile_identity_own', 'profile_identity_any', 'profile_extra_own', 'profile_extra_any', 'profile_remove_own', 'profile_remove_any', 'moderate_forum', 'manage_membergroups', 'profile_title_own', 'profile_title_any'));
Chris@76 3882 $context['allow_memberlist'] = allowedTo('view_mlist');
Chris@76 3883 $context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
Chris@76 3884 $context['allow_moderation_center'] = $context['user']['can_mod'];
Chris@76 3885 $context['allow_pm'] = allowedTo('pm_read');
Chris@76 3886
Chris@76 3887 $cacheTime = $modSettings['lastActive'] * 60;
Chris@76 3888
Chris@76 3889 // All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
Chris@76 3890 if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
Chris@76 3891 {
Chris@76 3892 $buttons = array(
Chris@76 3893 'home' => array(
Chris@76 3894 'title' => $txt['home'],
Chris@76 3895 'href' => $scripturl,
Chris@76 3896 'show' => true,
Chris@76 3897 'sub_buttons' => array(
Chris@76 3898 ),
Chris@76 3899 'is_last' => $context['right_to_left'],
Chris@76 3900 ),
Chris@76 3901 'help' => array(
Chris@76 3902 'title' => $txt['help'],
Chris@76 3903 'href' => $scripturl . '?action=help',
Chris@76 3904 'show' => true,
Chris@76 3905 'sub_buttons' => array(
Chris@76 3906 ),
Chris@76 3907 ),
Chris@76 3908 'search' => array(
Chris@76 3909 'title' => $txt['search'],
Chris@76 3910 'href' => $scripturl . '?action=search',
Chris@76 3911 'show' => $context['allow_search'],
Chris@76 3912 'sub_buttons' => array(
Chris@76 3913 ),
Chris@76 3914 ),
Chris@76 3915 'admin' => array(
Chris@76 3916 'title' => $txt['admin'],
Chris@76 3917 'href' => $scripturl . '?action=admin',
Chris@76 3918 'show' => $context['allow_admin'],
Chris@76 3919 'sub_buttons' => array(
Chris@76 3920 'featuresettings' => array(
Chris@76 3921 'title' => $txt['modSettings_title'],
Chris@76 3922 'href' => $scripturl . '?action=admin;area=featuresettings',
Chris@76 3923 'show' => allowedTo('admin_forum'),
Chris@76 3924 ),
Chris@76 3925 'packages' => array(
Chris@76 3926 'title' => $txt['package'],
Chris@76 3927 'href' => $scripturl . '?action=admin;area=packages',
Chris@76 3928 'show' => allowedTo('admin_forum'),
Chris@76 3929 ),
Chris@76 3930 'errorlog' => array(
Chris@76 3931 'title' => $txt['errlog'],
Chris@76 3932 'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
Chris@76 3933 'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
Chris@76 3934 ),
Chris@76 3935 'permissions' => array(
Chris@76 3936 'title' => $txt['edit_permissions'],
Chris@76 3937 'href' => $scripturl . '?action=admin;area=permissions',
Chris@76 3938 'show' => allowedTo('manage_permissions'),
Chris@76 3939 'is_last' => true,
Chris@76 3940 ),
Chris@76 3941 ),
Chris@76 3942 ),
Chris@76 3943 'moderate' => array(
Chris@76 3944 'title' => $txt['moderate'],
Chris@76 3945 'href' => $scripturl . '?action=moderate',
Chris@76 3946 'show' => $context['allow_moderation_center'],
Chris@76 3947 'sub_buttons' => array(
Chris@76 3948 'modlog' => array(
Chris@76 3949 'title' => $txt['modlog_view'],
Chris@76 3950 'href' => $scripturl . '?action=moderate;area=modlog',
Chris@76 3951 'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
Chris@76 3952 ),
Chris@76 3953 'poststopics' => array(
Chris@76 3954 'title' => $txt['mc_unapproved_poststopics'],
Chris@76 3955 'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
Chris@76 3956 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
Chris@76 3957 ),
Chris@76 3958 'attachments' => array(
Chris@76 3959 'title' => $txt['mc_unapproved_attachments'],
Chris@76 3960 'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
Chris@76 3961 'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
Chris@76 3962 ),
Chris@76 3963 'reports' => array(
Chris@76 3964 'title' => $txt['mc_reported_posts'],
Chris@76 3965 'href' => $scripturl . '?action=moderate;area=reports',
Chris@76 3966 'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
Chris@76 3967 'is_last' => true,
Chris@76 3968 ),
Chris@76 3969 ),
Chris@76 3970 ),
Chris@76 3971 'profile' => array(
Chris@76 3972 'title' => $txt['profile'],
Chris@76 3973 'href' => $scripturl . '?action=profile',
Chris@76 3974 'show' => $context['allow_edit_profile'],
Chris@76 3975 'sub_buttons' => array(
Chris@76 3976 'summary' => array(
Chris@76 3977 'title' => $txt['summary'],
Chris@76 3978 'href' => $scripturl . '?action=profile',
Chris@76 3979 'show' => true,
Chris@76 3980 ),
Chris@76 3981 'account' => array(
Chris@76 3982 'title' => $txt['account'],
Chris@76 3983 'href' => $scripturl . '?action=profile;area=account',
Chris@76 3984 'show' => allowedTo(array('profile_identity_any', 'profile_identity_own', 'manage_membergroups')),
Chris@76 3985 ),
Chris@76 3986 'profile' => array(
Chris@76 3987 'title' => $txt['forumprofile'],
Chris@76 3988 'href' => $scripturl . '?action=profile;area=forumprofile',
Chris@76 3989 'show' => allowedTo(array('profile_extra_any', 'profile_extra_own')),
Chris@76 3990 'is_last' => true,
Chris@76 3991 ),
Chris@76 3992 ),
Chris@76 3993 ),
Chris@76 3994 'pm' => array(
Chris@76 3995 'title' => $txt['pm_short'],
Chris@76 3996 'href' => $scripturl . '?action=pm',
Chris@76 3997 'show' => $context['allow_pm'],
Chris@76 3998 'sub_buttons' => array(
Chris@76 3999 'pm_read' => array(
Chris@76 4000 'title' => $txt['pm_menu_read'],
Chris@76 4001 'href' => $scripturl . '?action=pm',
Chris@76 4002 'show' => allowedTo('pm_read'),
Chris@76 4003 ),
Chris@76 4004 'pm_send' => array(
Chris@76 4005 'title' => $txt['pm_menu_send'],
Chris@76 4006 'href' => $scripturl . '?action=pm;sa=send',
Chris@76 4007 'show' => allowedTo('pm_send'),
Chris@76 4008 'is_last' => true,
Chris@76 4009 ),
Chris@76 4010 ),
Chris@76 4011 ),
Chris@76 4012 'calendar' => array(
Chris@76 4013 'title' => $txt['calendar'],
Chris@76 4014 'href' => $scripturl . '?action=calendar',
Chris@76 4015 'show' => $context['allow_calendar'],
Chris@76 4016 'sub_buttons' => array(
Chris@76 4017 'view' => array(
Chris@76 4018 'title' => $txt['calendar_menu'],
Chris@76 4019 'href' => $scripturl . '?action=calendar',
Chris@76 4020 'show' => allowedTo('calendar_post'),
Chris@76 4021 ),
Chris@76 4022 'post' => array(
Chris@76 4023 'title' => $txt['calendar_post_event'],
Chris@76 4024 'href' => $scripturl . '?action=calendar;sa=post',
Chris@76 4025 'show' => allowedTo('calendar_post'),
Chris@76 4026 'is_last' => true,
Chris@76 4027 ),
Chris@76 4028 ),
Chris@76 4029 ),
Chris@76 4030 'mlist' => array(
Chris@76 4031 'title' => $txt['members_title'],
Chris@76 4032 'href' => $scripturl . '?action=mlist',
Chris@76 4033 'show' => $context['allow_memberlist'],
Chris@76 4034 'sub_buttons' => array(
Chris@76 4035 'mlist_view' => array(
Chris@76 4036 'title' => $txt['mlist_menu_view'],
Chris@76 4037 'href' => $scripturl . '?action=mlist',
Chris@76 4038 'show' => true,
Chris@76 4039 ),
Chris@76 4040 'mlist_search' => array(
Chris@76 4041 'title' => $txt['mlist_search'],
Chris@76 4042 'href' => $scripturl . '?action=mlist;sa=search',
Chris@76 4043 'show' => true,
Chris@76 4044 'is_last' => true,
Chris@76 4045 ),
Chris@76 4046 ),
Chris@76 4047 ),
Chris@76 4048 'login' => array(
Chris@76 4049 'title' => $txt['login'],
Chris@76 4050 'href' => $scripturl . '?action=login',
Chris@76 4051 'show' => $user_info['is_guest'],
Chris@76 4052 'sub_buttons' => array(
Chris@76 4053 ),
Chris@76 4054 ),
Chris@76 4055 'register' => array(
Chris@76 4056 'title' => $txt['register'],
Chris@76 4057 'href' => $scripturl . '?action=register',
Chris@76 4058 'show' => $user_info['is_guest'],
Chris@76 4059 'sub_buttons' => array(
Chris@76 4060 ),
Chris@76 4061 'is_last' => !$context['right_to_left'],
Chris@76 4062 ),
Chris@76 4063 'logout' => array(
Chris@76 4064 'title' => $txt['logout'],
Chris@76 4065 'href' => $scripturl . '?action=logout;%1$s=%2$s',
Chris@76 4066 'show' => !$user_info['is_guest'],
Chris@76 4067 'sub_buttons' => array(
Chris@76 4068 ),
Chris@76 4069 'is_last' => !$context['right_to_left'],
Chris@76 4070 ),
Chris@76 4071 );
Chris@76 4072
Chris@76 4073 // Allow editing menu buttons easily.
Chris@76 4074 call_integration_hook('integrate_menu_buttons', array(&$buttons));
Chris@76 4075
Chris@76 4076 // Now we put the buttons in the context so the theme can use them.
Chris@76 4077 $menu_buttons = array();
Chris@76 4078 foreach ($buttons as $act => $button)
Chris@76 4079 if (!empty($button['show']))
Chris@76 4080 {
Chris@76 4081 $button['active_button'] = false;
Chris@76 4082
Chris@76 4083 // Make sure the last button truely is the last button.
Chris@76 4084 if (!empty($button['is_last']))
Chris@76 4085 {
Chris@76 4086 if (isset($last_button))
Chris@76 4087 unset($menu_buttons[$last_button]['is_last']);
Chris@76 4088 $last_button = $act;
Chris@76 4089 }
Chris@76 4090
Chris@76 4091 // Go through the sub buttons if there are any.
Chris@76 4092 if (!empty($button['sub_buttons']))
Chris@76 4093 foreach ($button['sub_buttons'] as $key => $subbutton)
Chris@76 4094 {
Chris@76 4095 if (empty($subbutton['show']))
Chris@76 4096 unset($button['sub_buttons'][$key]);
Chris@76 4097
Chris@76 4098 // 2nd level sub buttons next...
Chris@76 4099 if (!empty($subbutton['sub_buttons']))
Chris@76 4100 {
Chris@76 4101 foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
Chris@76 4102 {
Chris@76 4103 if (empty($sub_button2['show']))
Chris@76 4104 unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
Chris@76 4105 }
Chris@76 4106 }
Chris@76 4107 }
Chris@76 4108
Chris@76 4109 $menu_buttons[$act] = $button;
Chris@76 4110 }
Chris@76 4111
Chris@76 4112 if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
Chris@76 4113 cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
Chris@76 4114 }
Chris@76 4115
Chris@76 4116 $context['menu_buttons'] = $menu_buttons;
Chris@76 4117
Chris@76 4118 // Logging out requires the session id in the url.
Chris@76 4119 if (isset($context['menu_buttons']['logout']))
Chris@76 4120 $context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
Chris@76 4121
Chris@76 4122 // Figure out which action we are doing so we can set the active tab.
Chris@76 4123 // Default to home.
Chris@76 4124 $current_action = 'home';
Chris@76 4125
Chris@76 4126 if (isset($context['menu_buttons'][$context['current_action']]))
Chris@76 4127 $current_action = $context['current_action'];
Chris@76 4128 elseif ($context['current_action'] == 'search2')
Chris@76 4129 $current_action = 'search';
Chris@76 4130 elseif ($context['current_action'] == 'theme')
Chris@76 4131 $current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
Chris@76 4132 elseif ($context['current_action'] == 'register2')
Chris@76 4133 $current_action = 'register';
Chris@76 4134 elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
Chris@76 4135 $current_action = 'login';
Chris@76 4136 elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
Chris@76 4137 $current_action = 'moderate';
Chris@76 4138
Chris@76 4139 $context['menu_buttons'][$current_action]['active_button'] = true;
Chris@76 4140
Chris@76 4141 if (!$user_info['is_guest'] && $context['user']['unread_messages'] > 0 && isset($context['menu_buttons']['pm']))
Chris@76 4142 {
Chris@76 4143 $context['menu_buttons']['pm']['alttitle'] = $context['menu_buttons']['pm']['title'] . ' [' . $context['user']['unread_messages'] . ']';
Chris@76 4144 $context['menu_buttons']['pm']['title'] .= ' [<strong>' . $context['user']['unread_messages'] . '</strong>]';
Chris@76 4145 }
Chris@76 4146 }
Chris@76 4147
Chris@76 4148 // Generate a random seed and ensure it's stored in settings.
Chris@76 4149 function smf_seed_generator()
Chris@76 4150 {
Chris@76 4151 global $modSettings;
Chris@76 4152
Chris@76 4153 // Never existed?
Chris@76 4154 if (empty($modSettings['rand_seed']))
Chris@76 4155 {
Chris@76 4156 $modSettings['rand_seed'] = microtime() * 1000000;
Chris@76 4157 updateSettings(array('rand_seed' => $modSettings['rand_seed']));
Chris@76 4158 }
Chris@76 4159
Chris@76 4160 if (@version_compare(PHP_VERSION, '4.2.0') == -1)
Chris@76 4161 {
Chris@76 4162 $seed = ($modSettings['rand_seed'] + ((double) microtime() * 1000003)) & 0x7fffffff;
Chris@76 4163 mt_srand($seed);
Chris@76 4164 }
Chris@76 4165
Chris@76 4166 // Change the seed.
Chris@76 4167 updateSettings(array('rand_seed' => mt_rand()));
Chris@76 4168 }
Chris@76 4169
Chris@76 4170 // Process functions of an integration hook.
Chris@76 4171 function call_integration_hook($hook, $parameters = array())
Chris@76 4172 {
Chris@76 4173 global $modSettings;
Chris@76 4174
Chris@76 4175 $results = array();
Chris@76 4176 if (empty($modSettings[$hook]))
Chris@76 4177 return $results;
Chris@76 4178
Chris@76 4179 $functions = explode(',', $modSettings[$hook]);
Chris@76 4180
Chris@76 4181 // Loop through each function.
Chris@76 4182 foreach ($functions as $function)
Chris@76 4183 {
Chris@76 4184 $function = trim($function);
Chris@76 4185 $call = strpos($function, '::') !== false ? explode('::', $function) : $function;
Chris@76 4186
Chris@76 4187 // Is it valid?
Chris@76 4188 if (is_callable($call))
Chris@76 4189 $results[$function] = call_user_func_array($call, $parameters);
Chris@76 4190 }
Chris@76 4191
Chris@76 4192 return $results;
Chris@76 4193 }
Chris@76 4194
Chris@76 4195 // Add a function for integration hook.
Chris@76 4196 function add_integration_function($hook, $function, $permanent = true)
Chris@76 4197 {
Chris@76 4198 global $smcFunc, $modSettings;
Chris@76 4199
Chris@76 4200 // Is it going to be permanent?
Chris@76 4201 if ($permanent)
Chris@76 4202 {
Chris@76 4203 $request = $smcFunc['db_query']('', '
Chris@76 4204 SELECT value
Chris@76 4205 FROM {db_prefix}settings
Chris@76 4206 WHERE variable = {string:variable}',
Chris@76 4207 array(
Chris@76 4208 'variable' => $hook,
Chris@76 4209 )
Chris@76 4210 );
Chris@76 4211 list($current_functions) = $smcFunc['db_fetch_row']($request);
Chris@76 4212 $smcFunc['db_free_result']($request);
Chris@76 4213
Chris@76 4214 if (!empty($current_functions))
Chris@76 4215 {
Chris@76 4216 $current_functions = explode(',', $current_functions);
Chris@76 4217 if (in_array($function, $current_functions))
Chris@76 4218 return;
Chris@76 4219
Chris@76 4220 $permanent_functions = array_merge($current_functions, array($function));
Chris@76 4221 }
Chris@76 4222 else
Chris@76 4223 $permanent_functions = array($function);
Chris@76 4224
Chris@76 4225 updateSettings(array($hook => implode(',', $permanent_functions)));
Chris@76 4226 }
Chris@76 4227
Chris@76 4228 // Make current function list usable.
Chris@76 4229 $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
Chris@76 4230
Chris@76 4231 // Do nothing, if it's already there.
Chris@76 4232 if (in_array($function, $functions))
Chris@76 4233 return;
Chris@76 4234
Chris@76 4235 $functions[] = $function;
Chris@76 4236 $modSettings[$hook] = implode(',', $functions);
Chris@76 4237 }
Chris@76 4238
Chris@76 4239 // Remove an integration hook function.
Chris@76 4240 function remove_integration_function($hook, $function)
Chris@76 4241 {
Chris@76 4242 global $smcFunc, $modSettings;
Chris@76 4243
Chris@76 4244 // Get the permanent functions.
Chris@76 4245 $request = $smcFunc['db_query']('', '
Chris@76 4246 SELECT value
Chris@76 4247 FROM {db_prefix}settings
Chris@76 4248 WHERE variable = {string:variable}',
Chris@76 4249 array(
Chris@76 4250 'variable' => $hook,
Chris@76 4251 )
Chris@76 4252 );
Chris@76 4253 list($current_functions) = $smcFunc['db_fetch_row']($request);
Chris@76 4254 $smcFunc['db_free_result']($request);
Chris@76 4255
Chris@76 4256 if (!empty($current_functions))
Chris@76 4257 {
Chris@76 4258 $current_functions = explode(',', $current_functions);
Chris@76 4259
Chris@76 4260 if (in_array($function, $current_functions))
Chris@76 4261 updateSettings(array($hook => implode(',', array_diff($current_functions, array($function)))));
Chris@76 4262 }
Chris@76 4263
Chris@76 4264 // Turn the function list into something usable.
Chris@76 4265 $functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
Chris@76 4266
Chris@76 4267 // You can only remove it if it's available.
Chris@76 4268 if (!in_array($function, $functions))
Chris@76 4269 return;
Chris@76 4270
Chris@76 4271 $functions = array_diff($functions, array($function));
Chris@76 4272 $modSettings[$hook] = implode(',', $functions);
Chris@76 4273 }
Chris@76 4274
Chris@76 4275 ?>