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 (!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 ($x, $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; // 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); 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 */ 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) for ($i = 0; $i < $realTitleLen; ++$i) { switch ($req["title"][$i]) { case "<": $title .= "<"; break; case ">": $title .= ">"; break; case "/": $title .= "/"; break; case "\\": $title .= "\"; break; case "?": $title .= "?"; break; case "&": $title .= "&"; break; case "\n": $title .= "
"; break; case "\t": $title .= " "; 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()]); } ?>