<?php
# -- BEGIN LICENSE BLOCK ---------------------------------------
#
# This file is part of Dotclear 2.
#
# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
# Licensed under the GPL version 2.0 license.
# See LICENSE file or
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- END LICENSE BLOCK -----------------------------------------
if (!defined('DC_RC_PATH')) {return;}

class dcUrlHandlers extends urlHandler
{
    public $args;

    public function getURLFor($type, $value = '')
    {
        $core = &$GLOBALS['core'];
        $url  = $core->callBehavior("publicGetURLFor", $type, $value);
        if (!$url) {
            $url = $this->getBase($type);
            if ($value) {
                if ($url) {
                    $url .= '/';
                }
                $url .= $value;
            }
        }
        return $url;
    }

    public function register($type, $url, $representation, $handler)
    {
        $core = &$GLOBALS['core'];
        $t    = new ArrayObject(array($type, $url, $representation, $handler));
        $core->callBehavior("publicRegisterURL", $t);
        parent::register($t[0], $t[1], $t[2], $t[3]);
    }

    public static function p404()
    {
        throw new Exception("Page not found", 404);
    }

    public static function default404($args, $type, $e)
    {
        if ($e->getCode() != 404) {
            throw $e;
        }
        $_ctx = &$GLOBALS['_ctx'];
        $core = $GLOBALS['core'];

        header('Content-Type: text/html; charset=UTF-8');
        http::head(404, 'Not Found');
        $core->url->type    = '404';
        $_ctx->current_tpl  = '404.html';
        $_ctx->content_type = 'text/html';

        echo $core->tpl->getData($_ctx->current_tpl);

        # --BEHAVIOR-- publicAfterDocument
        $core->callBehavior('publicAfterDocument', $core);
        exit;
    }

    protected static function getPageNumber(&$args)
    {
        if (preg_match('#(^|/)page/([0-9]+)$#', $args, $m)) {
            $n = (integer) $m[2];
            if ($n > 0) {
                $args = preg_replace('#(^|/)page/([0-9]+)$#', '', $args);
                return $n;
            }
        }

        return false;
    }

    protected static function serveDocument($tpl, $content_type = 'text/html', $http_cache = true, $http_etag = true)
    {
        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        if ($_ctx->nb_entry_per_page === null) {
            $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_page;
        }
        if ($_ctx->nb_entry_first_page === null) {
            $_ctx->nb_entry_first_page = $_ctx->nb_entry_per_page;
        }

        $tpl_file = $core->tpl->getFilePath($tpl);

        if (!$tpl_file) {
            throw new Exception('Unable to find template ');
        }

        $result = new ArrayObject;

        $_ctx->current_tpl  = $tpl;
        $_ctx->content_type = $content_type;
        $_ctx->http_cache   = $http_cache;
        $_ctx->http_etag    = $http_etag;
        $core->callBehavior('urlHandlerBeforeGetData', $_ctx);

        if ($_ctx->http_cache) {
            $GLOBALS['mod_files'][] = $tpl_file;
            http::cache($GLOBALS['mod_files'], $GLOBALS['mod_ts']);
        }

        header('Content-Type: ' . $_ctx->content_type . '; charset=UTF-8');

        // Additional headers
        $headers = new ArrayObject;
        if ($core->blog->settings->system->prevents_clickjacking) {
            if ($_ctx->exists('xframeoption')) {
                $url    = parse_url($_ctx->xframeoption);
                $header = sprintf('X-Frame-Options: %s',
                    is_array($url) ? ("ALLOW-FROM " . $url['scheme'] . '://' . $url['host']) : 'SAMEORIGIN');
            } else {
                                                         // Prevents Clickjacking as far as possible
                $header = 'X-Frame-Options: SAMEORIGIN'; // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+
            }
            $headers[] = $header;
        }
        # --BEHAVIOR-- urlHandlerServeDocumentHeaders
        $core->callBehavior('urlHandlerServeDocumentHeaders', $headers);

        // Send additional headers if any
        foreach ($headers as $header) {
            header($header);
        }

        $result['content']      = $core->tpl->getData($_ctx->current_tpl);
        $result['content_type'] = $_ctx->content_type;
        $result['tpl']          = $_ctx->current_tpl;
        $result['blogupddt']    = $core->blog->upddt;
        $result['headers']      = $headers;

        # --BEHAVIOR-- urlHandlerServeDocument
        $core->callBehavior('urlHandlerServeDocument', $result);

        if ($_ctx->http_cache && $_ctx->http_etag) {
            http::etag($result['content'], http::getSelfURI());
        }
        echo $result['content'];
    }

