Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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