szurubooru/src/Controllers/PostController.php

1004 lines
28 KiB
PHP
Raw Normal View History

2013-10-05 12:55:03 +02:00
<?php
2013-10-05 21:24:20 +02:00
class PostController
2013-10-05 12:55:03 +02:00
{
2013-10-07 00:44:17 +02:00
public function workWrapper($callback)
{
2013-10-09 21:02:54 +02:00
$this->context->stylesheets []= '../lib/tagit/jquery.tagit.css';
$this->context->scripts []= '../lib/tagit/jquery.tagit.js';
2013-10-07 00:44:17 +02:00
$callback();
}
2013-10-13 12:28:16 +02:00
private static function serializeTags($post)
{
$x = [];
foreach ($post->sharedTag as $tag)
$x []= $tag->name;
natcasesort($x);
$x = join('', $x);
return md5($x);
}
private static function handleUploadErrors($file)
{
switch ($file['error'])
{
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_INI_SIZE:
throw new SimpleException('File is too big (maximum size allowed: ' . ini_get('upload_max_filesize') . ')');
case UPLOAD_ERR_FORM_SIZE:
throw new SimpleException('File is too big than it was allowed in HTML form');
case UPLOAD_ERR_PARTIAL:
throw new SimpleException('File transfer was interrupted');
case UPLOAD_ERR_NO_FILE:
throw new SimpleException('No file was uploaded');
case UPLOAD_ERR_NO_TMP_DIR:
throw new SimpleException('Server misconfiguration error: missing temporary folder');
case UPLOAD_ERR_CANT_WRITE:
throw new SimpleException('Server misconfiguration error: cannot write to disk');
case UPLOAD_ERR_EXTENSION:
throw new SimpleException('Server misconfiguration error: upload was canceled by an extension');
default:
throw new SimpleException('Generic file upload error (id: ' . $file['error'] . ')');
}
if (!is_uploaded_file($file['tmp_name']))
throw new SimpleException('Generic file upload error');
}
2013-10-08 23:02:31 +02:00
2013-10-05 12:55:03 +02:00
/**
2013-10-05 19:24:08 +02:00
* @route /posts
2013-10-09 11:45:18 +02:00
* @route /posts/{page}
* @route /posts/{query}/
2013-10-09 11:45:18 +02:00
* @route /posts/{query}/{page}
* @validate page \d*
* @validate query [^\/]*
2013-10-05 12:55:03 +02:00
*/
2013-10-09 12:24:25 +02:00
public function listAction($query = null, $page = 1)
2013-10-05 12:55:03 +02:00
{
$this->context->stylesheets []= 'post-small.css';
2013-10-09 11:45:18 +02:00
$this->context->stylesheets []= 'post-list.css';
2013-10-10 00:12:27 +02:00
$this->context->stylesheets []= 'paginator.css';
2013-10-22 00:17:06 +02:00
if ($this->context->user->hasEnabledEndlessScrolling())
2013-10-10 00:12:27 +02:00
$this->context->scripts []= 'paginator-endless.js';
2013-10-09 11:45:18 +02:00
2013-10-14 00:25:40 +02:00
//redirect requests in form of /posts/?query=... to canonical address
$formQuery = InputHelper::get('query');
if ($formQuery !== null)
{
$this->context->transport->searchQuery = $formQuery;
if (strpos($formQuery, '/') !== false)
2013-10-14 00:25:40 +02:00
throw new SimpleException('Search query contains invalid characters');
$url = \Chibi\UrlHelper::route('post', 'list', ['query' => urlencode($formQuery)]);
\Chibi\UrlHelper::forward($url);
return;
}
$query = trim(urldecode($query));
2013-10-09 11:45:18 +02:00
$page = intval($page);
$postsPerPage = intval($this->config->browsing->postsPerPage);
2013-10-20 00:36:50 +02:00
$this->context->subTitle = 'posts';
2013-10-13 13:17:23 +02:00
$this->context->transport->searchQuery = $query;
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
2013-10-13 13:17:23 +02:00
$buildDbQuery = function($dbQuery, $query)
2013-10-08 23:02:31 +02:00
{
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('comment')
->where('comment.post_id = post.id')
->close()
->as('comment_count');
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('favoritee')
->where('favoritee.post_id = post.id')
->close()
->as('fav_count');
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('post_tag')
->where('post_tag.post_id = post.id')
->close()
->as('tag_count');
2013-10-08 23:02:31 +02:00
$dbQuery->from('post');
2013-10-14 00:25:40 +02:00
/* safety */
2013-10-09 11:45:18 +02:00
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
{
2013-10-18 00:09:50 +02:00
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
2013-10-14 00:25:40 +02:00
$this->context->user->hasEnabledSafety($safety);
2013-10-09 11:45:18 +02:00
});
$dbQuery->where('safety')->in('(' . R::genSlots($allowedSafety) . ')');
2013-10-09 11:45:18 +02:00
foreach ($allowedSafety as $s)
$dbQuery->put($s);
2013-10-14 00:25:40 +02:00
/* hidden */
2013-10-18 00:09:50 +02:00
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
2013-10-13 12:28:16 +02:00
$dbQuery->andNot('hidden');
2013-10-14 00:25:40 +02:00
2013-10-15 22:48:44 +02:00
/* query tokens */
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
2013-10-13 13:17:23 +02:00
if (count($tokens) > $this->config->browsing->maxSearchTokens)
throw new SimpleException('Too many search tokens (maximum: ' . $this->config->browsing->maxSearchTokens . ')');
2013-10-15 22:48:44 +02:00
/* tokens */
$this->decorateSearchQuery($dbQuery, $tokens);
2013-10-09 11:45:18 +02:00
};
$countDbQuery = R::$f->begin();
2013-10-14 10:22:53 +02:00
$countDbQuery->select('COUNT(1)')->as('count');
2013-10-13 13:17:23 +02:00
$buildDbQuery($countDbQuery, $query);
2013-10-09 11:45:18 +02:00
$postCount = intval($countDbQuery->get('row')['count']);
$pageCount = ceil($postCount / $postsPerPage);
$page = max(1, min($pageCount, $page));
2013-10-09 11:45:18 +02:00
$searchDbQuery = R::$f->begin();
$searchDbQuery->select('post.*');
2013-10-13 13:17:23 +02:00
$buildDbQuery($searchDbQuery, $query);
2013-10-09 11:45:18 +02:00
$searchDbQuery->limit('?')->put($postsPerPage);
$searchDbQuery->offset('?')->put(($page - 1) * $postsPerPage);
2013-10-09 11:45:18 +02:00
$posts = $searchDbQuery->get();
$posts = R::convertToBeans('post', $posts);
R::preload($posts, ['uploader' => 'user']);
2013-10-16 13:07:01 +02:00
$this->context->transport->paginator = new StdClass;
$this->context->transport->paginator->page = $page;
$this->context->transport->paginator->pageCount = $pageCount;
$this->context->transport->paginator->entityCount = $postCount;
$this->context->transport->paginator->entities = $posts;
$this->context->transport->posts = $posts;
2013-10-05 19:24:08 +02:00
}
2013-10-08 23:02:31 +02:00
2013-10-13 12:28:16 +02:00
/**
* @route /favorites
* @route /favorites/{page}
* @validate page \d*
*/
public function favoritesAction($page = 1)
{
$this->listAction('favmin:1', $page);
$this->context->viewName = 'post-list';
}
2013-10-21 13:13:10 +02:00
/**
* @route /random
* @route /random/{page}
* @validate page \d*
*/
public function randomAction($page = 1)
{
$this->listAction('order:random', $page);
$this->context->viewName = 'post-list';
}
2013-10-05 19:24:08 +02:00
/**
* @route /post/upload
*/
public function uploadAction()
{
2013-10-07 00:44:17 +02:00
$this->context->stylesheets []= 'upload.css';
2013-10-25 09:40:33 +02:00
$this->context->stylesheets []= 'tabs.css';
2013-10-07 00:44:17 +02:00
$this->context->scripts []= 'upload.js';
2013-10-05 21:22:28 +02:00
$this->context->subTitle = 'upload';
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::UploadPost);
2013-10-16 18:07:23 +02:00
if ($this->config->registration->needEmailForUploading)
PrivilegesHelper::confirmEmail($this->context->user);
2013-10-07 00:44:17 +02:00
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
2013-10-07 00:44:17 +02:00
{
2013-10-13 12:28:16 +02:00
/* file contents */
2013-10-25 09:40:33 +02:00
if (isset($_FILES['file']))
{
$suppliedFile = $_FILES['file'];
self::handleUploadErrors($suppliedFile);
$origName = basename($suppliedFile['name']);
$sourcePath = $suppliedFile['tmp_name'];
}
elseif (InputHelper::get('url'))
{
$url = InputHelper::get('url');
2013-10-25 13:18:03 +02:00
$origName = $url;
2013-10-25 09:40:33 +02:00
if (!preg_match('/^https?:\/\//', $url))
throw new SimpleException('Invalid URL "' . $url . '"');
2013-10-25 13:18:03 +02:00
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $url, $matches))
2013-10-25 09:40:33 +02:00
{
2013-10-25 13:18:03 +02:00
$origName = $matches[1];
$postType = PostType::Youtube;
$sourcePath = null;
2013-10-25 09:40:33 +02:00
}
2013-10-25 13:18:03 +02:00
else
2013-10-25 09:40:33 +02:00
{
2013-10-25 13:18:03 +02:00
$sourcePath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
//warning: low level sh*t ahead
//download the URL $url into $sourcePath
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
set_time_limit(0);
$urlFP = fopen($url, 'rb');
if (!$urlFP)
throw new SimpleException('Cannot open URL for reading');
$sourceFP = fopen($sourcePath, 'w+b');
if (!$sourceFP)
2013-10-25 09:40:33 +02:00
{
2013-10-25 13:18:03 +02:00
fclose($urlFP);
throw new SimpleException('Cannot open file for writing');
}
try
{
while (!feof($urlFP))
{
$buffer = fread($urlFP, 4 * 1024);
if (fwrite($sourceFP, $buffer) === false)
throw new SimpleException('Cannot write into file');
fflush($sourceFP);
if (ftell($sourceFP) > $maxBytes)
throw new SimpleException('File is too big (maximum allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
}
}
finally
{
fclose($urlFP);
fclose($sourceFP);
2013-10-25 09:40:33 +02:00
}
}
}
2013-10-13 12:28:16 +02:00
/* file details */
2013-10-25 13:18:03 +02:00
$mimeType = $sourcePath ? mime_content_type($sourcePath) : null;
2013-10-12 12:38:49 +02:00
$imageWidth = null;
$imageHeight = null;
switch ($mimeType)
2013-10-07 00:44:17 +02:00
{
case 'image/gif':
case 'image/png':
case 'image/jpeg':
$postType = PostType::Image;
2013-10-25 09:40:33 +02:00
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
2013-10-07 00:44:17 +02:00
break;
case 'application/x-shockwave-flash':
$postType = PostType::Flash;
2013-10-25 09:40:33 +02:00
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
2013-10-07 00:44:17 +02:00
break;
default:
2013-10-25 13:18:03 +02:00
if (!isset($postType))
throw new SimpleException('Invalid file type "' . $mimeType . '"');
2013-10-07 00:44:17 +02:00
}
2013-10-25 13:18:03 +02:00
if ($sourcePath)
{
$fileSize = filesize($sourcePath);
$fileHash = md5_file($sourcePath);
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$fileHash]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
else
{
$fileSize = 0;
$fileHash = null;
if ($postType == PostType::Youtube)
{
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
}
2013-10-07 00:44:17 +02:00
do
{
$name = md5(mt_rand() . uniqid());
2013-10-09 19:25:56 +02:00
$path = $this->config->main->filesPath . DS . $name;
2013-10-07 00:44:17 +02:00
}
while (file_exists($path));
2013-10-13 21:05:01 +02:00
/* safety */
2013-10-15 20:22:52 +02:00
$suppliedSafety = InputHelper::get('safety');
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
2013-10-13 21:05:01 +02:00
/* tags */
2013-10-15 20:22:52 +02:00
$suppliedTags = InputHelper::get('tags');
2013-10-20 00:31:22 +02:00
$suppliedTags = Model_Tag::validateTags($suppliedTags);
2013-10-15 20:22:52 +02:00
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
2013-10-13 21:05:01 +02:00
/* source */
$suppliedSource = InputHelper::get('source');
$suppliedSource = Model_Post::validateSource($suppliedSource);
2013-10-13 12:28:16 +02:00
/* db storage */
2013-10-07 00:44:17 +02:00
$dbPost = R::dispense('post');
$dbPost->type = $postType;
$dbPost->name = $name;
2013-10-25 09:40:33 +02:00
$dbPost->orig_name = $origName;
2013-10-09 12:36:14 +02:00
$dbPost->file_hash = $fileHash;
2013-10-25 13:18:03 +02:00
$dbPost->file_size = $fileSize;
$dbPost->mime_type = $mimeType;
2013-10-07 00:44:17 +02:00
$dbPost->safety = $suppliedSafety;
$dbPost->source = $suppliedSource;
2013-10-13 12:28:16 +02:00
$dbPost->hidden = false;
$dbPost->upload_date = time();
2013-10-12 12:38:49 +02:00
$dbPost->image_width = $imageWidth;
$dbPost->image_height = $imageHeight;
if ($this->context->loggedIn)
$dbPost->uploader = $this->context->user;
2013-10-12 14:53:47 +02:00
$dbPost->ownFavoritee = [];
$dbPost->sharedTag = $dbTags;
2013-10-07 00:44:17 +02:00
2013-10-25 13:18:03 +02:00
if ($sourcePath)
{
if (is_uploaded_file($sourcePath))
move_uploaded_file($sourcePath, $path);
else
rename($sourcePath, $path);
}
2013-10-07 00:44:17 +02:00
R::store($dbPost);
$this->context->transport->success = true;
}
2013-10-05 19:24:08 +02:00
}
2013-10-13 12:28:16 +02:00
/**
* @route /post/{id}/edit
2013-10-13 12:28:16 +02:00
*/
public function editAction($id)
{
$post = Model_Post::locate($id);
2013-10-13 12:28:16 +02:00
R::preload($post, ['uploader' => 'user']);
$this->context->transport->post = $post;
2013-10-13 12:28:16 +02:00
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
2013-10-13 12:28:16 +02:00
{
2013-10-21 23:50:30 +02:00
/* safety */
$suppliedSafety = InputHelper::get('safety');
if ($suppliedSafety !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
$post->safety = $suppliedSafety;
$edited = true;
}
2013-10-13 12:28:16 +02:00
2013-10-21 23:50:30 +02:00
/* tags */
$suppliedTags = InputHelper::get('tags');
if ($suppliedTags !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$currentToken = self::serializeTags($post);
if (InputHelper::get('tags-token') != $currentToken)
throw new SimpleException('Someone else has changed the tags in the meantime');
$suppliedTags = Model_Tag::validateTags($suppliedTags);
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
$post->sharedTag = $dbTags;
$edited = true;
}
2013-10-13 12:28:16 +02:00
2013-10-21 23:50:30 +02:00
/* thumbnail */
if (!empty($_FILES['thumb']['name']))
{
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['thumb'];
self::handleUploadErrors($suppliedFile);
$mimeType = mime_content_type($suppliedFile['tmp_name']);
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
if ($imageWidth != $this->config->browsing->thumbWidth)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbWidth . ')');
if ($imageWidth != $this->config->browsing->thumbHeight)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbHeight . ')');
2013-10-27 19:32:48 +01:00
$path = $this->config->main->thumbsPath . DS . $post->name . '.custom';
2013-10-21 23:50:30 +02:00
move_uploaded_file($suppliedFile['tmp_name'], $path);
}
2013-10-13 12:28:16 +02:00
2013-10-21 23:50:30 +02:00
/* source */
$suppliedSource = InputHelper::get('source');
if ($suppliedSource !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSource = Model_Post::validateSource($suppliedSource);
$post->source = $suppliedSource;
$edited = true;
}
2013-10-13 12:28:16 +02:00
R::store($post);
2013-10-21 23:50:30 +02:00
$this->context->transport->success = true;
}
2013-10-13 12:28:16 +02:00
}
/**
* @route /post/{id}/hide
2013-10-13 12:28:16 +02:00
*/
public function hideAction($id)
{
$post = Model_Post::locate($id);
2013-10-22 00:20:58 +02:00
R::preload($post, ['uploader' => 'user']);
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
{
$post->hidden = true;
R::store($post);
$this->context->transport->success = true;
}
2013-10-13 12:28:16 +02:00
}
/**
* @route /post/{id}/unhide
2013-10-13 12:28:16 +02:00
*/
public function unhideAction($id)
{
$post = Model_Post::locate($id);
2013-10-22 00:20:58 +02:00
R::preload($post, ['uploader' => 'user']);
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
{
$post->hidden = false;
R::store($post);
$this->context->transport->success = true;
}
2013-10-13 12:28:16 +02:00
}
/**
* @route /post/{id}/delete
2013-10-13 12:28:16 +02:00
*/
public function deleteAction($id)
{
$post = Model_Post::locate($id);
2013-10-22 00:20:58 +02:00
R::preload($post, ['uploader' => 'user']);
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
{
//remove stuff from auxiliary tables
$post->ownFavoritee = [];
$post->sharedTag = [];
R::store($post);
R::trash($post);
$this->context->transport->success = true;
}
2013-10-13 12:28:16 +02:00
}
2013-10-12 14:53:47 +02:00
/**
* @route /post/{id}/add-fav
* @route /post/{id}/fav-add
2013-10-12 14:53:47 +02:00
*/
public function addFavoriteAction($id)
{
$post = Model_Post::locate($id);
2013-10-12 14:53:47 +02:00
R::preload($post, ['favoritee' => 'user']);
2013-10-21 23:50:30 +02:00
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
{
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
foreach ($post->via('favoritee')->sharedUser as $fav)
if ($fav->id == $this->context->user->id)
throw new SimpleException('Already in favorites');
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
$post->link('favoritee')->user = $this->context->user;
R::store($post);
$this->context->transport->success = true;
}
2013-10-12 14:53:47 +02:00
}
/**
* @route /post/{id}/rem-fav
* @route /post/{id}/fav-rem
2013-10-12 14:53:47 +02:00
*/
public function remFavoriteAction($id)
{
$post = Model_Post::locate($id);
2013-10-12 14:53:47 +02:00
R::preload($post, ['favoritee' => 'user']);
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
if (InputHelper::get('submit'))
{
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
$finalKey = null;
foreach ($post->ownFavoritee as $key => $fav)
if ($fav->user->id == $this->context->user->id)
$finalKey = $key;
2013-10-12 14:53:47 +02:00
2013-10-21 23:50:30 +02:00
if ($finalKey === null)
throw new SimpleException('Not in favorites');
unset ($post->ownFavoritee[$finalKey]);
R::store($post);
$this->context->transport->success = true;
}
2013-10-12 14:53:47 +02:00
}
2013-10-08 23:02:31 +02:00
2013-10-19 13:38:20 +02:00
/**
* @route /post/{id}/feature
*/
public function featureAction($id)
{
$post = Model_Post::locate($id);
PrivilegesHelper::confirmWithException(Privilege::FeaturePost);
Model_Property::set(Model_Property::FeaturedPostId, $post->id);
Model_Property::set(Model_Property::FeaturedPostUserId, $this->context->user->id);
Model_Property::set(Model_Property::FeaturedPostDate, time());
$this->context->transport->success = true;
}
2013-10-05 19:24:08 +02:00
/**
* Action that decorates the page containing the post.
2013-10-05 19:24:08 +02:00
* @route /post/{id}
*/
public function viewAction($id)
2013-10-05 19:24:08 +02:00
{
$post = Model_Post::locate($id);
R::preload($post, [
'favoritee' => 'user',
'uploader' => 'user',
'tag',
'comment',
'ownComment.commenter' => 'user']);
2013-10-12 10:46:15 +02:00
2013-10-13 12:28:16 +02:00
if ($post->hidden)
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::ViewPost, 'hidden');
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
2013-10-13 12:28:16 +02:00
$buildNextPostQuery = function($dbQuery, $id, $next)
{
$dbQuery->select('id')
->from('post')
->where($next ? 'id > ?' : 'id < ?')
->put($id);
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
{
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
$this->context->user->hasEnabledSafety($safety);
});
$dbQuery->and('safety')->in('(' . R::genSlots($allowedSafety) . ')');
foreach ($allowedSafety as $s)
$dbQuery->put($s);
2013-10-18 00:09:50 +02:00
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
2013-10-13 12:28:16 +02:00
$dbQuery->andNot('hidden');
$dbQuery->orderBy($next ? 'id asc' : 'id desc')
->limit(1);
};
$prevPostQuery = R::$f->begin();
$buildNextPostQuery($prevPostQuery, $id, false);
$prevPost = $prevPostQuery->get('row');
$nextPostQuery = R::$f->begin();
$buildNextPostQuery($nextPostQuery, $id, true);
$nextPost = $nextPostQuery->get('row');
2013-10-12 14:53:47 +02:00
$favorite = false;
if ($this->context->loggedIn)
foreach ($post->ownFavoritee as $fav)
if ($fav->user->id == $this->context->user->id)
$favorite = true;
2013-10-12 10:46:15 +02:00
$dbQuery = R::$f->begin();
$dbQuery->select('tag.name, COUNT(1) AS count');
$dbQuery->from('tag');
$dbQuery->innerJoin('post_tag');
$dbQuery->on('tag.id = post_tag.tag_id');
$dbQuery->where('tag.id')->in('(' . R::genSlots($post->sharedTag) . ')');
2013-10-12 10:46:15 +02:00
foreach ($post->sharedTag as $tag)
$dbQuery->put($tag->id);
$dbQuery->groupBy('tag.id');
$rows = $dbQuery->get();
$this->context->transport->tagDistribution = [];
foreach ($rows as $row)
$this->context->transport->tagDistribution[$row['name']] = $row['count'];
2013-10-09 21:58:57 +02:00
$this->context->stylesheets []= 'post-view.css';
$this->context->stylesheets []= 'comment-small.css';
2013-10-12 14:53:47 +02:00
$this->context->scripts []= 'post-view.js';
2013-10-19 22:56:56 +02:00
$this->context->subTitle = 'showing @' . $post->id . ' &ndash; ' . join(', ', array_map(function($x) { return $x['name']; }, $post->sharedTag));
2013-10-12 14:53:47 +02:00
$this->context->favorite = $favorite;
$this->context->transport->post = $post;
2013-10-13 12:28:16 +02:00
$this->context->transport->prevPostId = $prevPost ? $prevPost['id'] : null;
$this->context->transport->nextPostId = $nextPost ? $nextPost['id'] : null;
$this->context->transport->tagsToken = self::serializeTags($post);
}
2013-10-08 23:02:31 +02:00
/**
* Action that renders the thumbnail of the requested file and sends it to user.
* @route /post/{name}/thumb
2013-10-08 23:02:31 +02:00
*/
public function thumbAction($name)
2013-10-08 23:02:31 +02:00
{
$this->context->layoutName = 'layout-file';
2013-10-27 19:32:48 +01:00
$path = $this->config->main->thumbsPath . DS . $name . '.custom';
if (!file_exists($path))
$path = $this->config->main->thumbsPath . DS . $name . '.default';
2013-10-08 23:02:31 +02:00
if (!file_exists($path))
{
2013-10-23 22:16:08 +02:00
$post = Model_Post::locate($name);
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
2013-10-09 23:46:22 +02:00
$srcPath = $this->config->main->filesPath . DS . $post->name;
2013-10-08 23:02:31 +02:00
$dstWidth = $this->config->browsing->thumbWidth;
$dstHeight = $this->config->browsing->thumbHeight;
2013-10-25 13:18:03 +02:00
if ($post->type == PostType::Youtube)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
$contents = file_get_contents('http://img.youtube.com/vi/' . $post->orig_name . '/mqdefault.jpg');
file_put_contents($tmpPath, $contents);
if (file_exists($tmpPath))
$srcImage = imagecreatefromjpeg($tmpPath);
}
else switch ($post->mime_type)
2013-10-08 23:02:31 +02:00
{
case 'image/jpeg':
$srcImage = imagecreatefromjpeg($srcPath);
break;
case 'image/png':
$srcImage = imagecreatefrompng($srcPath);
break;
case 'image/gif':
$srcImage = imagecreatefromgif($srcPath);
break;
case 'application/x-shockwave-flash':
2013-10-22 21:44:22 +02:00
$srcImage = null;
exec('which dump-gnash', $tmp, $exitCode);
2013-10-22 21:44:22 +02:00
if ($exitCode == 0)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
exec('dump-gnash --screenshot last --screenshot-file ' . $tmpPath . ' -1 -r1 --max-advances 15 ' . $srcPath);
2013-10-22 21:44:22 +02:00
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
}
if (!$srcImage)
{
exec('which swfrender', $tmp, $exitCode);
if ($exitCode == 0)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
}
}
2013-10-08 23:02:31 +02:00
break;
default:
break;
}
if (isset($srcImage))
{
switch ($this->config->browsing->thumbStyle)
{
case 'outside':
$dstImage = ThumbnailHelper::cropOutside($srcImage, $dstWidth, $dstHeight);
break;
case 'inside':
$dstImage = ThumbnailHelper::cropInside($srcImage, $dstWidth, $dstHeight);
break;
default:
throw new SimpleException('Unknown thumbnail crop style');
}
2013-10-27 19:32:48 +01:00
imagepng($dstImage, $path);
2013-10-08 23:02:31 +02:00
imagedestroy($srcImage);
imagedestroy($dstImage);
}
2013-10-22 23:40:36 +02:00
else
{
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.png';
}
if (isset($tmpPath))
unlink($tmpPath);
2013-10-08 23:02:31 +02:00
}
if (!is_readable($path))
throw new SimpleException('Thumbnail file is not readable');
2013-10-19 13:00:03 +02:00
$this->context->transport->cacheDaysToLive = 30;
2013-10-08 23:02:31 +02:00
$this->context->transport->mimeType = 'image/png';
$this->context->transport->fileHash = 'thumb' . md5($name . filemtime($path));
2013-10-08 23:02:31 +02:00
$this->context->transport->filePath = $path;
}
/**
* Action that renders the requested file itself and sends it to user.
* @route /post/{name}/retrieve
*/
2013-10-07 23:17:33 +02:00
public function retrieveAction($name)
{
$this->context->layoutName = 'layout-file';
$post = Model_Post::locate($name, true);
2013-10-13 13:37:18 +02:00
R::preload($post, ['tag']);
2013-10-18 00:09:50 +02:00
PrivilegesHelper::confirmWithException(Privilege::RetrievePost);
PrivilegesHelper::confirmWithException(Privilege::RetrievePost, PostSafety::toString($post->safety));
2013-10-09 19:25:56 +02:00
$path = $this->config->main->filesPath . DS . $post->name;
if (!file_exists($path))
throw new SimpleException('Post file does not exist');
if (!is_readable($path))
throw new SimpleException('Post file is not readable');
2013-10-13 13:37:18 +02:00
$ext = substr($post->orig_name, strrpos($post->orig_name, '.') + 1);
if (strpos($post->orig_name, '.') === false)
$ext = '.dat';
$fn = sprintf('%s_%s_%s.%s',
$this->config->main->title,
$post->id, join(',', array_map(function($tag) { return $tag->name; }, $post->sharedTag)),
$ext);
$fn = preg_replace('/[[:^print:]]/', '', $fn);
2013-10-19 13:00:03 +02:00
$ttl = 60 * 60 * 24 * 14;
2013-10-13 14:01:07 +02:00
2013-10-19 13:00:03 +02:00
$this->context->transport->cacheDaysToLive = 14;
2013-10-13 13:37:18 +02:00
$this->context->transport->customFileName = $fn;
$this->context->transport->mimeType = $post->mimeType;
2013-10-19 13:00:03 +02:00
$this->context->transport->fileHash = 'post' . $post->file_hash;
$this->context->transport->filePath = $path;
2013-10-05 12:55:03 +02:00
}
2013-10-15 22:48:44 +02:00
private function decorateSearchQuery($dbQuery, $tokens)
{
$orderColumn = 'post.id';
2013-10-15 23:26:27 +02:00
$orderDir = 1;
2013-10-15 22:48:44 +02:00
$randomReset = true;
foreach ($tokens as $token)
{
if ($token{0} == '-')
{
$andFunc = 'andNot';
2013-10-15 22:48:44 +02:00
$token = substr($token, 1);
2013-10-15 23:26:27 +02:00
$neg = true;
2013-10-15 22:48:44 +02:00
}
else
{
$andFunc = 'and';
2013-10-15 23:26:27 +02:00
$neg = false;
2013-10-15 22:48:44 +02:00
}
$pos = strpos($token, ':');
if ($pos === false)
{
$val = $token;
$dbQuery
->$andFunc()
2013-10-15 22:48:44 +02:00
->exists()
->open()
->select('1')
->from('post_tag')
->innerJoin('tag')
->on('post_tag.tag_id = tag.id')
->where('post_id = post.id')
2013-10-21 14:32:47 +02:00
->and('LOWER(tag.name) = LOWER(?)')->put($val)
2013-10-15 22:48:44 +02:00
->close();
continue;
}
$key = substr($token, 0, $pos);
$val = substr($token, $pos + 1);
switch ($key)
{
case 'favmin':
case 'favmax':
$operator = $key == 'favmin' ? '>=' : '<=';
2013-10-15 22:48:44 +02:00
$dbQuery
->$andFunc('fav_count ' . $operator . ' ?')->put(intval($val));
2013-10-15 22:48:44 +02:00
break;
case 'type':
switch ($val)
{
case 'swf':
$type = PostType::Flash;
break;
case 'img':
$type = PostType::Image;
break;
2013-10-25 13:18:03 +02:00
case 'yt':
case 'youtube':
$type = PostType::Youtube;
break;
2013-10-15 22:48:44 +02:00
default:
throw new SimpleException('Unknown type "' . $val . '"');
}
$dbQuery
->$andFunc('type = ?')
2013-10-15 22:48:44 +02:00
->put($type);
break;
case 'date':
case 'datemin':
case 'datemax':
list ($year, $month, $day) = explode('-', $val . '-0-0');
$yearMin = $yearMax = intval($year);
$monthMin = $monthMax = intval($month);
$monthMin = $monthMin ?: 1;
$monthMax = $monthMax ?: 12;
$dayMin = $dayMax = intval($day);
$dayMin = $dayMin ?: 1;
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
if ($key == 'date')
{
$dbQuery
->$andFunc('upload_date >= ?')
2013-10-15 22:48:44 +02:00
->and('upload_date <= ?')
->put($timeMin)
->put($timeMax);
}
elseif ($key == 'datemin')
{
$dbQuery
->$andFunc('upload_date >= ?')
2013-10-15 22:48:44 +02:00
->put($timeMin);
}
elseif ($key == 'datemax')
{
$dbQuery
->$andFunc('upload_date <= ?')
2013-10-15 22:48:44 +02:00
->put($timeMax);
}
else
{
throw new Exception('Invalid key');
}
break;
case 'fav':
case 'favs':
case 'favoritee':
case 'favoriter':
$dbQuery
->$andFunc()
2013-10-15 22:48:44 +02:00
->exists()
->open()
->select('1')
->from('favoritee')
->innerJoin('user')
->on('favoritee.user_id = user.id')
->where('post_id = post.id')
->and('user.name = ?')->put($val)
->close();
break;
case 'submit':
case 'upload':
case 'uploader':
case 'uploaded':
$dbQuery
->$andFunc('uploader_id = ')
2013-10-15 22:48:44 +02:00
->open()
->select('user.id')
->from('user')
->where('name = ?')->put($val)
->close();
break;
case 'order':
2013-10-15 23:26:27 +02:00
if (substr($val, -4) == 'desc')
{
$orderDir = 1;
$val = rtrim(substr($val, 0, -4), ',');
}
elseif (substr($val, -3) == 'asc')
{
$orderDir = -1;
$val = rtrim(substr($val, 0, -3), ',');
}
2013-10-15 22:48:44 +02:00
if ($val{0} == '-')
{
2013-10-15 23:26:27 +02:00
$orderDir *= -1;
2013-10-15 22:48:44 +02:00
$val = substr($val, 1);
}
2013-10-15 23:26:27 +02:00
if ($neg)
{
$orderDir *= -1;
}
2013-10-15 22:48:44 +02:00
switch ($val)
{
2013-10-15 23:26:27 +02:00
case 'id':
$orderColumn = 'post.id';
2013-10-15 23:26:27 +02:00
break;
2013-10-15 22:48:44 +02:00
case 'date':
$orderColumn = 'post.upload_date';
2013-10-15 22:48:44 +02:00
break;
case 'comment':
case 'comments':
case 'commentcount':
$orderColumn = 'comment_count';
2013-10-15 22:48:44 +02:00
break;
case 'fav':
case 'favs':
case 'favcount':
$orderColumn = 'fav_count';
2013-10-15 22:48:44 +02:00
break;
case 'tag':
case 'tags':
case 'tagcount':
$orderColumn = 'tag_count';
2013-10-15 22:48:44 +02:00
break;
case 'random':
//seeding works like this: if you visit anything
//that triggers order other than random, the seed
//is going to reset. however, it stays the same as
//long as you keep visiting pages with order:random
//specified.
$randomReset = false;
if (!isset($_SESSION['browsing-seed']))
$_SESSION['browsing-seed'] = mt_rand();
$seed = $_SESSION['browsing-seed'];
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
break;
default:
throw new SimpleException('Unknown key "' . $val . '"');
}
break;
default:
throw new SimpleException('Unknown key "' . $key . '"');
}
}
if ($randomReset)
unset($_SESSION['browsing-seed']);
$dbQuery->orderBy($orderColumn);
if ($orderDir == 1)
$dbQuery->desc();
else
$dbQuery->asc();
2013-10-15 22:48:44 +02:00
}
2013-10-05 12:55:03 +02:00
}