PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/server_side/scripts/ssp.class.php

https://gitlab.com/yesnyes/DataTables
PHP | 526 lines | 260 code | 81 blank | 185 comment | 39 complexity | f8ee608dcae246c4081d5b754a62c916 MD5 | raw file
  1. <?php
  2. /*
  3. * Helper functions for building a DataTables server-side processing SQL query
  4. *
  5. * The static functions in this class are just helper functions to help build
  6. * the SQL used in the DataTables demo server-side processing scripts. These
  7. * functions obviously do not represent all that can be done with server-side
  8. * processing, they are intentionally simple to show how it works. More complex
  9. * server-side processing operations will likely require a custom script.
  10. *
  11. * See http://datatables.net/usage/server-side for full details on the server-
  12. * side processing requirements of DataTables.
  13. *
  14. * @license MIT - http://datatables.net/license_mit
  15. */
  16. // REMOVE THIS BLOCK - used for DataTables test environment only!
  17. $file = $_SERVER['DOCUMENT_ROOT'].'/datatables/mysql.php';
  18. if ( is_file( $file ) ) {
  19. include( $file );
  20. }
  21. class SSP {
  22. /**
  23. * Create the data output array for the DataTables rows
  24. *
  25. * @param array $columns Column information array
  26. * @param array $data Data from the SQL get
  27. * @return array Formatted data in a row based format
  28. */
  29. static function data_output ( $columns, $data )
  30. {
  31. $out = array();
  32. for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
  33. $row = array();
  34. for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
  35. $column = $columns[$j];
  36. // Is there a formatter?
  37. if ( isset( $column['formatter'] ) ) {
  38. $row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
  39. }
  40. else {
  41. $row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
  42. }
  43. }
  44. $out[] = $row;
  45. }
  46. return $out;
  47. }
  48. /**
  49. * Database connection
  50. *
  51. * Obtain an PHP PDO connection from a connection details array
  52. *
  53. * @param array $conn SQL connection details. The array should have
  54. * the following properties
  55. * * host - host name
  56. * * db - database name
  57. * * user - user name
  58. * * pass - user password
  59. * @return resource PDO connection
  60. */
  61. static function db ( $conn )
  62. {
  63. if ( is_array( $conn ) ) {
  64. return self::sql_connect( $conn );
  65. }
  66. return $conn;
  67. }
  68. /**
  69. * Paging
  70. *
  71. * Construct the LIMIT clause for server-side processing SQL query
  72. *
  73. * @param array $request Data sent to server by DataTables
  74. * @param array $columns Column information array
  75. * @return string SQL limit clause
  76. */
  77. static function limit ( $request, $columns )
  78. {
  79. $limit = '';
  80. if ( isset($request['start']) && $request['length'] != -1 ) {
  81. $limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
  82. }
  83. return $limit;
  84. }
  85. /**
  86. * Ordering
  87. *
  88. * Construct the ORDER BY clause for server-side processing SQL query
  89. *
  90. * @param array $request Data sent to server by DataTables
  91. * @param array $columns Column information array
  92. * @return string SQL order by clause
  93. */
  94. static function order ( $request, $columns )
  95. {
  96. $order = '';
  97. if ( isset($request['order']) && count($request['order']) ) {
  98. $orderBy = array();
  99. $dtColumns = self::pluck( $columns, 'dt' );
  100. for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
  101. // Convert the column index into the column data property
  102. $columnIdx = intval($request['order'][$i]['column']);
  103. $requestColumn = $request['columns'][$columnIdx];
  104. $columnIdx = array_search( $requestColumn['data'], $dtColumns );
  105. $column = $columns[ $columnIdx ];
  106. if ( $requestColumn['orderable'] == 'true' ) {
  107. $dir = $request['order'][$i]['dir'] === 'asc' ?
  108. 'ASC' :
  109. 'DESC';
  110. $orderBy[] = '`'.$column['db'].'` '.$dir;
  111. }
  112. }
  113. $order = 'ORDER BY '.implode(', ', $orderBy);
  114. }
  115. return $order;
  116. }
  117. /**
  118. * Searching / Filtering
  119. *
  120. * Construct the WHERE clause for server-side processing SQL query.
  121. *
  122. * NOTE this does not match the built-in DataTables filtering which does it
  123. * word by word on any field. It's possible to do here performance on large
  124. * databases would be very poor
  125. *
  126. * @param array $request Data sent to server by DataTables
  127. * @param array $columns Column information array
  128. * @param array $bindings Array of values for PDO bindings, used in the
  129. * sql_exec() function
  130. * @return string SQL where clause
  131. */
  132. static function filter ( $request, $columns, &$bindings )
  133. {
  134. $globalSearch = array();
  135. $columnSearch = array();
  136. $dtColumns = self::pluck( $columns, 'dt' );
  137. if ( isset($request['search']) && $request['search']['value'] != '' ) {
  138. $str = $request['search']['value'];
  139. for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
  140. $requestColumn = $request['columns'][$i];
  141. $columnIdx = array_search( $requestColumn['data'], $dtColumns );
  142. $column = $columns[ $columnIdx ];
  143. if ( $requestColumn['searchable'] == 'true' ) {
  144. $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
  145. $globalSearch[] = "`".$column['db']."` LIKE ".$binding;
  146. }
  147. }
  148. }
  149. // Individual column filtering
  150. if ( isset( $request['columns'] ) ) {
  151. for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
  152. $requestColumn = $request['columns'][$i];
  153. $columnIdx = array_search( $requestColumn['data'], $dtColumns );
  154. $column = $columns[ $columnIdx ];
  155. $str = $requestColumn['search']['value'];
  156. if ( $requestColumn['searchable'] == 'true' &&
  157. $str != '' ) {
  158. $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
  159. $columnSearch[] = "`".$column['db']."` LIKE ".$binding;
  160. }
  161. }
  162. }
  163. // Combine the filters into a single string
  164. $where = '';
  165. if ( count( $globalSearch ) ) {
  166. $where = '('.implode(' OR ', $globalSearch).')';
  167. }
  168. if ( count( $columnSearch ) ) {
  169. $where = $where === '' ?
  170. implode(' AND ', $columnSearch) :
  171. $where .' AND '. implode(' AND ', $columnSearch);
  172. }
  173. if ( $where !== '' ) {
  174. $where = 'WHERE '.$where;
  175. }
  176. return $where;
  177. }
  178. /**
  179. * Perform the SQL queries needed for an server-side processing requested,
  180. * utilising the helper functions of this class, limit(), order() and
  181. * filter() among others. The returned array is ready to be encoded as JSON
  182. * in response to an SSP request, or can be modified if needed before
  183. * sending back to the client.
  184. *
  185. * @param array $request Data sent to server by DataTables
  186. * @param array|PDO $conn PDO connection resource or connection parameters array
  187. * @param string $table SQL table to query
  188. * @param string $primaryKey Primary key of the table
  189. * @param array $columns Column information array
  190. * @return array Server-side processing response array
  191. */
  192. static function simple ( $request, $conn, $table, $primaryKey, $columns )
  193. {
  194. $bindings = array();
  195. $db = self::db( $conn );
  196. // Build the SQL query string from the request
  197. $limit = self::limit( $request, $columns );
  198. $order = self::order( $request, $columns );
  199. $where = self::filter( $request, $columns, $bindings );
  200. // Main query to actually get the data
  201. $data = self::sql_exec( $db, $bindings,
  202. "SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
  203. FROM `$table`
  204. $where
  205. $order
  206. $limit"
  207. );
  208. // Data set length after filtering
  209. $resFilterLength = self::sql_exec( $db,
  210. "SELECT FOUND_ROWS()"
  211. );
  212. $recordsFiltered = $resFilterLength[0][0];
  213. // Total data set length
  214. $resTotalLength = self::sql_exec( $db,
  215. "SELECT COUNT(`{$primaryKey}`)
  216. FROM `$table`"
  217. );
  218. $recordsTotal = $resTotalLength[0][0];
  219. /*
  220. * Output
  221. */
  222. return array(
  223. "draw" => isset ( $request['draw'] ) ?
  224. intval( $request['draw'] ) :
  225. 0,
  226. "recordsTotal" => intval( $recordsTotal ),
  227. "recordsFiltered" => intval( $recordsFiltered ),
  228. "data" => self::data_output( $columns, $data )
  229. );
  230. }
  231. /**
  232. * The difference between this method and the `simple` one, is that you can
  233. * apply additional `where` conditions to the SQL queries. These can be in
  234. * one of two forms:
  235. *
  236. * * 'Result condition' - This is applied to the result set, but not the
  237. * overall paging information query - i.e. it will not effect the number
  238. * of records that a user sees they can have access to. This should be
  239. * used when you want apply a filtering condition that the user has sent.
  240. * * 'All condition' - This is applied to all queries that are made and
  241. * reduces the number of records that the user can access. This should be
  242. * used in conditions where you don't want the user to ever have access to
  243. * particular records (for example, restricting by a login id).
  244. *
  245. * @param array $request Data sent to server by DataTables
  246. * @param array|PDO $conn PDO connection resource or connection parameters array
  247. * @param string $table SQL table to query
  248. * @param string $primaryKey Primary key of the table
  249. * @param array $columns Column information array
  250. * @param string $whereResult WHERE condition to apply to the result set
  251. * @param string $whereAll WHERE condition to apply to all queries
  252. * @return array Server-side processing response array
  253. */
  254. static function complex ( $request, $conn, $table, $primaryKey, $columns, $whereResult=null, $whereAll=null )
  255. {
  256. $bindings = array();
  257. $db = self::db( $conn );
  258. $localWhereResult = array();
  259. $localWhereAll = array();
  260. $whereAllSql = '';
  261. // Build the SQL query string from the request
  262. $limit = self::limit( $request, $columns );
  263. $order = self::order( $request, $columns );
  264. $where = self::filter( $request, $columns, $bindings );
  265. $whereResult = self::_flatten( $whereResult );
  266. $whereAll = self::_flatten( $whereAll );
  267. if ( $whereResult ) {
  268. $where = $where ?
  269. $where .' AND '.$whereResult :
  270. 'WHERE '.$whereResult;
  271. }
  272. if ( $whereAll ) {
  273. $where = $where ?
  274. $where .' AND '.$whereAll :
  275. 'WHERE '.$whereAll;
  276. $whereAllSql = 'WHERE '.$whereAll;
  277. }
  278. // Main query to actually get the data
  279. $data = self::sql_exec( $db, $bindings,
  280. "SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
  281. FROM `$table`
  282. $where
  283. $order
  284. $limit"
  285. );
  286. // Data set length after filtering
  287. $resFilterLength = self::sql_exec( $db,
  288. "SELECT FOUND_ROWS()"
  289. );
  290. $recordsFiltered = $resFilterLength[0][0];
  291. // Total data set length
  292. $resTotalLength = self::sql_exec( $db, $bindings,
  293. "SELECT COUNT(`{$primaryKey}`)
  294. FROM `$table` ".
  295. $whereAllSql
  296. );
  297. $recordsTotal = $resTotalLength[0][0];
  298. /*
  299. * Output
  300. */
  301. return array(
  302. "draw" => isset ( $request['draw'] ) ?
  303. intval( $request['draw'] ) :
  304. 0,
  305. "recordsTotal" => intval( $recordsTotal ),
  306. "recordsFiltered" => intval( $recordsFiltered ),
  307. "data" => self::data_output( $columns, $data )
  308. );
  309. }
  310. /**
  311. * Connect to the database
  312. *
  313. * @param array $sql_details SQL server connection details array, with the
  314. * properties:
  315. * * host - host name
  316. * * db - database name
  317. * * user - user name
  318. * * pass - user password
  319. * @return resource Database connection handle
  320. */
  321. static function sql_connect ( $sql_details )
  322. {
  323. try {
  324. $db = @new PDO(
  325. "mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
  326. $sql_details['user'],
  327. $sql_details['pass'],
  328. array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
  329. );
  330. }
  331. catch (PDOException $e) {
  332. self::fatal(
  333. "An error occurred while connecting to the database. ".
  334. "The error reported by the server was: ".$e->getMessage()
  335. );
  336. }
  337. return $db;
  338. }
  339. /**
  340. * Execute an SQL query on the database
  341. *
  342. * @param resource $db Database handler
  343. * @param array $bindings Array of PDO binding values from bind() to be
  344. * used for safely escaping strings. Note that this can be given as the
  345. * SQL query string if no bindings are required.
  346. * @param string $sql SQL query to execute.
  347. * @return array Result from the query (all rows)
  348. */
  349. static function sql_exec ( $db, $bindings, $sql=null )
  350. {
  351. // Argument shifting
  352. if ( $sql === null ) {
  353. $sql = $bindings;
  354. }
  355. $stmt = $db->prepare( $sql );
  356. //echo $sql;
  357. // Bind parameters
  358. if ( is_array( $bindings ) ) {
  359. for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
  360. $binding = $bindings[$i];
  361. $stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
  362. }
  363. }
  364. // Execute
  365. try {
  366. $stmt->execute();
  367. }
  368. catch (PDOException $e) {
  369. self::fatal( "An SQL error occurred: ".$e->getMessage() );
  370. }
  371. // Return all
  372. return $stmt->fetchAll( PDO::FETCH_BOTH );
  373. }
  374. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  375. * Internal methods
  376. */
  377. /**
  378. * Throw a fatal error.
  379. *
  380. * This writes out an error message in a JSON string which DataTables will
  381. * see and show to the user in the browser.
  382. *
  383. * @param string $msg Message to send to the client
  384. */
  385. static function fatal ( $msg )
  386. {
  387. echo json_encode( array(
  388. "error" => $msg
  389. ) );
  390. exit(0);
  391. }
  392. /**
  393. * Create a PDO binding key which can be used for escaping variables safely
  394. * when executing a query with sql_exec()
  395. *
  396. * @param array &$a Array of bindings
  397. * @param * $val Value to bind
  398. * @param int $type PDO field type
  399. * @return string Bound key to be used in the SQL where this parameter
  400. * would be used.
  401. */
  402. static function bind ( &$a, $val, $type )
  403. {
  404. $key = ':binding_'.count( $a );
  405. $a[] = array(
  406. 'key' => $key,
  407. 'val' => $val,
  408. 'type' => $type
  409. );
  410. return $key;
  411. }
  412. /**
  413. * Pull a particular property from each assoc. array in a numeric array,
  414. * returning and array of the property values from each item.
  415. *
  416. * @param array $a Array to get data from
  417. * @param string $prop Property to read
  418. * @return array Array of property values
  419. */
  420. static function pluck ( $a, $prop )
  421. {
  422. $out = array();
  423. for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
  424. $out[] = $a[$i][$prop];
  425. }
  426. return $out;
  427. }
  428. /**
  429. * Return a string from an array or a string
  430. *
  431. * @param array|string $a Array to join
  432. * @param string $join Glue for the concatenation
  433. * @return string Joined string
  434. */
  435. static function _flatten ( $a, $join = ' AND ' )
  436. {
  437. if ( ! $a ) {
  438. return '';
  439. }
  440. else if ( $a && is_array($a) ) {
  441. return implode( $join, $a );
  442. }
  443. return $a;
  444. }
  445. }