diff --git a/data/config.ini b/data/config.ini index 0197164a..962b38dd 100644 --- a/data/config.ini +++ b/data/config.ini @@ -91,7 +91,7 @@ editPostThumb=moderator editPostSource=moderator editPostRelations.own=registered editPostRelations.all=moderator -editPostFile=moderator +editPostContent=moderator massTag.own=registered massTag.all=power-user hidePost=moderator diff --git a/src/Api/Jobs/EditPostContentJob.php b/src/Api/Jobs/EditPostContentJob.php index 239e0032..72c5caef 100644 --- a/src/Api/Jobs/EditPostContentJob.php +++ b/src/Api/Jobs/EditPostContentJob.php @@ -2,13 +2,22 @@ class EditPostContentJob extends AbstractPostEditJob { const POST_CONTENT = 'post-content'; + const POST_CONTENT_URL = 'post-content-url'; public function execute() { $post = $this->post; - $file = $this->getArgument(self::POST_CONTENT); - $post->setContentFromPath($file->filePath, $file->fileName); + if ($this->hasArgument(self::POST_CONTENT_URL)) + { + $url = $this->getArgument(self::POST_CONTENT_URL); + $post->setContentFromUrl($url); + } + else + { + $file = $this->getArgument(self::POST_CONTENT); + $post->setContentFromPath($file->filePath, $file->fileName); + } if (!$this->skipSaving) PostModel::save($post); @@ -23,7 +32,7 @@ class EditPostContentJob extends AbstractPostEditJob public function requiresPrivilege() { return new Privilege( - Privilege::EditPostFile, + Privilege::EditPostContent, Access::getIdentity($this->post->getUploader())); } } diff --git a/src/Api/Jobs/EditPostJob.php b/src/Api/Jobs/EditPostJob.php index 59dd938b..a832e48b 100644 --- a/src/Api/Jobs/EditPostJob.php +++ b/src/Api/Jobs/EditPostJob.php @@ -14,7 +14,6 @@ class EditPostJob extends AbstractPostEditJob new EditPostSourceJob(), new EditPostRelationsJob(), new EditPostContentJob(), - new EditPostUrlJob(), new EditPostThumbJob(), ]; diff --git a/src/Api/Jobs/EditPostUrlJob.php b/src/Api/Jobs/EditPostUrlJob.php deleted file mode 100644 index cd2869f8..00000000 --- a/src/Api/Jobs/EditPostUrlJob.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php -class EditPostUrlJob extends AbstractPostEditJob -{ - const POST_CONTENT_URL = 'post-content-url'; - - public function execute() - { - $post = $this->post; - $url = $this->getArgument(self::POST_CONTENT_URL); - - $post->setContentFromUrl($url); - - if (!$this->skipSaving) - PostModel::save($post); - - Logger::log('{user} changed contents of {post}', [ - 'user' => TextHelper::reprUser(Auth::getCurrentUser()), - 'post' => TextHelper::reprPost($post)]); - - return $post; - } - - public function requiresPrivilege() - { - return new Privilege( - Privilege::EditPostFile, - Access::getIdentity($this->post->getUploader())); - } -} diff --git a/src/Controllers/PostController.php b/src/Controllers/PostController.php index 513fcb2f..12dc954a 100644 --- a/src/Controllers/PostController.php +++ b/src/Controllers/PostController.php @@ -96,7 +96,7 @@ class PostController if (!empty(InputHelper::get('url'))) { - $jobArgs[EditPostUrlJob::POST_CONTENT_URL] = InputHelper::get('url'); + $jobArgs[EditPostContentJob::POST_CONTENT_URL] = InputHelper::get('url'); } elseif (!empty($_FILES['file']['name'])) { @@ -106,6 +106,8 @@ class PostController $jobArgs[EditPostContentJob::POST_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); + + TransferHelper::remove($file['tmp_name']); } Api::run(new AddPostJob(), $jobArgs); @@ -138,7 +140,7 @@ class PostController if (!empty(InputHelper::get('url'))) { - $jobArgs[EditPostUrlJob::POST_CONTENT_URL] = InputHelper::get('url'); + $jobArgs[EditPostContentJob::POST_CONTENT_URL] = InputHelper::get('url'); } elseif (!empty($_FILES['file']['name'])) { @@ -148,6 +150,8 @@ class PostController $jobArgs[EditPostContentJob::POST_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); + + TransferHelper::remove($file['tmp_name']); } if (!empty($_FILES['thumb']['name'])) @@ -158,6 +162,8 @@ class PostController $jobArgs[EditPostThumbJob::THUMB_CONTENT] = new ApiFileInput( $file['tmp_name'], $file['name']); + + TransferHelper::remove($file['tmp_name']); } Api::run(new EditPostJob(), $jobArgs); diff --git a/src/Helpers/TransferHelper.php b/src/Helpers/TransferHelper.php index 55c6d8d0..5e561225 100644 --- a/src/Helpers/TransferHelper.php +++ b/src/Helpers/TransferHelper.php @@ -1,8 +1,17 @@ <?php class TransferHelper { + protected static $mocks = []; + public static function download($srcUrl, $dstPath, $maxBytes = null) { + if (isset(self::$mocks[$srcUrl])) + { + self::copy(self::$mocks[$srcUrl], $dstPath); + chmod($dstPath, 0644); + return; + } + set_time_limit(0); $srcHandle = fopen($srcUrl, 'rb'); if (!$srcHandle) @@ -42,6 +51,11 @@ class TransferHelper } } + public static function mockForDownload($url, $sourceFile) + { + self::$mocks[$url] = $sourceFile; + } + public static function moveUpload($srcPath, $dstPath) { if ($srcPath == $dstPath) @@ -55,8 +69,8 @@ class TransferHelper { //problems with permissions on some systems? #rename($srcPath, $dstPath); - copy($srcPath, $dstPath); - unlink($srcPath); + self::copy($srcPath, $dstPath); + self::remove($srcPath); } } @@ -68,6 +82,12 @@ class TransferHelper copy($srcPath, $dstPath); } + public static function remove($srcPath) + { + if (file_exists($srcPath)) + unlink($srcPath); + } + public static function createDirectory($dirPath) { if (file_exists($dirPath)) diff --git a/src/Models/Entities/PostEntity.php b/src/Models/Entities/PostEntity.php index 67bd70bb..0cc0403c 100644 --- a/src/Models/Entities/PostEntity.php +++ b/src/Models/Entities/PostEntity.php @@ -258,7 +258,7 @@ class PostEntity extends AbstractEntity implements IValidatable $dstPath = $this->getThumbCustomPath(); - TransferHelper::moveUpload($srcPath, $dstPath); + TransferHelper::copy($srcPath, $dstPath); } public function generateThumb($width = null, $height = null) @@ -332,7 +332,7 @@ class PostEntity extends AbstractEntity implements IValidatable $dstPath = $this->getFullPath(); - TransferHelper::moveUpload($srcPath, $dstPath); + TransferHelper::copy($srcPath, $dstPath); $thumbPath = $this->getThumbDefaultPath(); if (file_exists($thumbPath)) @@ -370,20 +370,20 @@ class PostEntity extends AbstractEntity implements IValidatable return; } - $srcPath = tempnam(sys_get_temp_dir(), 'upload') . '.dat'; + $tmpPath = tempnam(sys_get_temp_dir(), 'upload') . '.dat'; try { $maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize')); - TransferHelper::download($srcUrl, $srcPath, $maxBytes); + TransferHelper::download($srcUrl, $tmpPath, $maxBytes); - $this->setContentFromPath($srcPath, basename($srcUrl)); + $this->setContentFromPath($tmpPath, basename($srcUrl)); } finally { - if (file_exists($srcPath)) - unlink($srcPath); + if (file_exists($tmpPath)) + unlink($tmpPath); } } diff --git a/src/Models/Enums/Privilege.php b/src/Models/Enums/Privilege.php index 4b269d34..f8840c88 100644 --- a/src/Models/Enums/Privilege.php +++ b/src/Models/Enums/Privilege.php @@ -11,7 +11,7 @@ class Privilege extends Enum const EditPostThumb = 8; const EditPostSource = 26; const EditPostRelations = 30; - const EditPostFile = 36; + const EditPostContent = 36; const HidePost = 9; const DeletePost = 10; const FeaturePost = 25; diff --git a/src/Views/post-edit.phtml b/src/Views/post-edit.phtml index b5a0ebe8..6bbab574 100644 --- a/src/Views/post-edit.phtml +++ b/src/Views/post-edit.phtml @@ -86,7 +86,7 @@ <?php endif ?> <?php if (Access::check(new Privilege( - Privilege::EditPostFile, + Privilege::EditPostContent, Access::getIdentity($this->context->transport->post->getUploader())))): ?> <div class="form-row url"> diff --git a/tests/JobTests/EditPostContentJobTest.php b/tests/JobTests/EditPostContentJobTest.php new file mode 100644 index 00000000..88903a21 --- /dev/null +++ b/tests/JobTests/EditPostContentJobTest.php @@ -0,0 +1,225 @@ +<?php +class EditPostContentJobTest extends AbstractTest +{ + public function testFile() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $post = $this->uploadFromFile('image.jpg'); + $this->assert->doesNotThrow(function() use ($post) + { + PostModel::findById($post->getId()); + }); + } + + public function testFileJpeg() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $post = $this->uploadFromFile('image.jpg'); + $this->assert->areEqual('image/jpeg', $post->mimeType); + $this->assert->areEqual(PostType::Image, $post->getType()->toInteger()); + $this->assert->areEqual(320, $post->imageWidth); + $this->assert->areEqual(240, $post->imageHeight); + $this->assert->doesNotThrow(function() use ($post) + { + $post->generateThumb(); + }); + } + + public function testFilePng() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $post = $this->uploadFromFile('image.png'); + $this->assert->areEqual('image/png', $post->mimeType); + $this->assert->areEqual(PostType::Image, $post->getType()->toInteger()); + $this->assert->areEqual(320, $post->imageWidth); + $this->assert->areEqual(240, $post->imageHeight); + $this->assert->doesNotThrow(function() use ($post) + { + $post->generateThumb(); + }); + } + + public function testFileGif() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $post = $this->uploadFromFile('image.gif'); + $this->assert->areEqual('image/gif', $post->mimeType); + $this->assert->areEqual(PostType::Image, $post->getType()->toInteger()); + $this->assert->areEqual(320, $post->imageWidth); + $this->assert->areEqual(240, $post->imageHeight); + $this->assert->doesNotThrow(function() use ($post) + { + $post->generateThumb(); + }); + } + + public function testFileInvalid() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $this->assert->throws(function() + { + $this->uploadFromFile('text.txt'); + }, 'Invalid file type'); + } + + public function testUrl() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + $post = $this->uploadFromUrl('image.jpg'); + $this->assert->doesNotThrow(function() use ($post) + { + PostModel::findById($post->getId()); + }); + } + + public function testUrlYoutube() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + + $post = $this->mockPost(Auth::getCurrentUser()); + $post = Api::run( + new EditPostContentJob(), + [ + EditPostContentJob::POST_ID => $post->getId(), + EditPostContentJob::POST_CONTENT_URL => 'http://www.youtube.com/watch?v=qWq_jydCUw4', 'test.jpg', + ]); + $this->assert->areEqual(PostType::Youtube, $post->getType()->toInteger()); + $this->assert->areEqual('qWq_jydCUw4', $post->fileHash); + $this->assert->doesNotThrow(function() use ($post) + { + $post->generateThumb(); + }); + + $this->assert->doesNotThrow(function() use ($post) + { + PostModel::findById($post->getId()); + }); + } + + public function testNoAuth() + { + $this->prepare(); + $this->grantAccess('editPostContent'); + Auth::setCurrentUser(null); + + $this->assert->doesNotThrow(function() + { + $this->uploadFromFile('image.jpg'); + }); + } + + public function testOwnAccessDenial() + { + $this->prepare(); + + $this->assert->throws(function() + { + $this->uploadFromFile('image.jpg'); + }, 'Insufficient privileges'); + } + + public function testOtherAccessGrant() + { + $this->prepare(); + $this->grantAccess('editPostContent.all'); + + $post = $this->mockPost(Auth::getCurrentUser()); + + //login as someone else + $this->login($this->mockUser()); + + $this->assert->doesNotThrow(function() use ($post) + { + $this->uploadFromFile('image.jpg', $post); + }); + } + + public function testOtherAccessDenial() + { + $this->prepare(); + $this->grantAccess('editPostContent.own'); + + $post = $this->mockPost(Auth::getCurrentUser()); + + //login as someone else + $this->login($this->mockUser()); + + $this->assert->throws(function() use ($post) + { + $this->uploadFromFile('image.jpg', $post); + }, 'Insufficient privileges'); + } + + + public function testWrongPostId() + { + $this->assert->throws(function() + { + Api::run( + new EditPostContentJob(), + [ + EditPostContentJob::POST_ID => 100, + EditPostContentJob::POST_CONTENT => new ApiFileInput($this->getPath('image.jpg'), 'test.jpg'), + ]); + }, 'Invalid post ID'); + } + + + protected function prepare() + { + $this->login($this->mockUser()); + } + + protected function uploadFromUrl($fileName, $post = null) + { + if ($post === null) + $post = $this->mockPost(Auth::getCurrentUser()); + + $url = 'http://example.com/mock_' . $fileName; + TransferHelper::mockForDownload($url, $this->getPath($fileName)); + + $post = Api::run( + new EditPostContentJob(), + [ + EditPostContentJob::POST_ID => $post->getId(), + EditPostContentJob::POST_CONTENT_URL => $url, + ]); + + $this->assert->areEqual( + file_get_contents($this->getPath($fileName)), + file_get_contents(getConfig()->main->filesPath . DS . $post->getName())); + + return $post; + } + + protected function uploadFromFile($fileName, $post = null) + { + if ($post === null) + $post = $this->mockPost(Auth::getCurrentUser()); + + $post = Api::run( + new EditPostContentJob(), + [ + EditPostContentJob::POST_ID => $post->getId(), + EditPostContentJob::POST_CONTENT => new ApiFileInput($this->getPath($fileName), 'test.jpg'), + ]); + + $this->assert->areEqual( + file_get_contents($this->getPath($fileName)), + file_get_contents(getConfig()->main->filesPath . DS . $post->getName())); + + return $post; + } + + protected function getPath($name) + { + return getConfig()->rootDir . DS . 'tests' . DS . 'TestFiles' . DS . $name; + } +} diff --git a/tests/TestFiles/image.gif b/tests/TestFiles/image.gif new file mode 100644 index 00000000..ca3bfe7e Binary files /dev/null and b/tests/TestFiles/image.gif differ diff --git a/tests/TestFiles/image.jpg b/tests/TestFiles/image.jpg new file mode 100644 index 00000000..028c6894 Binary files /dev/null and b/tests/TestFiles/image.jpg differ diff --git a/tests/TestFiles/image.png b/tests/TestFiles/image.png new file mode 100644 index 00000000..8ea1e0dc Binary files /dev/null and b/tests/TestFiles/image.png differ diff --git a/tests/TestFiles/text.txt b/tests/TestFiles/text.txt new file mode 100644 index 00000000..84102df4 --- /dev/null +++ b/tests/TestFiles/text.txt @@ -0,0 +1 @@ +The quick brown fox jumps over the lazy dog