    public function getDocument()
    {
        $core = &$GLOBALS['core'];

        $type = $args = '';

        if ($this->mode == 'path_info') {
            $part = substr($_SERVER['PATH_INFO'], 1);
        } else {
            $part = '';

            $qs = $this->parseQueryString();

            # Recreates some _GET and _REQUEST pairs
            if (!empty($qs)) {
                foreach ($_GET as $k => $v) {
                    if (isset($_REQUEST[$k])) {
                        unset($_REQUEST[$k]);
                    }
                }
                $_GET     = $qs;
                $_REQUEST = array_merge($qs, $_REQUEST);

                foreach ($qs as $k => $v) {
                    if ($v === null) {
                        $part = $k;
                        unset($_GET[$k]);
                        unset($_REQUEST[$k]);
                    }
                    break;
                }
            }
        }

        $_SERVER['URL_REQUEST_PART'] = $part;

        $this->getArgs($part, $type, $this->args);

        # --BEHAVIOR-- urlHandlerGetArgsDocument
        $core->callBehavior('urlHandlerGetArgsDocument', $this);

        if (!$type) {
            $this->type = 'default';
            $this->callDefaultHandler($this->args);
        } else {
            $this->type = $type;
            $this->callHandler($type, $this->args);
        }
    }

    public static function home($args)
    {
        $n = self::getPageNumber($args);

        if ($args && !$n) {
            # "Then specified URL went unrecognized by all URL handlers and
            # defaults to the home page, but is not a page number.
            self::p404();
        } else {
            $_ctx = &$GLOBALS['_ctx'];
            $core = &$GLOBALS['core'];

            if ($n) {
                $GLOBALS['_page_number'] = $n;
                $core->url->type         = $n > 1 ? 'default-page' : 'default';
            }

            if (empty($_GET['q'])) {
                if ($core->blog->settings->system->nb_post_for_home !== null) {
                    $_ctx->nb_entry_first_page = $core->blog->settings->system->nb_post_for_home;
                }
                self::serveDocument('home.html');
                $core->blog->publishScheduledEntries();
            } else {
                self::search();
            }
        }
    }

    public static function search()
    {
        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        if ($core->blog->settings->system->no_search) {

            # Search is disabled for this blog.
            self::p404();

        } else {

            $core->url->type = 'search';

            $GLOBALS['_search'] = !empty($_GET['q']) ? rawurldecode($_GET['q']) : '';
            if ($GLOBALS['_search']) {
                $params = new ArrayObject(array('search' => $GLOBALS['_search']));
                $core->callBehavior('publicBeforeSearchCount', $params);
                $GLOBALS['_search_count'] = $core->blog->getPosts($params, true)->f(0);
            }

            self::serveDocument('search.html');
        }
    }

    public static function lang($args)
    {
        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        $n      = self::getPageNumber($args);
        $params = new ArrayObject(array(
            'lang' => $args));

        $core->callBehavior('publicLangBeforeGetLangs', $params, $args);

        $_ctx->langs = $core->blog->getLangs($params);

        if ($_ctx->langs->isEmpty()) {
            # The specified language does not exist.
            self::p404();
        } else {
            if ($n) {
                $GLOBALS['_page_number'] = $n;
            }
            $_ctx->cur_lang = $args;
            self::home(null);
        }
    }

    public static function category($args)
    {
        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        $n = self::getPageNumber($args);

        if ($args == '' && !$n) {
            # No category was specified.
            self::p404();
        } else {
            $params = new ArrayObject(array(
                'cat_url'       => $args,
                'post_type'     => 'post',
                'without_empty' => false));

            $core->callBehavior('publicCategoryBeforeGetCategories', $params, $args);

            $_ctx->categories = $core->blog->getCategories($params);

            if ($_ctx->categories->isEmpty()) {
                # The specified category does no exist.
                self::p404();
            } else {
                if ($n) {
                    $GLOBALS['_page_number'] = $n;
                }
                self::serveDocument('category.html');
            }
        }
    }

    public static function archive($args)
    {
        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        $year = $month = $cat_url = null;
        # Nothing or year and month
        if ($args == '') {
            self::serveDocument('archive.html');
        } elseif (preg_match('|^/([0-9]{4})/([0-9]{2})$|', $args, $m)) {
            $params = new ArrayObject(array(
                'year'  => $m[1],
                'month' => $m[2],
                'type'  => 'month'));

            $core->callBehavior('publicArchiveBeforeGetDates', $params, $args);

            $_ctx->archives = $core->blog->getDates($params);

            if ($_ctx->archives->isEmpty()) {
                # There is no entries for the specified period.
                self::p404();
            } else {
                self::serveDocument('archive_month.html');
            }
        } else {
            # The specified URL is not a date.
            self::p404();
        }
    }

    public static function post($args)
    {
        if ($args == '') {
            # No entry was specified.
            self::p404();
        } else {
            $_ctx = &$GLOBALS['_ctx'];
            $core = &$GLOBALS['core'];

            $core->blog->withoutPassword(false);

            $params = new ArrayObject(array(
                'post_url' => $args));

            $core->callBehavior('publicPostBeforeGetPosts', $params, $args);

            $_ctx->posts = $core->blog->getPosts($params);

            $_ctx->comment_preview               = new ArrayObject();
            $_ctx->comment_preview['content']    = '';
            $_ctx->comment_preview['rawcontent'] = '';
            $_ctx->comment_preview['name']       = '';
            $_ctx->comment_preview['mail']       = '';
            $_ctx->comment_preview['site']       = '';
            $_ctx->comment_preview['preview']    = false;
            $_ctx->comment_preview['remember']   = false;

            $core->blog->withoutPassword(true);

            if ($_ctx->posts->isEmpty()) {
                # The specified entry does not exist.
                self::p404();
            } else {
                $post_id       = $_ctx->posts->post_id;
                $post_password = $_ctx->posts->post_password;

                # Password protected entry
                if ($post_password != '' && !$_ctx->preview) {
                    # Get passwords cookie
                    if (isset($_COOKIE['dc_passwd'])) {
                        $pwd_cookie = json_decode($_COOKIE['dc_passwd']);
                        if ($pwd_cookie === null) {
                            $pwd_cookie = array();
                        } else {
                            $pwd_cookie = (array) $pwd_cookie;
                        }
                    } else {
                        $pwd_cookie = array();
                    }

                    # Check for match
                    # Note: We must prefix post_id key with '#'' in pwd_cookie array in order to avoid integer conversion
                    # because MyArray["12345"] is treated as MyArray[12345]
                    if ((!empty($_POST['password']) && $_POST['password'] == $post_password)
                        || (isset($pwd_cookie['#' . $post_id]) && $pwd_cookie['#' . $post_id] == $post_password)) {
                        $pwd_cookie['#' . $post_id] = $post_password;
                        setcookie('dc_passwd', json_encode($pwd_cookie), 0, '/');
                    } else {
                        self::serveDocument('password-form.html', 'text/html', false);
                        return;
                    }
                }

                $post_comment =
                isset($_POST['c_name']) && isset($_POST['c_mail']) &&
                isset($_POST['c_site']) && isset($_POST['c_content']) &&
                $_ctx->posts->commentsActive();

                # Posting a comment
                if ($post_comment) {
                    # Spam trap
                    if (!empty($_POST['f_mail'])) {
                        http::head(412, 'Precondition Failed');
                        header('Content-Type: text/plain');
                        echo "So Long, and Thanks For All the Fish";
                        # Exits immediately the application to preserve the server.
                        exit;
                    }

                    $name    = $_POST['c_name'];
                    $mail    = $_POST['c_mail'];
                    $site    = $_POST['c_site'];
                    $content = $_POST['c_content'];
                    $preview = !empty($_POST['preview']);

                    if ($content != '') {
                        # --BEHAVIOR-- publicBeforeCommentTransform
                        $buffer = $core->callBehavior('publicBeforeCommentTransform', $content);
                        if ($buffer != '') {
                            $content = $buffer;
                        } else {
                            if ($core->blog->settings->system->wiki_comments) {
                                $core->initWikiComment();
                            } else {
                                $core->initWikiSimpleComment();
                            }
                            $content = $core->wikiTransform($content);
                        }
                        $content = $core->HTMLfilter($content);
                    }

                    $_ctx->comment_preview['content']    = $content;
                    $_ctx->comment_preview['rawcontent'] = $_POST['c_content'];
                    $_ctx->comment_preview['name']       = $name;
                    $_ctx->comment_preview['mail']       = $mail;
                    $_ctx->comment_preview['site']       = $site;

                    if ($preview) {
                        # --BEHAVIOR-- publicBeforeCommentPreview
                        $core->callBehavior('publicBeforeCommentPreview', $_ctx->comment_preview);

                        $_ctx->comment_preview['preview'] = true;
                    } else {
                        # Post the comment
                        $cur                  = $core->con->openCursor($core->prefix . 'comment');
                        $cur->comment_author  = $name;
                        $cur->comment_site    = html::clean($site);
                        $cur->comment_email   = html::clean($mail);
                        $cur->comment_content = $content;
                        $cur->post_id         = $_ctx->posts->post_id;
                        $cur->comment_status  = $core->blog->settings->system->comments_pub ? 1 : -1;
                        $cur->comment_ip      = http::realIP();

                        $redir = $_ctx->posts->getURL();
                        $redir .= $core->blog->settings->system->url_scan == 'query_string' ? '&' : '?';

                        try
                        {
                            if (!text::isEmail($cur->comment_email)) {
                                throw new Exception(__('You must provide a valid email address.'));
                            }

                            # --BEHAVIOR-- publicBeforeCommentCreate
                            $core->callBehavior('publicBeforeCommentCreate', $cur);
                            if ($cur->post_id) {
                                $comment_id = $core->blog->addComment($cur);

                                # --BEHAVIOR-- publicAfterCommentCreate
                                $core->callBehavior('publicAfterCommentCreate', $cur, $comment_id);
                            }

                            if ($cur->comment_status == 1) {
                                $redir_arg = 'pub=1';
                            } else {
                                $redir_arg = 'pub=0';
                            }

                            header('Location: ' . $redir . $redir_arg);
                        } catch (Exception $e) {
                            $_ctx->form_error = $e->getMessage();
                            $_ctx->form_error;
                        }
                    }
                }

                # The entry
                if ($_ctx->posts->trackbacksActive()) {
                    header('X-Pingback: ' . $core->blog->url . $core->url->getURLFor("xmlrpc", $core->blog->id));
                    header('Link: <' . $core->blog->url . $core->url->getURLFor('webmention') . '>; rel="webmention"');
                }
                self::serveDocument('post.html');
            }
        }
    }

