diff --git a/public_html/dispatch.php b/public_html/dispatch.php
index 990df736..2c3d1d74 100644
--- a/public_html/dispatch.php
+++ b/public_html/dispatch.php
@@ -70,6 +70,9 @@ $postValidation =
'score' => '-1|0|1',
];
+\Chibi\Router::register(['PostController', 'uploadView'], 'GET', '/posts/upload', $postValidation);
+\Chibi\Router::register(['PostController', 'uploadAction'], 'POST', '/posts/upload', $postValidation);
+
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}', $postValidation);
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}', $postValidation);
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}/{page}', $postValidation);
@@ -92,7 +95,6 @@ $postValidation =
foreach (['GET', 'POST'] as $method)
{
- \Chibi\Router::register(['PostController', 'uploadAction'], $method, '/posts/upload', $postValidation);
\Chibi\Router::register(['PostController', 'viewAction'], $method, '/post/{id}', $postValidation);
\Chibi\Router::register(['PostController', 'retrieveAction'], $method, '/post/{name}/retrieve', $postValidation);
\Chibi\Router::register(['PostController', 'thumbAction'], $method, '/post/{name}/thumb', $postValidation);
diff --git a/public_html/media/js/post-upload.js b/public_html/media/js/post-upload.js
index 40d18c93..370e5eb3 100644
--- a/public_html/media/js/post-upload.js
+++ b/public_html/media/js/post-upload.js
@@ -166,6 +166,7 @@ $(function()
{
handleInputs(files, function(postDom, file)
{
+ postDom.data('url', '');
postDom.data('file', file);
$('.file-name strong', postDom).text(file.name);
@@ -198,6 +199,7 @@ $(function()
handleInputs(urls, function(postDom, url)
{
postDom.data('url', url);
+ postDom.data('file', '');
postDom.find('[name=source]').val(url);
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
{
diff --git a/src/Api.php b/src/Api.php
index 2b372a46..0d3fa212 100644
--- a/src/Api.php
+++ b/src/Api.php
@@ -40,4 +40,12 @@ class Api
});
return $statuses;
}
+
+ public static function serializeFile($filePath, $fileName)
+ {
+ $x = new StdClass;
+ $x->filePath = $filePath;
+ $x->fileName = $fileName;
+ return $x;
+ }
}
diff --git a/src/ApiMissingArgumentException.php b/src/ApiMissingArgumentException.php
new file mode 100644
index 00000000..83c32c95
--- /dev/null
+++ b/src/ApiMissingArgumentException.php
@@ -0,0 +1,8 @@
+registration->needEmailForUploading)
- Access::assertEmailConfirmation();
+ $jobArgs =
+ [
+ AddPostJob::ANONYMOUS => InputHelper::get('anonymous'),
+ EditPostSafetyJob::SAFETY => InputHelper::get('safety'),
+ EditPostTagsJob::TAG_NAMES => InputHelper::get('tags'),
+ EditPostSourceJob::SOURCE => InputHelper::get('source'),
+ ];
- if (!InputHelper::get('submit'))
- return;
-
- \Chibi\Database::transaction(function() use ($context)
+ if (!empty(InputHelper::get('url')))
{
- $post = PostModel::spawn();
- LogHelper::bufferChanges();
+ $jobArgs[EditPostUrlJob::CONTENT_URL] = InputHelper::get('url');
+ }
+ elseif (!empty($_FILES['file']['name']))
+ {
+ $file = $_FILES['file'];
+ TransferHelper::handleUploadErrors($file);
- //basic stuff
- $anonymous = InputHelper::get('anonymous');
- if (Auth::isLoggedIn() and !$anonymous)
- $post->setUploader(Auth::getCurrentUser());
+ $jobArgs[EditPostContentJob::POST_CONTENT] = Api::serializeFile(
+ $file['tmp_name'],
+ $file['name']);
+ }
- //store the post to get the ID in the logs
- PostModel::forgeId($post);
-
- //do the edits
- $this->doEdit($post, true);
-
- //this basically means that user didn't specify file nor url
- if (empty($post->type))
- throw new SimpleException('No post type detected; upload faled');
-
- //clean edit log
- LogHelper::setBuffer([]);
-
- //log
- $fmt = ($anonymous and !getConfig()->misc->logAnonymousUploads)
- ? '{anon}'
- : '{user}';
- $fmt .= ' added {post} (tags: {tags}, safety: {safety}, source: {source})';
- LogHelper::log($fmt, [
- 'post' => TextHelper::reprPost($post),
- 'tags' => TextHelper::reprTags($post->getTags()),
- 'safety' => PostSafety::toString($post->safety),
- 'source' => $post->source]);
-
- //finish
- LogHelper::flush();
- PostModel::save($post);
- });
+ Api::run(new AddPostJob(), $jobArgs);
}
public function editAction($id)
diff --git a/src/Jobs/Abstraction/AbstractJob.php b/src/Jobs/Abstraction/AbstractJob.php
index 64f099b9..a17a7768 100644
--- a/src/Jobs/Abstraction/AbstractJob.php
+++ b/src/Jobs/Abstraction/AbstractJob.php
@@ -4,6 +4,7 @@ abstract class AbstractJob
const COMMENT_ID = 'comment-id';
const POST_ID = 'post-id';
const TAG_NAME = 'tag-name';
+ const TAG_NAMES = 'tags';
const TEXT = 'text';
const PAGE_NUMBER = 'page-number';
const QUERY = 'query';
@@ -24,12 +25,22 @@ abstract class AbstractJob
public function getArgument($key)
{
- if (!isset($this->arguments[$key]))
- throw new SimpleException('Expected argument "' . $key . '" was not specified');
+ if (!$this->hasArgument($key))
+ throw new ApiMissingArgumentException($key);
return $this->arguments[$key];
}
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ public function hasArgument($key)
+ {
+ return isset($this->arguments[$key]);
+ }
+
public function setArguments($arguments)
{
$this->arguments = $arguments;
diff --git a/src/Jobs/AddPostJob.php b/src/Jobs/AddPostJob.php
new file mode 100644
index 00000000..f082811b
--- /dev/null
+++ b/src/Jobs/AddPostJob.php
@@ -0,0 +1,89 @@
+getArgument(self::ANONYMOUS);
+ if (Auth::isLoggedIn() and !$anonymous)
+ $post->setUploader(Auth::getCurrentUser());
+
+ //store the post to get the ID in the logs
+ PostModel::forgeId($post);
+
+ //do the edits
+ //warning: each handler runs uses the same privileges as post editing
+ $subJobs =
+ [
+ new EditPostSafetyJob(),
+ new EditPostTagsJob(),
+ new EditPostSourceJob(),
+ new EditPostRelationsJob(),
+ new EditPostContentJob(),
+ new EditPostUrlJob(),
+ ];
+
+ foreach ($subJobs as $subJob)
+ {
+ $args = $this->getArguments();
+ $args[self::POST_ID] = $post->id;
+ try
+ {
+ Api::run($subJob, $args);
+ }
+ catch (ApiMissingArgumentException $e)
+ {
+ }
+ }
+
+ //load the post after edits
+ $post = PostModel::findById($post->id);
+
+ // basically means that user didn't specify file nor url
+ //todo:
+ //- move this to PostEntity::isValid()
+ //- create IValidatable interface
+ //- enforce entity validity upon calling save() in models
+ if (empty($post->type))
+ throw new SimpleException('No post type detected; upload faled');
+
+ //clean edit log
+ LogHelper::setBuffer([]);
+
+ //log
+ LogHelper::log('{user} added {post} (tags: {tags}, safety: {safety}, source: {source})', [
+ 'user' => ($anonymous and !getConfig()->misc->logAnonymousUploads)
+ ? TextHelper::reprUser(UserModel::getAnonymousName())
+ : TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'tags' => TextHelper::reprTags($post->getTags()),
+ 'safety' => PostSafety::toString($post->safety),
+ 'source' => $post->source]);
+
+ //finish
+ LogHelper::flush();
+ PostModel::save($post);
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return Privilege::UploadPost;
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return getConfig()->registration->needEmailForUploading;
+ }
+}
diff --git a/src/Jobs/EditPostContentJob.php b/src/Jobs/EditPostContentJob.php
new file mode 100644
index 00000000..26ab0762
--- /dev/null
+++ b/src/Jobs/EditPostContentJob.php
@@ -0,0 +1,38 @@
+getArgument(self::CONTENT);
+
+ $this->post->setContentFromPath($file->filePath, $file->fileName);
+
+ PostModel::save($this->post);
+ LogHelper::log('{user} changed contents of {post}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($this->post)]);
+
+ return $this->post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostFile,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Jobs/EditPostRelationsJob.php b/src/Jobs/EditPostRelationsJob.php
new file mode 100644
index 00000000..055d53dd
--- /dev/null
+++ b/src/Jobs/EditPostRelationsJob.php
@@ -0,0 +1,54 @@
+post;
+ $relations = $this->getArgument(self::RELATED_POST_IDS);
+
+ $oldRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
+ $post->setRelationsFromText($relations);
+ $newRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
+
+ PostModel::save($post);
+
+ foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
+ {
+ LogHelper::log('{user} removed relation between {post} and {post2}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'post2' => TextHelper::reprPost($post2id)]);
+ }
+
+ foreach (array_diff($newRelatedIds, $oldRelatedIds) as $post2id)
+ {
+ LogHelper::log('{user} added relation between {post} and {post2}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'post2' => TextHelper::reprPost($post2id)]);
+ }
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostRelations,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Jobs/EditPostSafetyJob.php b/src/Jobs/EditPostSafetyJob.php
new file mode 100644
index 00000000..77621fe4
--- /dev/null
+++ b/src/Jobs/EditPostSafetyJob.php
@@ -0,0 +1,45 @@
+post;
+ $newSafety = $this->getArgument(self::SAFETY);
+
+ $oldSafety = $post->safety;
+ $post->setSafety($newSafety);
+
+ PostModel::save($post);
+
+ if ($oldSafety != $newSafety)
+ {
+ LogHelper::log('{user} changed safety of {post} to {safety}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'safety' => PostSafety::toString($post->safety)]);
+ }
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostSafety,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Jobs/EditPostSourceJob.php b/src/Jobs/EditPostSourceJob.php
new file mode 100644
index 00000000..a2f5b6bc
--- /dev/null
+++ b/src/Jobs/EditPostSourceJob.php
@@ -0,0 +1,45 @@
+post;
+ $newSource = $this->getArgument(self::SOURCE);
+
+ $oldSource = $post->source;
+ $post->setSource($newSource);
+
+ PostModel::save($post);
+
+ if ($oldSource != $newSource)
+ {
+ LogHelper::log('{user} changed source of {post} to {source}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'source' => $post->source]);
+ }
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostSource,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Jobs/EditPostTagsJob.php b/src/Jobs/EditPostTagsJob.php
new file mode 100644
index 00000000..a0c65aad
--- /dev/null
+++ b/src/Jobs/EditPostTagsJob.php
@@ -0,0 +1,52 @@
+post;
+ $tags = $this->getArgument(self::TAG_NAMES);
+
+ $oldTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
+ $post->setTagsFromText($tags);
+ $newTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
+
+ PostModel::save($post);
+
+ foreach (array_diff($oldTags, $newTags) as $tag)
+ {
+ LogHelper::log('{user} untagged {post} with {tag}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'tag' => TextHelper::reprTag($tag)]);
+ }
+
+ foreach (array_diff($newTags, $oldTags) as $tag)
+ {
+ LogHelper::log('{user} tagged {post} with {tag}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post),
+ 'tag' => TextHelper::reprTag($tag)]);
+ }
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostTags,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Jobs/EditPostUrlJob.php b/src/Jobs/EditPostUrlJob.php
new file mode 100644
index 00000000..c945abb9
--- /dev/null
+++ b/src/Jobs/EditPostUrlJob.php
@@ -0,0 +1,40 @@
+post;
+ $url = $this->getArgument(self::CONTENT_URL);
+
+ $post->setContentFromUrl($url);
+
+ PostModel::save($post);
+
+ LogHelper::log('{user} changed contents of {post}', [
+ 'user' => TextHelper::reprUser(Auth::getCurrentUser()),
+ 'post' => TextHelper::reprPost($post)]);
+
+ return $post;
+ }
+
+ public function requiresPrivilege()
+ {
+ return
+ [
+ Privilege::EditPostFile,
+ Access::getIdentity($this->post->getUploader())
+ ];
+ }
+
+ public function requiresAuthentication()
+ {
+ return false;
+ }
+
+ public function requiresConfirmedEmail()
+ {
+ return false;
+ }
+}
diff --git a/src/Models/AbstractCrudModel.php b/src/Models/AbstractCrudModel.php
index 9c796228..434fdf32 100644
--- a/src/Models/AbstractCrudModel.php
+++ b/src/Models/AbstractCrudModel.php
@@ -120,6 +120,14 @@ abstract class AbstractCrudModel implements IModel
{
$stmt = new Sql\InsertStatement();
$stmt->setTable($table);
+ foreach ($entity as $key => $val)
+ {
+ $key = TextCaseConverter::convert($key,
+ TextCaseConverter::LOWER_CAMEL_CASE,
+ TextCaseConverter::SNAKE_CASE);
+
+ $stmt->setColumn($key, new Sql\Binding($val));
+ }
Database::exec($stmt);
$entity->id = Database::lastInsertId();
}
diff --git a/src/Models/PostModel.php b/src/Models/PostModel.php
index b616fd0b..80b236df 100644
--- a/src/Models/PostModel.php
+++ b/src/Models/PostModel.php
@@ -19,6 +19,10 @@ class PostModel extends AbstractCrudModel
public static function spawn()
{
$post = new PostEntity;
+ $post->score = 0;
+ $post->favCount = 0;
+ $post->commentCount = 0;
+ $post->safety = PostSafety::Safe;
$post->hidden = false;
$post->uploadDate = time();
do
diff --git a/src/Models/TokenModel.php b/src/Models/TokenModel.php
index fe4167ec..b9336cd5 100644
--- a/src/Models/TokenModel.php
+++ b/src/Models/TokenModel.php
@@ -30,7 +30,6 @@ class TokenModel extends AbstractCrudModel
$stmt->setColumn($key, new Sql\Binding($val));
Database::exec($stmt);
-
});
}
diff --git a/src/Views/top-navigation.phtml b/src/Views/top-navigation.phtml
index a9653fd8..2af70ccc 100644
--- a/src/Views/top-navigation.phtml
+++ b/src/Views/top-navigation.phtml
@@ -30,7 +30,7 @@
{
$registerNavItem(
'Upload',
- \Chibi\Router::linkTo(['PostController', 'uploadAction']),
+ \Chibi\Router::linkTo(['PostController', 'uploadView']),
$activeController == 'post' and $activeAction == 'upload');
}