SSP::simple()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
c 1
b 0
f 0
dl 0
loc 41
rs 9.552
cc 1
nc 1
nop 5
1
<?php
2
3
/*
4
 * Helper functions for building a DataTables server-side processing SQL query
5
 *
6
 * The static functions in this class are just helper functions to help build
7
 * the SQL used in the DataTables demo server-side processing scripts. These
8
 * functions obviously do not represent all that can be done with server-side
9
 * processing, they are intentionally simple to show how it works. More complex
10
 * server-side processing operations will likely require a custom script.
11
 *
12
 * See http://datatables.net/usage/server-side for full details on the server-
13
 * side processing requirements of DataTables.
14
 *
15
 * @license MIT - http://datatables.net/license_mit
16
 */
17
18
19
// REMOVE THIS BLOCK - used for DataTables test environment only!
20
$file = $_SERVER['DOCUMENT_ROOT'].'/datatables/mysql.php';
21
if ( is_file( $file ) ) {
22
	include( $file );
23
}
24
25
26
class SSP {
27
	/**
28
	 * Create the data output array for the DataTables rows
29
	 *
30
	 *  @param  array $columns Column information array
31
	 *  @param  array $data    Data from the SQL get
32
	 *  @return array          Formatted data in a row based format
33
	 */
34
	static function data_output ( $columns, $data )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
35
	{
36
		$out = array();
37
38
		for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
39
			$row = array();
40
41
			for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
42
				$column = $columns[$j];
43
44
				// Is there a formatter?
45
				if ( isset( $column['formatter'] ) ) {
46
					$row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
47
				}
48
				else {
49
					$row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
50
				}
51
			}
52
53
			$out[] = $row;
54
		}
55
56
		return $out;
57
	}
58
59
60
	/**
61
	 * Database connection
62
	 *
63
	 * Obtain an PHP PDO connection from a connection details array
64
	 *
65
	 *  @param  array $conn SQL connection details. The array should have
66
	 *    the following properties
67
	 *     * host - host name
68
	 *     * db   - database name
69
	 *     * user - user name
70
	 *     * pass - user password
71
	 *  @return resource PDO connection
72
	 */
73
	static function db ( $conn )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
74
	{
75
		if ( is_array( $conn ) ) {
0 ignored issues
show
introduced by
The condition is_array($conn) is always true.
Loading history...
76
			return self::sql_connect( $conn );
77
		}
78
79
		return $conn;
80
	}
81
82
83
	/**
84
	 * Paging
85
	 *
86
	 * Construct the LIMIT clause for server-side processing SQL query
87
	 *
88
	 *  @param  array $request Data sent to server by DataTables
89
	 *  @param  array $columns Column information array
90
	 *  @return string SQL limit clause
91
	 */
92
	static function limit ( $request, $columns )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Unused Code introduced by
The parameter $columns is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

92
	static function limit ( $request, /** @scrutinizer ignore-unused */ $columns )

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
93
	{
94
		$limit = '';
95
96
		if ( isset($request['start']) && $request['length'] != -1 ) {
97
			$limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
98
		}
99
100
		return $limit;
101
	}
102
103
104
	/**
105
	 * Ordering
106
	 *
107
	 * Construct the ORDER BY clause for server-side processing SQL query
108
	 *
109
	 *  @param  array $request Data sent to server by DataTables
110
	 *  @param  array $columns Column information array
111
	 *  @return string SQL order by clause
112
	 */
113
	static function order ( $request, $columns )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
114
	{
115
		$order = '';
116
117
		if ( isset($request['order']) && count($request['order']) ) {
118
			$orderBy = array();
119
			$dtColumns = self::pluck( $columns, 'dt' );
120
121
			for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
122
				// Convert the column index into the column data property
123
				$columnIdx = intval($request['order'][$i]['column']);
124
				$requestColumn = $request['columns'][$columnIdx];
125
126
				$columnIdx = array_search( $requestColumn['data'], $dtColumns );
127
				$column = $columns[ $columnIdx ];
128
129
				if ( $requestColumn['orderable'] == 'true' ) {
130
					$dir = $request['order'][$i]['dir'] === 'asc' ?
131
						'ASC' :
132
						'DESC';
133
134
					$orderBy[] = '`'.$column['db'].'` '.$dir;
135
				}
136
			}
137
138
			$order = 'ORDER BY '.implode(', ', $orderBy);
139
		}
140
141
		return $order;
142
	}
143
144
145
	/**
146
	 * Searching / Filtering
147
	 *
148
	 * Construct the WHERE clause for server-side processing SQL query.
149
	 *
150
	 * NOTE this does not match the built-in DataTables filtering which does it
151
	 * word by word on any field. It's possible to do here performance on large
152
	 * databases would be very poor
153
	 *
154
	 *  @param  array $request Data sent to server by DataTables
155
	 *  @param  array $columns Column information array
156
	 *  @param  array $bindings Array of values for PDO bindings, used in the
157
	 *    sql_exec() function
158
	 *  @return string SQL where clause
159
	 */
160
	static function filter ( $request, $columns, &$bindings )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
161
	{
162
		$globalSearch = array();
163
		$columnSearch = array();
164
		$dtColumns = self::pluck( $columns, 'dt' );
165
166
		if ( isset($request['search']) && $request['search']['value'] != '' ) {
167
			$str = $request['search']['value'];
168
169
			for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
170
				$requestColumn = $request['columns'][$i];
171
				$columnIdx = array_search( $requestColumn['data'], $dtColumns );
172
				$column = $columns[ $columnIdx ];
173
174
				if ( $requestColumn['searchable'] == 'true' ) {
175
					$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
176
					$globalSearch[] = "`".$column['db']."` LIKE ".$binding;
177
				}
178
			}
179
		}
180
181
		// Individual column filtering
182
		for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
183
			$requestColumn = $request['columns'][$i];
184
			$columnIdx = array_search( $requestColumn['data'], $dtColumns );
185
			$column = $columns[ $columnIdx ];
186
187
			$str = $requestColumn['search']['value'];
188
189
			if ( $requestColumn['searchable'] == 'true' &&
190
			 $str != '' ) {
191
				$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
192
				$columnSearch[] = "`".$column['db']."` LIKE ".$binding;
193
			}
194
		}
195
196
		// Combine the filters into a single string
197
		$where = '';
198
199
		if ( count( $globalSearch ) ) {
200
			$where = '('.implode(' OR ', $globalSearch).')';
201
		}
202
203
		if ( count( $columnSearch ) ) {
204
			$where = $where === '' ?
205
				implode(' AND ', $columnSearch) :
206
				$where .' AND '. implode(' AND ', $columnSearch);
207
		}
208
209
		if ( $where !== '' ) {
210
			$where = 'WHERE '.$where;
211
		}
212
213
		return $where;
214
	}
215
216
217
	/**
218
	 * Perform the SQL queries needed for an server-side processing requested,
219
	 * utilising the helper functions of this class, limit(), order() and
220
	 * filter() among others. The returned array is ready to be encoded as JSON
221
	 * in response to an SSP request, or can be modified if needed before
222
	 * sending back to the client.
223
	 *
224
	 *  @param  array $request Data sent to server by DataTables
225
	 *  @param  array|PDO $conn PDO connection resource or connection parameters array
226
	 *  @param  string $table SQL table to query
227
	 *  @param  string $primaryKey Primary key of the table
228
	 *  @param  array $columns Column information array
229
	 *  @return array          Server-side processing response array
230
	 */
231
	static function simple ( $request, $conn, $table, $primaryKey, $columns )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
