From 9ac3a2ab4ed4a1c800d94a68a1b9d7eb922a37e6 Mon Sep 17 00:00:00 2001 From: Floorb <132411956+Neetpone@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:09:08 -0400 Subject: [PATCH] Some work for SSP replacement --- api/ajax_pastes.php | 84 +++--- api/ssp.pastes.php | 419 ------------------------------ includes/DatabaseHandle.class.php | 4 +- includes/NonRetardedSSP.class.php | 34 ++- 4 files changed, 82 insertions(+), 459 deletions(-) delete mode 100644 api/ssp.pastes.php diff --git a/api/ajax_pastes.php b/api/ajax_pastes.php index 48d9dab..83a73c1 100644 --- a/api/ajax_pastes.php +++ b/api/ajax_pastes.php @@ -1,45 +1,55 @@ 'id', 'dt' => 0), - array('db' => 'title', 'dt' => 1), - array('db' => 'member', 'dt' => 2), - array('db' => 'tagsys', 'dt' => 3), - array('db' => 'visible', 'dt' => 4), -); +function tagsToHtml(string $tags) : string { + $output = ""; + $tagsSplit = explode(",", $tags); + foreach ($tagsSplit as $tag) { + if (stripos($tag, 'nsfw') !== false) { + $tag = strtoupper($tag); + $tagcolor = "tag is-danger"; + } elseif (stripos($tag, 'SAFE') !== false) { + $tag = strtoupper($tag); + $tagcolor = "tag is-success"; + } elseif (str_contains($tag, '/')) { + $tagcolor = "tag is-primary"; + } else { + $tagcolor = "tag is-info"; + } + $output .= '' . pp_html_escape(ucfirst($tag)) . ''; + } + return $output; +} -$columns2 = array( - array('db' => 'title', 'dt' => 0), - array('db' => 'member', 'dt' => 1), - array('db' => 'tagsys', 'dt' => 2), + +function transformDataRow($row) { + $titleHtml = '' . pp_html_escape($row[1]) . ''; + $authorHtml = '' . pp_html_escape($row[2]) . ''; + $tagsHtml = tagsToHtml($row[3]); + + return [ + $titleHtml, + $authorHtml, + $tagsHtml + ]; +} + +$data = NonRetardedSSP::run( + $conn, $_GET, + 'SELECT COUNT(*) FROM pastes', + 'SELECT pastes.id AS id, title, users.username, GROUP_CONCAT(tags.name SEPARATOR \',\') AS tagsys FROM pastes + INNER JOIN users ON users.id = pastes.user_id + INNER JOIN paste_taggings on pastes.id = paste_taggings.paste_id + INNER JOIN tags ON tags.id = paste_taggings.tag_id + GROUP BY pastes.id' ); -// SQL server connection information -$sql_details = array( - 'user' => $db_user, - 'pass' => $db_pass, - 'db' => $db_schema, - 'host' => $db_host -); +$data['data'] = array_map('transformDataRow', $data['data']); - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * If you just want to use the basic configuration for DataTables with PHP - * server-side, there is no need to edit below this line. - */ - -require('ssp.pastes.php'); -echo json_encode( - SSP::simple($_GET, $sql_details, $table, $primaryKey, $columns, $columns2) -); +echo json_encode($data); diff --git a/api/ssp.pastes.php b/api/ssp.pastes.php deleted file mode 100644 index b551b0f..0000000 --- a/api/ssp.pastes.php +++ /dev/null @@ -1,419 +0,0 @@ - -' . trim($word) . ''; - } - return $output; -} - - -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) { - $limit = ''; - - if (isset($request['start']) && $request['length'] != -1) { - $limit = "LIMIT " . intval($request['start']) . ", " . intval($request['length']); - } - - 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) { - $order = ''; - - if (isset($request['order']) && count($request['order'])) { - $orderBy = array(); - $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'] === 'DESC' ? - 'ASC' : - 'DESC'; - - $orderBy[] = '`' . $column['db'] . '` ' . $dir; - } - } - - $order = 'ORDER BY ' . implode(', ', $orderBy); - } - - 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, &$bindings) { - $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') { - $binding = self::bind($bindings, '%' . $str . '%', PDO::PARAM_STR); - $globalSearch[] = "`" . $column['db'] . "` LIKE " . $binding; - } - } - } - - // Individual column filtering - 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 != '') { - $binding = self::bind($bindings, '%' . $str . '%', PDO::PARAM_STR); - $columnSearch[] = "`" . $column['db'] . "` LIKE " . $binding; - } - } - - // 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 array $sql_details SQL connection details - see sql_connect() - * @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, $sql_details, $table, $primaryKey, $columns, $columns2) { - $bindings = array(); - $db = self::sql_connect($sql_details); - - // Build the SQL query string from the request - $limit = self::limit($request, $columns); - $order = self::order($request, $columns); - //$where = self::filter( $request, $columns, $bindings ); - - // Main query to actually get the data - $data = self::Ssql_exec($db, $bindings, - "SELECT SQL_CALC_FOUND_ROWS pastes.id AS id, users.username AS member, tagsys, title, visible - FROM `$table` - INNER JOIN users ON users.id = pastes.user_id WHERE visible='0' AND tagsys IS NOT NULL AND NOT title LIKE '' - $order - $limit" - ); - - // Data set length after filtering - $resFilterLength = self::sql_exec($db, - "SELECT FOUND_ROWS()" - ); - $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($columns2, $data) - ); - } - - - /** - * Connect to the database - * - * @param array $sql_details SQL server connection details array, with the - * properties: - * * host - host name - * * db - database name - * * user - user name - * * pass - user password - * @return PDO Database connection handle - */ - static function sql_connect($sql_details) { - try { - $db = @new PDO( - "mysql:host={$sql_details['host']};dbname={$sql_details['db']}", - $sql_details['user'], - $sql_details['pass'], - array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION) - ); - } catch (PDOException $e) { - self::fatal( - "An error occurred while connecting to the database. " . - "The error reported by the server was: " . $e->getMessage() - ); - } - - return $db; - } - - - /** - * Execute an SQL query on the database - * - * @param resource $db Database handler - * @param array $bindings Array of PDO binding values from bind() to be - * used for safely escaping strings. Note that this can be given as the - * SQL query string if no bindings are required. - * @param string $sql SQL query to execute. - * @return array Result from the query (all rows) - */ - static function sql_exec($db, $bindings, $sql = null) { - // Argument shifting - if ($sql === null) { - $sql = $bindings; - } - - $stmt = $db->prepare($sql); - //echo $sql; - - // Bind parameters - if (is_array($bindings)) { - for ($i = 0, $ien = count($bindings); $i < $ien; $i++) { - $binding = $bindings[$i]; - $stmt->bindValue($binding['key'], $binding['val'], $binding['type']); - } - } - - // Execute - try { - $stmt->execute(); - } catch (PDOException $e) { - self::fatal("An SQL error occurred: " . $e->getMessage()); - } - return $stmt->fetchAll(); - } - - static function Ssql_exec($db, $bindings, $sql = null) { - // Argument shifting - if ($sql === null) { - $sql = $bindings; - } - - $stmt = $db->prepare($sql); - - // Bind parameters - if (is_array($bindings)) { - for ($i = 0, $ien = count($bindings); $i < $ien; $i++) { - $binding = $bindings[$i]; - $stmt->bindValue($binding['key'], $binding['val'], $binding['type']); - } - } - - // Execute - try { - $stmt->execute(); - } catch (PDOException $e) { - self::fatal("An SQL error occurred: " . $e->getMessage()); - } - $loop = '0'; - - - while ($arr = $stmt->fetch(PDO::FETCH_ASSOC)) { - $result[$loop]['id'] = $arr['id']; - $result[$loop]['member'] = $arr['member']; - $result[$loop]['tagsys'] = sandwitch($arr['tagsys']); - $date_time = strtotime($arr['date'] ?? '0'); - $result[$loop]['date'] = date("d F Y", $date_time); - $myid = $arr['id']; - $mytitle = $arr['title']; - $mymember = $arr['member']; - $result[$loop]['title'] = "" . $mytitle . ""; - $result[$loop]['member'] = "" . $mymember . ""; - $loop = $loop + 1; - } - - // Return all - return $result; - } - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Internal methods - */ - - /** - * Throw a fatal error. - * - * This writes out an error message in a JSON string which DataTables will - * see and show to the user in the browser. - * - * @param string $msg Message to send to the client - */ - static function fatal($msg) { - echo json_encode(array( - "error" => $msg - )); - - exit(0); - } - - /** - * Create a PDO binding key which can be used for escaping variables safely - * when executing a query with sql_exec() - * - * @param array &$a Array of bindings - * @param * $val Value to bind - * @param int $type PDO field type - * @return string Bound key to be used in the SQL where this parameter - * would be used. - */ - static function bind(&$a, $val, $type) { - $key = ':binding_' . count($a); - - $a[] = array( - 'key' => $key, - 'val' => $val, - 'type' => $type - ); - - return $key; - } - - - /** - * 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; - } -} - -?> \ No newline at end of file diff --git a/includes/DatabaseHandle.class.php b/includes/DatabaseHandle.class.php index 8a552e8..35d86c8 100644 --- a/includes/DatabaseHandle.class.php +++ b/includes/DatabaseHandle.class.php @@ -26,10 +26,10 @@ class DatabaseHandle { return $stmt; } - public function querySelectOne(string $query, array $params = null) : array|null { + public function querySelectOne(string $query, array $params = null, int $fetchMode = PDO::FETCH_ASSOC) : array|null { $stmt = $this->query($query, $params); - if ($row = $stmt->fetch()) { + if ($row = $stmt->fetch($fetchMode)) { return $row; } diff --git a/includes/NonRetardedSSP.class.php b/includes/NonRetardedSSP.class.php index 4506405..96ef246 100644 --- a/includes/NonRetardedSSP.class.php +++ b/includes/NonRetardedSSP.class.php @@ -1,4 +1,36 @@ querySelectOne($countQuery, [], PDO::FETCH_NUM)[0]; + + /* build query */ + $params = []; + + if ($length != 0) { + $query .= ' LIMIT ?'; + array_push($params, $length); + + if ($start != 0) { + $query .= ' OFFSET ?'; + array_push($params, $start); + } + } + + /* fire it off */ + $stmt = $conn->query($query, $params); + $data = $stmt->fetchAll(PDO::FETCH_NUM); + + return [ + 'draw' => $draw, + 'recordsTotal' => $recordsTotal, + 'recordsFiltered' => ($recordsTotal - count($data)), + 'data' => $data + ]; + } +}