    public static function preview($args)
    {
        $core = $GLOBALS['core'];
        $_ctx = $GLOBALS['_ctx'];

        if (!preg_match('#^(.+?)/([0-9a-z]{40})/(.+?)$#', $args, $m)) {
            # The specified Preview URL is malformed.
            self::p404();
        } else {
            $user_id  = $m[1];
            $user_key = $m[2];
            $post_url = $m[3];
            if (!$core->auth->checkUser($user_id, null, $user_key)) {
                # The user has no access to the entry.
                self::p404();
            } else {
                $_ctx->preview = true;
                if (defined("DC_ADMIN_URL")) {
                    $_ctx->xframeoption = DC_ADMIN_URL;
                }
                self::post($post_url);
            }
        }
    }

    public static function feed($args)
    {
        $type     = null;
        $comments = false;
        $cat_url  = false;
        $post_id  = null;
        $subtitle = '';

        $mime = 'application/xml';

        $_ctx = &$GLOBALS['_ctx'];
        $core = &$GLOBALS['core'];

        if (preg_match('!^([a-z]{2}(-[a-z]{2})?)/(.*)$!', $args, $m)) {
            $params = new ArrayObject(array('lang' => $m[1]));

            $args = $m[3];

            $core->callBehavior('publicFeedBeforeGetLangs', $params, $args);

            $_ctx->langs = $core->blog->getLangs($params);

            if ($_ctx->langs->isEmpty()) {
                # The specified language does not exist.
                self::p404();
                return;
            } else {
                $_ctx->cur_lang = $m[1];
            }
        }

        if (preg_match('#^rss2/xslt$#', $args, $m)) {
            # RSS XSLT stylesheet
            self::serveDocument('rss2.xsl', 'text/xml');
            return;
        } elseif (preg_match('#^(atom|rss2)/comments/([0-9]+)$#', $args, $m)) {
            # Post comments feed
            $type     = $m[1];
            $comments = true;
            $post_id  = (integer) $m[2];
        } elseif (preg_match('#^(?:category/(.+)/)?(atom|rss2)(/comments)?$#', $args, $m)) {
            # All posts or comments feed
            $type     = $m[2];
            $comments = !empty($m[3]);
            if (!empty($m[1])) {
                $cat_url = $m[1];
            }
        } else {
            # The specified Feed URL is malformed.
            self::p404();
            return;
        }

        if ($cat_url) {
            $params = new ArrayObject(array(
                'cat_url'   => $cat_url,
                'post_type' => 'post'));

            $core->callBehavior('publicFeedBeforeGetCategories', $params, $args);

            $_ctx->categories = $core->blog->getCategories($params);

            if ($_ctx->categories->isEmpty()) {
                # The specified category does no exist.
                self::p404();
                return;
            }

            $subtitle = ' - ' . $_ctx->categories->cat_title;
        } elseif ($post_id) {
            $params = new ArrayObject(array(
                'post_id'   => $post_id,
                'post_type' => ''));

            $core->callBehavior('publicFeedBeforeGetPosts', $params, $args);

            $_ctx->posts = $core->blog->getPosts($params);

            if ($_ctx->posts->isEmpty()) {
                # The specified post does not exist.
                self::p404();
                return;
            }

            $subtitle = ' - ' . $_ctx->posts->post_title;
        }

        $tpl = $type;
        if ($comments) {
            $tpl .= '-comments';
            $_ctx->nb_comment_per_page = $core->blog->settings->system->nb_comment_per_feed;
        } else {
            $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_feed;
            $_ctx->short_feed_items  = $core->blog->settings->system->short_feed_items;
        }
        $tpl .= '.xml';

        if ($type == 'atom') {
            $mime = 'application/atom+xml';
        }

        $_ctx->feed_subtitle = $subtitle;

        header('X-Robots-Tag: ' . context::robotsPolicy($core->blog->settings->system->robots_policy, ''));
        self::serveDocument($tpl, $mime);
        if (!$comments && !$cat_url) {
            $core->blog->publishScheduledEntries();
        }
    }

