From 9582ed7696b7cbb286aabc28f7774cf1ace4a74b Mon Sep 17 00:00:00 2001 From: coderkun Date: Fri, 4 Apr 2014 13:21:17 +0200 Subject: [PATCH] implement UploadsAgent for user uploads --- agents/intermediate/UploadsAgent.inc | 35 ++++++ configs/AppConfig.inc | 10 +- controllers/UploadsController.inc | 174 +++++++++++++++++++++++++++ models/UploadsModel.inc | 148 +++++++++++++++++++++++ uploads/empty | 0 views/binary/uploads/index.tpl | 1 + 6 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 agents/intermediate/UploadsAgent.inc create mode 100644 controllers/UploadsController.inc create mode 100644 models/UploadsModel.inc create mode 100644 uploads/empty create mode 100644 views/binary/uploads/index.tpl diff --git a/agents/intermediate/UploadsAgent.inc b/agents/intermediate/UploadsAgent.inc new file mode 100644 index 00000000..457b6a49 --- /dev/null +++ b/agents/intermediate/UploadsAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to process and show user uploads. + * + * @author Oliver Hanraths + */ + class UploadsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/configs/AppConfig.inc b/configs/AppConfig.inc index 817b0114..541a51e2 100644 --- a/configs/AppConfig.inc +++ b/configs/AppConfig.inc @@ -62,7 +62,8 @@ 'locale' => 'locale', 'media' => 'media', 'questtypes' => 'questtypes', - 'temporary' => 'tmp' + 'temporary' => 'tmp', + 'uploads' => 'uploads' ); @@ -87,7 +88,9 @@ array('characters/(?!(index|character))', 'characters/index/$1', true), array('charactergroups/(?!(index|groupsgroup|group))', 'charactergroups/index/$1', true), array('charactergroupsquests/(?!(quest))', 'charactergroupsquests/quest/$1', true), - array('media/(.*)', 'media/$1?layout=binary', false) + array('media/(.*)', 'media/$1?layout=binary', false), + array('uploads/(.*)', 'uploads/$1?layout=binary', false), + array('uploads/(?!(index))', 'uploads/index/$1', true) ); @@ -104,7 +107,8 @@ //array('seminaries/seminary/(.*)', '$1', false) array('characters/index/(.*)', 'characters/$1', true), array('charactergroups/index/(.*)', 'charactergroups/$1', true), - array('charactergroupsquests/quest/(.*)', 'charactergroupsquests/$1', true) + array('charactergroupsquests/quest/(.*)', 'charactergroupsquests/$1', true), + array('uploads/index/(.*)', 'uploads/$1', true) ); diff --git a/controllers/UploadsController.inc b/controllers/UploadsController.inc new file mode 100644 index 00000000..a1fb7701 --- /dev/null +++ b/controllers/UploadsController.inc @@ -0,0 +1,174 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the UploadsAgent to process and show user uploads. + * + * @author Oliver Hanraths + */ + class UploadsController extends \hhu\z\Controller + { + /** + * Required models + * + * @var array + */ + public $models = array('uploads', 'users', 'userroles'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user', 'userseminaryroles') + ); + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set headers for caching control + $response->addHeader("Pragma: public"); + $response->addHeader("Cache-control: public, max-age=".(60*60*24)); + $response->addHeader("Expires: ".gmdate('r', time()+(60*60*24))); + $response->addHeader("Date: ".gmdate(\DateTime::RFC822)); + } + + + /** + * Action: index. + * + * Display an upload. + * + * @throws AccessDeniedException + * @throws IdNotFoundException + * @param string $uploadUrl URL-name of the upload + */ + public function index($uploadUrl) + { + // Get Upload + $upload = $this->Uploads->getUploadByUrl($uploadUrl); + + // Check permissions + $user = $this->Users->getUserById($this->Auth->getUserId()); + $user['roles'] = array(); + foreach($this->Userroles->getUserrolesForUserById($user['id']) as $role) { + $user['roles'][] = $role['name']; + } + if(!$upload['public']) + { + // System roles + if(count(array_intersect(array('admin', 'moderator'), $user['roles'])) == 0) + { + // Owner of file + if($upload['created_user_id'] != $user['id']) + { + if(!is_null($upload['seminary_id'])) { + throw new \nre\exceptions\AccessDeniedException(); + } + else + { + // Seminary + $seminary = $this->Seminaries->getSeminaryById($upload['seminary_id']); + + // Seminary roles + $userSeminaryRoles = array(); + foreach($this->Userseminaryroles->getUserseminaryrolesForUserById($user['id'], $seminary['id']) as $role) { + $userSeminaryRoles[] = $role['name']; + } + + if(count(array_intersect(array('admin', 'moderator'), $userSeminaryRoles)) == 0) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + } + } + } + + // Set content-type + $this->response->addHeader("Content-type: ".$upload['mimetype'].""); + + // Set filename + $upload['filename'] = ROOT.DS.\nre\configs\AppConfig::$dirs['uploads'].DS.$upload['id']; + if(!file_exists($upload['filename'])) { + throw new \nre\exceptions\IdNotFoundException($uploadUrl); + } + + // Cache + if($this->setCacheHeaders($upload['filename'])) { + return; + } + + // Load file + $file = file_get_contents($upload['filename']); + + + + + // Pass data to view + $this->set('upload', $upload); + $this->set('file', $file); + } + + + + + /** + * Determine file information and set the HTTP-header for + * caching accordingly. + * + * @param string $fileName Filename + * @return boolean HTTP-status 304 was set (in cache) + */ + private function setCacheHeaders($fileName) + { + // Determine last change of file + $fileLastModified = gmdate('r', filemtime($fileName)); + + // Generate E-Tag + $fileEtag = hash('sha256', $fileLastModified.$fileName); + + + // Set header + $this->response->addHeader("Last-Modified: ".$fileLastModified); + $this->response->addHeader("Etag: ".$fileEtag); + // HTTP-status + $headerModifiedSince = $this->request->getServerParam('HTTP_IF_MODIFIED_SINCE'); + $headerNoneMatch = $this->request->getServerParam('HTTP_IF_NONE_MATCH'); + if( + !is_null($headerModifiedSince) && $fileLastModified < strtotime($headerModifiedSince) && + !is_null($headerNoneMatch) && $headerNoneMatch == $fileEtag + ) { + $this->response->setExit(true); + $this->response->addHeader(\nre\core\WebUtils::getHttpHeader(304)); + + return true; + } + + + return false; + } + + } + +?> diff --git a/models/UploadsModel.inc b/models/UploadsModel.inc new file mode 100644 index 00000000..078b1ed4 --- /dev/null +++ b/models/UploadsModel.inc @@ -0,0 +1,148 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to handle files to upload. + * + * @author Oliver Hanraths + */ + class UploadsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new UploadsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Upload a file and create a database record. + * + * @param int $userId ID of user that uploads the file + * @param string $filename Name of file to upload + * @param string $tmpFilename Name of temporary uploaded file + * @param string $mimetype Mimetype of file to upload + * @param int $seminaryId Optional ID of Seminary if the upload is in the context of one + * @return mixed ID of database record or false + */ + public function uploadFile($userId, $filename, $tmpFilename, $mimetype, $seminaryId=null) + { + $uploadId = false; + $this->db->setAutocommit(false); + + try { + // Create database record + if(is_null($seminaryId)) + { + $this->db->query( + 'INSERT INTO uploads '. + '(created_user_id, name, url, mimetype) '. + 'VALUES '. + '(?, ? ,? ,?)', + 'isss', + $userId, $filename, \nre\core\Linker::createLinkParam($filename), $mimetype + ); + } + else + { + $this->db->query( + 'INSERT INTO uploads '. + '(created_user_id, seminary_id, name, url, mimetype) '. + 'VALUES '. + '(?, ?, ? ,? ,?)', + 'iisss', + $userId, $seminaryId, $filename, \nre\core\Linker::createLinkParam($filename), $mimetype + ); + } + $uploadId = $this->db->getInsertId(); + + // Create filename + $filename = ROOT.DS.\nre\configs\AppConfig::$dirs['uploads'].DS.$uploadId; + if(!move_uploaded_file($tmpFilename, $filename)) + { + $this->db->rollback(); + $uploadId = false; + } + } + catch(\nre\exceptions\DatamodelException $e) { + $this->db->rollback(); + $this->db->setAutocommit(true); + } + + + $this->db->setAutocommit(true); + return $uploadId; + } + + + /** + * Get an upload by its ID. + * + * @throws IdNotFoundException + * @param int $uploadId ID of the uploaded file + * @return array Upload data + */ + public function getUploadById($uploadId) + { + $data = $this->db->query( + 'SELECT id, created, created_user_id, seminary_id, name, url, mimetype, public '. + 'FROM uploads '. + 'WHERE id = ?', + 'i', + $uploadId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($uploadId); + } + + + return $data[0]; + } + + + /** + * Get an upload by its URL. + * + * @throws IdNotFoundException + * @param int $uploadId ID of the uploaded file + * @return array Upload data + */ + public function getUploadByUrl($uploadUrl) + { + $data = $this->db->query( + 'SELECT id, created, created_user_id, seminary_id, name, url, mimetype, public '. + 'FROM uploads '. + 'WHERE url = ?', + 's', + $uploadUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($uploadUrl); + } + + + return $data[0]; + } + + } + +?> diff --git a/uploads/empty b/uploads/empty new file mode 100644 index 00000000..e69de29b diff --git a/views/binary/uploads/index.tpl b/views/binary/uploads/index.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/uploads/index.tpl @@ -0,0 +1 @@ +