Rename + list files bugfix
This commit is contained in:
		
							parent
							
								
									0df60ddd96
								
							
						
					
					
						commit
						42d2a3d919
					
				| @ -111,26 +111,65 @@ namespace Api { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     protected function createFileList($res) { | ||||
|       $files = array(); | ||||
|       foreach ($res as $row) { | ||||
|         if ($row["uid"] === null) continue; | ||||
|         $fileId = $row["uid"]; | ||||
|         $parentId = $row["parentId"]; | ||||
|         $fileName = $row["name"]; | ||||
|         $isDirectory = $row["directory"]; | ||||
|         $fileElement = array("uid" => $fileId, "name" => $fileName, "isDirectory" => $isDirectory); | ||||
|         if ($isDirectory) { | ||||
|           $fileElement["items"] = array(); | ||||
|     private function insert(&$files, $row) { | ||||
|       $entry = array("uid" => $row["uid"], "name" => $row["name"], "isDirectory" => $row["directory"]); | ||||
|       if ($row["directory"]) { | ||||
|         $entry["items"] = array(); | ||||
|       } else { | ||||
|           $fileElement["size"] = @filesize($row["path"]); | ||||
|           $fileElement["mimeType"] = @mime_content_type($row["path"]); | ||||
|         $entry["size"] = @filesize($row["path"]); | ||||
|         $entry["mimeType"] = @mime_content_type($row["path"]); | ||||
|       } | ||||
| 
 | ||||
|         $dir =& $this->findDirectory($files, $parentId); | ||||
|         $dir[$fileId] = $fileElement; | ||||
|         unset($dir); | ||||
|       $dir =& $this->findDirectory($files, $row["parentId"]); | ||||
|       if ($dir !== $files || $row["parentId"] === null) { | ||||
|         $dir[$row["uid"]] = $entry; | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     protected function createFileList($res) { | ||||
| 
 | ||||
|       $files = array(); | ||||
| 
 | ||||
|       // Create temporary files
 | ||||
|       $tempFiles = []; | ||||
|       foreach ($res as $row) { | ||||
|         $fileId = $row["uid"]; | ||||
|         $parentId = $row["parentId"]; | ||||
|         if ($fileId === null) { | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         // insert all files/dirs in the root directory
 | ||||
|         if ($parentId === null) { | ||||
|           $this->insert($files, $row); | ||||
|         } else { | ||||
|           // save other files temporary
 | ||||
|           $tempFiles[$fileId] = $row; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // repeatedly try to find the directory
 | ||||
|       // a bit ugly code over here..
 | ||||
|       $filesToProcess = count($tempFiles); | ||||
|       while (count($tempFiles) > 0) { | ||||
| 
 | ||||
|         foreacH($tempFiles as $fileId => $row) { | ||||
|           if ($this->insert($files, $row)) { | ||||
|             unset($tempFiles[$fileId]); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         // some files could not be inserted for some reason
 | ||||
|         if (count($tempFiles) === $filesToProcess) { | ||||
|           break; | ||||
|         } else { | ||||
|           $filesToProcess = count($tempFiles); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return $files; | ||||
|     } | ||||
| 
 | ||||
| @ -434,9 +473,175 @@ namespace Api\File { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class Rename extends FileAPI { } | ||||
|   class Rename extends FileAPI { | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT), | ||||
|         "name" => new StringType("name", 64, false), | ||||
|         "token" => new StringType("token", 36, true, null) | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|   class Move extends FileAPI { } | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $fileId = $this->getParam("id"); | ||||
|       $newName = $this->getParam("name"); | ||||
|       $token = $this->getParam("token"); | ||||
| 
 | ||||
|       if (!$this->user->isLoggedIn() && is_null($token)) { | ||||
|         return $this->createError("Permission denied (expected token)"); | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
| 
 | ||||
|       $selectColumns = ($token !== null ? array("parent_id", "user_id") : array("parent_id")); | ||||
|       $query =  $sql->select(...$selectColumns)->from("UserFile"); | ||||
|       $this->filterFiles($sql, $query, $fileId, $token); | ||||
|       $res = $query->execute(); | ||||
| 
 | ||||
|       $this->success = $res !== false; | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } else if (empty($res)) { | ||||
|         return $this->createError("File not found"); | ||||
|       } | ||||
| 
 | ||||
|       // check if file already exists
 | ||||
|       $parentId = $res[0]["parent_id"]; | ||||
|       $userId = ($token === null) ? $this->user->getId() : $res[0]["user_id"]; | ||||
|       $res = $sql->select($sql->count()) | ||||
|         ->from("UserFile") | ||||
|         ->where(new Compare("name", $newName)) | ||||
|         ->where(new Compare("parent_id", $parentId)) | ||||
|         ->where(new Compare("user_id", $userId)) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = $res !== false; | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } else if ($res[0]["count"] > 0) { | ||||
|         return $this->createError("A file or directory with this name does already exist"); | ||||
|       } | ||||
| 
 | ||||
|       $res = $sql->update("UserFile") | ||||
|         ->set("name", $newName) | ||||
|         ->where(new Compare("parent_id", $parentId)) | ||||
|         ->where(new Compare("user_id", $userId)) | ||||
|         ->where(new Compare("uid", $fileId)) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = $res !== false; | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       return $this->success; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   class Move extends FileAPI { | ||||
| 
 | ||||
|     public function __construct(User $user, bool $externalCall = false) { | ||||
|       parent::__construct($user, $externalCall, array( | ||||
|         'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null), | ||||
|         "id" => new ArrayType("id", Parameter::TYPE_INT, true), | ||||
|       )); | ||||
|       $this->loginRequired = true; | ||||
|     } | ||||
| 
 | ||||
|     public function execute($values = array()) { | ||||
|       if (!parent::execute($values)) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $sql = $this->user->getSQL(); | ||||
|       $fileIds = array_unique($this->getParam("id")); | ||||
|       $destinationId = $this->getParam("parentId"); | ||||
|       $userId = $this->user->getId(); | ||||
| 
 | ||||
|       // check, if we are trying to do some illegal move
 | ||||
|       if ($destinationId !== null && in_array($destinationId,  $fileIds)) { | ||||
|         return $this->createError("Cannot move a file to the given directory"); | ||||
|       } | ||||
| 
 | ||||
|       $query = $sql->select("UserFile.uid", "directory", "name", "parent_id")->from("UserFile"); | ||||
|       $this->filterFiles($sql, $query, array_merge($fileIds, [$destinationId])); | ||||
|       $query->orderBy("parent_id")->ascending(); | ||||
| 
 | ||||
|       $res = $query->execute(); | ||||
|       $this->success = ($res !== false); | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       $foundFiles = array(); | ||||
|       $moveFiles = array(); | ||||
|       $skipDirectories = array(); | ||||
| 
 | ||||
|       foreach($res as $row) { | ||||
|         $fileId = $row["uid"]; | ||||
|         $isDirectory = $row["directory"]; | ||||
|         $fileName = $row["name"]; | ||||
|         $filesDirectory = $row["parent_id"]; | ||||
|         if ($fileId === $destinationId) { | ||||
|           if (!$isDirectory) { | ||||
|             return $this->createError("Cannot move file: Destination is not a directory"); | ||||
|           } | ||||
|         } else { | ||||
|           $foundFiles[] = $fileId; | ||||
| 
 | ||||
|           if ($filesDirectory === null || !in_array($filesDirectory, $skipDirectories)) { | ||||
|             $moveFiles[$fileId] = $fileName; | ||||
|           } | ||||
| 
 | ||||
|           if ($isDirectory) { | ||||
|             $skipDirectories[] = $fileId; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (count($foundFiles) !== count($fileIds)) { | ||||
|         foreach ($fileIds as $fileId) { | ||||
|           if (!array_key_exists($fileId, $foundFiles)) { | ||||
|             return $this->createError("File not found: $fileId"); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // check for duplicates
 | ||||
|       $res = $sql->select($sql->count()) | ||||
|         ->from("UserFile") | ||||
|         ->where(new Compare("user_id", $userId)) | ||||
|         ->where(new Compare("parent_id", $destinationId)) | ||||
|         ->where(new CondIn("name", array_values($moveFiles))) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = $res !== false; | ||||
|       $this->lastError = $sql->getLastError(); | ||||
|       if (!$this->success) { | ||||
|         return false; | ||||
|       } else if($res[0]["count"] > 0) { | ||||
|         return $this->createError("Cannot move files: the destination directory already contains one or " . | ||||
|           "more files or directories with the same name"); | ||||
|       } | ||||
| 
 | ||||
|       // update parent ids
 | ||||
|       $res = $sql->update("UserFile") | ||||
|         ->set("parent_id", $destinationId) | ||||
|         ->where(new Compare("user_id", $userId)) | ||||
|         ->where(new CondIn("uid", array_keys($moveFiles))) | ||||
|         ->execute(); | ||||
| 
 | ||||
|       $this->success = $res !== false; | ||||
|       $this->lastError = $sql->getLastError(); | ||||
| 
 | ||||
|       return $this->success; | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   class Upload extends FileAPI { | ||||
| 
 | ||||
|  | ||||
| @ -64,6 +64,14 @@ export default class API { | ||||
|         return this.apiCall("file/createDirectory", { name: name, parentId: parentId }); | ||||
|     } | ||||
| 
 | ||||
|     moveFiles(files, parentId = null) { | ||||
|         return this.apiCall("file/move", { id: files, parentId: parentId }); | ||||
|     } | ||||
| 
 | ||||
|     rename(id, name, token = null) { | ||||
|         return this.apiCall("file/rename", { id: id, name: name, token: token }); | ||||
|     } | ||||
| 
 | ||||
|     getRestrictions() { | ||||
|         return this.apiCall("file/getRestrictions"); | ||||
|     } | ||||
|  | ||||
| @ -17,10 +17,14 @@ export function FileBrowser(props) { | ||||
|     let directories = props.directories || {}; | ||||
|     let restrictions = props.restrictions || {maxFiles: 0, maxSize: 0, extensions: ""}; | ||||
| 
 | ||||
|     let [popup, setPopup] = useState({visible: false, directoryName: "", directory: 0, type: "upload"}); | ||||
|     let [popup, setPopup] = useState({ visible: false, name: "", directory: 0, target: null, type: "createDirectory" }); | ||||
|     let [alerts, setAlerts] = useState([]); | ||||
|     let [filesToUpload, setFilesToUpload] = useState([]); | ||||
| 
 | ||||
|     function canUpload() { | ||||
|         return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload"); | ||||
|     } | ||||
| 
 | ||||
|     function svgMiddle(key, scale = 1.0) { | ||||
|         let width = 48 * scale; | ||||
|         let height = 64 * scale; | ||||
| @ -122,10 +126,6 @@ export function FileBrowser(props) { | ||||
|         } | ||||
|     }, [filesToUpload]); | ||||
| 
 | ||||
|     function canUpload() { | ||||
|         return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload"); | ||||
|     } | ||||
| 
 | ||||
|     function onAddUploadFiles(acceptedFiles, rejectedFiles) { | ||||
| 
 | ||||
|         if (rejectedFiles && rejectedFiles.length > 0) { | ||||
| @ -179,6 +179,13 @@ export function FileBrowser(props) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     let selectedIds = getSelectedIds(); | ||||
|     let selectedCount = selectedIds.length; | ||||
|     let uploadZone = <></>; | ||||
|     let writePermissions = canUpload(); | ||||
|     let uploadedFiles = []; | ||||
|     let alertElements = []; | ||||
| 
 | ||||
|     function createFileList(elements, indentation = 0) { | ||||
|         let rows = []; | ||||
|         let rowIndex = 0; | ||||
| @ -224,6 +231,10 @@ export function FileBrowser(props) { | ||||
|                         <input type={"checkbox"} checked={!!fileElement.selected} | ||||
|                                onChange={(e) => onSelectFile(e, uid)} | ||||
|                         /> | ||||
|                         { writePermissions ? | ||||
|                             <Icon icon={"pencil-alt"} title={"Rename"} className={"ml-2 clickable text-secondary"} | ||||
|                                 style={{marginTop: "-17px"}} onClick={() => onPopupOpen("rename", uid)} /> : | ||||
|                             <></> } | ||||
|                     </td> | ||||
|                 </tr> | ||||
|             ); | ||||
| @ -236,14 +247,6 @@ export function FileBrowser(props) { | ||||
|         return rows; | ||||
|     } | ||||
| 
 | ||||
|     let rows = createFileList(files); | ||||
|     let selectedIds = getSelectedIds(); | ||||
|     let selectedCount = selectedIds.length; | ||||
|     let uploadZone = <></>; | ||||
|     let writePermissions = canUpload(); | ||||
|     let uploadedFiles = []; | ||||
|     let alertElements = []; | ||||
| 
 | ||||
|     for (let i = 0; i < alerts.length; i++) { | ||||
|         const alert = alerts[i]; | ||||
|         alertElements.push( | ||||
| @ -287,6 +290,7 @@ export function FileBrowser(props) { | ||||
|         setFilesToUpload(files); | ||||
|     } | ||||
| 
 | ||||
|     let rows = createFileList(files); | ||||
|     if (writePermissions) { | ||||
| 
 | ||||
|         for (let i = 0; i < filesToUpload.length; i++) { | ||||
| @ -340,6 +344,54 @@ export function FileBrowser(props) { | ||||
|         </>; | ||||
|     } | ||||
| 
 | ||||
|     let singleButton = { | ||||
|         gridColumnStart: 1, | ||||
|         gridColumnEnd: 3, | ||||
|         width: "40%", | ||||
|         margin: "0 auto" | ||||
|     }; | ||||
| 
 | ||||
|     function createPopup() { | ||||
|         let title = ""; | ||||
|         let inputs = []; | ||||
| 
 | ||||
|         if (popup.type === "createDirectory" || popup.type === "moveFiles") { | ||||
|             inputs.push( | ||||
|                 <div className={"form-group"} key={"select-directory"}> | ||||
|                     <label>Destination Directory:</label> | ||||
|                     <select value={popup.directory} className={"form-control"} | ||||
|                             onChange={(e) => onPopupChange(e, "directory")}> | ||||
|                         {options} | ||||
|                     </select> | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if (popup.type === "createDirectory" || popup.type === "rename") { | ||||
|             inputs.push( | ||||
|                 <div className={"form-group"} key={"input-name"}> | ||||
|                     <label>{ popup.type === "createDirectory" ? "Create Directory" : "New Name" }</label> | ||||
|                     <input type={"text"} className={"form-control"} value={popup.name} maxLength={32} | ||||
|                            placeholder={"Enter name…"} | ||||
|                            onChange={(e) => onPopupChange(e, "name")}/> | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if (popup.type === "createDirectory") { | ||||
|             title = "Create Directory"; | ||||
|         } else if (popup.type === "moveFiles") { | ||||
|             title = "Move Files"; | ||||
|         } else if (popup.type === "rename") { | ||||
|             title = "Rename File or Directory"; | ||||
|         } | ||||
| 
 | ||||
|         return <Popup title={title} visible={popup.visible} buttons={["Ok", "Cancel"]} onClose={onPopupClose} | ||||
|                       onClick={onPopupButton}> | ||||
|             { inputs } | ||||
|         </Popup> | ||||
|     } | ||||
| 
 | ||||
|     return <> | ||||
|         <h4> | ||||
|             <Icon icon={"sync"} className={"mx-3 clickable small"} onClick={fetchFiles}/> | ||||
| @ -366,12 +418,11 @@ export function FileBrowser(props) { | ||||
|             </tbody> | ||||
|         </table> | ||||
|         <div className={"file-control-buttons"}> | ||||
|             <button type={"button"} className={"btn btn-success"} disabled={selectedCount === 0} | ||||
|             <button type={"button"} className={"btn btn-success"} disabled={selectedCount === 0} style={!writePermissions ? singleButton : {}} | ||||
|                     onClick={() => onDownload(selectedIds)}> | ||||
|                 <Icon icon={"download"} className={"mr-1"}/> | ||||
|                 Download Selected Files ({selectedCount}) | ||||
|             </button> | ||||
|             <span/> | ||||
|             { | ||||
|                 writePermissions ? | ||||
|                     <> | ||||
| @ -381,11 +432,18 @@ export function FileBrowser(props) { | ||||
|                             Delete Selected Files ({selectedCount}) | ||||
|                         </button> | ||||
|                         {api.loggedIn ? | ||||
|                             <> | ||||
|                                 <button type={"button"} className={"btn btn-info"} | ||||
|                                     onClick={(e) => onPopupOpen("createDirectory")}> | ||||
|                                 <Icon icon={"plus"} className={"mr-1"}/> | ||||
|                                 Create Directory | ||||
|                             </button> : | ||||
|                                 </button> | ||||
|                                 <button type={"button"} className={"btn btn-primary"} disabled={selectedCount === 0} | ||||
|                                         onClick={(e) => onPopupOpen("moveFiles")}> | ||||
|                                     <Icon icon={"plus"} className={"mr-1"}/> | ||||
|                                     Move Selected Files ({selectedCount}) | ||||
|                                 </button> | ||||
|                             </>: | ||||
|                             <></> | ||||
|                         } | ||||
|                     </> | ||||
| @ -402,28 +460,11 @@ export function FileBrowser(props) { | ||||
|         <div> | ||||
|             {alertElements} | ||||
|         </div> | ||||
|         <Popup title={"Create Directory"} visible={popup.visible} buttons={["Ok", "Cancel"]} onClose={onPopupClose} | ||||
|                onClick={onPopupButton}> | ||||
|             <div className={"form-group"}> | ||||
|                 <label>Destination Directory:</label> | ||||
|                 <select value={popup.directory} className={"form-control"} | ||||
|                         onChange={(e) => onPopupChange(e, "directory")}> | ||||
|                     {options} | ||||
|                 </select> | ||||
|             </div> | ||||
|             {popup.type !== "upload" ? | ||||
|                 <div className={"form-group"}> | ||||
|                     <label>Directory Name</label> | ||||
|                     <input type={"text"} className={"form-control"} value={popup.directoryName} maxLength={32} | ||||
|                            placeholder={"Enter name…"} | ||||
|                            onChange={(e) => onPopupChange(e, "directoryName")}/> | ||||
|                 </div> : <></> | ||||
|             } | ||||
|         </Popup> | ||||
|         { createPopup() } | ||||
|     </>; | ||||
| 
 | ||||
|     function onPopupOpen(type) { | ||||
|         setPopup({...popup, visible: true, type: type}); | ||||
|     function onPopupOpen(type, target = null) { | ||||
|         setPopup({...popup, visible: true, type: type, target: target}); | ||||
|     } | ||||
| 
 | ||||
|     function onPopupClose() { | ||||
| @ -439,13 +480,29 @@ export function FileBrowser(props) { | ||||
|         if (btn === "Ok") { | ||||
|             let parentId = popup.directory === 0 ? null : popup.directory; | ||||
|             if (popup.type === "createDirectory") { | ||||
|                 api.createDirectory(popup.directoryName, parentId).then((res) => { | ||||
|                 api.createDirectory(popup.name, parentId).then((res) => { | ||||
|                     if (!res.success) { | ||||
|                         pushAlert(res, "Error creating directory"); | ||||
|                     } else { | ||||
|                         fetchFiles(); | ||||
|                     } | ||||
|                 }); | ||||
|             } else if (popup.type === "moveFiles") { | ||||
|                 api.moveFiles(selectedIds, parentId).then((res) => { | ||||
|                     if (!res.success) { | ||||
|                         pushAlert(res, "Error moving files"); | ||||
|                     } else { | ||||
|                         fetchFiles(); | ||||
|                     } | ||||
|                 }); | ||||
|             } else if (popup.type === "rename") { | ||||
|                 api.rename(popup.target, popup.name, tokenObj.valid ? tokenObj.value : null).then((res) => { | ||||
|                     if (!res.success) { | ||||
|                         pushAlert(res, "Error renaming file or directory"); | ||||
|                     } else { | ||||
|                         fetchFiles(); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -84,9 +84,9 @@ class FileControlPanel extends React.Component { | ||||
|                     if (this.onSelectFile(e, uid, fileElement.items)) { | ||||
|                         if (!e.target.checked) { | ||||
|                             fileElement.selected = false; | ||||
|                         } else if (this.getSelectedIds(fileElement.items, false).length === Object.values(fileElement.items).length) { | ||||
|                         }/* else if (this.getSelectedIds(fileElement.items, false).length === Object.values(fileElement.items).length) { | ||||
|                             fileElement.selected = true; | ||||
|                         } | ||||
|                         }*/ | ||||
|                         found = true; | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
							
								
								
									
										2
									
								
								js/files.min.js
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								js/files.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user