Функция поиска по постам
Мне это всё расписывать что-ли? Смотрите в содержание коммита, мне феерически индифферентно
This commit is contained in:
parent
8700a544b9
commit
62b7b68976
13
TODO.md
13
TODO.md
@ -5,7 +5,7 @@
|
||||
- Детальная стата по инстансу
|
||||
- Демонстрация наполнения и управление БД
|
||||
- "Большая Красная Кнопка"
|
||||
- Общая статистика по инстансу
|
||||
- ~~Общая статистика по инстансу~~
|
||||
- Главная страница
|
||||
- Страница регистрации
|
||||
- Страница с отображением поста-картинки
|
||||
@ -48,10 +48,10 @@
|
||||
- Приглашения
|
||||
- Регистрация по приглашению автоматически даёт роль "проверенный"
|
||||
- Пост с картинкой
|
||||
- Рекодирование пикчи в низкое разрешение для превью
|
||||
- Описание
|
||||
- Теги
|
||||
- Добавление нового
|
||||
- ~~Рекодирование пикчи в низкое разрешение для превью~~
|
||||
- ~~Описание~~
|
||||
- ~~Теги~~
|
||||
- ~~Добавление нового~~
|
||||
- Редактирование тегов существующего
|
||||
- Удаление
|
||||
- Оценки
|
||||
@ -59,7 +59,8 @@
|
||||
- Статистика по всем картинкам
|
||||
- Комментарии
|
||||
- Теги
|
||||
- Перечень одобренных
|
||||
- ~~Перечень одобренных~~
|
||||
- Добавление, редактирование и удаление одобренных
|
||||
- Шаблонная разметка
|
||||
- Локализация
|
||||
- Кастомизация внешнего вида
|
||||
|
@ -30,6 +30,7 @@ const E_AUT_WRONGCREDS = 305; // User with that credentials does not exist
|
||||
const E_ACS_PERMDENIED = 401; // Permission to object denied
|
||||
const E_ACS_INSUFROLE = 402; // Insufficient role
|
||||
// Database-related errors
|
||||
const E_DBE_UNKNOWN = 500; // Unknown error
|
||||
const E_DBE_INSERTFAIL = 501; // INSERT query failed
|
||||
const E_DBE_SELECTFAIL = 502; // SELECT query failed
|
||||
const E_DBE_DELETEFAIL = 503; // DELETE query failed
|
||||
@ -64,6 +65,7 @@ $Errors_Enum = array(
|
||||
array("acs.permdenied", E_ACS_PERMDENIED, "permission denied"),
|
||||
array("acs.insufrole", E_ACS_INSUFROLE, "insufficient role"),
|
||||
// Database-related errors
|
||||
array("dbe.unknown", E_DBE_UNKNOWN, "unknown database error"),
|
||||
array("dbe.insertfail", E_DBE_INSERTFAIL, "insert query failed"),
|
||||
array("dbe.selectfail", E_DBE_SELECTFAIL, "select query failed"),
|
||||
array("dbe.deletefail", E_DBE_DELETEFAIL, "delete query failed")
|
||||
|
27
api/_input_checks.php
Normal file
27
api/_input_checks.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// Functions for common input checks
|
||||
|
||||
|
||||
|
||||
// Check 32 bit integer
|
||||
function InpChk_IsValidInt32 (&$value): bool {
|
||||
if (is_null($value))
|
||||
return false;
|
||||
|
||||
if (is_string($value)) {
|
||||
if (strlen($value) > 24)
|
||||
return false;
|
||||
if (!ctype_digit($value))
|
||||
return false;
|
||||
$value = intval($value);
|
||||
}
|
||||
|
||||
if ($value > 0xffffffff || $value < -(0xffffffff))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
@ -4,20 +4,19 @@
|
||||
|
||||
|
||||
// Check if request was to specified file
|
||||
function Utils_ThisFileIsRequested ($fullpath): bool {
|
||||
return substr($fullpath, -strlen($_SERVER["SCRIPT_NAME"])) === $_SERVER["SCRIPT_NAME"];
|
||||
function Utils_ThisFileIsRequested (string $fullpath): bool {
|
||||
return (substr($fullpath, -strlen($_SERVER["SCRIPT_NAME"])) === $_SERVER["SCRIPT_NAME"])
|
||||
|| ($fullpath === $_SERVER["SCRIPT_NAME"]); // Old variant won't work on some configurations, as reported by doesnm
|
||||
}
|
||||
|
||||
// Generate secure random string
|
||||
function Utils_GenerateRandomString (int $length, string $keyspace = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"): string {
|
||||
if ($length < 1) {
|
||||
if ($length < 1)
|
||||
die("cant generate random string of size less than 1");
|
||||
}
|
||||
$pieces = [];
|
||||
$max = mb_strlen($keyspace, "8bit") - 1;
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
for ($i = 0; $i < $length; ++$i)
|
||||
$pieces []= $keyspace[random_int(0, $max)];
|
||||
}
|
||||
return implode("", $pieces);
|
||||
}
|
||||
|
||||
@ -32,7 +31,8 @@ function Utils_GetRatio ($x, $y) {
|
||||
function Utils_JoinPaths () {
|
||||
$paths = array();
|
||||
foreach (func_get_args() as $arg) {
|
||||
if ($arg !== '') { $paths[] = $arg; }
|
||||
if ($arg !== "")
|
||||
$paths[] = $arg;
|
||||
}
|
||||
return preg_replace('#/+#', '/', join('/', $paths));
|
||||
}
|
||||
|
@ -45,13 +45,14 @@ function Comments_GetSectionRange (int $sec_id, int $ts_from = 0, int $ts_to = 0
|
||||
$result = array();
|
||||
|
||||
$s = $db->prepare("SELECT * FROM comments WHERE comment_section_id=? AND created_at>=? AND created_at<=? ORDER BY created_at");
|
||||
$s->bind_param("sss", $sec_id, date("Y-m-d H:i:s", $ts_from), date("Y-m-d H:i:s", $ts_to));
|
||||
$s->bind_param("iss", $sec_id, date("Y-m-d H:i:s", $ts_from), date("Y-m-d H:i:s", $ts_to));
|
||||
$s->execute();
|
||||
$d = $s->get_result();
|
||||
|
||||
if (!(bool)$d)
|
||||
return new ReturnT(data: $result);
|
||||
|
||||
// TODO: move this check to method
|
||||
$isAdmin = false;
|
||||
if ($LOGGED_IN && User_HasRole($THIS_USER, "admin")->GetData())
|
||||
$isAdmin = true;
|
||||
|
@ -45,7 +45,7 @@ function Post_ParseRawTagString (string $str): ReturnT {
|
||||
$currLen = 0;
|
||||
$currTag = "";
|
||||
} else {
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "syntax error while trying to parse tags");
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "syntax error at index $i while trying to parse tags");
|
||||
}
|
||||
} elseif (!IntlChar::isspace($str[$i])) {
|
||||
$currTag .= $str[$i];
|
||||
@ -70,7 +70,7 @@ function Post_ParseRawTagString (string $str): ReturnT {
|
||||
* FUNCTION
|
||||
* Check if image size properties are valid
|
||||
*/
|
||||
function Post_ImgResIsValid ($x, $y): bool {
|
||||
function Post_ImgResIsValid (int $x, int $y): bool {
|
||||
global $Config;
|
||||
|
||||
return ($x <= $Config["media"]["max_pic_res"]["x"])
|
||||
@ -179,10 +179,6 @@ function Post_Create (
|
||||
|
||||
$result = null;
|
||||
|
||||
// Author ID must exist
|
||||
if (!User_IDExist($author_id))
|
||||
return new ReturnT(err_code: E_UIN_WRONGID, err_desc: "specified user id does not exist");
|
||||
|
||||
// Performing SQL query
|
||||
$s = $db->prepare("INSERT INTO posts (author_id,tags,title,pic_path,preview_path,comments_enabled,edit_lock) VALUES (?,?,?,?,?,?,?)");
|
||||
$s->bind_param("issssii", $author_id, $tags, $title, $pic_path, $prev_path, $comms_enabled, $edit_lock);
|
||||
@ -202,6 +198,11 @@ function Post_Create (
|
||||
/*
|
||||
* METHOD
|
||||
* Create single publication
|
||||
* Request fields:
|
||||
* tags - list of tags, should be delimited by comma
|
||||
* title - optional title for post
|
||||
* Files fields:
|
||||
* pic - id of file object in $_FILES variable
|
||||
*/
|
||||
function Post_Create_Method (array $req, array $files): ReturnT {
|
||||
global $Config, $LOGGED_IN, $THIS_USER;
|
||||
@ -257,7 +258,7 @@ function Post_Create_Method (array $req, array $files): ReturnT {
|
||||
$realTitleLen = strlen($req["title"]);
|
||||
if ($realTitleLen > $maxTitleLen)
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "title length exceeds maximum value");
|
||||
// Cleaning off all bad symbols (no script injection allowed here)
|
||||
// Cleaning off all bad symbols (no script injection allowed here) TODO: move to function
|
||||
for ($i = 0; $i < $realTitleLen; ++$i) {
|
||||
switch ($req["title"][$i]) {
|
||||
case "<":
|
||||
|
305
api/post/find.php
Normal file
305
api/post/find.php
Normal file
@ -0,0 +1,305 @@
|
||||
<?php
|
||||
// Search posts
|
||||
|
||||
|
||||
|
||||
// Includes
|
||||
if (isset($IS_FRONTEND) && $IS_FRONTEND) {
|
||||
require_once("api/_auth.php");
|
||||
require_once("api/_utils.php");
|
||||
require_once("api/_input_checks.php");
|
||||
require_once("api/_errorslist.php");
|
||||
require_once("api/_types.php");
|
||||
require_once("api/user/index.php");
|
||||
require_once("api/post/index.php");
|
||||
} else {
|
||||
require_once("../_auth.php");
|
||||
require_once("../_utils.php");
|
||||
require_once("../_input_checks.php");
|
||||
require_once("../_errorslist.php");
|
||||
require_once("../_types.php");
|
||||
require_once("../user/index.php");
|
||||
require_once("./index.php");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Functions
|
||||
|
||||
/*
|
||||
* FUNCTION
|
||||
* Get list of posts from range. Almost no checks of arguments are performed, so beware
|
||||
* Arguments:
|
||||
* offset - offset from start
|
||||
* amount - amount of posts to return
|
||||
*/
|
||||
function Post_GetPostsFromRange (?int $offset = null, ?int $amount = null): ReturnT {
|
||||
global $db, $Config;
|
||||
|
||||
$result = array();
|
||||
|
||||
// Managing defaults
|
||||
if (is_null($offset))
|
||||
$offset = 0;
|
||||
if (empty($amount))
|
||||
$amount = $Config["max_posts_per_request"];
|
||||
|
||||
// Get posts from db in range
|
||||
$statement = $db->prepare("SELECT * FROM posts LIMIT ?, ?");
|
||||
$statement->bind_param("ii", $offset, $amount);
|
||||
$statement->execute();
|
||||
if (($queryResult = $statement->get_result()) === false)
|
||||
return new ReturnT(err_code: E_DBE_UNKNOWN);
|
||||
|
||||
$result = $queryResult->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
return new ReturnT(data: $result);
|
||||
}
|
||||
|
||||
/*
|
||||
* FUNCTION
|
||||
* Get list of posts matching criteria. No additional checks of arguments are performed
|
||||
* Arguments:
|
||||
* tags - must be array of valid tags or null
|
||||
* author_ids - must be array of valid author ids or null
|
||||
* ts_after - valid starting timestamp for filtering by time, that less or equal to ts_before, or null
|
||||
* ts_before - valid ending timestamp for filtering by time, that bigger or equal to ts_after, or null
|
||||
*/
|
||||
function Post_GetMatchingPosts (
|
||||
?array $tags = null,
|
||||
?array $author_ids = null,
|
||||
?int $ts_after = null,
|
||||
?int $ts_before = null
|
||||
): ReturnT {
|
||||
global $db;
|
||||
|
||||
$result = array();
|
||||
|
||||
// Managing defaults
|
||||
if (is_null($ts_after))
|
||||
$ts_after = 0;
|
||||
if (is_null($ts_before))
|
||||
$ts_before = 0xffffffff;
|
||||
|
||||
$dateFrom = date("Y-m-d H:i:s", $ts_after);
|
||||
$dateTo = date("Y-m-d H:i:s", $ts_before);
|
||||
|
||||
// Get posts from db in time range
|
||||
$s = $db->prepare("SELECT * FROM posts WHERE created_at>=? AND created_at<=?");
|
||||
$s->bind_param("ss", $dateFrom, $dateTo);
|
||||
$s->execute();
|
||||
$d = $s->get_result();
|
||||
|
||||
// Filter them out
|
||||
// NOTICE: ~~skill~~ perf issue, will wildly affect response time and memory usage on big sets
|
||||
|
||||
// Filter by author, if needed
|
||||
$needToFilterByAuthor = !empty($author_ids);
|
||||
$tempFilteredByAuthor = array();
|
||||
// If post author is any author from list - we take it
|
||||
while ($row = $d->fetch_array()) {
|
||||
if (!$needToFilterByAuthor || ($needToFilterByAuthor && in_array($row["author_id"], $author_ids)))
|
||||
$tempFilteredByAuthor[] = array( // NOTICE: this should look better
|
||||
"id" => $row["id"],
|
||||
"author_id" => $row["author_id"],
|
||||
"comment_section_id" => $row["comment_section_id"],
|
||||
"created_at" => $row["created_at"],
|
||||
"tags" => $row["tags"],
|
||||
"title" => $row["title"],
|
||||
"votes_up" => $row["votes_up"],
|
||||
"votes_down" => $row["votes_down"],
|
||||
"views" => $row["views"],
|
||||
"pic_path" => $row["pic_path"],
|
||||
"preview_path" => $row["preview_path"],
|
||||
"comments_enabled" => $row["comments_enabled"],
|
||||
"edit_lock" => $row["edit_lock"]
|
||||
);
|
||||
}
|
||||
if (!count($tempFilteredByAuthor))
|
||||
return new ReturnT(data: $result);
|
||||
|
||||
// Filter by tags
|
||||
// If post has all of the tags from list - we take it
|
||||
foreach ($tempFilteredByAuthor as $post) {
|
||||
$fitsFilter = true;
|
||||
foreach ($tags as $singleTag) {
|
||||
if (!str_contains($post["tags"], $singleTag)) {
|
||||
$fitsFilter = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($fitsFilter)
|
||||
$result[] = $post;
|
||||
}
|
||||
|
||||
// Return result
|
||||
return new ReturnT(data: $result);
|
||||
}
|
||||
|
||||
/*
|
||||
* FUNCTION
|
||||
* Parse raw query to list of tags and author IDs. Checks on encoding are not performed
|
||||
* Arguments:
|
||||
* query - ASCII query string
|
||||
*/
|
||||
function Post_ParseRawQuery (string $query): ReturnT {
|
||||
global $Config;
|
||||
|
||||
$result = array(
|
||||
"tags" => array(),
|
||||
"author_ids" => array()
|
||||
);
|
||||
|
||||
$allowedTagSymbols = $Config["posting"]["tags"]["allowed_syms"];
|
||||
$badTagSymbolsPreg = "/[^" . $allowedTagSymbols . "]/";
|
||||
$allowedLoginSymbols = $Config["registration"]["allowed_syms"];
|
||||
$badLoginSymbolsPreg = "/[^" . $allowedLoginSymbols . "]/";
|
||||
|
||||
$maxTagLength = $Config["posting"]["tags"]["max_single_length"];
|
||||
$queryLength = strlen($query);
|
||||
|
||||
$currWord = "";
|
||||
$currWordLen = 0;
|
||||
$isAuthor = false;
|
||||
|
||||
// Parse everything
|
||||
for ($i = 0; $i <= $queryLength; ++$i) {
|
||||
if ($i === $queryLength || $query[$i] === ",") { // If end of query or comma
|
||||
// NOTICE: potential fix ` || (IntlChar::isspace($query[$i]) && $isAuthor)`
|
||||
// NOTICE: currently, query tags are separated by comma, but may be i should make it by space
|
||||
if ($currWordLen > 0) { // If we have some word
|
||||
if ($isAuthor) { // If word is author meta-field
|
||||
$isAuthor = false;
|
||||
if (preg_match($badLoginSymbolsPreg, $currWord)) // Unallowed symbols in login are detected
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "only allowed symbols in logins are \"$allowedLoginSymbols\"");
|
||||
$userIDRet = User_GetIDByLogin($currWord); // Fetching user ID by login
|
||||
if ($userIDRet->IsError())
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "user $currWord does not exist");
|
||||
else
|
||||
$result["author_ids"][] = $userIDRet->GetData();
|
||||
} else { // If word is tag
|
||||
$result["tags"][] = $currWord;
|
||||
}
|
||||
// Reset current word
|
||||
$currWordLen = 0;
|
||||
$currWord = "";
|
||||
} else { // If malformed query
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "syntax error at index $i: starting/ending comma, sequence of commas or missing meta-field value");
|
||||
}
|
||||
} elseif ($query[$i] === ":") { // Semicolon means this is meta-field
|
||||
if (strtolower($currWord) === "author") { // If meta-field is author
|
||||
$isAuthor = true; // Set author meta-field flag
|
||||
// Reset word
|
||||
$currWordLen = 0;
|
||||
$currWord = "";
|
||||
} else { // Invalid metafield
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "syntax error at index $i: invalid meta-field name \"$currWord\"");
|
||||
}
|
||||
} elseif (!preg_match($badTagSymbolsPreg, $query[$i]) || $isAuthor) { // If any valid non-special symbol OR we parsing login now
|
||||
$currWord .= $query[$i];
|
||||
if (++$currWordLen > $maxTagLength)
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "word too large: $currWord");
|
||||
} elseif (!IntlChar::isspace($query[$i])) { // If we have something that is not whitespace
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "unexpected symbol at index $i: " . $query[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReturnT(data: $result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Methods
|
||||
|
||||
/*
|
||||
* METHOD
|
||||
* Returns list of posts from supplied range based on supplied raw filter parameters
|
||||
* Request fields:
|
||||
* query - raw query string
|
||||
* offset - beginning of posts range
|
||||
* amount - number of posts to get (optional)
|
||||
*/
|
||||
function Post_GetMatchingPosts_Method (array $req): ReturnT {
|
||||
global $Config;
|
||||
|
||||
$cfgMaxPostsPerRequest = $Config["max_posts_per_request"];
|
||||
|
||||
$reqQuery = null;
|
||||
$reqOffset = null;
|
||||
$reqAmount = null;
|
||||
// TODO: filter by time range
|
||||
|
||||
// Input sanity checks
|
||||
|
||||
// Generic checks
|
||||
if (isset($req["offset"])) {
|
||||
$reqOffset = $req["offset"];
|
||||
if (!InpChk_IsValidInt32($reqOffset))
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "invalid offset value: $reqOffset");
|
||||
} else {
|
||||
$reqOffset = 0;
|
||||
}
|
||||
if (isset($req["amount"])) {
|
||||
$reqAmount = $req["amount"];
|
||||
if (!InpChk_IsValidInt32($reqAmount))
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "invalid amount value: $reqAmount");
|
||||
} else {
|
||||
$reqAmount = $cfgMaxPostsPerRequest; // TODO: account defaults
|
||||
}
|
||||
|
||||
// Specific checks
|
||||
if ($reqOffset < 0)
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "offset must be zero or bigger");
|
||||
if ($reqAmount < 1)
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "posts amount must be bigger than 1");
|
||||
if ($reqAmount > $cfgMaxPostsPerRequest)
|
||||
$reqAmount = $cfgMaxPostsPerRequest;
|
||||
|
||||
// Generic check again
|
||||
if (!isset($req["query"])) {
|
||||
$result = Post_GetPostsFromRange($reqOffset, $reqAmount);
|
||||
if ($result->IsError())
|
||||
return $result;
|
||||
$resData = $result->GetData();
|
||||
return new ReturnT(data: array( // Just return posts from range, without filtering
|
||||
"data" => $resData,
|
||||
"total_amount" => count($resData)
|
||||
));
|
||||
}
|
||||
$reqQuery = $req["query"];
|
||||
|
||||
// Check query and parse it to array
|
||||
if (!Utils_IsAscii($reqQuery))
|
||||
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "query must be ASCII string");
|
||||
$qr = Post_ParseRawQuery($reqQuery);
|
||||
if ($qr->IsError())
|
||||
return $qr;
|
||||
$query = $qr->GetData();
|
||||
|
||||
// Actions
|
||||
|
||||
// NOTICE: perf issue
|
||||
$result = Post_GetMatchingPosts($query["tags"], $query["author_ids"]);
|
||||
if ($result->IsError())
|
||||
return $result;
|
||||
$resData = $result->GetData();
|
||||
return new ReturnT(data: array(
|
||||
"data" => array_slice($resData, $reqOffset, $reqAmount),
|
||||
"total_amount" => count($resData) // NOTICE: very shitty design
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (Utils_ThisFileIsRequested(__FILE__)) {
|
||||
require_once("../_json.php");
|
||||
|
||||
$result = Post_GetMatchingPosts_Method($_REQUEST);
|
||||
|
||||
if ($result->IsError())
|
||||
$result->ThrowJSONError();
|
||||
else
|
||||
JSON_ReturnData($result->GetData());
|
||||
}
|
||||
|
||||
?>
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
// Includes
|
||||
if ($IS_FRONTEND) {
|
||||
if (isset($IS_FRONTEND) && $IS_FRONTEND) {
|
||||
require_once("api/_auth.php");
|
||||
require_once("api/_utils.php");
|
||||
require_once("api/_errorslist.php");
|
||||
@ -61,7 +61,7 @@ function Post_GetByID (int $id): ReturnT {
|
||||
$result = array();
|
||||
|
||||
$s = $db->prepare("SELECT * FROM posts WHERE id = ?");
|
||||
$s->bind_param("s", $id);
|
||||
$s->bind_param("i", $id);
|
||||
$s->execute();
|
||||
$d = $s->get_result()->fetch_assoc();
|
||||
|
||||
@ -97,7 +97,7 @@ function Post_GetByID (int $id): ReturnT {
|
||||
* METHOD
|
||||
* Get post information by ID
|
||||
*/
|
||||
function Post_GetByID_Method (array $req) {
|
||||
function Post_GetByID_Method (array $req): ReturnT {
|
||||
// Input sanity checks
|
||||
|
||||
$PostID = null;
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
// Includes
|
||||
if ($IS_FRONTEND) {
|
||||
if (isset($IS_FRONTEND) && $IS_FRONTEND) {
|
||||
require_once("api/_auth.php");
|
||||
require_once("api/_utils.php");
|
||||
require_once("api/_errorslist.php");
|
||||
@ -109,7 +109,25 @@ function User_IsMod (int $id): ReturnT {
|
||||
|
||||
/*
|
||||
* FUNCTION
|
||||
* Get user information from DB
|
||||
* Get user ID by login
|
||||
*/
|
||||
function User_GetIDByLogin (string $login): ReturnT {
|
||||
global $db;
|
||||
|
||||
$s = $db->prepare("SELECT * FROM users WHERE login = ?");
|
||||
$s->bind_param("s", $login);
|
||||
$s->execute();
|
||||
$d = $s->get_result()->fetch_assoc();
|
||||
|
||||
if (!(bool)$d)
|
||||
return new ReturnT(err_code: E_UIN_WRONGID, err_desc: "user not found in database");
|
||||
|
||||
return new ReturnT(data: $d["id"]);
|
||||
}
|
||||
|
||||
/*
|
||||
* FUNCTION
|
||||
* Get user information from DB by supplied ID
|
||||
*/
|
||||
function User_GetInfoByID (int $id): ReturnT {
|
||||
global $db, $THIS_USER, $LOGGED_IN;
|
||||
@ -117,7 +135,7 @@ function User_GetInfoByID (int $id): ReturnT {
|
||||
$result = array();
|
||||
|
||||
$s = $db->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$s->bind_param("s", $id);
|
||||
$s->bind_param("i", $id);
|
||||
$s->execute();
|
||||
$d = $s->get_result()->fetch_assoc();
|
||||
|
||||
@ -151,6 +169,8 @@ function User_GetInfoByID (int $id): ReturnT {
|
||||
/*
|
||||
* METHOD
|
||||
* Get user information from DB
|
||||
* Request fields:
|
||||
* id - user id
|
||||
*/
|
||||
function User_GetInfoByID_Method (array $req): ReturnT {
|
||||
global $THIS_USER, $LOGGED_IN;
|
||||
|
@ -46,5 +46,6 @@
|
||||
"title": {
|
||||
"max_length": 4096
|
||||
}
|
||||
}
|
||||
},
|
||||
"max_posts_per_request": 120
|
||||
}
|
@ -15,6 +15,9 @@ function NTFY_AddNotice (string $text, string $type = "fail") {
|
||||
case "fail":
|
||||
$NTFY_NoticesQueue[] = "<div class=\"notification_fail\"><p>$text</p></div>";
|
||||
break;
|
||||
case "warning":
|
||||
$NTFY_NoticesQueue[] = "<div class=\"notification_warning\"><p>$text</p></div>";
|
||||
break;
|
||||
case "success":
|
||||
$NTFY_NoticesQueue[] = "<div class=\"notification_success\"><p>$text</p></div>";
|
||||
break;
|
||||
|
@ -17,16 +17,17 @@ require_once("api/post/index.php");
|
||||
|
||||
$totalPostsAmount = Post_GetPostsAmount();
|
||||
$totalPostsAmount = strval($totalPostsAmount);
|
||||
$totalPostsAmountLen = strlen($totalPostsAmount);
|
||||
|
||||
?>
|
||||
<div class="visualbox">
|
||||
<?php
|
||||
// India stronk 🇮🇳💪
|
||||
$allNumbers = array();
|
||||
for ($i = 0; $i < strlen($totalPostsAmount); ++$i)
|
||||
$allNumbers[] = "<img src=\"front/images/counter/" . $totalPostsAmount[$i] . ".png\">";
|
||||
while (count($allNumbers) < 7)
|
||||
while (count($allNumbers) < (7 - $totalPostsAmountLen))
|
||||
$allNumbers[] = "<img src=\"front/images/counter/0.png\">";
|
||||
for ($i = 0; $i < $totalPostsAmountLen; ++$i)
|
||||
$allNumbers[] = "<img src=\"front/images/counter/" . $totalPostsAmount[$i] . ".png\">";
|
||||
foreach ($allNumbers as $numberImg)
|
||||
echo $numberImg;
|
||||
?>
|
||||
|
@ -2,5 +2,5 @@
|
||||
// TODO: picking random meme
|
||||
?>
|
||||
<div class="visualbox">
|
||||
<img style="max-width: 90%; max-height: 240px;" src="test.png">
|
||||
<img src="test.png">
|
||||
</div>
|
||||
|
@ -47,7 +47,7 @@ require_once("api/user/index.php");
|
||||
<div>
|
||||
<form action="." accept-charset="UTF-8" method="get">
|
||||
<input type="hidden" name="do" value="search_posts">
|
||||
<input type="text" name="tags" id="tags" value="" size="36" autofocus="autofocus" autocomplete="on"><br>
|
||||
<input type="text" name="query" value="" size="36" autofocus="autofocus" autocomplete="on"><br>
|
||||
<input type="submit" value="Search">
|
||||
<!-- TODO: JS
|
||||
<input type="button" value="Show random meme" id="random-meme">
|
||||
|
@ -44,7 +44,7 @@
|
||||
</li>
|
||||
<span>|</span>
|
||||
<li>
|
||||
<p><a title="A paginated list of every tag" href="./?do=view_tags">Tags</p></a>
|
||||
<p><a title="A (not yet) paginated list of every tag" href="./?do=view_tags">Tags</p></a>
|
||||
</li>
|
||||
<span>|</span>
|
||||
<li>
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
// Includes
|
||||
require_once("api/post/create.php");
|
||||
// Markup includes
|
||||
require_once("front/pages/main_nav.php");
|
||||
require_once("front/notifications.php");
|
||||
|
||||
@ -52,7 +53,7 @@ NTFY_EchoAllNotices();
|
||||
</div>
|
||||
<div>
|
||||
<label for="title">Post title:</label><br>
|
||||
<textarea placeholder="Lorem ipsum dolor sit amet..." name="title" id="title" style="width: 98%;" rows="2"><?php if (isset($_POST["title"]) && !$_POST["title"]) { echo $_POST["title"]; } ?></textarea>
|
||||
<textarea placeholder="Lorem ipsum dolor sit amet..." name="title" id="title" style="width: 98%;" rows="2"><?php if (isset($_POST["title"]) && $_POST["title"]) { echo $_POST["title"]; } ?></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit">
|
||||
|
58
front/pages/search_posts/gen_post_entry.php
Normal file
58
front/pages/search_posts/gen_post_entry.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// Create entry in posts search list
|
||||
|
||||
|
||||
|
||||
// Includes
|
||||
require_once("api/user/index.php");
|
||||
|
||||
|
||||
|
||||
// Create entry from post info
|
||||
function GenPostEntry (array $data): string {
|
||||
$userReqResp = User_GetInfoByID($data["author_id"]);
|
||||
if ($userReqResp->IsError())
|
||||
$userLogin = strval($data["author_id"]);
|
||||
else
|
||||
$userLogin = $userReqResp->GetData()["login"];
|
||||
|
||||
if (!is_string($data["created_at"]))
|
||||
$timestamp = strval($data["created_at"]);
|
||||
else
|
||||
$timestamp = $data["created_at"];
|
||||
|
||||
$placeholderString = "ID: " . strval($data["id"]);
|
||||
$placeholderString .= " | AUTHOR: $userLogin";
|
||||
$placeholderString .= " | TIMESTAMP: $timestamp";
|
||||
$placeholderString .= " | TAGS: " . $data["tags"];
|
||||
|
||||
|
||||
$result = "<a class=\"entry\">\n"; // TODO: href
|
||||
|
||||
$result .= "<img src=\"";
|
||||
if ($data["preview_path"])
|
||||
$result .= $data["preview_path"];
|
||||
else
|
||||
$result .= $data["pic_path"];
|
||||
$result .= "\" alt=\"$placeholderString\" title=\"$placeholderString\">\n";
|
||||
|
||||
$result .= "<div class=\"stats\">\n";
|
||||
|
||||
$result .= "<p><b>+</b>" . strval($data["votes_up"]);
|
||||
$result .= " <b>-</b>" . strval($data["votes_down"]);
|
||||
$result .= " <b>V</b> " . strval($data["views"]);
|
||||
/*
|
||||
if ($data["comments_enabled"]) {
|
||||
$result .= " <b>C</b> " . null; // TODO: resolve comment section by id and count comments amount
|
||||
}
|
||||
*/
|
||||
$result .= "</p>\n";
|
||||
|
||||
$result .= "</div>\n";
|
||||
|
||||
$result .= "</a>\n";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
?>
|
180
front/pages/search_posts/page.php
Normal file
180
front/pages/search_posts/page.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
// Search posts by filter
|
||||
|
||||
|
||||
|
||||
// Includes
|
||||
// API
|
||||
require_once("api/_input_checks.php");
|
||||
require_once("api/post/find.php");
|
||||
// Front pieces
|
||||
require_once("front/pages/search_posts/gen_post_entry.php");
|
||||
// Markup includes
|
||||
require_once("front/pages/main_nav.php");
|
||||
require_once("front/notifications.php");
|
||||
|
||||
|
||||
|
||||
// Needed config values
|
||||
$cfgMaxPostsPerRequest = $Config["max_posts_per_request"];
|
||||
|
||||
|
||||
|
||||
// Checking request fields
|
||||
// Checking amount of posts per page
|
||||
$postsPerPage = null;
|
||||
if (isset($_REQUEST["amount"])) {
|
||||
$postsPerPage = $_REQUEST["amount"];
|
||||
if (
|
||||
(!InpChk_IsValidInt32($postsPerPage))
|
||||
|| ($postsPerPage > $cfgMaxPostsPerRequest)
|
||||
|| ($postsPerPage < 1)
|
||||
) {
|
||||
$postsPerPage = $_REQUEST["amount"] = $cfgMaxPostsPerRequest; // TODO: user defaults
|
||||
NTFY_AddNotice("Wrong posts \"amount\" value in query, was normalized to $postsPerPage.", "warning");
|
||||
} else {
|
||||
$_REQUEST["amount"] = intval($_REQUEST["amount"]);
|
||||
}
|
||||
} else {
|
||||
$postsPerPage = $cfgMaxPostsPerRequest; // TODO: user defaults
|
||||
}
|
||||
// Checking posts offset
|
||||
$currentOffset = null;
|
||||
if (!empty($_REQUEST["offset"])) {
|
||||
$currentOffset = $_REQUEST["offset"];
|
||||
if (
|
||||
(!InpChk_IsValidInt32($currentOffset))
|
||||
|| $currentOffset < 0
|
||||
) {
|
||||
$currentOffset = $_REQUEST["offset"] = 0;
|
||||
NTFY_AddNotice("Wrong \"offset\" value in query, was defaulted to $currentOffset.", "warning");
|
||||
} else {
|
||||
$_REQUEST["offset"] = intval($_REQUEST["offset"]);
|
||||
}
|
||||
} else {
|
||||
$currentOffset = 0;
|
||||
}
|
||||
$currentOffsetIsCorrect = ($currentOffset === 0) || (!($currentOffset % $postsPerPage));
|
||||
|
||||
|
||||
|
||||
// Processing request
|
||||
$result = Post_GetMatchingPosts_Method($_REQUEST);
|
||||
$requestedPostsResult = null;
|
||||
if ($result->IsError()) // Something happened
|
||||
NTFY_AddNotice("Failed to fetch posts! Reason:<br>" . $result->GetError(), "fail");
|
||||
else
|
||||
$requestedPostsResult = $result->GetData();
|
||||
|
||||
|
||||
|
||||
// Checking offset again NOTICE: doshirak-code
|
||||
if ($requestedPostsResult && $currentOffset > $requestedPostsResult["total_amount"]) {
|
||||
$currentOffset = $_REQUEST["offset"] = 0;
|
||||
NTFY_AddNotice("Wrong \"offset\" value in query!", "fail");
|
||||
$requestedPostsResult = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
NTFY_EchoAllNotices();
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<div class="visualbox">
|
||||
<menu class="paginator">
|
||||
<?php
|
||||
// TODO: move to function, remove trashy dependencies and dublicate at bottom of posts list
|
||||
|
||||
if ($requestedPostsResult) {
|
||||
// Calculating current page based on offset NOTICE: counting starts from zero, so user will see $currentPage + 1
|
||||
$currentPage = intdiv($currentOffset, $postsPerPage);
|
||||
if (!$currentOffsetIsCorrect)
|
||||
++$currentPage;
|
||||
// Calculating maximum page based on total posts amount
|
||||
$totalPagesAmount = intdiv($requestedPostsResult["total_amount"], $postsPerPage);
|
||||
if ($requestedPostsResult["total_amount"] % $postsPerPage)
|
||||
++$totalPagesAmount;
|
||||
} else {
|
||||
$currentPage = 0;
|
||||
$totalPagesAmount = 0;
|
||||
}
|
||||
|
||||
$tempGETArr = $_GET;
|
||||
$tempGETArr["offset"] = $currentOffset; // Starting offset
|
||||
$startingPage = $currentPage; // And page, yeah
|
||||
|
||||
// Calculating starting offset
|
||||
for ($i = 0; $i < 2 && $tempGETArr["offset"] > 0; ++$i) {
|
||||
$tempGETArr["offset"] -= $postsPerPage;
|
||||
--$startingPage;
|
||||
}
|
||||
|
||||
// If list of posts does not begin from first
|
||||
if ($currentOffset > 0) { // Then adding link to first and previous page
|
||||
$secondTempGETArr = $_GET;
|
||||
$secondTempGETArr["offset"] = 0;
|
||||
// First page
|
||||
echo "<li><a title=\"First page\" href=\"./?" . http_build_query($secondTempGETArr) . "\">First</a></li>\n";
|
||||
// Previous page
|
||||
if ($currentOffsetIsCorrect) {
|
||||
$secondTempGETArr["offset"] = $currentOffset - $postsPerPage;
|
||||
echo "<li><a title=\"Previous page\" href=\"./?" . http_build_query($secondTempGETArr) . "\"><</a></li>\n";
|
||||
}
|
||||
unset($secondTempGETArr);
|
||||
|
||||
echo "<li><p>...</p></li>";
|
||||
}
|
||||
|
||||
// Showing pages
|
||||
$i = $startingPage;
|
||||
$endingPage = $startingPage + 5; // Unaccurate naming, tbh, but who cares
|
||||
for (; $i < $endingPage && $i < $totalPagesAmount; ++$i) {
|
||||
echo "<li>";
|
||||
if ($tempGETArr["offset"] === $currentOffset)
|
||||
echo "<b>";
|
||||
echo "<a href=\"./?" . http_build_query($tempGETArr) . "\">" . strval($i + 1) ."</a>";
|
||||
if ($tempGETArr["offset"] === $currentOffset)
|
||||
echo "</b>";
|
||||
echo "</li>\n";
|
||||
|
||||
$tempGETArr["offset"] += $postsPerPage;
|
||||
}
|
||||
|
||||
// If we have even MORE posts!
|
||||
if ($currentPage < ($totalPagesAmount - 1)) { // Then adding link to last and next page
|
||||
echo "<li><p>...</p></li>";
|
||||
|
||||
$secondTempGETArr = $_GET;
|
||||
// Next page
|
||||
if ($currentOffsetIsCorrect) {
|
||||
$secondTempGETArr["offset"] = $currentOffset + $postsPerPage;
|
||||
echo "<li><a title=\"Next page\" href=\"./?" . http_build_query($secondTempGETArr) . "\">></a></li>\n";
|
||||
}
|
||||
// Last page
|
||||
$secondTempGETArr["offset"] = ($totalPagesAmount - 1) * $postsPerPage;
|
||||
echo "<li><a title=\"Last page\" href=\"./?" . http_build_query($secondTempGETArr) . "\">Last</a></li>\n";
|
||||
}
|
||||
?>
|
||||
</menu>
|
||||
<div class="postsearchcolumn">
|
||||
<h3 style="margin-top: 4px; margin-bottom: 4px;">Search</h3>
|
||||
<form class="basicform" action="." accept-charset="UTF-8" method="get">
|
||||
<input type="hidden" name="do" value="search_posts">
|
||||
<input type="text" name="query" autocomplete="on" <?php if (isset($_REQUEST["query"])) { echo "value=\"" . $_REQUEST["query"] . "\""; } ?>>
|
||||
<input type="submit" value="Search">
|
||||
</form>
|
||||
</div>
|
||||
<div class="postlist">
|
||||
<?php
|
||||
if ($requestedPostsResult && $requestedPostsResult["total_amount"]) {
|
||||
foreach ($requestedPostsResult["data"] as $postData)
|
||||
echo GenPostEntry($postData);
|
||||
} else {
|
||||
echo "<h2 style=\"color: gray; font-style: italic;\">Nothing found!</h2>";
|
||||
}
|
||||
// TODO: pages
|
||||
?>
|
||||
</div>
|
||||
</div>
|
39
front/pages/user_info/page.php
Normal file
39
front/pages/user_info/page.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// Approved tags list
|
||||
|
||||
|
||||
// Includes
|
||||
// API
|
||||
require_once("api/_config.php");
|
||||
require_once("api/user/index.php");
|
||||
// Front pieces
|
||||
require_once("front/pages/main_nav.php");
|
||||
require_once("front/notifications.php");
|
||||
|
||||
|
||||
|
||||
// Process request
|
||||
if (isset($_REQUEST["id"])) {
|
||||
$result = User_GetInfoByID_Method($_REQUEST);
|
||||
if ($result->IsError())
|
||||
NTFY_AddNotice("Failed to fetch posts! Reason:<br>" . $result->GetError());
|
||||
} else {
|
||||
header("Location: .");
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
NTFY_EchoAllNotices();
|
||||
|
||||
|
||||
|
||||
?>
|
||||
<div class="visualbox">
|
||||
<?php
|
||||
if (!$result->IsError()) {
|
||||
echo "<h2>" . $result["login"] . "'s personal page</h2>";
|
||||
// TODO
|
||||
}
|
||||
?>
|
||||
</div>
|
@ -77,6 +77,19 @@ div.notification_fail p {
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.notification_warning {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px orange;
|
||||
text-shadow: 0 0 2px black, 0 0 6px black;
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: #fa03;
|
||||
}
|
||||
div.notification_warning p {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
div.notification_success {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
|
@ -54,3 +54,12 @@ div.nav a.useraccount {
|
||||
color: orange;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Index random meme box */
|
||||
|
||||
div.visualbox img {
|
||||
max-width: 90%;
|
||||
max-height: 240px;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ td {
|
||||
|
||||
/* Other */
|
||||
|
||||
/* Basic form form login and registration */
|
||||
/* Basic form for login and registration */
|
||||
|
||||
form.basicform div {
|
||||
margin-bottom: 14px;
|
||||
@ -117,3 +117,103 @@ div.loginmisc p {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/* Paginator */
|
||||
|
||||
menu.paginator {
|
||||
text-align: center;
|
||||
margin: 2px auto;
|
||||
}
|
||||
|
||||
menu.paginator li {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
menu.paginator li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
menu.paginator li p {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/* Post search column styling */
|
||||
|
||||
div.postsearchcolumn {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
div.postsearchcolumn form input[type="text"] {
|
||||
width: 10em;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-right: 1px solid gray;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
div.postsearchcolumn form input[type="submit"] {
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-left: 1px solid gray;
|
||||
display: inline-block;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
/* Posts list styling */
|
||||
|
||||
div.postlist {
|
||||
display: block;
|
||||
margin-left: 15em;
|
||||
overflow: visible;
|
||||
padding-left: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.postlist a.entry {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: text-top;
|
||||
width: 150px;
|
||||
max-width: 150px; /* TODO: must be relative or at least tunable through profile settings */
|
||||
background-color: #00904910;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 5px #0006;
|
||||
transition: all 0.2s;
|
||||
margin: 5px;
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
div.postlist a.entry:hover {
|
||||
background-color: #00904920;
|
||||
box-shadow: 0 0 8px #000a;
|
||||
scale: 1.015;
|
||||
}
|
||||
|
||||
div.postlist a.entry img {
|
||||
object-fit: contain;
|
||||
max-width: 150px;
|
||||
max-height: 150px; /* TODO: same as stated higher */
|
||||
}
|
||||
|
||||
div.postlist a.entry div.stats {
|
||||
width: 100%;
|
||||
border-top: 1px solid #00c07c20;
|
||||
}
|
||||
|
||||
div.postlist a.entry div.stats p {
|
||||
color: #00c07c;
|
||||
font-size: 12px;
|
||||
text-shadow: none;
|
||||
margin: 3px 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
div.postlist a.entry:hover div.stats p {
|
||||
color: #49f49f;
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ switch ($PICKED_PAGE) {
|
||||
$PAGE_STYLE = "front/styles/main.css";
|
||||
$PAGE_FILE = "front/pages/tags_viewer/page.php";
|
||||
break;
|
||||
// Posts viewer
|
||||
case "search_posts":
|
||||
$PAGE_TITLE = "Search posts";
|
||||
$PAGE_STYLE = "front/styles/main.css";
|
||||
$PAGE_FILE = "front/pages/search_posts/page.php";
|
||||
break;
|
||||
// Registration page
|
||||
case "register":
|
||||
$PAGE_TITLE = "Register";
|
||||
|
Loading…
Reference in New Issue
Block a user