Implemented post uploads (closed #11)
This commit is contained in:
parent
bb7b1f3321
commit
bc8e1b05a6
37 changed files with 1159 additions and 89 deletions
1
data/.gitignore
vendored
1
data/.gitignore
vendored
|
@ -2,3 +2,4 @@ db.sqlite
|
||||||
executed_upgrades.txt
|
executed_upgrades.txt
|
||||||
local.ini
|
local.ini
|
||||||
thumbnails
|
thumbnails
|
||||||
|
posts
|
||||||
|
|
|
@ -38,6 +38,7 @@ listSafePosts = anonymous, regularUser, powerUser, moderator, administ
|
||||||
listSketchyPosts = anonymous, regularUser, powerUser, moderator, administrator
|
listSketchyPosts = anonymous, regularUser, powerUser, moderator, administrator
|
||||||
listUnsafePosts = anonymous, regularUser, powerUser, moderator, administrator
|
listUnsafePosts = anonymous, regularUser, powerUser, moderator, administrator
|
||||||
uploadPosts = regularUser, powerUser, moderator, administrator
|
uploadPosts = regularUser, powerUser, moderator, administrator
|
||||||
|
uploadPostsAnonymously = regularUser, powerUser, moderator, administrator
|
||||||
|
|
||||||
listTags = anonymous, regularUser, powerUser, moderator, administrator
|
listTags = anonymous, regularUser, powerUser, moderator, administrator
|
||||||
|
|
||||||
|
|
|
@ -51,19 +51,25 @@ input[type=button] {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
input[type=button]:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
button:hover,
|
button:not(:disabled):hover,
|
||||||
input[type=button]:hover {
|
input[type=button]:not(:disabled):hover {
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
button:disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
button.highlight,
|
button.highlight,
|
||||||
input[type=button].highlight {
|
input[type=button].highlight {
|
||||||
background: #ad5;
|
background: #ad5;
|
||||||
}
|
}
|
||||||
button:hover.highlight,
|
button:not(:disabled):hover.highlight,
|
||||||
input[type=button]:hover.highlight {
|
input[type=button]:not(:disabled):hover.highlight {
|
||||||
background: #dfa;
|
background: #dfa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
|
||||||
listSketchyPosts: 'listSketchyPosts',
|
listSketchyPosts: 'listSketchyPosts',
|
||||||
listUnsafePosts: 'listUnsafePosts',
|
listUnsafePosts: 'listUnsafePosts',
|
||||||
uploadPosts: 'uploadPosts',
|
uploadPosts: 'uploadPosts',
|
||||||
|
uploadPostsAnonymously: 'uploadPostsAnonymously',
|
||||||
|
|
||||||
listTags: 'listTags',
|
listTags: 'listTags',
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ App.Controls.TagInput = function(
|
||||||
var KEY_RETURN = 13;
|
var KEY_RETURN = 13;
|
||||||
var KEY_SPACE = 32;
|
var KEY_SPACE = 32;
|
||||||
var KEY_BACKSPACE = 8;
|
var KEY_BACKSPACE = 8;
|
||||||
|
var tagConfirmKeys = [KEY_RETURN, KEY_SPACE];
|
||||||
|
|
||||||
var tags = [];
|
var tags = [];
|
||||||
var options = {
|
var options = {
|
||||||
|
@ -54,7 +55,7 @@ App.Controls.TagInput = function(
|
||||||
});
|
});
|
||||||
|
|
||||||
$input.unbind('keydown').bind('keydown', function(e) {
|
$input.unbind('keydown').bind('keydown', function(e) {
|
||||||
if (e.which === KEY_RETURN || e.which === KEY_SPACE) {
|
if (_.contains(tagConfirmKeys, e.which)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var tag = $input.val();
|
var tag = $input.val();
|
||||||
addTag(tag);
|
addTag(tag);
|
||||||
|
@ -128,11 +129,16 @@ App.Controls.TagInput = function(
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
$input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
_.extend(options, {
|
_.extend(options, {
|
||||||
setTags: setTags,
|
setTags: setTags,
|
||||||
getTags: getTags,
|
getTags: getTags,
|
||||||
removeTag: removeTag,
|
removeTag: removeTag,
|
||||||
addTag: addTag,
|
addTag: addTag,
|
||||||
|
focus: focus,
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
mousetrap,
|
mousetrap,
|
||||||
promise,
|
promise,
|
||||||
util,
|
util,
|
||||||
|
auth,
|
||||||
api,
|
api,
|
||||||
router,
|
router,
|
||||||
topNavigationPresenter,
|
topNavigationPresenter,
|
||||||
|
@ -30,7 +31,9 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$el.html(template());
|
$el.html(template({
|
||||||
|
canUploadPostsAnonymously: auth.hasPrivilege(auth.privileges.uploadPostsAnonymously)
|
||||||
|
}));
|
||||||
$messages = $el.find('.messages');
|
$messages = $el.find('.messages');
|
||||||
|
|
||||||
tagInput = new App.Controls.TagInput($el.find('form [name=tags]'), _, jQuery);
|
tagInput = new App.Controls.TagInput($el.find('form [name=tags]'), _, jQuery);
|
||||||
|
@ -61,10 +64,10 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
return {
|
return {
|
||||||
safety: 'safe',
|
safety: 'safe',
|
||||||
source: null,
|
source: null,
|
||||||
fileName: null,
|
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|
||||||
|
fileName: null,
|
||||||
content: null,
|
content: null,
|
||||||
url: null,
|
url: null,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
|
@ -273,7 +276,9 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
hidePostEditForm();
|
hidePostEditForm();
|
||||||
} else {
|
} else {
|
||||||
showPostEditForm(selectedPosts);
|
showPostEditForm(selectedPosts);
|
||||||
|
tagInput.focus();
|
||||||
}
|
}
|
||||||
|
$el.find('.post-table-op').prop('disabled', selectedPosts.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePostEditForm() {
|
function hidePostEditForm() {
|
||||||
|
@ -292,7 +297,7 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
} else {
|
} else {
|
||||||
var post = selectedPosts[0];
|
var post = selectedPosts[0];
|
||||||
$postEditForm.parent('.form-slider').find('.thumbnail').slideDown();
|
$postEditForm.parent('.form-slider').find('.thumbnail').slideDown();
|
||||||
$postEditForm.find('.file-name strong').text(post.fileName);
|
$postEditForm.find('.file-name strong').text(post.fileName || post.url);
|
||||||
updatePostThumbnailInForm(post);
|
updatePostThumbnailInForm(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,6 +493,7 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
|
|
||||||
function uploadNextPost() {
|
function uploadNextPost() {
|
||||||
messagePresenter.hideMessages($messages);
|
messagePresenter.hideMessages($messages);
|
||||||
|
messagePresenter.showInfo($messages, 'Uploading in progress…');
|
||||||
|
|
||||||
var posts = getAllPosts();
|
var posts = getAllPosts();
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0) {
|
||||||
|
@ -503,14 +509,16 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
if (post.url) {
|
if (post.url) {
|
||||||
formData.url = post.url;
|
formData.url = post.url;
|
||||||
} else {
|
} else {
|
||||||
formData.file = post.content;
|
formData.content = post.content;
|
||||||
|
formData.contentFileName = post.fileName;
|
||||||
}
|
}
|
||||||
formData.source = post.source;
|
formData.source = post.source;
|
||||||
formData.safety = post.safety;
|
formData.safety = post.safety;
|
||||||
formData.anonymous = post.anonymous;
|
formData.anonymous = (post.anonymous | 0);
|
||||||
formData.tags = post.tags.join(', ');
|
formData.tags = post.tags.join(' ');
|
||||||
|
|
||||||
if (post.tags.length === 0) {
|
if (post.tags.length === 0) {
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
messagePresenter.showError($messages, 'No tags set.');
|
messagePresenter.showError($messages, 'No tags set.');
|
||||||
interactionEnabled = true;
|
interactionEnabled = true;
|
||||||
return;
|
return;
|
||||||
|
@ -519,9 +527,12 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
promise.wait(api.post('/posts', formData)).then(function(response) {
|
promise.wait(api.post('/posts', formData)).then(function(response) {
|
||||||
$row.slideUp(function(response) {
|
$row.slideUp(function(response) {
|
||||||
$row.remove();
|
$row.remove();
|
||||||
|
posts.shift();
|
||||||
|
setAllPosts(posts);
|
||||||
uploadNextPost();
|
uploadNextPost();
|
||||||
});
|
});
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
messagePresenter.showError($messages, response.json && response.json.error || response);
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
interactionEnabled = true;
|
interactionEnabled = true;
|
||||||
});
|
});
|
||||||
|
@ -536,7 +547,6 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
$el.find('tbody input[type=checkbox]').prop('checked', false);
|
$el.find('tbody input[type=checkbox]').prop('checked', false);
|
||||||
postTableCheckboxesChanged();
|
postTableCheckboxesChanged();
|
||||||
|
|
||||||
messagePresenter.showInfo($messages, 'Uploading in progress…');
|
|
||||||
interactionEnabled = false;
|
interactionEnabled = false;
|
||||||
uploadNextPost();
|
uploadNextPost();
|
||||||
|
|
||||||
|
@ -549,4 +559,4 @@ App.Presenters.PostUploadPresenter = function(
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('postUploadPresenter', ['_', 'jQuery', 'mousetrap', 'promise', 'util', 'api', 'router', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostUploadPresenter);
|
App.DI.register('postUploadPresenter', ['_', 'jQuery', 'mousetrap', 'promise', 'util', 'auth', 'api', 'router', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostUploadPresenter);
|
||||||
|
|
|
@ -47,13 +47,13 @@
|
||||||
|
|
||||||
<ul class="operations"><!--
|
<ul class="operations"><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="remove"><i class="fa fa-remove"></i> Remove</button>
|
<button class="post-table-op remove"><i class="fa fa-remove"></i> Remove</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="move-up"><i class="fa fa-chevron-up"></i> Move up</button>
|
<button class="post-table-op move-up"><i class="fa fa-chevron-up"></i> Move up</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="move-down"><i class="fa fa-chevron-down"></i> Move down</button>
|
<button class="post-table-op move-down"><i class="fa fa-chevron-down"></i> Move down</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li class="right">
|
--><li class="right">
|
||||||
<button class="submit highlight" type="submit"><i class="fa fa-upload"></i> Submit</button>
|
<button class="submit highlight" type="submit"><i class="fa fa-upload"></i> Submit</button>
|
||||||
|
@ -112,15 +112,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<% if (canUploadPostsAnonymously) { %>
|
||||||
<label class="form-label" for="post-anonymous">Anonymity:</label>
|
<div class="form-row">
|
||||||
<div class="form-input">
|
<label class="form-label" for="post-anonymous">Anonymity:</label>
|
||||||
<label for="post-anonymous">
|
<div class="form-input">
|
||||||
<input type="checkbox" id="post-anonymous" name="anonymous"/>
|
<label for="post-anonymous">
|
||||||
Don't show my name in this post
|
<input type="checkbox" id="post-anonymous" name="anonymous"/>
|
||||||
</label>
|
Don't show my name in this post
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% } %>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
41
src/Controllers/PostController.php
Normal file
41
src/Controllers/PostController.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Controllers;
|
||||||
|
|
||||||
|
final class PostController extends AbstractController
|
||||||
|
{
|
||||||
|
private $privilegeService;
|
||||||
|
private $postService;
|
||||||
|
private $inputReader;
|
||||||
|
private $postViewProxy;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\Szurubooru\Services\PrivilegeService $privilegeService,
|
||||||
|
\Szurubooru\Services\PostService $postService,
|
||||||
|
\Szurubooru\Helpers\InputReader $inputReader,
|
||||||
|
\Szurubooru\Controllers\ViewProxies\PostViewProxy $postViewProxy)
|
||||||
|
{
|
||||||
|
$this->privilegeService = $privilegeService;
|
||||||
|
$this->postService = $postService;
|
||||||
|
$this->inputReader = $inputReader;
|
||||||
|
$this->postViewProxy = $postViewProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerRoutes(\Szurubooru\Router $router)
|
||||||
|
{
|
||||||
|
$router->post('/api/posts', [$this, 'createPost']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPost()
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::UPLOAD_POSTS);
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData($this->inputReader);
|
||||||
|
|
||||||
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::UPLOAD_POSTS);
|
||||||
|
|
||||||
|
if ($formData->anonymous)
|
||||||
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::UPLOAD_POSTS_ANONYMOUSLY);
|
||||||
|
|
||||||
|
$post = $this->postService->createPost($formData);
|
||||||
|
return $this->postViewProxy->fromEntity($post);
|
||||||
|
}
|
||||||
|
}
|
27
src/Controllers/ViewProxies/PostViewProxy.php
Normal file
27
src/Controllers/ViewProxies/PostViewProxy.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Controllers\ViewProxies;
|
||||||
|
|
||||||
|
class PostViewProxy extends AbstractViewProxy
|
||||||
|
{
|
||||||
|
public function fromEntity($post)
|
||||||
|
{
|
||||||
|
$result = new \StdClass;
|
||||||
|
if ($post)
|
||||||
|
{
|
||||||
|
$result->id = $post->getId();
|
||||||
|
$result->name = $post->getName();
|
||||||
|
$result->userId = $post->getUserId();
|
||||||
|
$result->uploadTime = $post->getUploadTime();
|
||||||
|
$result->lastEditTime = $post->getLastEditTime();
|
||||||
|
$result->safety = \Szurubooru\Helpers\EnumHelper::postSafetyToString($post->getSafety());
|
||||||
|
$result->contentType = \Szurubooru\Helpers\EnumHelper::postTypeToString($post->getContentType());
|
||||||
|
$result->contentChecksum = $post->getContentChecksum();
|
||||||
|
$result->source = $post->getSource();
|
||||||
|
$result->imageWidth = $post->getImageWidth();
|
||||||
|
$result->imageHeight = $post->getImageHeight();
|
||||||
|
$result->tags = $post->getTags();
|
||||||
|
$result->originalFileSize = $post->getOriginalFileSize();
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,17 +32,15 @@ abstract class AbstractDao implements ICrudDao
|
||||||
|
|
||||||
public function save(&$entity)
|
public function save(&$entity)
|
||||||
{
|
{
|
||||||
$arrayEntity = $this->entityConverter->toArray($entity);
|
|
||||||
if ($entity->getId())
|
if ($entity->getId())
|
||||||
{
|
{
|
||||||
$this->fpdo->update($this->tableName)->set($arrayEntity)->where('id', $entity->getId())->execute();
|
$entity = $this->update($entity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$this->fpdo->insertInto($this->tableName)->values($arrayEntity)->execute();
|
$entity = $this->create($entity);
|
||||||
$arrayEntity['id'] = $this->pdo->lastInsertId();
|
|
||||||
}
|
}
|
||||||
$entity = $this->entityConverter->toEntity($arrayEntity);
|
$this->afterSave($entity);
|
||||||
return $entity;
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +51,7 @@ abstract class AbstractDao implements ICrudDao
|
||||||
foreach ($query as $arrayEntity)
|
foreach ($query as $arrayEntity)
|
||||||
{
|
{
|
||||||
$entity = $this->entityConverter->toEntity($arrayEntity);
|
$entity = $this->entityConverter->toEntity($arrayEntity);
|
||||||
|
$this->afterLoad($entity);
|
||||||
$entities[$entity->getId()] = $entity;
|
$entities[$entity->getId()] = $entity;
|
||||||
}
|
}
|
||||||
return $entities;
|
return $entities;
|
||||||
|
@ -73,6 +72,21 @@ abstract class AbstractDao implements ICrudDao
|
||||||
return $this->deleteBy('id', $entityId);
|
return $this->deleteBy('id', $entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function update(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
$arrayEntity = $this->entityConverter->toArray($entity);
|
||||||
|
$this->fpdo->update($this->tableName)->set($arrayEntity)->where('id', $entity->getId())->execute();
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function create(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
$arrayEntity = $this->entityConverter->toArray($entity);
|
||||||
|
$this->fpdo->insertInto($this->tableName)->values($arrayEntity)->execute();
|
||||||
|
$entity->setId(intval($this->pdo->lastInsertId()));
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
protected function hasAnyRecords()
|
protected function hasAnyRecords()
|
||||||
{
|
{
|
||||||
return count(iterator_to_array($this->fpdo->from($this->tableName)->limit(1))) > 0;
|
return count(iterator_to_array($this->fpdo->from($this->tableName)->limit(1))) > 0;
|
||||||
|
@ -81,11 +95,24 @@ abstract class AbstractDao implements ICrudDao
|
||||||
protected function findOneBy($columnName, $value)
|
protected function findOneBy($columnName, $value)
|
||||||
{
|
{
|
||||||
$arrayEntity = iterator_to_array($this->fpdo->from($this->tableName)->where($columnName, $value));
|
$arrayEntity = iterator_to_array($this->fpdo->from($this->tableName)->where($columnName, $value));
|
||||||
return $arrayEntity ? $this->entityConverter->toEntity($arrayEntity[0]) : null;
|
if (!$arrayEntity)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$entity = $this->entityConverter->toEntity($arrayEntity[0]);
|
||||||
|
$this->afterLoad($entity);
|
||||||
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function deleteBy($columnName, $value)
|
protected function deleteBy($columnName, $value)
|
||||||
{
|
{
|
||||||
$this->fpdo->deleteFrom($this->tableName)->where($columnName, $value)->execute();
|
$this->fpdo->deleteFrom($this->tableName)->where($columnName, $value)->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function afterLoad(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function afterSave(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,35 @@ class PostEntityConverter implements IEntityConverter
|
||||||
[
|
[
|
||||||
'id' => $entity->getId(),
|
'id' => $entity->getId(),
|
||||||
'name' => $entity->getName(),
|
'name' => $entity->getName(),
|
||||||
|
'userId' => $entity->getUserId(),
|
||||||
|
'uploadTime' => $entity->getUploadTime(),
|
||||||
|
'lastEditTime' => $entity->getLastEditTime(),
|
||||||
|
'safety' => $entity->getSafety(),
|
||||||
|
'contentType' => $entity->getContentType(),
|
||||||
|
'contentChecksum' => $entity->getContentChecksum(),
|
||||||
|
'source' => $entity->getSource(),
|
||||||
|
'imageWidth' => $entity->getImageWidth(),
|
||||||
|
'imageHeight' => $entity->getImageHeight(),
|
||||||
|
'originalFileSize' => $entity->getOriginalFileSize(),
|
||||||
|
'originalFileName' => $entity->getOriginalFileName(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toEntity(array $array)
|
public function toEntity(array $array)
|
||||||
{
|
{
|
||||||
$entity = new \Szurubooru\Entities\Post($array['id']);
|
$entity = new \Szurubooru\Entities\Post(intval($array['id']));
|
||||||
$entity->setName($array['name']);
|
$entity->setName($array['name']);
|
||||||
|
$entity->setUserId($array['userId']);
|
||||||
|
$entity->setUploadTime($array['uploadTime']);
|
||||||
|
$entity->setLastEditTime($array['lastEditTime']);
|
||||||
|
$entity->setSafety(intval($array['safety']));
|
||||||
|
$entity->setContentType(intval($array['contentType']));
|
||||||
|
$entity->setContentChecksum($array['contentChecksum']);
|
||||||
|
$entity->setSource($array['source']);
|
||||||
|
$entity->setImageWidth($array['imageWidth']);
|
||||||
|
$entity->setImageHeight($array['imageHeight']);
|
||||||
|
$entity->setOriginalFileSize($array['originalFileSize']);
|
||||||
|
$entity->setOriginalFileName($array['originalFileName']);
|
||||||
return $entity;
|
return $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/Dao/EntityConverters/TagEntityConverter.php
Normal file
20
src/Dao/EntityConverters/TagEntityConverter.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Dao\EntityConverters;
|
||||||
|
|
||||||
|
class TagEntityConverter implements IEntityConverter
|
||||||
|
{
|
||||||
|
public function toArray(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
'name' => $entity->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toEntity(array $array)
|
||||||
|
{
|
||||||
|
$entity = new \Szurubooru\Entities\Tag($array['name']);
|
||||||
|
$entity->setName($array['name']);
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,82 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Szurubooru\Dao;
|
namespace Szurubooru\Dao;
|
||||||
|
|
||||||
final class PostDao extends AbstractDao implements ICrudDao
|
class PostDao extends AbstractDao implements ICrudDao
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(\Szurubooru\DatabaseConnection $databaseConnection)
|
||||||
\Szurubooru\DatabaseConnection $databaseConnection)
|
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$databaseConnection,
|
$databaseConnection,
|
||||||
'posts',
|
'posts',
|
||||||
new \Szurubooru\Dao\EntityConverters\PostEntityConverter());
|
new \Szurubooru\Dao\EntityConverters\PostEntityConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByName($name)
|
||||||
|
{
|
||||||
|
return $this->findOneBy('name', $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByContentChecksum($checksum)
|
||||||
|
{
|
||||||
|
return $this->findOneBy('contentChecksum', $checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function afterLoad(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
$entity->setLazyLoader('tags', function(\Szurubooru\Entities\Post $post)
|
||||||
|
{
|
||||||
|
return $this->getTags($post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function afterSave(\Szurubooru\Entities\Entity $entity)
|
||||||
|
{
|
||||||
|
$this->syncTags($entity->getId(), $entity->getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTags(\Szurubooru\Entities\Post $post)
|
||||||
|
{
|
||||||
|
$postId = $post->getId();
|
||||||
|
$result = [];
|
||||||
|
$query = $this->fpdo->from('postTags')->where('postId', $postId)->select('tagName');
|
||||||
|
foreach ($query as $arrayEntity)
|
||||||
|
$result[] = $arrayEntity['tagName'];
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function syncTags($postId, array $tags)
|
||||||
|
{
|
||||||
|
$existingTags = iterator_to_array($this->fpdo->from('postTags')->where('postId', $postId));
|
||||||
|
$tagRelationsToInsert = array_diff($tags, $existingTags);
|
||||||
|
$tagRelationsToDelete = array_diff($existingTags, $tags);
|
||||||
|
$this->createMissingTags($tags);
|
||||||
|
foreach ($tagRelationsToInsert as $tag)
|
||||||
|
{
|
||||||
|
$this->fpdo->insertInto('postTags')->values(['postId' => $postId, 'tagName' => $tag])->execute();
|
||||||
|
}
|
||||||
|
foreach ($tagRelationsToDelete as $tag)
|
||||||
|
{
|
||||||
|
$this->fpdo->deleteFrom('postTags')->where('postId', $postId)->and('tagName', $tag)->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createMissingTags(array $tags)
|
||||||
|
{
|
||||||
|
if (empty($tags))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$tagsNotToCreate = array_map(
|
||||||
|
function($arrayEntity)
|
||||||
|
{
|
||||||
|
return $arrayEntity['name'];
|
||||||
|
},
|
||||||
|
iterator_to_array($this->fpdo->from('tags')->where('name', $tags)));
|
||||||
|
|
||||||
|
$tagsToCreate = array_diff($tags, $tagsNotToCreate);
|
||||||
|
|
||||||
|
foreach ($tagsToCreate as $tag)
|
||||||
|
{
|
||||||
|
$this->fpdo->insertInto('tags')->values(['name' => $tag])->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/Dao/TagDao.php
Normal file
13
src/Dao/TagDao.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Dao;
|
||||||
|
|
||||||
|
class TagDao extends AbstractDao implements ICrudDao
|
||||||
|
{
|
||||||
|
public function __construct(\Szurubooru\DatabaseConnection $databaseConnection)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
$databaseConnection,
|
||||||
|
'tags',
|
||||||
|
new \Szurubooru\Dao\EntityConverters\TagEntityConverter());
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ namespace Szurubooru\Entities;
|
||||||
abstract class Entity
|
abstract class Entity
|
||||||
{
|
{
|
||||||
protected $id = null;
|
protected $id = null;
|
||||||
|
private $lazyLoaders = [];
|
||||||
|
private $lazyContainers = [];
|
||||||
|
|
||||||
public function __construct($id = null)
|
public function __construct($id = null)
|
||||||
{
|
{
|
||||||
|
@ -14,4 +16,42 @@ abstract class Entity
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetLazyLoaders()
|
||||||
|
{
|
||||||
|
$this->lazyLoaders = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLazyLoader($lazyContainerName, $getter)
|
||||||
|
{
|
||||||
|
$this->lazyLoaders[$lazyContainerName] = $getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function lazyLoad($lazyContainerName, $defaultValue)
|
||||||
|
{
|
||||||
|
if (!isset($this->lazyContainers[$lazyContainerName]))
|
||||||
|
{
|
||||||
|
if (!isset($this->lazyLoaders[$lazyContainerName]))
|
||||||
|
{
|
||||||
|
return $defaultValue;
|
||||||
|
}
|
||||||
|
$result = $this->lazyLoaders[$lazyContainerName]($this);
|
||||||
|
$this->lazySave($lazyContainerName, $result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$result = $this->lazyContainers[$lazyContainerName];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function lazySave($lazyContainerName, $value)
|
||||||
|
{
|
||||||
|
$this->lazyContainers[$lazyContainerName] = $value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,32 @@ namespace Szurubooru\Entities;
|
||||||
|
|
||||||
final class Post extends Entity
|
final class Post extends Entity
|
||||||
{
|
{
|
||||||
|
const POST_SAFETY_SAFE = 1;
|
||||||
|
const POST_SAFETY_SKETCHY = 2;
|
||||||
|
const POST_SAFETY_UNSAFE = 3;
|
||||||
|
|
||||||
|
const POST_TYPE_IMAGE = 1;
|
||||||
|
const POST_TYPE_FLASH = 2;
|
||||||
|
const POST_TYPE_VIDEO = 3;
|
||||||
|
const POST_TYPE_YOUTUBE = 4;
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
|
protected $userId;
|
||||||
|
protected $uploadTime;
|
||||||
|
protected $lastEditTime;
|
||||||
|
protected $safety;
|
||||||
|
protected $contentType;
|
||||||
|
protected $contentChecksum;
|
||||||
|
protected $source;
|
||||||
|
protected $imageWidth;
|
||||||
|
protected $imageHeight;
|
||||||
|
protected $originalFileSize;
|
||||||
|
protected $originalFileName;
|
||||||
|
|
||||||
|
public function getIdMarkdown()
|
||||||
|
{
|
||||||
|
return '@' . $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
|
@ -14,4 +39,136 @@ final class Post extends Entity
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUserId()
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserId($userId)
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(\Szurubooru\Entities\User $user = null)
|
||||||
|
{
|
||||||
|
if ($user)
|
||||||
|
{
|
||||||
|
$this->userId = $user->getId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->userId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSafety()
|
||||||
|
{
|
||||||
|
return $this->safety;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSafety($safety)
|
||||||
|
{
|
||||||
|
$this->safety = $safety;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploadTime()
|
||||||
|
{
|
||||||
|
return $this->uploadTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUploadTime($uploadTime)
|
||||||
|
{
|
||||||
|
$this->uploadTime = $uploadTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastEditTime()
|
||||||
|
{
|
||||||
|
return $this->lastEditTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastEditTime($lastEditTime)
|
||||||
|
{
|
||||||
|
$this->lastEditTime = $lastEditTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContentType()
|
||||||
|
{
|
||||||
|
return $this->contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContentType($contentType)
|
||||||
|
{
|
||||||
|
$this->contentType = $contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContentChecksum()
|
||||||
|
{
|
||||||
|
return $this->contentChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContentChecksum($contentChecksum)
|
||||||
|
{
|
||||||
|
$this->contentChecksum = $contentChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSource()
|
||||||
|
{
|
||||||
|
return $this->source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSource($source)
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageWidth()
|
||||||
|
{
|
||||||
|
return $this->imageWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setImageWidth($imageWidth)
|
||||||
|
{
|
||||||
|
$this->imageWidth = $imageWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageHeight()
|
||||||
|
{
|
||||||
|
return $this->imageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setImageHeight($imageHeight)
|
||||||
|
{
|
||||||
|
$this->imageHeight = $imageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalFileSize()
|
||||||
|
{
|
||||||
|
return $this->originalFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOriginalFileSize($originalFileSize)
|
||||||
|
{
|
||||||
|
$this->originalFileSize = $originalFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalFileName()
|
||||||
|
{
|
||||||
|
return $this->originalFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOriginalFileName($originalFileName)
|
||||||
|
{
|
||||||
|
$this->originalFileName = $originalFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTags()
|
||||||
|
{
|
||||||
|
return $this->lazyLoad('tags', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTags(array $tags)
|
||||||
|
{
|
||||||
|
$this->lazySave('tags', $tags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,16 @@ final class Tag extends Entity
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $usages;
|
protected $usages;
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->name = $id;
|
||||||
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
|
|
39
src/FormData/UploadFormData.php
Normal file
39
src/FormData/UploadFormData.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\FormData;
|
||||||
|
|
||||||
|
class UploadFormData implements \Szurubooru\IValidatable
|
||||||
|
{
|
||||||
|
public $contentFileName;
|
||||||
|
public $content;
|
||||||
|
public $url;
|
||||||
|
public $anonymous;
|
||||||
|
public $safety;
|
||||||
|
public $source;
|
||||||
|
public $tags;
|
||||||
|
|
||||||
|
public function __construct($inputReader = null)
|
||||||
|
{
|
||||||
|
if ($inputReader !== null)
|
||||||
|
{
|
||||||
|
$this->contentFileName = $inputReader->contentFileName;
|
||||||
|
$this->content = $inputReader->decodeBase64($inputReader->content);
|
||||||
|
$this->url = $inputReader->url;
|
||||||
|
$this->anonymous = $inputReader->anonymous;
|
||||||
|
$this->safety = \Szurubooru\Helpers\EnumHelper::postSafetyFromString($inputReader->safety);
|
||||||
|
$this->source = $inputReader->source;
|
||||||
|
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(\Szurubooru\Validator $validator)
|
||||||
|
{
|
||||||
|
if ($this->content === null and $this->url === null)
|
||||||
|
throw new \DomainException('Neither data or URL provided.');
|
||||||
|
|
||||||
|
$validator->validatePostTags($this->tags);
|
||||||
|
|
||||||
|
if ($this->source !== null)
|
||||||
|
$validator->validateMaxLength($this->source, 200, 'Source');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,21 @@ class EnumHelper
|
||||||
'blank' => \Szurubooru\Entities\User::AVATAR_STYLE_BLANK,
|
'blank' => \Szurubooru\Entities\User::AVATAR_STYLE_BLANK,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static $postSafetyMap =
|
||||||
|
[
|
||||||
|
'safe' => \Szurubooru\Entities\Post::POST_SAFETY_SAFE,
|
||||||
|
'sketchy' => \Szurubooru\Entities\Post::POST_SAFETY_SKETCHY,
|
||||||
|
'unsafe' => \Szurubooru\Entities\Post::POST_SAFETY_UNSAFE,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $postTypeMap =
|
||||||
|
[
|
||||||
|
'image' => \Szurubooru\Entities\Post::POST_TYPE_IMAGE,
|
||||||
|
'video' => \Szurubooru\Entities\Post::POST_TYPE_VIDEO,
|
||||||
|
'flash' => \Szurubooru\Entities\Post::POST_TYPE_FLASH,
|
||||||
|
'youtube' => \Szurubooru\Entities\Post::POST_TYPE_YOUTUBE,
|
||||||
|
];
|
||||||
|
|
||||||
public static function accessRankToString($accessRank)
|
public static function accessRankToString($accessRank)
|
||||||
{
|
{
|
||||||
return self::enumToString(self::$accessRankMap, $accessRank);
|
return self::enumToString(self::$accessRankMap, $accessRank);
|
||||||
|
@ -40,6 +55,21 @@ class EnumHelper
|
||||||
return self::stringToEnum(self::$avatarStyleMap, $avatarStyleString);
|
return self::stringToEnum(self::$avatarStyleMap, $avatarStyleString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function postSafetyToString($postSafety)
|
||||||
|
{
|
||||||
|
return self::enumToString(self::$postSafetyMap, $postSafety);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function postSafetyFromString($postSafetyString)
|
||||||
|
{
|
||||||
|
return self::stringToEnum(self::$postSafetyMap, $postSafetyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function postTypeToString($postType)
|
||||||
|
{
|
||||||
|
return self::enumToString(self::$postTypeMap, $postType);
|
||||||
|
}
|
||||||
|
|
||||||
private static function enumToString($enumMap, $enumValue)
|
private static function enumToString($enumMap, $enumValue)
|
||||||
{
|
{
|
||||||
$reverseMap = array_flip($enumMap);
|
$reverseMap = array_flip($enumMap);
|
||||||
|
@ -53,7 +83,7 @@ class EnumHelper
|
||||||
{
|
{
|
||||||
$key = trim(strtolower($enumString));
|
$key = trim(strtolower($enumString));
|
||||||
if (!isset($enumMap[$key]))
|
if (!isset($enumMap[$key]))
|
||||||
throw new \DomainException('Unrecognized avatar style: ' . $enumString);
|
throw new \DomainException('Unrecognized value: ' . $enumString);
|
||||||
|
|
||||||
return $enumMap[$key];
|
return $enumMap[$key];
|
||||||
}
|
}
|
||||||
|
|
37
src/Helpers/MimeHelper.php
Normal file
37
src/Helpers/MimeHelper.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Helpers;
|
||||||
|
|
||||||
|
class MimeHelper
|
||||||
|
{
|
||||||
|
public static function getMimeTypeFromFile($path)
|
||||||
|
{
|
||||||
|
$finfo = new \finfo(FILEINFO_MIME);
|
||||||
|
return self::stripCharset($finfo->load($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMimeTypeFromBuffer($buffer)
|
||||||
|
{
|
||||||
|
$finfo = new \finfo(FILEINFO_MIME);
|
||||||
|
return self::stripCharset($finfo->buffer($buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isFlash($mime)
|
||||||
|
{
|
||||||
|
return $mime === 'application/x-shockwave-flash';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isVideo($mime)
|
||||||
|
{
|
||||||
|
return $mime === 'application/ogg' or preg_match('/video\//', $mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isImage($mime)
|
||||||
|
{
|
||||||
|
return in_array($mime, ['image/jpeg', 'image/png', 'image/gif']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function stripCharset($mime)
|
||||||
|
{
|
||||||
|
return preg_replace('/;\s*charset.*$/', '', $mime);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ class Privilege
|
||||||
const LIST_SKETCHY_POSTS = 'listSketchyPosts';
|
const LIST_SKETCHY_POSTS = 'listSketchyPosts';
|
||||||
const LIST_UNSAFE_POSTS = 'listUnsafePosts';
|
const LIST_UNSAFE_POSTS = 'listUnsafePosts';
|
||||||
const UPLOAD_POSTS = 'uploadPosts';
|
const UPLOAD_POSTS = 'uploadPosts';
|
||||||
|
const UPLOAD_POSTS_ANONYMOUSLY = 'uploadPostsAnonymously';
|
||||||
|
|
||||||
const LIST_TAGS = 'listTags';
|
const LIST_TAGS = 'listTags';
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class FileService
|
||||||
|
|
||||||
public function createFolders($target)
|
public function createFolders($target)
|
||||||
{
|
{
|
||||||
$fullPath = $this->getFullPath($target);
|
$fullPath = $this->getFullPath(dirname($target));
|
||||||
if (!file_exists($fullPath))
|
if (!file_exists($fullPath))
|
||||||
mkdir($fullPath, 0777, true);
|
mkdir($fullPath, 0777, true);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ class FileService
|
||||||
|
|
||||||
public function save($destination, $data)
|
public function save($destination, $data)
|
||||||
{
|
{
|
||||||
|
$this->createFolders($destination);
|
||||||
$finalDestination = $this->getFullPath($destination);
|
$finalDestination = $this->getFullPath($destination);
|
||||||
file_put_contents($finalDestination, $data);
|
file_put_contents($finalDestination, $data);
|
||||||
}
|
}
|
||||||
|
@ -89,4 +90,42 @@ class FileService
|
||||||
{
|
{
|
||||||
return $this->dataDirectory . DIRECTORY_SEPARATOR . $destination;
|
return $this->dataDirectory . DIRECTORY_SEPARATOR . $destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function download($url, $maxBytes = null)
|
||||||
|
{
|
||||||
|
set_time_limit(60);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$srcHandle = fopen($url, 'rb');
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
throw new \Exception('Cannot open URL for reading: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$srcHandle)
|
||||||
|
throw new \Exception('Cannot open URL for reading');
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!feof($srcHandle))
|
||||||
|
{
|
||||||
|
$buffer = fread($srcHandle, 4 * 1024);
|
||||||
|
if ($maxBytes !== null and ftell($dstHandle) > $maxBytes)
|
||||||
|
{
|
||||||
|
throw new \Exception(
|
||||||
|
'File is too big (maximum size: %s)',
|
||||||
|
TextHelper::useBytesUnits($maxBytes));
|
||||||
|
}
|
||||||
|
$result .= $buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
fclose($srcHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
176
src/Services/PostService.php
Normal file
176
src/Services/PostService.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Services;
|
||||||
|
|
||||||
|
class PostService
|
||||||
|
{
|
||||||
|
private $config;
|
||||||
|
private $validator;
|
||||||
|
private $transactionManager;
|
||||||
|
private $postDao;
|
||||||
|
private $fileService;
|
||||||
|
private $timeService;
|
||||||
|
private $authService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\Szurubooru\Config $config,
|
||||||
|
\Szurubooru\Validator $validator,
|
||||||
|
\Szurubooru\Dao\TransactionManager $transactionManager,
|
||||||
|
\Szurubooru\Dao\PostDao $postDao,
|
||||||
|
\Szurubooru\Services\AuthService $authService,
|
||||||
|
\Szurubooru\Services\TimeService $timeService,
|
||||||
|
\Szurubooru\Services\FileService $fileService)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
$this->validator = $validator;
|
||||||
|
$this->transactionManager = $transactionManager;
|
||||||
|
$this->postDao = $postDao;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->timeService = $timeService;
|
||||||
|
$this->authService = $authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPost(\Szurubooru\FormData\UploadFormData $formData)
|
||||||
|
{
|
||||||
|
return $this->transactionManager->commit(function() use ($formData)
|
||||||
|
{
|
||||||
|
$formData->validate($this->validator);
|
||||||
|
|
||||||
|
$post = new \Szurubooru\Entities\Post();
|
||||||
|
$post->setUploadTime($this->timeService->getCurrentTime());
|
||||||
|
$post->setLastEditTime($this->timeService->getCurrentTime());
|
||||||
|
$post->setUser($formData->anonymous ? null : $this->authService->getLoggedInUser());
|
||||||
|
$post->setOriginalFileName($formData->contentFileName);
|
||||||
|
$post->setName($this->getUniqueRandomPostName());
|
||||||
|
|
||||||
|
$this->updatePostSafety($post, $formData->safety);
|
||||||
|
$this->updatePostSource($post, $formData->source);
|
||||||
|
$this->updatePostTags($post, $formData->tags);
|
||||||
|
$this->updatePostContentFromStringOrUrl($post, $formData->content, $formData->url);
|
||||||
|
|
||||||
|
return $this->postDao->save($post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostSafety(\Szurubooru\Entities\Post $post, $newSafety)
|
||||||
|
{
|
||||||
|
$post->setSafety($newSafety);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostSource(\Szurubooru\Entities\Post $post, $newSource)
|
||||||
|
{
|
||||||
|
$post->setSource($newSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostContentFromStringOrUrl(\Szurubooru\Entities\Post $post, $content, $url)
|
||||||
|
{
|
||||||
|
if ($url)
|
||||||
|
$this->updatePostContentFromUrl($post, $url);
|
||||||
|
else if ($content)
|
||||||
|
$this->updatePostContentFromString($post, $content);
|
||||||
|
else
|
||||||
|
throw new \DomainException('No content specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostContentFromString(\Szurubooru\Entities\Post $post, $content)
|
||||||
|
{
|
||||||
|
if (!$content)
|
||||||
|
throw new \DomainException('File cannot be empty.');
|
||||||
|
|
||||||
|
$mime = \Szurubooru\Helpers\MimeHelper::getMimeTypeFromBuffer($content);
|
||||||
|
|
||||||
|
if (\Szurubooru\Helpers\MimeHelper::isFlash($mime))
|
||||||
|
$post->setContentType(\Szurubooru\Entities\Post::POST_TYPE_FLASH);
|
||||||
|
elseif (\Szurubooru\Helpers\MimeHelper::isImage($mime))
|
||||||
|
$post->setContentType(\Szurubooru\Entities\Post::POST_TYPE_IMAGE);
|
||||||
|
elseif (\Szurubooru\Helpers\MimeHelper::isVideo($mime))
|
||||||
|
$post->setContentType(\Szurubooru\Entities\Post::POST_TYPE_VIDEO);
|
||||||
|
else
|
||||||
|
throw new \DomainException('Unhandled file type: "' . $mime . '"');
|
||||||
|
|
||||||
|
$post->setContentChecksum(sha1($content));
|
||||||
|
$this->assertNoPostWithThisContentChecksum($post);
|
||||||
|
|
||||||
|
$target = $this->getPostContentPath($post);
|
||||||
|
$this->fileService->save($target, $content);
|
||||||
|
$fullPath = $this->fileService->getFullPath($target);
|
||||||
|
|
||||||
|
list ($imageWidth, $imageHeight) = getimagesize($fullPath);
|
||||||
|
$post->setImageWidth($imageWidth);
|
||||||
|
$post->setImageHeight($imageHeight);
|
||||||
|
|
||||||
|
$post->setOriginalFileSize(filesize($fullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostContentFromUrl(\Szurubooru\Entities\Post $post, $url)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^https?:\/\//', $url))
|
||||||
|
throw new \InvalidArgumentException('Invalid URL "' . $url . '"');
|
||||||
|
|
||||||
|
$youtubeId = null;
|
||||||
|
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $url, $matches))
|
||||||
|
$youtubeId = $matches[1];
|
||||||
|
|
||||||
|
if ($youtubeId)
|
||||||
|
{
|
||||||
|
$post->setContentType(\Szurubooru\Entities\Post::POST_TYPE_YOUTUBE);
|
||||||
|
$post->setImageWidth(null);
|
||||||
|
$post->setImageHeight(null);
|
||||||
|
$post->setContentChecksum($url);
|
||||||
|
$post->setOriginalFileName($url);
|
||||||
|
$post->setOriginalFileSize(null);
|
||||||
|
$post->setContentChecksum($youtubeId);
|
||||||
|
|
||||||
|
$this->assertNoPostWithThisContentChecksum($post);
|
||||||
|
$this->removeThumbnail($post);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$contents = $this->fileService->download($url);
|
||||||
|
$this->updatePostContentFromString($post, $contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePostTags(\Szurubooru\Entities\Post $post, array $newTags)
|
||||||
|
{
|
||||||
|
$post->setTags($newTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeThumbnail(\Szurubooru\Entities\Post $post)
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
//todo: remove thumbnail on upload
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertNoPostWithThisContentChecksum(\Szurubooru\Entities\Post $parent)
|
||||||
|
{
|
||||||
|
$checksumToCheck = $parent->getContentChecksum();
|
||||||
|
$postWithThisChecksum = $this->postDao->findByContentChecksum($checksumToCheck);
|
||||||
|
if ($postWithThisChecksum and $postWithThisChecksum->getId() !== $parent->getId())
|
||||||
|
throw new \DomainException('Duplicate post: ' . $postWithThisChecksum->getIdMarkdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRandomPostName()
|
||||||
|
{
|
||||||
|
return sha1(microtime(true) . mt_rand() . uniqid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUniqueRandomPostName()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$name = $this->getRandomPostName();
|
||||||
|
if (!$this->postDao->findByName($name))
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPostContentPath(\Szurubooru\Entities\Post $post)
|
||||||
|
{
|
||||||
|
return 'posts' . DIRECTORY_SEPARATOR . $post->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPostThumbnailSourcePath(\Szuruboor\Entities\Post $post)
|
||||||
|
{
|
||||||
|
return 'posts' . DIRECTORY_SEPARATOR . $post->getName() . '-custom-thumb';
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,32 +22,17 @@ class SmartThumbnailGenerator implements IThumbnailGenerator
|
||||||
if (!file_exists($srcPath))
|
if (!file_exists($srcPath))
|
||||||
throw new \InvalidArgumentException($srcPath . ' does not exist');
|
throw new \InvalidArgumentException($srcPath . ' does not exist');
|
||||||
|
|
||||||
$mime = mime_content_type($srcPath);
|
$mime = \Szurubooru\Helpers\MimeHelper::getMimeTypeFromFile($srcPath);
|
||||||
|
|
||||||
if ($this->isFlash($mime))
|
if (\Szurubooru\Helpers\MimeHelper::isFlash($mime))
|
||||||
return $this->flashThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
return $this->flashThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
||||||
|
|
||||||
if ($this->isVideo($mime))
|
if (\Szurubooru\Helpers\MimeHelper::isVideo($mime))
|
||||||
return $this->videoThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
return $this->videoThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
||||||
|
|
||||||
if ($this->isImage($mime))
|
if (\Szurubooru\Helpers\MimeHelper::isImage($mime))
|
||||||
return $this->imageThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
return $this->imageThumbnailGenerator->generate($srcPath, $dstPath, $width, $height);
|
||||||
|
|
||||||
throw new \InvalidArgumentException('Invalid thumbnail file type: ' . $mime);
|
throw new \InvalidArgumentException('Invalid thumbnail file type: ' . $mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isFlash($mime)
|
|
||||||
{
|
|
||||||
return $mime === 'application/x-shockwave-flash';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isVideo($mime)
|
|
||||||
{
|
|
||||||
return $mime === 'application/ogg' or preg_match('/video\//', $mime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isImage($mime)
|
|
||||||
{
|
|
||||||
return in_array($mime, ['image/jpeg', 'image/png', 'image/gif']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class ThumbnailService
|
||||||
|
|
||||||
$fullSource = $this->fileService->getFullPath($source);
|
$fullSource = $this->fileService->getFullPath($source);
|
||||||
$fullTarget = $this->fileService->getFullPath($target);
|
$fullTarget = $this->fileService->getFullPath($target);
|
||||||
$this->fileService->createFolders(dirname($target));
|
$this->fileService->createFolders($target);
|
||||||
$this->thumbnailGenerator->generate($fullSource, $fullTarget, $width, $height);
|
$this->thumbnailGenerator->generate($fullSource, $fullTarget, $width, $height);
|
||||||
|
|
||||||
return $target;
|
return $target;
|
||||||
|
|
42
src/Upgrades/Upgrade03.php
Normal file
42
src/Upgrades/Upgrade03.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Upgrades;
|
||||||
|
|
||||||
|
class Upgrade03 implements IUpgrade
|
||||||
|
{
|
||||||
|
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
|
||||||
|
{
|
||||||
|
$databaseConnection->getPDO()->exec('DROP TABLE "posts"');
|
||||||
|
|
||||||
|
$databaseConnection->getPDO()->exec('
|
||||||
|
CREATE TABLE "posts"
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
userId INTEGER,
|
||||||
|
uploadTime TIMESTAMP NOT NULL,
|
||||||
|
lastEditTime TIMESTAMP,
|
||||||
|
safety INTEGER NOT NULL,
|
||||||
|
contentType INTEGER NOT NULL,
|
||||||
|
contentChecksum TEXT NOT NULL,
|
||||||
|
source TEXT,
|
||||||
|
imageWidth INTEGER,
|
||||||
|
imageHeight INTEGER,
|
||||||
|
originalFileSize INTEGER,
|
||||||
|
originalFileName TEXT
|
||||||
|
)');
|
||||||
|
|
||||||
|
$databaseConnection->getPDO()->exec('
|
||||||
|
CREATE TABLE "tags"
|
||||||
|
(
|
||||||
|
name TEXT PRIMARY KEY NOT NULL
|
||||||
|
)');
|
||||||
|
|
||||||
|
$databaseConnection->getPDO()->exec('
|
||||||
|
CREATE TABLE "postTags"
|
||||||
|
(
|
||||||
|
postId INTEGER NOT NULL,
|
||||||
|
tagName TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (postId, tagName)
|
||||||
|
)');
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,6 +80,30 @@ class Validator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function validatePostTags($tags)
|
||||||
|
{
|
||||||
|
if (empty($tags))
|
||||||
|
throw new \DomainException('Tags cannot be empty.');
|
||||||
|
|
||||||
|
$illegalCharacters = str_split("\r\n\t " . chr(160));
|
||||||
|
foreach ($tags as $tag)
|
||||||
|
{
|
||||||
|
if (empty($tag))
|
||||||
|
{
|
||||||
|
throw new \DomainException('Tags cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($illegalCharacters as $char)
|
||||||
|
{
|
||||||
|
if (strpos($tag, $char) !== false)
|
||||||
|
{
|
||||||
|
throw new \DomainException(
|
||||||
|
'Tags cannot contain any of following characters: ' . implode(', ', $illegalCharacters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function validateToken($token)
|
public function validateToken($token)
|
||||||
{
|
{
|
||||||
$this->validateNonEmpty($token, 'Token');
|
$this->validateNonEmpty($token, 'Token');
|
||||||
|
|
|
@ -11,6 +11,7 @@ return [
|
||||||
return [
|
return [
|
||||||
$container->get(\Szurubooru\Upgrades\Upgrade01::class),
|
$container->get(\Szurubooru\Upgrades\Upgrade01::class),
|
||||||
$container->get(\Szurubooru\Upgrades\Upgrade02::class),
|
$container->get(\Szurubooru\Upgrades\Upgrade02::class),
|
||||||
|
$container->get(\Szurubooru\Upgrades\Upgrade03::class),
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ return [
|
||||||
$container->get(\Szurubooru\Controllers\AuthController::class),
|
$container->get(\Szurubooru\Controllers\AuthController::class),
|
||||||
$container->get(\Szurubooru\Controllers\UserController::class),
|
$container->get(\Szurubooru\Controllers\UserController::class),
|
||||||
$container->get(\Szurubooru\Controllers\UserAvatarController::class),
|
$container->get(\Szurubooru\Controllers\UserAvatarController::class),
|
||||||
|
$container->get(\Szurubooru\Controllers\PostController::class),
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -26,6 +26,16 @@ abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTestFile($fileName)
|
||||||
|
{
|
||||||
|
return file_get_contents($this->getTestFilePath($fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTestFilePath($fileName)
|
||||||
|
{
|
||||||
|
return __DIR__ . DIRECTORY_SEPARATOR . 'test_files' . DIRECTORY_SEPARATOR . $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
protected function tearDown()
|
protected function tearDown()
|
||||||
{
|
{
|
||||||
$this->cleanTestDirectory();
|
$this->cleanTestDirectory();
|
||||||
|
|
|
@ -5,10 +5,9 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
{
|
{
|
||||||
public function testCreating()
|
public function testCreating()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
|
|
||||||
$post = new \Szurubooru\Entities\Post();
|
$post = $this->getPost();
|
||||||
$post->setName('test');
|
|
||||||
$savedPost = $postDao->save($post);
|
$savedPost = $postDao->save($post);
|
||||||
$this->assertEquals('test', $post->getName());
|
$this->assertEquals('test', $post->getName());
|
||||||
$this->assertNotNull($savedPost->getId());
|
$this->assertNotNull($savedPost->getId());
|
||||||
|
@ -16,9 +15,8 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
|
|
||||||
public function testUpdating()
|
public function testUpdating()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
$post = new \Szurubooru\Entities\Post();
|
$post = $this->getPost();
|
||||||
$post->setName('test');
|
|
||||||
$post = $postDao->save($post);
|
$post = $postDao->save($post);
|
||||||
$this->assertEquals('test', $post->getName());
|
$this->assertEquals('test', $post->getName());
|
||||||
$id = $post->getId();
|
$id = $post->getId();
|
||||||
|
@ -30,17 +28,17 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
|
|
||||||
public function testGettingAll()
|
public function testGettingAll()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
|
|
||||||
$post1 = new \Szurubooru\Entities\Post();
|
|
||||||
$post1->setName('test2');
|
|
||||||
$post2 = new \Szurubooru\Entities\Post();
|
|
||||||
$post2->setName('test2');
|
|
||||||
|
|
||||||
|
$post1 = $this->getPost();
|
||||||
|
$post2 = $this->getPost();
|
||||||
$postDao->save($post1);
|
$postDao->save($post1);
|
||||||
$postDao->save($post2);
|
$postDao->save($post2);
|
||||||
|
|
||||||
$actual = $postDao->findAll();
|
$actual = $postDao->findAll();
|
||||||
|
foreach ($actual as $post)
|
||||||
|
$post->resetLazyLoaders();
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
$post1->getId() => $post1,
|
$post1->getId() => $post1,
|
||||||
$post2->getId() => $post2,
|
$post2->getId() => $post2,
|
||||||
|
@ -51,31 +49,27 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
|
|
||||||
public function testGettingById()
|
public function testGettingById()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
|
|
||||||
$post1 = new \Szurubooru\Entities\Post();
|
|
||||||
$post1->setName('test2');
|
|
||||||
$post2 = new \Szurubooru\Entities\Post();
|
|
||||||
$post2->setName('test2');
|
|
||||||
|
|
||||||
|
$post1 = $this->getPost();
|
||||||
|
$post2 = $this->getPost();
|
||||||
$postDao->save($post1);
|
$postDao->save($post1);
|
||||||
$postDao->save($post2);
|
$postDao->save($post2);
|
||||||
|
|
||||||
$actualPost1 = $postDao->findById($post1->getId());
|
$actualPost1 = $postDao->findById($post1->getId());
|
||||||
$actualPost2 = $postDao->findById($post2->getId());
|
$actualPost2 = $postDao->findById($post2->getId());
|
||||||
|
$actualPost1->resetLazyLoaders();
|
||||||
|
$actualPost2->resetLazyLoaders();
|
||||||
$this->assertEquals($post1, $actualPost1);
|
$this->assertEquals($post1, $actualPost1);
|
||||||
$this->assertEquals($post2, $actualPost2);
|
$this->assertEquals($post2, $actualPost2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeletingAll()
|
public function testDeletingAll()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
|
|
||||||
$post1 = new \Szurubooru\Entities\Post();
|
|
||||||
$post1->setName('test2');
|
|
||||||
$post2 = new \Szurubooru\Entities\Post();
|
|
||||||
$post2->setName('test2');
|
|
||||||
|
|
||||||
|
$post1 = $this->getPost();
|
||||||
|
$post2 = $this->getPost();
|
||||||
$postDao->save($post1);
|
$postDao->save($post1);
|
||||||
$postDao->save($post2);
|
$postDao->save($post2);
|
||||||
|
|
||||||
|
@ -90,13 +84,10 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
|
|
||||||
public function testDeletingById()
|
public function testDeletingById()
|
||||||
{
|
{
|
||||||
$postDao = new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
$postDao = $this->getPostDao();
|
||||||
|
|
||||||
$post1 = new \Szurubooru\Entities\Post();
|
|
||||||
$post1->setName('test2');
|
|
||||||
$post2 = new \Szurubooru\Entities\Post();
|
|
||||||
$post2->setName('test2');
|
|
||||||
|
|
||||||
|
$post1 = $this->getPost();
|
||||||
|
$post2 = $this->getPost();
|
||||||
$postDao->save($post1);
|
$postDao->save($post1);
|
||||||
$postDao->save($post2);
|
$postDao->save($post2);
|
||||||
|
|
||||||
|
@ -108,4 +99,41 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||||
$this->assertEquals($actualPost2, $actualPost2);
|
$this->assertEquals($actualPost2, $actualPost2);
|
||||||
$this->assertEquals(1, count($postDao->findAll()));
|
$this->assertEquals(1, count($postDao->findAll()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSavingTags()
|
||||||
|
{
|
||||||
|
$testTags = ['tag1', 'tag2'];
|
||||||
|
$postDao = $this->getPostDao();
|
||||||
|
$post = $this->getPost();
|
||||||
|
$post->setTags($testTags);
|
||||||
|
$postDao->save($post);
|
||||||
|
|
||||||
|
$savedPost = $postDao->findById($post->getId());
|
||||||
|
$this->assertEquals($testTags, $post->getTags());
|
||||||
|
$this->assertEquals($post->getTags(), $savedPost->getTags());
|
||||||
|
|
||||||
|
$tagDao = $this->getTagDao();
|
||||||
|
$this->assertEquals(2, count($tagDao->findAll()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPostDao()
|
||||||
|
{
|
||||||
|
return new \Szurubooru\Dao\PostDao($this->databaseConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTagDao()
|
||||||
|
{
|
||||||
|
return new \Szurubooru\Dao\TagDao($this->databaseConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPost()
|
||||||
|
{
|
||||||
|
$post = new \Szurubooru\Entities\Post();
|
||||||
|
$post->setName('test');
|
||||||
|
$post->setUploadTime('whatever');
|
||||||
|
$post->setSafety(\Szurubooru\Entities\Post::POST_SAFETY_SAFE);
|
||||||
|
$post->setContentType(\Szurubooru\Entities\Post::POST_TYPE_YOUTUBE);
|
||||||
|
$post->setContentChecksum('whatever');
|
||||||
|
return $post;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
13
tests/Helpers/InputReaderTest.php
Normal file
13
tests/Helpers/InputReaderTest.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Tests\Helpers;
|
||||||
|
|
||||||
|
class InputReaderTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testDecodingBase64()
|
||||||
|
{
|
||||||
|
$inputReader = new \Szurubooru\Helpers\InputReader();
|
||||||
|
$actual = $inputReader->decodeBase64('data:text/plain,YXdlc29tZSBkb2c=');
|
||||||
|
$expected = 'awesome dog';
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,12 +7,20 @@ class FileServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
{
|
{
|
||||||
$testDirectory = $this->createTestDirectory();
|
$testDirectory = $this->createTestDirectory();
|
||||||
$configMock = $this->mockConfig($testDirectory);
|
$configMock = $this->mockConfig($testDirectory);
|
||||||
$httpHelper = $this->mock( \Szurubooru\Helpers\HttpHelper::class);
|
$httpHelper = $this->mock(\Szurubooru\Helpers\HttpHelper::class);
|
||||||
$fileService = new \Szurubooru\Services\FileService($configMock, $httpHelper);
|
$fileService = new \Szurubooru\Services\FileService($configMock, $httpHelper);
|
||||||
$input = 'data:text/plain,YXdlc29tZSBkb2c=';
|
$fileService->save('dog.txt', 'awesome dog');
|
||||||
$fileService->saveFromBase64($input, 'dog.txt');
|
|
||||||
$expected = 'awesome dog';
|
$expected = 'awesome dog';
|
||||||
$actual = file_get_contents($testDirectory . DIRECTORY_SEPARATOR . 'dog.txt');
|
$actual = file_get_contents($testDirectory . DIRECTORY_SEPARATOR . 'dog.txt');
|
||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDownload()
|
||||||
|
{
|
||||||
|
$configMock = $this->mockConfig();
|
||||||
|
$httpHelper = $this->mock(\Szurubooru\Helpers\HttpHelper::class);
|
||||||
|
$fileService = new \Szurubooru\Services\FileService($configMock, $httpHelper);
|
||||||
|
$content = $fileService->download('http://modernseoul.files.wordpress.com/2012/04/korean-alphabet-chart-modern-seoul.jpg');
|
||||||
|
$this->assertGreaterThan(0, strlen($content));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
157
tests/Services/PostServiceTest.php
Normal file
157
tests/Services/PostServiceTest.php
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Tests\Services;
|
||||||
|
|
||||||
|
class PostServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
{
|
||||||
|
private $configMock;
|
||||||
|
private $validatorMock;
|
||||||
|
private $transactionManagerMock;
|
||||||
|
private $postDaoMock;
|
||||||
|
private $authServiceMock;
|
||||||
|
private $timeServiceMock;
|
||||||
|
private $fileServiceMock;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->configMock = $this->mockConfig();
|
||||||
|
$this->validatorMock = $this->mock(\Szurubooru\Validator::class);
|
||||||
|
$this->transactionManagerMock = $this->mockTransactionManager();
|
||||||
|
$this->postDaoMock = $this->mock(\Szurubooru\Dao\PostDao::class);
|
||||||
|
$this->authServiceMock = $this->mock(\Szurubooru\Services\AuthService::class);
|
||||||
|
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
|
||||||
|
$this->fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testCreatingYoutubePost()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->source = 'source';
|
||||||
|
$formData->tags = ['test', 'test2'];
|
||||||
|
$formData->url = 'https://www.youtube.com/watch?v=QYK2c4OVG6s';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('save')->will($this->returnArgument(0));
|
||||||
|
$this->authServiceMock->expects($this->once())->method('getLoggedInUser')->willReturn(new \Szurubooru\Entities\User(5));
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$savedPost = $this->postService->createPost($formData);
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\Post::POST_SAFETY_SAFE, $savedPost->getSafety());
|
||||||
|
$this->assertEquals(5, $savedPost->getUserId());
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\Post::POST_TYPE_YOUTUBE, $savedPost->getContentType());
|
||||||
|
$this->assertEquals('QYK2c4OVG6s', $savedPost->getContentChecksum());
|
||||||
|
$this->assertEquals('source', $savedPost->getSource());
|
||||||
|
$this->assertNull($savedPost->getImageWidth());
|
||||||
|
$this->assertNull($savedPost->getImageHeight());
|
||||||
|
$this->assertEquals($formData->url, $savedPost->getOriginalFileName());
|
||||||
|
$this->assertNull($savedPost->getOriginalFileSize());
|
||||||
|
$this->assertEquals(['test', 'test2'], $savedPost->getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreatingPosts()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->tags = ['test'];
|
||||||
|
$formData->content = $this->getTestFile('image.jpg');
|
||||||
|
$formData->contentFileName = 'blah';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('save')->will($this->returnArgument(0));
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('save');
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('getFullPath')->willReturn($this->getTestFilePath('image.jpg'));
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$savedPost = $this->postService->createPost($formData);
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\Post::POST_TYPE_IMAGE, $savedPost->getContentType());
|
||||||
|
$this->assertEquals('24216edd12328de3a3c55e2f98220ee7613e3be1', $savedPost->getContentChecksum());
|
||||||
|
$this->assertEquals(640, $savedPost->getImageWidth());
|
||||||
|
$this->assertEquals(480, $savedPost->getImageHeight());
|
||||||
|
$this->assertEquals($formData->contentFileName, $savedPost->getOriginalFileName());
|
||||||
|
$this->assertEquals(687645, $savedPost->getOriginalFileSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreatingVideos()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->tags = ['test'];
|
||||||
|
$formData->content = $this->getTestFile('video.mp4');
|
||||||
|
$formData->contentFileName = 'blah';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('save')->will($this->returnArgument(0));
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('save');
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('getFullPath')->willReturn($this->getTestFilePath('video.mp4'));
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$savedPost = $this->postService->createPost($formData);
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\Post::POST_TYPE_VIDEO, $savedPost->getContentType());
|
||||||
|
$this->assertEquals('16dafaa07cda194d03d590529c06c6ec1a5b80b0', $savedPost->getContentChecksum());
|
||||||
|
$this->assertNull($savedPost->getImageWidth());
|
||||||
|
$this->assertNull($savedPost->getImageHeight());
|
||||||
|
$this->assertEquals($formData->contentFileName, $savedPost->getOriginalFileName());
|
||||||
|
$this->assertEquals(14667, $savedPost->getOriginalFileSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreatingFlashes()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->tags = ['test'];
|
||||||
|
$formData->content = $this->getTestFile('flash.swf');
|
||||||
|
$formData->contentFileName = 'blah';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('save')->will($this->returnArgument(0));
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('save');
|
||||||
|
$this->fileServiceMock->expects($this->once())->method('getFullPath')->willReturn($this->getTestFilePath('flash.swf'));
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$savedPost = $this->postService->createPost($formData);
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\Post::POST_TYPE_FLASH, $savedPost->getContentType());
|
||||||
|
$this->assertEquals('d897e044b801d892291b440534c3be3739034f68', $savedPost->getContentChecksum());
|
||||||
|
$this->assertEquals(320, $savedPost->getImageWidth());
|
||||||
|
$this->assertEquals(240, $savedPost->getImageHeight());
|
||||||
|
$this->assertEquals($formData->contentFileName, $savedPost->getOriginalFileName());
|
||||||
|
$this->assertEquals(226172, $savedPost->getOriginalFileSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFileDuplicates()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->tags = ['test'];
|
||||||
|
$formData->content = $this->getTestFile('flash.swf');
|
||||||
|
$formData->contentFileName = 'blah';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('findByContentChecksum')->willReturn(new \Szurubooru\Entities\Post(5));
|
||||||
|
$this->setExpectedException(\Exception::class, 'Duplicate post: @5');
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$this->postService->createPost($formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testYoutubeDuplicates()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\UploadFormData;
|
||||||
|
$formData->safety = \Szurubooru\Entities\Post::POST_SAFETY_SAFE;
|
||||||
|
$formData->tags = ['test'];
|
||||||
|
$formData->url = 'https://www.youtube.com/watch?v=QYK2c4OVG6s';
|
||||||
|
|
||||||
|
$this->postDaoMock->expects($this->once())->method('findByContentChecksum')->with('QYK2c4OVG6s')->willReturn(new \Szurubooru\Entities\Post(5));
|
||||||
|
$this->setExpectedException(\Exception::class, 'Duplicate post: @5');
|
||||||
|
|
||||||
|
$this->postService = $this->getPostService();
|
||||||
|
$this->postService->createPost($formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPostService()
|
||||||
|
{
|
||||||
|
return new \Szurubooru\Services\PostService(
|
||||||
|
$this->configMock,
|
||||||
|
$this->validatorMock,
|
||||||
|
$this->transactionManagerMock,
|
||||||
|
$this->postDaoMock,
|
||||||
|
$this->authServiceMock,
|
||||||
|
$this->timeServiceMock,
|
||||||
|
$this->fileServiceMock);
|
||||||
|
}
|
||||||
|
}
|
|
@ -124,6 +124,33 @@ final class ValidatorTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertNull($validator->validatePassword('password'));
|
$this->assertNull($validator->validatePassword('password'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNoTags()
|
||||||
|
{
|
||||||
|
$this->setExpectedException(\Exception::class, 'Tags cannot be empty');
|
||||||
|
$validator = $this->getValidator();
|
||||||
|
$validator->validatePostTags([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyTags()
|
||||||
|
{
|
||||||
|
$this->setExpectedException(\Exception::class, 'Tags cannot be empty');
|
||||||
|
$validator = $this->getValidator();
|
||||||
|
$validator->validatePostTags(['good_tag', '']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTagsWithInvalidCharacters()
|
||||||
|
{
|
||||||
|
$this->setExpectedException(\Exception::class, 'Tags cannot contain any of following');
|
||||||
|
$validator = $this->getValidator();
|
||||||
|
$validator->validatePostTags(['good_tag', 'bad' . chr(160) . 'tag']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidTags()
|
||||||
|
{
|
||||||
|
$validator = $this->getValidator();
|
||||||
|
$this->assertNull($validator->validatePostTags(['good_tag', 'good_tag2', 'góód_as_well', ':3']));
|
||||||
|
}
|
||||||
|
|
||||||
private function getValidator()
|
private function getValidator()
|
||||||
{
|
{
|
||||||
return new \Szurubooru\Validator($this->configMock);
|
return new \Szurubooru\Validator($this->configMock);
|
||||||
|
|
BIN
tests/test_files/flash.swf
Normal file
BIN
tests/test_files/flash.swf
Normal file
Binary file not shown.
BIN
tests/test_files/image.jpg
Normal file
BIN
tests/test_files/image.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 672 KiB |
BIN
tests/test_files/video.mp4
Normal file
BIN
tests/test_files/video.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue