[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/includes/ -> functions_content.php (source)

   1  <?php
   2  /**
   3  *
   4  * This file is part of the phpBB Forum Software package.
   5  *
   6  * @copyright (c) phpBB Limited <https://www.phpbb.com>
   7  * @license GNU General Public License, version 2 (GPL-2.0)
   8  *
   9  * For full copyright and license information, please see
  10  * the docs/CREDITS.txt file.
  11  *
  12  */
  13  
  14  /**
  15  * @ignore
  16  */
  17  if (!defined('IN_PHPBB'))
  18  {
  19      exit;
  20  }
  21  
  22  /**
  23  * gen_sort_selects()
  24  * make_jumpbox()
  25  * bump_topic_allowed()
  26  * get_context()
  27  * phpbb_clean_search_string()
  28  * decode_message()
  29  * strip_bbcode()
  30  * generate_text_for_display()
  31  * generate_text_for_storage()
  32  * generate_text_for_edit()
  33  * make_clickable_callback()
  34  * make_clickable()
  35  * censor_text()
  36  * bbcode_nl2br()
  37  * smiley_text()
  38  * parse_attachments()
  39  * extension_allowed()
  40  * truncate_string()
  41  * get_username_string()
  42  * class bitfield
  43  */
  44  
  45  /**
  46  * Generate sort selection fields
  47  */
  48  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)
  49  {
  50      global $user, $phpbb_dispatcher;
  51  
  52      $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']);
  53  
  54      $sorts = array(
  55          'st'    => array(
  56              'key'        => 'sort_days',
  57              'default'    => $def_st,
  58              'options'    => $limit_days,
  59              'output'    => &$s_limit_days,
  60          ),
  61  
  62          'sk'    => array(
  63              'key'        => 'sort_key',
  64              'default'    => $def_sk,
  65              'options'    => $sort_by_text,
  66              'output'    => &$s_sort_key,
  67          ),
  68  
  69          'sd'    => array(
  70              'key'        => 'sort_dir',
  71              'default'    => $def_sd,
  72              'options'    => $sort_dir_text,
  73              'output'    => &$s_sort_dir,
  74          ),
  75      );
  76      $u_sort_param  = '';
  77  
  78      foreach ($sorts as $name => $sort_ary)
  79      {
  80          $key = $sort_ary['key'];
  81          $selected = ${$sort_ary['key']};
  82  
  83          // Check if the key is selectable. If not, we reset to the default or first key found.
  84          // This ensures the values are always valid. We also set $sort_dir/sort_key/etc. to the
  85          // correct value, else the protection is void. ;)
  86          if (!isset($sort_ary['options'][$selected]))
  87          {
  88              if ($sort_ary['default'] !== false)
  89              {
  90                  $selected = ${$key} = $sort_ary['default'];
  91              }
  92              else
  93              {
  94                  @reset($sort_ary['options']);
  95                  $selected = ${$key} = key($sort_ary['options']);
  96              }
  97          }
  98  
  99          $sort_ary['output'] = '<select name="' . $name . '" id="' . $name . '">';
 100          foreach ($sort_ary['options'] as $option => $text)
 101          {
 102              $sort_ary['output'] .= '<option value="' . $option . '"' . (($selected == $option) ? ' selected="selected"' : '') . '>' . $text . '</option>';
 103          }
 104          $sort_ary['output'] .= '</select>';
 105  
 106          $u_sort_param .= ($selected !== $sort_ary['default']) ? ((strlen($u_sort_param)) ? '&amp;' : '') . "{$name}={$selected}" : '';
 107      }
 108  
 109      /**
 110       * Run code before generated sort selects are returned
 111       *
 112       * @event core.gen_sort_selects_after
 113       * @var    int      limit_days     Days limit
 114       * @var    array    sort_by_text   Sort by text options
 115       * @var    int      sort_days      Sort by days flag
 116       * @var    string   sort_key       Sort key
 117       * @var    string   sort_dir       Sort dir
 118       * @var    string   s_limit_days   String of days limit
 119       * @var    string   s_sort_key     String of sort key
 120       * @var    string   s_sort_dir     String of sort dir
 121       * @var    string   u_sort_param   Sort URL params
 122       * @var    bool     def_st         Default sort days
 123       * @var    bool     def_sk         Default sort key
 124       * @var    bool     def_sd         Default sort dir
 125       * @var    array    sorts          Sorts
 126       * @since 3.1.9-RC1
 127       */
 128      $vars = array(
 129          'limit_days',
 130          'sort_by_text',
 131          'sort_days',
 132          'sort_key',
 133          'sort_dir',
 134          's_limit_days',
 135          's_sort_key',
 136          's_sort_dir',
 137          'u_sort_param',
 138          'def_st',
 139          'def_sk',
 140          'def_sd',
 141          'sorts',
 142      );
 143      extract($phpbb_dispatcher->trigger_event('core.gen_sort_selects_after', compact($vars)));
 144  
 145      return;
 146  }
 147  
 148  /**
 149  * Generate Jumpbox
 150  */
 151  function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list = false, $force_display = false)
 152  {
 153      global $config, $auth, $template, $user, $db, $phpbb_path_helper, $phpbb_dispatcher;
 154  
 155      // We only return if the jumpbox is not forced to be displayed (in case it is needed for functionality)
 156      if (!$config['load_jumpbox'] && $force_display === false)
 157      {
 158          return;
 159      }
 160  
 161      $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
 162          FROM ' . FORUMS_TABLE . '
 163          ORDER BY left_id ASC';
 164      $result = $db->sql_query($sql, 600);
 165  
 166      $rowset = array();
 167      while ($row = $db->sql_fetchrow($result))
 168      {
 169          $rowset[(int) $row['forum_id']] = $row;
 170      }
 171      $db->sql_freeresult($result);
 172  
 173      $right = $padding = 0;
 174      $padding_store = array('0' => 0);
 175      $display_jumpbox = false;
 176      $iteration = 0;
 177  
 178      /**
 179      * Modify the jumpbox forum list data
 180      *
 181      * @event core.make_jumpbox_modify_forum_list
 182      * @var    array    rowset    Array with the forums list data
 183      * @since 3.1.10-RC1
 184      */
 185      $vars = array('rowset');
 186      extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_forum_list', compact($vars)));
 187  
 188      // Sometimes it could happen that forums will be displayed here not be displayed within the index page
 189      // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
 190      // If this happens, the padding could be "broken"
 191  
 192      foreach ($rowset as $row)
 193      {
 194          if ($row['left_id'] < $right)
 195          {
 196              $padding++;
 197              $padding_store[$row['parent_id']] = $padding;
 198          }
 199          else if ($row['left_id'] > $right + 1)
 200          {
 201              // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
 202              // @todo digging deep to find out "how" this can happen.
 203              $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
 204          }
 205  
 206          $right = $row['right_id'];
 207  
 208          if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']))
 209          {
 210              // Non-postable forum with no subforums, don't display
 211              continue;
 212          }
 213  
 214          if (!$auth->acl_get('f_list', $row['forum_id']))
 215          {
 216              // if the user does not have permissions to list this forum skip
 217              continue;
 218          }
 219  
 220          if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id']))
 221          {
 222              continue;
 223          }
 224  
 225          $tpl_ary = array();
 226          if (!$display_jumpbox)
 227          {
 228              $tpl_ary[] = array(
 229                  'FORUM_ID'        => ($select_all) ? 0 : -1,
 230                  'FORUM_NAME'    => ($select_all) ? $user->lang['ALL_FORUMS'] : $user->lang['SELECT_FORUM'],
 231                  'S_FORUM_COUNT'    => $iteration,
 232                  'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $forum_id)),
 233              );
 234  
 235              $iteration++;
 236              $display_jumpbox = true;
 237          }
 238  
 239          $tpl_ary[] = array(
 240              'FORUM_ID'        => $row['forum_id'],
 241              'FORUM_NAME'    => $row['forum_name'],
 242              'SELECTED'        => ($row['forum_id'] == $forum_id) ? ' selected="selected"' : '',
 243              'S_FORUM_COUNT'    => $iteration,
 244              'S_IS_CAT'        => ($row['forum_type'] == FORUM_CAT) ? true : false,
 245              'S_IS_LINK'        => ($row['forum_type'] == FORUM_LINK) ? true : false,
 246              'S_IS_POST'        => ($row['forum_type'] == FORUM_POST) ? true : false,
 247              'LINK'            => $phpbb_path_helper->append_url_params($action, array('f' => $row['forum_id'])),
 248          );
 249  
 250          /**
 251           * Modify the jumpbox before it is assigned to the template
 252           *
 253           * @event core.make_jumpbox_modify_tpl_ary
 254           * @var    array    row                The data of the forum
 255           * @var    array    tpl_ary            Template data of the forum
 256           * @since 3.1.10-RC1
 257           */
 258          $vars = array(
 259              'row',
 260              'tpl_ary',
 261          );
 262          extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_tpl_ary', compact($vars)));
 263  
 264          $template->assign_block_vars_array('jumpbox_forums', $tpl_ary);
 265  
 266          unset($tpl_ary);
 267  
 268          for ($i = 0; $i < $padding; $i++)
 269          {
 270              $template->assign_block_vars('jumpbox_forums.level', array());
 271          }
 272          $iteration++;
 273      }
 274      unset($padding_store, $rowset);
 275  
 276      $url_parts = $phpbb_path_helper->get_url_parts($action);
 277  
 278      $template->assign_vars(array(
 279          'S_DISPLAY_JUMPBOX'            => $display_jumpbox,
 280          'S_JUMPBOX_ACTION'            => $action,
 281          'HIDDEN_FIELDS_FOR_JUMPBOX'    => build_hidden_fields($url_parts['params']),
 282      ));
 283  
 284      return;
 285  }
 286  
 287  /**
 288  * Bump Topic Check - used by posting and viewtopic
 289  */
 290  function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster)
 291  {
 292      global $config, $auth, $user, $phpbb_dispatcher;
 293  
 294      /**
 295       * Event to run code before the topic bump checks
 296       *
 297       * @event core.bump_topic_allowed_before
 298       * @var    int        forum_id            ID of the forum
 299       * @var    int        topic_bumped        Flag indicating if the topic was already bumped (0/1)
 300       * @var    int        last_post_time        The time of the topic last post
 301       * @var    int        topic_poster        User ID of the topic author
 302       * @var    int        last_topic_poster    User ID of the topic last post author
 303       * @since 3.3.14-RC1
 304       */
 305      $vars = [
 306          'forum_id',
 307          'topic_bumped',
 308          'last_post_time',
 309          'topic_poster',
 310          'last_topic_poster',
 311      ];
 312      extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_before', compact($vars)));
 313  
 314      // Check permission and make sure the last post was not already bumped
 315      if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped)
 316      {
 317          return false;
 318      }
 319  
 320      // Check bump time range, is the user really allowed to bump the topic at this time?
 321      $bump_time = ($config['bump_type'] == 'm') ? $config['bump_interval'] * 60 : (($config['bump_type'] == 'h') ? $config['bump_interval'] * 3600 : $config['bump_interval'] * 86400);
 322  
 323      // Check bump time
 324      if ($last_post_time + $bump_time > time())
 325      {
 326          return false;
 327      }
 328  
 329      // Check bumper, only topic poster and last poster are allowed to bump
 330      if ($topic_poster != $user->data['user_id'] && $last_topic_poster != $user->data['user_id'])
 331      {
 332          return false;
 333      }
 334  
 335      /**
 336       * Event to run code after the topic bump checks
 337       *
 338       * @event core.bump_topic_allowed_after
 339       * @var    int        forum_id            ID of the forum
 340       * @var    int        topic_bumped        Flag indicating if the topic was already bumped (0/1)
 341       * @var    int        last_post_time        The time of the topic last post
 342       * @var    int        topic_poster        User ID of the topic author
 343       * @var    int        last_topic_poster    User ID of the topic last post author
 344       * @var    int        bump_time            Bump time range
 345       * @since 3.3.14-RC1
 346       */
 347      $vars = [
 348          'forum_id',
 349          'topic_bumped',
 350          'last_post_time',
 351          'topic_poster',
 352          'last_topic_poster',
 353          'bump_time',
 354      ];
 355      extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_after', compact($vars)));
 356  
 357      // A bump time of 0 will completely disable the bump feature... not intended but might be useful.
 358      return $bump_time;
 359  }
 360  
 361  /**
 362  * Generates a text with approx. the specified length which contains the specified words and their context
 363  *
 364  * @param    string    $text    The full text from which context shall be extracted
 365  * @param    array    $words    An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!)
 366  * @param    int        $length    The desired length of the resulting text, however the result might be shorter or longer than this value
 367  *
 368  * @return    string            Context of the specified words separated by "..."
 369  */
 370  function get_context(string $text, array $words, int $length = 400): string
 371  {
 372      if ($length <= 0)
 373      {
 374          return $text;
 375      }
 376  
 377      // We need to turn the entities back into their original form, to not cut the message in between them
 378      $text = htmlspecialchars_decode($text);
 379  
 380      // Replace all spaces/invisible characters with single spaces
 381      $text = preg_replace("/[\p{Z}\h\v]+/u", ' ', $text);
 382  
 383      $text_length = utf8_strlen($text);
 384  
 385      // Get first occurrence of each word
 386      $word_indexes = [];
 387      foreach ($words as $word)
 388      {
 389          $pos = utf8_stripos($text, $word);
 390  
 391          if ($pos !== false)
 392          {
 393              $word_indexes[$pos] = $word;
 394          }
 395      }
 396      if (!empty($word_indexes))
 397      {
 398          ksort($word_indexes);
 399  
 400          // Size of the fragment of text per word
 401          $num_indexes = count($word_indexes);
 402          $characters_per_word = (int) ($length / $num_indexes) + 2; // 2 to leave one character of margin at the sides to don't cut words
 403  
 404          // Get text fragment indexes
 405          $fragments = [];
 406          foreach ($word_indexes as $index => $word)
 407          {
 408              $word_length = utf8_strlen($word);
 409              $start = max(0, min($text_length - 1 - $characters_per_word, (int) ($index + ($word_length / 2) - ($characters_per_word / 2))));
 410              $end = $start + $characters_per_word;
 411  
 412              // Check if we can merge this fragment into the previous fragment
 413              if (!empty($fragments))
 414              {
 415                  [$prev_start, $prev_end] = end($fragments);
 416  
 417                  if ($prev_end + $characters_per_word >= $index + $word_length)
 418                  {
 419                      array_pop($fragments);
 420                      $start = $prev_start;
 421                      $end = $prev_end + $characters_per_word;
 422                  }
 423              }
 424  
 425              $fragments[] = [$start, $end];
 426          }
 427      }
 428      else
 429      {
 430          // There is no coincidences, so we just create a fragment with the first $length characters
 431          $fragments[] = [0, $length];
 432          $end = $length;
 433      }
 434  
 435      $output = [];
 436      foreach ($fragments as [$start, $end])
 437      {
 438          $fragment = utf8_substr($text, $start, $end - $start + 1);
 439  
 440          $fragment_start = 0;
 441          $fragment_end = $end - $start + 1;
 442  
 443          // Find the first valid alphanumeric character in the fragment to don't cut words
 444          if ($start > 0 && preg_match('/[^\p{L}\p{N}][\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE))
 445          {
 446              $fragment_start = utf8_strlen(substr($fragment, 0, (int) $matches[0][1])) + 1;
 447          }
 448  
 449          // Find the last valid alphanumeric character in the fragment to don't cut words
 450          if ($end < $text_length - 1 && preg_match_all('/[\p{L}\p{N}][^\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE))
 451          {
 452              $fragment_end = utf8_strlen(substr($fragment, 0, end($matches[0])[1]));
 453          }
 454  
 455          $output[] = utf8_substr($fragment, $fragment_start, $fragment_end - $fragment_start + 1);
 456      }
 457  
 458      return ($fragments[0][0] !== 0 ? '... ' : '') . utf8_htmlspecialchars(implode(' ... ', $output)) . ($end < $text_length - 1 ? ' ...' : '');
 459  }
 460  
 461  /**
 462  * Cleans a search string by removing single wildcards from it and replacing multiple spaces with a single one.
 463  *
 464  * @param string $search_string The full search string which should be cleaned.
 465  *
 466  * @return string The cleaned search string without any wildcards and multiple spaces.
 467  */
 468  function phpbb_clean_search_string($search_string)
 469  {
 470      // This regular expressions matches every single wildcard.
 471      // That means one after a whitespace or the beginning of the string or one before a whitespace or the end of the string.
 472      $search_string = preg_replace('#(?<=^|\s)\*+(?=\s|$)#', '', $search_string);
 473      $search_string = trim($search_string);
 474      $search_string = preg_replace(array('#\s+#u', '#\*+#u'), array(' ', '*'), $search_string);
 475      return $search_string;
 476  }
 477  
 478  /**
 479  * Decode text whereby text is coming from the db and expected to be pre-parsed content
 480  * We are placing this outside of the message parser because we are often in need of it...
 481  *
 482  * NOTE: special chars are kept encoded
 483  *
 484  * @param string &$message Original message, passed by reference
 485  * @param string $bbcode_uid BBCode UID
 486  * @return null
 487  */
 488  function decode_message(&$message, $bbcode_uid = '')
 489  {
 490      global $phpbb_container, $phpbb_dispatcher;
 491  
 492      /**
 493       * Use this event to modify the message before it is decoded
 494       *
 495       * @event core.decode_message_before
 496       * @var string    message_text    The message content
 497       * @var string    bbcode_uid        The message BBCode UID
 498       * @since 3.1.9-RC1
 499       */
 500      $message_text = $message;
 501      $vars = array('message_text', 'bbcode_uid');
 502      extract($phpbb_dispatcher->trigger_event('core.decode_message_before', compact($vars)));
 503      $message = $message_text;
 504  
 505      if (preg_match('#^<[rt][ >]#', $message))
 506      {
 507          $message = htmlspecialchars($phpbb_container->get('text_formatter.utils')->unparse($message), ENT_COMPAT);
 508      }
 509      else
 510      {
 511          if ($bbcode_uid)
 512          {
 513              $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid");
 514              $replace = array("\n", '', '', '', '');
 515          }
 516          else
 517          {
 518              $match = array('<br />');
 519              $replace = array("\n");
 520          }
 521  
 522          $message = str_replace($match, $replace, $message);
 523  
 524          $match = get_preg_expression('bbcode_htm');
 525          $replace = array('\1', '\1', '\2', '\2', '\1', '', '');
 526  
 527          $message = preg_replace($match, $replace, $message);
 528      }
 529  
 530      /**
 531      * Use this event to modify the message after it is decoded
 532      *
 533      * @event core.decode_message_after
 534      * @var string    message_text    The message content
 535      * @var string    bbcode_uid        The message BBCode UID
 536      * @since 3.1.9-RC1
 537      */
 538      $message_text = $message;
 539      $vars = array('message_text', 'bbcode_uid');
 540      extract($phpbb_dispatcher->trigger_event('core.decode_message_after', compact($vars)));
 541      $message = $message_text;
 542  }
 543  
 544  /**
 545  * Strips all bbcode from a text in place
 546  */
 547  function strip_bbcode(&$text, $uid = '')
 548  {
 549      global $phpbb_container;
 550  
 551      if (preg_match('#^<[rt][ >]#', $text))
 552      {
 553          $text = utf8_htmlspecialchars($phpbb_container->get('text_formatter.utils')->clean_formatting($text));
 554      }
 555      else
 556      {
 557          if (!$uid)
 558          {
 559              $uid = '[0-9a-z]{5,}';
 560          }
 561  
 562          $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:&quot;.*&quot;|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text);
 563  
 564          $match = get_preg_expression('bbcode_htm');
 565          $replace = array('\1', '\1', '\2', '\1', '', '');
 566  
 567          $text = preg_replace($match, $replace, $text);
 568      }
 569  }
 570  
 571  /**
 572  * For display of custom parsed text on user-facing pages
 573  * Expects $text to be the value directly from the database (stored value)
 574  */
 575  function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true)
 576  {
 577      static $bbcode;
 578      global $auth, $config, $user;
 579      global $phpbb_dispatcher, $phpbb_container;
 580  
 581      if ($text === '')
 582      {
 583          return '';
 584      }
 585  
 586      /**
 587      * Use this event to modify the text before it is parsed
 588      *
 589      * @event core.modify_text_for_display_before
 590      * @var string    text            The text to parse
 591      * @var string    uid                The BBCode UID
 592      * @var string    bitfield        The BBCode Bitfield
 593      * @var int        flags            The BBCode Flags
 594      * @var bool        censor_text        Whether or not to apply word censors
 595      * @since 3.1.0-a1
 596      */
 597      $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text');
 598      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars)));
 599  
 600      if (preg_match('#^<[rt][ >]#', $text))
 601      {
 602          $renderer = $phpbb_container->get('text_formatter.renderer');
 603  
 604          // Temporarily switch off viewcensors if applicable
 605          $old_censor = $renderer->get_viewcensors();
 606  
 607          // Check here if the user is having viewing censors disabled (and also allowed to do so).
 608          if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors'))
 609          {
 610              $censor_text = false;
 611          }
 612  
 613          if ($old_censor !== $censor_text)
 614          {
 615              $renderer->set_viewcensors($censor_text);
 616          }
 617  
 618          $text = $renderer->render($text);
 619  
 620          // Restore the previous value
 621          if ($old_censor !== $censor_text)
 622          {
 623              $renderer->set_viewcensors($old_censor);
 624          }
 625      }
 626      else
 627      {
 628          if ($censor_text)
 629          {
 630              $text = censor_text($text);
 631          }
 632  
 633          // Parse bbcode if bbcode uid stored and bbcode enabled
 634          if ($uid && ($flags & OPTION_FLAG_BBCODE))
 635          {
 636              if (!class_exists('bbcode'))
 637              {
 638                  global $phpbb_root_path, $phpEx;
 639                  include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
 640              }
 641  
 642              if (empty($bbcode))
 643              {
 644                  $bbcode = new bbcode($bitfield);
 645              }
 646              else
 647              {
 648                  $bbcode->bbcode_set_bitfield($bitfield);
 649              }
 650  
 651              $bbcode->bbcode_second_pass($text, $uid);
 652          }
 653  
 654          $text = bbcode_nl2br($text);
 655          $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES));
 656      }
 657  
 658      /**
 659      * Use this event to modify the text after it is parsed
 660      *
 661      * @event core.modify_text_for_display_after
 662      * @var string    text        The text to parse
 663      * @var string    uid            The BBCode UID
 664      * @var string    bitfield    The BBCode Bitfield
 665      * @var int        flags        The BBCode Flags
 666      * @since 3.1.0-a1
 667      */
 668      $vars = array('text', 'uid', 'bitfield', 'flags');
 669      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_after', compact($vars)));
 670  
 671      return $text;
 672  }
 673  
 674  /**
 675  * For parsing custom parsed text to be stored within the database.
 676  * This function additionally returns the uid and bitfield that needs to be stored.
 677  * Expects $text to be the value directly from $request->variable() and in it's non-parsed form
 678  *
 679  * @param string $text The text to be replaced with the parsed one
 680  * @param string $uid The BBCode uid for this parse
 681  * @param string $bitfield The BBCode bitfield for this parse
 682  * @param int $flags The allow_bbcode, allow_urls and allow_smilies compiled into a single integer.
 683  * @param bool $allow_bbcode If BBCode is allowed (i.e. if BBCode is parsed)
 684  * @param bool $allow_urls If urls is allowed
 685  * @param bool $allow_smilies If smilies are allowed
 686  * @param bool $allow_img_bbcode
 687  * @param bool $allow_flash_bbcode
 688  * @param bool $allow_quote_bbcode
 689  * @param bool $allow_url_bbcode
 690  * @param string $mode Mode to parse text as, e.g. post or sig
 691  *
 692  * @return array    An array of string with the errors that occurred while parsing
 693  */
 694  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')
 695  {
 696      global $phpbb_root_path, $phpEx, $phpbb_dispatcher;
 697  
 698      /**
 699      * Use this event to modify the text before it is prepared for storage
 700      *
 701      * @event core.modify_text_for_storage_before
 702      * @var string    text            The text to parse
 703      * @var string    uid                The BBCode UID
 704      * @var string    bitfield        The BBCode Bitfield
 705      * @var int        flags            The BBCode Flags
 706      * @var bool        allow_bbcode    Whether or not to parse BBCode
 707      * @var bool        allow_urls        Whether or not to parse URLs
 708      * @var bool        allow_smilies    Whether or not to parse Smilies
 709      * @var bool        allow_img_bbcode    Whether or not to parse the [img] BBCode
 710      * @var bool        allow_flash_bbcode    Whether or not to parse the [flash] BBCode
 711      * @var bool        allow_quote_bbcode    Whether or not to parse the [quote] BBCode
 712      * @var bool        allow_url_bbcode    Whether or not to parse the [url] BBCode
 713      * @var string    mode                Mode to parse text as, e.g. post or sig
 714      * @since 3.1.0-a1
 715      * @changed 3.2.0-a1 Added mode
 716      */
 717      $vars = array(
 718          'text',
 719          'uid',
 720          'bitfield',
 721          'flags',
 722          'allow_bbcode',
 723          'allow_urls',
 724          'allow_smilies',
 725          'allow_img_bbcode',
 726          'allow_flash_bbcode',
 727          'allow_quote_bbcode',
 728          'allow_url_bbcode',
 729          'mode',
 730      );
 731      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_before', compact($vars)));
 732  
 733      $uid = $bitfield = '';
 734      $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0);
 735  
 736      if (!class_exists('parse_message'))
 737      {
 738          include($phpbb_root_path . 'includes/message_parser.' . $phpEx);
 739      }
 740  
 741      $message_parser = new parse_message($text);
 742      $message_parser->parse($allow_bbcode, $allow_urls, $allow_smilies, $allow_img_bbcode, $allow_flash_bbcode, $allow_quote_bbcode, $allow_url_bbcode, true, $mode);
 743  
 744      $text = $message_parser->message;
 745      $uid = $message_parser->bbcode_uid;
 746  
 747      // If the bbcode_bitfield is empty, there is no need for the uid to be stored.
 748      if (!$message_parser->bbcode_bitfield)
 749      {
 750          $uid = '';
 751      }
 752  
 753      $bitfield = $message_parser->bbcode_bitfield;
 754  
 755      /**
 756      * Use this event to modify the text after it is prepared for storage
 757      *
 758      * @event core.modify_text_for_storage_after
 759      * @var string    text            The text to parse
 760      * @var string    uid                The BBCode UID
 761      * @var string    bitfield        The BBCode Bitfield
 762      * @var int        flags            The BBCode Flags
 763      * @var string    message_parser    The message_parser object
 764      * @since 3.1.0-a1
 765      * @changed 3.1.11-RC1            Added message_parser to vars
 766      */
 767      $vars = array('text', 'uid', 'bitfield', 'flags', 'message_parser');
 768      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_after', compact($vars)));
 769  
 770      return $message_parser->warn_msg;
 771  }
 772  
 773  /**
 774  * For decoding custom parsed text for edits as well as extracting the flags
 775  * Expects $text to be the value directly from the database (pre-parsed content)
 776  */
 777  function generate_text_for_edit($text, $uid, $flags)
 778  {
 779      global $phpbb_dispatcher;
 780  
 781      /**
 782      * Use this event to modify the text before it is decoded for editing
 783      *
 784      * @event core.modify_text_for_edit_before
 785      * @var string    text            The text to parse
 786      * @var string    uid                The BBCode UID
 787      * @var int        flags            The BBCode Flags
 788      * @since 3.1.0-a1
 789      */
 790      $vars = array('text', 'uid', 'flags');
 791      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_before', compact($vars)));
 792  
 793      decode_message($text, $uid);
 794  
 795      /**
 796      * Use this event to modify the text after it is decoded for editing
 797      *
 798      * @event core.modify_text_for_edit_after
 799      * @var string    text            The text to parse
 800      * @var int        flags            The BBCode Flags
 801      * @since 3.1.0-a1
 802      */
 803      $vars = array('text', 'flags');
 804      extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_after', compact($vars)));
 805  
 806      return array(
 807          'allow_bbcode'    => ($flags & OPTION_FLAG_BBCODE) ? 1 : 0,
 808          'allow_smilies'    => ($flags & OPTION_FLAG_SMILIES) ? 1 : 0,
 809          'allow_urls'    => ($flags & OPTION_FLAG_LINKS) ? 1 : 0,
 810          'text'            => $text
 811      );
 812  }
 813  
 814  /**
 815  * A subroutine of make_clickable used with preg_replace
 816  * It places correct HTML around an url, shortens the displayed text
 817  * and makes sure no entities are inside URLs
 818  */
 819  function make_clickable_callback($type, $whitespace, $url, $relative_url, $class)
 820  {
 821      $orig_url        = $url;
 822      $orig_relative    = $relative_url;
 823      $append            = '';
 824      $url            = html_entity_decode($url, ENT_COMPAT);
 825      $relative_url    = html_entity_decode($relative_url, ENT_COMPAT);
 826  
 827      // make sure no HTML entities were matched
 828      $chars = array('<', '>', '"');
 829      $split = false;
 830  
 831      foreach ($chars as $char)
 832      {
 833          $next_split = strpos($url, $char);
 834          if ($next_split !== false)
 835          {
 836              $split = ($split !== false) ? min($split, $next_split) : $next_split;
 837          }
 838      }
 839  
 840      if ($split !== false)
 841      {
 842          // an HTML entity was found, so the URL has to end before it
 843          $append            = substr($url, $split) . $relative_url;
 844          $url            = substr($url, 0, $split);
 845          $relative_url    = '';
 846      }
 847      else if ($relative_url)
 848      {
 849          // same for $relative_url
 850          $split = false;
 851          foreach ($chars as $char)
 852          {
 853              $next_split = strpos($relative_url, $char);
 854              if ($next_split !== false)
 855              {
 856                  $split = ($split !== false) ? min($split, $next_split) : $next_split;
 857              }
 858          }
 859  
 860          if ($split !== false)
 861          {
 862              $append            = substr($relative_url, $split);
 863              $relative_url    = substr($relative_url, 0, $split);
 864          }
 865      }
 866  
 867      // if the last character of the url is a punctuation mark, exclude it from the url
 868      $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];
 869  
 870      switch ($last_char)
 871      {
 872          case '.':
 873          case '?':
 874          case '!':
 875          case ':':
 876          case ',':
 877              $append = $last_char;
 878              if ($relative_url)
 879              {
 880                  $relative_url = substr($relative_url, 0, -1);
 881              }
 882              else
 883              {
 884                  $url = substr($url, 0, -1);
 885              }
 886          break;
 887  
 888          // set last_char to empty here, so the variable can be used later to
 889          // check whether a character was removed
 890          default:
 891              $last_char = '';
 892          break;
 893      }
 894  
 895      $short_url = (utf8_strlen($url) > 55) ? utf8_substr($url, 0, 39) . ' ... ' . utf8_substr($url, -10) : $url;
 896  
 897      switch ($type)
 898      {
 899          case MAGIC_URL_LOCAL:
 900              $tag            = 'l';
 901              $relative_url    = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url));
 902              $url            = $url . '/' . $relative_url;
 903              $text            = $relative_url;
 904  
 905              // this url goes to http://domain.tld/path/to/board/ which
 906              // would result in an empty link if treated as local so
 907              // don't touch it and let MAGIC_URL_FULL take care of it.
 908              if (!$relative_url)
 909              {
 910                  return $whitespace . $orig_url . '/' . $orig_relative; // slash is taken away by relative url pattern
 911              }
 912          break;
 913  
 914          case MAGIC_URL_FULL:
 915              $tag    = 'm';
 916              $text    = $short_url;
 917          break;
 918  
 919          case MAGIC_URL_WWW:
 920              $tag    = 'w';
 921              $url    = 'http://' . $url;
 922              $text    = $short_url;
 923          break;
 924  
 925          case MAGIC_URL_EMAIL:
 926              $tag    = 'e';
 927              $text    = $short_url;
 928              $url    = 'mailto:' . $url;
 929          break;
 930      }
 931  
 932      $url    = htmlspecialchars($url, ENT_COMPAT);
 933      $text    = htmlspecialchars($text, ENT_COMPAT);
 934      $append    = htmlspecialchars($append, ENT_COMPAT);
 935  
 936      $html    = "$whitespace<!-- $tag --><a$class href=\"$url\">$text</a><!-- $tag -->$append";
 937  
 938      return $html;
 939  }
 940  
 941  /**
 942   * Replaces magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
 943   * Cuts down displayed size of link if over 50 chars, turns absolute links
 944   * into relative versions when the server/script path matches the link
 945   *
 946   * @param string        $text        Message text to parse URL/email entries
 947   * @param bool|string    $server_url    The server URL. If false, the board URL will be used
 948   * @param string        $class        CSS class selector to add to the parsed URL entries
 949   *
 950   * @return string    A text with parsed URL/email entries
 951   */
 952  function make_clickable($text, $server_url = false, string $class = 'postlink')
 953  {
 954      if ($server_url === false)
 955      {
 956          $server_url = generate_board_url();
 957      }
 958  
 959      static $static_class;
 960      static $magic_url_match_args;
 961  
 962      if (!isset($magic_url_match_args[$server_url]) || $static_class != $class)
 963      {
 964          $static_class = $class;
 965          $class = ($static_class) ? ' class="' . $static_class . '"' : '';
 966          $local_class = ($static_class) ? ' class="' . $static_class . '-local"' : '';
 967  
 968          if (!is_array($magic_url_match_args))
 969          {
 970              $magic_url_match_args = array();
 971          }
 972  
 973          // Check if the match for this $server_url and $class already exists
 974          $element_exists = false;
 975          if (isset($magic_url_match_args[$server_url]))
 976          {
 977              array_walk_recursive($magic_url_match_args[$server_url], function($value) use (&$element_exists, $static_class)
 978                  {
 979                      if ($value == $static_class)
 980                      {
 981                          $element_exists = true;
 982                          return;
 983                      }
 984                  }
 985              );
 986          }
 987  
 988          // Only add new $server_url and $class matches if not exist
 989          if (!$element_exists)
 990          {
 991              // relative urls for this board
 992              $magic_url_match_args[$server_url][] = [
 993                  '#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#iu',
 994                  MAGIC_URL_LOCAL,
 995                  $local_class,
 996                  $static_class,
 997              ];
 998  
 999              // matches a xxxx://aaaaa.bbb.cccc. ...
1000              $magic_url_match_args[$server_url][] = [
1001                  '#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#iu',
1002                  MAGIC_URL_FULL,
1003                  $class,
1004                  $static_class,
1005              ];
1006  
1007              // matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
1008              $magic_url_match_args[$server_url][] = [
1009                  '#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#iu',
1010                  MAGIC_URL_WWW,
1011                  $class,
1012                  $static_class,
1013              ];
1014          }
1015  
1016          if (!isset($magic_url_match_args[$server_url]['email']))
1017          {
1018              // matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode.
1019              $magic_url_match_args[$server_url]['email'] = [
1020                  '/(^|[\n\t (>])(' . get_preg_expression('email') . ')/iu',
1021                  MAGIC_URL_EMAIL,
1022                  '',
1023              ];
1024          }
1025      }
1026  
1027      foreach ($magic_url_match_args[$server_url] as $magic_args)
1028      {
1029          if (preg_match($magic_args[0], $text, $matches))
1030          {
1031              // Only apply $class from the corresponding function call argument (excepting emails which never has a class)
1032              if ($magic_args[1] != MAGIC_URL_EMAIL && $magic_args[3] != $static_class)
1033              {
1034                  continue;
1035              }
1036  
1037              $text = preg_replace_callback($magic_args[0], function($matches) use ($magic_args)
1038              {
1039                  $relative_url = isset($matches[3]) ? $matches[3] : '';
1040                  return make_clickable_callback($magic_args[1], $matches[1], $matches[2], $relative_url, $magic_args[2]);
1041              }, $text);
1042          }
1043      }
1044  
1045      return $text;
1046  }
1047  
1048  /**
1049  * Censoring
1050  */
1051  function censor_text($text)
1052  {
1053      static $censors;
1054  
1055      // Nothing to do?
1056      if ($text === '')
1057      {
1058          return '';
1059      }
1060  
1061      // We moved the word censor checks in here because we call this function quite often - and then only need to do the check once
1062      if (!isset($censors) || !is_array($censors))
1063      {
1064          global $config, $user, $auth, $cache;
1065  
1066          // We check here if the user is having viewing censors disabled (and also allowed to do so).
1067          if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors'))
1068          {
1069              $censors = array();
1070          }
1071          else
1072          {
1073              $censors = $cache->obtain_word_list();
1074          }
1075      }
1076  
1077      if (count($censors))
1078      {
1079          return preg_replace($censors['match'], $censors['replace'], $text);
1080      }
1081  
1082      return $text;
1083  }
1084  
1085  /**
1086  * custom version of nl2br which takes custom BBCodes into account
1087  */
1088  function bbcode_nl2br($text)
1089  {
1090      // custom BBCodes might contain carriage returns so they
1091      // are not converted into <br /> so now revert that
1092      $text = str_replace(array("\n", "\r"), array('<br />', "\n"), $text);
1093      return $text;
1094  }
1095  
1096  /**
1097  * Smiley processing
1098  */
1099  function smiley_text($text, $force_option = false)
1100  {
1101      global $config, $user, $phpbb_path_helper, $phpbb_dispatcher;
1102  
1103      if ($force_option || !$config['allow_smilies'] || !$user->optionget('viewsmilies'))
1104      {
1105          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#', '\1', $text);
1106      }
1107      else
1108      {
1109          $root_path = $phpbb_path_helper->get_web_root_path();
1110  
1111          /**
1112          * Event to override the root_path for smilies
1113          *
1114          * @event core.smiley_text_root_path
1115          * @var string root_path root_path for smilies
1116          * @since 3.1.11-RC1
1117          */
1118          $vars = array('root_path');
1119          extract($phpbb_dispatcher->trigger_event('core.smiley_text_root_path', compact($vars)));
1120          return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img class="smilies" src="' . $root_path . $config['smilies_path'] . '/\2 />', $text);
1121      }
1122  }
1123  
1124  /**
1125  * General attachment parsing
1126  *
1127  * @param mixed $forum_id The forum id the attachments are displayed in (false if in private message)
1128  * @param string &$message The post/private message
1129  * @param array &$attachments The attachments to parse for (inline) display. The attachments array will hold templated data after parsing.
1130  * @param array &$update_count_ary The attachment counts to be updated - will be filled
1131  * @param bool $preview If set to true the attachments are parsed for preview. Within preview mode the comments are fetched from the given $attachments array and not fetched from the database.
1132  */
1133  function parse_attachments($forum_id, &$message, &$attachments, &$update_count_ary, $preview = false)
1134  {
1135      if (!count($attachments))
1136      {
1137          return;
1138      }
1139  
1140      global $template, $cache, $user, $phpbb_dispatcher;
1141      global $extensions, $config, $phpbb_root_path, $phpEx;
1142  
1143      //
1144      $compiled_attachments = array();
1145  
1146      if (!isset($template->filename['attachment_tpl']))
1147      {
1148          $template->set_filenames(array(
1149              'attachment_tpl'    => 'attachment.html')
1150          );
1151      }
1152  
1153      if (empty($extensions) || !is_array($extensions))
1154      {
1155          $extensions = $cache->obtain_attach_extensions($forum_id);
1156      }
1157  
1158      // Look for missing attachment information...
1159      $attach_ids = array();
1160      foreach ($attachments as $pos => $attachment)
1161      {
1162          // If is_orphan is set, we need to retrieve the attachments again...
1163          if (!isset($attachment['extension']) && !isset($attachment['physical_filename']))
1164          {
1165              $attach_ids[(int) $attachment['attach_id']] = $pos;
1166          }
1167      }
1168  
1169      // Grab attachments (security precaution)
1170      if (count($attach_ids))
1171      {
1172          global $db;
1173  
1174          $new_attachment_data = array();
1175  
1176          $sql = 'SELECT *
1177              FROM ' . ATTACHMENTS_TABLE . '
1178              WHERE ' . $db->sql_in_set('attach_id', array_keys($attach_ids));
1179          $result = $db->sql_query($sql);
1180  
1181          while ($row = $db->sql_fetchrow($result))
1182          {
1183              if (!isset($attach_ids[$row['attach_id']]))
1184              {
1185                  continue;
1186              }
1187  
1188              // If we preview attachments we will set some retrieved values here
1189              if ($preview)
1190              {
1191                  $row['attach_comment'] = $attachments[$attach_ids[$row['attach_id']]]['attach_comment'];
1192              }
1193  
1194              $new_attachment_data[$attach_ids[$row['attach_id']]] = $row;
1195          }
1196          $db->sql_freeresult($result);
1197  
1198          $attachments = $new_attachment_data;
1199          unset($new_attachment_data);
1200      }
1201  
1202      // Make sure attachments are properly ordered
1203      ksort($attachments);
1204  
1205      foreach ($attachments as $attachment)
1206      {
1207          if (!count($attachment))
1208          {
1209              continue;
1210          }
1211  
1212          // We need to reset/empty the _file block var, because this function might be called more than once
1213          $template->destroy_block_vars('_file');
1214  
1215          $block_array = array();
1216  
1217          // Some basics...
1218          $attachment['extension'] = strtolower(trim($attachment['extension']));
1219          $filename = $phpbb_root_path . $config['upload_path'] . '/' . utf8_basename($attachment['physical_filename']);
1220  
1221          $upload_icon = '';
1222          $download_link = '';
1223          $display_cat = false;
1224  
1225          if (isset($extensions[$attachment['extension']]))
1226          {
1227              if ($user->img('icon_topic_attach', '') && !$extensions[$attachment['extension']]['upload_icon'])
1228              {
1229                  $upload_icon = $user->img('icon_topic_attach', '');
1230              }
1231              else if ($extensions[$attachment['extension']]['upload_icon'])
1232              {
1233                  $upload_icon = '<img src="' . $phpbb_root_path . $config['upload_icons_path'] . '/' . trim($extensions[$attachment['extension']]['upload_icon']) . '" alt="" />';
1234              }
1235          }
1236  
1237          $filesize = get_formatted_filesize($attachment['filesize'], false);
1238  
1239          $comment = bbcode_nl2br(censor_text($attachment['attach_comment']));
1240  
1241          $block_array += array(
1242              'UPLOAD_ICON'        => $upload_icon,
1243              'FILESIZE'            => $filesize['value'],
1244              'SIZE_LANG'            => $filesize['unit'],
1245              'DOWNLOAD_NAME'        => utf8_basename($attachment['real_filename']),
1246              'COMMENT'            => $comment,
1247          );
1248  
1249          $denied = false;
1250  
1251          if (!extension_allowed($forum_id, $attachment['extension'], $extensions))
1252          {
1253              $denied = true;
1254  
1255              $block_array += array(
1256                  'S_DENIED'            => true,
1257                  'DENIED_MESSAGE'    => sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])
1258              );
1259          }
1260  
1261          if (!$denied)
1262          {
1263              $display_cat = $extensions[$attachment['extension']]['display_cat'];
1264  
1265              if ($display_cat == ATTACHMENT_CATEGORY_IMAGE)
1266              {
1267                  if ($attachment['thumbnail'])
1268                  {
1269                      $display_cat = ATTACHMENT_CATEGORY_THUMB;
1270                  }
1271                  else
1272                  {
1273                      if ($config['img_display_inlined'])
1274                      {
1275                          if ($config['img_link_width'] || $config['img_link_height'])
1276                          {
1277                              $dimension = @getimagesize($filename);
1278  
1279                              // If the dimensions could not be determined or the image being 0x0 we display it as a link for safety purposes
1280                              if ($dimension === false || empty($dimension[0]) || empty($dimension[1]))
1281                              {
1282                                  $display_cat = ATTACHMENT_CATEGORY_NONE;
1283                              }
1284                              else
1285                              {
1286                                  $display_cat = ($dimension[0] <= $config['img_link_width'] && $dimension[1] <= $config['img_link_height']) ? ATTACHMENT_CATEGORY_IMAGE : ATTACHMENT_CATEGORY_NONE;
1287                              }
1288                          }
1289                      }
1290                      else
1291                      {
1292                          $display_cat = ATTACHMENT_CATEGORY_NONE;
1293                      }
1294                  }
1295              }
1296  
1297              // Make some descisions based on user options being set.
1298              if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg'))
1299              {
1300                  $display_cat = ATTACHMENT_CATEGORY_NONE;
1301              }
1302  
1303              $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
1304              $l_downloaded_viewed = 'VIEWED_COUNTS';
1305  
1306              switch ($display_cat)
1307              {
1308                  // Images
1309                  case ATTACHMENT_CATEGORY_IMAGE:
1310                      $inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);
1311                      $download_link .= '&amp;mode=view';
1312  
1313                      $block_array += array(
1314                          'S_IMAGE'        => true,
1315                          'U_INLINE_LINK'        => $inline_link,
1316                      );
1317  
1318                      $update_count_ary[] = $attachment['attach_id'];
1319                  break;
1320  
1321                  // Images, but display Thumbnail
1322                  case ATTACHMENT_CATEGORY_THUMB:
1323                      $thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&amp;t=1');
1324                      $download_link .= '&amp;mode=view';
1325  
1326                      $block_array += array(
1327                          'S_THUMBNAIL'        => true,
1328                          'THUMB_IMAGE'        => $thumbnail_link,
1329                      );
1330  
1331                      $update_count_ary[] = $attachment['attach_id'];
1332                  break;
1333  
1334                  default:
1335                      $l_downloaded_viewed = 'DOWNLOAD_COUNTS';
1336  
1337                      $block_array += array(
1338                          'S_FILE'        => true,
1339                      );
1340                  break;
1341              }
1342  
1343              if (!isset($attachment['download_count']))
1344              {
1345                  $attachment['download_count'] = 0;
1346              }
1347  
1348              $block_array += array(
1349                  'U_DOWNLOAD_LINK'        => $download_link,
1350                  'L_DOWNLOAD_COUNT'        => $user->lang($l_downloaded_viewed, (int) $attachment['download_count']),
1351              );
1352          }
1353  
1354          $update_count = $update_count_ary;
1355          /**
1356          * Use this event to modify the attachment template data.
1357          *
1358          * This event is triggered once per attachment.
1359          *
1360          * @event core.parse_attachments_modify_template_data
1361          * @var array    attachment        Array with attachment data
1362          * @var array    block_array        Template data of the attachment
1363          * @var int        display_cat        Attachment category data
1364          * @var string    download_link    Attachment download link
1365          * @var array    extensions        Array with attachment extensions data
1366          * @var mixed     forum_id         The forum id the attachments are displayed in (false if in private message)
1367          * @var bool        preview            Flag indicating if we are in post preview mode
1368          * @var array    update_count    Array with attachment ids to update download count
1369          * @since 3.1.0-RC5
1370          */
1371          $vars = array(
1372              'attachment',
1373              'block_array',
1374              'display_cat',
1375              'download_link',
1376              'extensions',
1377              'forum_id',
1378              'preview',
1379              'update_count',
1380          );
1381          extract($phpbb_dispatcher->trigger_event('core.parse_attachments_modify_template_data', compact($vars)));
1382          $update_count_ary = $update_count;
1383          unset($update_count, $display_cat, $download_link);
1384  
1385          $template->assign_block_vars('_file', $block_array);
1386  
1387          $compiled_attachments[] = $template->assign_display('attachment_tpl');
1388      }
1389  
1390      $attachments = $compiled_attachments;
1391      unset($compiled_attachments);
1392  
1393      $unset_tpl = array();
1394  
1395      preg_match_all('#<!\-\- ia([0-9]+) \-\->(.*?)<!\-\- ia\1 \-\->#', $message, $matches, PREG_PATTERN_ORDER);
1396  
1397      $replace = array();
1398      foreach ($matches[0] as $num => $capture)
1399      {
1400          $index = $matches[1][$num];
1401  
1402          $replace['from'][] = $matches[0][$num];
1403          $replace['to'][] = (isset($attachments[$index])) ? $attachments[$index] : sprintf($user->lang['MISSING_INLINE_ATTACHMENT'], $matches[2][array_search($index, $matches[1])]);
1404  
1405          $unset_tpl[] = $index;
1406      }
1407  
1408      if (isset($replace['from']))
1409      {
1410          $message = str_replace($replace['from'], $replace['to'], $message);
1411      }
1412  
1413      $unset_tpl = array_unique($unset_tpl);
1414  
1415      // Sort correctly
1416      if ($config['display_order'])
1417      {
1418          // Ascending sort
1419          krsort($attachments);
1420      }
1421      else
1422      {
1423          // Descending sort
1424          ksort($attachments);
1425      }
1426  
1427      // Needed to let not display the inlined attachments at the end of the post again
1428      foreach ($unset_tpl as $index)
1429      {
1430          unset($attachments[$index]);
1431      }
1432  }
1433  
1434  /**
1435  * Check if extension is allowed to be posted.
1436  *
1437  * @param mixed $forum_id The forum id to check or false if private message
1438  * @param string $extension The extension to check, for example zip.
1439  * @param array &$extensions The extension array holding the information from the cache (will be obtained if empty)
1440  *
1441  * @return bool False if the extension is not allowed to be posted, else true.
1442  */
1443  function extension_allowed($forum_id, $extension, &$extensions)
1444  {
1445      if (empty($extensions))
1446      {
1447          global $cache;
1448          $extensions = $cache->obtain_attach_extensions($forum_id);
1449      }
1450  
1451      return (!isset($extensions['_allowed_'][$extension])) ? false : true;
1452  }
1453  
1454  /**
1455  * Truncates string while retaining special characters if going over the max length
1456  * The default max length is 60 at the moment
1457  * The maximum storage length is there to fit the string within the given length. The string may be further truncated due to html entities.
1458  * For example: string given is 'a "quote"' (length: 9), would be a stored as 'a &quot;quote&quot;' (length: 19)
1459  *
1460  * @param string $string The text to truncate to the given length. String is specialchared.
1461  * @param int $max_length Maximum length of string (multibyte character count as 1 char / Html entity count as 1 char)
1462  * @param int $max_store_length Maximum character length of string (multibyte character count as 1 char / Html entity count as entity chars).
1463  * @param bool $allow_reply Allow Re: in front of string
1464  *     NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_length) and is deprecated.
1465  * @param string $append String to be appended
1466  */
1467  function truncate_string($string, $max_length = 60, $max_store_length = 255, $allow_reply = false, $append = '')
1468  {
1469      $strip_reply = false;
1470      $stripped = false;
1471      if ($allow_reply && strpos($string, 'Re: ') === 0)
1472      {
1473          $strip_reply = true;
1474          $string = substr($string, 4);
1475      }
1476  
1477      $_chars = utf8_str_split(html_entity_decode($string, ENT_COMPAT));
1478      $chars = array_map('utf8_htmlspecialchars', $_chars);
1479  
1480      // Now check the length ;)
1481      if (count($chars) > $max_length)
1482      {
1483          // Cut off the last elements from the array
1484          $string = implode('', array_slice($chars, 0, $max_length - utf8_strlen($append)));
1485          $stripped = true;
1486      }
1487  
1488      // Due to specialchars, we may not be able to store the string...
1489      if (utf8_strlen($string) > $max_store_length)
1490      {
1491          // let's split again, we do not want half-baked strings where entities are split
1492          $_chars = utf8_str_split(html_entity_decode($string, ENT_COMPAT));
1493          $chars = array_map('utf8_htmlspecialchars', $_chars);
1494  
1495          do
1496          {
1497              array_pop($chars);
1498              $string = implode('', $chars);
1499          }
1500          while (!empty($chars) && utf8_strlen($string) > $max_store_length);
1501      }
1502  
1503      if ($strip_reply)
1504      {
1505          $string = 'Re: ' . $string;
1506      }
1507  
1508      if ($append != '' && $stripped)
1509      {
1510          $string = $string . $append;
1511      }
1512  
1513      return $string;
1514  }
1515  
1516  /**
1517  * Get username details for placing into templates.
1518  * This function caches all modes on first call, except for no_profile and anonymous user - determined by $user_id.
1519  *
1520  * @html Username spans and links
1521  *
1522  * @param string $mode Can be profile (for getting an url to the profile), username (for obtaining the username), colour (for obtaining the user colour), full (for obtaining a html string representing a coloured link to the users profile) or no_profile (the same as full but forcing no profile link)
1523  * @param int $user_id The users id
1524  * @param string $username The users name
1525  * @param string $username_colour The users colour
1526  * @param string $guest_username optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then.
1527  * @param string $custom_profile_url optional parameter to specify a profile url. The user id get appended to this url as &amp;u={user_id}
1528  *
1529  * @return string A string consisting of what is wanted based on $mode.
1530  */
1531  function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false)
1532  {
1533      static $_profile_cache;
1534      global $phpbb_dispatcher;
1535  
1536      // We cache some common variables we need within this function
1537      if (empty($_profile_cache))
1538      {
1539          global $phpbb_root_path, $phpEx;
1540  
1541          /** @html Username spans and links for usage in the template */
1542          $_profile_cache['base_url'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile&amp;u={USER_ID}');
1543          $_profile_cache['tpl_noprofile'] = '<span class="username">{USERNAME}</span>';
1544          $_profile_cache['tpl_noprofile_colour'] = '<span style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</span>';
1545          $_profile_cache['tpl_profile'] = '<a href="{PROFILE_URL}" class="username">{USERNAME}</a>';
1546          $_profile_cache['tpl_profile_colour'] = '<a href="{PROFILE_URL}" style="color: {USERNAME_COLOUR};" class="username-coloured">{USERNAME}</a>';
1547      }
1548  
1549      global $user, $auth;
1550  
1551      // This switch makes sure we only run code required for the mode
1552      switch ($mode)
1553      {
1554          case 'full':
1555          case 'no_profile':
1556          case 'colour':
1557  
1558              // Build correct username colour
1559              $username_colour = ($username_colour) ? '#' . $username_colour : '';
1560  
1561              // Return colour
1562              if ($mode == 'colour')
1563              {
1564                  $username_string = $username_colour;
1565                  break;
1566              }
1567  
1568          // no break;
1569  
1570          case 'username':
1571  
1572              // Build correct username
1573              if ($guest_username === false)
1574              {
1575                  $username = ($username) ? $username : $user->lang['GUEST'];
1576              }
1577              else
1578              {
1579                  $username = ($user_id && $user_id != ANONYMOUS) ? $username : ((!empty($guest_username)) ? $guest_username : $user->lang['GUEST']);
1580              }
1581  
1582              // Return username
1583              if ($mode == 'username')
1584              {
1585                  $username_string = $username;
1586                  break;
1587              }
1588  
1589          // no break;
1590  
1591          case 'profile':
1592  
1593              // Build correct profile url - only show if not anonymous and permission to view profile if registered user
1594              // For anonymous the link leads to a login page.
1595              if ($user_id && $user_id != ANONYMOUS && ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('u_viewprofile')))
1596              {
1597                  $profile_url = ($custom_profile_url !== false) ? $custom_profile_url . '&amp;u=' . (int) $user_id : str_replace(array('={USER_ID}', '=%7BUSER_ID%7D'), '=' . (int) $user_id, $_profile_cache['base_url']);
1598              }
1599              else
1600              {
1601                  $profile_url = '';
1602              }
1603  
1604              // Return profile
1605              if ($mode == 'profile')
1606              {
1607                  $username_string = $profile_url;
1608                  break;
1609              }
1610  
1611          // no break;
1612      }
1613  
1614      if (!isset($username_string))
1615      {
1616          if (($mode == 'full' && !$profile_url) || $mode == 'no_profile')
1617          {
1618              $username_string = str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']);
1619          }
1620          else
1621          {
1622              $username_string = str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_profile'] : $_profile_cache['tpl_profile_colour']);
1623          }
1624      }
1625  
1626      /**
1627      * Use this event to change the output of get_username_string()
1628      *
1629      * @event core.modify_username_string
1630      * @var string mode                profile|username|colour|full|no_profile
1631      * @var int user_id                String or array of additional url
1632      *                                parameters
1633      * @var string username            The user's username
1634      * @var string username_colour    The user's colour
1635      * @var string guest_username    Optional parameter to specify the
1636      *                                guest username.
1637      * @var string custom_profile_url Optional parameter to specify a
1638      *                                profile url.
1639      * @var string username_string    The string that has been generated
1640      * @var array _profile_cache        Array of original return templates
1641      * @since 3.1.0-a1
1642      */
1643      $vars = array(
1644          'mode',
1645          'user_id',
1646          'username',
1647          'username_colour',
1648          'guest_username',
1649          'custom_profile_url',
1650          'username_string',
1651          '_profile_cache',
1652      );
1653      extract($phpbb_dispatcher->trigger_event('core.modify_username_string', compact($vars)));
1654  
1655      return $username_string;
1656  }
1657  
1658  /**
1659   * Add an option to the quick-mod tools.
1660   *
1661   * @param string $url The recepting URL for the quickmod actions.
1662   * @param string $option The language key for the value of the option.
1663   * @param string $lang_string The language string to use.
1664   */
1665  function phpbb_add_quickmod_option($url, $option, $lang_string)
1666  {
1667      global $template, $user, $phpbb_path_helper;
1668  
1669      $lang_string = $user->lang($lang_string);
1670      $template->assign_block_vars('quickmod', array(
1671          'VALUE'        => $option,
1672          'TITLE'        => $lang_string,
1673          'LINK'        => $phpbb_path_helper->append_url_params($url, array('action' => $option)),
1674      ));
1675  }
1676  
1677  /**
1678  * Concatenate an array into a string list.
1679  *
1680  * @param array $items Array of items to concatenate
1681  * @param object $user The phpBB $user object.
1682  *
1683  * @return string String list. Examples: "A"; "A and B"; "A, B, and C"
1684  */
1685  function phpbb_generate_string_list($items, $user)
1686  {
1687      if (empty($items))
1688      {
1689          return '';
1690      }
1691  
1692      $count = count($items);
1693      $last_item = array_pop($items);
1694      $lang_key = 'STRING_LIST_MULTI';
1695  
1696      if ($count == 1)
1697      {
1698          return $last_item;
1699      }
1700      else if ($count == 2)
1701      {
1702          $lang_key = 'STRING_LIST_SIMPLE';
1703      }
1704      $list = implode($user->lang['COMMA_SEPARATOR'], $items);
1705  
1706      return $user->lang($lang_key, $list, $last_item);
1707  }
1708  
1709  class bitfield
1710  {
1711      var $data;
1712  
1713  	function __construct($bitfield = '')
1714      {
1715          $this->data = base64_decode($bitfield);
1716      }
1717  
1718      /**
1719      */
1720  	function get($n)
1721      {
1722          // Get the ($n / 8)th char
1723          $byte = $n >> 3;
1724  
1725          if (strlen($this->data) >= $byte + 1)
1726          {
1727              $c = $this->data[$byte];
1728  
1729              // Lookup the ($n % 8)th bit of the byte
1730              $bit = 7 - ($n & 7);
1731              return (bool) (ord($c) & (1 << $bit));
1732          }
1733          else
1734          {
1735              return false;
1736          }
1737      }
1738  
1739  	function set($n)
1740      {
1741          $byte = $n >> 3;
1742          $bit = 7 - ($n & 7);
1743  
1744          if (strlen($this->data) >= $byte + 1)
1745          {
1746              $this->data[$byte] = $this->data[$byte] | chr(1 << $bit);
1747          }
1748          else
1749          {
1750              $this->data .= str_repeat("\0", $byte - strlen($this->data));
1751              $this->data .= chr(1 << $bit);
1752          }
1753      }
1754  
1755  	function clear($n)
1756      {
1757          $byte = $n >> 3;
1758  
1759          if (strlen($this->data) >= $byte + 1)
1760          {
1761              $bit = 7 - ($n & 7);
1762              $this->data[$byte] = $this->data[$byte] &~ chr(1 << $bit);
1763          }
1764      }
1765  
1766  	function get_blob()
1767      {
1768          return $this->data;
1769      }
1770  
1771  	function get_base64()
1772      {
1773          return base64_encode($this->data);
1774      }
1775  
1776  	function get_bin()
1777      {
1778          $bin = '';
1779          $len = strlen($this->data);
1780  
1781          for ($i = 0; $i < $len; ++$i)
1782          {
1783              $bin .= str_pad(decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT);
1784          }
1785  
1786          return $bin;
1787      }
1788  
1789  	function get_all_set()
1790      {
1791          return array_keys(array_filter(str_split($this->get_bin())));
1792      }
1793  
1794  	function merge($bitfield)
1795      {
1796          $this->data = $this->data | $bitfield->get_blob();
1797      }
1798  }
1799  
1800  /**
1801   * Formats the quote according to the given BBCode status setting
1802   *
1803   * @param phpbb\language\language                $language Language class
1804   * @param parse_message                         $message_parser Message parser class
1805   * @param phpbb\textformatter\utils_interface    $text_formatter_utils Text formatter utilities
1806   * @param bool                                     $bbcode_status The status of the BBCode setting
1807   * @param array                                 $quote_attributes The attributes of the quoted post
1808   * @param string                                 $message_link Link of the original quoted post
1809   */
1810  function phpbb_format_quote($language, $message_parser, $text_formatter_utils, $bbcode_status, $quote_attributes, $message_link = '')
1811  {
1812      if ($bbcode_status)
1813      {
1814          $quote_text = $text_formatter_utils->generate_quote(
1815              censor_text($message_parser->message),
1816              $quote_attributes
1817          );
1818  
1819          $message_parser->message = $quote_text . "\n\n";
1820      }
1821      else
1822      {
1823          $offset = 0;
1824          $quote_string = "&gt; ";
1825          $message = censor_text(trim($message_parser->message));
1826          // see if we are nesting. It's easily tricked but should work for one level of nesting
1827          if (strpos($message, "&gt;") !== false)
1828          {
1829              $offset = 10;
1830          }
1831          $message = utf8_wordwrap($message, 75 + $offset, "\n");
1832  
1833          $message = $quote_string . $message;
1834          $message = str_replace("\n", "\n" . $quote_string, $message);
1835  
1836          $message_parser->message = $quote_attributes['author'] . " " . $language->lang('WROTE') . ":\n" . $message . "\n";
1837      }
1838  
1839      if ($message_link)
1840      {
1841          $message_parser->message = $message_link . $message_parser->message;
1842      }
1843  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1