* @license PHP License * @package WB * @subpackage rest */ WBClass::load('WBRest_Component' , 'WBUser' ); /** * Rest Component: Explorer * * * * @version 0.2.1 * @package WB * @subpackage rest */ class WBRest_Component_Explorer extends WBRest_Component { /** * File object * @var WBVFS_File */ protected $file; /** * File explorer * @var WBVFS_Explorer */ protected $expl; /** * Table * @var WBDatasource_Table */ protected $table; /** * current user * @var WBUser */ protected $user; /** * downloadable file url * @var string */ protected $urlFmt; /** * 2nd constructor * */ public function init() { $this->user = WBUser::getCurrent(); $this->urlFmt = '[[PROTOCOL]]://[[SERVER]][[SERVICE_FILE]]/%s'; } /** * load file object * * Helber function to initialize file object */ public function startFile() { if ($this->file) { return; } $param = array( 'user' => $this->user->getId() ); $this->file = WBClass::create('WBVFS_File', $param); } /** * load explorer object * * Helper function to initilialize file explorer object */ public function startExplorer() { if ($this->expl) { return; } $param = array( 'user' => $this->user->getId() ); $this->expl = WBClass::create('WBVFS_Explorer', $param); } /** * handle empty request * */ public function emptyRequest() { $this->startFile(); $mega = 1024 * 1024; $quota = $this->file->getQuota(); $this->out['usage'] = $quota; $this->out['upload'] = array( 'filesize' => 100 * $mega, 'chunksize' => 2 * $mega ); } /** * upload files in chunks * * - PUT initilalize new chunked upload * - GET fetch a list of initialized uploads * - POST add chunk to upload * - DELETE cancel upload and remove everything left behind * * @param unknown_type $id */ public function handleUpload($id = null) { // start table $this->table = WBClass::create('WBDatasource_Table'); // PUT if ($this->method == WBRest::METHOD_PUT) { if (!empty($id)) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:1', 'message' => patI18n::dgettext('wombat', 'Id is not allowed for PUT') ); return; } if ($this->table->count('vfsfileupload', null, $this->user->getId()) > 50) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:2', 'message' => patI18n::dgettext('wombat', 'Cannot upload more than 5 files at once.') ); return; } $this->processForm('upload/put'); return; } // load upload data $node = $this->table->get('vfsfileupload', $id, $this->user->getId()); // GET if ($this->method == WBRest::METHOD_GET) { $this->out['uploads'] = $node; return; } // validate id if ($id == null || count($node) != 1) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:3', 'message' => patI18n::dgettext('wombat', 'Upload id is invalid') ); return; } // DELETE if ($this->method == WBRest::METHOD_DELETE) { $this->table->delete('vfsfileupload', $id); $this->unlinkUpload($id); return; } // POST $this->in['id'] = $id; $this->processForm('upload/post'); } /** * directory operations * * - list dir with GET * - create folder with PUT * - rename and move folder with POST * - delete folder and all contents with DELETE * * @param string $dir */ public function handleDir($dir = null) { $this->startExplorer(); if (empty($dir)) { $dir = null; } try{ $node = $this->expl->getDir($dir); $dir = $node['id']; } catch (WBException_Datasource $e) { if ($e->getCode() != 1) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':5', 'message' => patI18n::dgettext('wombat', 'Selected folder does not exist') ); return; } } $this->in['dir'] = $dir; switch ($this->method) { case WBRest::METHOD_PUT: $this->processForm('dir/put'); break; case WBRest::METHOD_POST: $this->processForm('dir/post'); break; case WBRest::METHOD_GET: return $this->listDir($dir); break; case WBRest::METHOD_DELETE: if (!$dir) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':6', 'message' => patI18n::dgettext('wombat', 'Cannot remove root folder') ); } $this->expl->rmDir($dir); break; default: break; } } /** * Handle file * * - Fetch file info with GET * - Update file's name, description or folder with POST * - Create a new file from upload with PUT * - Delete file with DELETE * * @param string $id */ public function handleFile($id) { if (empty($id)) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':1', 'message' => patI18n::dgettext('wombat', 'Id is required') ); return; } $this->startFile(); // create media file if ($this->method == WBRest::METHOD_PUT) { $this->in['id'] = $id; $this->processForm('file/put'); return; } $this->file = $this->file->loadByObscureId($id); $this->out['obscureid'] = $id; if (!$this->file->isOK()) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':2', 'message' => patI18n::dgettext('wombat', 'File not found') ); return; } // select action by method switch ($this->method) { case WBRest::METHOD_PUT; WBClass::load('WBException_Never'); throw new WBException_Never('PUT method is handled a few lines above, but nut here!', 1, __CLASS__); break; case WBRest::METHOD_DELETE: if ($this->file->delete() == $this->file->getId()) { $this->status = self::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':3', 'message' => patI18n::dgettext('wombat', 'Failed to delete file') ); } break; // change file's name or descriptin case WBRest::METHOD_POST: $this->processForm('file/post'); $this->mergeFileInfo($this->out); break; // deliverf file info case WBRest::METHOD_GET: $this->mergeFileInfo($this->out); break; } } /** * add file information to output * * this is just an auxillary function * * @param array $destination */ protected function mergeFileInfo(&$des) { $des['uri'] = $this->file->getUri(); $des['url'] = sprintf($this->urlFmt, $this->file->getUri()); $des['name'] = $this->file->getName(); $des['description'] = $this->file->getDescription(); $des['folder'] = $this->file->getDirId(); $des['size'] = $this->file->getSize(); $des['md5'] = $this->file->getMd5(); $des['mime'] = $this->file->getMime(WBVFS_File::MIME_MAJOR | WBVFS_File::MIME_MINOR); $des['created'] = $this->file->getCreated(); $des['changed'] = $this->file->getChanged(); $des['views'] = $this->file->getViews(); } /** * list files and directories * * Like "ls -l" * * @param string $dir */ protected function listDir($dir = null) { $this->startExplorer(); $this->startFile(); $this->out['dirs'] = array(); $this->out['files'] = array(); $dirs = $this->expl->lsDir($dir); $files = $this->file->listDir($dir); foreach($files as &$f) { $f['url'] = sprintf($this->urlFmt, $f['uri']); unset($f['info']); unset($f['id']); } $this->out['dirs'] = $dirs; $this->out['files'] = $files; } /** * remove temporary upload folder * * Remove folder and all chunks * * @param string $id */ protected function unlinkUpload($id) { $dir = WBParam::get('wb/dir/base') . '/var/spool/rest/upload/' . $id; if (!is_dir($dir)) { return; } foreach (new DirectoryIterator($dir) as $file) { if ($file->isDot()) { continue; } if ($file->isDir()) { WBClass::load('WBException_File'); throw new WBException_File('File expected, but got directory!', 1, __CLASS__); } unlink($file->getPathName()); } rmdir($dir); } /** * event handler for valid form "dir/put" * * rename dir, move folder to other folder * * @param $form * @param $values * @return unknown_type */ protected function onUploadPutValid(patForms $form, $values) { $this->startFile(); $quota = $this->file->checkQuota($values['size']); if ($quota != WBVFS_FILE::QUOTA_OK) { $this->out['quota'] = $quota; $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:2', 'message' => patI18n::dgettext('wombat', 'Quota exceeded') ); return; } $uPrimary = $this->table->getIdentifier('user'); // check for same md5 sum $clause = array(); $clause[] = array( 'field' => $uPrimary, 'value' => $this->user->getId() ); $clause[] = array( 'field' => 'md5', 'value' => $values['md5'] ); if ($this->table->count('vfsfileupload', null, null, $clause)) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:3', 'message' => patI18n::dgettext('wombat', 'There is already an upload with this MD5 sum') ); return; } $data = array( $uPrimary => $this->user->getId(), 'size' => $values['size'], 'md5' => $values['md5'] ); $id = $this->table->save('vfsfileupload', '__new', $data); $this->out['id'] = $id; } /** * Invalid form "dir/put" * * @param patForms $form */ protected function onUploadPutInvalid(patForms $form) { $this->addValidationErrors($form); } /** * event handler for valid form "upload/post" * * save new chunk at position * * @param $form * @param $values * @return unknown_type */ protected function onUploadPostValid(patForms $form, $values) { $dir = WBParam::get('wb/dir/base') . '/var/spool/rest/upload/' . $this->in['id']; if (!is_dir($dir)) { mkdir($dir, 0777, true); } $cnt = base64_decode($values['chunk'], true); if (!$cnt) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:3', 'message' => patI18n::dgettext('wombat', 'Could not decode chunk using base64.') ); return; } $file = $dir . '/' . $values['position']; file_put_contents($file, $cnt); chmod($file, 0666); $this->out['position'] = $values['position']; $this->out['size'] = strlen($cnt); } /** * Invalid form "uploadr/post" * * @param patForms $form */ protected function onUploadPostInvalid(patForms $form) { $this->addValidationErrors($form); } /** * event handler for valid form "file/put" * * crate file from uploaded chunks * * @param $form * @param $values */ protected function onFilePutValid(patForms $form, $values) { // prepare output $this->out['id'] = null; $this->out['file'] = array(); $this->out['upload'] = array(); // put file in special folder if (isset($values['folder'])) { if (!$values['folder']) { $values['folder'] = null; } $this->startExplorer(); try{ $folder = $this->expl->getDir($values['folder']); } catch (WBException_Datasource $e) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':5', 'message' => patI18n::dgettext('wombat', 'Selected folder does not exist') ); return; } if (!$values['folder']) { $values['folder'] = 0; } $this->file->setCurrentDir($values['folder']); } // uploaded chunks $this->table = WBClass::create('WBDatasource_Table'); $upload = $this->table->get('vfsfileupload', $this->in['id'], $this->user->getId()); if ($this->in['id'] == null || count($upload) != 1) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':upload:3', 'message' => patI18n::dgettext('wombat', 'Upload id is invalid') ); return; } // merge upload info $this->out['upload'] = $upload[0]; // lookup upload chunks $dir = WBParam::get('wb/dir/base') . '/var/spool/rest/upload/' . $this->in['id']; if (!is_dir($dir)) { $this->status = WBRest::STATUS_ERROR; $this->error[] = array( 'code' => __CLASS__ . ':file:3', 'message' => patI18n::dgettext('wombat', 'Could not find any upload chunks') ); unlink($file); return; } // create temporary file from chunks $file = tempnam(WBParam::get('wb/dir/base'), 'upload'); // glue chunks together $fh = fopen($file, 'w'); foreach (new DirectoryIterator($dir) as $chunk) { if ($chunk->isDot()) { continue; } fseek($fh, $chunk->getFilename()); fwrite($fh, file_get_contents($chunk->getPathName()), $chunk->getSize()); } fclose($fh); // validate filesize clearstatcache(); if (filesize($file) != $this->out['upload']['size']) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':file:3', 'message' => patI18n::dgettext('wombat', 'File size does not match.') ); unlink($file); return; } // validate md5 sum if (md5_file($file) != $this->out['upload']['md5']) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':file:4', 'message' => patI18n::dgettext('wombat', 'MD5 checksum does not match.') ); unlink($file); return; } // create file $this->file = $this->file->import($file, $values['name']); if ($this->file->isOK()) { $this->out['id'] = $this->file->getId(); $this->mergeFileInfo($this->out['file']); $this->table->delete('vfsfileupload', $this->in['id']); $this->unlinkUpload($this->in['id']); return; } unlink($file); } /** * event handler for invalud form "file/put" * * * @uses addValidationErrors() * @param $form * @return unknown_type */ protected function onFilePutInvalid(patForms $form) { $this->addValidationErrors($form); } /** * event handler for valid form "file" * * Allow to save file attribites: * - name * - description * - folder * * @param $form * @param $values * @return unknown_type */ protected function onFilePostValid(patForms $form, $values) { foreach($values as $k => $v) { $v = $v; if (empty($v)) { unset($values[$k]); } } if (empty($values)) { $this->status = WBRest::STATUS_FAILED; $this->error[] = array( 'code' => __CLASS__ . ':4', 'message' => patI18n::dgettext('wombat', 'Failed to update file, nothing to save here!') ); } // rename folder column if (isset($values['folder'])) { $this->startExplorer(); $values[$this->expl->getDirIdentifier()] = $values['folder']; unset($values['folder']); } $this->file->save($values); } /** * event handler for invalid form "file/post" * * @uses addValidationErrors() * @param $form * @return unknown_type */ protected function onFilePostInvalid(patForms $form) { $this->addValidationErrors($form); } /** * event handler for valid form "dir/put" * * mkdir * * @param $form * @param $values * @return unknown_type */ protected function onDirPutValid(patForms $form, $values) { $this->expl->mkDir($this->in['dir'], $values['name']); } /** * Invalid form "dir/put" * * @param patForms $form */ protected function onDirPutInvalid(patForms $form) { $this->addValidationErrors($form); } /** * event handler for valid form "dir/post" * * rename dir, move folder to other folder * * @param $form * @param $values * @return unknown_type */ protected function onDirPostValid(patForms $form, $values) { $name = null; if (strlen($values['name'])) { $name = $values['name']; } if (strlen($values['folder'])) { if ($values['folder'] == 0) { $values['folder'] = null; } $this->expl->mvDir($this->in['dir'], $values['folder'], $name); return; } $this->expl->renameDir($this->in['dir'], $name); } /** * Invalid form "dir/post" * * rename dir, move folder to other folder * * @param patForms $form */ protected function onDirPostInvalid(patForms $form) { $this->addValidationErrors($form); } } ?>