Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


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

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

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

driver.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 28.17 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  namespace phpbb\db\driver;
0015   
0016  /**
0017  * Database Abstraction Layer
0018  */
0019  abstract class driver implements driver_interface
0020  {
0021      var $db_connect_id;
0022      var $query_result;
0023      var $return_on_error = false;
0024      var $transaction = false;
0025      var $sql_time = 0;
0026      var $num_queries = array();
0027      var $open_queries = array();
0028   
0029      var $curtime = 0;
0030      var $query_hold = '';
0031      var $html_hold = '';
0032      var $sql_report = '';
0033   
0034      var $persistency = false;
0035      var $user = '';
0036      var $server = '';
0037      var $dbname = '';
0038   
0039      // Set to true if error triggered
0040      var $sql_error_triggered = false;
0041   
0042      // Holding the last sql query on sql error
0043      var $sql_error_sql = '';
0044      // Holding the error information - only populated if sql_error_triggered is set
0045      var $sql_error_returned = array();
0046   
0047      // Holding transaction count
0048      var $transactions = 0;
0049   
0050      // Supports multi inserts?
0051      var $multi_insert = false;
0052   
0053      /**
0054      * Current sql layer
0055      */
0056      var $sql_layer = '';
0057   
0058      /**
0059      * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions
0060      */
0061      var $any_char;
0062      var $one_char;
0063   
0064      /**
0065      * Exact version of the DBAL, directly queried
0066      */
0067      var $sql_server_version = false;
0068   
0069      const LOGICAL_OP = 0;
0070      const STATEMENTS = 1;
0071      const LEFT_STMT = 0;
0072      const COMPARE_OP = 1;
0073      const RIGHT_STMT = 2;
0074      const SUBQUERY_OP = 3;
0075      const SUBQUERY_SELECT_TYPE = 4;
0076      const SUBQUERY_BUILD = 5;
0077   
0078      /**
0079      * @var bool
0080      */
0081      protected $debug_load_time = false;
0082   
0083      /**
0084      * @var bool
0085      */
0086      protected $debug_sql_explain = false;
0087   
0088      /**
0089      * Constructor
0090      */
0091      function __construct()
0092      {
0093          $this->num_queries = array(
0094              'cached'    => 0,
0095              'normal'    => 0,
0096              'total'        => 0,
0097          );
0098   
0099          // Fill default sql layer based on the class being called.
0100          // This can be changed by the specified layer itself later if needed.
0101          $this->sql_layer = substr(get_class($this), strlen('phpbb\db\driver\\'));
0102   
0103          // Do not change this please! This variable is used to easy the use of it - and is hardcoded.
0104          $this->any_char = chr(0) . '%';
0105          $this->one_char = chr(0) . '_';
0106      }
0107   
0108      /**
0109      * {@inheritdoc}
0110      */
0111      public function set_debug_load_time($value)
0112      {
0113          $this->debug_load_time = $value;
0114      }
0115   
0116      /**
0117      * {@inheritdoc}
0118      */
0119      public function set_debug_sql_explain($value)
0120      {
0121          $this->debug_sql_explain = $value;
0122      }
0123   
0124      /**
0125      * {@inheritdoc}
0126      */
0127      public function get_sql_layer()
0128      {
0129          return $this->sql_layer;
0130      }
0131   
0132      /**
0133      * {@inheritdoc}
0134      */
0135      public function get_db_name()
0136      {
0137          return $this->dbname;
0138      }
0139   
0140      /**
0141      * {@inheritdoc}
0142      */
0143      public function get_any_char()
0144      {
0145          return $this->any_char;
0146      }
0147   
0148      /**
0149      * {@inheritdoc}
0150      */
0151      public function get_one_char()
0152      {
0153          return $this->one_char;
0154      }
0155   
0156      /**
0157      * {@inheritdoc}
0158      */
0159      public function get_db_connect_id()
0160      {
0161          return $this->db_connect_id;
0162      }
0163   
0164      /**
0165      * {@inheritdoc}
0166      */
0167      public function get_sql_error_triggered()
0168      {
0169          return $this->sql_error_triggered;
0170      }
0171   
0172      /**
0173      * {@inheritdoc}
0174      */
0175      public function get_sql_error_sql()
0176      {
0177          return $this->sql_error_sql;
0178      }
0179   
0180      /**
0181      * {@inheritdoc}
0182      */
0183      public function get_transaction()
0184      {
0185          return $this->transaction;
0186      }
0187   
0188      /**
0189      * {@inheritdoc}
0190      */
0191      public function get_sql_time()
0192      {
0193          return $this->sql_time;
0194      }
0195   
0196      /**
0197      * {@inheritdoc}
0198      */
0199      public function get_sql_error_returned()
0200      {
0201          return $this->sql_error_returned;
0202      }
0203   
0204      /**
0205      * {@inheritdoc}
0206      */
0207      public function get_multi_insert()
0208      {
0209          return $this->multi_insert;
0210      }
0211   
0212      /**
0213      * {@inheritdoc}
0214      */
0215      public function set_multi_insert($multi_insert)
0216      {
0217          $this->multi_insert = $multi_insert;
0218      }
0219   
0220      /**
0221      * {@inheritDoc}
0222      */
0223      function sql_return_on_error($fail = false)
0224      {
0225          $this->sql_error_triggered = false;
0226          $this->sql_error_sql = '';
0227   
0228          $this->return_on_error = $fail;
0229      }
0230   
0231      /**
0232      * {@inheritDoc}
0233      */
0234      function sql_num_queries($cached = false)
0235      {
0236          return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal'];
0237      }
0238   
0239      /**
0240      * {@inheritDoc}
0241      */
0242      function sql_add_num_queries($cached = false)
0243      {
0244          $this->num_queries['cached'] += ($cached !== false) ? 1 : 0;
0245          $this->num_queries['normal'] += ($cached !== false) ? 0 : 1;
0246          $this->num_queries['total'] += 1;
0247      }
0248   
0249      /**
0250      * {@inheritDoc}
0251      */
0252      function sql_close()
0253      {
0254          if (!$this->db_connect_id)
0255          {
0256              return false;
0257          }
0258   
0259          if ($this->transaction)
0260          {
0261              do
0262              {
0263                  $this->sql_transaction('commit');
0264              }
0265              while ($this->transaction);
0266          }
0267   
0268          foreach ($this->open_queries as $query_id)
0269          {
0270              $this->sql_freeresult($query_id);
0271          }
0272   
0273          // Connection closed correctly. Set db_connect_id to false to prevent errors
0274          if ($result = $this->_sql_close())
0275          {
0276              $this->db_connect_id = false;
0277          }
0278   
0279          return $result;
0280      }
0281   
0282      /**
0283      * {@inheritDoc}
0284      */
0285      function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
0286      {
0287          if (empty($query))
0288          {
0289              return false;
0290          }
0291   
0292          // Never use a negative total or offset
0293          $total = ($total < 0) ? 0 : $total;
0294          $offset = ($offset < 0) ? 0 : $offset;
0295   
0296          return $this->_sql_query_limit($query, $total, $offset, $cache_ttl);
0297      }
0298   
0299      /**
0300      * {@inheritDoc}
0301      */
0302      function sql_fetchrowset($query_id = false)
0303      {
0304          if ($query_id === false)
0305          {
0306              $query_id = $this->query_result;
0307          }
0308   
0309          if ($query_id)
0310          {
0311              $result = array();
0312              while ($row = $this->sql_fetchrow($query_id))
0313              {
0314                  $result[] = $row;
0315              }
0316   
0317              return $result;
0318          }
0319   
0320          return false;
0321      }
0322   
0323      /**
0324      * {@inheritDoc}
0325      */
0326      function sql_rowseek($rownum, &$query_id)
0327      {
0328          global $cache;
0329   
0330          if ($query_id === false)
0331          {
0332              $query_id = $this->query_result;
0333          }
0334   
0335          if ($cache && $cache->sql_exists($query_id))
0336          {
0337              return $cache->sql_rowseek($rownum, $query_id);
0338          }
0339   
0340          if (!$query_id)
0341          {
0342              return false;
0343          }
0344   
0345          $this->sql_freeresult($query_id);
0346          $query_id = $this->sql_query($this->last_query_text);
0347   
0348          if (!$query_id)
0349          {
0350              return false;
0351          }
0352   
0353          // We do not fetch the row for rownum == 0 because then the next resultset would be the second row
0354          for ($i = 0; $i < $rownum; $i++)
0355          {
0356              if (!$this->sql_fetchrow($query_id))
0357              {
0358                  return false;
0359              }
0360          }
0361   
0362          return true;
0363      }
0364   
0365      /**
0366      * {@inheritDoc}
0367      */
0368      function sql_fetchfield($field, $rownum = false, $query_id = false)
0369      {
0370          global $cache;
0371   
0372          if ($query_id === false)
0373          {
0374              $query_id = $this->query_result;
0375          }
0376   
0377          if ($query_id)
0378          {
0379              if ($rownum !== false)
0380              {
0381                  $this->sql_rowseek($rownum, $query_id);
0382              }
0383   
0384              if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
0385              {
0386                  return $cache->sql_fetchfield($query_id, $field);
0387              }
0388   
0389              $row = $this->sql_fetchrow($query_id);
0390              return (isset($row[$field])) ? $row[$field] : false;
0391          }
0392   
0393          return false;
0394      }
0395   
0396      /**
0397      * {@inheritDoc}
0398      */
0399      function sql_like_expression($expression)
0400      {
0401          $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression);
0402          $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);
0403   
0404          return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\'');
0405      }
0406   
0407      /**
0408      * {@inheritDoc}
0409      */
0410      function sql_not_like_expression($expression)
0411      {
0412          $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression);
0413          $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);
0414   
0415          return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\'');
0416      }
0417   
0418      /**
0419      * {@inheritDoc}
0420      */
0421      public function sql_case($condition, $action_true, $action_false = false)
0422      {
0423          $sql_case = 'CASE WHEN ' . $condition;
0424          $sql_case .= ' THEN ' . $action_true;
0425          $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : '';
0426          $sql_case .= ' END';
0427          return $sql_case;
0428      }
0429   
0430      /**
0431      * {@inheritDoc}
0432      */
0433      public function sql_concatenate($expr1, $expr2)
0434      {
0435          return $expr1 . ' || ' . $expr2;
0436      }
0437   
0438      /**
0439      * {@inheritDoc}
0440      */
0441      function sql_buffer_nested_transactions()
0442      {
0443          return false;
0444      }
0445   
0446      /**
0447      * {@inheritDoc}
0448      */
0449      function sql_transaction($status = 'begin')
0450      {
0451          switch ($status)
0452          {
0453              case 'begin':
0454                  // If we are within a transaction we will not open another one, but enclose the current one to not loose data (preventing auto commit)
0455                  if ($this->transaction)
0456                  {
0457                      $this->transactions++;
0458                      return true;
0459                  }
0460   
0461                  $result = $this->_sql_transaction('begin');
0462   
0463                  if (!$result)
0464                  {
0465                      $this->sql_error();
0466                  }
0467   
0468                  $this->transaction = true;
0469              break;
0470   
0471              case 'commit':
0472                  // If there was a previously opened transaction we do not commit yet...
0473                  // but count back the number of inner transactions
0474                  if ($this->transaction && $this->transactions)
0475                  {
0476                      $this->transactions--;
0477                      return true;
0478                  }
0479   
0480                  // Check if there is a transaction (no transaction can happen if
0481                  // there was an error, with a combined rollback and error returning enabled)
0482                  // This implies we have transaction always set for autocommit db's
0483                  if (!$this->transaction)
0484                  {
0485                      return false;
0486                  }
0487   
0488                  $result = $this->_sql_transaction('commit');
0489   
0490                  if (!$result)
0491                  {
0492                      $this->sql_error();
0493                  }
0494   
0495                  $this->transaction = false;
0496                  $this->transactions = 0;
0497              break;
0498   
0499              case 'rollback':
0500                  $result = $this->_sql_transaction('rollback');
0501                  $this->transaction = false;
0502                  $this->transactions = 0;
0503              break;
0504   
0505              default:
0506                  $result = $this->_sql_transaction($status);
0507              break;
0508          }
0509   
0510          return $result;
0511      }
0512   
0513      /**
0514      * {@inheritDoc}
0515      */
0516      function sql_build_array($query, $assoc_ary = false)
0517      {
0518          if (!is_array($assoc_ary))
0519          {
0520              return false;
0521          }
0522   
0523          $fields = $values = array();
0524   
0525          if ($query == 'INSERT' || $query == 'INSERT_SELECT')
0526          {
0527              foreach ($assoc_ary as $key => $var)
0528              {
0529                  $fields[] = $key;
0530   
0531                  if (is_array($var) && is_string($var[0]))
0532                  {
0533                      // This is used for INSERT_SELECT(s)
0534                      $values[] = $var[0];
0535                  }
0536                  else
0537                  {
0538                      $values[] = $this->_sql_validate_value($var);
0539                  }
0540              }
0541   
0542              $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' ';
0543          }
0544          else if ($query == 'MULTI_INSERT')
0545          {
0546              trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR);
0547          }
0548          else if ($query == 'UPDATE' || $query == 'SELECT' || $query == 'DELETE')
0549          {
0550              $values = array();
0551              foreach ($assoc_ary as $key => $var)
0552              {
0553                  $values[] = "$key = " . $this->_sql_validate_value($var);
0554              }
0555              $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values);
0556          }
0557   
0558          return $query;
0559      }
0560   
0561      /**
0562      * {@inheritDoc}
0563      */
0564      function sql_in_set($field, $array, $negate = false, $allow_empty_set = false)
0565      {
0566          $array = (array) $array;
0567   
0568          if (!count($array))
0569          {
0570              if (!$allow_empty_set)
0571              {
0572                  // Print the backtrace to help identifying the location of the problematic code
0573                  $this->sql_error('No values specified for SQL IN comparison');
0574              }
0575              else
0576              {
0577                  // NOT IN () actually means everything so use a tautology
0578                  if ($negate)
0579                  {
0580                      return '1=1';
0581                  }
0582                  // IN () actually means nothing so use a contradiction
0583                  else
0584                  {
0585                      return '1=0';
0586                  }
0587              }
0588          }
0589   
0590          if (count($array) == 1)
0591          {
0592              @reset($array);
0593              $var = current($array);
0594   
0595              return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var);
0596          }
0597          else
0598          {
0599              return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')';
0600          }
0601      }
0602   
0603      /**
0604      * {@inheritDoc}
0605      */
0606      function sql_bit_and($column_name, $bit, $compare = '')
0607      {
0608          if (method_exists($this, '_sql_bit_and'))
0609          {
0610              return $this->_sql_bit_and($column_name, $bit, $compare);
0611          }
0612   
0613          return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
0614      }
0615   
0616      /**
0617      * {@inheritDoc}
0618      */
0619      function sql_bit_or($column_name, $bit, $compare = '')
0620      {
0621          if (method_exists($this, '_sql_bit_or'))
0622          {
0623              return $this->_sql_bit_or($column_name, $bit, $compare);
0624          }
0625   
0626          return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
0627      }
0628   
0629      /**
0630      * {@inheritDoc}
0631      */
0632      function cast_expr_to_bigint($expression)
0633      {
0634          return $expression;
0635      }
0636   
0637      /**
0638       * {@inheritDoc}
0639       */
0640      public function sql_nextid()
0641      {
0642          return $this->sql_last_inserted_id();
0643      }
0644   
0645      /**
0646      * {@inheritDoc}
0647      */
0648      function cast_expr_to_string($expression)
0649      {
0650          return $expression;
0651      }
0652   
0653      /**
0654      * {@inheritDoc}
0655      */
0656      function sql_lower_text($column_name)
0657      {
0658          return "LOWER($column_name)";
0659      }
0660   
0661      /**
0662      * {@inheritDoc}
0663      */
0664      function sql_multi_insert($table, $sql_ary)
0665      {
0666          if (!count($sql_ary))
0667          {
0668              return false;
0669          }
0670   
0671          if ($this->multi_insert)
0672          {
0673              $ary = array();
0674              foreach ($sql_ary as $id => $_sql_ary)
0675              {
0676                  // If by accident the sql array is only one-dimensional we build a normal insert statement
0677                  if (!is_array($_sql_ary))
0678                  {
0679                      return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary));
0680                  }
0681   
0682                  $values = array();
0683                  foreach ($_sql_ary as $key => $var)
0684                  {
0685                      $values[] = $this->_sql_validate_value($var);
0686                  }
0687                  $ary[] = '(' . implode(', ', $values) . ')';
0688              }
0689   
0690              return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary));
0691          }
0692          else
0693          {
0694              foreach ($sql_ary as $ary)
0695              {
0696                  if (!is_array($ary))
0697                  {
0698                      return false;
0699                  }
0700   
0701                  $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary));
0702   
0703                  if (!$result)
0704                  {
0705                      return false;
0706                  }
0707              }
0708          }
0709   
0710          return true;
0711      }
0712   
0713      /**
0714      * Function for validating values
0715      * @access private
0716      */
0717      function _sql_validate_value($var)
0718      {
0719          if (is_null($var))
0720          {
0721              return 'NULL';
0722          }
0723          else if (is_string($var))
0724          {
0725              return "'" . $this->sql_escape($var) . "'";
0726          }
0727          else
0728          {
0729              return (is_bool($var)) ? intval($var) : $var;
0730          }
0731      }
0732   
0733      /**
0734      * {@inheritDoc}
0735      */
0736      function sql_build_query($query, $array)
0737      {
0738          $sql = '';
0739          switch ($query)
0740          {
0741              case 'SELECT':
0742              case 'SELECT_DISTINCT';
0743   
0744                  $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM ';
0745   
0746                  // Build table array. We also build an alias array for later checks.
0747                  $table_array = $aliases = array();
0748                  $used_multi_alias = false;
0749   
0750                  foreach ($array['FROM'] as $table_name => $alias)
0751                  {
0752                      if (is_array($alias))
0753                      {
0754                          $used_multi_alias = true;
0755   
0756                          foreach ($alias as $multi_alias)
0757                          {
0758                              $table_array[] = $table_name . ' ' . $multi_alias;
0759                              $aliases[] = $multi_alias;
0760                          }
0761                      }
0762                      else
0763                      {
0764                          $table_array[] = $table_name . ' ' . $alias;
0765                          $aliases[] = $alias;
0766                      }
0767                  }
0768   
0769                  // We run the following code to determine if we need to re-order the table array. ;)
0770                  // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison.
0771                  // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is.
0772                  if (!empty($array['LEFT_JOIN']) && count($array['FROM']) > 1 && $used_multi_alias !== false)
0773                  {
0774                      // Take first LEFT JOIN
0775                      $join = current($array['LEFT_JOIN']);
0776   
0777                      // Determine the table used there (even if there are more than one used, we only want to have one
0778                      preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches);
0779   
0780                      // If there is a first join match, we need to make sure the table order is correct
0781                      if (!empty($matches[1]))
0782                      {
0783                          $first_join_match = trim($matches[1]);
0784                          $table_array = $last = array();
0785   
0786                          foreach ($array['FROM'] as $table_name => $alias)
0787                          {
0788                              if (is_array($alias))
0789                              {
0790                                  foreach ($alias as $multi_alias)
0791                                  {
0792                                      ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias;
0793                                  }
0794                              }
0795                              else
0796                              {
0797                                  ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias;
0798                              }
0799                          }
0800   
0801                          $table_array = array_merge($table_array, $last);
0802                      }
0803                  }
0804   
0805                  $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array));
0806   
0807                  if (!empty($array['LEFT_JOIN']))
0808                  {
0809                      foreach ($array['LEFT_JOIN'] as $join)
0810                      {
0811                          $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')';
0812                      }
0813                  }
0814   
0815                  if (!empty($array['WHERE']))
0816                  {
0817                      $sql .= ' WHERE ';
0818   
0819                      if (is_array($array['WHERE']))
0820                      {
0821                          $sql_where = $this->_process_boolean_tree_first($array['WHERE']);
0822                      }
0823                      else
0824                      {
0825                          $sql_where = $array['WHERE'];
0826                      }
0827   
0828                      $sql .= $this->_sql_custom_build('WHERE', $sql_where);
0829                  }
0830   
0831                  if (!empty($array['GROUP_BY']))
0832                  {
0833                      $sql .= ' GROUP BY ' . $array['GROUP_BY'];
0834                  }
0835   
0836                  if (!empty($array['ORDER_BY']))
0837                  {
0838                      $sql .= ' ORDER BY ' . $array['ORDER_BY'];
0839                  }
0840   
0841              break;
0842          }
0843   
0844          return $sql;
0845      }
0846   
0847   
0848      protected function _process_boolean_tree_first($operations_ary)
0849      {
0850          // In cases where an array exists but there is no head condition,
0851          // it should be because there's only 1 WHERE clause. This seems the best way to deal with it.
0852          if ($operations_ary[self::LOGICAL_OP] !== 'AND' &&
0853              $operations_ary[self::LOGICAL_OP] !== 'OR')
0854          {
0855              $operations_ary = array('AND', array($operations_ary));
0856          }
0857          return $this->_process_boolean_tree($operations_ary) . "\n";
0858      }
0859   
0860      protected function _process_boolean_tree($operations_ary)
0861      {
0862          $operation = $operations_ary[self::LOGICAL_OP];
0863   
0864          foreach ($operations_ary[self::STATEMENTS] as &$condition)
0865          {
0866              switch ($condition[self::LOGICAL_OP])
0867              {
0868                  case 'AND':
0869                  case 'OR':
0870   
0871                      $condition = ' ( ' . $this->_process_boolean_tree($condition) . ') ';
0872   
0873                  break;
0874                  case 'NOT':
0875   
0876                      $condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') ';
0877   
0878                  break;
0879   
0880                  default:
0881   
0882                      switch (count($condition))
0883                      {
0884                          case 3:
0885   
0886                              // Typical 3 element clause with {left hand} {operator} {right hand}
0887                              switch ($condition[self::COMPARE_OP])
0888                              {
0889                                  case 'IN':
0890                                  case 'NOT_IN':
0891   
0892                                      // As this is used with an IN, assume it is a set of elements for sql_in_set()
0893                                      $condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true);
0894   
0895                                  break;
0896   
0897                                  case 'LIKE':
0898   
0899                                      $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' ';
0900   
0901                                  break;
0902   
0903                                  case 'NOT_LIKE':
0904   
0905                                      $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' ';
0906   
0907                                  break;
0908   
0909                                  case 'IS_NOT':
0910   
0911                                      $condition[self::COMPARE_OP] = 'IS NOT';
0912   
0913                                  // no break
0914                                  case 'IS':
0915   
0916                                      // If the value is NULL, the string of it is the empty string ('') which is not the intended result.
0917                                      // this should solve that
0918                                      if ($condition[self::RIGHT_STMT] === null)
0919                                      {
0920                                          $condition[self::RIGHT_STMT] = 'NULL';
0921                                      }
0922   
0923                                      $condition = implode(' ', $condition);
0924   
0925                                  break;
0926   
0927                                  default:
0928   
0929                                      $condition = implode(' ', $condition);
0930   
0931                                  break;
0932                              }
0933   
0934                          break;
0935   
0936                          case 5:
0937   
0938                              // Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query}
0939   
0940                              $result = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( ';
0941                              $result .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]);
0942                              $result .= ' )';
0943                              $condition = $result;
0944   
0945                          break;
0946   
0947                          default:
0948                              // This is an unpredicted clause setup. Just join all elements.
0949                              $condition = implode(' ', $condition);
0950   
0951                          break;
0952                      }
0953   
0954                  break;
0955              }
0956   
0957          }
0958   
0959          if ($operation === 'NOT')
0960          {
0961              $operations_ary =  implode("", $operations_ary[self::STATEMENTS]);
0962          }
0963          else
0964          {
0965              $operations_ary = implode(" \n    $operation ", $operations_ary[self::STATEMENTS]);
0966          }
0967   
0968          return $operations_ary;
0969      }
0970   
0971   
0972      /**
0973      * {@inheritDoc}
0974      */
0975      function sql_error($sql = '')
0976      {
0977          global $auth, $user, $config;
0978   
0979          // Set var to retrieve errored status
0980          $this->sql_error_triggered = true;
0981          $this->sql_error_sql = $sql;
0982   
0983          $this->sql_error_returned = $this->_sql_error();
0984   
0985          if (!$this->return_on_error)
0986          {
0987              $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]<br /><br />' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']';
0988   
0989              // Show complete SQL error and path to administrators only
0990              // Additionally show complete error on installation or if extended debug mode is enabled
0991              // The DEBUG constant is for development only!
0992              if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || $this->debug_sql_explain)
0993              {
0994                  $message .= ($sql) ? '<br /><br />SQL<br /><br />' . htmlspecialchars($sql, ENT_COMPAT) : '';
0995              }
0996              else
0997              {
0998                  // If error occurs in initiating the session we need to use a pre-defined language string
0999                  // This could happen if the connection could not be established for example (then we are not able to grab the default language)
1000                  if (!isset($user->lang['SQL_ERROR_OCCURRED']))
1001                  {
1002                      $message .= '<br /><br />An sql error occurred while fetching this page. Please contact an administrator if this problem persists.';
1003                  }
1004                  else
1005                  {
1006                      if (!empty($config['board_contact']))
1007                      {
1008                          $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '<a href="mailto:' . htmlspecialchars($config['board_contact'], ENT_COMPAT) . '">', '</a>');
1009                      }
1010                      else
1011                      {
1012                          $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', '');
1013                      }
1014                  }
1015              }
1016   
1017              if ($this->transaction)
1018              {
1019                  $this->sql_transaction('rollback');
1020              }
1021   
1022              if (strlen($message) > 1024)
1023              {
1024                  // We need to define $msg_long_text here to circumvent text stripping.
1025                  global $msg_long_text;
1026                  $msg_long_text = $message;
1027   
1028                  trigger_error(false, E_USER_ERROR);
1029              }
1030   
1031              trigger_error($message, E_USER_ERROR);
1032          }
1033   
1034          if ($this->transaction)
1035          {
1036              $this->sql_transaction('rollback');
1037          }
1038   
1039          return $this->sql_error_returned;
1040      }
1041   
1042      /**
1043      * {@inheritDoc}
1044      */
1045      function sql_report($mode, $query = '')
1046      {
1047          global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper;
1048   
1049          if (!$query && $this->query_hold != '')
1050          {
1051              $query = $this->query_hold;
1052          }
1053   
1054          switch ($mode)
1055          {
1056              case 'display':
1057                  if (!empty($cache))
1058                  {
1059                      $cache->unload();
1060                  }
1061                  $this->sql_close();
1062   
1063                  $mtime = explode(' ', microtime());
1064                  $totaltime = $mtime[0] + $mtime[1] - $starttime;
1065   
1066                  echo '<!DOCTYPE html>
1067                      <html dir="ltr">
1068                      <head>
1069                          <meta charset="utf-8">
1070                          <meta http-equiv="X-UA-Compatible" content="IE=edge">
1071                          <title>SQL Report</title>
1072                          <link href="' . htmlspecialchars($phpbb_path_helper->update_web_root_path($phpbb_root_path) . $phpbb_path_helper->get_adm_relative_path(), ENT_COMPAT) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" />
1073                      </head>
1074                      <body id="errorpage">
1075                      <div id="wrap">
1076                          <div id="page-header">
1077                              <a href="' . build_url('explain') . '">Return to previous page</a>
1078                          </div>
1079                          <div id="page-body">
1080                              <div id="acp">
1081                              <div class="panel">
1082                                  <span class="corners-top"><span></span></span>
1083                                  <div id="content">
1084                                      <h1>SQL Report</h1>
1085                                      <br />
1086                                      <p><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></p>
1087   
1088                                      <p>Time spent on ' . $this->sql_layer . ' queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></p>
1089   
1090                                      <br /><br />
1091                                      ' . $this->sql_report . '
1092                                  </div>
1093                                  <span class="corners-bottom"><span></span></span>
1094                              </div>
1095                              </div>
1096                          </div>
1097                          <div id="page-footer">
1098                              Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited
1099                          </div>
1100                      </div>
1101                      </body>
1102                      </html>';
1103   
1104                  exit_handler();
1105   
1106              break;
1107   
1108              case 'stop':
1109                  $endtime = explode(' ', microtime());
1110                  $endtime = $endtime[0] + $endtime[1];
1111   
1112                  $this->sql_report .= '
1113   
1114                      <table cellspacing="1">
1115                      <thead>
1116                      <tr>
1117                          <th>Query #' . $this->num_queries['total'] . '</th>
1118                      </tr>
1119                      </thead>
1120                      <tbody>
1121                      <tr>
1122                          <td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query), ENT_COMPAT)) . '</textarea></td>
1123                      </tr>
1124                      </tbody>
1125                      </table>
1126   
1127                      ' . $this->html_hold . '
1128   
1129                      <p style="text-align: center;">
1130                  ';
1131   
1132                  if ($this->query_result)
1133                  {
1134                      if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query))
1135                      {
1136                          $this->sql_report .= 'Affected rows: <b>' . $this->sql_affectedrows() . '</b> | ';
1137                      }
1138                      $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $this->curtime) . 's</b>';
1139                  }
1140                  else
1141                  {
1142                      $error = $this->sql_error();
1143                      $this->sql_report .= '<b style="color: red">FAILED</b> - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message'], ENT_COMPAT);
1144                  }
1145   
1146                  $this->sql_report .= '</p><br /><br />';
1147   
1148                  $this->sql_time += $endtime - $this->curtime;
1149              break;
1150   
1151              case 'start':
1152                  $this->query_hold = $query;
1153                  $this->html_hold = '';
1154   
1155                  $this->_sql_report($mode, $query);
1156   
1157                  $this->curtime = explode(' ', microtime());
1158                  $this->curtime = $this->curtime[0] + $this->curtime[1];
1159   
1160              break;
1161   
1162              case 'add_select_row':
1163   
1164                  $html_table = func_get_arg(2);
1165                  $row = func_get_arg(3);
1166   
1167                  if (!$html_table && count($row))
1168                  {
1169                      $html_table = true;
1170                      $this->html_hold .= '<table cellspacing="1"><tr>';
1171   
1172                      foreach (array_keys($row) as $val)
1173                      {
1174                          $this->html_hold .= '<th>' . (($val) ? ucwords(str_replace('_', ' ', $val)) : '&nbsp;') . '</th>';
1175                      }
1176                      $this->html_hold .= '</tr>';
1177                  }
1178                  $this->html_hold .= '<tr>';
1179   
1180                  $class = 'row1';
1181                  foreach (array_values($row) as $val)
1182                  {
1183                      $class = ($class == 'row1') ? 'row2' : 'row1';
1184                      $this->html_hold .= '<td class="' . $class . '">' . (($val) ? $val : '&nbsp;') . '</td>';
1185                  }
1186                  $this->html_hold .= '</tr>';
1187   
1188                  return $html_table;
1189   
1190              break;
1191   
1192              case 'fromcache':
1193   
1194                  $this->_sql_report($mode, $query);
1195   
1196              break;
1197   
1198              case 'record_fromcache':
1199   
1200                  $endtime = func_get_arg(2);
1201                  $splittime = func_get_arg(3);
1202   
1203                  $time_cache = $endtime - $this->curtime;
1204                  $time_db = $splittime - $endtime;
1205                  $color = ($time_db > $time_cache) ? 'green' : 'red';
1206   
1207                  $this->sql_report .= '<table cellspacing="1"><thead><tr><th>Query results obtained from the cache</th></tr></thead><tbody><tr>';
1208                  $this->sql_report .= '<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query), ENT_COMPAT)) . '</textarea></td></tr></tbody></table>';
1209                  $this->sql_report .= '<p style="text-align: center;">';
1210                  $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p><br /><br />';
1211   
1212                  // Pad the start time to not interfere with page timing
1213                  $starttime += $time_db;
1214   
1215              break;
1216   
1217              default:
1218   
1219                  $this->_sql_report($mode, $query);
1220   
1221              break;
1222          }
1223   
1224          return true;
1225      }
1226   
1227      /**
1228      * {@inheritDoc}
1229      */
1230      function get_estimated_row_count($table_name)
1231      {
1232          return $this->get_row_count($table_name);
1233      }
1234   
1235      /**
1236      * {@inheritDoc}
1237      */
1238      function get_row_count($table_name)
1239      {
1240          $sql = 'SELECT COUNT(*) AS rows_total
1241              FROM ' . $this->sql_escape($table_name);
1242          $result = $this->sql_query($sql);
1243          $rows_total = $this->sql_fetchfield('rows_total');
1244          $this->sql_freeresult($result);
1245   
1246          return $rows_total;
1247      }
1248   
1249      /**
1250       * {@inheritDoc}
1251       */
1252      public function clean_query_id($query_id)
1253      {
1254          // Some DBMS functions accept/return objects and/or resources instead if identifiers
1255          // Attempting to use objects/resources as array keys will throw error, hence correctly handle all cases
1256          if (is_resource($query_id))
1257          {
1258              return function_exists('get_resource_id') ? get_resource_id($query_id) : (int) $query_id;
1259          }
1260          else
1261          {
1262              return is_object($query_id) ? spl_object_id($query_id) : $query_id;
1263          }
1264      }
1265  }
1266