Some work for SSP replacement

This commit is contained in:
Floorb 2021-08-17 13:09:08 -04:00
parent 7ed9f53573
commit 9ac3a2ab4e
4 changed files with 82 additions and 459 deletions

View file

@ -1,45 +1,55 @@
<?php <?php
require_once('../includes/config.php'); header('Content-Type: application/json; charset=UTF-8');
// DB table to use
$table = 'pastes';
// Table's primary key define('IN_PONEPASTE', 1);
$primaryKey = 'id'; //require_once('../includes/config.php');
require_once('../includes/common.php');
require_once('../includes/NonRetardedSSP.class.php');
// Array of database columns which should be read and sent back to DataTables. function tagsToHtml(string $tags) : string {
// The `db` parameter represents the column name in the database, while the `dt` $output = "";
// parameter represents the DataTables column identifier. In this case simple $tagsSplit = explode(",", $tags);
// indexes foreach ($tagsSplit as $tag) {
$columns = array( if (stripos($tag, 'nsfw') !== false) {
array('db' => 'id', 'dt' => 0), $tag = strtoupper($tag);
array('db' => 'title', 'dt' => 1), $tagcolor = "tag is-danger";
array('db' => 'member', 'dt' => 2), } elseif (stripos($tag, 'SAFE') !== false) {
array('db' => 'tagsys', 'dt' => 3), $tag = strtoupper($tag);
array('db' => 'visible', 'dt' => 4), $tagcolor = "tag is-success";
); } elseif (str_contains($tag, '/')) {
$tagcolor = "tag is-primary";
} else {
$tagcolor = "tag is-info";
}
$output .= '<a href="/archive?q=' . urlencode($tag) . '"><span class="' . $tagcolor . '">' . pp_html_escape(ucfirst($tag)) . '</span></a>';
}
return $output;
}
$columns2 = array(
array('db' => 'title', 'dt' => 0), function transformDataRow($row) {
array('db' => 'member', 'dt' => 1), $titleHtml = '<a href="/' . urlencode($row[0]) . '">' . pp_html_escape($row[1]) . '</a>';
array('db' => 'tagsys', 'dt' => 2), $authorHtml = '<a href="' . urlencode($row[2]) . '">' . pp_html_escape($row[2]) . '</a>';
$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 $data['data'] = array_map('transformDataRow', $data['data']);
$sql_details = array(
'user' => $db_user,
'pass' => $db_pass,
'db' => $db_schema,
'host' => $db_host
);
echo json_encode($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)
);

View file

@ -1,419 +0,0 @@
<?php
// Turn off all error reporting
//error_reporting(0);
?>
<?php
function sandwitch($str) {
$output = "";
$arr = explode(",", $str);
foreach ($arr as $word) {
$word = ucfirst($word);
if (stripos($word, 'nsfw') !== false) {
$word = strtoupper($word);
$tagcolor = "tag is-danger";
} elseif (stripos($word, 'SAFE') !== false) {
$word = strtoupper($word);
$tagcolor = "tag is-success";
} elseif (strstr($word, '/')) {
$tagcolor = "tag is-primary";
} else {
$tagcolor = "tag is-info";
}
$output .= '<a href="/archive?q=' . trim($word) . '"><span class="' . $tagcolor . '">' . trim($word) . '</span></a>';
}
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'] = "<a href=/" . $myid . ">" . $mytitle . "</a>";
$result[$loop]['member'] = "<a href=/user/" . $mymember . ">" . $mymember . "</a>";
$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;
}
}
?>

View file

@ -26,10 +26,10 @@ class DatabaseHandle {
return $stmt; 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); $stmt = $this->query($query, $params);
if ($row = $stmt->fetch()) { if ($row = $stmt->fetch($fetchMode)) {
return $row; return $row;
} }

View file

@ -1,4 +1,36 @@
<?php <?php
class NonRetardedSSP { class NonRetardedSSP {
public static function run(DatabaseHandle $conn, array $request, string $countQuery, string $query) {
/* Some of these parameters might not be passed; zero is an OK default */
$draw = (int) @$request['draw'];
$start = (int) @$request['start'];
$length = (int) @$request['length'];
/* figure out total records */
$recordsTotal = (int) $conn->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
];
}
} }