232
	{
233
		$bindings = array();
234
		$db = self::db( $conn );
0 ignored issues
show
Bug introduced by
It seems like $conn can also be of type PDO; however, parameter $conn of SSP::db() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
		$db = self::db( /** @scrutinizer ignore-type */ $conn );
Loading history...
235
236
		// Build the SQL query string from the request
237
		$limit = self::limit( $request, $columns );
238
		$order = self::order( $request, $columns );
239
		$where = self::filter( $request, $columns, $bindings );
240
241
		// Main query to actually get the data
242
		$data = self::sql_exec( $db, $bindings,
243
			"SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
244
			 FROM `$table`
245
			 $where
246
			 $order
247
			 $limit"
248
		);
249
250
		// Data set length after filtering
251
		$resFilterLength = self::sql_exec( $db,
252
			"SELECT FOUND_ROWS()"
0 ignored issues
show
Bug introduced by
'SELECT FOUND_ROWS()' of type string is incompatible with the type array expected by parameter $bindings of SSP::sql_exec(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

252
			/** @scrutinizer ignore-type */ "SELECT FOUND_ROWS()"
Loading history...
253
		);
254
		$recordsFiltered = $resFilterLength[0][0];
255
256
		// Total data set length
257
		$resTotalLength = self::sql_exec( $db,
258
			"SELECT COUNT(`{$primaryKey}`)
259
			 FROM   `$table`"
260
		);
261
		$recordsTotal = $resTotalLength[0][0];
262
263
264
		/*
265
		 * Output
266
		 */
267
		return array(
268
			"draw"            => intval( $request['draw'] ),
269
			"recordsTotal"    => intval( $recordsTotal ),
270
			"recordsFiltered" => intval( $recordsFiltered ),
271
			"data"            => self::data_output( $columns, $data )
272
		);
273
	}
274
275
276
	/**
277
	 * The difference between this method and the `simple` one, is that you can
278
	 * apply additional `where` conditions to the SQL queries. These can be in
279
	 * one of two forms:
280
	 *
281
	 * * 'Result condition' - This is applied to the result set, but not the
282
	 *   overall paging information query - i.e. it will not effect the number
283
	 *   of records that a user sees they can have access to. This should be
284
	 *   used when you want apply a filtering condition that the user has sent.
285
	 * * 'All condition' - This is applied to all queries that are made and
286
	 *   reduces the number of records that the user can access. This should be
287
	 *   used in conditions where you don't want the user to ever have access to
288
	 *   particular records (for example, restricting by a login id).
289
	 *
290
	 *  @param  array $request Data sent to server by DataTables
291
	 *  @param  array|PDO $conn PDO connection resource or connection parameters array
292
	 *  @param  string $table SQL table to query
293
	 *  @param  string $primaryKey Primary key of the table
294
	 *  @param  array $columns Column information array
295
	 *  @param  string $whereResult WHERE condition to apply to the result set
296
	 *  @param  string $whereAll WHERE condition to apply to all queries
297
	 *  @return array          Server-side processing response array
298
	 */
299
	static function complex ( $request, $conn, $table, $primaryKey, $columns, $whereResult=null, $whereAll=null )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
300
	{
301
		$bindings = array();
302
		$db = self::db( $conn );
0 ignored issues
show
Bug introduced by
It seems like $conn can also be of type PDO; however, parameter $conn of SSP::db() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
		$db = self::db( /** @scrutinizer ignore-type */ $conn );
Loading history...
303
		$localWhereResult = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $localWhereResult is dead and can be removed.
Loading history...
304
		$localWhereAll = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $localWhereAll is dead and can be removed.
Loading history...
305
		$whereAllSql = '';
306
307
		// Build the SQL query string from the request
308
		$limit = self::limit( $request, $columns );
309
		$order = self::order( $request, $columns );
310
		$where = self::filter( $request, $columns, $bindings );
311
312
		$whereResult = self::_flatten( $whereResult );
313
		$whereAll = self::_flatten( $whereAll );
314
315
		if ( $whereResult ) {
316
			$where = $where ?
317
				$where .' AND '.$whereResult :
318
				'WHERE '.$whereResult;
319
		}
320
321
		if ( $whereAll ) {
322
			$where = $where ?
323
				$where .' AND '.$whereAll :
324
				'WHERE '.$whereAll;
325
326
			$whereAllSql = 'WHERE '.$whereAll;
327
		}
328
329
		// Main query to actually get the data
330
		$data = self::sql_exec( $db, $bindings,
331
			"SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
332
			 FROM `$table`
333
			 $where
334
			 $order
335
			 $limit"
336
		);
337
338
		// Data set length after filtering
339
		$resFilterLength = self::sql_exec( $db,
340
			"SELECT FOUND_ROWS()"
0 ignored issues
show
Bug introduced by
'SELECT FOUND_ROWS()' of type string is incompatible with the type array expected by parameter $bindings of SSP::sql_exec(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

340
			/** @scrutinizer ignore-type */ "SELECT FOUND_ROWS()"
Loading history...
341
		);
342
		$recordsFiltered = $resFilterLength[0][0];
343
344
		// Total data set length
345
		$resTotalLength = self::sql_exec( $db, $bindings,
346
			"SELECT COUNT(`{$primaryKey}`)
347
			 FROM   `$table` ".
348
			$whereAllSql
349
		);
350
		$recordsTotal = $resTotalLength[0][0];
351
352
		/*
353
		 * Output
354
		 */
355
		return array(
356
			"draw"            => intval( $request['draw'] ),
357
			"recordsTotal"    => intval( $recordsTotal ),
358
			"recordsFiltered" => intval( $recordsFiltered ),
359
			"data"            => self::data_output( $columns, $data )
360
		);
361
	}
362
363
364
	/**
365
	 * Connect to the database
366
	 *
367
	 * @param  array $sql_details SQL server connection details array, with the
368
	 *   properties:
369
	 *     * host - host name
370
	 *     * db   - database name
371
	 *     * user - user name
372
	 *     * pass - user password
373
	 * @return resource Database connection handle
374
	 */
375
	static function sql_connect ( $sql_details )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
376
	{
377
		try {
378
			$db = @new PDO(
379
				"mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
380
				$sql_details['user'],
381
				$sql_details['pass'],
382
				array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
383
			);
384
		}
385
		catch (PDOException $e) {
386
			self::fatal(
387
				"An error occurred while connecting to the database. ".
388
				"The error reported by the server was: ".$e->getMessage()
389
			);
390
		}
391
392
		return $db;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $db returns the type PDO which is incompatible with the documented return type resource.
Loading history...
393
	}
394
395
396
	/**
397
	 * Execute an SQL query on the database
398
	 *
399
	 * @param  resource $db  Database handler
400
	 * @param  array    $bindings Array of PDO binding values from bind() to be
401
	 *   used for safely escaping strings. Note that this can be given as the
402
	 *   SQL query string if no bindings are required.
403
	 * @param  string   $sql SQL query to execute.
404
	 * @return array         Result from the query (all rows)
405
	 */
406
	static function sql_exec ( $db, $bindings, $sql=null )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
407
	{
408
		// Argument shifting
409
		if ( $sql === null ) {
410
			$sql = $bindings;
411
		}
412
413
		$stmt = $db->prepare( $sql );
414
		//echo $sql;
415
416
		// Bind parameters
417
		if ( is_array( $bindings ) ) {
0 ignored issues
show
introduced by
The condition is_array($bindings) is always true.
Loading history...
418
			for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
419
				$binding = $bindings[$i];
420
				$stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
421
			}
422
		}
423
424
		// Execute
425
		try {
426
			$stmt->execute();
427
		}
428
		catch (PDOException $e) {
429
			self::fatal( "An SQL error occurred: ".$e->getMessage() );
430
		}
431
432
		// Return all
433
		return $stmt->fetchAll();
434
	}
435
436
437
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
438
	 * Internal methods
439
	 */
440
441
	/**
442
	 * Throw a fatal error.
443
	 *
444
	 * This writes out an error message in a JSON string which DataTables will
445
	 * see and show to the user in the browser.
446
	 *
447
	 * @param  string $msg Message to send to the client
448
	 */
449
	static function fatal ( $msg )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
450
	{
451
		echo json_encode( array( 
452
			"error" => $msg
453
		) );
454
455
		exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
456
	}
457
458
	/**
459
	 * Create a PDO binding key which can be used for escaping variables safely
460
	 * when executing a query with sql_exec()
461
	 *
462
	 * @param  array &$a    Array of bindings
463
	 * @param  *      $val  Value to bind
0 ignored issues
show
Documentation Bug introduced by
The doc comment $val at position 0 could not be parsed: Unknown type name '$val' at position 0 in $val.
Loading history...
464
	 * @param  int    $type PDO field type
465
	 * @return string       Bound key to be used in the SQL where this parameter
466
	 *   would be used.
467
	 */
468
	static function bind ( &$a, $val, $type )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
469
	{
470
		$key = ':binding_'.count( $a );
471
472
		$a[] = array(
473
			'key' => $key,
474
			'val' => $val,
475
			'type' => $type
476
		);
477
478
		return $key;
479
	}
480
481
482
	/**
483
	 * Pull a particular property from each assoc. array in a numeric array, 
484
	 * returning and array of the property values from each item.
485
	 *
486
	 *  @param  array  $a    Array to get data from
487
	 *  @param  string $prop Property to read
488
	 *  @return array        Array of property values
489
	 */
490
	static function pluck ( $a, $prop )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
491
	{
492
		$out = array();
493
494
		for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
495
			$out[] = $a[$i][$prop];
496
		}
497
498
		return $out;
499
	}
500
501
502
	/**
503
	 * Return a string from an array or a string
504
	 *
505
	 * @param  array|string $a Array to join
506
	 * @param  string $join Glue for the concatenation
507
	 * @return string Joined string
508
	 */
509
	static function _flatten ( $a, $join = ' AND ' )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
510
	{
511
		if ( ! $a ) {
512
			return '';
513
		}
514
		else if ( $a && is_array($a) ) {
515
			return implode( $join, $a );
516
		}
517
		return $a;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $a also could return the type array which is incompatible with the documented return type string.
Loading history...
518
	}
519
}
520
521