Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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: 02.04.2025, 15:01 - Dateigröße: 52.40 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, $phpbb_dispatcher;
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      /**
0110       * Run code before generated sort selects are returned
0111       *
0112       * @event core.gen_sort_selects_after
0113       * @var    int      limit_days     Days limit
0114       * @var    array    sort_by_text   Sort by text options
0115       * @var    int      sort_days      Sort by days flag
0116       * @var    string   sort_key       Sort key
0117       * @var    string   sort_dir       Sort dir
0118       * @var    string   s_limit_days   String of days limit
0119       * @var    string   s_sort_key     String of sort key
0120       * @var    string   s_sort_dir     String of sort dir
0121       * @var    string   u_sort_param   Sort URL params
0122       * @var    bool     def_st         Default sort days
0123       * @var    bool     def_sk         Default sort key
0124       * @var    bool     def_sd         Default sort dir
0125       * @var    array    sorts          Sorts
0126       * @since 3.1.9-RC1
0127       */
0128      $vars = array(
0129          'limit_days',
0130          'sort_by_text',
0131          'sort_days',
0132          'sort_key',
0133          'sort_dir',
0134          's_limit_days',
0135          's_sort_key',
0136          's_sort_dir',
0137          'u_sort_param',
0138          'def_st',
0139          'def_sk',
0140          'def_sd',
0141          'sorts',
0142      );
0143      extract($phpbb_dispatcher->trigger_event('core.gen_sort_selects_after', compact($vars)));
0144   
0145      return;
0146  }
0147   
0148  /**
0149  * Generate Jumpbox
0150  */
0151  function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list = false, $force_display = false)
0152  {
0153      global $config, $auth, $template, $user, $db, $phpbb_path_helper, $phpbb_dispatcher;
0154   
0155      // We only return if the jumpbox is not forced to be displayed (in case it is needed for functionality)
0156      if (!$config['load_jumpbox'] && $force_display === false)
0157      {
0158          return;
0159      }
0160   
0161      $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
0162          FROM ' . FORUMS_TABLE . '
0163          ORDER BY left_id ASC';
0164      $result = $db->sql_query($sql, 600);
0165   
0166      $rowset = array();
0167      while ($row = $db->sql_fetchrow($result))
0168      {
0169          $rowset[(int) $row['forum_id']] = $row;
0170      }
0171      $db->sql_freeresult($result);
0172   
0173      $right = $padding = 0;
0174      $padding_store = array('0' => 0);
0175      $display_jumpbox = false;
0176      $iteration = 0;
0177   
0178      /**
0179      * Modify the jumpbox forum list data
0180      *
0181      * @event core.make_jumpbox_modify_forum_list
0182      * @var    array    rowset    Array with the forums list data
0183      * @since 3.1.10-RC1
0184      */
0185      $vars = array('rowset');
0186      extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_forum_list', compact($vars)));
0187   
0188      // Sometimes it could happen that forums will be displayed here not be displayed within the index page
0189      // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
0190      // If this happens, the padding could be "broken"
0191   
0192      foreach ($rowset as $row)
0193      {
0194          if ($row['left_id'] < $right)
0195          {
0196              $padding++;
0197              $padding_store[$row['parent_id']] = $padding;
0198          }
0199          else if ($row['left_id'] > $right + 1)
0200          {
0201              // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
0202              // @todo digging deep to find out "how" this can happen.
0203              $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
0204          }
0205   
0206          $right = $row['right_id'];
0207   
0208          if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']))
0209          {
0210              // Non-postable forum with no subforums, don't display
0211              continue;
0212          }
0213   
0214          if (!$auth->acl_get('f_list', $row['forum_id']))
0215          {
0216              // if the user does not have permissions to list this forum skip
0217              continue;
0218          }
0219   
0220          if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id']))
0221          {
0222              continue;
0223          }
0224   
0225          $tpl_ary = array();
0226          if (!$display_jumpbox)
0227          {
0228              $tpl_ary[] = array(
0229                  'FORUM_ID'        => ($select_all) ? 0 : -1,
0230                  'FORUM_NAME'    => ($select_all) ? $user->lang['ALL_FORUMS'] : $user->lang['SELECT_FORUM'],
0231                  'S_FORUM_COUNT'    => $iteration,
0232                  'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $forum_id)),
0233              );
0234   
0235              $iteration++;
0236              $display_jumpbox = true;
0237          }
0238   
0239          $tpl_ary[] = array(
0240              'FORUM_ID'        => $row['forum_id'],
0241              'FORUM_NAME'    => $row['forum_name'],
0242              'SELECTED'        => ($row['forum_id'] == $forum_id) ? ' selected="selected"' : '',
0243              'S_FORUM_COUNT'    => $iteration,
0244              'S_IS_CAT'        => ($row['forum_type'] == FORUM_CAT) ? true : false,
0245              'S_IS_LINK'        => ($row['forum_type'] == FORUM_LINK) ? true : false,
0246              'S_IS_POST'        => ($row['forum_type'] == FORUM_POST) ? true : false,
0247              'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $row['forum_id'])),
0248          );
0249   
0250          /**
0251           * Modify the jumpbox before it is assigned to the template
0252           *
0253           * @event core.make_jumpbox_modify_tpl_ary
0254           * @var    array    row                The data of the forum
0255           * @var    array    tpl_ary            Template data of the forum
0256           * @since 3.1.10-RC1
0257           */
0258          $vars = array(
0259              'row',
0260              'tpl_ary',
0261          );
0262          extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_tpl_ary', compact($vars)));
0263   
0264          $template->assign_block_vars_array('jumpbox_forums', $tpl_ary);
0265   
0266          unset($tpl_ary);
0267   
0268          for ($i = 0; $i < $padding; $i++)
0269          {
0270              $template->assign_block_vars('jumpbox_forums.level', array());
0271          }
0272          $iteration++;
0273      }
0274      unset($padding_store, $rowset);
0275   
0276      $url_parts = $phpbb_path_helper->get_url_parts($action);
0277   
0278      $template->assign_vars(array(
0279          'S_DISPLAY_JUMPBOX'            => $display_jumpbox,
0280          'S_JUMPBOX_ACTION'            => $action,
0281          'HIDDEN_FIELDS_FOR_JUMPBOX'    => build_hidden_fields($url_parts['params']),
0282      ));
0283   
0284      return;
0285  }
0286   
0287  /**
0288  * Bump Topic Check - used by posting and viewtopic
0289  */
0290  function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster)
0291  {
0292      global $config, $auth, $user, $phpbb_dispatcher;
0293   
0294      /**
0295       * Event to run code before the topic bump checks
0296       *
0297       * @event core.bump_topic_allowed_before
0298       * @var    int        forum_id            ID of the forum
0299       * @var    int        topic_bumped        Flag indicating if the topic was already bumped (0/1)
0300       * @var    int        last_post_time        The time of the topic last post
0301       * @var    int        topic_poster        User ID of the topic author
0302       * @var    int        last_topic_poster    User ID of the topic last post author
0303       * @since 3.3.14-RC1
0304       */
0305      $vars = [
0306          'forum_id',
0307          'topic_bumped',
0308          'last_post_time',
0309          'topic_poster',
0310          'last_topic_poster',
0311      ];
0312      extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_before', compact($vars)));
0313   
0314      // Check permission and make sure the last post was not already bumped
0315      if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped)
0316      {
0317          return false;
0318      }
0319   
0320      // Check bump time range, is the user really allowed to bump the topic at this time?
0321      $bump_time = ($config['bump_type'] == 'm') ? $config['bump_interval'] * 60 : (($config['bump_type'] == 'h') ? $config['bump_interval'] * 3600 : $config['bump_interval'] * 86400);
0322   
0323      // Check bump time
0324      if ($last_post_time + $bump_time > time())
0325      {
0326          return false;
0327      }
0328   
0329      // Check bumper, only topic poster and last poster are allowed to bump
0330      if ($topic_poster != $user->data['user_id'] && $last_topic_poster != $user->data['user_id'])
0331      {
0332          return false;
0333      }
0334   
0335      /**
0336       * Event to run code after the topic bump checks
0337       *
0338       * @event core.bump_topic_allowed_after
0339       * @var    int        forum_id            ID of the forum
0340       * @var    int        topic_bumped        Flag indicating if the topic was already bumped (0/1)
0341       * @var    int        last_post_time        The time of the topic last post
0342       * @var    int        topic_poster        User ID of the topic author
0343       * @var    int        last_topic_poster    User ID of the topic last post author
0344       * @var    int        bump_time            Bump time range
0345       * @since 3.3.14-RC1
0346       */
0347      $vars = [
0348          'forum_id',
0349          'topic_bumped',
0350          'last_post_time',
0351          'topic_poster',
0352          'last_topic_poster',
0353          'bump_time',
0354      ];
0355      extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_after', compact($vars)));
0356   
0357      // A bump time of 0 will completely disable the bump feature... not intended but might be useful.
0358      return $bump_time;
0359  }
0360   
0361  /**
0362  * Generates a text with approx. the specified length which contains the specified words and their context
0363  *
0364  * @param    string    $text    The full text from which context shall be extracted
0365  * @param    array    $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!)
0366  * @param    int        $length    The desired length of the resulting text, however the result might be shorter or longer than this value
0367  *
0368  * @return    string            Context of the specified words separated by "..."
0369  */
0370  function get_context(string $text, array $words, int $length = 400): string
0371  {
0372      if ($length <= 0)
0373      {
0374          return $text;
0375      }
0376   
0377      // We need to turn the entities back into their original form, to not cut the message in between them
0378      $text = htmlspecialchars_decode($text);
0379   
0380      // Replace all spaces/invisible characters with single spaces
0381      $text = preg_replace("/[\p{Z}\h\v]+/u", ' ', $text);
0382   
0383      $text_length = utf8_strlen($text);
0384   
0385      // Get first occurrence of each word
0386      $word_indexes = [];
0387      foreach ($words as $word)
0388      {
0389          $pos = utf8_stripos($text, $word);
0390   
0391          if ($pos !== false)
0392          {
0393              $word_indexes[$pos] = $word;
0394          }
0395      }
0396      if (!empty($word_indexes))
0397      {
0398          ksort($word_indexes);
0399   
0400          // Size of the fragment of text per word
0401          $num_indexes = count($word_indexes);
0402          $characters_per_word = (int) ($length / $num_indexes) + 2; // 2 to leave one character of margin at the sides to don't cut words
0403   
0404          // Get text fragment indexes
0405          $fragments = [];
0406          foreach ($word_indexes as $index => $word)
0407          {
0408              $word_length = utf8_strlen($word);
0409              $start = max(0, min($text_length - 1 - $characters_per_word, (int) ($index + ($word_length / 2) - ($characters_per_word / 2))));
0410              $end = $start + $characters_per_word;
0411   
0412              // Check if we can merge this fragment into the previous fragment
0413              if (!empty($fragments))
0414              {
0415                  [$prev_start, $prev_end] = end($fragments);
0416   
0417                  if ($prev_end + $characters_per_word >= $index + $word_length)
0418                  {
0419                      array_pop($fragments);
0420                      $start = $prev_start;
0421                      $end = $prev_end + $characters_per_word;
0422                  }
0423              }
0424   
0425              $fragments[] = [$start, $end];
0426          }
0427      }
0428      else
0429      {
0430          // There is no coincidences, so we just create a fragment with the first $length characters
0431          $fragments[] = [0, $length];
0432          $end = $length;
0433      }
0434   
0435      $output = [];
0436      foreach ($fragments as [$start, $end])
0437      {
0438          $fragment = utf8_substr($text, $start, $end - $start + 1);
0439   
0440          $fragment_start = 0;
0441          $fragment_end = $end - $start + 1;
0442   
0443          // Find the first valid alphanumeric character in the fragment to don't cut words
0444          if ($start > 0 && preg_match('/[^\p{L}\p{N}][\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE))
0445          {
0446              $fragment_start = utf8_strlen(substr($fragment, 0, (int) $matches[0][1])) + 1;
0447          }
0448   
0449          // Find the last valid alphanumeric character in the fragment to don't cut words
0450          if ($end < $text_length - 1 && preg_match_all('/[\p{L}\p{N}][^\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE))
0451          {
0452              $fragment_end = utf8_strlen(substr($fragment, 0, end($matches[0])[1]));
0453          }
0454   
0455          $output[] = utf8_substr($fragment, $fragment_start, $fragment_end - $fragment_start + 1);
0456      }
0457   
0458      return ($fragments[0][0] !== 0 ? '... ' : '') . utf8_htmlspecialchars(implode(' ... ', $output)) . ($end < $text_length - 1 ? ' ...' : '');
0459  }
0460   
0461  /**
0462  * Cleans a search string by removing single wildcards from it and replacing multiple spaces with a single one.
0463  *
0464  * @param string $search_string The full search string which should be cleaned.
0465  *
0466  * @return string The cleaned search string without any wildcards and multiple spaces.
0467  */
0468  function phpbb_clean_search_string($search_string)
0469  {
0470      // This regular expressions matches every single wildcard.
0471      // That means one after a whitespace or the beginning of the string or one before a whitespace or the end of the string.
0472      $search_string = preg_replace('#(?<=^|\s)\*+(?=\s|$)#', '', $search_string);
0473      $search_string = trim($search_string);
0474      $search_string = preg_replace(array('#\s+#u', '#\*+#u'), array(' ', '*'), $search_string);
0475      return $search_string;
0476  }
0477   
0478  /**
0479  * Decode text whereby text is coming from the db and expected to be pre-parsed content
0480  * We are placing this outside of the message parser because we are often in need of it...
0481  *
0482  * NOTE: special chars are kept encoded
0483  *
0484  * @param string &$message Original message, passed by reference
0485  * @param string $bbcode_uid BBCode UID
0486  * @return null
0487  */
0488  function decode_message(&$message, $bbcode_uid = '')
0489  {
0490      global $phpbb_container, $phpbb_dispatcher;
0491   
0492      /**
0493       * Use this event to modify the message before it is decoded
0494       *
0495       * @event core.decode_message_before
0496       * @var string    message_text    The message content
0497       * @var string    bbcode_uid        The message BBCode UID
0498       * @since 3.1.9-RC1
0499       */
0500      $message_text = $message;
0501      $vars = array('message_text', 'bbcode_uid');
0502      extract($phpbb_dispatcher->trigger_event('core.decode_message_before', compact($vars)));
0503      $message = $message_text;
0504   
0505      if (preg_match('#^<[rt][ >]#', $message))
0506      {
0507          $message = htmlspecialchars($phpbb_container->get('text_formatter.utils')->unparse($message), ENT_COMPAT);
0508      }
0509      else
0510      {
0511          if ($bbcode_uid)
0512          {
0513              $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid");
0514              $replace = array("\n", '', '', '', '');
0515          }
0516          else
0517          {
0518              $match = array('<br />');
0519              $replace = array("\n");
0520          }
0521   
0522          $message = str_replace($match, $replace, $message);
0523   
0524          $match = get_preg_expression('bbcode_htm');
0525          $replace = array('\1', '\1', '\2', '\2', '\1', '', '');
0526   
0527          $message = preg_replace($match, $replace, $message);
0528      }
0529   
0530      /**
0531      * Use this event to modify the message after it is decoded
0532      *
0533      * @event core.decode_message_after
0534      * @var string    message_text    The message content
0535      * @var string    bbcode_uid        The message BBCode UID
0536      * @since 3.1.9-RC1
0537      */
0538      $message_text = $message;
0539      $vars = array('message_text', 'bbcode_uid');
0540      extract($phpbb_dispatcher->trigger_event('core.decode_message_after', compact($vars)));
0541      $message = $message_text;
0542  }
0543   
0544  /**
0545  * Strips all bbcode from a text in place
0546  */
0547  function strip_bbcode(&$text, $uid = '')
0548  {
0549      global $phpbb_container;
0550   
0551      if (preg_match('#^<[rt][ >]#', $text))
0552      {
0553          $text = utf8_htmlspecialchars($phpbb_container->get('text_formatter.utils')->clean_formatting($text));
0554      }
0555      else
0556      {
0557          if (!$uid)
0558          {
0559              $uid = '[0-9a-z]{5,}';
0560          }
0561   
0562          $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:&quot;.*&quot;|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text);
0563   
0564          $match = get_preg_expression('bbcode_htm');
0565          $replace = array('\1', '\1', '\2', '\1', '', '');
0566   
0567          $text = preg_replace($match, $replace, $text);
0568      }
0569  }
0570   
0571  /**
0572  * For display of custom parsed text on user-facing pages
0573  * Expects $text to be the value directly from the database (stored value)
0574  */
0575  function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true)
0576  {
0577      static $bbcode;
0578      global $auth, $config, $user;
0579      global $phpbb_dispatcher, $phpbb_container;
0580   
0581      if ($text === '')
0582      {
0583          return '';
0584      }
0585   
0586      /**
0587      * Use this event to modify the text before it is parsed
0588      *
0589      * @event core.modify_text_for_display_before
0590      * @var string    text            The text to parse
0591      * @var string    uid                The BBCode UID
0592      * @var string    bitfield        The BBCode Bitfield
0593      * @var int        flags            The BBCode Flags
0594      * @var bool        censor_text        Whether or not to apply word censors
0595      * @since 3.1.0-a1
0596      */
0597      $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text');
0598      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars)));
0599   
0600      if (preg_match('#^<[rt][ >]#', $text))
0601      {
0602          $renderer = $phpbb_container->get('text_formatter.renderer');
0603   
0604          // Temporarily switch off viewcensors if applicable
0605          $old_censor = $renderer->get_viewcensors();
0606   
0607          // Check here if the user is having viewing censors disabled (and also allowed to do so).
0608          if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors'))
0609          {
0610              $censor_text = false;
0611          }
0612   
0613          if ($old_censor !== $censor_text)
0614          {
0615              $renderer->set_viewcensors($censor_text);
0616          }
0617   
0618          $text = $renderer->render($text);
0619   
0620          // Restore the previous value
0621          if ($old_censor !== $censor_text)
0622          {
0623              $renderer->set_viewcensors($old_censor);
0624          }
0625      }
0626      else
0627      {
0628          if ($censor_text)
0629          {
0630              $text = censor_text($text);
0631          }
0632   
0633          // Parse bbcode if bbcode uid stored and bbcode enabled
0634          if ($uid && ($flags & OPTION_FLAG_BBCODE))
0635          {
0636              if (!class_exists('bbcode'))
0637              {
0638                  global $phpbb_root_path, $phpEx;
0639                  include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
0640              }
0641   
0642              if (empty($bbcode))
0643              {
0644                  $bbcode = new bbcode($bitfield);
0645              }
0646              else
0647              {
0648                  $bbcode->bbcode_set_bitfield($bitfield);
0649              }
0650   
0651              $bbcode->bbcode_second_pass($text, $uid);
0652          }
0653   
0654          $text = bbcode_nl2br($text);
0655          $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES));
0656      }
0657   
0658      /**
0659      * Use this event to modify the text after it is parsed
0660      *
0661      * @event core.modify_text_for_display_after
0662      * @var string    text        The text to parse
0663      * @var string    uid            The BBCode UID
0664      * @var string    bitfield    The BBCode Bitfield
0665      * @var int        flags        The BBCode Flags
0666      * @since 3.1.0-a1
0667      */
0668      $vars = array('text', 'uid', 'bitfield', 'flags');
0669      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_after', compact($vars)));
0670   
0671      return $text;
0672  }
0673   
0674  /**
0675  * For parsing custom parsed text to be stored within the database.
0676  * This function additionally returns the uid and bitfield that needs to be stored.
0677  * Expects $text to be the value directly from $request->variable() and in it's non-parsed form
0678  *
0679  * @param string $text The text to be replaced with the parsed one
0680  * @param string $uid The BBCode uid for this parse
0681  * @param string $bitfield The BBCode bitfield for this parse
0682  * @param int $flags The allow_bbcode, allow_urls and allow_smilies compiled into a single integer.
0683  * @param bool $allow_bbcode If BBCode is allowed (i.e. if BBCode is parsed)
0684  * @param bool $allow_urls If urls is allowed
0685  * @param bool $allow_smilies If smilies are allowed
0686  * @param bool $allow_img_bbcode
0687  * @param bool $allow_flash_bbcode
0688  * @param bool $allow_quote_bbcode
0689  * @param bool $allow_url_bbcode
0690  * @param string $mode Mode to parse text as, e.g. post or sig
0691  *
0692  * @return array    An array of string with the errors that occurred while parsing
0693  */
0694  function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $mode = 'post')
0695  {
0696      global $phpbb_root_path, $phpEx, $phpbb_dispatcher;
0697   
0698      /**
0699      * Use this event to modify the text before it is prepared for storage
0700      *
0701      * @event core.modify_text_for_storage_before
0702      * @var string    text            The text to parse
0703      * @var string    uid                The BBCode UID
0704      * @var string    bitfield        The BBCode Bitfield
0705      * @var int        flags            The BBCode Flags
0706      * @var bool        allow_bbcode    Whether or not to parse BBCode
0707      * @var bool        allow_urls        Whether or not to parse URLs
0708      * @var bool        allow_smilies    Whether or not to parse Smilies
0709      * @var bool        allow_img_bbcode    Whether or not to parse the [img] BBCode
0710      * @var bool        allow_flash_bbcode    Whether or not to parse the [flash] BBCode
0711      * @var bool        allow_quote_bbcode    Whether or not to parse the [quote] BBCode
0712      * @var bool        allow_url_bbcode    Whether or not to parse the [url] BBCode
0713      * @var string    mode                Mode to parse text as, e.g. post or sig
0714      * @since 3.1.0-a1
0715      * @changed 3.2.0-a1 Added mode
0716      */
0717      $vars = array(
0718          'text',
0719          'uid',
0720          'bitfield',
0721          'flags',
0722          'allow_bbcode',
0723          'allow_urls',
0724          'allow_smilies',
0725          'allow_img_bbcode',
0726          'allow_flash_bbcode',
0727          'allow_quote_bbcode',
0728          'allow_url_bbcode',
0729          'mode',
0730      );
0731      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_before', compact($vars)));
0732   
0733      $uid = $bitfield = '';
0734      $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0);
0735   
0736      if (!class_exists('parse_message'))
0737      {
0738          include($phpbb_root_path . 'includes/message_parser.' . $phpEx);
0739      }
0740   
0741      $message_parser = new parse_message($text);
0742      $message_parser->parse($allow_bbcode, $allow_urls, $allow_smilies, $allow_img_bbcode, $allow_flash_bbcode, $allow_quote_bbcode, $allow_url_bbcode, true, $mode);
0743   
0744      $text = $message_parser->message;
0745      $uid = $message_parser->bbcode_uid;
0746   
0747      // If the bbcode_bitfield is empty, there is no need for the uid to be stored.
0748      if (!$message_parser->bbcode_bitfield)
0749      {
0750          $uid = '';
0751      }
0752   
0753      $bitfield = $message_parser->bbcode_bitfield;
0754   
0755      /**
0756      * Use this event to modify the text after it is prepared for storage
0757      *
0758      * @event core.modify_text_for_storage_after
0759      * @var string    text            The text to parse
0760      * @var string    uid                The BBCode UID
0761      * @var string    bitfield        The BBCode Bitfield
0762      * @var int        flags            The BBCode Flags
0763      * @var string    message_parser    The message_parser object
0764      * @since 3.1.0-a1
0765      * @changed 3.1.11-RC1            Added message_parser to vars
0766      */
0767      $vars = array('text', 'uid', 'bitfield', 'flags', 'message_parser');
0768      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_after', compact($vars)));
0769   
0770      return $message_parser->warn_msg;
0771  }
0772   
0773  /**
0774  * For decoding custom parsed text for edits as well as extracting the flags
0775  * Expects $text to be the value directly from the database (pre-parsed content)
0776  */
0777  function generate_text_for_edit($text, $uid, $flags)
0778  {
0779      global $phpbb_dispatcher;
0780   
0781      /**
0782      * Use this event to modify the text before it is decoded for editing
0783      *
0784      * @event core.modify_text_for_edit_before
0785      * @var string    text            The text to parse
0786      * @var string    uid                The BBCode UID
0787      * @var int        flags            The BBCode Flags
0788      * @since 3.1.0-a1
0789      */
0790      $vars = array('text', 'uid', 'flags');
0791      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_before', compact($vars)));
0792   
0793      decode_message($text, $uid);
0794   
0795      /**
0796      * Use this event to modify the text after it is decoded for editing
0797      *
0798      * @event core.modify_text_for_edit_after
0799      * @var string    text            The text to parse
0800      * @var int        flags            The BBCode Flags
0801      * @since 3.1.0-a1
0802      */
0803      $vars = array('text', 'flags');
0804      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_after', compact($vars)));
0805   
0806      return array(
0807          'allow_bbcode'    => ($flags & OPTION_FLAG_BBCODE) ? 1 : 0,
0808          'allow_smilies'    => ($flags & OPTION_FLAG_SMILIES) ? 1 : 0,
0809          'allow_urls'    => ($flags & OPTION_FLAG_LINKS) ? 1 : 0,
0810          'text'            => $text
0811      );
0812  }
0813   
0814  /**
0815  * A subroutine of make_clickable used with preg_replace
0816  * It places correct HTML around an url, shortens the displayed text
0817  * and makes sure no entities are inside URLs
0818  */
0819  function make_clickable_callback($type, $whitespace, $url, $relative_url, $class)
0820  {
0821      $orig_url        = $url;
0822      $orig_relative    = $relative_url;
0823      $append            = '';
0824      $url            = html_entity_decode($url, ENT_COMPAT);
0825      $relative_url    = html_entity_decode($relative_url, ENT_COMPAT);
0826   
0827      // make sure no HTML entities were matched
0828      $chars = array('<', '>', '"');
0829      $split = false;
0830   
0831      foreach ($chars as $char)
0832      {
0833          $next_split = strpos($url, $char);
0834          if ($next_split !== false)
0835          {
0836              $split = ($split !== false) ? min($split, $next_split) : $next_split;
0837          }
0838      }
0839   
0840      if ($split !== false)
0841      {
0842          // an HTML entity was found, so the URL has to end before it
0843          $append            = substr($url, $split) . $relative_url;
0844          $url            = substr($url, 0, $split);
0845          $relative_url    = '';
0846      }
0847      else if ($relative_url)
0848      {
0849          // same for $relative_url
0850          $split = false;
0851          foreach ($chars as $char)
0852          {
0853              $next_split = strpos($relative_url, $char);
0854              if ($next_split !== false)
0855              {
0856                  $split = ($split !== false) ? min($split, $next_split) : $next_split;
0857              }
0858          }
0859   
0860          if ($split !== false)
0861          {
0862              $append            = substr($relative_url, $split);
0863              $relative_url    = substr($relative_url, 0, $split);
0864          }
0865      }
0866   
0867      // if the last character of the url is a punctuation mark, exclude it from the url
0868      $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];
0869   
0870      switch ($last_char)
0871      {
0872          case '.':
0873          case '?':
0874          case '!':
0875          case ':':
0876          case ',':
0877              $append = $last_char;
0878              if ($relative_url)
0879              {
0880                  $relative_url = substr($relative_url, 0, -1);
0881              }
0882              else
0883              {
0884                  $url = substr($url, 0, -1);
0885              }
0886          break;
0887   
0888          // set last_char to empty here, so the variable can be used later to
0889          // check whether a character was removed
0890          default:
0891              $last_char = '';
0892          break;
0893      }
0894   
0895      $short_url = (utf8_strlen($url) > 55) ? utf8_substr($url, 0, 39) . ' ... ' . utf8_substr($url, -10) : $url;
0896   
0897      switch ($type)
0898      {
0899          case MAGIC_URL_LOCAL:
0900              $tag            = 'l';
0901              $relative_url    = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url));
0902              $url            = $url . '/' . $relative_url;
0903              $text            = $relative_url;
0904   
0905              // this url goes to http://domain.tld/path/to/board/ which
0906              // would result in an empty link if treated as local so
0907              // don't touch it and let MAGIC_URL_FULL take care of it.
0908              if (!$relative_url)
0909              {
0910                  return $whitespace . $orig_url . '/' . $orig_relative; // slash is taken away by relative url pattern
0911              }
0912          break;
0913   
0914          case MAGIC_URL_FULL:
0915              $tag    = 'm';
0916              $text    = $short_url;
0917          break;
0918   
0919          case MAGIC_URL_WWW:
0920              $tag    = 'w';
0921              $url    = 'http://' . $url;
0922              $text    = $short_url;
0923          break;
0924   
0925          case MAGIC_URL_EMAIL:
0926              $tag    = 'e';
0927              $text    = $short_url;
0928              $url    = 'mailto:' . $url;
0929          break;
0930      }
0931   
0932      $url    = htmlspecialchars($url, ENT_COMPAT);
0933      $text    = htmlspecialchars($text, ENT_COMPAT);
0934      $append    = htmlspecialchars($append, ENT_COMPAT);
0935   
0936      $html    = "$whitespace<!-- $tag --><a$class href=\"$url\">$text</a><!-- $tag -->$append";
0937   
0938      return $html;
0939  }
0940   
0941  /**
0942   * Replaces magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
0943   * Cuts down displayed size of link if over 50 chars, turns absolute links
0944   * into relative versions when the server/script path matches the link
0945   *
0946   * @param string        $text        Message text to parse URL/email entries
0947   * @param bool|string    $server_url    The server URL. If false, the board URL will be used
0948   * @param string        $class        CSS class selector to add to the parsed URL entries
0949   *
0950   * @return string    A text with parsed URL/email entries
0951   */
0952  function make_clickable($text, $server_url = false, string $class = 'postlink')
0953  {
0954      if ($server_url === false)
0955      {
0956          $server_url = generate_board_url();
0957      }
0958   
0959      static $static_class;
0960      static $magic_url_match_args;
0961   
0962      if (!isset($magic_url_match_args[$server_url]) || $static_class != $class)
0963      {
0964          $static_class = $class;
0965          $class = ($static_class) ? ' class="' . $static_class . '"' : '';
0966          $local_class = ($static_class) ? ' class="' . $static_class . '-local"' : '';
0967   
0968          if (!is_array($magic_url_match_args))
0969          {
0970              $magic_url_match_args = array();
0971          }
0972   
0973          // Check if the match for this $server_url and $class already exists
0974          $element_exists = false;
0975          if (isset($magic_url_match_args[$server_url]))
0976          {
0977              array_walk_recursive($magic_url_match_args[$server_url], function($value) use (&$element_exists, $static_class)
0978                  {
0979                      if ($value == $static_class)
0980                      {
0981                          $element_exists = true;
0982                          return;
0983                      }
0984                  }
0985              );
0986          }
0987   
0988          // Only add new $server_url and $class matches if not exist
0989          if (!$element_exists)
0990          {
0991              // relative urls for this board
0992              $magic_url_match_args[$server_url][] = [
0993                  '#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#iu',
0994                  MAGIC_URL_LOCAL,
0995                  $local_class,
0996                  $static_class,
0997              ];
0998   
0999              // matches a xxxx://aaaaa.bbb.cccc. ...
1000              $magic_url_match_args[$server_url][] = [
1001                  '#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#iu',
1002                  MAGIC_URL_FULL,
1003                  $class,
1004                  $static_class,
1005              ];
1006   
1007              // matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
1008              $magic_url_match_args[$server_url][] = [
1009                  '#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#iu',
1010                  MAGIC_URL_WWW,
1011                  $class,
1012                  $static_class,
1013              ];
1014          }
1015   
1016          if (!isset($magic_url_match_args[$server_url]['email']))
1017          {
1018              // matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode.
1019              $magic_url_match_args[$server_url]['email'] = [
1020                  '/(^|[\n\t (>])(' . get_preg_expression('email') . ')/iu',
1021                  MAGIC_URL_EMAIL,
1022                  '',
1023              ];
1024          }
1025      }
1026   
1027      foreach ($magic_url_match_args[$server_url] as $magic_args)
1028      {
1029          if (preg_match($magic_args[0], $text, $matches))
1030          {
1031              // Only apply $class from the corresponding function call argument (excepting emails which never has a class)
1032              if ($magic_args[1] != MAGIC_URL_EMAIL && $magic_args[3] != $static_class)
1033              {
1034                  continue;
1035              }
1036   
1037              $text = preg_replace_callback($magic_args[0], function($matches) use ($magic_args)
1038              {
1039                  $relative_url = isset($matches[3]) ? $matches[3] : '';
1040                  return make_clickable_callback($magic_args[1], $matches[1], $matches[2], $relative_url, $magic_args[2]);
1041              }, $text);
1042          }
1043      }
1044   
1045      return $text;
1046  }
1047   
1048  /**
1049  * Censoring
1050  */
1051  function censor_text($text)
1052  {
1053      static $censors;
1054   
1055      // Nothing to do?
1056      if ($text === '')
1057      {
1058          return '';
1059      }
1060   
1061      // We moved the word censor checks in here because we call this function quite often - and then only need to do the check once
1062      if (!isset($censors) || !is_array($censors))
1063      {
1064          global $config, $user, $auth, $cache;
1065   
1066          // We check here if the user is having viewing censors disabled (and also allowed to do so).
1067          if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors'))
1068          {
1069              $censors = array();
1070          }
1071          else
1072          {
1073              $censors = $cache->obtain_word_list();
1074          }
1075      }
1076   
1077      if (count($censors))
1078      {
1079          return preg_replace($censors['match'], $censors['replace'], $text);
1080      }
1081   
1082      return $text;
1083  }
1084   
1085  /**
1086  * custom version of nl2br which takes custom BBCodes into account
1087  */
1088  function bbcode_nl2br($text)
1089  {
1090      // custom BBCodes might contain carriage returns so they
1091      // are not converted into <br /> so now revert that
1092      $text = str_replace(array("\n", "\r"), array('<br />', "\n"), $text);
1093      return $text;
1094  }
1095   
1096  /**
1097  * Smiley processing
1098  */
1099  function smiley_text($text, $force_option = false)
1100  {
1101      global $config, $user, $phpbb_path_helper, $phpbb_dispatcher;
1102   
1103      if ($force_option || !$config['allow_smilies'] || !$user->optionget('viewsmilies'))
1104      {
1105          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#', '\1', $text);
1106      }
1107      else
1108      {
1109          $root_path = $phpbb_path_helper->get_web_root_path();
1110   
1111          /**
1112          * Event to override the root_path for smilies
1113          *
1114          * @event core.smiley_text_root_path
1115          * @var string root_path root_path for smilies
1116          * @since 3.1.11-RC1
1117          */
1118          $vars = array('root_path');
1119          extract($phpbb_dispatcher->trigger_event('core.smiley_text_root_path', compact($vars)));
1120          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img class="smilies" src="' . $root_path . $config['smilies_path'] . '/\2 />', $text);
1121      }
1122  }
1123   
1124  /**
1125  * General attachment parsing
1126  *
1127  * @param mixed $forum_id The forum id the attachments are displayed in (false if in private message)
1128  * @param string &$message The post/private message
1129  * @param array &$attachments The attachments to parse for (inline) display. The attachments array will hold templated data after parsing.
1130  * @param array &$update_count_ary The attachment counts to be updated - will be filled
1131  * @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.
1132  */
1133  function parse_attachments($forum_id, &$message, &$attachments, &$update_count_ary, $preview = false)
1134  {
1135      if (!count($attachments))
1136      {
1137          return;
1138      }
1139   
1140      global $template, $cache, $user, $phpbb_dispatcher;
1141      global $extensions, $config, $phpbb_root_path, $phpEx;
1142   
1143      //
1144      $compiled_attachments = array();
1145   
1146      if (!isset($template->filename['attachment_tpl']))
1147      {
1148          $template->set_filenames(array(
1149              'attachment_tpl'    => 'attachment.html')
1150          );
1151      }
1152   
1153      if (empty($extensions) || !is_array($extensions))
1154      {
1155          $extensions = $cache->obtain_attach_extensions($forum_id);
1156      }
1157   
1158      // Look for missing attachment information...
1159      $attach_ids = array();
1160      foreach ($attachments as $pos => $attachment)
1161      {
1162          // If is_orphan is set, we need to retrieve the attachments again...
1163          if (!isset($attachment['extension']) && !isset($attachment['physical_filename']))
1164          {
1165              $attach_ids[(int) $attachment['attach_id']] = $pos;
1166          }
1167      }
1168   
1169      // Grab attachments (security precaution)
1170      if (count($attach_ids))
1171      {
1172          global $db;
1173   
1174          $new_attachment_data = array();
1175   
1176          $sql = 'SELECT *
1177              FROM ' . ATTACHMENTS_TABLE . '
1178              WHERE ' . $db->sql_in_set('attach_id', array_keys($attach_ids));
1179          $result = $db->sql_query($sql);
1180   
1181          while ($row = $db->sql_fetchrow($result))
1182          {
1183              if (!isset($attach_ids[$row['attach_id']]))
1184              {
1185                  continue;
1186              }
1187   
1188              // If we preview attachments we will set some retrieved values here
1189              if ($preview)
1190              {
1191                  $row['attach_comment'] = $attachments[$attach_ids[$row['attach_id']]]['attach_comment'];
1192              }
1193   
1194              $new_attachment_data[$attach_ids[$row['attach_id']]] = $row;
1195          }
1196          $db->sql_freeresult($result);
1197   
1198          $attachments = $new_attachment_data;
1199          unset($new_attachment_data);
1200      }
1201   
1202      // Make sure attachments are properly ordered
1203      ksort($attachments);
1204   
1205      foreach ($attachments as $attachment)
1206      {
1207          if (!count($attachment))
1208          {
1209              continue;
1210          }
1211   
1212          // We need to reset/empty the _file block var, because this function might be called more than once
1213          $template->destroy_block_vars('_file');
1214   
1215          $block_array = array();
1216   
1217          // Some basics...
1218          $attachment['extension'] = strtolower(trim($attachment['extension']));
1219          $filename = $phpbb_root_path . $config['upload_path'] . '/' . utf8_basename($attachment['physical_filename']);
1220   
1221          $upload_icon = '';
1222          $download_link = '';
1223          $display_cat = false;
1224   
1225          if (isset($extensions[$attachment['extension']]))
1226          {
1227              if ($user->img('icon_topic_attach', '') && !$extensions[$attachment['extension']]['upload_icon'])
1228              {
1229                  $upload_icon = $user->img('icon_topic_attach', '');
1230              }
1231              else if ($extensions[$attachment['extension']]['upload_icon'])
1232              {
1233                  $upload_icon = '<img src="' . $phpbb_root_path . $config['upload_icons_path'] . '/' . trim($extensions[$attachment['extension']]['upload_icon']) . '" alt="" />';
1234              }
1235          }
1236   
1237          $filesize = get_formatted_filesize($attachment['filesize'], false);
1238   
1239          $comment = bbcode_nl2br(censor_text($attachment['attach_comment']));
1240   
1241          $block_array += array(
1242              'UPLOAD_ICON'        => $upload_icon,
1243              'FILESIZE'            => $filesize['value'],
1244              'SIZE_LANG'            => $filesize['unit'],
1245              'DOWNLOAD_NAME'        => utf8_basename($attachment['real_filename']),
1246              'COMMENT'            => $comment,
1247          );
1248   
1249          $denied = false;
1250   
1251          if (!extension_allowed($forum_id, $attachment['extension'], $extensions))
1252          {
1253              $denied = true;
1254   
1255              $block_array += array(
1256                  'S_DENIED'            => true,
1257                  'DENIED_MESSAGE'    => sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])
1258              );
1259          }
1260   
1261          if (!$denied)
1262          {
1263              $display_cat = $extensions[$attachment['extension']]['display_cat'];
1264   
1265              if ($display_cat == ATTACHMENT_CATEGORY_IMAGE)
1266              {
1267                  if ($attachment['thumbnail'])
1268                  {
1269                      $display_cat = ATTACHMENT_CATEGORY_THUMB;
1270                  }
1271                  else
1272                  {
1273                      if ($config['img_display_inlined'])
1274                      {
1275                          if ($config['img_link_width'] || $config['img_link_height'])
1276                          {
1277                              $dimension = @getimagesize($filename);
1278   
1279                              // If the dimensions could not be determined or the image being 0x0 we display it as a link for safety purposes
1280                              if ($dimension === false || empty($dimension[0]) || empty($dimension[1]))
1281                              {
1282                                  $display_cat = ATTACHMENT_CATEGORY_NONE;
1283                              }
1284                              else
1285                              {
1286                                  $display_cat = ($dimension[0] <= $config['img_link_width'] && $dimension[1] <= $config['img_link_height']) ? ATTACHMENT_CATEGORY_IMAGE : ATTACHMENT_CATEGORY_NONE;
1287                              }
1288                          }
1289                      }
1290                      else
1291                      {
1292                          $display_cat = ATTACHMENT_CATEGORY_NONE;
1293                      }
1294                  }
1295              }
1296   
1297              // Make some descisions based on user options being set.
1298              if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg'))
1299              {
1300                  $display_cat = ATTACHMENT_CATEGORY_NONE;
1301              }
1302   
1303              $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
1304              $l_downloaded_viewed = 'VIEWED_COUNTS';
1305   
1306              switch ($display_cat)
1307              {
1308                  // Images
1309                  case ATTACHMENT_CATEGORY_IMAGE:
1310                      $inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
1311                      $download_link .= '&amp;mode=view';
1312   
1313                      $block_array += array(
1314                          'S_IMAGE'        => true,
1315                          'U_INLINE_LINK'        => $inline_link,
1316                      );
1317   
1318                      $update_count_ary[] = $attachment['attach_id'];
1319                  break;
1320   
1321                  // Images, but display Thumbnail
1322                  case ATTACHMENT_CATEGORY_THUMB:
1323                      $thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&amp;t=1');
1324                      $download_link .= '&amp;mode=view';
1325   
1326                      $block_array += array(
1327                          'S_THUMBNAIL'        => true,
1328                          'THUMB_IMAGE'        => $thumbnail_link,
1329                      );
1330   
1331                      $update_count_ary[] = $attachment['attach_id'];
1332                  break;
1333   
1334                  default:
1335                      $l_downloaded_viewed = 'DOWNLOAD_COUNTS';
1336   
1337                      $block_array += array(
1338                          'S_FILE'        => true,
1339                      );
1340                  break;
1341              }
1342   
1343              if (!isset($attachment['download_count']))
1344              {
1345                  $attachment['download_count'] = 0;
1346              }
1347   
1348              $block_array += array(
1349                  'U_DOWNLOAD_LINK'        => $download_link,
1350                  'L_DOWNLOAD_COUNT'        => $user->lang($l_downloaded_viewed, (int) $attachment['download_count']),
1351              );
1352          }
1353   
1354          $update_count = $update_count_ary;
1355          /**
1356          * Use this event to modify the attachment template data.
1357          *
1358          * This event is triggered once per attachment.
1359          *
1360          * @event core.parse_attachments_modify_template_data
1361          * @var array    attachment        Array with attachment data
1362          * @var array    block_array        Template data of the attachment
1363          * @var int        display_cat        Attachment category data
1364          * @var string    download_link    Attachment download link
1365          * @var array    extensions        Array with attachment extensions data
1366          * @var mixed     forum_id         The forum id the attachments are displayed in (false if in private message)
1367          * @var bool        preview            Flag indicating if we are in post preview mode
1368          * @var array    update_count    Array with attachment ids to update download count
1369          * @since 3.1.0-RC5
1370          */
1371          $vars = array(
1372              'attachment',
1373              'block_array',
1374              'display_cat',
1375              'download_link',
1376              'extensions',
1377              'forum_id',
1378              'preview',
1379              'update_count',
1380          );
1381          extract($phpbb_dispatcher->trigger_event('core.parse_attachments_modify_template_data', compact($vars)));
1382          $update_count_ary = $update_count;
1383          unset($update_count, $display_cat, $download_link);
1384   
1385          $template->assign_block_vars('_file', $block_array);
1386   
1387          $compiled_attachments[] = $template->assign_display('attachment_tpl');
1388      }
1389   
1390      $attachments = $compiled_attachments;
1391      unset($compiled_attachments);
1392   
1393      $unset_tpl = array();
1394   
1395      preg_match_all('#<!\-\- ia([0-9]+) \-\->(.*?)<!\-\- ia\1 \-\->#', $message, $matches, PREG_PATTERN_ORDER);
1396   
1397      $replace = array();
1398      foreach ($matches[0] as $num => $capture)
1399      {
1400          $index = $matches[1][$num];
1401   
1402          $replace['from'][] = $matches[0][$num];
1403          $replace['to'][] = (isset($attachments[$index])) ? $attachments[$index] : sprintf($user->lang['MISSING_INLINE_ATTACHMENT'], $matches[2][array_search($index, $matches[1])]);
1404   
1405          $unset_tpl[] = $index;
1406      }
1407   
1408      if (isset($replace['from']))
1409      {
1410          $message = str_replace($replace['from'], $replace['to'], $message);
1411      }
1412   
1413      $unset_tpl = array_unique($unset_tpl);
1414   
1415      // Sort correctly
1416      if ($config['display_order'])
1417      {
1418          // Ascending sort
1419          krsort($attachments);
1420      }
1421      else
1422      {
1423          // Descending sort
1424          ksort($attachments);
1425      }
1426   
1427      // Needed to let not display the inlined attachments at the end of the post again
1428      foreach ($unset_tpl as $index)
1429      {
1430          unset($attachments[$index]);
1431      }
1432  }
1433   
1434  /**
1435  * Check if extension is allowed to be posted.
1436  *
1437  * @param mixed $forum_id The forum id to check or false if private message
1438  * @param string $extension The extension to check, for example zip.
1439  * @param array &$extensions The extension array holding the information from the cache (will be obtained if empty)
1440  *
1441  * @return bool False if the extension is not allowed to be posted, else true.
1442  */
1443  function extension_allowed($forum_id, $extension, &$extensions)
1444  {
1445      if (empty($extensions))
1446      {
1447          global $cache;
1448          $extensions = $cache->obtain_attach_extensions($forum_id);
1449      }
1450   
1451      return (!isset($extensions['_allowed_'][$extension])) ? false : true;
1452  }
1453   
1454  /**
1455  * Truncates string while retaining special characters if going over the max length
1456  * The default max length is 60 at the moment
1457  * The maximum storage length is there to fit the string within the given length. The string may be further truncated due to html entities.
1458  * For example: string given is 'a "quote"' (length: 9), would be a stored as 'a &quot;quote&quot;' (length: 19)
1459  *
1460  * @param string $string The text to truncate to the given length. String is specialchared.
1461  * @param int $max_length Maximum length of string (multibyte character count as 1 char / Html entity count as 1 char)
1462  * @param int $max_store_length Maximum character length of string (multibyte character count as 1 char / Html entity count as entity chars).
1463  * @param bool $allow_reply Allow Re: in front of string
1464  *     NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_length) and is deprecated.
1465  * @param string $append String to be appended
1466  */
1467  function truncate_string($string, $max_length = 60, $max_store_length = 255, $allow_reply = false, $append = '')
1468  {
1469      $strip_reply = false;
1470      $stripped = false;
1471      if ($allow_reply && strpos($string, 'Re: ') === 0)
1472      {
1473          $strip_reply = true;
1474          $string = substr($string, 4);
1475      }
1476   
1477      $_chars = utf8_str_split(html_entity_decode($string, ENT_COMPAT));
1478      $chars = array_map('utf8_htmlspecialchars', $_chars);
1479   
1480      // Now check the length ;)
1481      if (count($chars) > $max_length)
1482      {
1483          // Cut off the last elements from the array
1484          $string = implode('', array_slice($chars, 0, $max_length - utf8_strlen($append)));
1485          $stripped = true;
1486      }
1487   
1488      // Due to specialchars, we may not be able to store the string...
1489      if (utf8_strlen($string) > $max_store_length)
1490      {
1491          // let's split again, we do not want half-baked strings where entities are split
1492          $_chars = utf8_str_split(html_entity_decode($string, ENT_COMPAT));
1493          $chars = array_map('utf8_htmlspecialchars', $_chars);
1494   
1495          do
1496          {
1497              array_pop($chars);
1498              $string = implode('', $chars);
1499          }
1500          while (!empty($chars) && utf8_strlen($string) > $max_store_length);
1501      }
1502   
1503      if ($strip_reply)
1504      {
1505          $string = 'Re: ' . $string;
1506      }
1507   
1508      if ($append != '' && $stripped)
1509      {
1510          $string = $string . $append;
1511      }
1512   
1513      return $string;
1514  }
1515   
1516  /**
1517  * Get username details for placing into templates.
1518  * This function caches all modes on first call, except for no_profile and anonymous user - determined by $user_id.
1519  *
1520  * @html Username spans and links
1521  *
1522  * @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)
1523  * @param int $user_id The users id
1524  * @param string $username The users name
1525  * @param string $username_colour The users colour
1526  * @param string $guest_username optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then.
1527  * @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}
1528  *
1529  * @return string A string consisting of what is wanted based on $mode.
1530  */
1531  function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false)
1532  {
1533      static $_profile_cache;
1534      global $phpbb_dispatcher;
1535   
1536      // We cache some common variables we need within this function
1537      if (empty($_profile_cache))
1538      {
1539          global $phpbb_root_path, $phpEx;
1540   
1541          /** @html Username spans and links for usage in the template */
1542          $_profile_cache['base_url'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile&amp;u={USER_ID}');
1543          $_profile_cache['tpl_noprofile'] = '<span class="username">{USERNAME}</span>';
1544          $_profile_cache['tpl_noprofile_colour'] = '<span style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</span>';
1545          $_profile_cache['tpl_profile'] = '<a href="{PROFILE_URL}" class="username">{USERNAME}</a>';
1546          $_profile_cache['tpl_profile_colour'] = '<a href="{PROFILE_URL}" style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</a>';
1547      }
1548   
1549      global $user, $auth;
1550   
1551      // This switch makes sure we only run code required for the mode
1552      switch ($mode)
1553      {
1554          case 'full':
1555          case 'no_profile':
1556          case 'colour':
1557   
1558              // Build correct username colour
1559              $username_colour = ($username_colour) ? '#' . $username_colour : '';
1560   
1561              // Return colour
1562              if ($mode == 'colour')
1563              {
1564                  $username_string = $username_colour;
1565                  break;
1566              }
1567   
1568          // no break;
1569   
1570          case 'username':
1571   
1572              // Build correct username
1573              if ($guest_username === false)
1574              {
1575                  $username = ($username) ? $username : $user->lang['GUEST'];
1576              }
1577              else
1578              {
1579                  $username = ($user_id && $user_id != ANONYMOUS) ? $username : ((!empty($guest_username)) ? $guest_username : $user->lang['GUEST']);
1580              }
1581   
1582              // Return username
1583              if ($mode == 'username')
1584              {
1585                  $username_string = $username;
1586                  break;
1587              }
1588   
1589          // no break;
1590   
1591          case 'profile':
1592   
1593              // Build correct profile url - only show if not anonymous and permission to view profile if registered user
1594              // For anonymous the link leads to a login page.
1595              if ($user_id && $user_id != ANONYMOUS && ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('u_viewprofile')))
1596              {
1597                  $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']);
1598              }
1599              else
1600              {
1601                  $profile_url = '';
1602              }
1603   
1604              // Return profile
1605              if ($mode == 'profile')
1606              {
1607                  $username_string = $profile_url;
1608                  break;
1609              }
1610   
1611          // no break;
1612      }
1613   
1614      if (!isset($username_string))
1615      {
1616          if (($mode == 'full' && !$profile_url) || $mode == 'no_profile')
1617          {
1618              $username_string = str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']);
1619          }
1620          else
1621          {
1622              $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']);
1623          }
1624      }
1625   
1626      /**
1627      * Use this event to change the output of get_username_string()
1628      *
1629      * @event core.modify_username_string
1630      * @var string mode                profile|username|colour|full|no_profile
1631      * @var int user_id                String or array of additional url
1632      *                                parameters
1633      * @var string username            The user's username
1634      * @var string username_colour    The user's colour
1635      * @var string guest_username    Optional parameter to specify the
1636      *                                guest username.
1637      * @var string custom_profile_url Optional parameter to specify a
1638      *                                profile url.
1639      * @var string username_string    The string that has been generated
1640      * @var array _profile_cache        Array of original return templates
1641      * @since 3.1.0-a1
1642      */
1643      $vars = array(
1644          'mode',
1645          'user_id',
1646          'username',
1647          'username_colour',
1648          'guest_username',
1649          'custom_profile_url',
1650          'username_string',
1651          '_profile_cache',
1652      );
1653      extract($phpbb_dispatcher->trigger_event('core.modify_username_string', compact($vars)));
1654   
1655      return $username_string;
1656  }
1657   
1658  /**
1659   * Add an option to the quick-mod tools.
1660   *
1661   * @param string $url The recepting URL for the quickmod actions.
1662   * @param string $option The language key for the value of the option.
1663   * @param string $lang_string The language string to use.
1664   */
1665  function phpbb_add_quickmod_option($url, $option, $lang_string)
1666  {
1667      global $template, $user, $phpbb_path_helper;
1668   
1669      $lang_string = $user->lang($lang_string);
1670      $template->assign_block_vars('quickmod', array(
1671          'VALUE'        => $option,
1672          'TITLE'        => $lang_string,
1673          'LINK'        => $phpbb_path_helper->append_url_params($url, array('action' => $option)),
1674      ));
1675  }
1676   
1677  /**
1678  * Concatenate an array into a string list.
1679  *
1680  * @param array $items Array of items to concatenate
1681  * @param object $user The phpBB $user object.
1682  *
1683  * @return string String list. Examples: "A"; "A and B"; "A, B, and C"
1684  */
1685  function phpbb_generate_string_list($items, $user)
1686  {
1687      if (empty($items))
1688      {
1689          return '';
1690      }
1691   
1692      $count = count($items);
1693      $last_item = array_pop($items);
1694      $lang_key = 'STRING_LIST_MULTI';
1695   
1696      if ($count == 1)
1697      {
1698          return $last_item;
1699      }
1700      else if ($count == 2)
1701      {
1702          $lang_key = 'STRING_LIST_SIMPLE';
1703      }
1704      $list = implode($user->lang['COMMA_SEPARATOR'], $items);
1705   
1706      return $user->lang($lang_key, $list, $last_item);
1707  }
1708   
1709  class bitfield
1710  {
1711      var $data;
1712   
1713      function __construct($bitfield = '')
1714      {
1715          $this->data = base64_decode($bitfield);
1716      }
1717   
1718      /**
1719      */
1720      function get($n)
1721      {
1722          // Get the ($n / 8)th char
1723          $byte = $n >> 3;
1724   
1725          if (strlen($this->data) >= $byte + 1)
1726          {
1727              $c = $this->data[$byte];
1728   
1729              // Lookup the ($n % 8)th bit of the byte
1730              $bit = 7 - ($n & 7);
1731              return (bool) (ord($c) & (1 << $bit));
1732          }
1733          else
1734          {
1735              return false;
1736          }
1737      }
1738   
1739      function set($n)
1740      {
1741          $byte = $n >> 3;
1742          $bit = 7 - ($n & 7);
1743   
1744          if (strlen($this->data) >= $byte + 1)
1745          {
1746              $this->data[$byte] = $this->data[$byte] | chr(1 << $bit);
1747          }
1748          else
1749          {
1750              $this->data .= str_repeat("\0", $byte - strlen($this->data));
1751              $this->data .= chr(1 << $bit);
1752          }
1753      }
1754   
1755      function clear($n)
1756      {
1757          $byte = $n >> 3;
1758   
1759          if (strlen($this->data) >= $byte + 1)
1760          {
1761              $bit = 7 - ($n & 7);
1762              $this->data[$byte] = $this->data[$byte] &~ chr(1 << $bit);
1763          }
1764      }
1765   
1766      function get_blob()
1767      {
1768          return $this->data;
1769      }
1770   
1771      function get_base64()
1772      {
1773          return base64_encode($this->data);
1774      }
1775   
1776      function get_bin()
1777      {
1778          $bin = '';
1779          $len = strlen($this->data);
1780   
1781          for ($i = 0; $i < $len; ++$i)
1782          {
1783              $bin .= str_pad(decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT);
1784          }
1785   
1786          return $bin;
1787      }
1788   
1789      function get_all_set()
1790      {
1791          return array_keys(array_filter(str_split($this->get_bin())));
1792      }
1793   
1794      function merge($bitfield)
1795      {
1796          $this->data = $this->data | $bitfield->get_blob();
1797      }
1798  }
1799   
1800  /**
1801   * Formats the quote according to the given BBCode status setting
1802   *
1803   * @param phpbb\language\language                $language Language class
1804   * @param parse_message                         $message_parser Message parser class
1805   * @param phpbb\textformatter\utils_interface    $text_formatter_utils Text formatter utilities
1806   * @param bool                                     $bbcode_status The status of the BBCode setting
1807   * @param array                                 $quote_attributes The attributes of the quoted post
1808   * @param string                                 $message_link Link of the original quoted post
1809   */
1810  function phpbb_format_quote($language, $message_parser, $text_formatter_utils, $bbcode_status, $quote_attributes, $message_link = '')
1811  {
1812      if ($bbcode_status)
1813      {
1814          $quote_text = $text_formatter_utils->generate_quote(
1815              censor_text($message_parser->message),
1816              $quote_attributes
1817          );
1818   
1819          $message_parser->message = $quote_text . "\n\n";
1820      }
1821      else
1822      {
1823          $offset = 0;
1824          $quote_string = "&gt; ";
1825          $message = censor_text(trim($message_parser->message));
1826          // see if we are nesting. It's easily tricked but should work for one level of nesting
1827          if (strpos($message, "&gt;") !== false)
1828          {
1829              $offset = 10;
1830          }
1831          $message = utf8_wordwrap($message, 75 + $offset, "\n");
1832   
1833          $message = $quote_string . $message;
1834          $message = str_replace("\n", "\n" . $quote_string, $message);
1835   
1836          $message_parser->message = $quote_attributes['author'] . " " . $language->lang('WROTE') . ":\n" . $message . "\n";
1837      }
1838   
1839      if ($message_link)
1840      {
1841          $message_parser->message = $message_link . $message_parser->message;
1842      }
1843  }
1844