Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions_content.php

Zuletzt modifiziert: 09.10.2024, 12:51 - Dateigröße: 45.59 KiB


 0001  <?php
 0002  /**
 0003  *
 0004  * This file is part of the phpBB Forum Software package.
 0005  *
 0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
 0007  * @license GNU General Public License, version 2 (GPL-2.0)
 0008  *
 0009  * For full copyright and license information, please see
 0010  * the docs/CREDITS.txt file.
 0011  *
 0012  */
 0013   
 0014  /**
 0015  * @ignore
 0016  */
 0017  if (!defined('IN_PHPBB'))
 0018  {
 0019      exit;
 0020  }
 0021   
 0022  /**
 0023  * gen_sort_selects()
 0024  * make_jumpbox()
 0025  * bump_topic_allowed()
 0026  * get_context()
 0027  * phpbb_clean_search_string()
 0028  * decode_message()
 0029  * strip_bbcode()
 0030  * generate_text_for_display()
 0031  * generate_text_for_storage()
 0032  * generate_text_for_edit()
 0033  * make_clickable_callback()
 0034  * make_clickable()
 0035  * censor_text()
 0036  * bbcode_nl2br()
 0037  * smiley_text()
 0038  * parse_attachments()
 0039  * extension_allowed()
 0040  * truncate_string()
 0041  * get_username_string()
 0042  * class bitfield
 0043  */
 0044   
 0045  /**
 0046  * Generate sort selection fields
 0047  */
 0048  function gen_sort_selects(&$limit_days, &$sort_by_text, &$sort_days, &$sort_key, &$sort_dir, &$s_limit_days, &$s_sort_key, &$s_sort_dir, &$u_sort_param, $def_st = false, $def_sk = false, $def_sd = false)
 0049  {
 0050      global $user;
 0051   
 0052      $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']);
 0053   
 0054      $sorts = array(
 0055          'st'    => array(
 0056              'key'        => 'sort_days',
 0057              'default'    => $def_st,
 0058              'options'    => $limit_days,
 0059              'output'    => &$s_limit_days,
 0060          ),
 0061   
 0062          'sk'    => array(
 0063              'key'        => 'sort_key',
 0064              'default'    => $def_sk,
 0065              'options'    => $sort_by_text,
 0066              'output'    => &$s_sort_key,
 0067          ),
 0068   
 0069          'sd'    => array(
 0070              'key'        => 'sort_dir',
 0071              'default'    => $def_sd,
 0072              'options'    => $sort_dir_text,
 0073              'output'    => &$s_sort_dir,
 0074          ),
 0075      );
 0076      $u_sort_param  = '';
 0077   
 0078      foreach ($sorts as $name => $sort_ary)
 0079      {
 0080          $key = $sort_ary['key'];
 0081          $selected = $$sort_ary['key'];
 0082   
 0083          // Check if the key is selectable. If not, we reset to the default or first key found.
 0084          // This ensures the values are always valid. We also set $sort_dir/sort_key/etc. to the
 0085          // correct value, else the protection is void. ;)
 0086          if (!isset($sort_ary['options'][$selected]))
 0087          {
 0088              if ($sort_ary['default'] !== false)
 0089              {
 0090                  $selected = $$key = $sort_ary['default'];
 0091              }
 0092              else
 0093              {
 0094                  @reset($sort_ary['options']);
 0095                  $selected = $$key = key($sort_ary['options']);
 0096              }
 0097          }
 0098   
 0099          $sort_ary['output'] = '<select name="' . $name . '" id="' . $name . '">';
 0100          foreach ($sort_ary['options'] as $option => $text)
 0101          {
 0102              $sort_ary['output'] .= '<option value="' . $option . '"' . (($selected == $option) ? ' selected="selected"' : '') . '>' . $text . '</option>';
 0103          }
 0104          $sort_ary['output'] .= '</select>';
 0105   
 0106          $u_sort_param .= ($selected !== $sort_ary['default']) ? ((strlen($u_sort_param)) ? '&amp;' : '') . "{$name}={$selected}" : '';
 0107      }
 0108   
 0109      return;
 0110  }
 0111   
 0112  /**
 0113  * Generate Jumpbox
 0114  */
 0115  function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list = false, $force_display = false)
 0116  {
 0117      global $config, $auth, $template, $user, $db, $phpbb_path_helper;
 0118   
 0119      // We only return if the jumpbox is not forced to be displayed (in case it is needed for functionality)
 0120      if (!$config['load_jumpbox'] && $force_display === false)
 0121      {
 0122          return;
 0123      }
 0124   
 0125      $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
 0126          FROM ' . FORUMS_TABLE . '
 0127          ORDER BY left_id ASC';
 0128      $result = $db->sql_query($sql, 600);
 0129   
 0130      $right = $padding = 0;
 0131      $padding_store = array('0' => 0);
 0132      $display_jumpbox = false;
 0133      $iteration = 0;
 0134   
 0135      // Sometimes it could happen that forums will be displayed here not be displayed within the index page
 0136      // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
 0137      // If this happens, the padding could be "broken"
 0138   
 0139      while ($row = $db->sql_fetchrow($result))
 0140      {
 0141          if ($row['left_id'] < $right)
 0142          {
 0143              $padding++;
 0144              $padding_store[$row['parent_id']] = $padding;
 0145          }
 0146          else if ($row['left_id'] > $right + 1)
 0147          {
 0148              // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
 0149              // @todo digging deep to find out "how" this can happen.
 0150              $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
 0151          }
 0152   
 0153          $right = $row['right_id'];
 0154   
 0155          if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']))
 0156          {
 0157              // Non-postable forum with no subforums, don't display
 0158              continue;
 0159          }
 0160   
 0161          if (!$auth->acl_get('f_list', $row['forum_id']))
 0162          {
 0163              // if the user does not have permissions to list this forum skip
 0164              continue;
 0165          }
 0166   
 0167          if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id']))
 0168          {
 0169              continue;
 0170          }
 0171   
 0172          if (!$display_jumpbox)
 0173          {
 0174              $template->assign_block_vars('jumpbox_forums', array(
 0175                  'FORUM_ID'        => ($select_all) ? 0 : -1,
 0176                  'FORUM_NAME'    => ($select_all) ? $user->lang['ALL_FORUMS'] : $user->lang['SELECT_FORUM'],
 0177                  'S_FORUM_COUNT'    => $iteration,
 0178                  'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $forum_id)),
 0179              ));
 0180   
 0181              $iteration++;
 0182              $display_jumpbox = true;
 0183          }
 0184   
 0185          $template->assign_block_vars('jumpbox_forums', array(
 0186              'FORUM_ID'        => $row['forum_id'],
 0187              'FORUM_NAME'    => $row['forum_name'],
 0188              'SELECTED'        => ($row['forum_id'] == $forum_id) ? ' selected="selected"' : '',
 0189              'S_FORUM_COUNT'    => $iteration,
 0190              'S_IS_CAT'        => ($row['forum_type'] == FORUM_CAT) ? true : false,
 0191              'S_IS_LINK'        => ($row['forum_type'] == FORUM_LINK) ? true : false,
 0192              'S_IS_POST'        => ($row['forum_type'] == FORUM_POST) ? true : false,
 0193              'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $row['forum_id'])),
 0194          ));
 0195   
 0196          for ($i = 0; $i < $padding; $i++)
 0197          {
 0198              $template->assign_block_vars('jumpbox_forums.level', array());
 0199          }
 0200          $iteration++;
 0201      }
 0202      $db->sql_freeresult($result);
 0203      unset($padding_store);
 0204   
 0205      $url_parts = $phpbb_path_helper->get_url_parts($action);
 0206   
 0207      $template->assign_vars(array(
 0208          'S_DISPLAY_JUMPBOX'            => $display_jumpbox,
 0209          'S_JUMPBOX_ACTION'            => $action,
 0210          'HIDDEN_FIELDS_FOR_JUMPBOX'    => build_hidden_fields($url_parts['params']),
 0211      ));
 0212   
 0213      return;
 0214  }
 0215   
 0216  /**
 0217  * Bump Topic Check - used by posting and viewtopic
 0218  */
 0219  function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster)
 0220  {
 0221      global $config, $auth, $user;
 0222   
 0223      // Check permission and make sure the last post was not already bumped
 0224      if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped)
 0225      {
 0226          return false;
 0227      }
 0228   
 0229      // Check bump time range, is the user really allowed to bump the topic at this time?
 0230      $bump_time = ($config['bump_type'] == 'm') ? $config['bump_interval'] * 60 : (($config['bump_type'] == 'h') ? $config['bump_interval'] * 3600 : $config['bump_interval'] * 86400);
 0231   
 0232      // Check bump time
 0233      if ($last_post_time + $bump_time > time())
 0234      {
 0235          return false;
 0236      }
 0237   
 0238      // Check bumper, only topic poster and last poster are allowed to bump
 0239      if ($topic_poster != $user->data['user_id'] && $last_topic_poster != $user->data['user_id'])
 0240      {
 0241          return false;
 0242      }
 0243   
 0244      // A bump time of 0 will completely disable the bump feature... not intended but might be useful.
 0245      return $bump_time;
 0246  }
 0247   
 0248  /**
 0249  * Generates a text with approx. the specified length which contains the specified words and their context
 0250  *
 0251  * @param    string    $text    The full text from which context shall be extracted
 0252  * @param    string    $words    An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!)
 0253  * @param    int        $length    The desired length of the resulting text, however the result might be shorter or longer than this value
 0254  *
 0255  * @return    string            Context of the specified words separated by "..."
 0256  */
 0257  function get_context($text, $words, $length = 400)
 0258  {
 0259      // first replace all whitespaces with single spaces
 0260      $text = preg_replace('/ +/', ' ', strtr($text, "\t\n\r\x0C ", '     '));
 0261   
 0262      // we need to turn the entities back into their original form, to not cut the message in between them
 0263      $entities = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
 0264      $characters = array('<', '>', '[', ']', '.', ':', ':');
 0265      $text = str_replace($entities, $characters, $text);
 0266   
 0267      $word_indizes = array();
 0268      if (sizeof($words))
 0269      {
 0270          $match = '';
 0271          // find the starting indizes of all words
 0272          foreach ($words as $word)
 0273          {
 0274              if ($word)
 0275              {
 0276                  if (preg_match('#(?:[^\w]|^)(' . $word . ')(?:[^\w]|$)#i', $text, $match))
 0277                  {
 0278                      if (empty($match[1]))
 0279                      {
 0280                          continue;
 0281                      }
 0282   
 0283                      $pos = utf8_strpos($text, $match[1]);
 0284                      if ($pos !== false)
 0285                      {
 0286                          $word_indizes[] = $pos;
 0287                      }
 0288                  }
 0289              }
 0290          }
 0291          unset($match);
 0292   
 0293          if (sizeof($word_indizes))
 0294          {
 0295              $word_indizes = array_unique($word_indizes);
 0296              sort($word_indizes);
 0297   
 0298              $wordnum = sizeof($word_indizes);
 0299              // number of characters on the right and left side of each word
 0300              $sequence_length = (int) ($length / (2 * $wordnum)) - 2;
 0301              $final_text = '';
 0302              $word = $j = 0;
 0303              $final_text_index = -1;
 0304   
 0305              // cycle through every character in the original text
 0306              for ($i = $word_indizes[$word], $n = utf8_strlen($text); $i < $n; $i++)
 0307              {
 0308                  // if the current position is the start of one of the words then append $sequence_length characters to the final text
 0309                  if (isset($word_indizes[$word]) && ($i == $word_indizes[$word]))
 0310                  {
 0311                      if ($final_text_index < $i - $sequence_length - 1)
 0312                      {
 0313                          $final_text .= '... ' . preg_replace('#^([^ ]*)#', '', utf8_substr($text, $i - $sequence_length, $sequence_length));
 0314                      }
 0315                      else
 0316                      {
 0317                          // if the final text is already nearer to the current word than $sequence_length we only append the text
 0318                          // from its current index on and distribute the unused length to all other sequenes
 0319                          $sequence_length += (int) (($final_text_index - $i + $sequence_length + 1) / (2 * $wordnum));
 0320                          $final_text .= utf8_substr($text, $final_text_index + 1, $i - $final_text_index - 1);
 0321                      }
 0322                      $final_text_index = $i - 1;
 0323   
 0324                      // add the following characters to the final text (see below)
 0325                      $word++;
 0326                      $j = 1;
 0327                  }
 0328   
 0329                  if ($j > 0)
 0330                  {
 0331                      // add the character to the final text and increment the sequence counter
 0332                      $final_text .= utf8_substr($text, $i, 1);
 0333                      $final_text_index++;
 0334                      $j++;
 0335   
 0336                      // if this is a whitespace then check whether we are done with this sequence
 0337                      if (utf8_substr($text, $i, 1) == ' ')
 0338                      {
 0339                          // only check whether we have to exit the context generation completely if we haven't already reached the end anyway
 0340                          if ($i + 4 < $n)
 0341                          {
 0342                              if (($j > $sequence_length && $word >= $wordnum) || utf8_strlen($final_text) > $length)
 0343                              {
 0344                                  $final_text .= ' ...';
 0345                                  break;
 0346                              }
 0347                          }
 0348                          else
 0349                          {
 0350                              // make sure the text really reaches the end
 0351                              $j -= 4;
 0352                          }
 0353   
 0354                          // stop context generation and wait for the next word
 0355                          if ($j > $sequence_length)
 0356                          {
 0357                              $j = 0;
 0358                          }
 0359                      }
 0360                  }
 0361              }
 0362              return str_replace($characters, $entities, $final_text);
 0363          }
 0364      }
 0365   
 0366      if (!sizeof($words) || !sizeof($word_indizes))
 0367      {
 0368          return str_replace($characters, $entities, ((utf8_strlen($text) >= $length + 3) ? utf8_substr($text, 0, $length) . '...' : $text));
 0369      }
 0370  }
 0371   
 0372  /**
 0373  * Cleans a search string by removing single wildcards from it and replacing multiple spaces with a single one.
 0374  *
 0375  * @param string $search_string The full search string which should be cleaned.
 0376  *
 0377  * @return string The cleaned search string without any wildcards and multiple spaces.
 0378  */
 0379  function phpbb_clean_search_string($search_string)
 0380  {
 0381      // This regular expressions matches every single wildcard.
 0382      // That means one after a whitespace or the beginning of the string or one before a whitespace or the end of the string.
 0383      $search_string = preg_replace('#(?<=^|\s)\*+(?=\s|$)#', '', $search_string);
 0384      $search_string = trim($search_string);
 0385      $search_string = preg_replace(array('#\s+#u', '#\*+#u'), array(' ', '*'), $search_string);
 0386      return $search_string;
 0387  }
 0388   
 0389  /**
 0390  * Decode text whereby text is coming from the db and expected to be pre-parsed content
 0391  * We are placing this outside of the message parser because we are often in need of it...
 0392  */
 0393  function decode_message(&$message, $bbcode_uid = '')
 0394  {
 0395      global $config;
 0396   
 0397      if ($bbcode_uid)
 0398      {
 0399          $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid");
 0400          $replace = array("\n", '', '', '', '');
 0401      }
 0402      else
 0403      {
 0404          $match = array('<br />');
 0405          $replace = array("\n");
 0406      }
 0407   
 0408      $message = str_replace($match, $replace, $message);
 0409   
 0410      $match = get_preg_expression('bbcode_htm');
 0411      $replace = array('\1', '\1', '\2', '\1', '', '');
 0412   
 0413      $message = preg_replace($match, $replace, $message);
 0414  }
 0415   
 0416  /**
 0417  * Strips all bbcode from a text and returns the plain content
 0418  */
 0419  function strip_bbcode(&$text, $uid = '')
 0420  {
 0421      if (!$uid)
 0422      {
 0423          $uid = '[0-9a-z]{5,}';
 0424      }
 0425   
 0426      $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:&quot;.*&quot;|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text);
 0427   
 0428      $match = get_preg_expression('bbcode_htm');
 0429      $replace = array('\1', '\1', '\2', '\1', '', '');
 0430   
 0431      $text = preg_replace($match, $replace, $text);
 0432  }
 0433   
 0434  /**
 0435  * For display of custom parsed text on user-facing pages
 0436  * Expects $text to be the value directly from the database (stored value)
 0437  */
 0438  function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true)
 0439  {
 0440      static $bbcode;
 0441      global $phpbb_dispatcher;
 0442   
 0443      if ($text === '')
 0444      {
 0445          return '';
 0446      }
 0447   
 0448      /**
 0449      * Use this event to modify the text before it is parsed
 0450      *
 0451      * @event core.modify_text_for_display_before
 0452      * @var string    text            The text to parse
 0453      * @var string    uid                The BBCode UID
 0454      * @var string    bitfield        The BBCode Bitfield
 0455      * @var int        flags            The BBCode Flags
 0456      * @var bool        censor_text        Whether or not to apply word censors
 0457      * @since 3.1.0-a1
 0458      */
 0459      $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text');
 0460      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars)));
 0461   
 0462      if ($censor_text)
 0463      {
 0464          $text = censor_text($text);
 0465      }
 0466   
 0467      // Parse bbcode if bbcode uid stored and bbcode enabled
 0468      if ($uid && ($flags & OPTION_FLAG_BBCODE))
 0469      {
 0470          if (!class_exists('bbcode'))
 0471          {
 0472              global $phpbb_root_path, $phpEx;
 0473              include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
 0474          }
 0475   
 0476          if (empty($bbcode))
 0477          {
 0478              $bbcode = new bbcode($bitfield);
 0479          }
 0480          else
 0481          {
 0482              $bbcode->bbcode($bitfield);
 0483          }
 0484   
 0485          $bbcode->bbcode_second_pass($text, $uid);
 0486      }
 0487   
 0488      $text = bbcode_nl2br($text);
 0489      $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES));
 0490   
 0491      /**
 0492      * Use this event to modify the text after it is parsed
 0493      *
 0494      * @event core.modify_text_for_display_after
 0495      * @var string    text        The text to parse
 0496      * @var string    uid            The BBCode UID
 0497      * @var string    bitfield    The BBCode Bitfield
 0498      * @var int        flags        The BBCode Flags
 0499      * @since 3.1.0-a1
 0500      */
 0501      $vars = array('text', 'uid', 'bitfield', 'flags');
 0502      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_after', compact($vars)));
 0503   
 0504      return $text;
 0505  }
 0506   
 0507  /**
 0508  * For parsing custom parsed text to be stored within the database.
 0509  * This function additionally returns the uid and bitfield that needs to be stored.
 0510  * Expects $text to be the value directly from request_var() and in it's non-parsed form
 0511  *
 0512  * @param string $text The text to be replaced with the parsed one
 0513  * @param string $uid The BBCode uid for this parse
 0514  * @param string $bitfield The BBCode bitfield for this parse
 0515  * @param int $flags The allow_bbcode, allow_urls and allow_smilies compiled into a single integer.
 0516  * @param bool $allow_bbcode If BBCode is allowed (i.e. if BBCode is parsed)
 0517  * @param bool $allow_urls If urls is allowed
 0518  * @param bool $allow_smilies If smilies are allowed
 0519  *
 0520  * @return array    An array of string with the errors that occurred while parsing
 0521  */
 0522  function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false)
 0523  {
 0524      global $phpbb_root_path, $phpEx, $phpbb_dispatcher;
 0525   
 0526      /**
 0527      * Use this event to modify the text before it is prepared for storage
 0528      *
 0529      * @event core.modify_text_for_storage_before
 0530      * @var string    text            The text to parse
 0531      * @var string    uid                The BBCode UID
 0532      * @var string    bitfield        The BBCode Bitfield
 0533      * @var int        flags            The BBCode Flags
 0534      * @var bool        allow_bbcode    Whether or not to parse BBCode
 0535      * @var bool        allow_urls        Whether or not to parse URLs
 0536      * @var bool        allow_smilies    Whether or not to parse Smilies
 0537      * @since 3.1.0-a1
 0538      */
 0539      $vars = array(
 0540          'text',
 0541          'uid',
 0542          'bitfield',
 0543          'flags',
 0544          'allow_bbcode',
 0545          'allow_urls',
 0546          'allow_smilies',
 0547      );
 0548      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_before', compact($vars)));
 0549   
 0550      $uid = $bitfield = '';
 0551      $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0);
 0552   
 0553      if ($text === '')
 0554      {
 0555          return;
 0556      }
 0557   
 0558      if (!class_exists('parse_message'))
 0559      {
 0560          include($phpbb_root_path . 'includes/message_parser.' . $phpEx);
 0561      }
 0562   
 0563      $message_parser = new parse_message($text);
 0564      $message_parser->parse($allow_bbcode, $allow_urls, $allow_smilies);
 0565   
 0566      $text = $message_parser->message;
 0567      $uid = $message_parser->bbcode_uid;
 0568   
 0569      // If the bbcode_bitfield is empty, there is no need for the uid to be stored.
 0570      if (!$message_parser->bbcode_bitfield)
 0571      {
 0572          $uid = '';
 0573      }
 0574   
 0575      $bitfield = $message_parser->bbcode_bitfield;
 0576   
 0577      /**
 0578      * Use this event to modify the text after it is prepared for storage
 0579      *
 0580      * @event core.modify_text_for_storage_after
 0581      * @var string    text            The text to parse
 0582      * @var string    uid                The BBCode UID
 0583      * @var string    bitfield        The BBCode Bitfield
 0584      * @var int        flags            The BBCode Flags
 0585      * @since 3.1.0-a1
 0586      */
 0587      $vars = array('text', 'uid', 'bitfield', 'flags');
 0588      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_after', compact($vars)));
 0589   
 0590      return $message_parser->warn_msg;
 0591  }
 0592   
 0593  /**
 0594  * For decoding custom parsed text for edits as well as extracting the flags
 0595  * Expects $text to be the value directly from the database (pre-parsed content)
 0596  */
 0597  function generate_text_for_edit($text, $uid, $flags)
 0598  {
 0599      global $phpbb_root_path, $phpEx, $phpbb_dispatcher;
 0600   
 0601      /**
 0602      * Use this event to modify the text before it is decoded for editing
 0603      *
 0604      * @event core.modify_text_for_edit_before
 0605      * @var string    text            The text to parse
 0606      * @var string    uid                The BBCode UID
 0607      * @var int        flags            The BBCode Flags
 0608      * @since 3.1.0-a1
 0609      */
 0610      $vars = array('text', 'uid', 'flags');
 0611      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_before', compact($vars)));
 0612   
 0613      decode_message($text, $uid);
 0614   
 0615      /**
 0616      * Use this event to modify the text after it is decoded for editing
 0617      *
 0618      * @event core.modify_text_for_edit_after
 0619      * @var string    text            The text to parse
 0620      * @var int        flags            The BBCode Flags
 0621      * @since 3.1.0-a1
 0622      */
 0623      $vars = array('text', 'flags');
 0624      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_after', compact($vars)));
 0625   
 0626      return array(
 0627          'allow_bbcode'    => ($flags & OPTION_FLAG_BBCODE) ? 1 : 0,
 0628          'allow_smilies'    => ($flags & OPTION_FLAG_SMILIES) ? 1 : 0,
 0629          'allow_urls'    => ($flags & OPTION_FLAG_LINKS) ? 1 : 0,
 0630          'text'            => $text
 0631      );
 0632  }
 0633   
 0634  /**
 0635  * A subroutine of make_clickable used with preg_replace
 0636  * It places correct HTML around an url, shortens the displayed text
 0637  * and makes sure no entities are inside URLs
 0638  */
 0639  function make_clickable_callback($type, $whitespace, $url, $relative_url, $class)
 0640  {
 0641      $orig_url        = $url;
 0642      $orig_relative    = $relative_url;
 0643      $append            = '';
 0644      $url            = htmlspecialchars_decode($url);
 0645      $relative_url    = htmlspecialchars_decode($relative_url);
 0646   
 0647      // make sure no HTML entities were matched
 0648      $chars = array('<', '>', '"');
 0649      $split = false;
 0650   
 0651      foreach ($chars as $char)
 0652      {
 0653          $next_split = strpos($url, $char);
 0654          if ($next_split !== false)
 0655          {
 0656              $split = ($split !== false) ? min($split, $next_split) : $next_split;
 0657          }
 0658      }
 0659   
 0660      if ($split !== false)
 0661      {
 0662          // an HTML entity was found, so the URL has to end before it
 0663          $append            = substr($url, $split) . $relative_url;
 0664          $url            = substr($url, 0, $split);
 0665          $relative_url    = '';
 0666      }
 0667      else if ($relative_url)
 0668      {
 0669          // same for $relative_url
 0670          $split = false;
 0671          foreach ($chars as $char)
 0672          {
 0673              $next_split = strpos($relative_url, $char);
 0674              if ($next_split !== false)
 0675              {
 0676                  $split = ($split !== false) ? min($split, $next_split) : $next_split;
 0677              }
 0678          }
 0679   
 0680          if ($split !== false)
 0681          {
 0682              $append            = substr($relative_url, $split);
 0683              $relative_url    = substr($relative_url, 0, $split);
 0684          }
 0685      }
 0686   
 0687      // if the last character of the url is a punctuation mark, exclude it from the url
 0688      $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];
 0689   
 0690      switch ($last_char)
 0691      {
 0692          case '.':
 0693          case '?':
 0694          case '!':
 0695          case ':':
 0696          case ',':
 0697              $append = $last_char;
 0698              if ($relative_url)
 0699              {
 0700                  $relative_url = substr($relative_url, 0, -1);
 0701              }
 0702              else
 0703              {
 0704                  $url = substr($url, 0, -1);
 0705              }
 0706          break;
 0707   
 0708          // set last_char to empty here, so the variable can be used later to
 0709          // check whether a character was removed
 0710          default:
 0711              $last_char = '';
 0712          break;
 0713      }
 0714   
 0715      $short_url = (strlen($url) > 55) ? substr($url, 0, 39) . ' ... ' . substr($url, -10) : $url;
 0716   
 0717      switch ($type)
 0718      {
 0719          case MAGIC_URL_LOCAL:
 0720              $tag            = 'l';
 0721              $relative_url    = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url));
 0722              $url            = $url . '/' . $relative_url;
 0723              $text            = $relative_url;
 0724   
 0725              // this url goes to http://domain.tld/path/to/board/ which
 0726              // would result in an empty link if treated as local so
 0727              // don't touch it and let MAGIC_URL_FULL take care of it.
 0728              if (!$relative_url)
 0729              {
 0730                  return $whitespace . $orig_url . '/' . $orig_relative; // slash is taken away by relative url pattern
 0731              }
 0732          break;
 0733   
 0734          case MAGIC_URL_FULL:
 0735              $tag    = 'm';
 0736              $text    = $short_url;
 0737          break;
 0738   
 0739          case MAGIC_URL_WWW:
 0740              $tag    = 'w';
 0741              $url    = 'http://' . $url;
 0742              $text    = $short_url;
 0743          break;
 0744   
 0745          case MAGIC_URL_EMAIL:
 0746              $tag    = 'e';
 0747              $text    = $short_url;
 0748              $url    = 'mailto:' . $url;
 0749          break;
 0750      }
 0751   
 0752      $url    = htmlspecialchars($url);
 0753      $text    = htmlspecialchars($text);
 0754      $append    = htmlspecialchars($append);
 0755   
 0756      $html    = "$whitespace<!-- $tag --><a$class href=\"$url\">$text</a><!-- $tag -->$append";
 0757   
 0758      return $html;
 0759  }
 0760   
 0761  /**
 0762  * make_clickable function
 0763  *
 0764  * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
 0765  * Cuts down displayed size of link if over 50 chars, turns absolute links
 0766  * into relative versions when the server/script path matches the link
 0767  */
 0768  function make_clickable($text, $server_url = false, $class = 'postlink')
 0769  {
 0770      if ($server_url === false)
 0771      {
 0772          $server_url = generate_board_url();
 0773      }
 0774   
 0775      static $static_class;
 0776      static $magic_url_match_args;
 0777   
 0778      if (!isset($magic_url_match_args[$server_url]) || $static_class != $class)
 0779      {
 0780          $static_class = $class;
 0781          $class = ($static_class) ? ' class="' . $static_class . '"' : '';
 0782          $local_class = ($static_class) ? ' class="' . $static_class . '-local"' : '';
 0783   
 0784          if (!is_array($magic_url_match_args))
 0785          {
 0786              $magic_url_match_args = array();
 0787          }
 0788   
 0789          // relative urls for this board
 0790          $magic_url_match_args[$server_url][] = array(
 0791              '#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#i',
 0792              MAGIC_URL_LOCAL,
 0793              $local_class,
 0794          );
 0795   
 0796          // matches a xxxx://aaaaa.bbb.cccc. ...
 0797          $magic_url_match_args[$server_url][] = array(
 0798              '#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#i',
 0799              MAGIC_URL_FULL,
 0800              $class,
 0801          );
 0802   
 0803          // matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
 0804          $magic_url_match_args[$server_url][] = array(
 0805              '#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#i',
 0806              MAGIC_URL_WWW,
 0807              $class,
 0808          );
 0809   
 0810          // matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode.
 0811          $magic_url_match_args[$server_url][] = array(
 0812              '/(^|[\n\t (>])(' . get_preg_expression('email') . ')/i',
 0813              MAGIC_URL_EMAIL,
 0814              '',
 0815          );
 0816      }
 0817   
 0818      foreach ($magic_url_match_args[$server_url] as $magic_args)
 0819      {
 0820          if (preg_match($magic_args[0], $text, $matches))
 0821          {
 0822              $text = preg_replace_callback($magic_args[0], function($matches) use ($magic_args)
 0823              {
 0824                  $relative_url = isset($matches[3]) ? $matches[3] : '';
 0825                  return make_clickable_callback($magic_args[1], $matches[1], $matches[2], $relative_url, $magic_args[2]);
 0826              }, $text);
 0827          }
 0828      }
 0829   
 0830      return $text;
 0831  }
 0832   
 0833  /**
 0834  * Censoring
 0835  */
 0836  function censor_text($text)
 0837  {
 0838      static $censors;
 0839   
 0840      // Nothing to do?
 0841      if ($text === '')
 0842      {
 0843          return '';
 0844      }
 0845   
 0846      // We moved the word censor checks in here because we call this function quite often - and then only need to do the check once
 0847      if (!isset($censors) || !is_array($censors))
 0848      {
 0849          global $config, $user, $auth, $cache;
 0850   
 0851          // We check here if the user is having viewing censors disabled (and also allowed to do so).
 0852          if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors'))
 0853          {
 0854              $censors = array();
 0855          }
 0856          else
 0857          {
 0858              $censors = $cache->obtain_word_list();
 0859          }
 0860      }
 0861   
 0862      if (sizeof($censors))
 0863      {
 0864          return preg_replace($censors['match'], $censors['replace'], $text);
 0865      }
 0866   
 0867      return $text;
 0868  }
 0869   
 0870  /**
 0871  * custom version of nl2br which takes custom BBCodes into account
 0872  */
 0873  function bbcode_nl2br($text)
 0874  {
 0875      // custom BBCodes might contain carriage returns so they
 0876      // are not converted into <br /> so now revert that
 0877      $text = str_replace(array("\n", "\r"), array('<br />', "\n"), $text);
 0878      return $text;
 0879  }
 0880   
 0881  /**
 0882  * Smiley processing
 0883  */
 0884  function smiley_text($text, $force_option = false)
 0885  {
 0886      global $config, $user, $phpbb_path_helper;
 0887   
 0888      if ($force_option || !$config['allow_smilies'] || !$user->optionget('viewsmilies'))
 0889      {
 0890          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#', '\1', $text);
 0891      }
 0892      else
 0893      {
 0894          $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_path_helper->get_web_root_path();
 0895          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img class="smilies" src="' . $root_path . $config['smilies_path'] . '/\2 />', $text);
 0896      }
 0897  }
 0898   
 0899  /**
 0900  * General attachment parsing
 0901  *
 0902  * @param mixed $forum_id The forum id the attachments are displayed in (false if in private message)
 0903  * @param string &$message The post/private message
 0904  * @param array &$attachments The attachments to parse for (inline) display. The attachments array will hold templated data after parsing.
 0905  * @param array &$update_count The attachment counts to be updated - will be filled
 0906  * @param bool $preview If set to true the attachments are parsed for preview. Within preview mode the comments are fetched from the given $attachments array and not fetched from the database.
 0907  */
 0908  function parse_attachments($forum_id, &$message, &$attachments, &$update_count, $preview = false)
 0909  {
 0910      if (!sizeof($attachments))
 0911      {
 0912          return;
 0913      }
 0914   
 0915      global $template, $cache, $user, $phpbb_dispatcher;
 0916      global $extensions, $config, $phpbb_root_path, $phpEx;
 0917   
 0918      //
 0919      $compiled_attachments = array();
 0920   
 0921      if (!isset($template->filename['attachment_tpl']))
 0922      {
 0923          $template->set_filenames(array(
 0924              'attachment_tpl'    => 'attachment.html')
 0925          );
 0926      }
 0927   
 0928      if (empty($extensions) || !is_array($extensions))
 0929      {
 0930          $extensions = $cache->obtain_attach_extensions($forum_id);
 0931      }
 0932   
 0933      // Look for missing attachment information...
 0934      $attach_ids = array();
 0935      foreach ($attachments as $pos => $attachment)
 0936      {
 0937          // If is_orphan is set, we need to retrieve the attachments again...
 0938          if (!isset($attachment['extension']) && !isset($attachment['physical_filename']))
 0939          {
 0940              $attach_ids[(int) $attachment['attach_id']] = $pos;
 0941          }
 0942      }
 0943   
 0944      // Grab attachments (security precaution)
 0945      if (sizeof($attach_ids))
 0946      {
 0947          global $db;
 0948   
 0949          $new_attachment_data = array();
 0950   
 0951          $sql = 'SELECT *
 0952              FROM ' . ATTACHMENTS_TABLE . '
 0953              WHERE ' . $db->sql_in_set('attach_id', array_keys($attach_ids));
 0954          $result = $db->sql_query($sql);
 0955   
 0956          while ($row = $db->sql_fetchrow($result))
 0957          {
 0958              if (!isset($attach_ids[$row['attach_id']]))
 0959              {
 0960                  continue;
 0961              }
 0962   
 0963              // If we preview attachments we will set some retrieved values here
 0964              if ($preview)
 0965              {
 0966                  $row['attach_comment'] = $attachments[$attach_ids[$row['attach_id']]]['attach_comment'];
 0967              }
 0968   
 0969              $new_attachment_data[$attach_ids[$row['attach_id']]] = $row;
 0970          }
 0971          $db->sql_freeresult($result);
 0972   
 0973          $attachments = $new_attachment_data;
 0974          unset($new_attachment_data);
 0975      }
 0976   
 0977      // Sort correctly
 0978      if ($config['display_order'])
 0979      {
 0980          // Ascending sort
 0981          krsort($attachments);
 0982      }
 0983      else
 0984      {
 0985          // Descending sort
 0986          ksort($attachments);
 0987      }
 0988   
 0989      foreach ($attachments as $attachment)
 0990      {
 0991          if (!sizeof($attachment))
 0992          {
 0993              continue;
 0994          }
 0995   
 0996          // We need to reset/empty the _file block var, because this function might be called more than once
 0997          $template->destroy_block_vars('_file');
 0998   
 0999          $block_array = array();
 1000   
 1001          // Some basics...
 1002          $attachment['extension'] = strtolower(trim($attachment['extension']));
 1003          $filename = $phpbb_root_path . $config['upload_path'] . '/' . utf8_basename($attachment['physical_filename']);
 1004          $thumbnail_filename = $phpbb_root_path . $config['upload_path'] . '/thumb_' . utf8_basename($attachment['physical_filename']);
 1005   
 1006          $upload_icon = '';
 1007   
 1008          if (isset($extensions[$attachment['extension']]))
 1009          {
 1010              if ($user->img('icon_topic_attach', '') && !$extensions[$attachment['extension']]['upload_icon'])
 1011              {
 1012                  $upload_icon = $user->img('icon_topic_attach', '');
 1013              }
 1014              else if ($extensions[$attachment['extension']]['upload_icon'])
 1015              {
 1016                  $upload_icon = '<img src="' . $phpbb_root_path . $config['upload_icons_path'] . '/' . trim($extensions[$attachment['extension']]['upload_icon']) . '" alt="" />';
 1017              }
 1018          }
 1019   
 1020          $filesize = get_formatted_filesize($attachment['filesize'], false);
 1021   
 1022          $comment = bbcode_nl2br(censor_text($attachment['attach_comment']));
 1023   
 1024          $block_array += array(
 1025              'UPLOAD_ICON'        => $upload_icon,
 1026              'FILESIZE'            => $filesize['value'],
 1027              'SIZE_LANG'            => $filesize['unit'],
 1028              'DOWNLOAD_NAME'        => utf8_basename($attachment['real_filename']),
 1029              'COMMENT'            => $comment,
 1030          );
 1031   
 1032          $denied = false;
 1033   
 1034          if (!extension_allowed($forum_id, $attachment['extension'], $extensions))
 1035          {
 1036              $denied = true;
 1037   
 1038              $block_array += array(
 1039                  'S_DENIED'            => true,
 1040                  'DENIED_MESSAGE'    => sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])
 1041              );
 1042          }
 1043   
 1044          if (!$denied)
 1045          {
 1046              $l_downloaded_viewed = $download_link = '';
 1047              $display_cat = $extensions[$attachment['extension']]['display_cat'];
 1048   
 1049              if ($display_cat == ATTACHMENT_CATEGORY_IMAGE)
 1050              {
 1051                  if ($attachment['thumbnail'])
 1052                  {
 1053                      $display_cat = ATTACHMENT_CATEGORY_THUMB;
 1054                  }
 1055                  else
 1056                  {
 1057                      if ($config['img_display_inlined'])
 1058                      {
 1059                          if ($config['img_link_width'] || $config['img_link_height'])
 1060                          {
 1061                              $dimension = @getimagesize($filename);
 1062   
 1063                              // If the dimensions could not be determined or the image being 0x0 we display it as a link for safety purposes
 1064                              if ($dimension === false || empty($dimension[0]) || empty($dimension[1]))
 1065                              {
 1066                                  $display_cat = ATTACHMENT_CATEGORY_NONE;
 1067                              }
 1068                              else
 1069                              {
 1070                                  $display_cat = ($dimension[0] <= $config['img_link_width'] && $dimension[1] <= $config['img_link_height']) ? ATTACHMENT_CATEGORY_IMAGE : ATTACHMENT_CATEGORY_NONE;
 1071                              }
 1072                          }
 1073                      }
 1074                      else
 1075                      {
 1076                          $display_cat = ATTACHMENT_CATEGORY_NONE;
 1077                      }
 1078                  }
 1079              }
 1080   
 1081              // Make some descisions based on user options being set.
 1082              if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg'))
 1083              {
 1084                  $display_cat = ATTACHMENT_CATEGORY_NONE;
 1085              }
 1086   
 1087              if ($display_cat == ATTACHMENT_CATEGORY_FLASH && !$user->optionget('viewflash'))
 1088              {
 1089                  $display_cat = ATTACHMENT_CATEGORY_NONE;
 1090              }
 1091   
 1092              $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
 1093              $l_downloaded_viewed = 'VIEWED_COUNTS';
 1094   
 1095              switch ($display_cat)
 1096              {
 1097                  // Images
 1098                  case ATTACHMENT_CATEGORY_IMAGE:
 1099                      $inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
 1100                      $download_link .= '&amp;mode=view';
 1101   
 1102                      $block_array += array(
 1103                          'S_IMAGE'        => true,
 1104                          'U_INLINE_LINK'        => $inline_link,
 1105                      );
 1106   
 1107                      $update_count[] = $attachment['attach_id'];
 1108                  break;
 1109   
 1110                  // Images, but display Thumbnail
 1111                  case ATTACHMENT_CATEGORY_THUMB:
 1112                      $thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&amp;t=1');
 1113                      $download_link .= '&amp;mode=view';
 1114   
 1115                      $block_array += array(
 1116                          'S_THUMBNAIL'        => true,
 1117                          'THUMB_IMAGE'        => $thumbnail_link,
 1118                      );
 1119   
 1120                      $update_count[] = $attachment['attach_id'];
 1121                  break;
 1122   
 1123                  // Windows Media Streams
 1124                  case ATTACHMENT_CATEGORY_WM:
 1125   
 1126                      // Giving the filename directly because within the wm object all variables are in local context making it impossible
 1127                      // to validate against a valid session (all params can differ)
 1128                      // $download_link = $filename;
 1129   
 1130                      $block_array += array(
 1131                          'U_FORUM'        => generate_board_url(),
 1132                          'ATTACH_ID'        => $attachment['attach_id'],
 1133                          'S_WM_FILE'        => true,
 1134                      );
 1135   
 1136                      // Viewed/Heared File ... update the download count
 1137                      $update_count[] = $attachment['attach_id'];
 1138                  break;
 1139   
 1140                  // Real Media Streams
 1141                  case ATTACHMENT_CATEGORY_RM:
 1142                  case ATTACHMENT_CATEGORY_QUICKTIME:
 1143   
 1144                      $block_array += array(
 1145                          'S_RM_FILE'            => ($display_cat == ATTACHMENT_CATEGORY_RM) ? true : false,
 1146                          'S_QUICKTIME_FILE'    => ($display_cat == ATTACHMENT_CATEGORY_QUICKTIME) ? true : false,
 1147                          'U_FORUM'            => generate_board_url(),
 1148                          'ATTACH_ID'            => $attachment['attach_id'],
 1149                      );
 1150   
 1151                      // Viewed/Heared File ... update the download count
 1152                      $update_count[] = $attachment['attach_id'];
 1153                  break;
 1154   
 1155                  // Macromedia Flash Files
 1156                  case ATTACHMENT_CATEGORY_FLASH:
 1157                      list($width, $height) = @getimagesize($filename);
 1158   
 1159                      $block_array += array(
 1160                          'S_FLASH_FILE'    => true,
 1161                          'WIDTH'            => $width,
 1162                          'HEIGHT'        => $height,
 1163                          'U_VIEW_LINK'    => $download_link . '&amp;view=1',
 1164                      );
 1165   
 1166                      // Viewed/Heared File ... update the download count
 1167                      $update_count[] = $attachment['attach_id'];
 1168                  break;
 1169   
 1170                  default:
 1171                      $l_downloaded_viewed = 'DOWNLOAD_COUNTS';
 1172   
 1173                      $block_array += array(
 1174                          'S_FILE'        => true,
 1175                      );
 1176                  break;
 1177              }
 1178   
 1179              if (!isset($attachment['download_count']))
 1180              {
 1181                  $attachment['download_count'] = 0;
 1182              }
 1183   
 1184              $block_array += array(
 1185                  'U_DOWNLOAD_LINK'        => $download_link,
 1186                  'L_DOWNLOAD_COUNT'        => $user->lang($l_downloaded_viewed, (int) $attachment['download_count']),
 1187              );
 1188          }
 1189   
 1190          /**
 1191          * Use this event to modify the attachment template data.
 1192          *
 1193          * This event is triggered once per attachment.
 1194          *
 1195          * @event core.parse_attachments_modify_template_data
 1196          * @var array    attachment        Array with attachment data
 1197          * @var array    block_array        Template data of the attachment
 1198          * @var int        display_cat        Attachment category data
 1199          * @var string    download_link    Attachment download link
 1200          * @var array    extensions        Array with attachment extensions data
 1201          * @var mixed     forum_id         The forum id the attachments are displayed in (false if in private message)
 1202          * @var bool        preview            Flag indicating if we are in post preview mode
 1203          * @var array    update_count    Array with attachment ids to update download count
 1204          * @since 3.1.0-RC5
 1205          */
 1206          $vars = array(
 1207              'attachment',
 1208              'block_array',
 1209              'display_cat',
 1210              'download_link',
 1211              'extensions',
 1212              'forum_id',
 1213              'preview',
 1214              'update_count',
 1215          );
 1216          extract($phpbb_dispatcher->trigger_event('core.parse_attachments_modify_template_data', compact($vars)));
 1217   
 1218          $template->assign_block_vars('_file', $block_array);
 1219   
 1220          $compiled_attachments[] = $template->assign_display('attachment_tpl');
 1221      }
 1222   
 1223      $attachments = $compiled_attachments;
 1224      unset($compiled_attachments);
 1225   
 1226      $tpl_size = sizeof($attachments);
 1227   
 1228      $unset_tpl = array();
 1229   
 1230      preg_match_all('#<!\-\- ia([0-9]+) \-\->(.*?)<!\-\- ia\1 \-\->#', $message, $matches, PREG_PATTERN_ORDER);
 1231   
 1232      $replace = array();
 1233      foreach ($matches[0] as $num => $capture)
 1234      {
 1235          // Flip index if we are displaying the reverse way
 1236          $index = ($config['display_order']) ? ($tpl_size-($matches[1][$num] + 1)) : $matches[1][$num];
 1237   
 1238          $replace['from'][] = $matches[0][$num];
 1239          $replace['to'][] = (isset($attachments[$index])) ? $attachments[$index] : sprintf($user->lang['MISSING_INLINE_ATTACHMENT'], $matches[2][array_search($index, $matches[1])]);
 1240   
 1241          $unset_tpl[] = $index;
 1242      }
 1243   
 1244      if (isset($replace['from']))
 1245      {
 1246          $message = str_replace($replace['from'], $replace['to'], $message);
 1247      }
 1248   
 1249      $unset_tpl = array_unique($unset_tpl);
 1250   
 1251      // Needed to let not display the inlined attachments at the end of the post again
 1252      foreach ($unset_tpl as $index)
 1253      {
 1254          unset($attachments[$index]);
 1255      }
 1256  }
 1257   
 1258  /**
 1259  * Check if extension is allowed to be posted.
 1260  *
 1261  * @param mixed $forum_id The forum id to check or false if private message
 1262  * @param string $extension The extension to check, for example zip.
 1263  * @param array &$extensions The extension array holding the information from the cache (will be obtained if empty)
 1264  *
 1265  * @return bool False if the extension is not allowed to be posted, else true.
 1266  */
 1267  function extension_allowed($forum_id, $extension, &$extensions)
 1268  {
 1269      if (empty($extensions))
 1270      {
 1271          global $cache;
 1272          $extensions = $cache->obtain_attach_extensions($forum_id);
 1273      }
 1274   
 1275      return (!isset($extensions['_allowed_'][$extension])) ? false : true;
 1276  }
 1277   
 1278  /**
 1279  * Truncates string while retaining special characters if going over the max length
 1280  * The default max length is 60 at the moment
 1281  * The maximum storage length is there to fit the string within the given length. The string may be further truncated due to html entities.
 1282  * For example: string given is 'a "quote"' (length: 9), would be a stored as 'a &quot;quote&quot;' (length: 19)
 1283  *
 1284  * @param string $string The text to truncate to the given length. String is specialchared.
 1285  * @param int $max_length Maximum length of string (multibyte character count as 1 char / Html entity count as 1 char)
 1286  * @param int $max_store_length Maximum character length of string (multibyte character count as 1 char / Html entity count as entity chars).
 1287  * @param bool $allow_reply Allow Re: in front of string
 1288  *     NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_length) and is deprecated.
 1289  * @param string $append String to be appended
 1290  */
 1291  function truncate_string($string, $max_length = 60, $max_store_length = 255, $allow_reply = false, $append = '')
 1292  {
 1293      $chars = array();
 1294   
 1295      $strip_reply = false;
 1296      $stripped = false;
 1297      if ($allow_reply && strpos($string, 'Re: ') === 0)
 1298      {
 1299          $strip_reply = true;
 1300          $string = substr($string, 4);
 1301      }
 1302   
 1303      $_chars = utf8_str_split(htmlspecialchars_decode($string));
 1304      $chars = array_map('utf8_htmlspecialchars', $_chars);
 1305   
 1306      // Now check the length ;)
 1307      if (sizeof($chars) > $max_length)
 1308      {
 1309          // Cut off the last elements from the array
 1310          $string = implode('', array_slice($chars, 0, $max_length - utf8_strlen($append)));
 1311          $stripped = true;
 1312      }
 1313   
 1314      // Due to specialchars, we may not be able to store the string...
 1315      if (utf8_strlen($string) > $max_store_length)
 1316      {
 1317          // let's split again, we do not want half-baked strings where entities are split
 1318          $_chars = utf8_str_split(htmlspecialchars_decode($string));
 1319          $chars = array_map('utf8_htmlspecialchars', $_chars);
 1320   
 1321          do
 1322          {
 1323              array_pop($chars);
 1324              $string = implode('', $chars);
 1325          }
 1326          while (!empty($chars) && utf8_strlen($string) > $max_store_length);
 1327      }
 1328   
 1329      if ($strip_reply)
 1330      {
 1331          $string = 'Re: ' . $string;
 1332      }
 1333   
 1334      if ($append != '' && $stripped)
 1335      {
 1336          $string = $string . $append;
 1337      }
 1338   
 1339      return $string;
 1340  }
 1341   
 1342  /**
 1343  * Get username details for placing into templates.
 1344  * This function caches all modes on first call, except for no_profile and anonymous user - determined by $user_id.
 1345  *
 1346  * @param string $mode Can be profile (for getting an url to the profile), username (for obtaining the username), colour (for obtaining the user colour), full (for obtaining a html string representing a coloured link to the users profile) or no_profile (the same as full but forcing no profile link)
 1347  * @param int $user_id The users id
 1348  * @param string $username The users name
 1349  * @param string $username_colour The users colour
 1350  * @param string $guest_username optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then.
 1351  * @param string $custom_profile_url optional parameter to specify a profile url. The user id get appended to this url as &amp;u={user_id}
 1352  *
 1353  * @return string A string consisting of what is wanted based on $mode.
 1354  */
 1355  function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false)
 1356  {
 1357      static $_profile_cache;
 1358      global $phpbb_dispatcher;
 1359   
 1360      // We cache some common variables we need within this function
 1361      if (empty($_profile_cache))
 1362      {
 1363          global $phpbb_root_path, $phpEx;
 1364   
 1365          $_profile_cache['base_url'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile&amp;u={USER_ID}');
 1366          $_profile_cache['tpl_noprofile'] = '<span class="username">{USERNAME}</span>';
 1367          $_profile_cache['tpl_noprofile_colour'] = '<span style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</span>';
 1368          $_profile_cache['tpl_profile'] = '<a href="{PROFILE_URL}" class="username">{USERNAME}</a>';
 1369          $_profile_cache['tpl_profile_colour'] = '<a href="{PROFILE_URL}" style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</a>';
 1370      }
 1371   
 1372      global $user, $auth;
 1373   
 1374      // This switch makes sure we only run code required for the mode
 1375      switch ($mode)
 1376      {
 1377          case 'full':
 1378          case 'no_profile':
 1379          case 'colour':
 1380   
 1381              // Build correct username colour
 1382              $username_colour = ($username_colour) ? '#' . $username_colour : '';
 1383   
 1384              // Return colour
 1385              if ($mode == 'colour')
 1386              {
 1387                  $username_string = $username_colour;
 1388                  break;
 1389              }
 1390   
 1391          // no break;
 1392   
 1393          case 'username':
 1394   
 1395              // Build correct username
 1396              if ($guest_username === false)
 1397              {
 1398                  $username = ($username) ? $username : $user->lang['GUEST'];
 1399              }
 1400              else
 1401              {
 1402                  $username = ($user_id && $user_id != ANONYMOUS) ? $username : ((!empty($guest_username)) ? $guest_username : $user->lang['GUEST']);
 1403              }
 1404   
 1405              // Return username
 1406              if ($mode == 'username')
 1407              {
 1408                  $username_string = $username;
 1409                  break;
 1410              }
 1411   
 1412          // no break;
 1413   
 1414          case 'profile':
 1415   
 1416              // Build correct profile url - only show if not anonymous and permission to view profile if registered user
 1417              // For anonymous the link leads to a login page.
 1418              if ($user_id && $user_id != ANONYMOUS && ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('u_viewprofile')))
 1419              {
 1420                  $profile_url = ($custom_profile_url !== false) ? $custom_profile_url . '&amp;u=' . (int) $user_id : str_replace(array('={USER_ID}', '=%7BUSER_ID%7D'), '=' . (int) $user_id, $_profile_cache['base_url']);
 1421              }
 1422              else
 1423              {
 1424                  $profile_url = '';
 1425              }
 1426   
 1427              // Return profile
 1428              if ($mode == 'profile')
 1429              {
 1430                  $username_string = $profile_url;
 1431                  break;
 1432              }
 1433   
 1434          // no break;
 1435      }
 1436   
 1437      if (!isset($username_string))
 1438      {
 1439          if (($mode == 'full' && !$profile_url) || $mode == 'no_profile')
 1440          {
 1441              $username_string = str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']);
 1442          }
 1443          else
 1444          {
 1445              $username_string = str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_profile'] : $_profile_cache['tpl_profile_colour']);
 1446          }
 1447      }
 1448   
 1449      /**
 1450      * Use this event to change the output of get_username_string()
 1451      *
 1452      * @event core.modify_username_string
 1453      * @var string mode                profile|username|colour|full|no_profile
 1454      * @var int user_id                String or array of additional url
 1455      *                                parameters
 1456      * @var string username            The user's username
 1457      * @var string username_colour    The user's colour
 1458      * @var string guest_username    Optional parameter to specify the
 1459      *                                guest username.
 1460      * @var string custom_profile_url Optional parameter to specify a
 1461      *                                profile url.
 1462      * @var string username_string    The string that has been generated
 1463      * @var array _profile_cache        Array of original return templates
 1464      * @since 3.1.0-a1
 1465      */
 1466      $vars = array(
 1467          'mode',
 1468          'user_id',
 1469          'username',
 1470          'username_colour',
 1471          'guest_username',
 1472          'custom_profile_url',
 1473          'username_string',
 1474          '_profile_cache',
 1475      );
 1476      extract($phpbb_dispatcher->trigger_event('core.modify_username_string', compact($vars)));
 1477   
 1478      return $username_string;
 1479  }
 1480   
 1481  /**
 1482   * Add an option to the quick-mod tools.
 1483   *
 1484   * @param string $url The recepting URL for the quickmod actions.
 1485   * @param string $option The language key for the value of the option.
 1486   * @param string $lang_string The language string to use.
 1487   */
 1488  function phpbb_add_quickmod_option($url, $option, $lang_string)
 1489  {
 1490      global $template, $user, $phpbb_path_helper;
 1491   
 1492      $lang_string = $user->lang($lang_string);
 1493      $template->assign_block_vars('quickmod', array(
 1494          'VALUE'        => $option,
 1495          'TITLE'        => $lang_string,
 1496          'LINK'        => $phpbb_path_helper->append_url_params($url, array('action' => $option)),
 1497      ));
 1498  }
 1499   
 1500  /**
 1501  * Concatenate an array into a string list.
 1502  *
 1503  * @param array $items Array of items to concatenate
 1504  * @param object $user The phpBB $user object.
 1505  *
 1506  * @return string String list. Examples: "A"; "A and B"; "A, B, and C"
 1507  */
 1508  function phpbb_generate_string_list($items, $user)
 1509  {
 1510      if (empty($items))
 1511      {
 1512          return '';
 1513      }
 1514   
 1515      $count = sizeof($items);
 1516      $last_item = array_pop($items);
 1517      $lang_key = 'STRING_LIST_MULTI';
 1518   
 1519      if ($count == 1)
 1520      {
 1521          return $last_item;
 1522      }
 1523      else if ($count == 2)
 1524      {
 1525          $lang_key = 'STRING_LIST_SIMPLE';
 1526      }
 1527      $list = implode($user->lang['COMMA_SEPARATOR'], $items);
 1528   
 1529      return $user->lang($lang_key, $list, $last_item);
 1530  }
 1531   
 1532  class bitfield
 1533  {
 1534      var $data;
 1535   
 1536      function bitfield($bitfield = '')
 1537      {
 1538          $this->data = base64_decode($bitfield);
 1539      }
 1540   
 1541      /**
 1542      */
 1543      function get($n)
 1544      {
 1545          // Get the ($n / 8)th char
 1546          $byte = $n >> 3;
 1547   
 1548          if (strlen($this->data) >= $byte + 1)
 1549          {
 1550              $c = $this->data[$byte];
 1551   
 1552              // Lookup the ($n % 8)th bit of the byte
 1553              $bit = 7 - ($n & 7);
 1554              return (bool) (ord($c) & (1 << $bit));
 1555          }
 1556          else
 1557          {
 1558              return false;
 1559          }
 1560      }
 1561   
 1562      function set($n)
 1563      {
 1564          $byte = $n >> 3;
 1565          $bit = 7 - ($n & 7);
 1566   
 1567          if (strlen($this->data) >= $byte + 1)
 1568          {
 1569              $this->data[$byte] = $this->data[$byte] | chr(1 << $bit);
 1570          }
 1571          else
 1572          {
 1573              $this->data .= str_repeat("\0", $byte - strlen($this->data));
 1574              $this->data .= chr(1 << $bit);
 1575          }
 1576      }
 1577   
 1578      function clear($n)
 1579      {
 1580          $byte = $n >> 3;
 1581   
 1582          if (strlen($this->data) >= $byte + 1)
 1583          {
 1584              $bit = 7 - ($n & 7);
 1585              $this->data[$byte] = $this->data[$byte] &~ chr(1 << $bit);
 1586          }
 1587      }
 1588   
 1589      function get_blob()
 1590      {
 1591          return $this->data;
 1592      }
 1593   
 1594      function get_base64()
 1595      {
 1596          return base64_encode($this->data);
 1597      }
 1598   
 1599      function get_bin()
 1600      {
 1601          $bin = '';
 1602          $len = strlen($this->data);
 1603   
 1604          for ($i = 0; $i < $len; ++$i)
 1605          {
 1606              $bin .= str_pad(decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT);
 1607          }
 1608   
 1609          return $bin;
 1610      }
 1611   
 1612      function get_all_set()
 1613      {
 1614          return array_keys(array_filter(str_split($this->get_bin())));
 1615      }
 1616   
 1617      function merge($bitfield)
 1618      {
 1619          $this->data = $this->data | $bitfield->get_blob();
 1620      }
 1621  }
 1622