dragon_fire/www/src/db_handler/mysql_handler.php

347 lines
No EOL
8.8 KiB
PHP

<?php
require_once 'analytics_interface.php';
require_once 'db_interface.php';
function taglist_escape_tag($tag) {
return preg_replace_callback('/[\WZ]/', function($match) {
return "Z" . ord($match[0]);
}, strtolower($tag));
}
function taglist_to_sql_string($post_tags) {
$post_tags = array_unique($post_tags);
$post_tags = array_map(function($val) {
return taglist_escape_tag($val);
}, $post_tags);
asort($post_tags);
$post_tags = join(' ', $post_tags);
return $post_tags;
}
class MySQLHandler
implements PostdataInterface {
CONST SQL_READ_COLUMNS = [
'id', 'path', 'created_at', 'updated_at',
'title', 'view_count', 'brief'];
CONST SQL_WRITE_COLUMNS = ['path', 'title', 'brief'];
private $sql_connection;
public $hostname;
public $debugging;
function __construct($sql_connection, $hostname) {
$this->sql_connection = $sql_connection;
$this->hostname = $hostname;
$this->debugging = false;
}
private function _dbg($message) {
if($this->debugging) {
echo $message;
}
}
private function _exec($qery, $argtypes = '', ...$args) {
$stmt = $this->sql_connection->prepare($qery);
if($argtypes != ""){
$stmt->bind_param($argtypes, ...$args);
}
$stmt->execute();
return $stmt->get_result();
}
private function clear_post_settings_cache($post_path) {
$post_path = sanitize_post_path($post_path);
$this->_exec("
UPDATE posts
SET post_settings_cache=NULL
WHERE host = ? AND post_path LIKE ?;
", "ss", $this->hostname, $post_path . "%");
}
public function stub_postdata($path) {
$post_path = sanitize_post_path($path);
$path_depth = substr_count($post_path, "/");
$qry = "
INSERT INTO posts
(host, post_path, post_path_depth)
VALUES
( ?, ?, ?) AS new
ON DUPLICATE KEY UPDATE post_path=new.post_path;";
$this->_exec($qry, "ssi",
$this->hostname,
$post_path,
$path_depth);
}
public function stub_postdata_tree($path) {
$post_path = sanitize_post_path($path);
while(true) {
if($post_path == '/') {
$post_path = '';
}
try {
$this->stub_postdata($post_path);
}
catch(Exception $e) {
}
$post_path = dirname($post_path);
if(strlen($post_path) == 0) {
break;
}
}
}
public function set_postdata($data) {
$data['path'] = sanitize_post_path($data['path']);
$post_path = $data['path'];
unset($data['path']);
$this->stub_postdata_tree($post_path);
$data['title'] ??= basename($post_path);
$post_tags = $data['tags'] ?? [];
array_push($post_tags,
'path:' . $post_path
);
$sql_args = [
$this->hostname,
$post_path,
substr_count($post_path, "/"),
$data['title'],
taglist_to_sql_string($post_tags),
$data['brief'] ?? null
];
unset($data['title']);
unset($data['brief']);
$post_markdown = $data['markdown'] ?? null;
unset($data['markdown']);
unset($data['html']);
array_push($sql_args, json_encode($data));
$qry =
"INSERT INTO posts
(host,
post_path, post_path_depth,
post_title, post_tags, post_brief,
post_metadata, post_settings_cache)
VALUES
( ?, ?, ?, ?, ?, ?, ?, null) AS new
ON DUPLICATE KEY
UPDATE post_title=new.post_title,
post_tags=new.post_tags,
post_brief=new.post_brief,
post_metadata=new.post_metadata,
post_updated_at=CURRENT_TIMESTAMP;
";
$this->_exec($qry, "ssissss", ...$sql_args);
if(isset($post_markdown)) {
$this->set_post_markdown($this->sql_connection->insert_id, $post_markdown);
}
$this->clear_post_settings_cache($post_path);
}
public function set_post_markdown($id, $markdown) {
$qry =
"INSERT INTO post_markdown ( post_id, post_markdown )
VALUES (?, ?) AS new
ON DUPLICATE KEY UPDATE post_markdown=new.post_markdown;
";
$this->_exec($qry, "is", $id, $markdown);
}
private function get_post_settings($post_path) {
$post_path = sanitize_post_path($post_path);
$this->_dbg("-> gps: getting path " . $post_path . "\n");
$post_settings = $this->_exec("
SELECT post_settings_cache
FROM posts
WHERE post_path = ? AND host = ?
", "ss", $post_path, $this->hostname)->fetch_assoc();
if(!isset($post_settings)) {
$this->_dbg("-> gps: Returning because of no result\n");
return [];
}
if(isset($post_settings['post_settings_cache'])) {
$result = json_decode($post_settings['post_settings_cache'], true);
if($this->debugging) {
echo "-> gps: Returning because of cached result:\n";
echo "--> " . json_encode($result) . "\n";
}
return $result;
}
$parent_settings = [];
if($post_path != "") {
$parent_settings = $this->get_post_settings(dirname($post_path));
}
$post_settings = [];
$post_metadata = $this->_exec("
SELECT post_path, post_metadata
FROM posts
WHERE post_path = ? AND host = ?
", "ss", $post_path, $this->hostname)->fetch_assoc();
if(isset($post_metadata['post_metadata'])) {
$post_metadata = json_decode($post_metadata['post_metadata'], true);
if(isset($post_metadata['settings'])) {
$post_settings = $post_metadata['settings'];
}
}
$post_settings = array_merge($parent_settings, $post_settings);
$this->_dbg("-> gps: Merged post settings are " . json_encode($post_settings) . ", saving...\n");
$this->_exec("
UPDATE posts SET post_settings_cache=? WHERE post_path=? AND host=?
", "sss",
json_encode($post_settings), $post_path, $this->hostname);
return $post_settings;
}
private function process_postdata($data) {
if(!isset($data)) {
return null;
}
if(!isset($data['post_path'])) {
echo "ERROR, trying to get a post data package without path!";
die();
}
$outdata = [];
foreach($this::SQL_READ_COLUMNS as $key) {
if(isset($data['post_' . $key])) {
$outdata[$key] = $data['post_' . $key];
}
}
$post_metadata = json_decode($data['post_metadata'] ?? '{}', true);
$post_settings = [];
if(isset($data['post_settings_cache'])) {
$post_settings = json_decode($data['post_settings_cache'], true);
}
else {
$post_settings = $this->get_post_settings($data['post_path']);
}
$outdata = array_merge($post_settings, $post_metadata, $outdata);
return $outdata;
}
public function get_postdata($path) {
$path = sanitize_post_path($path);
$qry = "
SELECT *
FROM posts
WHERE post_path = ? AND host = ?;
";
$data = $this->_exec($qry, "ss", $path, $this->hostname)->fetch_assoc();
return $this->process_postdata($data);
}
public function get_post_children($path,
$limit = 50, $depth_start = 1, $depth_end = 1,
$order_by = 'path') {
$path = sanitize_post_path($path);
$path_depth = substr_count($path, "/");
$allowed_ordering = [
'path' => true,
'path DESC' => true,
'created_at' => true,
'created_at DESC' => true,
'modified_at' => true,
'modified_at DESC' => true
];
if(!isset($allowed_ordering[$order_by])) {
throw new Exception('Children ordering not allowed');
}
$order_by = 'post_' . $order_by;
if($this->debugging) {
echo "-> GPC: Getting children for path " . $path;
}
$qry = "
SELECT *
FROM posts
WHERE post_path_depth BETWEEN ? AND ?
AND post_path LIKE ?
ORDER BY " . $order_by .
" LIMIT ?";
$data = $this->_exec($qry, "iisi",
$path_depth + $depth_start, $path_depth + $depth_end,
$path.'/%', $limit
)->fetch_all(MYSQLI_ASSOC);
$outdata = [];
foreach($data AS $post_element) {
$outdata[$post_element['post_path']] =
$this->process_postdata($post_element);
}
return $outdata;
}
public function get_post_markdown($id) {
$qry =
"SELECT post_markdown
FROM post_markdown
WHERE post_id = ?
";
$data = $this->_exec($qry, "i", $id)->fetch_assoc();
if(!isset($data)) {
return "";
}
return $data['post_markdown'];
}
}
?>