    public static function trackback($args)
    {
        if (!preg_match('/^[0-9]+$/', $args)) {
            # The specified trackback URL is not an number
            self::p404();
        } else {
            $core = &$GLOBALS['core'];

            // Save locally post_id from args
            $post_id = (integer) $args;

            if (!is_array($args)) {
                $args = array();
            }

            $args['post_id'] = $post_id;
            $args['type']    = 'trackback';

            # --BEHAVIOR-- publicBeforeReceiveTrackback
            $core->callBehavior('publicBeforeReceiveTrackback', $core, $args);

            $tb = new dcTrackback($core);
            $tb->receiveTrackback($post_id);
        }
    }

    public static function webmention($args)
    {
        $core = &$GLOBALS['core'];
        if (!is_array($args)) {
            $args = array();
        }

        $args['type'] = 'webmention';

        # --BEHAVIOR-- publicBeforeReceiveTrackback
        $core->callBehavior('publicBeforeReceiveTrackback', $core, $args);

        $tb = new dcTrackback($core);
        $tb->receiveWebmention();
    }

    public static function rsd($args)
    {
        $core = &$GLOBALS['core'];
        http::cache($GLOBALS['mod_files'], $GLOBALS['mod_ts']);

        header('Content-Type: text/xml; charset=UTF-8');
        echo
        '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
        '<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">' . "\n" .
        "<service>\n" .
        "  <engineName>Dotclear</engineName>\n" .
        "  <engineLink>http://www.dotclear.org/</engineLink>\n" .
        '  <homePageLink>' . html::escapeHTML($core->blog->url) . "</homePageLink>\n";

        if ($core->blog->settings->system->enable_xmlrpc) {
            $u = sprintf(DC_XMLRPC_URL, $core->blog->url, $core->blog->id);

            echo
                "  <apis>\n" .
                '    <api name="WordPress" blogID="1" preferred="true" apiLink="' . $u . '"/>' . "\n" .
                '    <api name="Movable Type" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
                '    <api name="MetaWeblog" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
                '    <api name="Blogger" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
                "  </apis>\n";
        }

        echo
            "</service>\n" .
            "</rsd>\n";
    }

    public static function xmlrpc($args)
    {
        $core    = &$GLOBALS['core'];
        $blog_id = preg_replace('#^([^/]*).*#', '$1', $args);
        $server  = new dcXmlRpc($core, $blog_id);
        $server->serve();
    }
}
