OPiQuotations  v.03.00.00 — April 5, 2019
Db.inc
Go to the documentation of this file.
1 <?php // -*- coding: utf-8 -*-
2 
3 /** \file Db.inc
4  * (March 14, 2018)
5  *
6  * \brief
7  * Class to connect and deal to the MySQL database.
8  *
9  * <img src="db_opiquotations_graph.png" width="682" height="771" border="0" alt="[DB opiquotations graph]" />
10  *
11  * Piece of OPiQuotations.
12  * https://bitbucket.org/OPiMedia/opiquotations
13  *
14  * GPLv3 --- Copyright (C) 2014, 2015, 2016, 2017, 2018 Olivier Pirson
15  * http://www.opimedia.be/
16  *
17  * @package OPiQuotations
18  */
19 namespace OPiQuotations;
20 
21 require_once 'OPiQuotations/log.inc';
22 require_once 'OPiQuotations/OPiQuotation.inc';
23 
24 
25 /**
26  * Class to connect and deal to the MySQL database.
27  */
28 class Db {
29  /**
30  * \brief
31  * Open the connection to the database $db_name.
32  *
33  * If connection fails
34  * then write a message in the errors log file.
35  *
36  * See is_connected() function.
37  *
38  * @param string $host Host name or IP address of the server
39  * @param string $user MySQL user name
40  * @param string $password MySQL password
41  * @param string $db_name Name of the database
42  */
43  public function __construct($host, $user, $password, $db_name) {
44  #DEBUG
45  assert('is_string($host)');
46  assert('is_string($user)');
47  assert('is_string($password)');
48  assert('is_string($db_name)');
49  #DEBUG_END
50 
51  \mysqli_report(MYSQLI_REPORT_OFF);
52  #DEBUG
53  assert(\mysqli_report(MYSQLI_REPORT_ALL^MYSQLI_REPORT_STRICT^MYSQLI_REPORT_INDEX) === true);
54  #DEBUG_END
55 
56  $this->connect = new \mysqli($host, $user, $password, $db_name);
57 
58  if ($this->connect->connect_error) {
59  to_log('mysqli connect error '.\mysqli_connect_errno().' : '.\mysqli_connect_error().'
60 '.@print_r($this->connect, true));
61  $this->connect = null;
62  } elseif (!$this->connect->set_charset('utf8')) {
63  to_log('mysqli::set_charset(\'utf8\') failed!
64 Server:'.$this->connect->server_info.'
65 Client:'.$this->connect->client_info.'
66 '.@print_r($this->connect, true));
67  $this->connect->close();
68  $this->connect = null;
69  }
70  }
71 
72 
73  /**
74  * \brief
75  * Close the connection to the database.
76  */
77  public function __destruct() {
78  if (!empty($this->connect)) {
79  $this->connect->close();
80  }
81  }
82 
83 
84 
85  /**
86  * \brief
87  * If connection is ok
88  * then return true
89  * else return false.
90  *
91  * @return bool
92  */
93  public function is_connected() {
94  return !empty($this->connect);
95  }
96 
97 
98  /**
99  * \brief
100  * Return the string with its special characters escaped
101  * for use in a SQL statement.
102  *
103  * @param string $s
104  *
105  * @return string
106  */
107  public function escape($s) {
108  #DEBUG
109  assert('is_string($s)');
110  assert('$this->is_connected()');
111  #DEBUG_END
112 
113  return $this->connect->real_escape_string($s);
114  }
115 
116 
117  /**
118  * \brief
119  * Return a associative table id => array(name, number of use)
120  * of elements of the table.
121  *
122  * @param string $table (must be 'author', 'nation', 'subject' or 'work')
123  *
124  * @return array[array]
125  */
126  public function list_to_assoc($table) {
127  #DEBUG
128  assert('is_string($table)');
129  assert('in_array($table, array(\'author\', \'nation\', \'subject\' or \'work\'))');
130  #DEBUG_END
131 
132  $quots = array();
133 
134  if (!$this->is_connected()) {
135  to_log('Db.list_to_assoc(\''.print_r($table, true).'\') impossible because is NOT connected!');
136 
137  return $quots;
138  }
139 
140  $id_names = array('author' => 'nation_author_id',
141  'nation' => 'nation_author_id');
142  $id_name = (isset($id_names[$table])
143  ? $id_names[$table]
144  : $table.'_id');
145  $is_maxim = ($table === 'nation'
146  ? ' AND `q`.`is_maxim`=1'
147  : ($table === 'author' || $table === 'work'
148  ? ' AND `q`.`is_maxim`=0'
149  : ''));
150 
151  $query = 'SELECT `t`.`id`, `t`.`name`, (SELECT COUNT(*) FROM `quotation` AS `q` WHERE `t`.`id`=`q`.`'.$id_name.'`'.$is_maxim.') AS `nb`
152 FROM `'.$table.'` AS `t`
153 ORDER BY `name` COLLATE utf8_unicode_ci;';
154 
155  $result = $this->connect->query($query);
156  if ($result !== false) {
157  while ($row = $result->fetch_assoc()) {
158  settype($row['id'], 'int');
159  settype($row['nb'], 'int');
160  if ($row['nb'] > 0) {
161  $quots[$row['id']] = array($row['name'], $row['nb']);
162  }
163  }
164 
165  $result->free_result();
166  }
167  else {
168  to_log('MySQL Db.list_to_assoc
169 '.$query.'
170 failed!
171 Server:'.$this->connect->server_info.'
172 Client:'.$this->connect->client_info.'
173 '.@print_r($this->connect, true));
174  }
175 
176  return $quots;
177  }
178 
179 
180  /**
181  * \brief
182  * Return the numbers of quotations and/or maxims.
183  *
184  * If $is_maxim === null then return the numbers of quotations/maxims,\n
185  * if $is_maxim === false then return the numbers of quotations,\n
186  * if $is_maxim === true then return the numbers of maxims.
187  *
188  * @param null|bool $is_maxim
189  *
190  * @return int >= 0
191  */
192  public function nb($is_maxim=null) {
193  #DEBUG
194  assert('($is_maxim === null) || is_bool($is_maxim)');
195  assert('$this->is_connected()');
196  #DEBUG_END
197 
198  if (!$this->is_connected()) {
199  to_log('Db.nb(\''.print_r($is_maxim, true).'\') impossible because is NOT connected!');
200 
201  return 0;
202  }
203 
204  $query = 'SELECT COUNT(*) AS `nb`
205 FROM `quotation` AS `q`';
206  if ($is_maxim !== null) {
207  $query .= '
208 WHERE `q`.`is_maxim`='.($is_maxim
209  ? '1'
210  : '0');
211  }
212  $query .= ';';
213 
214  $result = $this->connect->query($query);
215  if ($result !== false) {
216  $row = $result->fetch_assoc();
217 
218  return (int)$row['nb'];
219  }
220  else {
221  to_log('MySQL query
222 '.$query.'
223 failed!
224 Server:'.$this->connect->server_info.'
225 Client:'.$this->connect->client_info.'
226 '.@print_r($this->connect, true));
227 
228  return 0;
229  }
230  }
231 
232 
233  /**
234  * \brief
235  * Execute the MySQL query INSERT.
236  *
237  * If insertion is ok
238  * then return true,
239  * else return false.
240  *
241  * @param string $query Valid MySQL query
242  *
243  * @return bool
244  */
245  public function query_insert($query) {
246  #DEBUG
247  assert('is_string($query)');
248  assert('$this->is_connected()');
249  #DEBUG_END
250 
251  if (!$this->is_connected()) {
252  to_log('Db.query_insert(\''.print_r($query, true).'\') impossible because is NOT connected!');
253 
254  return $quots;
255  }
256 
257  $r = $this->connect->query($query);
258 
259  if (!$r) {
260  to_log('MySQL Db.query_insert
261 '.$query.'
262 failed!
263 Server:'.$this->connect->server_info.'
264 Client:'.$this->connect->client_info.'
265 '.@print_r($this->connect, true));
266  }
267 
268  return $r;
269  }
270 
271 
272  /**
273  * \brief
274  * Return an array with all OPiQuotation
275  * that match with the SQL $where condition
276  * in order specified by $order.
277  *
278  * If $limit is not null
279  * then return only $limit OPiQuotation.
280  *
281  * If $limit and $offset are not null
282  * then return only $limit OPiQuotation from $offset.
283  *
284  * @warning Use escape() with each string piece of $where if necessary.
285  *
286  * @param string $where Valid WHERE clause of the SELECT MySQL command used
287  * (see http://dev.mysql.com/doc/refman/5.1/en/select.html )
288  * @param string $order Valid ORDER clause of the SELECT MySQL command used
289  * (see http://dev.mysql.com/doc/refman/5.1/en/select.html )
290  * @param null|int $limit (must be >= 0)
291  * @param null|int $offset (must be >= 0)
292  *
293  * @return OPiQuotation[]
294  */
295  public function query_quotations($where='', $order='', $limit=null, $offset=null) {
296  #DEBUG
297  assert('is_string($where)');
298  assert('is_string($order)');
299  assert('($limit === null) || (is_int($limit) && ($limit >= 0))');
300  assert('($offset === null) || (is_int($offset) && ($offset >= 0))');
301  assert('$this->is_connected()');
302  #DEBUG_END
303 
304  $quots = array();
305 
306  if (!$this->is_connected()) {
307  to_log('Db.query_quotations(\''.print_r($where, true).'\', \''.print_r($order, true).'\') impossible because is NOT connected!');
308 
309  return $quots;
310  }
311 
312 
313  // Get quotations
314  $query = array('SELECT `id`, `text`, `text_lang`, `translation`, `is_maxim`, `is_marked`, `subject`, `nation`, `author`, `work`,',
315  ' `selection_label`, `is_misattributed`',
316  'FROM `vw_quotation_selection`');
317 
318  $where = (string)$where;
319  if ($where !== '') {
320  $query[] = $where;
321  }
322 
323  unset($where);
324 
325  $query[] = 'GROUP BY `id`';
326 
327  $order = (string)$order;
328  if ($order !== '') {
329  $query[] = $order;
330  }
331 
332  unset($order);
333 
334  if ($limit !== null) {
335  $limit = max(0, (int)$limit);
336 
337  if ($offset === null) {
338  $query[] = 'LIMIT '.$limit;
339  }
340  else {
341  $offset = max(0, (int)$offset);
342  $query[] = 'LIMIT '.$limit.' OFFSET '.$offset;
343  }
344  }
345 
346  unset($limit);
347  unset($offset);
348 
349  $query = implode('
350 ', $query).';';
351 
352  $result = $this->connect->query($query);
353  if ($result !== false) {
354  // Fill associative table
355  while ($row = $result->fetch_assoc()) {
356  settype($row['id'], 'int');
357  settype($row['is_maxim'], 'bool');
358  settype($row['is_marked'], 'bool');
359  settype($row['is_misattributed'], 'bool');
360 
361  $id = $row['id'];
362 
363  #DEBUG
364  assert(!isset($quots[$id]));
365  #DEBUG_END
366 
367  $quots[$id] = new OPiQuotation($id, $row['text'],
368  $row['is_maxim'],
369  $row['is_marked'],
370  $row['translation'],
371  $row['subject'],
372  ($row['is_maxim']
373  ? $row['nation']
374  : $row['author']),
375  $row['work'],
376  $row['text_lang'],
377  null,
378  $row['is_misattributed']);
379  }
380 
381  $result->free_result();
382 
383  unset($id);
384  unset($row);
385  }
386  else {
387  to_log('MySQL Db.query_quotations: get quotations
388 '.$query.'
389 failed!
390 Server:'.$this->connect->server_info.'
391 Client:'.$this->connect->client_info.'
392 '.@print_r($this->connect, true));
393  }
394 
395  unset($query);
396  unset($result);
397 
398 
399  if (!empty($quots)) {
400  // Get selections for all these quotations
401  $query = 'SELECT `id`, `selection_label`, `selection_datetime_utc`, `selection_url`
402 FROM `vw_quotation_selection`
403 WHERE `selection_label` IS NOT NULL AND `id` IN ('.implode(',', array_keys($quots)).')
404 ORDER BY `selection_datetime_utc`';
405 
406  $result = $this->connect->query($query);
407  if ($result !== false) {
408  while ($row = $result->fetch_assoc()) {
409  settype($row['id'], 'int');
410 
411  $selection = new Selection($row['selection_label'],
412  ($row['selection_datetime_utc'] === null
413  ? null
414  : new \DateTime($row['selection_datetime_utc'])),
415  $row['selection_url']);
416  $quots[$row['id']]->selections_add($selection);
417  }
418 
419  $result->free_result();
420 
421  unset($selection);
422  unset($row);
423  }
424  else {
425  to_log('MySQL Db.query_quotations: get selections
426 '.$query.'
427 failed!
428 Server:'.$this->connect->server_info.'
429 Client:'.$this->connect->client_info.'
430 '.@print_r($this->connect, true));
431  }
432 
433  unset($query);
434  unset($result);
435  }
436 
437 
438  // Convert associative table $quots to an array
439  $array = array();
440 
441  foreach ($quots as $id => $quot) {
442  #DEBUG
443  assert($id === $quot->id());
444  #DEBUG_END
445 
446  $array[] = $quot;
447  }
448 
449  unset($id);
450  unset($quot);
451 
452  return $array;
453  }
454 
455 
456  /**
457  * \brief
458  * Return the number of all OPiQuotation
459  * that match with the SQL $where condition.
460  *
461  * @warning Use escape() with each string piece of $where if necessary.
462  *
463  * @param string $where Valid WHERE clause of the SELECT MySQL command used
464  * (see http://dev.mysql.com/doc/refman/5.1/en/select.html )
465  *
466  * @return int
467  */
468  public function query_quotations_nb($where='') {
469  #DEBUG
470  assert('is_string($where)');
471  #DEBUG_END
472 
473  if (!$this->is_connected()) {
474  to_log('Db.query_quotations(\''.print_r($where, true).'\', \''.print_r($order, true).'\') impossible because is NOT connected!');
475 
476  return 0;
477  }
478 
479 
480  // Get quotations
481  $query = array('SELECT DISTINCT `id`',
482  'FROM `vw_quotation_selection`');
483 
484  $where = (string)$where;
485  if ($where !== '') {
486  $query[] = $where;
487  }
488 
489  unset($where);
490 
491  $query = implode('
492 ', $query).';';
493 
494  $nb = 0;
495 
496  $result = $this->connect->query($query);
497  if ($result !== false) {
498  $nb = $result->num_rows;
499  }
500  else {
501  to_log('MySQL Db.query_quotations: get quotations numbers
502 '.$query.'
503 failed!
504 Server:'.$this->connect->server_info.'
505 Client:'.$this->connect->client_info.'
506 '.@print_r($this->connect, true));
507  }
508 
509  unset($query);
510  unset($result);
511 
512  return $nb;
513  }
514 
515 
516  /**
517  * \brief
518  * Return escaped and quoted $x (converted to string).
519  *
520  * @param null|mixed $x
521  *
522  * @return string
523  */
524  public function to_string($x) {
525  return '\''.$this->escape((string)$x).'\'';
526  }
527 
528 
529  /**
530  * \brief
531  * If $x === null
532  * then return 'NULL'
533  * else return to_string($x).
534  *
535  * @param null|mixed $x
536  *
537  * @return string
538  */
539  public function to_string_or_NULL($x) {
540  return ($x === null
541  ? 'NULL'
542  : $this->to_string(''.$x));
543  }
544 
545 
546 
547  /** @var \mysqli $connect
548  * \brief
549  * Connection to the MySQL database.
550  */
551  protected $connect;
552 }
553 
554 
555 return true;
556 
557 ?>
to_log($message)
Append $message in LOG_FILE.
Definition: log.inc:104
Class selection.
query_quotations_nb($where='')
Return the number of all OPiQuotation that match with the SQL $where condition.
Definition: Db.inc:468
query_quotations($where='', $order='', $limit=null, $offset=null)
Return an array with all OPiQuotation that match with the SQL $where condition in order specified by ...
Definition: Db.inc:295
__destruct()
Close the connection to the database.
Definition: Db.inc:77
nb($is_maxim=null)
Return the numbers of quotations and/or maxims.
Definition: Db.inc:192
list_to_assoc($table)
Return a associative table id => array(name, number of use) of elements of the table.
Definition: Db.inc:126
query_insert($query)
Execute the MySQL query INSERT.
Definition: Db.inc:245
if($ch===false) $result
__construct($host, $user, $password, $db_name)
Open the connection to the database $db_name.
Definition: Db.inc:43
is_connected()
If connection is ok then return true else return false.
Definition: Db.inc:93
to_string_or_NULL($x)
If $x === null then return &#39;NULL&#39; else return to_string($x).
Definition: Db.inc:539
escape($s)
Return the string with its special characters escaped for use in a SQL statement. ...
Definition: Db.inc:107
$connect
Connection to the MySQL database.
Definition: Db.inc:551
if( $search !==null &&$search !=='')
Definition: index.php:542