* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * \file /multicompany/class/ssp.class.php * \ingroup multicompany * \brief File of class to manage DataTables server-side processing */ /** * \class SSP * \brief DataTables server-side processing class */ class SSP { /** * Create the data output array for the DataTables rows * * @param array $columns Column information array * @param array $data Data from the SQL get * @return array Formatted data in a row based format */ static function data_output($columns, $data) { $out = array(); for ($i=0, $ien=count($data); $i<$ien; $i++) { $row = array(); for ($j=0, $jen=count($columns); $j<$jen; $j++) { $column = $columns[$j]; // Is there a formatter? if (isset( $column['formatter'])) { $row[$column['dt']] = $column['formatter']($data[$i][$column['db']], $data[$i]); } else { $row[$column['dt']] = $data[$i][$columns[$j]['db']]; } } $out[] = $row; } return $out; } /** * Paging * * Construct the LIMIT clause for server-side processing SQL query * * @param array $request Data sent to server by DataTables * @param array $columns Column information array * @return string SQL limit clause */ static function limit($request, $columns, $db) { $limit = ''; if (isset($request['start']) && $request['length'] != -1) { $limit = $db->plimit(intval($request['length']), intval($request['start'])); } return $limit; } /** * Ordering * * Construct the ORDER BY clause for server-side processing SQL query * * @param array $request Data sent to server by DataTables * @param array $columns Column information array * @return string SQL order by clause */ static function order($request, $columns, $db) { $order = ''; $sortfield = array(); $sortorder = array(); if (isset($request['order']) && count($request['order'])) { $dtColumns = self::pluck( $columns, 'dt' ); for ($i=0, $ien=count($request['order']); $i<$ien; $i++) { // Convert the column index into the column data property $columnIdx = intval($request['order'][$i]['column']); $requestColumn = $request['columns'][$columnIdx]; $columnIdx = array_search( $requestColumn['data'], $dtColumns ); $column = $columns[ $columnIdx ]; if ($requestColumn['orderable'] == 'true') { $dir = $request['order'][$i]['dir'] === 'asc' ? 'ASC' : 'DESC'; $sortfield[] = $column['db']; $sortorder[] = $dir; } } $order = $db->order(implode(', ', $sortfield), implode(', ', $sortorder)); } return $order; } /** * Searching / Filtering * * Construct the WHERE clause for server-side processing SQL query. * * NOTE this does not match the built-in DataTables filtering which does it * word by word on any field. It's possible to do here performance on large * databases would be very poor * * @param array $request Data sent to server by DataTables * @param array $columns Column information array * @param array $bindings Array of values for PDO bindings, used in the * sql_exec() function * @return string SQL where clause */ static function filter($request, $columns, $db) { $globalSearch = array(); $columnSearch = array(); $dtColumns = self::pluck( $columns, 'dt' ); if (isset($request['search']) && $request['search']['value'] != '') { $str = $request['search']['value']; for ($i=0, $ien=count($request['columns']); $i<$ien; $i++) { $requestColumn = $request['columns'][$i]; $columnIdx = array_search( $requestColumn['data'], $dtColumns ); $column = $columns[ $columnIdx ]; if ($requestColumn['searchable'] == 'true') { $globalSearch[] = $column['db']." LIKE '%" . $db->escape($str)."%'"; } } } // Individual column filtering if (isset( $request['columns'])) { for ($i=0, $ien=count($request['columns']); $i<$ien; $i++) { $requestColumn = $request['columns'][$i]; $columnIdx = array_search( $requestColumn['data'], $dtColumns ); $column = $columns[$columnIdx]; $str = $requestColumn['search']['value']; if ($requestColumn['searchable'] == 'true' && $str != '') { $columnSearch[] = $column['db']." LIKE '%" . $db->escape($str)."%'"; } } } // Combine the filters into a single string $where = ''; if (count( $globalSearch)) { $where = '('.implode(' OR ', $globalSearch).')'; } if (count( $columnSearch)) { $where = $where === '' ? implode(' AND ', $columnSearch) : $where .' AND '. implode(' AND ', $columnSearch); } if ($where !== '') { $where = 'WHERE '.$where; } return $where; } /** * Perform the SQL queries needed for an server-side processing requested, * utilising the helper functions of this class, limit(), order() and * filter() among others. The returned array is ready to be encoded as JSON * in response to an SSP request, or can be modified if needed before * sending back to the client. * * @param array $request Data sent to server by DataTables * @param string $table SQL table to query * @param string $primaryKey Primary key of the table * @param array $columns Column information array * @return array Server-side processing response array */ static function simple($request, $db, $table, $primaryKey, $columns) { // Build the SQL query string from the request $limit = self::limit( $request, $columns, $db ); $order = self::order( $request, $columns, $db ); $where = self::filter( $request, $columns, $db ); // Main query to actually get the data $sql = "SELECT ".implode(", ", self::pluck($columns, 'db'))." FROM $table $where $order $limit"; $data = self::sql_exec($db, $sql); //print_r($data); // Data set length after filtering $resFilterLength = self::sql_exec($db, "SELECT COUNT({$primaryKey}) FROM $table $where" ); $recordsFiltered = $resFilterLength[0][0]; // Total data set length $resTotalLength = self::sql_exec($db, "SELECT COUNT({$primaryKey}) FROM $table" ); $recordsTotal = $resTotalLength[0][0]; /* * Output */ return array( "draw" => (isset ( $request['draw'] ) ? intval( $request['draw'] ) : 0), "recordsTotal" => intval( $recordsTotal ), "recordsFiltered" => intval( $recordsFiltered ), "data" => self::data_output( $columns, $data ) ); } /** * The difference between this method and the `simple` one, is that you can * apply additional `where` conditions to the SQL queries. These can be in * one of two forms: * * * 'Result condition' - This is applied to the result set, but not the * overall paging information query - i.e. it will not effect the number * of records that a user sees they can have access to. This should be * used when you want apply a filtering condition that the user has sent. * * 'All condition' - This is applied to all queries that are made and * reduces the number of records that the user can access. This should be * used in conditions where you don't want the user to ever have access to * particular records (for example, restricting by a login id). * * @param array $request Data sent to server by DataTables * @param string $table SQL table to query * @param string $primaryKey Primary key of the table * @param array $columns Column information array * @param string $whereResult WHERE condition to apply to the result set * @param string $whereAll WHERE condition to apply to all queries * @return array Server-side processing response array */ static function complex($request, $db, $table, $primaryKey, $columns, $whereResult=null, $whereAll=null) { $whereAllSql = ''; // Build the SQL query string from the request $limit = self::limit( $request, $columns, $db ); $order = self::order( $request, $columns, $db ); $where = self::filter( $request, $columns, $db ); $whereResult = self::_flatten( $whereResult ); $whereAll = self::_flatten( $whereAll ); if (!empty($whereResult)) { $where = $where ? $where .' AND '.$whereResult : 'WHERE '.$whereResult; } if (!empty($whereAll)) { $where = $where ? $where .' AND '.$whereAll : 'WHERE '.$whereAll; $whereAllSql = 'WHERE '.$whereAll; } // Main query to actually get the data $data = self::sql_exec($db, "SELECT ".implode(", ", self::pluck($columns, 'db'))." FROM $table $where $order $limit" ); // Data set length after filtering $resFilterLength = self::sql_exec($db, "SELECT COUNT({$primaryKey}) FROM $table $where" ); $recordsFiltered = $resFilterLength[0][0]; // Total data set length $resTotalLength = self::sql_exec($db, "SELECT COUNT({$primaryKey}) FROM $table ".$whereAllSql ); $recordsTotal = $resTotalLength[0][0]; /* * Output */ return array( "draw" => (isset($request['draw']) ? intval($request['draw']) : 0), "recordsTotal" => intval($recordsTotal), "recordsFiltered" => intval($recordsFiltered), "data" => self::data_output($columns, $data) ); } /** * Execute an SQL query on the database * * @param string $sql SQL query to execute. * @return array Result from the query (all rows) */ static function sql_exec($db, $sql) { $out=array(); $resql = $db->query($sql); if (!empty($resql)) { $i = 0; $num_rows = $db->num_rows($resql); while ($i < $num_rows) { $array = $db->fetch_array($resql); array_push($out, $array); $i++; } } return $out; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Internal methods */ /** * Pull a particular property from each assoc. array in a numeric array, * returning and array of the property values from each item. * * @param array $a Array to get data from * @param string $prop Property to read * @return array Array of property values */ static function pluck($a, $prop) { $out = array(); for ($i=0, $len=count($a); $i<$len; $i++) { $out[] = $a[$i][$prop]; } return $out; } /** * Return a string from an array or a string * * @param array|string $a Array to join * @param string $join Glue for the concatenation * @return string Joined string */ static function _flatten($a, $join = ' AND ') { if (empty($a)) { return ''; } else if (!empty($a) && is_array($a)) { return implode( $join, $a ); } return $a; } }