e949/api/post/create.php
shr3dd3r 62b7b68976 Функция поиска по постам
Мне это всё расписывать что-ли? Смотрите в содержание коммита, мне феерически индифферентно
2024-03-07 19:58:39 +03:00

347 lines
10 KiB
PHP

<?php
// Create new post
// Includes
if (isset($IS_FRONTEND) && $IS_FRONTEND) {
require_once("api/_auth.php");
require_once("api/_utils.php");
require_once("api/_errorslist.php");
require_once("api/_types.php");
require_once("api/user/index.php");
require_once("api/tags/index.php");
} else {
require_once("../_auth.php");
require_once("../_utils.php");
require_once("../_errorslist.php");
require_once("../_types.php");
require_once("../user/index.php");
require_once("../tags/index.php");
}
// Functions
/*
* FUNCTION
* Parse tags from raw string
*/
function Post_ParseRawTagString (string $str): ReturnT {
global $Config;
$allowedSymbols = $Config["posting"]["tags"]["allowed_syms"];
$maxTagLength = $Config["posting"]["tags"]["max_single_length"];
$strLength = strlen($str);
$currLen = 0;
$currTag = "";
$result = array();
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 at index $i while trying to parse tags");
}
} 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");
}
}
$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);
}
/*
* FUNCTION
* Check if image size properties are valid
*/
function Post_ImgResIsValid (int $x, int $y): bool {
global $Config;
return ($x <= $Config["media"]["max_pic_res"]["x"])
&& ($y <= $Config["media"]["max_pic_res"]["y"])
&& (Utils_GetRatio($x, $y) <= $Config["media"]["max_pic_res"]["ratio"]);
}
/*
* FUNCTION
* Create preview version of image
*/
function Post_CreatePreviewFromImage (string $src, string $dst): ReturnT {
$img = null;
// Reading image from source path
switch (mime_content_type($src)) {
case "image/jpeg":
$img = imagecreatefromjpeg($src);
break;
case "image/png":
$img = imagecreatefrompng($src);
break;
default:
return new ReturnT(err_code: E_UIN_FILETYPE, err_desc: "invalid mime type");
}
// Saving it as LQ JPEG
$saveResult = imagejpeg($img, $dst, 30);
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);
}
/*
* FUNCTION
* Stores image and returns paths to picture and its preview
*/
function Post_StoreImage (string $path): ReturnT {
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
$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 = $baseDir . $Config["media"]["prevs_path"];
$previewPath = Utils_JoinPaths($previewDir, $fileName . ".jpg");
$res = Post_CreatePreviewFromImage($targetPath, $previewPath);
if ($res->IsError()) // $path and $targetPath arent our responsibility, neither is $previewPath
return $res;
if (!file_exists($previewPath)) // If no preview was created - then just nullify path
$previewPath = null;
}
return new ReturnT(data: array(
"preview" => $previewPath,
"picture" => $targetPath
));
}
/*
* FUNCTION
* Create single publication
*/
function Post_Create (
int $author_id,
string $tags,
string $pic_path,
?string $title = null,
?string $prev_path = null,
bool $comms_enabled = false,
bool $edit_lock = false
): ReturnT {
global $db;
$result = null;
// 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);
if ($s->execute() === false)
return new ReturnT(err_code: E_DBE_INSERTFAIL, err_desc: "failed to create post record in DB");
$result = true;
return new ReturnT(data: $result);
}
// Methods
/*
* 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;
$tags = null;
$pic_path = null;
$title = null;
$prev_path = null;
$comms_enabled = false; // TODO: support for this option at post creation
// Input sanity checks
// Check if user is authenticated
if (!$LOGGED_IN)
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"]) || 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"]);
$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
if (!Utils_IsAscii($req["tags"]))
return new ReturnT(err_code: E_UIN_BADARGS, err_desc: "tags must be ASCII-only");
// Parsing tags
$parsedTags = Post_ParseRawTagString($req["tags"]);
if ($parsedTags->IsError())
return $parsedTags;
$parsedTags = $parsedTags->GetData();
// 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");
}
// Concatenating parsed tags to single comma-separated string
$tags = implode(",", $parsedTags);
// Check user role TODO: add rate-limiting, instead of this
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) TODO: move to function
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"];
// 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");
// Check if resolution is bigger than allowed or have unacceptable aspect ratio
list($SzX, $SzY, $Type, $Attr) = getimagesize($TmpFilePath);
if (!Post_ImgResIsValid($SzX, $SzY))
return new ReturnT(err_code: E_UIN_IMGBADRES, err_desc: "image with that resolution or aspect ratio cant be accepted");
// Actions
// Copy picture to storage folder
$res = Post_StoreImage($TmpFilePath);
if ($res->IsError()) { // $TmpFilePath seemingly isnt our responsibility, BUT, only we know how and what to cleanup
unlink($TmpFilePath);
return $res;
}
$res = $res->GetData();
$pic_path = $res["picture"];
$prev_path = $res["preview"];
$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;
}
if (Utils_ThisFileIsRequested(__FILE__)) {
require_once("../_json.php");
$result = Post_Create_Method($_POST, $_FILES);
// Checking result
if ($result->IsError())
$result->ThrowJSONError();
else
JSON_ReturnData(["success" => $result->GetData()]);
}
?>