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-29 09:04:42 +01:00
* @ route / { source }
* @ route / { source } / { page }
* @ route / { source } / { query } /
* @ route / { source } / { query } / { page }
* @ route / { source } / { additionalInfo } / { query } /
* @ route / { source } / { additionalInfo } / { query } / { page }
* @ validate source posts | mass - tag
2013-10-09 11:45:18 +02:00
* @ validate page \d *
* @ validate query [ ^ \ / ] *
2013-10-29 09:04:42 +01:00
* @ validate additionalInfo [ ^ \ / ] *
2013-10-05 12:55:03 +02:00
*/
2013-10-29 09:04:42 +01:00
public function listAction ( $query = null , $page = 1 , $source = 'posts' , $additionalInfo = null )
2013-10-05 12:55:03 +02:00
{
2013-10-17 22:57:32 +02:00
$this -> context -> stylesheets [] = 'post-small.css' ;
2013-10-09 11:45:18 +02:00
$this -> context -> stylesheets [] = 'post-list.css' ;
2013-11-01 12:48:02 +01:00
$this -> context -> stylesheets [] = 'tabs.css' ;
2013-10-10 00:12:27 +02:00
$this -> context -> stylesheets [] = 'paginator.css' ;
2013-11-01 12:48:02 +01:00
$this -> context -> viewName = 'post-list-wrapper' ;
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-29 09:04:42 +01:00
if ( $source == 'mass-tag' )
$this -> context -> scripts [] = 'mass-tag.js' ;
$this -> context -> source = $source ;
$this -> context -> additionalInfo = $additionalInfo ;
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
2013-10-05 22:52:55 +02:00
$formQuery = InputHelper :: get ( 'query' );
2013-10-13 22:20:06 +02:00
if ( $formQuery !== null )
2013-10-05 22:52:55 +02:00
{
2013-10-13 22:20:06 +02:00
$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' );
2013-10-29 09:04:42 +01:00
$url = \Chibi\UrlHelper :: route ( 'post' , 'list' , [ 'source' => $source , 'additionalInfo' => $additionalInfo , 'query' => urlencode ( $formQuery )]);
2013-10-05 22:52:55 +02:00
\Chibi\UrlHelper :: forward ( $url );
return ;
}
2013-10-13 22:20:06 +02:00
$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-29 09:04:42 +01:00
if ( $source == 'mass-tag' )
{
PrivilegesHelper :: confirmWithException ( Privilege :: MassTag );
$this -> context -> massTagTag = $additionalInfo ;
$this -> context -> massTagQuery = $query ;
}
2013-10-07 20:44:14 +02:00
2013-10-28 11:19:15 +01:00
$postCount = Model_Post :: getEntityCount ( $query );
2013-10-09 11:45:18 +02:00
$pageCount = ceil ( $postCount / $postsPerPage );
2013-10-13 22:20:06 +02:00
$page = max ( 1 , min ( $pageCount , $page ));
2013-10-28 11:19:15 +01:00
$posts = Model_Post :: getEntities ( $query , $postsPerPage , $page );
2013-10-09 11:45:18 +02:00
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 ;
2013-10-07 20:44:14 +02:00
$this -> context -> transport -> posts = $posts ;
2013-10-05 19:24:08 +02:00
}
2013-10-08 23:02:31 +02:00
2013-10-29 09:04:42 +01:00
/**
* @ route / post / { id } / toggle - tag / { tag }
* @ validate tag [ ^ \ / ] *
*/
public function toggleTagAction ( $id , $tag )
{
$post = Model_Post :: locate ( $id );
R :: preload ( $post , [ 'uploader' => 'user' ]);
$this -> context -> transport -> post = $post ;
$tag = Model_Tag :: validateTag ( $tag );
if ( InputHelper :: get ( 'submit' ))
{
PrivilegesHelper :: confirmWithException ( Privilege :: MassTag );
$tags = array_map ( function ( $x ) { return $x -> name ; }, $post -> sharedTag );
if ( in_array ( $tag , $tags ))
2013-11-16 21:21:43 +01:00
{
2013-10-29 09:04:42 +01:00
$tags = array_diff ( $tags , [ $tag ]);
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-tag-del' , '{user} untagged {post} with {tag}' , [ 'post' => TextHelper :: reprPost ( $post ), 'tag' => TextHelper :: reprTag ( $tag )]);
2013-11-16 21:21:43 +01:00
}
2013-10-29 09:04:42 +01:00
else
2013-11-16 21:21:43 +01:00
{
2013-10-29 09:04:42 +01:00
$tags += [ $tag ];
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-tag-add' , '{user} tagged {post} with {tag}' , [ 'post' => TextHelper :: reprPost ( $post ), 'tag' => TextHelper :: reprTag ( $tag )]);
2013-11-16 21:21:43 +01:00
}
2013-10-29 09:04:42 +01:00
$dbTags = Model_Tag :: insertOrUpdate ( $tags );
$post -> sharedTag = $dbTags ;
R :: store ( $post );
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-29 09:04:42 +01: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 );
}
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 );
}
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-11-13 23:36:58 +01:00
$mimeType = null ;
if ( $sourcePath )
{
if ( function_exists ( 'mime_content_type' ))
$mimeType = mime_content_type ( $sourcePath );
else
$mimeType = $suppliedFile [ 'type' ];
}
2013-10-12 12:38:49 +02:00
$imageWidth = null ;
$imageHeight = null ;
2013-10-09 01:17:25 +02:00
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
2013-10-19 20:58:51 +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 ;
2013-10-09 01:17:25 +02:00
$dbPost -> mime_type = $mimeType ;
2013-10-07 00:44:17 +02:00
$dbPost -> safety = $suppliedSafety ;
2013-10-19 20:58:51 +02:00
$dbPost -> source = $suppliedSource ;
2013-10-13 12:28:16 +02:00
$dbPost -> hidden = false ;
2013-10-07 20:44:14 +02:00
$dbPost -> upload_date = time ();
2013-10-12 12:38:49 +02:00
$dbPost -> image_width = $imageWidth ;
$dbPost -> image_height = $imageHeight ;
2013-10-30 16:51:22 +01:00
if ( $this -> context -> loggedIn and ! InputHelper :: get ( 'anonymous' ))
2013-10-27 20:39:32 +01:00
$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 );
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-new' , '{user} added {post} tagged with {tags} marked as {safety}' , [
'post' => TextHelper :: reprPost ( $dbPost ),
'tags' => join ( ', ' , array_map ([ 'TextHelper' , 'reprTag' ], $dbTags )),
'safety' => PostSafety :: toString ( $dbPost -> safety )]);
2013-11-16 21:21:43 +01:00
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-07 00:44:17 +02:00
}
2013-10-05 19:24:08 +02:00
}
2013-10-13 12:28:16 +02:00
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / edit
2013-10-13 12:28:16 +02:00
*/
public function editAction ( $id )
{
2013-10-17 22:57:32 +02:00
$post = Model_Post :: locate ( $id );
2013-10-13 12:28:16 +02:00
R :: preload ( $post , [ 'uploader' => 'user' ]);
2013-10-15 13:14:48 +02:00
$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-11-16 21:21:43 +01:00
LogHelper :: bufferChanges ();
2013-10-21 23:50:30 +02:00
/* safety */
$suppliedSafety = InputHelper :: get ( 'safety' );
2013-11-16 21:21:43 +01:00
if ( $suppliedSafety !== null and $suppliedSafety != $post -> safety )
2013-10-21 23:50:30 +02:00
{
PrivilegesHelper :: confirmWithException ( Privilege :: EditPostSafety , PrivilegesHelper :: getIdentitySubPrivilege ( $post -> uploader ));
$suppliedSafety = Model_Post :: validateSafety ( $suppliedSafety );
$post -> safety = $suppliedSafety ;
$edited = true ;
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-edit' , '{user} changed safety for {post} to {safety}' , [ 'post' => TextHelper :: reprPost ( $post ), 'safety' => PostSafety :: toString ( $post -> safety )]);
2013-10-21 23:50:30 +02:00
}
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 );
2013-11-16 21:21:43 +01:00
$oldTags = array_map ( function ( $tag ) { return $tag -> name ; }, $post -> sharedTag );
2013-10-21 23:50:30 +02:00
$post -> sharedTag = $dbTags ;
$edited = true ;
2013-11-16 21:21:43 +01:00
foreach ( array_diff ( $oldTags , $suppliedTags ) as $tag )
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-tag-del' , '{user} untagged {post} with {tag}' , [ 'post' => TextHelper :: reprPost ( $post ), 'tag' => TextHelper :: reprTag ( $tag )]);
2013-11-16 21:21:43 +01:00
foreach ( array_diff ( $suppliedTags , $oldTags ) as $tag )
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-tag-add' , '{user} tagged {post} with {tag}' , [ 'post' => TextHelper :: reprPost ( $post ), 'tag' => TextHelper :: reprTag ( $tag )]);
2013-10-21 23:50:30 +02:00
}
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-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-edit' , '{user} added custom thumb for {post}' , [ 'post' => TextHelper :: reprPost ( $post )]);
2013-10-21 23:50:30 +02:00
}
2013-10-13 12:28:16 +02:00
2013-10-19 20:58:51 +02:00
2013-10-21 23:50:30 +02:00
/* source */
$suppliedSource = InputHelper :: get ( 'source' );
2013-11-16 21:21:43 +01:00
if ( $suppliedSource !== null and $suppliedSource != $post -> sorce )
2013-10-21 23:50:30 +02:00
{
PrivilegesHelper :: confirmWithException ( Privilege :: EditPostSource , PrivilegesHelper :: getIdentitySubPrivilege ( $post -> uploader ));
$suppliedSource = Model_Post :: validateSource ( $suppliedSource );
$post -> source = $suppliedSource ;
$edited = true ;
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-edit' , '{user} changed source for {post} to {source}' , [ 'post' => TextHelper :: reprPost ( $post ), 'source' => $post -> source ]);
2013-10-21 23:50:30 +02:00
}
2013-10-19 20:58:51 +02:00
2013-10-30 20:20:01 +01:00
/* relations */
$suppliedRelations = InputHelper :: get ( 'relations' );
if ( $suppliedRelations !== null )
{
PrivilegesHelper :: confirmWithException ( Privilege :: EditPostRelations , PrivilegesHelper :: getIdentitySubPrivilege ( $post -> uploader ));
$relatedIds = array_filter ( preg_split ( '/\D/' , $suppliedRelations ));
$relatedPosts = [];
foreach ( $relatedIds as $relatedId )
{
if ( $relatedId == $post -> id )
continue ;
if ( count ( $relatedPosts ) > $this -> config -> browsing -> maxRelatedPosts )
throw new SimpleException ( 'Too many related posts (maximum: ' . $this -> config -> browsing -> maxRelatedPosts . ')' );
$relatedPosts [] = Model_Post :: locate ( $relatedId );
}
2013-11-16 21:21:43 +01:00
$oldRelatedIds = array_map ( function ( $post ) { return $post -> id ; }, $post -> via ( 'crossref' ) -> sharedPost );
2013-10-30 20:20:01 +01:00
$post -> via ( 'crossref' ) -> sharedPost = $relatedPosts ;
2013-11-16 21:21:43 +01:00
foreach ( array_diff ( $oldRelatedIds , $relatedIds ) as $post2id )
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-relation-del' , '{user} removed relation between {post} and {post2}' , [ 'post' => TextHelper :: reprPost ( $post ), 'post2' => TextHelper :: reprPost ( $post2id )]);
2013-11-16 21:21:43 +01:00
foreach ( array_diff ( $relatedIds , $oldRelatedIds ) as $post2id )
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-relation-add' , '{user} added relation between {post} and {post2}' , [ 'post' => TextHelper :: reprPost ( $post ), 'post2' => TextHelper :: reprPost ( $post2id )]);
2013-10-30 20:20:01 +01:00
}
2013-10-13 12:28:16 +02:00
R :: store ( $post );
2013-11-01 20:51:19 +01:00
Model_Tag :: removeUnused ();
2013-10-30 20:20:01 +01:00
2013-11-16 21:21:43 +01:00
LogHelper :: flush ();
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-13 12:28:16 +02:00
}
2013-11-17 14:52:46 +01:00
/**
* @ route / post / { id } / flag
*/
public function flagAction ( $id )
{
$post = Model_Post :: locate ( $id );
PrivilegesHelper :: confirmWithException ( Privilege :: FlagPost );
if ( InputHelper :: get ( 'submit' ))
{
2013-11-17 20:30:04 +01:00
$key = TextHelper :: reprPost ( $post );
2013-11-17 14:52:46 +01:00
2013-11-17 20:30:04 +01:00
$flagged = SessionHelper :: get ( 'flagged' , []);
if ( in_array ( $key , $flagged ))
2013-11-17 14:52:46 +01:00
throw new SimpleException ( 'You already flagged this post' );
2013-11-17 20:30:04 +01:00
$flagged [] = $key ;
SessionHelper :: set ( 'flagged' , $flagged );
2013-11-17 14:52:46 +01:00
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-flag' , '{user} flagged {post} for moderator attention' , [ 'post' => TextHelper :: reprPost ( $post )]);
2013-11-17 14:52:46 +01:00
StatusHelper :: success ();
}
}
2013-10-13 12:28:16 +02:00
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / hide
2013-10-13 12:28:16 +02:00
*/
public function hideAction ( $id )
{
2013-10-17 22:57:32 +02:00
$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-11-16 21:21:43 +01:00
2013-10-21 23:50:30 +02:00
if ( InputHelper :: get ( 'submit' ))
{
$post -> hidden = true ;
R :: store ( $post );
2013-11-16 21:21:43 +01:00
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-hide' , '{user} hidden {post}' , [ 'post' => TextHelper :: reprPost ( $post )]);
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-13 12:28:16 +02:00
}
2013-11-16 21:21:43 +01:00
2013-10-13 12:28:16 +02:00
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / unhide
2013-10-13 12:28:16 +02:00
*/
public function unhideAction ( $id )
{
2013-10-17 22:57:32 +02:00
$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-11-16 21:21:43 +01:00
2013-10-21 23:50:30 +02:00
if ( InputHelper :: get ( 'submit' ))
{
$post -> hidden = false ;
R :: store ( $post );
2013-11-16 21:21:43 +01:00
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-unhide' , '{user} unhidden {post}' , [ 'post' => TextHelper :: reprPost ( $post )]);
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-13 12:28:16 +02:00
}
2013-11-16 21:21:43 +01:00
2013-10-13 12:28:16 +02:00
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / delete
2013-10-13 12:28:16 +02:00
*/
public function deleteAction ( $id )
{
2013-10-17 22:57:32 +02:00
$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-11-16 21:21:43 +01:00
2013-10-21 23:50:30 +02:00
if ( InputHelper :: get ( 'submit' ))
{
//remove stuff from auxiliary tables
2013-10-30 17:06:35 +01:00
foreach ( $post -> ownComment as $comment )
{
$comment -> post = null ;
R :: store ( $comment );
}
2013-10-21 23:50:30 +02:00
$post -> ownFavoritee = [];
$post -> sharedTag = [];
R :: store ( $post );
R :: trash ( $post );
2013-11-16 21:21:43 +01:00
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-delete' , '{user} deleted {post}' , [ 'post' => TextHelper :: reprPost ( $id )]);
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-13 12:28:16 +02:00
}
2013-10-12 14:53:47 +02:00
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / add - fav
* @ route / post / { id } / fav - add
2013-10-12 14:53:47 +02:00
*/
public function addFavoriteAction ( $id )
{
2013-10-17 22:57:32 +02:00
$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 );
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-12 14:53:47 +02:00
}
/**
2013-10-17 22:57:32 +02:00
* @ route / post / { id } / rem - fav
* @ route / post / { id } / fav - rem
2013-10-12 14:53:47 +02:00
*/
public function remFavoriteAction ( $id )
{
2013-10-17 22:57:32 +02:00
$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 );
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-10-21 23:50:30 +02:00
}
2013-10-12 14:53:47 +02:00
}
2013-10-08 23:02:31 +02:00
2013-11-10 12:23:59 +01:00
/**
* @ route / post / { id } / score / { score }
* @ validate score - 1 | 0 | 1
*/
public function scoreAction ( $id , $score )
{
$post = Model_Post :: locate ( $id );
PrivilegesHelper :: confirmWithException ( Privilege :: ScorePost );
if ( InputHelper :: get ( 'submit' ))
{
if ( ! $this -> context -> loggedIn )
throw new SimpleException ( 'Not logged in' );
2013-11-13 22:14:32 +01:00
$p = R :: findOne ( 'postscore' , 'post_id = ? AND user_id = ?' , [ $post -> id , $this -> context -> user -> id ]);
2013-11-10 12:23:59 +01:00
if ( ! $p )
{
2013-11-13 22:14:32 +01:00
$p = R :: dispense ( 'postscore' );
2013-11-10 12:23:59 +01:00
$p -> post = $post ;
$p -> user = $this -> context -> user ;
}
$p -> score = $score ;
R :: store ( $p );
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-11-10 12:23:59 +01: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 ());
2013-11-16 18:40:26 +01:00
StatusHelper :: success ();
2013-11-17 23:29:59 +01:00
LogHelper :: logEvent ( 'post-feature' , '{user} featured {post} on main page' , [ 'post' => TextHelper :: reprPost ( $post )]);
2013-10-19 13:38:20 +02:00
}
2013-10-05 19:24:08 +02:00
/**
2013-10-07 20:44:14 +02:00
* Action that decorates the page containing the post .
2013-10-05 19:24:08 +02:00
* @ route / post / { id }
*/
2013-10-07 20:44:14 +02:00
public function viewAction ( $id )
2013-10-05 19:24:08 +02:00
{
2013-10-17 22:57:32 +02:00
$post = Model_Post :: locate ( $id );
R :: preload ( $post , [
'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-07 20:44:14 +02:00
2013-10-13 12:28:16 +02:00
$buildNextPostQuery = function ( $dbQuery , $id , $next )
{
$dbQuery -> select ( 'id' )
-> from ( 'post' )
-> where ( $next ? 'id > ?' : 'id < ?' )
-> put ( $id );
2013-10-27 20:48:40 +01:00
$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 ;
2013-11-10 12:23:59 +01:00
$score = null ;
2013-10-12 14:53:47 +02:00
if ( $this -> context -> loggedIn )
2013-11-10 12:23:59 +01:00
{
2013-10-12 14:53:47 +02:00
foreach ( $post -> ownFavoritee as $fav )
if ( $fav -> user -> id == $this -> context -> user -> id )
$favorite = true ;
2013-11-13 22:14:32 +01:00
$s = R :: findOne ( 'postscore' , 'post_id = ? AND user_id = ?' , [ $post -> id , $this -> context -> user -> id ]);
2013-11-10 12:23:59 +01:00
if ( $s )
$score = intval ( $s -> score );
}
2013-11-17 20:30:04 +01:00
$flagged = in_array ( TextHelper :: reprPost ( $post ), SessionHelper :: get ( 'flagged' , []));
2013-10-09 21:58:57 +02:00
$this -> context -> stylesheets [] = 'post-view.css' ;
2013-10-17 22:57:32 +02:00
$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 . ' – ' . join ( ', ' , array_map ( function ( $x ) { return $x [ 'name' ]; }, $post -> sharedTag ));
2013-10-12 14:53:47 +02:00
$this -> context -> favorite = $favorite ;
2013-11-10 12:23:59 +01:00
$this -> context -> score = $score ;
2013-11-17 20:30:04 +01:00
$this -> context -> flagged = $flagged ;
2013-10-07 20:44:14 +02:00
$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-07 20:44:14 +02:00
}
2013-10-08 23:02:31 +02:00
/**
* Action that renders the thumbnail of the requested file and sends it to user .
2013-10-23 00:16:52 +02:00
* @ route / post / { name } / thumb
2013-10-08 23:02:31 +02:00
*/
2013-10-23 00:16:52 +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 );
2013-10-23 00:16:52 +02:00
2013-10-30 23:03:37 +01:00
PrivilegesHelper :: confirmWithException ( Privilege :: ListPosts );
PrivilegesHelper :: confirmWithException ( Privilege :: ListPosts , 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 )
{
2013-10-29 09:18:00 +01:00
$tmpPath = tempnam ( sys_get_temp_dir (), 'thumb' ) . '.jpg' ;
2013-10-25 13:18:03 +02:00
$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 ;
2013-10-27 19:27:25 +01:00
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' ;
2013-10-27 19:27:25 +01:00
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 );
}
2013-10-27 19:27:25 +01:00
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-29 09:18:00 +01:00
imagejpeg ( $dstImage , $path );
2013-10-08 23:02:31 +02:00
imagedestroy ( $srcImage );
imagedestroy ( $dstImage );
}
2013-10-22 23:40:36 +02:00
else
{
2013-10-29 09:18:00 +01:00
$path = $this -> config -> main -> mediaPath . DS . 'img' . DS . 'thumb.jpg' ;
2013-10-22 23:40:36 +02:00
}
2013-10-23 00:16:52 +02:00
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-29 09:18:00 +01:00
$this -> context -> transport -> mimeType = 'image/jpeg' ;
2013-10-23 00:16:52 +02:00
$this -> context -> transport -> fileHash = 'thumb' . md5 ( $name . filemtime ( $path ));
2013-10-08 23:02:31 +02:00
$this -> context -> transport -> filePath = $path ;
}
2013-10-07 20:44:14 +02:00
/**
* Action that renders the requested file itself and sends it to user .
2013-10-17 22:57:32 +02:00
* @ route / post / { name } / retrieve
2013-10-07 20:44:14 +02:00
*/
2013-10-07 23:17:33 +02:00
public function retrieveAction ( $name )
2013-10-07 20:44:14 +02:00
{
$this -> context -> layoutName = 'layout-file' ;
2013-10-17 22:57:32 +02:00
$post = Model_Post :: locate ( $name , true );
2013-10-13 13:37:18 +02:00
R :: preload ( $post , [ 'tag' ]);
2013-10-07 20:44:14 +02:00
2013-10-18 00:09:50 +02:00
PrivilegesHelper :: confirmWithException ( Privilege :: RetrievePost );
PrivilegesHelper :: confirmWithException ( Privilege :: RetrievePost , PostSafety :: toString ( $post -> safety ));
2013-10-07 20:44:14 +02:00
2013-10-09 19:25:56 +02:00
$path = $this -> config -> main -> filesPath . DS . $post -> name ;
2013-10-07 20:44:14 +02:00
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 ;
2013-10-07 20:44:14 +02:00
$this -> context -> transport -> mimeType = $post -> mimeType ;
2013-10-19 13:00:03 +02:00
$this -> context -> transport -> fileHash = 'post' . $post -> file_hash ;
2013-10-07 20:44:14 +02:00
$this -> context -> transport -> filePath = $path ;
2013-10-05 12:55:03 +02:00
}
}