Создание поста

Метод API для создания поста; страница фронта и стили для него; новый тип ошибки при неудачной загрузке файла; фикс функции проверки строки на соответствие кодировке ASCII; фикс парсинга тегов; умное создание превью (проверка на случай, если превью получилось больше оригинала); исправление функции сохранения изображения; фикс функции создания поста, которая взаимодействует с БД; добавлена проверка корректности подписи к посту; добавление новых пунктов в навигацию; небольшое улучшение QoL в плане конфига.
This commit is contained in:
2024-02-09 00:13:23 +03:00
parent de456dea0a
commit 705e8cd6a2
15 changed files with 321 additions and 85 deletions
+114 -35
View File
@@ -38,15 +38,16 @@ function Post_ParseRawTagString (string $str): ReturnT {
$currTag = "";
$result = array();
for ($i = 0; $i < $strLength; ++$i) {
if ($str[$i] === ",") {
if ($currLen) {
for ($i = 0; $i <= $strLength; ++$i) {
if ($i === $strLength || $str[$i] === ",") {
if ($currLen > 0) {
$result[] = $currTag;
$currLen = 0;
$currTag = "";
} else {
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "syntax error while trying to parse tags");
}
} elseif ($str[$i] !== " " && $str[$i] !== "\t") {
} elseif (!IntlChar::isspace($str[$i])) {
$currTag .= $str[$i];
if (++$currLen > $maxTagLength)
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tag too large: $currTag");
@@ -54,9 +55,12 @@ function Post_ParseRawTagString (string $str): ReturnT {
}
$preg_str = "/[^" . $allowedSymbols . "]/";
foreach ($result as $tag) {
if (preg_match($preg_str, $tag))
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "only allowed symbols in tags are: " . $allowedSymbols);
elseif (!$tag)
unset($tag);
}
return new ReturnT(data: $result);
@@ -94,10 +98,17 @@ function Post_CreatePreviewFromImage (string $src, string $dst): ReturnT {
}
// Saving it as LQ JPEG
imagejpeg($img, $dst, 30);
$saveResult = imagejpeg($img, $dst, 30);
if (!file_exists($dst)) // $src isnt our responsibility, $dst is
if (!$saveResult) {
if (file_exists($dst)) // $src isnt our responsibility, $dst is
unlink($dst);
return new ReturnT(err_code: E_UNS_UNEXPECTED, err_desc: "failed to create preview");
}
// Check if preview is bigger or same size as original...
if (filesize($src) < filesize($dst))
unlink($dst); // ...then we can just delete preview. Frontend will use preview only when it exist
return new ReturnT(data: true);
}
@@ -107,28 +118,42 @@ function Post_CreatePreviewFromImage (string $src, string $dst): ReturnT {
* Stores image and returns paths to picture and its preview
*/
function Post_StoreImage (string $path): ReturnT {
global $Config;
global $Config, $IS_FRONTEND;
// Original extension
$ext = "";
if (mime_content_type($path) === "image/jpeg")
$ext = "jpg";
elseif (mime_content_type($path) === "image/png")
$ext = "png";
else
return new ReturnT(err_code: E_UNS_UNEXPECTED, err_desc: "failed to determine correctly mime type of image");
// Paths
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$fileName = strval(time()) . "_" . GenerateRandomString(4);
$targetDir = "../../" . $Config["media"]["pics_path"];
$fileName = strval(time()) . "_" . Utils_GenerateRandomString(4);
// Defining base dir
$baseDir = "";
if (isset($IS_FRONTEND) && $IS_FRONTEND)
$baseDir = "./";
else
$baseDir = "../../";
$targetDir = $baseDir . $Config["media"]["pics_path"];
$targetPath = Utils_JoinPaths($targetDir, $fileName . "." . $ext);
// Moving original picture
$moveResult = move_uploaded_file($path, $targetPath);
if (!$moveResult)
return new ReturnT(err_code: E_UNS_UNEXPECTED, err_desc: "failed to move uploaded file");
// Creating preview file
if ($Config["media"]["previews_enabled"]) {
$previewDir = "../../" . $Config["media"]["prevs_path"];
$previewDir = $baseDir . $Config["media"]["prevs_path"];
$previewPath = Utils_JoinPaths($previewDir, $fileName . ".jpg");
$res = Post_CreatePreviewFromImage($path, $previewPath);
if ($res->IsError()) // $path isnt our responsibility
$res = Post_CreatePreviewFromImage($targetPath, $previewPath);
if ($res->IsError()) // $path and $targetPath arent our responsibility, neither is $previewPath
return $res;
}
move_uploaded_file($path, $targetPath);
if (!file_exists($targetPath)) { // however, $previewPath IS our responsibility
unlink($previewPath);
return new ReturnT(err_code: E_UNS_UNEXPECTED, err_desc: "failed to move uploaded file");
if (!file_exists($previewPath)) // If no preview was created - then just nullify path
$previewPath = null;
}
return new ReturnT(data: array(
@@ -159,8 +184,8 @@ function Post_Create (
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,comment_section_id,tags,title,pic_path,preview_path,comments_enabled,edit_lock) VALUES (?,?,?,?,?,?,?,?)");
$s->bind_param("ssssssss", $author_id, null, $tags, $title, $pic_path, $prev_path, $comms_enabled, $edit_lock);
$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);
if ($s->execute() === false)
return new ReturnT(err_code: E_DBE_INSERTFAIL, err_desc: "failed to create post record in DB");
@@ -181,12 +206,11 @@ function Post_Create (
function Post_Create_Method (array $req, array $files): ReturnT {
global $Config, $LOGGED_IN, $THIS_USER;
$author_id = $THIS_USER;
$tags = null;
$pic_path = null;
$title = null;
$prev_path = null;
$comms_enabled = false;
$comms_enabled = false; // TODO: support for this option at post creation
// Input sanity checks
@@ -195,14 +219,15 @@ function Post_Create_Method (array $req, array $files): ReturnT {
return new ReturnT(err_code: E_AUT_NOTAUTHED, err_desc: "you must be logged in to create posts");
// Check if there are necessary input
if (!(isset($req["tags"]) && isset($files["pic"])))
if (!isset($req["tags"]) || !isset($files["pic"]) || is_array($files["pic"]["error"]) || $files["pic"]["error"] === UPLOAD_ERR_NO_FILE)
return new ReturnT(err_code: E_UIN_INSUFARGS, err_desc: "tags and picture are necessary");
// Check tags
// If raw string length not fits into limit
$tagsLen = strlen($req["tags"]);
if ($tagsLen > $Config["posting"]["tags"]["max_raw_input_str_length"])
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tags string length exceeds limit: " . strval($Config["posting"]["tags"]["max_raw_input_str_length"]));
$tagsMaxLen = $Config["posting"]["tags"]["max_raw_input_str_length"];
if ($tagsLen > $tagsMaxLen)
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tags string length exceeds limit: " . $tagsMaxLen);
elseif ($tagsLen < 1)
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tags cant be empty");
// Check if supplied string is ASCII
@@ -216,7 +241,7 @@ function Post_Create_Method (array $req, array $files): ReturnT {
// Check if tags are approved
foreach ($parsedTags as $singleTag) {
if (!Tags_IsTagApproved($singleTag))
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tag $singleTag is not approved");
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tag \"$singleTag\" is not approved");
}
// Concatenating parsed tags to single comma-separated string
$tags = implode(",", $parsedTags);
@@ -225,15 +250,56 @@ function Post_Create_Method (array $req, array $files): ReturnT {
if (User_HasRole($THIS_USER, "newbie")->GetData())
return new ReturnT(err_code: E_ACS_INSUFROLE, err_desc: "newbies cant create posts");
// Checking title
if (isset($req["title"])) {
// Title length
$maxTitleLen = $Config["posting"]["title"]["max_length"];
$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)
for ($i = 0; $i < $realTitleLen; ++$i) {
switch ($req["title"][$i]) {
case "<":
$title .= "&lt;";
break;
case ">":
$title .= "&gt;";
break;
case "/":
$title .= "&#47;";
break;
case "\\":
$title .= "&#92;";
break;
case "?":
$title .= "&#63;";
break;
case "&":
$title .= "&amp;";
break;
case "\n":
$title .= "<br>";
break;
case "\t":
$title .= "&emsp;";
break;
default:
$title .= $req["title"][$i];
}
}
if (strlen($title) > $maxTitleLen)
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "title length exceeds maximum value after escaping");
}
// Check image properties
// If error happened while uploading
if ($files["pic"]["error"] !== UPLOAD_ERR_OK)
return new ReturnT(err_code: E_UIN_FAIL2UPLD, err_desc: "error while uploading file: " . strval($files["pic"]["error"]));
// If size is too large
if ($files["pic"]["size"] > $Config["media"]["max_pic_size"])
return new ReturnT(err_code: E_UIN_FILE2LARGE, err_desc: "picture size is too large");
$TmpFilePath = $_FILES["pic"]["tmp_name"];
$Ext = strtolower(pathinfo($TmpFilePath, PATHINFO_EXTENSION));
// If file extension is not in list of allowed
if (!in_array($Ext, $Config["media"]["allowed_exts"]))
return new ReturnT(err_code: E_UIN_FILETYPE, err_desc: "picture extension is invalid");
$TmpFilePath = $files["pic"]["tmp_name"];
// If file mime type is not in list of allowed
if (!in_array(mime_content_type($TmpFilePath), $Config["media"]["allowed_mimetypes"]))
return new ReturnT(err_code: E_UIN_FILETYPE, err_desc: "picture mime type is invalid");
@@ -250,10 +316,17 @@ function Post_Create_Method (array $req, array $files): ReturnT {
unlink($TmpFilePath);
return $res;
}
$res = $res->GetData();
$pic_path = $res["picture"];
$prev_path = $res["preview"];
return Post_Create($author_id, $tags, $pic_path, $title, $prev_path, $comms_enabled, false);
$res = Post_Create($THIS_USER, $tags, $pic_path, $title, $prev_path, $comms_enabled, false);
if ($res->IsError()) { // Cleaning up all processed pics
unlink($pic_path);
if ($prev_path)
unlink($prev_path);
}
return $res;
}
@@ -261,7 +334,13 @@ function Post_Create_Method (array $req, array $files): ReturnT {
if (Utils_ThisFileIsRequested(__FILE__)) {
require_once("../_json.php");
// TODO
$result = Post_Create_Method($_POST, $_FILES);
// Checking result
if ($result->IsError())
$result->ThrowJSONError();
else
JSON_ReturnData(["success" => $result->GetData()]);
}
?>