FileApi start
This commit is contained in:
		
							parent
							
								
									fae8a71bac
								
							
						
					
					
						commit
						8747812a56
					
				| @ -162,8 +162,8 @@ namespace Api\ApiKey { | ||||
|       $this->loginRequired = true; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($aValues = array()) { | ||||
|       if(!parent::execute($aValues)) { | ||||
|     public function execute($values = array()) { | ||||
|       if(!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -53,6 +53,7 @@ namespace Api\Contact { | ||||
|       } | ||||
| 
 | ||||
|       $this->createNotification(); | ||||
|       $this->sendMail(); | ||||
| 
 | ||||
|       if (!$this->success) { | ||||
|         return $this->createError("The contact request was saved, but the server was unable to create a notification."); | ||||
| @ -110,6 +111,17 @@ namespace Api\Contact { | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
| 
 | ||||
|     private function sendMail() { | ||||
|       /*$email = $this->getParam("fromEmail"); | ||||
|       $settings = $this->user->getConfiguration()->getSettings(); | ||||
|       $request = new \Api\Mail\Send($this->user); | ||||
|       $this->success = $request->execute(array( | ||||
|         "to" => $settings->get, | ||||
|         "subject" => "[$siteName] Account Invitation", | ||||
|         "body" => $messageBody | ||||
|       ));*/ | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										737
									
								
								core/Api/FileAPI.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										737
									
								
								core/Api/FileAPI.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,737 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Api { | ||||
| 
 | ||||
|   use Driver\SQL\Condition\Compare; | ||||
| 
 | ||||
|   abstract class FileAPI extends Request { | ||||
| 
 | ||||
|     protected function checkDirectory($parentId) { | ||||
|       if ($parentId === null) { | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $res = $sql->select("directory") | ||||
|         ->from("UserFile") | ||||
|         ->where(new Compare("uid", $parentId)) | ||||
|         ->limit(1) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         if (empty($res)) { | ||||
|           return $this->createError("Parent directory not found"); | ||||
|         } else if(!$res[0]["directory"]) { | ||||
|           return $this->createError("Parent file is not a directory"); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
| 
 | ||||
|     protected function downloadFile($name, $path) { | ||||
|       if (!file_exists($path)) { | ||||
|         http_response_code(404); | ||||
|         die("404 - File does not exist anymore"); | ||||
|       } else if(!is_file($path) || !is_readable($path)) { | ||||
|         die("403 - Unable to download file."); | ||||
|       } else { | ||||
|         $mimeType = @mime_content_type($path); | ||||
|         if ($mimeType) { | ||||
|           header("Content-Type: $mimeType"); | ||||
|         } | ||||
| 
 | ||||
|         $name = trim(preg_replace('/\s\s+/', ' ', $name)); | ||||
|         header("Content-Disposition: attachment; filename=$name"); | ||||
| 
 | ||||
|         //No cache
 | ||||
|         header('Expires: 0'); | ||||
|         header('Cache-Control: must-revalidate'); | ||||
|         header('Pragma: public'); | ||||
| 
 | ||||
|         //Define file size
 | ||||
|         header('Content-Length: ' . filesize($path)); | ||||
| 
 | ||||
|         ob_clean(); | ||||
|         flush(); | ||||
|         readfile($path); | ||||
|         exit; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     protected function isInDirectory($fileId, $directoryId) { | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| namespace Api\File { | ||||
| 
 | ||||
|   use Api\FileAPI; | ||||
|   use Api\Parameter\Parameter; | ||||
|   use Api\Parameter\StringType; | ||||
|   use Driver\SQL\Condition\Compare; | ||||
|   use Driver\SQL\Condition\CondIn; | ||||
|   use Objects\User; | ||||
| 
 | ||||
|   class ValidateToken extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         'token' => new StringType('token', 36, false) | ||||
|       )); | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $token = $this->getParam("token"); | ||||
|       $res = $sql->select("UserFile.uid", "valid_until", "token_type", "maxFiles", "maxSize", "extensions", "name", "path", "directory") | ||||
|         ->from("UserFileToken") | ||||
|         ->leftJoin("UserFileTokenFile", "UserFileToken.uid", "token_id") | ||||
|         ->innerJoin("UserFile", "UserFile.uid", "file_id") | ||||
|         ->where(new Compare("token", $token)) | ||||
|         ->where(new Compare("valid_until", $sql->now(), ">")) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         if (empty($res)) { | ||||
|           return $this->createError("Invalid Token"); | ||||
|         } else { | ||||
|           $row = $res[0]; | ||||
|           $this->result["token"] = array( | ||||
|             "type" => $row["token_type"], | ||||
|             "valid_until" => $row["valid_until"] | ||||
|           ); | ||||
| 
 | ||||
|           $this->result["files"] = array(); | ||||
|           foreach ($res as $row) { | ||||
|             $file = array( | ||||
|               "isDirectory" => $row["directory"], | ||||
|               "name" => $row["name"], | ||||
|               "uid" => $row["uid"] | ||||
|             ); | ||||
| 
 | ||||
|             if ($file["isDirectory"]) { | ||||
|               $file["items"] = array(); | ||||
|             } else { | ||||
|               $file["size"] = @filesize($row["path"]); | ||||
|               $file["mimeType"] = @mime_content_type($row["path"]); | ||||
|             } | ||||
| 
 | ||||
|             $this->result["files"][] = $file; | ||||
|           } | ||||
| 
 | ||||
|           if ($row["token_type"] === "upload") { | ||||
|             $this->result["restrictions"] = array( | ||||
|               "maxFiles" => $row["maxFiles"] ?? 0, | ||||
|               "maxSize"  => $row["maxSize"] ?? 0, | ||||
|               "extensions" => $row["extensions"] ?? "" | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class RevokeToken extends FileAPI { | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "token" => new StringType("token", 36) | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = true; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $token = $this->getParam("token"); | ||||
|       $res = $sql->select($sql->count()) | ||||
|         ->from("UserToken") | ||||
|         ->where(new Compare("user_id", $this->user->getId())) | ||||
|         ->where(new Compare("token", $token)) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         if(empty($res)) { | ||||
|           return $this->getParam("Invalid token"); | ||||
|         } else { | ||||
|           $res = $sql->update("UserToken") | ||||
|             ->set("valid_until", new \DateTime()) | ||||
|             ->where(new Compare("user_id", $this->user->getId())) | ||||
|             ->where(new Compare("token", $token)) | ||||
|             ->execute(); | ||||
|           $this->success = ($res !== false); | ||||
|           $this->lastError = $sql->getLastError(); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class ListFiles extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "directory" => new Parameter("directory", Parameter::TYPE_INT, true, null) | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     private function &findDirectory(&$files, $id) { | ||||
| 
 | ||||
|       if ($id !== null) { | ||||
|         $id = (string)$id; | ||||
|         if (isset($files[$id])) { | ||||
|           return $files[$id]["items"]; | ||||
|         } else { | ||||
|           foreach ($files as &$dir) { | ||||
|             if ($dir["isDirectory"]) { | ||||
|               $target =& $this->findDirectory($dir["items"], $id); | ||||
|               if ($target !== $dir) { | ||||
|                 return $target; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           return $files; | ||||
|         } | ||||
|       } else { | ||||
|         return $files; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $res = $sql->select( | ||||
|           "UserFile.uid", "UserFile.directory", "UserFile.path", "UserFile.name", | ||||
|           "UserFile.user_id", "parentTable.uid as parentId", "parentTable.name as parentName") | ||||
|         ->from("UserFile") | ||||
|         ->leftJoin("UserFile", "UserFile.parent_id", "parentTable.uid", "parentTable") | ||||
|         ->where(new Compare("UserFile.user_id", $this->user->getId())) | ||||
|         ->orderBy("UserFile.parent_id") | ||||
|         ->ascending() | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         $files = array(); | ||||
| 
 | ||||
|         foreach ($res as $row) { | ||||
|           $fileId = (string)$row["uid"]; | ||||
|           $parentId = $row["parentId"]; | ||||
|           $fileName = $row["name"]; | ||||
|           $isDirectory = $row["directory"]; | ||||
|           $fileElement = array("uid" => $fileId, "name" => $fileName, "isDirectory" => $isDirectory); | ||||
|           if ($isDirectory) { | ||||
|             $fileElement["items"] = array(); | ||||
|           } else { | ||||
|             $fileElement["size"] = @filesize($row["path"]); | ||||
|             $fileElement["mimeType"] = @mime_content_type($row["path"]); | ||||
|           } | ||||
| 
 | ||||
|           $dir =& $this->findDirectory($files, $parentId); | ||||
|           $dir[$fileId] = $fileElement; | ||||
|           unset($dir); | ||||
|         } | ||||
| 
 | ||||
|         $directoryId = $this->getParam("directory"); | ||||
|         if (!is_null($directoryId)) { | ||||
|           $wantedDir =& $this->findDirectory($files, $directoryId); | ||||
|           if ($files === $wantedDir) { | ||||
|             $files = array(); | ||||
|           } else { | ||||
|             $files = $wantedDir; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         $this->result["files"] = $files; | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class ListTokens extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array()); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $res = $sql->select("uid","token","valid_until","token_type", "maxFiles", "maxSize", "extensions") | ||||
|         ->from("UserFileToken") | ||||
|         ->orderBy("valid_until") | ||||
|         ->descending() | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         $this->result["tokens"] = array(); | ||||
|         foreach ($res as $row) { | ||||
|           $tokenType = $row["token_type"]; | ||||
|           if ($tokenType !== "upload") { | ||||
|             unset($row["maxFiles"]); | ||||
|             unset($row["maxSize"]); | ||||
|             unset($row["extensions"]); | ||||
|           } | ||||
|           $this->result["tokens"][] = $row; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class CreateDirectory extends FileAPI { | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         'name' => new StringType('name', 32), | ||||
|         'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null) | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $name = $this->getParam('name'); | ||||
|       $parentId = $this->getParam("parentId"); | ||||
|       if (!$this->checkDirectory($parentId)) { | ||||
|         return $this->success; | ||||
|       } | ||||
| 
 | ||||
|       $res = $sql->insert("UserFile", array("directory", "name", "user_id", "parent_id")) | ||||
|         ->addRow(true, $name, $this->user->getId(), $parentId) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class Rename extends FileAPI { } | ||||
| 
 | ||||
|   class Move extends FileAPI { } | ||||
| 
 | ||||
|   class Upload extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null), | ||||
|         'token' => new StringType('token', 36, true, null) | ||||
|       )); | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $token = $this->getParam("token"); | ||||
|       if (!$this->user->isLoggedIn() && is_null($token)) { | ||||
|         return $this->createError("Permission denied (expected token)"); | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $parentId = $this->getParam("parentId"); | ||||
|       if (!is_null($token) && !is_null($parentId)) { | ||||
|         return $this->createError("Cannot upload to parent directory using token"); | ||||
|       } | ||||
| 
 | ||||
|       if (!is_null($parentId) && !$this->checkDirectory($parentId)) { | ||||
|         return $this->success; | ||||
|       } | ||||
| 
 | ||||
|       $fileKeys = array_keys($_FILES); | ||||
|       $numFilesUploaded = count($fileKeys); | ||||
| 
 | ||||
|       if (!is_null($token)) { | ||||
| 
 | ||||
|         $res = $sql->select("uid",  "token_type", "maxFiles", "maxSize", "extensions", "user_id") | ||||
|           ->from("UserFileToken") | ||||
|           ->where(new Compare("token", $token)) | ||||
|           ->where(new Compare("valid_until", $sql->now(), ">=")) | ||||
|           ->limit(1) | ||||
|           ->execute(); | ||||
| 
 | ||||
|         $this->success = ($res !== false); | ||||
|         $this->lastError = $sql->getLastError(); | ||||
|         if (!$this->success) { | ||||
|           return false; | ||||
|         } | ||||
| 
 | ||||
|         if (empty($res) || $res[0]["token_type"] !== "upload") { | ||||
|           return $this->createError("Permission denied (token)"); | ||||
|         } | ||||
| 
 | ||||
|         $tokenId = $res[0]["uid"]; | ||||
|         $maxFiles = $res[0]["maxFiles"] ?? 0; | ||||
|         $maxSize = $res[0]["maxSize"] ?? 0; | ||||
|         $userId = $res[0]["user_id"]; | ||||
|         $extensions = explode(",", strtolower($res[0]["extensions"] ?? "")); | ||||
| 
 | ||||
|         $res = $sql->select($sql->count()) | ||||
|           ->from("UserFileTokenFile") | ||||
|           ->innerJoin("UserFileToken", "token_id", "UserFileToken.uid") | ||||
|           ->execute(); | ||||
| 
 | ||||
|         $this->success = ($res !== false); | ||||
|         $this->lastError = $sql->getLastError(); | ||||
|         if (!$this->success) { | ||||
|           return false; | ||||
|         } | ||||
| 
 | ||||
|         $count = $res[0]["count"]; | ||||
|         if ($maxFiles > 0 && $numFilesUploaded > 0 && $numFilesUploaded + $count > $maxFiles) { | ||||
|           return $this->createError("File limit exceeded. Currently uploaded $count / $maxFiles count"); | ||||
|         } | ||||
| 
 | ||||
|         if ($maxSize > 0 || !empty($extensions)) { | ||||
|           foreach ($_FILES as $file) { | ||||
|             $name = $file["name"]; | ||||
|             if ($maxSize > 0 && $file["size"] > $maxSize) { | ||||
|               return $this->createError("File Size limit of $maxSize bytes exceeded for file $name"); | ||||
|             } | ||||
| 
 | ||||
|             $dotPos = strrpos($name, "."); | ||||
|             $ext = ($dotPos !== false ? strtolower(substr($name, $dotPos + 1)) : false); | ||||
|             if (!empty($extensions) && $ext !== false && !in_array($ext, $extensions)) { | ||||
|               return $this->createError("File '$name' has prohibited extension. Allowed extensions: " . implode(",", $extensions)); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         $userId = $this->user->getId(); | ||||
|       } | ||||
| 
 | ||||
|       if ($numFilesUploaded === 0) { | ||||
|         return $this->createError("No file uploaded"); | ||||
|       } | ||||
| 
 | ||||
|       $uploadDir = realpath($_SERVER["DOCUMENT_ROOT"] . "/files/uploaded/"); | ||||
|       if (!is_writable($uploadDir)) { | ||||
|         return $this->createError("Upload directory is not writable"); | ||||
|       } | ||||
| 
 | ||||
|       $fileIds = array(); | ||||
|       foreach ($_FILES as $key => $file) { | ||||
|         $fileName = $file["name"]; | ||||
|         $tmpPath = $file["tmp_name"]; | ||||
|         $md5Hash = hash_file('md5', $tmpPath); | ||||
|         $sha1Hash = hash_file('sha1', $tmpPath); | ||||
|         $filePath =  $uploadDir . "/" . $md5Hash . $sha1Hash; | ||||
|         if (move_uploaded_file($tmpPath, $filePath)) { | ||||
|           $res = $sql->insert("UserFile", array("name", "directory", "path", "user_id", "parent_id")) | ||||
|             ->addRow($fileName, false, $filePath, $userId, $parentId) | ||||
|             ->returning("uid") | ||||
|             ->execute(); | ||||
| 
 | ||||
|           if ($res === false) { | ||||
|             $this->lastError = $sql->getLastError(); | ||||
|             $this->success = false; | ||||
|             return false; | ||||
|           } else { | ||||
|             $fileIds[] = $sql->getLastInsertId(); | ||||
|           } | ||||
|         } else { | ||||
|           return $this->createError("Could not create file: " . $fileName); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (!is_null($token)) { | ||||
|         $query = $sql->insert("UserFileTokenFile", array("file_id", "token_id")); | ||||
|         foreach ($fileIds as $fileId) { | ||||
|           $query->addRow($fileId, $tokenId); | ||||
|         } | ||||
| 
 | ||||
|         $res = $query->execute(); | ||||
|         $this->success = ($res !== false); | ||||
|         $this->lastError = $sql->getLastError(); | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class Download extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT), | ||||
|         "token" => new StringType("token", 36, true, null) | ||||
|       )); | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $token = $this->getParam("token"); | ||||
|       if (!$this->user->isLoggedIn() && is_null($token)) { | ||||
|         return $this->createError("Permission denied (expected token)"); | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $fileId = $this->getParam("id"); | ||||
|       $query =  $sql->select("path", "name") | ||||
|         ->from("UserFile") | ||||
|         ->where(new Compare("UserFile.uid", $fileId)); | ||||
| 
 | ||||
|       if (is_null($token)) { | ||||
|         $query->where(new Compare("user_id", $this->user->getId())); | ||||
|       } else { | ||||
|         $query->innerJoin("UserFileTokenFile", "UserFile.uid", "file_id") | ||||
|           ->innerJoin("UserFileToken", "UserFileToken.uid", "token_id") | ||||
|           ->where(new Compare("token", $token)); | ||||
|       } | ||||
| 
 | ||||
|       $res = $query->execute(); | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         if (empty($res)) { | ||||
|           if (is_null($token)) { | ||||
|             return $this->createError("File not found"); | ||||
|           } else { | ||||
|             return $this->createError("Permission denied (token)"); | ||||
|           } | ||||
|         } else { | ||||
|           if ($res[0]["directory"]) { | ||||
|             return $this->createError("Cannot download directory (yet)"); | ||||
|           } else { | ||||
|             $path = $res[0]["path"]; | ||||
|             $name = $res[0]["name"]; | ||||
|             $this->downloadFile($name, $path); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class Delete extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT), | ||||
|         "token" => new StringType("token", 36, true, null) | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class CreateUploadToken extends FileAPI { | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "maxFiles" => new Parameter("maxFiles", Parameter::TYPE_INT, true, 1), | ||||
|         "maxSize"  => new Parameter("maxSize", Parameter::TYPE_INT, true, null), | ||||
|         "extensions" => new StringType("extensions", 64, true, null), | ||||
|         "durability" => new Parameter("durability", Parameter::TYPE_INT, true, 60*24*2) | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $maxFiles = $this->getParam("maxFiles"); | ||||
|       $maxSize  = $this->getParam("maxSize"); | ||||
|       $extensions = $this->getParam("extensions"); | ||||
|       $durability = $this->getParam("durability"); | ||||
| 
 | ||||
|       if (!is_null($maxFiles) && $maxFiles < 0) { | ||||
|         return $this->createError("Invalid number of maximum files."); | ||||
|       } | ||||
| 
 | ||||
|       if (!is_null($maxSize) && $maxSize < 0) { | ||||
|         return $this->createError("Invalid maximum size for uploaded files."); | ||||
|       } | ||||
| 
 | ||||
|       if (!is_null($durability) && $durability < 0) { | ||||
|         return $this->createError("Invalid durability."); | ||||
|       } | ||||
| 
 | ||||
|       if (!is_null($extensions)) { | ||||
|         $extensions = explode(",",$extensions); | ||||
|         foreach ($extensions as $i => $ext) { | ||||
|           if (strlen($ext) === 0 || (strlen($ext) === 1 && $ext[0] === ".")) { | ||||
|             unset ($extensions[$i]); | ||||
|           } else if ($ext[0] === ".") { | ||||
|             $extensions[$i] = substr($ext, 1); | ||||
|           } | ||||
|         } | ||||
|         $extensions = implode(",", $extensions); | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $token = generateRandomString(36); | ||||
|       $validUntil = (new \DateTime())->modify("+$durability HOURS"); | ||||
| 
 | ||||
|       $res = $sql->insert("UserFileToken", | ||||
|           array("token", "token_type", "maxSize", "maxFiles", "extensions", "valid_until", "user_id")) | ||||
|         ->addRow($token, "upload", $maxSize, $maxFiles, $extensions, $validUntil, $this->user->getId()) | ||||
|         ->returning("uid") | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         $this->result["token"] = $token; | ||||
|         $this->result["tokenId"] = $sql->getLastInsertId(); | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class CreateDownloadToken extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "durability" => new Parameter("durability", Parameter::TYPE_INT, true, 60*24*2), | ||||
|         "files" => new Parameter("files", Parameter::TYPE_ARRAY, false) | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|       $this->csrfTokenRequired = false; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $durability = $this->getParam("durability"); | ||||
|       $fileIds = $this->getParam("files"); | ||||
| 
 | ||||
|       if (!is_null($durability) && $durability < 0) { | ||||
|         return $this->createError("Invalid durability."); | ||||
|       } | ||||
| 
 | ||||
|       foreach ($fileIds as $fileId) { | ||||
|         if (!is_int($fileId) && is_numeric($fileId)) { | ||||
|           $fileId = intval($fileId); | ||||
|         } | ||||
|         if (!is_int($fileId) || $fileId < 1) { | ||||
|           return $this->createError("Invalid file id: $fileId"); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       $fileIds = array_unique($fileIds); | ||||
| 
 | ||||
|       // Check for files:
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $res = $sql->select("uid") | ||||
|         ->from("UserFile") | ||||
|         ->where(new CondIn("uid", $fileIds)) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       if (count($res) !== count($fileIds)) { | ||||
|         $foundFiles = array_map(function ($row) { return $row["uid"]; }, $res); | ||||
|         foreach ($fileIds as $fileId) { | ||||
|           if (!in_array($fileId, $foundFiles)) { | ||||
|             return $this->createError("File not found: $fileId"); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // Insert
 | ||||
|       $token = generateRandomString(36); | ||||
|       $validUntil = (new \DateTime())->modify("+$durability HOURS"); | ||||
|       $res = $sql->insert("UserFileToken", array("token_type", "valid_until", "user_id", "token")) | ||||
|         ->addRow("download", $validUntil, $this->user->getId(), $token) | ||||
|         ->returning("uid") | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $tokenId = $sql->getLastInsertId(); | ||||
|       $query = $sql->insert("UserFileTokenFile", array("token_id", "file_id")); | ||||
|       foreach ($fileIds as $fileId) { | ||||
|         $query->addRow($tokenId, $fileId); | ||||
|       } | ||||
| 
 | ||||
|       $res = $query->execute(); | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         $this->result["token"] = $token; | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										65
									
								
								core/Api/PatchSQL.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										65
									
								
								core/Api/PatchSQL.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Api; | ||||
| 
 | ||||
| use Api\Parameter\StringType; | ||||
| use Configuration\DatabaseScript; | ||||
| use Objects\User; | ||||
| 
 | ||||
| class PatchSQL extends Request { | ||||
| 
 | ||||
|   public function __construct(User $user, bool $externalCall = false) { | ||||
|     parent::__construct($user, $externalCall, array( | ||||
|       "className" => new StringType("className", 64) | ||||
|     )); | ||||
|     $this->loginRequired = true; | ||||
|     $this->csrfTokenRequired = false; | ||||
|   } | ||||
| 
 | ||||
|   public function execute($values = array()) { | ||||
|     if (!parent::execute($values)) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     $className = $this->getParam("className"); | ||||
|     $fullClassName = "\\Configuration\\Patch\\" . $className; | ||||
|     $path = getClassPath($fullClassName, true); | ||||
|     if (!file_exists($path)) { | ||||
|       return $this->createError("File not found"); | ||||
|     } | ||||
| 
 | ||||
|     if(!class_exists($fullClassName)) { | ||||
|       return $this->createError("Class not found."); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       $reflection = new \ReflectionClass($fullClassName); | ||||
|       if (!$reflection->isInstantiable()) { | ||||
|         return $this->createError("Class is not instantiable"); | ||||
|       } | ||||
| 
 | ||||
|       if (!$reflection->isSubclassOf(DatabaseScript::class)) { | ||||
|         return $this->createError("Not a database script."); | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $obj = $reflection->newInstance(); | ||||
|       $queries = $obj->createQueries($sql); | ||||
|       if (!is_array($queries)) { | ||||
|         return $this->createError("Database script returned invalid values"); | ||||
|       } | ||||
| 
 | ||||
|       foreach($queries as $query) { | ||||
|         if (!$query->execute()) { | ||||
|           return $this->createError("Query error: " . $sql->getLastError()); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       $this->success = true; | ||||
|     } catch (\ReflectionException $e) { | ||||
|       return $this->createError("Error reflecting class: " . $e->getMessage()); | ||||
|     } | ||||
| 
 | ||||
|     return $this->success; | ||||
|   } | ||||
| } | ||||
| @ -187,6 +187,7 @@ class Request { | ||||
|   public function success() { return $this->success; } | ||||
|   public function loginRequired() { return $this->loginRequired; } | ||||
|   public function isExternalCall() { return $this->externalCall; } | ||||
|   public function clearError() { $this->success = true; $this->lastError = ""; } | ||||
| 
 | ||||
|   private function getMethod() { | ||||
|     $class = str_replace("\\", "/", get_class($this)); | ||||
|  | ||||
| @ -39,7 +39,7 @@ namespace Api\Settings { | ||||
| 
 | ||||
|        $query = $sql->select("name", "value") ->from("Settings"); | ||||
| 
 | ||||
|        if (!is_null($key) && !empty($key)) { | ||||
|        if (!is_null($key)) { | ||||
|          $query->where(new CondRegex(new Column("name"), $key)); | ||||
|        } | ||||
| 
 | ||||
|  | ||||
| @ -6,7 +6,7 @@ use Driver\SQL\SQL; | ||||
| use \Driver\SQL\Strategy\SetNullStrategy; | ||||
| use \Driver\SQL\Strategy\CascadeStrategy; | ||||
| 
 | ||||
| class CreateDatabase { | ||||
| class CreateDatabase extends DatabaseScript { | ||||
| 
 | ||||
|   // NOTE:
 | ||||
|   // explicit serial ids removed due to postgres' serial implementation
 | ||||
| @ -192,7 +192,10 @@ class CreateDatabase { | ||||
|       ->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user") | ||||
|       ->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user") | ||||
|       ->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions") | ||||
|       ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics"); | ||||
|       ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics") | ||||
|       ->addRow("PatchSQL", array(USER_GROUP_ADMIN), "Allows users to import database patches"); | ||||
| 
 | ||||
|     self::loadPatches($queries, $sql); | ||||
| 
 | ||||
|     return $queries; | ||||
|   } | ||||
| @ -225,4 +228,22 @@ class CreateDatabase { | ||||
|       "Best Regards<br>" . | ||||
|       "{{site_name}} Administration"; | ||||
|   } | ||||
| 
 | ||||
|   private static function loadPatches(&$queries, $sql) { | ||||
|     $patchDirectory = './core/Configuration/Patch/'; | ||||
|     if (file_exists($patchDirectory) && is_dir($patchDirectory)) { | ||||
|       $scan_arr = scandir($patchDirectory); | ||||
|       $files_arr = array_diff($scan_arr, array('.','..')); | ||||
|       foreach ($files_arr as $file) { | ||||
|         $suffix = ".class.php"; | ||||
|         if (endsWith($file, $suffix)) { | ||||
|           $className = substr($file, 0, strlen($file) - strlen($suffix)); | ||||
|           $className = "\\Configuration\\Patch\\$className"; | ||||
|           $method = "$className::createQueries"; | ||||
|           $patchQueries = call_user_func($method, $sql); | ||||
|           foreach($patchQueries as $query) $queries[] = $query; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										9
									
								
								core/Configuration/DatabaseScript.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										9
									
								
								core/Configuration/DatabaseScript.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Configuration; | ||||
| 
 | ||||
| use Driver\SQL\SQL; | ||||
| 
 | ||||
| abstract class DatabaseScript { | ||||
|   public static abstract function createQueries(SQL $sql); | ||||
| } | ||||
							
								
								
									
										72
									
								
								core/Configuration/Patch/file_api.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										72
									
								
								core/Configuration/Patch/file_api.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Configuration\Patch; | ||||
| 
 | ||||
| use Configuration\DatabaseScript; | ||||
| use Driver\SQL\SQL; | ||||
| use Driver\SQL\Column\Column; | ||||
| use Driver\SQL\Strategy\CascadeStrategy; | ||||
| use Driver\SQL\Strategy\UpdateStrategy; | ||||
| 
 | ||||
| class file_api extends DatabaseScript { | ||||
| 
 | ||||
|   public static function createQueries(SQL $sql) { | ||||
| 
 | ||||
|     $queries = array(); | ||||
| 
 | ||||
|     $queries[] = $sql->insert("ApiPermission", array("method", "groups", "description")) | ||||
|       ->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array("method" => new Column("method")))) | ||||
|       ->addRow("File/Download", array(), "Allows users to download files when logged in, or using a given token") | ||||
|       ->addRow("File/Upload", array(), "Allows users to upload files when logged in, or using a given token") | ||||
|       ->addRow("File/ValidateToken", array(), "Allows users to validate a given token") | ||||
|       ->addRow("File/RevokeToken", array(USER_GROUP_ADMIN), "Allows users to revoke a token") | ||||
|       ->addRow("File/ListFiles", array(), "Allows users to list all files assigned to an account") | ||||
|       ->addRow("File/ListTokens", array(USER_GROUP_ADMIN), "Allows users to list all tokens assigned to the virtual filesystem of an account") | ||||
|       ->addRow("File/CreateDirectory", array(), "Allows users to create a virtual directory") | ||||
|       ->addRow("File/Rename", array(), "Allows users to rename files in the virtual filesystem") | ||||
|       ->addRow("File/Move", array(), "Allows users to move files in the virtual filesystem") | ||||
|       ->addRow("File/Delete", array(), "Allows users to delete files in the virtual filesystem") | ||||
|       ->addRow("File/CreateUploadToken", array(USER_GROUP_ADMIN), "Allows users to create a token to upload files to the virtual filesystem assigned to the users account") | ||||
|       ->addRow("File/CreateDownloadToken", array(USER_GROUP_ADMIN), "Allows users to create a token to download files from the virtual filesystem assigned to the users account"); | ||||
| 
 | ||||
|     $queries[] = $sql->, array("request", "action", "target", "extra")) | ||||
|       ->onDuplicateKeyStrategy(new UpdateStrategy(array("request"), array("request" => new Column("request")))) | ||||
|       ->addRow("^/files(/.*)?$", "dynamic", "\\Documents\\Files", NULL); | ||||
| 
 | ||||
|     $queries[] = $sql->createTable("UserFile") | ||||
|       ->onlyIfNotExists() | ||||
|       ->addSerial("uid") | ||||
|       ->addBool("directory") | ||||
|       ->addString("name", 64, false) | ||||
|       ->addString("path", 512, true) | ||||
|       ->addInt("parent_id", true) | ||||
|       ->addInt("user_id", true) | ||||
|       ->primaryKey("uid") | ||||
|       ->unique("parent_id", "name") | ||||
|       ->foreignKey("parent_id", "UserFile", "uid", new CascadeStrategy()) | ||||
|       ->foreignKey("user_id", "User", "uid", new CascadeStrategy()); | ||||
| 
 | ||||
|     $queries[] = $sql->createTable("UserFileToken") | ||||
|       ->onlyIfNotExists() | ||||
|       ->addSerial("uid") | ||||
|       ->addString("token", 36, false) | ||||
|       ->addDateTime("valid_until", true) | ||||
|       ->addEnum("token_type", array("download", "upload")) | ||||
|       ->addInt("user_id") | ||||
|       # upload only:
 | ||||
|       ->addInt("maxFiles", true) | ||||
|       ->addInt("maxSize", true) | ||||
|       ->addString("extensions", 64, true) | ||||
|       ->primaryKey("uid") | ||||
|       ->foreignKey("user_id", "User", "uid"); | ||||
| 
 | ||||
|     $queries[] = $sql->createTable("UserFileTokenFile") | ||||
|       ->addInt("file_id") | ||||
|       ->addInt("token_id") | ||||
|       ->unique("file_id", "token_id") | ||||
|       ->foreignKey("file_id", "UserFile", "uid") | ||||
|       ->foreignKey("token_id", "UserFileToken", "uid"); | ||||
| 
 | ||||
|     return $queries; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										61
									
								
								core/Documents/Files.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										61
									
								
								core/Documents/Files.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Documents { | ||||
| 
 | ||||
|   use Documents\Files\FilesBody; | ||||
|   use Documents\Files\FilesHead; | ||||
|   use Elements\Document; | ||||
|   use Objects\User; | ||||
| 
 | ||||
|   class Files extends Document { | ||||
|     public function __construct(User $user, string $view = NULL) { | ||||
|       parent::__construct($user, FilesHead::class, FilesBody::class, $view); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| namespace Documents\Files { | ||||
| 
 | ||||
|   use Elements\Head; | ||||
|   use Elements\SimpleBody; | ||||
| 
 | ||||
|   class FilesHead extends Head { | ||||
| 
 | ||||
|     protected function initSources() { | ||||
|       $this->loadBootstrap(); | ||||
|       $this->loadFontawesome(); | ||||
|     } | ||||
| 
 | ||||
|     protected function initMetas() { | ||||
|       return array( | ||||
|         array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'), | ||||
|         array('name' => 'format-detection', 'content' => 'telephone=yes'), | ||||
|         array('charset' => 'utf-8'), | ||||
|         array("http-equiv" => 'expires', 'content' => '0'), | ||||
|         array("name" => 'robots', 'content' => 'noarchive') | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     protected function initRawFields() { | ||||
|       return array(); | ||||
|     } | ||||
| 
 | ||||
|     protected function initTitle() { | ||||
|       return "File Control Panel"; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class FilesBody extends SimpleBody { | ||||
| 
 | ||||
|     public function __construct($document) { | ||||
|       parent::__construct($document); | ||||
|     } | ||||
| 
 | ||||
|     protected function getContent() { | ||||
|       $html = "<noscript>" . $this->createErrorText("Javascript is required for this site to render.") . "</noscript>"; | ||||
|       $html .= "<div id=\"root\"></div>"; | ||||
|       return $html; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										7
									
								
								core/Elements/EmptyBody.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										7
									
								
								core/Elements/EmptyBody.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Elements\Body; | ||||
| 
 | ||||
| class EmptyBody extends Body { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										26
									
								
								core/Elements/EmptyHead.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										26
									
								
								core/Elements/EmptyHead.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Elements; | ||||
| 
 | ||||
| class EmptyHead extends Head { | ||||
| 
 | ||||
|   public function __construct($document) { | ||||
|     parent::__construct($document); | ||||
|   } | ||||
| 
 | ||||
|   protected function initSources() { | ||||
|   } | ||||
| 
 | ||||
|   protected function initMetas() { | ||||
|     return array( | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   protected function initRawFields() { | ||||
|     return array(); | ||||
|   } | ||||
| 
 | ||||
|   protected function initTitle() { | ||||
|     return ""; | ||||
|   } | ||||
| } | ||||
| @ -2,8 +2,6 @@ | ||||
| 
 | ||||
| namespace Elements; | ||||
| 
 | ||||
| use External\PHPMailer\Exception; | ||||
| 
 | ||||
| abstract class View extends StaticView { | ||||
| 
 | ||||
|   private Document $document; | ||||
| @ -81,9 +79,10 @@ abstract class View extends StaticView { | ||||
|     return $this->createList($items, "ul"); | ||||
|   } | ||||
| 
 | ||||
|   protected function createLink($link, $title=null) { | ||||
|   protected function createLink($link, $title=null, $classes="") { | ||||
|     if(is_null($title)) $title=$link; | ||||
|     return "<a href=\"$link\">$title</a>";
 | ||||
|     if(!empty($classes)) $classes = " class=\"$classes\"";
 | ||||
|     return "<a href=\"$link\"$classes>$title</a>";
 | ||||
|   } | ||||
| 
 | ||||
|   protected function createExternalLink($link, $title=null) { | ||||
| @ -123,14 +122,91 @@ abstract class View extends StaticView { | ||||
|     return $this->createStatusText("info", $text, $id, $hidden); | ||||
|   } | ||||
| 
 | ||||
|   protected function createStatusText($type, $text, $id="", $hidden=false) { | ||||
|   protected function createStatusText($type, $text, $id="", $hidden=false, $classes="") { | ||||
|     if(strlen($id) > 0) $id = " id=\"$id\"";
 | ||||
|     $hidden = ($hidden?" hidden" : ""); | ||||
|     return "<div class=\"alert alert-$type$hidden\" role=\"alert\"$id>$text</div>"; | ||||
|     if($hidden) $classes .= " hidden"; | ||||
|     if(strlen($classes) > 0) $classes = " $classes"; | ||||
|     return "<div class=\"alert alert-$type$hidden$classes\" role=\"alert\"$id>$text</div>"; | ||||
|   } | ||||
| 
 | ||||
|   protected function createBadge($type, $text) { | ||||
|     $text = htmlspecialchars($text); | ||||
|     return "<span class=\"badge badge-$type\">$text</span>";
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   protected function createJumbotron(string $content, bool $fluid=false, $class="") { | ||||
|     $jumbotronClass = "jumbotron" . ($fluid ? "-fluid" : ""); | ||||
|     if (!empty($class)) $jumbotronClass .= " $class"; | ||||
| 
 | ||||
|     return " | ||||
|       <div class=\"row\">
 | ||||
|         <div class=\"col-12\">
 | ||||
|           <div class=\"$jumbotronClass\"> | ||||
|             $content | ||||
|           </div> | ||||
|         </div> | ||||
|       </div>";
 | ||||
|   } | ||||
| 
 | ||||
|   public function createSimpleParagraph(string $content, string $class="") { | ||||
|     if($class) $class = " class=\"$class\"";
 | ||||
|     return "<p$class>$content</p>"; | ||||
|   } | ||||
| 
 | ||||
|   public function createParagraph($title, $id, $content) { | ||||
|     $id = replaceCssSelector($id); | ||||
|     $iconId = urlencode("$id-icon"); | ||||
|     return " | ||||
|       <div class=\"row mt-4\">
 | ||||
|         <div class=\"col-12\">
 | ||||
|           <h2 id=\"$id\" data-target=\"$iconId\" class=\"inlineLink\">$title</h2>
 | ||||
|           <hr/> | ||||
|           $content | ||||
|         </div> | ||||
|       </div>";
 | ||||
|   } | ||||
| 
 | ||||
|   protected function createBootstrapTable($data, string $classes="") { | ||||
|     $classes = empty($classes) ? "" : " $classes"; | ||||
|     $code = "<div class=\"container$classes\">";
 | ||||
|     foreach($data as $row) { | ||||
|       $code .= "<div class=\"row mt-2 mb-2\">"; | ||||
|       $columnCount = count($row); | ||||
|       if($columnCount > 0) { | ||||
|         $remainingSize = 12; | ||||
|         $columnSize = 12 / $columnCount; | ||||
|         foreach($row as $col) { | ||||
|           $size = ($columnSize <= $remainingSize ? $columnSize : $remainingSize); | ||||
|           $content = $col; | ||||
|           $class = ""; | ||||
|           $code .= "<div"; | ||||
| 
 | ||||
|           if(is_array($col)) { | ||||
|             $content = ""; | ||||
|             foreach($col as $key => $val) { | ||||
|               if(strcmp($key, "content") === 0) { | ||||
|                 $content = $val; | ||||
|               } else if(strcmp($key, "class") === 0) { | ||||
|                 $class = " " . $col["class"]; | ||||
|               } else if(strcmp($key, "cols") === 0 && is_numeric($val)) { | ||||
|                 $size = intval($val); | ||||
|               } else { | ||||
|                 $code .= " $key=\"$val\"";
 | ||||
|               } | ||||
|             } | ||||
| 
 | ||||
|             if(isset($col["class"])) $class = " " . $col["class"]; | ||||
|           } | ||||
| 
 | ||||
|           if($size <= 6) $class .= " col-md-" . intval($size * 2); | ||||
|           $code .= " class=\"col-lg-$size$class\">$content</div>";
 | ||||
|           $remainingSize -= $size; | ||||
|         } | ||||
|       } | ||||
|       $code .= "</div>"; | ||||
|     } | ||||
| 
 | ||||
|     $code .= "</div>"; | ||||
|     return $code; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <?php | ||||
| 
 | ||||
| define("WEBBASE_VERSION", "1.0.4"); | ||||
| define("WEBBASE_VERSION", "1.1.0"); | ||||
| 
 | ||||
| function getProtocol() { | ||||
|   return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https" : "http"; | ||||
| @ -75,6 +75,10 @@ function replaceCssSelector($sel) { | ||||
|   return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel)); | ||||
| } | ||||
| 
 | ||||
| function urlId($str) { | ||||
|   return urlencode(htmlspecialchars(preg_replace("[: ]","-", $str))); | ||||
| } | ||||
| 
 | ||||
| function getClassPath($class, $suffix = true) { | ||||
|   $path = str_replace('\\', '/', $class); | ||||
|   $path = array_values(array_filter(explode("/", $path))); | ||||
| @ -134,7 +138,6 @@ function serveStatic(string $webRoot, string $file) { | ||||
|     $length = $size; | ||||
| 
 | ||||
|     if (isset($_SERVER['HTTP_RANGE'])) { | ||||
|       $partialContent = true; | ||||
|       preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches); | ||||
|       $offset = intval($matches[1]); | ||||
|       $length = intval($matches[2]) - $offset; | ||||
| @ -166,4 +169,4 @@ function parseClass($class) { | ||||
|   $parts = explode("\\", $class); | ||||
|   $parts = array_map('ucfirst', $parts); | ||||
|   return implode("\\", $parts); | ||||
| } | ||||
| } | ||||
|  | ||||
							
								
								
									
										1
									
								
								files/uploaded/.htaccess
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								files/uploaded/.htaccess
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| DENY FROM ALL | ||||
| @ -27,6 +27,8 @@ $(document).ready(function () { | ||||
|     } | ||||
| 
 | ||||
|     // Login
 | ||||
|     $("#username").keypress(function (e) { if(e.which == 13) $("#password").focus(); }); | ||||
|     $("#password").keypress(function (e) { if(e.which == 13) $("#btnLogin").click(); }); | ||||
|     $("#btnLogin").click(function() { | ||||
|         const username = $("#username").val(); | ||||
|         const password = $("#password").val(); | ||||
| @ -45,7 +47,7 @@ $(document).ready(function () { | ||||
|                 btn.prop("disabled", false); | ||||
|                 $("#password").val(""); | ||||
|                 createdDiv.hide(); | ||||
|                 showAlert(res.msg); | ||||
|                 showAlert("danger", res.msg); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| @ -172,4 +174,4 @@ $(document).ready(function () { | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user