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