Subversion Repositories oidplus

Rev

Rev 224 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. if (!defined('IN_OIDPLUS')) die();
  21.  
  22. define('OIDPLUS_MYSQL_QUERYLOG', false);
  23. define('MYSQLND_AVAILABLE', function_exists('mysqli_fetch_all'));
  24.  
  25. if (OIDPLUS_MYSQL_QUERYLOG) {
  26.         function CallingFunctionName() {
  27.                 $ex = new Exception();
  28.                 $trace = $ex->getTrace();
  29.                 if (!isset($trace[2])) return '(main)';
  30.                 $final_call = $trace[2];
  31.                 return $final_call['file'].':'.$final_call['line'].'/'.$final_call['function'].'()';
  32.         }
  33. }
  34.  
  35. class OIDplusDataBasePluginMySQLi extends OIDplusDataBasePlugin {
  36.         private $mysqli;
  37.         private $last_query;
  38.         private $prepare_cache = array();
  39.  
  40.         public static function getPluginInformation() {
  41.                 $out = array();
  42.                 $out['name'] = 'MySQLi';
  43.                 $out['author'] = 'ViaThinkSoft';
  44.                 $out['version'] = null;
  45.                 $out['descriptionHTML'] = null;
  46.                 return $out;
  47.         }
  48.  
  49.         public static function name() {
  50.                 return "MySQL";
  51.         }
  52.  
  53.         public function query($sql, $prepared_args=null) {
  54.                 $this->last_query = $sql;
  55.                 if (OIDPLUS_MYSQL_QUERYLOG) file_put_contents("query.log", "$sql <== ".CallingFunctionName()."\n", FILE_APPEND);
  56.                 if (is_null($prepared_args)) {
  57.                         return $this->mysqli->query($sql, MYSQLI_STORE_RESULT);
  58.                 } else {
  59.                         if (!is_array($prepared_args)) {
  60.                                 throw new Exception("'prepared_args' must be either NULL or an ARRAY.");
  61.                         }
  62.                         if (isset($this->prepare_cache[$sql])) {
  63.                                 $ps = $this->prepare_cache[$sql];
  64.                         } else {
  65.                                 $ps = $this->mysqli->prepare($sql);
  66.                                 if (!$ps) {
  67.                                         throw new Exception("Cannot prepare statement '$sql'");
  68.                                 }
  69.                                 $this->prepare_cache[$sql] = $ps;
  70.                         }
  71.  
  72.                         bind_placeholder_vars($ps,$prepared_args);
  73.                         if (!$ps->execute()) return false;
  74.  
  75.                         $res = MYSQLND_AVAILABLE ? $ps->get_result() : iimysqli_stmt_get_result($ps);
  76.  
  77.                         if ($res === false) return true; // A non-SELECT statement does not give a result-set, but it is still successful
  78.                         return $res;
  79.                 }
  80.         }
  81.         public function num_rows($res) {
  82.                 if (!is_object($res)) {
  83.                         throw new Exception("num_rows called on non object. Last query: ".$this->last_query);
  84.                 } else {
  85.                         return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->num_rows : $res->num_rows();
  86.                 }
  87.         }
  88.         public function fetch_array($res) {
  89.                 if (!is_object($res)) {
  90.                         throw new Exception("fetch_array called on non object. Last query: ".$this->last_query);
  91.                 } else {
  92.                         return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->fetch_array(MYSQLI_BOTH) : $res->fetch_array();
  93.                 }
  94.         }
  95.         public function fetch_object($res) {
  96.                 if (!is_object($res)) {
  97.                         throw new Exception("fetch_object called on non object. Last query: ".$this->last_query);
  98.                 } else {
  99.                         return (get_class($res)=='mysqli_result') || MYSQLND_AVAILABLE ? $res->fetch_object("stdClass") : $res->fetch_object();
  100.                 }
  101.         }
  102.         public function insert_id() {
  103.                 return $this->mysqli->insert_id;
  104.         }
  105.         public function error() {
  106.                 return !empty($this->mysqli->connect_error) ? $this->mysqli->connect_error : $this->mysqli->error;
  107.         }
  108.  
  109.         public function connect() {
  110.                 if (OIDPLUS_MYSQL_QUERYLOG) file_put_contents("query.log", '');
  111.  
  112.                 $html = OIDPLUS_HTML_OUTPUT;
  113.  
  114.                 // Try connecting to the database
  115.                 list($hostname,$port) = explode(':', OIDPLUS_MYSQL_HOST.':'.ini_get("mysqli.default_port"));
  116.                 $this->mysqli = @new mysqli($hostname, OIDPLUS_MYSQL_USERNAME, base64_decode(OIDPLUS_MYSQL_PASSWORD), OIDPLUS_MYSQL_DATABASE, $port);
  117.                 if (!empty($this->mysqli->connect_error) || ($this->mysqli->connect_errno != 0)) {
  118.                         if ($html) {
  119.                                 echo "<h1>Error</h1><p>Database connection failed! (".$this->error().")</p>";
  120.                                 if (is_dir(__DIR__.'/../../../setup')) {
  121.                                         echo '<p>If you believe that the login credentials are wrong, please run <a href="setup/">setup</a> again.</p>';
  122.                                 }
  123.                         } else {
  124.                                 echo "Error: Database connection failed! (".$this->error().")";
  125.                                 if (is_dir(__DIR__.'/../../../setup')) {
  126.                                         echo ' If you believe that the login credentials are wrong, please run setup again.';
  127.                                 }
  128.                         }
  129.                         die();
  130.                 }
  131.  
  132.                 $this->query("SET NAMES 'utf8'");
  133.                 $this->afterConnect($html);
  134.                 $this->connected = true;
  135.         }
  136.  
  137.         private $intransaction = false;
  138.  
  139.         public function transaction_begin() {
  140.                 if ($this->intransaction) throw new Exception("Nested transactions are not supported by this database plugin.");
  141.                 $this->mysqli->autocommit(true);
  142.                 $this->intransaction = true;
  143.         }
  144.  
  145.         public function transaction_commit() {
  146.                 $this->mysqli->commit();
  147.                 $this->mysqli->autocommit(false);
  148.                 $this->intransaction = false;
  149.         }
  150.  
  151.         public function transaction_rollback() {
  152.                 $this->mysqli->rollback();
  153.                 $this->mysqli->autocommit(false);
  154.                 $this->intransaction = false;
  155.         }
  156.  
  157. }
  158.  
  159. function bind_placeholder_vars(&$stmt,$params,$debug=0) {
  160.         // Credit to: Dave Morgan
  161.         // Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
  162.         //                   https://stackoverflow.com/questions/17219214/how-to-bind-in-mysqli-dynamically
  163.         if ($params != null) {
  164.                 $types = '';                        //initial sting with types
  165.                 foreach ($params as $param) {        //for each element, determine type and add
  166.                         if (is_int($param)) {
  167.                                 $types .= 'i';              //integer
  168.                         } elseif (is_float($param)) {
  169.                                 $types .= 'd';              //double
  170.                         } elseif (is_string($param)) {
  171.                                 $types .= 's';              //string
  172.                         } else {
  173.                                 $types .= 'b';              //blob and unknown
  174.                         }
  175.                 }
  176.  
  177.                 $bind_names = array();
  178.                 $bind_names[] = $types;             //first param needed is the type string
  179.                                                                 // eg:  'issss'
  180.  
  181.                 for ($i=0; $i<count($params);$i++) {    //go through incoming params and added em to array
  182.                         $bind_name = 'bind' . $i;       //give them an arbitrary name
  183.                         $$bind_name = $params[$i];      //add the parameter to the variable variable
  184.                         $bind_names[] = &$$bind_name;   //now associate the variable as an element in an array
  185.                 }
  186.  
  187.                 if ($debug) {
  188.                         echo "\$bind_names:<br />\n";
  189.                         var_dump($bind_names);
  190.                         echo "<br />\n";
  191.                 }
  192.                 //error_log("better_mysqli has params ".print_r($bind_names, 1));
  193.                 //call the function bind_param with dynamic params
  194.                 call_user_func_array(array($stmt,'bind_param'),$bind_names);
  195.                 return true;
  196.         }else{
  197.                 return false;
  198.         }
  199. }
  200.  
  201. function bind_result_array($stmt, &$row) {
  202.         // Credit to: Dave Morgan
  203.         // Code ripped from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
  204.         $meta = $stmt->result_metadata();
  205.         while ($field = $meta->fetch_field()) {
  206.                 $params[] = &$row[$field->name];
  207.         }
  208.         call_user_func_array(array($stmt, 'bind_result'), $params);
  209.         return true;
  210. }
  211.  
  212. if (!MYSQLND_AVAILABLE) {
  213.         class iimysqli_result {
  214.                 // Source: https://www.php.net/manual/de/mysqli-stmt.get-result.php#113398
  215.  
  216.                 public $stmt, $nCols;
  217.  
  218.                 function fetch_array() {
  219.                         // https://stackoverflow.com/questions/10752815/mysqli-get-result-alternative , modified
  220.                         $stmt = $this->stmt;
  221.                         $stmt->store_result();
  222.                         $resultkeys = array();
  223.                         $thisName = "";
  224.  
  225.                         if ($stmt->num_rows==0) return false;
  226.  
  227.                         for ( $i = 0; $i < $stmt->num_rows; $i++ ) {
  228.                                 $metadata = $stmt->result_metadata();
  229.                                 while ( $field = $metadata->fetch_field() ) {
  230.                                         $thisName = $field->name;
  231.                                         $resultkeys[] = $thisName;
  232.                                 }
  233.                         }
  234.  
  235.                         $ret = array();
  236.                         $code = "return mysqli_stmt_bind_result(\$this->stmt ";
  237.                         for ($i=0; $i<$this->nCols; $i++) {
  238.                                 $ret[$i] = NULL;
  239.                                 $theValue = $resultkeys[$i];
  240.                                 $code .= ", \$ret['$theValue']";
  241.                         }
  242.  
  243.                         $code .= ");";
  244.                         if (!eval($code)) {
  245.                                 return NULL;
  246.                         }
  247.  
  248.                         // This should advance the "$stmt" cursor.
  249.                         if (!mysqli_stmt_fetch($this->stmt)) {
  250.                                 return NULL;
  251.                         }
  252.  
  253.                         // Return the array we built.
  254.                         return $ret;
  255.                 }
  256.  
  257.                 public function num_rows() {
  258.                         $this->stmt->store_result();
  259.                         return $this->stmt->num_rows;
  260.                 }
  261.  
  262.                 public function fetch_object() {
  263.                         $obj = new stdClass;
  264.                         $ary = $this->fetch_array();
  265.                         if (!$ary) return false;
  266.                         foreach ($ary as $name => $val) {
  267.                                 $obj->$name = $val;
  268.                         }
  269.                         return $obj;
  270.                 }
  271.         }
  272.  
  273.         function iimysqli_stmt_get_result($stmt) {
  274.                 // Source: https://www.php.net/manual/de/mysqli-stmt.get-result.php#113398
  275.  
  276.                 /**    EXPLANATION:
  277.                  * We are creating a fake "result" structure to enable us to have
  278.                  * source-level equivalent syntax to a query executed via
  279.                  * mysqli_query().
  280.                  *
  281.                  *    $stmt = mysqli_prepare($conn, "");
  282.                  *    mysqli_bind_param($stmt, "types", ...);
  283.                  *
  284.                  *    $param1 = 0;
  285.                  *    $param2 = 'foo';
  286.                  *    $param3 = 'bar';
  287.                  *    mysqli_execute($stmt);
  288.                  *    $result _mysqli_stmt_get_result($stmt);
  289.                  *        [ $arr = _mysqli_result_fetch_array($result);
  290.                  *            || $assoc = _mysqli_result_fetch_assoc($result); ]
  291.                  *    mysqli_stmt_close($stmt);
  292.                  *    mysqli_close($conn);
  293.                  *
  294.                  * At the source level, there is no difference between this and mysqlnd.
  295.                  **/
  296.                 $metadata = mysqli_stmt_result_metadata($stmt);
  297.                 $ret = new iimysqli_result;
  298.                 if (!$ret) return NULL;
  299.  
  300.                 if (is_bool($metadata)) {
  301.                         return $metadata;
  302.                 }
  303.  
  304.                 $ret->nCols = mysqli_num_fields($metadata);
  305.                 $ret->stmt = $stmt;
  306.  
  307.                 mysqli_free_result($metadata);
  308.                 return $ret;
  309.         }
  310. }
  311.