| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- <?php
- /* Copyright (C) 2017-2021 Regis Houssin <regis.houssin@inodbox.com>
- *
- * 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;
- }
- }
|