841 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			841 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
/*
 | 
						|
 * Name: Ouroboros API
 | 
						|
 * Author: Diftraku <diftraku[at]derpy.me>
 | 
						|
 * Description: Ouroboros-like API for Shimmie
 | 
						|
 * Version: 0.2
 | 
						|
 * Documentation:
 | 
						|
 *   Currently working features
 | 
						|
 *   <ul>
 | 
						|
 *     <li>Post:
 | 
						|
 *       <ul>
 | 
						|
 *         <li>Index/List</li>
 | 
						|
 *         <li>Show</li>
 | 
						|
 *         <li>Create</li>
 | 
						|
 *       </ul>
 | 
						|
 *     </li>
 | 
						|
 *     <li>Tag:
 | 
						|
 *       <ul>
 | 
						|
 *         <li>Index/List</li>
 | 
						|
 *       </ul>
 | 
						|
 *     </li>
 | 
						|
 *   </ul>
 | 
						|
 *   Tested to work with CartonBox using "Danbooru 1.18.x" as site type.
 | 
						|
 *   Does not work with Andbooru or Danbooru Gallery for reasons beyond me, took me a while to figure rating "u" is bad...
 | 
						|
 *   Lots of Ouroboros/Danbooru specific values use their defaults (or what I gathered them to be default)
 | 
						|
 *   and tons of stuff not supported directly in Shimmie is botched to work
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
class _SafeOuroborosImage
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * Author
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Post author
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $author = '';
 | 
						|
    /**
 | 
						|
     * Post author user ID
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $creator_id = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Image
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Image height
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $height = null;
 | 
						|
    /**
 | 
						|
     * Image width
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $width = null;
 | 
						|
    /**
 | 
						|
     * File Size in bytes
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $file_size = null;
 | 
						|
    /**
 | 
						|
     * URL to the static file
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $file_url = '';
 | 
						|
    /**
 | 
						|
     * File MD5 hash
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $md5 = '';
 | 
						|
 | 
						|
    /**
 | 
						|
     * Post Meta
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * (Unknown) Change
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $change = null;
 | 
						|
    /**
 | 
						|
     * Timestamp for post creation
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $created_at = null;
 | 
						|
    /**
 | 
						|
     * Post ID
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $id = null;
 | 
						|
    /**
 | 
						|
     * Parent post ID
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $parent_id = null;
 | 
						|
    /**
 | 
						|
     * Post content rating
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $rating = 'q';
 | 
						|
    /**
 | 
						|
     * Post score
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $score = 1;
 | 
						|
    /**
 | 
						|
     * Post source
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $source = '';
 | 
						|
    /**
 | 
						|
     * Post status
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $status = '';
 | 
						|
    /**
 | 
						|
     * Post tags
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $tags = '';
 | 
						|
    /**
 | 
						|
     * Flag if the post has child posts
 | 
						|
     * @var bool
 | 
						|
     */
 | 
						|
    public $has_children = false;
 | 
						|
    /**
 | 
						|
     * Flag if the post has comments
 | 
						|
     * @var bool
 | 
						|
     */
 | 
						|
    public $has_comments = false;
 | 
						|
    /**
 | 
						|
     * Flag if the post has notes
 | 
						|
     * @var bool
 | 
						|
     */
 | 
						|
    public $has_notes = false;
 | 
						|
    /**
 | 
						|
     * Post description
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $description = '';
 | 
						|
 | 
						|
    /**
 | 
						|
     * Thumbnail
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Thumbnail Height
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $preview_height = null;
 | 
						|
    /**
 | 
						|
     * Thumbnail URL
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $preview_url = '';
 | 
						|
    /**
 | 
						|
     * Thumbnail Width
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $preview_width = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Downscaled Image
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Downscaled image height
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $sample_height = null;
 | 
						|
    /**
 | 
						|
     * Downscaled image
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    public $sample_url = '';
 | 
						|
    /**
 | 
						|
     * Downscaled image
 | 
						|
     * @var integer
 | 
						|
     */
 | 
						|
    public $sample_width = null;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor
 | 
						|
     * @param Image $img
 | 
						|
     */
 | 
						|
    function __construct(Image $img)
 | 
						|
    {
 | 
						|
        global $config;
 | 
						|
        // author
 | 
						|
        $author = $img->get_owner();
 | 
						|
        $this->author = $author->name;
 | 
						|
        $this->creator_id = intval($author->id);
 | 
						|
 | 
						|
        // file
 | 
						|
        $this->height = intval($img->height);
 | 
						|
        $this->width = intval($img->width);
 | 
						|
        $this->file_ext = $img->ext;
 | 
						|
        $this->file_size = intval($img->filesize);
 | 
						|
        $this->file_url = make_http($img->get_image_link());
 | 
						|
        $this->md5 = $img->hash;
 | 
						|
 | 
						|
        // meta
 | 
						|
        $this->change = intval($img->id); //DaFug is this even supposed to do? ChangeID?
 | 
						|
        // Should be JSON specific, just strip this when converting to XML
 | 
						|
        $this->created_at = array('n' => 123456789, 's' => $img->posted_timestamp, 'json_class' => 'Time');
 | 
						|
        $this->id = intval($img->id);
 | 
						|
        $this->parent_id = null;
 | 
						|
        if (defined('ENABLED_EXTS')) {
 | 
						|
            if (strstr(ENABLED_EXTS, 'rating') !== false) {
 | 
						|
                // 'u' is not a "valid" rating
 | 
						|
                if ($img->rating == 's' || $img->rating == 'q' || $img->rating == 'e') {
 | 
						|
                    $this->rating = $img->rating;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (strstr(ENABLED_EXTS, 'numeric_score') !== false) {
 | 
						|
                $this->score = $img->numeric_score;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        $this->source = $img->source;
 | 
						|
        $this->status = 'active'; //not supported in Shimmie... yet
 | 
						|
        $this->tags = $img->get_tag_list();
 | 
						|
        $this->has_children = false;
 | 
						|
        $this->has_comments = false;
 | 
						|
        $this->has_notes = false;
 | 
						|
 | 
						|
        // thumb
 | 
						|
        $this->preview_height = $config->get_int('thumb_height');
 | 
						|
        $this->preview_width = $config->get_int('thumb_width');
 | 
						|
        $this->preview_url = make_http($img->get_thumb_link());
 | 
						|
 | 
						|
        // sample (use the full image here)
 | 
						|
        $this->sample_height = intval($img->height);
 | 
						|
        $this->sample_width = intval($img->width);
 | 
						|
        $this->sample_url = make_http($img->get_image_link());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class OuroborosPost extends _SafeOuroborosImage
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * Multipart File
 | 
						|
     * @var array
 | 
						|
     */
 | 
						|
    public $file = array();
 | 
						|
 | 
						|
    /**
 | 
						|
     * Create with rating locked
 | 
						|
     * @var bool
 | 
						|
     */
 | 
						|
    public $is_rating_locked = false;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Create with notes locked
 | 
						|
     * @var bool
 | 
						|
     */
 | 
						|
    public $is_note_locked = false;
 | 
						|
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initialize an OuroborosPost for creation
 | 
						|
     * Mainly just acts as a wrapper and validation layer
 | 
						|
     * @TODO implement more validation from OuroborosAPI
 | 
						|
     * @param array $post
 | 
						|
     */
 | 
						|
    public function __construct(array $post)
 | 
						|
    {
 | 
						|
        if (array_key_exists('tags', $post)) {
 | 
						|
            $this->tags = $post['tags'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('file', $post)) {
 | 
						|
            if (!is_null($post['file'])) {
 | 
						|
                assert(is_array($post['file']));
 | 
						|
                assert(array_key_exists('tmp_name', $post['file']));
 | 
						|
                assert(array_key_exists('name', $post['file']));
 | 
						|
                $this->file = $post['file'];
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (array_key_exists('rating', $post)) {
 | 
						|
            assert(
 | 
						|
                $post['rating'] == 's' ||
 | 
						|
                $post['rating'] == 'q' ||
 | 
						|
                $post['rating'] == 'e'
 | 
						|
            );
 | 
						|
            $this->rating = $post['rating'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('source', $post)) {
 | 
						|
            $this->file_url = $post['source'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('sourceurl', $post)) {
 | 
						|
            $this->source = $post['sourceurl'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('description', $post)) {
 | 
						|
            $this->description = $post['description'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('is_rating_locked', $post)) {
 | 
						|
            $this->is_rating_locked = $post['is_rating_locked'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('is_note_locked', $post)) {
 | 
						|
            $this->is_note_locked = $post['is_note_locked'];
 | 
						|
        }
 | 
						|
        if (array_key_exists('parent_id', $post)) {
 | 
						|
            $this->parent_id = $post['parent_id'];
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class _SafeOuroborosTag
 | 
						|
{
 | 
						|
    public $ambiguous = false;
 | 
						|
    public $count = 0;
 | 
						|
    public $id = 0;
 | 
						|
    public $name = '';
 | 
						|
    public $type = 0;
 | 
						|
 | 
						|
    function __construct(array $tag)
 | 
						|
    {
 | 
						|
        $this->count = $tag['count'];
 | 
						|
        $this->id = $tag['id'];
 | 
						|
        $this->name = $tag['tag'];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class OuroborosAPI extends Extension
 | 
						|
{
 | 
						|
    private $event;
 | 
						|
    private $type;
 | 
						|
    const HEADER_HTTP_200 = 'OK';
 | 
						|
    const MSG_HTTP_200 = 'Request was successful';
 | 
						|
 | 
						|
    const HEADER_HTTP_403 = 'Forbidden';
 | 
						|
    const MSG_HTTP_403 = 'Access denied';
 | 
						|
 | 
						|
    const HEADER_HTTP_404 = 'Not found';
 | 
						|
    const MSG_HTTP_404 = 'Not found';
 | 
						|
 | 
						|
    const HEADER_HTTP_418 = 'I\'m a teapot';
 | 
						|
    const MSG_HTTP_418 = 'Short and stout';
 | 
						|
 | 
						|
    const HEADER_HTTP_420 = 'Invalid Record';
 | 
						|
    const MSG_HTTP_420 = 'Record could not be saved';
 | 
						|
 | 
						|
    const HEADER_HTTP_421 = 'User Throttled';
 | 
						|
    const MSG_HTTP_421 = 'User is throttled, try again later';
 | 
						|
 | 
						|
    const HEADER_HTTP_422 = 'Locked';
 | 
						|
    const MSG_HTTP_422 = 'The resource is locked and cannot be modified';
 | 
						|
 | 
						|
    const HEADER_HTTP_423 = 'Already Exists';
 | 
						|
    const MSG_HTTP_423 = 'Resource already exists';
 | 
						|
 | 
						|
    const HEADER_HTTP_424 = 'Invalid Parameters';
 | 
						|
    const MSG_HTTP_424 = 'The given parameters were invalid';
 | 
						|
 | 
						|
    const HEADER_HTTP_500 = 'Internal Server Error';
 | 
						|
    const MSG_HTTP_500 = 'Some unknown error occurred on the server';
 | 
						|
 | 
						|
    const HEADER_HTTP_503 = 'Service Unavailable';
 | 
						|
    const MSG_HTTP_503 = 'Server cannot currently handle the request, try again later';
 | 
						|
 | 
						|
    const ERROR_POST_CREATE_MD5 = 'MD5 mismatch';
 | 
						|
    const ERROR_POST_CREATE_DUPE = 'Duplicate';
 | 
						|
 | 
						|
    public function onPageRequest(PageRequestEvent $event)
 | 
						|
    {
 | 
						|
        global $database, $page, $config, $user;
 | 
						|
 | 
						|
        if (preg_match("%\.(xml|json)$%", implode('/', $event->args), $matches) === 1) {
 | 
						|
            $this->event = $event;
 | 
						|
            $this->type = $matches[1];
 | 
						|
            if ($this->type == 'json') {
 | 
						|
                $page->set_type('application/json; charset=utf-8');
 | 
						|
            } elseif ($this->type == 'xml') {
 | 
						|
                $page->set_type('text/xml; charset=utf-8');
 | 
						|
            }
 | 
						|
            $page->set_mode('data');
 | 
						|
            $this->tryAuth();
 | 
						|
 | 
						|
            if ($event->page_matches('post')) {
 | 
						|
                if ($this->match('create')) {
 | 
						|
                    // Create
 | 
						|
                    // @TODO Should move the validation logic into OuroborosPost instead?
 | 
						|
                    if ($user->can("create_image")) {
 | 
						|
                        $post = array(
 | 
						|
                            'tags' => !empty($_REQUEST['post']['tags']) ? Tag::implode(
 | 
						|
                                    array_map(
 | 
						|
                                        array('Tag', 'sanitise'),
 | 
						|
                                        Tag::explode(urldecode($_REQUEST['post']['tags']))
 | 
						|
                                    )
 | 
						|
                                ) : 'tagme',
 | 
						|
                            'file' => !empty($_REQUEST['post']['file']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['file'],
 | 
						|
                                    FILTER_UNSAFE_RAW
 | 
						|
                                ) : null,
 | 
						|
                            'rating' => !empty($_REQUEST['post']['rating']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['rating'],
 | 
						|
                                    FILTER_SANITIZE_NUMBER_INT
 | 
						|
                                ) : 'q',
 | 
						|
                            'source' => !empty($_REQUEST['post']['source']) ? filter_var(
 | 
						|
                                    urldecode($_REQUEST['post']['source']),
 | 
						|
                                    FILTER_SANITIZE_URL
 | 
						|
                                ) : null,
 | 
						|
                            'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var(
 | 
						|
                                    urldecode($_REQUEST['post']['sourceurl']),
 | 
						|
                                    FILTER_SANITIZE_URL
 | 
						|
                                ) : '',
 | 
						|
                            'description' => !empty($_REQUEST['post']['description']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['description'],
 | 
						|
                                    FILTER_SANITIZE_STRING
 | 
						|
                                ) : '',
 | 
						|
                            'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['is_rating_locked'],
 | 
						|
                                    FILTER_SANITIZE_NUMBER_INT
 | 
						|
                                ) : false,
 | 
						|
                            'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['is_note_locked'],
 | 
						|
                                    FILTER_SANITIZE_NUMBER_INT
 | 
						|
                                ) : false,
 | 
						|
                            'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var(
 | 
						|
                                    $_REQUEST['post']['parent_id'],
 | 
						|
                                    FILTER_SANITIZE_NUMBER_INT
 | 
						|
                                ) : null,
 | 
						|
                        );
 | 
						|
                        $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null;
 | 
						|
                        $this->postCreate(new OuroborosPost($post), $md5);
 | 
						|
                    } else {
 | 
						|
                        $this->sendResponse(403, 'You cannot create new posts');
 | 
						|
                    }
 | 
						|
 | 
						|
                } elseif ($this->match('update')) {
 | 
						|
                    // Update
 | 
						|
                    //@todo add post update
 | 
						|
                } elseif ($this->match('show')) {
 | 
						|
                    // Show
 | 
						|
                    $id = !empty($_REQUEST['id']) ? filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT) : null;
 | 
						|
                    $this->postShow($id);
 | 
						|
                } elseif ($this->match('index') || $this->match('list')) {
 | 
						|
                    // List
 | 
						|
                    $limit = !empty($_REQUEST['limit']) ? intval(
 | 
						|
                        filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : 45;
 | 
						|
                    $p = !empty($_REQUEST['page']) ? intval(
 | 
						|
                        filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : 1;
 | 
						|
                    $tags = !empty($_REQUEST['tags']) ? filter_var($_REQUEST['tags'], FILTER_SANITIZE_STRING) : array();
 | 
						|
                    if (!empty($tags)) {
 | 
						|
                        $tags = Tag::explode($tags);
 | 
						|
                    }
 | 
						|
                    $this->postIndex($limit, $p, $tags);
 | 
						|
                }
 | 
						|
            } elseif ($event->page_matches('tag')) {
 | 
						|
                if ($this->match('index') || $this->match('list')) {
 | 
						|
                    $limit = !empty($_REQUEST['limit']) ? intval(
 | 
						|
                        filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : 50;
 | 
						|
                    $p = !empty($_REQUEST['page']) ? intval(
 | 
						|
                        filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : 1;
 | 
						|
                    $order = (!empty($_REQUEST['order']) && ($_REQUEST['order'] == 'date' || $_REQUEST['order'] == 'count' || $_REQUEST['order'] == 'name')) ? filter_var(
 | 
						|
                        $_REQUEST['order'],
 | 
						|
                        FILTER_SANITIZE_STRING
 | 
						|
                    ) : 'date';
 | 
						|
                    $id = !empty($_REQUEST['id']) ? intval(
 | 
						|
                        filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : null;
 | 
						|
                    $after_id = !empty($_REQUEST['after_id']) ? intval(
 | 
						|
                        filter_var($_REQUEST['after_id'], FILTER_SANITIZE_NUMBER_INT)
 | 
						|
                    ) : null;
 | 
						|
                    $name = !empty($_REQUEST['name']) ? filter_var($_REQUEST['name'], FILTER_SANITIZE_STRING) : '';
 | 
						|
                    $name_pattern = !empty($_REQUEST['name_pattern']) ? filter_var(
 | 
						|
                        $_REQUEST['name_pattern'],
 | 
						|
                        FILTER_SANITIZE_STRING
 | 
						|
                    ) : '';
 | 
						|
                    $this->tagIndex($limit, $p, $order, $id, $after_id, $name, $name_pattern);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        } elseif ($event->page_matches('post/show')) {
 | 
						|
            $page->set_mode('redirect');
 | 
						|
            $page->set_redirect(make_link(str_replace('post/show', 'post/view', implode('/', $event->args))));
 | 
						|
            $page->display();
 | 
						|
            die();
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Post
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wrapper for post creation
 | 
						|
     * @param OuroborosPost $post
 | 
						|
     * @param string $md5
 | 
						|
     */
 | 
						|
    protected function postCreate(OuroborosPost $post, $md5 = '')
 | 
						|
    {
 | 
						|
        global $page, $config, $user;
 | 
						|
        if (!empty($md5)) {
 | 
						|
            $img = Image::by_hash($md5);
 | 
						|
            if (!is_null($img)) {
 | 
						|
                $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        $meta = array();
 | 
						|
        $meta['tags'] = $post->tags;
 | 
						|
        $meta['source'] = $post->source;
 | 
						|
        if (defined('ENABLED_EXTS')) {
 | 
						|
            if (strstr(ENABLED_EXTS, 'rating') !== false) {
 | 
						|
                $meta['rating'] = $post->rating;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        // Check where we should try for the file
 | 
						|
        if (empty($post->file) && !empty($post->file_url) && filter_var(
 | 
						|
                $post->file_url,
 | 
						|
                FILTER_VALIDATE_URL
 | 
						|
            ) !== false
 | 
						|
        ) {
 | 
						|
            // Transload from source
 | 
						|
            $meta['file'] = tempnam('/tmp', 'shimmie_transload_' . $config->get_string('transload_engine'));
 | 
						|
            $meta['filename'] = basename($post->file_url);
 | 
						|
            if (!transload($post->file_url, $meta['file'])) {
 | 
						|
                $this->sendResponse(500, 'Transloading failed');
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            $meta['hash'] = md5_file($meta['file']);
 | 
						|
        } else {
 | 
						|
            // Use file
 | 
						|
            $meta['file'] = $post->file['tmp_name'];
 | 
						|
            $meta['filename'] = $post->file['name'];
 | 
						|
            $meta['hash'] = md5_file($meta['file']);
 | 
						|
        }
 | 
						|
        if (!empty($md5) && $md5 !== $meta['hash']) {
 | 
						|
            $this->sendResponse(420, self::ERROR_POST_CREATE_MD5);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        if (!empty($meta['hash'])) {
 | 
						|
            $img = Image::by_hash($meta['hash']);
 | 
						|
            if (!is_null($img)) {
 | 
						|
                $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        $meta['extension'] = pathinfo($meta['filename'], PATHINFO_EXTENSION);
 | 
						|
        try {
 | 
						|
            $upload = new DataUploadEvent($meta['file'], $meta);
 | 
						|
            send_event($upload);
 | 
						|
            $image = Image::by_hash($meta['hash']);
 | 
						|
            if (!is_null($image)) {
 | 
						|
                $this->sendResponse(200, make_link('post/view/' . $image->id), true);
 | 
						|
                return;
 | 
						|
            } else {
 | 
						|
                // Fail, unsupported file?
 | 
						|
                $this->sendResponse(500, 'Unknown error');
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        } catch (UploadException $e) {
 | 
						|
            // Cleanup in case shit hit the fan
 | 
						|
            $this->sendResponse(500, $e->getMessage());
 | 
						|
            return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wrapper for getting a single post
 | 
						|
     * @param int $id
 | 
						|
     */
 | 
						|
    protected function postShow($id = null)
 | 
						|
    {
 | 
						|
        if (!is_null($id)) {
 | 
						|
			$image = Image::by_id($id);
 | 
						|
			if ( ! $image instanceof Image) {
 | 
						|
				$this->sendResponse(404, 'ID not found');
 | 
						|
			} else {
 | 
						|
				$post = new _SafeOuroborosImage($image);
 | 
						|
				$this->sendData('post', $post);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			$this->sendResponse(424, 'ID is mandatory');
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wrapper for getting a list of posts
 | 
						|
     * @param $limit
 | 
						|
     * @param $page
 | 
						|
     * @param $tags
 | 
						|
     */
 | 
						|
    protected function postIndex($limit, $page, $tags)
 | 
						|
    {
 | 
						|
        $start = ($page - 1) * $limit;
 | 
						|
        $results = Image::find_images(max($start, 0), min($limit, 100), $tags);
 | 
						|
        $posts = array();
 | 
						|
        foreach ($results as $img) {
 | 
						|
            if (!is_object($img)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            $posts[] = new _SafeOuroborosImage($img);
 | 
						|
        }
 | 
						|
        $this->sendData('post', $posts, max($start, 0));
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Tag
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wrapper for getting a list of tags
 | 
						|
     * @param $limit
 | 
						|
     * @param $page
 | 
						|
     * @param $order
 | 
						|
     * @param $id
 | 
						|
     * @param $after_id
 | 
						|
     * @param $name
 | 
						|
     * @param $name_pattern
 | 
						|
     */
 | 
						|
    protected function tagIndex($limit, $page, $order, $id, $after_id, $name, $name_pattern)
 | 
						|
    {
 | 
						|
        global $database, $config;
 | 
						|
        $start = ($page - 1) * $limit;
 | 
						|
        $tag_data = array();
 | 
						|
        switch ($order) {
 | 
						|
            case 'name':
 | 
						|
                $tag_data = $database->get_col(
 | 
						|
                    $database->scoreql_to_sql(
 | 
						|
                        "
 | 
						|
                                                        SELECT DISTINCT
 | 
						|
                                                            id, SCORE_STRNORM(substr(tag, 1, 1)), count
 | 
						|
                                                        FROM tags
 | 
						|
                                                        WHERE count >= :tags_min
 | 
						|
                                                        ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items
 | 
						|
                                                    "
 | 
						|
                    ),
 | 
						|
                    array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)
 | 
						|
                );
 | 
						|
                break;
 | 
						|
            case 'count':
 | 
						|
                $tag_data = $database->get_all(
 | 
						|
                    "
 | 
						|
                                                    SELECT id, tag, count
 | 
						|
                                                    FROM tags
 | 
						|
                                                    WHERE count >= :tags_min
 | 
						|
                                                    ORDER BY count DESC, tag ASC LIMIT :start, :max_items
 | 
						|
                                                    ",
 | 
						|
                    array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)
 | 
						|
                );
 | 
						|
                break;
 | 
						|
            case 'date':
 | 
						|
                $tag_data = $database->get_all(
 | 
						|
                    "
 | 
						|
                                                    SELECT id, tag, count
 | 
						|
                                                    FROM tags
 | 
						|
                                                    WHERE count >= :tags_min
 | 
						|
                                                    ORDER BY count DESC, tag ASC LIMIT :start, :max_items
 | 
						|
                                                    ",
 | 
						|
                    array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)
 | 
						|
                );
 | 
						|
                break;
 | 
						|
        }
 | 
						|
        $tags = array();
 | 
						|
        foreach ($tag_data as $tag) {
 | 
						|
            if (!is_array($tag)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            $tags[] = new _SafeOuroborosTag($tag);
 | 
						|
        }
 | 
						|
        $this->sendData('tag', $tags, $start);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Utility methods
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sends a simple {success,reason} message to browser
 | 
						|
     *
 | 
						|
     * @param int $code HTTP equivalent code for the message
 | 
						|
     * @param string $reason Reason for the code
 | 
						|
     * @param bool $location Is $reason a location? (used mainly for post/create)
 | 
						|
     */
 | 
						|
    private function sendResponse($code = 200, $reason = '', $location = false)
 | 
						|
    {
 | 
						|
        global $page;
 | 
						|
        if ($code == 200) {
 | 
						|
            $success = true;
 | 
						|
        } else {
 | 
						|
            $success = false;
 | 
						|
        }
 | 
						|
        if (empty($reason)) {
 | 
						|
            if (defined("self::MSG_HTTP_{$code}")) {
 | 
						|
                $reason = constant("self::MSG_HTTP_{$code}");
 | 
						|
            } else {
 | 
						|
                $reason = self::MSG_HTTP_418;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if ($code != 200) {
 | 
						|
            $proto = $_SERVER['SERVER_PROTOCOL'];
 | 
						|
            if (defined("self::HEADER_HTTP_{$code}")) {
 | 
						|
                $header = constant("self::HEADER_HTTP_{$code}");
 | 
						|
            } else {
 | 
						|
                // I'm a teapot!
 | 
						|
                $code = 418;
 | 
						|
                $header = self::HEADER_HTTP_418;
 | 
						|
            }
 | 
						|
            header("{$proto} {$code} {$header}", true);
 | 
						|
        }
 | 
						|
        $response = array('success' => $success, 'reason' => $reason);
 | 
						|
        if ($this->type == 'json') {
 | 
						|
            if ($location !== false) {
 | 
						|
                $response['location'] = $response['reason'];
 | 
						|
                unset($response['reason']);
 | 
						|
            }
 | 
						|
            $response = json_encode($response);
 | 
						|
        } elseif ($this->type == 'xml') {
 | 
						|
            // Seriously, XML sucks...
 | 
						|
            $xml = new XMLWriter();
 | 
						|
            $xml->openMemory();
 | 
						|
            $xml->startDocument('1.0', 'utf-8');
 | 
						|
            $xml->startElement('response');
 | 
						|
            $xml->writeAttribute('success', var_export($success, true));
 | 
						|
            if ($location !== false) {
 | 
						|
                $xml->writeAttribute('location', $reason);
 | 
						|
            } else {
 | 
						|
                $xml->writeAttribute('reason', $reason);
 | 
						|
            }
 | 
						|
            $xml->endElement();
 | 
						|
            $xml->endDocument();
 | 
						|
            $response = $xml->outputMemory(true);
 | 
						|
            unset($xml);
 | 
						|
        }
 | 
						|
        $page->set_data($response);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Send data to the browser
 | 
						|
     * @param string $type
 | 
						|
     * @param mixed $data
 | 
						|
     * @param int $offset
 | 
						|
     */
 | 
						|
    private function sendData($type = '', $data = array(), $offset = 0)
 | 
						|
    {
 | 
						|
        global $page;
 | 
						|
        $response = '';
 | 
						|
        if ($this->type == 'json') {
 | 
						|
            $response = json_encode($data);
 | 
						|
        } elseif ($this->type == 'xml') {
 | 
						|
            $xml = new XMLWriter();
 | 
						|
            $xml->openMemory();
 | 
						|
            $xml->startDocument('1.0', 'utf-8');
 | 
						|
            if (array_key_exists(0, $data)) {
 | 
						|
                $xml->startElement($type . 's');
 | 
						|
                if ($type == 'post') {
 | 
						|
                    $xml->writeAttribute('count', count($data));
 | 
						|
                    $xml->writeAttribute('offset', $offset);
 | 
						|
                }
 | 
						|
                if ($type == 'tag') {
 | 
						|
                    $xml->writeAttribute('type', 'array');
 | 
						|
                }
 | 
						|
                foreach ($data as $item) {
 | 
						|
                    $this->createItemXML($xml, $type, $item);
 | 
						|
                }
 | 
						|
                $xml->endElement();
 | 
						|
            } else {
 | 
						|
                $this->createItemXML($xml, $type, $data);
 | 
						|
            }
 | 
						|
            $xml->endDocument();
 | 
						|
            $response = $xml->outputMemory(true);
 | 
						|
            unset($xml);
 | 
						|
        }
 | 
						|
        $page->set_data($response);
 | 
						|
    }
 | 
						|
 | 
						|
    private function createItemXML(XMLWriter &$xml, $type, $item)
 | 
						|
    {
 | 
						|
        $xml->startElement($type);
 | 
						|
        foreach ($item as $key => $val) {
 | 
						|
            if ($key == 'created_at' && $type == 'post') {
 | 
						|
                $xml->writeAttribute($key, $val['s']);
 | 
						|
            } else {
 | 
						|
                if (is_bool($val)) {
 | 
						|
                    $val = $val ? 'true' : 'false';
 | 
						|
                }
 | 
						|
                $xml->writeAttribute($key, $val);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        $xml->endElement();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Try to figure who is uploading
 | 
						|
     *
 | 
						|
     * Currently checks for either user & session in request or cookies
 | 
						|
     * and initializes a global User
 | 
						|
     * @param void
 | 
						|
     * @return void
 | 
						|
     */
 | 
						|
    private function tryAuth()
 | 
						|
    {
 | 
						|
        global $config, $user;
 | 
						|
 | 
						|
        if (isset($_REQUEST['user']) && isset($_REQUEST['session'])) {
 | 
						|
            //Auth by session data from query
 | 
						|
            $name = $_REQUEST['user'];
 | 
						|
            $session = $_REQUEST['session'];
 | 
						|
            $duser = User::by_session($name, $session);
 | 
						|
            if (!is_null($duser)) {
 | 
						|
                $user = $duser;
 | 
						|
            } else {
 | 
						|
                $user = User::by_id($config->get_int("anon_id", 0));
 | 
						|
            }
 | 
						|
        } elseif (isset($_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'session']) &&
 | 
						|
            isset($_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'user'])
 | 
						|
        ) {
 | 
						|
            //Auth by session data from cookies
 | 
						|
            $session = $_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'session'];
 | 
						|
            $user = $_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'user'];
 | 
						|
            $duser = User::by_session($user, $session);
 | 
						|
            if (!is_null($duser)) {
 | 
						|
                $user = $duser;
 | 
						|
            } else {
 | 
						|
                $user = User::by_id($config->get_int("anon_id", 0));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Helper for matching API methods from event
 | 
						|
     * @param $page
 | 
						|
     * @return bool
 | 
						|
     */
 | 
						|
    private function match($page)
 | 
						|
    {
 | 
						|
        return (preg_match("%{$page}\.(xml|json)$%", implode('/', $this->event->args), $matches) === 1);
 | 
						|
    }
 | 
						|
}
 |