Dotclear

source: inc/public/lib.urlhandlers.php @ 3731:3770620079d4

Revision 3731:3770620079d4, 24.2 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Simplify licence block at the beginning of each file

Line 
1<?php
2/**
3 * @package Dotclear
4 * @subpackage Public
5 *
6 * @copyright Olivier Meunier & Association Dotclear
7 * @copyright GPL-2.0-only
8 */
9
10if (!defined('DC_RC_PATH')) {return;}
11
12class dcUrlHandlers extends urlHandler
13{
14    public $args;
15
16    public function getURLFor($type, $value = '')
17    {
18        $core = &$GLOBALS['core'];
19        $url  = $core->callBehavior("publicGetURLFor", $type, $value);
20        if (!$url) {
21            $url = $this->getBase($type);
22            if ($value) {
23                if ($url) {
24                    $url .= '/';
25                }
26                $url .= $value;
27            }
28        }
29        return $url;
30    }
31
32    public function register($type, $url, $representation, $handler)
33    {
34        $core = &$GLOBALS['core'];
35        $t    = new ArrayObject(array($type, $url, $representation, $handler));
36        $core->callBehavior("publicRegisterURL", $t);
37        parent::register($t[0], $t[1], $t[2], $t[3]);
38    }
39
40    public static function p404()
41    {
42        throw new Exception("Page not found", 404);
43    }
44
45    public static function default404($args, $type, $e)
46    {
47        if ($e->getCode() != 404) {
48            throw $e;
49        }
50        $_ctx = &$GLOBALS['_ctx'];
51        $core = $GLOBALS['core'];
52
53        header('Content-Type: text/html; charset=UTF-8');
54        http::head(404, 'Not Found');
55        $core->url->type    = '404';
56        $_ctx->current_tpl  = '404.html';
57        $_ctx->content_type = 'text/html';
58
59        echo $core->tpl->getData($_ctx->current_tpl);
60
61        # --BEHAVIOR-- publicAfterDocument
62        $core->callBehavior('publicAfterDocument', $core);
63        exit;
64    }
65
66    protected static function getPageNumber(&$args)
67    {
68        if (preg_match('#(^|/)page/([0-9]+)$#', $args, $m)) {
69            $n = (integer) $m[2];
70            if ($n > 0) {
71                $args = preg_replace('#(^|/)page/([0-9]+)$#', '', $args);
72                return $n;
73            }
74        }
75
76        return false;
77    }
78
79    protected static function serveDocument($tpl, $content_type = 'text/html', $http_cache = true, $http_etag = true)
80    {
81        $_ctx = &$GLOBALS['_ctx'];
82        $core = &$GLOBALS['core'];
83
84        if ($_ctx->nb_entry_per_page === null) {
85            $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_page;
86        }
87        if ($_ctx->nb_entry_first_page === null) {
88            $_ctx->nb_entry_first_page = $_ctx->nb_entry_per_page;
89        }
90
91        $tpl_file = $core->tpl->getFilePath($tpl);
92
93        if (!$tpl_file) {
94            throw new Exception('Unable to find template ');
95        }
96
97        $result = new ArrayObject;
98
99        $_ctx->current_tpl  = $tpl;
100        $_ctx->content_type = $content_type;
101        $_ctx->http_cache   = $http_cache;
102        $_ctx->http_etag    = $http_etag;
103        $core->callBehavior('urlHandlerBeforeGetData', $_ctx);
104
105        if ($_ctx->http_cache) {
106            $GLOBALS['mod_files'][] = $tpl_file;
107            http::cache($GLOBALS['mod_files'], $GLOBALS['mod_ts']);
108        }
109
110        header('Content-Type: ' . $_ctx->content_type . '; charset=UTF-8');
111
112        // Additional headers
113        $headers = new ArrayObject;
114        if ($core->blog->settings->system->prevents_clickjacking) {
115            if ($_ctx->exists('xframeoption')) {
116                $url    = parse_url($_ctx->xframeoption);
117                $header = sprintf('X-Frame-Options: %s',
118                    is_array($url) ? ("ALLOW-FROM " . $url['scheme'] . '://' . $url['host']) : 'SAMEORIGIN');
119            } else {
120                                                         // Prevents Clickjacking as far as possible
121                $header = 'X-Frame-Options: SAMEORIGIN'; // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+
122            }
123            $headers[] = $header;
124        }
125        # --BEHAVIOR-- urlHandlerServeDocumentHeaders
126        $core->callBehavior('urlHandlerServeDocumentHeaders', $headers);
127
128        // Send additional headers if any
129        foreach ($headers as $header) {
130            header($header);
131        }
132
133        $result['content']      = $core->tpl->getData($_ctx->current_tpl);
134        $result['content_type'] = $_ctx->content_type;
135        $result['tpl']          = $_ctx->current_tpl;
136        $result['blogupddt']    = $core->blog->upddt;
137        $result['headers']      = $headers;
138
139        # --BEHAVIOR-- urlHandlerServeDocument
140        $core->callBehavior('urlHandlerServeDocument', $result);
141
142        if ($_ctx->http_cache && $_ctx->http_etag) {
143            http::etag($result['content'], http::getSelfURI());
144        }
145        echo $result['content'];
146    }
147
148    public function getDocument()
149    {
150        $core = &$GLOBALS['core'];
151
152        $type = $args = '';
153
154        if ($this->mode == 'path_info') {
155            $part = substr($_SERVER['PATH_INFO'], 1);
156        } else {
157            $part = '';
158
159            $qs = $this->parseQueryString();
160
161            # Recreates some _GET and _REQUEST pairs
162            if (!empty($qs)) {
163                foreach ($_GET as $k => $v) {
164                    if (isset($_REQUEST[$k])) {
165                        unset($_REQUEST[$k]);
166                    }
167                }
168                $_GET     = $qs;
169                $_REQUEST = array_merge($qs, $_REQUEST);
170
171                foreach ($qs as $k => $v) {
172                    if ($v === null) {
173                        $part = $k;
174                        unset($_GET[$k]);
175                        unset($_REQUEST[$k]);
176                    }
177                    break;
178                }
179            }
180        }
181
182        $_SERVER['URL_REQUEST_PART'] = $part;
183
184        $this->getArgs($part, $type, $this->args);
185
186        # --BEHAVIOR-- urlHandlerGetArgsDocument
187        $core->callBehavior('urlHandlerGetArgsDocument', $this);
188
189        if (!$type) {
190            $this->type = 'default';
191            $this->callDefaultHandler($this->args);
192        } else {
193            $this->type = $type;
194            $this->callHandler($type, $this->args);
195        }
196    }
197
198    public static function home($args)
199    {
200        $n = self::getPageNumber($args);
201
202        if ($args && !$n) {
203            # "Then specified URL went unrecognized by all URL handlers and
204            # defaults to the home page, but is not a page number.
205            self::p404();
206        } else {
207            $_ctx = &$GLOBALS['_ctx'];
208            $core = &$GLOBALS['core'];
209
210            if ($n) {
211                $GLOBALS['_page_number'] = $n;
212                $core->url->type         = $n > 1 ? 'default-page' : 'default';
213            }
214
215            if (empty($_GET['q'])) {
216                if ($core->blog->settings->system->nb_post_for_home !== null) {
217                    $_ctx->nb_entry_first_page = $core->blog->settings->system->nb_post_for_home;
218                }
219                self::serveDocument('home.html');
220                $core->blog->publishScheduledEntries();
221            } else {
222                self::search();
223            }
224        }
225    }
226
227    public static function search()
228    {
229        $_ctx = &$GLOBALS['_ctx'];
230        $core = &$GLOBALS['core'];
231
232        if ($core->blog->settings->system->no_search) {
233
234            # Search is disabled for this blog.
235            self::p404();
236
237        } else {
238
239            $core->url->type = 'search';
240
241            $GLOBALS['_search'] = !empty($_GET['q']) ? rawurldecode($_GET['q']) : '';
242            if ($GLOBALS['_search']) {
243                $params = new ArrayObject(array('search' => $GLOBALS['_search']));
244                $core->callBehavior('publicBeforeSearchCount', $params);
245                $GLOBALS['_search_count'] = $core->blog->getPosts($params, true)->f(0);
246            }
247
248            self::serveDocument('search.html');
249        }
250    }
251
252    public static function lang($args)
253    {
254        $_ctx = &$GLOBALS['_ctx'];
255        $core = &$GLOBALS['core'];
256
257        $n      = self::getPageNumber($args);
258        $params = new ArrayObject(array(
259            'lang' => $args));
260
261        $core->callBehavior('publicLangBeforeGetLangs', $params, $args);
262
263        $_ctx->langs = $core->blog->getLangs($params);
264
265        if ($_ctx->langs->isEmpty()) {
266            # The specified language does not exist.
267            self::p404();
268        } else {
269            if ($n) {
270                $GLOBALS['_page_number'] = $n;
271            }
272            $_ctx->cur_lang = $args;
273            self::home(null);
274        }
275    }
276
277    public static function category($args)
278    {
279        $_ctx = &$GLOBALS['_ctx'];
280        $core = &$GLOBALS['core'];
281
282        $n = self::getPageNumber($args);
283
284        if ($args == '' && !$n) {
285            # No category was specified.
286            self::p404();
287        } else {
288            $params = new ArrayObject(array(
289                'cat_url'       => $args,
290                'post_type'     => 'post',
291                'without_empty' => false));
292
293            $core->callBehavior('publicCategoryBeforeGetCategories', $params, $args);
294
295            $_ctx->categories = $core->blog->getCategories($params);
296
297            if ($_ctx->categories->isEmpty()) {
298                # The specified category does no exist.
299                self::p404();
300            } else {
301                if ($n) {
302                    $GLOBALS['_page_number'] = $n;
303                }
304                self::serveDocument('category.html');
305            }
306        }
307    }
308
309    public static function archive($args)
310    {
311        $_ctx = &$GLOBALS['_ctx'];
312        $core = &$GLOBALS['core'];
313
314        $year = $month = $cat_url = null;
315        # Nothing or year and month
316        if ($args == '') {
317            self::serveDocument('archive.html');
318        } elseif (preg_match('|^/([0-9]{4})/([0-9]{2})$|', $args, $m)) {
319            $params = new ArrayObject(array(
320                'year'  => $m[1],
321                'month' => $m[2],
322                'type'  => 'month'));
323
324            $core->callBehavior('publicArchiveBeforeGetDates', $params, $args);
325
326            $_ctx->archives = $core->blog->getDates($params);
327
328            if ($_ctx->archives->isEmpty()) {
329                # There is no entries for the specified period.
330                self::p404();
331            } else {
332                self::serveDocument('archive_month.html');
333            }
334        } else {
335            # The specified URL is not a date.
336            self::p404();
337        }
338    }
339
340    public static function post($args)
341    {
342        if ($args == '') {
343            # No entry was specified.
344            self::p404();
345        } else {
346            $_ctx = &$GLOBALS['_ctx'];
347            $core = &$GLOBALS['core'];
348
349            $core->blog->withoutPassword(false);
350
351            $params = new ArrayObject(array(
352                'post_url' => $args));
353
354            $core->callBehavior('publicPostBeforeGetPosts', $params, $args);
355
356            $_ctx->posts = $core->blog->getPosts($params);
357
358            $_ctx->comment_preview               = new ArrayObject();
359            $_ctx->comment_preview['content']    = '';
360            $_ctx->comment_preview['rawcontent'] = '';
361            $_ctx->comment_preview['name']       = '';
362            $_ctx->comment_preview['mail']       = '';
363            $_ctx->comment_preview['site']       = '';
364            $_ctx->comment_preview['preview']    = false;
365            $_ctx->comment_preview['remember']   = false;
366
367            $core->blog->withoutPassword(true);
368
369            if ($_ctx->posts->isEmpty()) {
370                # The specified entry does not exist.
371                self::p404();
372            } else {
373                $post_id       = $_ctx->posts->post_id;
374                $post_password = $_ctx->posts->post_password;
375
376                # Password protected entry
377                if ($post_password != '' && !$_ctx->preview) {
378                    # Get passwords cookie
379                    if (isset($_COOKIE['dc_passwd'])) {
380                        $pwd_cookie = json_decode($_COOKIE['dc_passwd']);
381                        if ($pwd_cookie === null) {
382                            $pwd_cookie = array();
383                        } else {
384                            $pwd_cookie = (array) $pwd_cookie;
385                        }
386                    } else {
387                        $pwd_cookie = array();
388                    }
389
390                    # Check for match
391                    # Note: We must prefix post_id key with '#'' in pwd_cookie array in order to avoid integer conversion
392                    # because MyArray["12345"] is treated as MyArray[12345]
393                    if ((!empty($_POST['password']) && $_POST['password'] == $post_password)
394                        || (isset($pwd_cookie['#' . $post_id]) && $pwd_cookie['#' . $post_id] == $post_password)) {
395                        $pwd_cookie['#' . $post_id] = $post_password;
396                        setcookie('dc_passwd', json_encode($pwd_cookie), 0, '/');
397                    } else {
398                        self::serveDocument('password-form.html', 'text/html', false);
399                        return;
400                    }
401                }
402
403                $post_comment =
404                isset($_POST['c_name']) && isset($_POST['c_mail']) &&
405                isset($_POST['c_site']) && isset($_POST['c_content']) &&
406                $_ctx->posts->commentsActive();
407
408                # Posting a comment
409                if ($post_comment) {
410                    # Spam trap
411                    if (!empty($_POST['f_mail'])) {
412                        http::head(412, 'Precondition Failed');
413                        header('Content-Type: text/plain');
414                        echo "So Long, and Thanks For All the Fish";
415                        # Exits immediately the application to preserve the server.
416                        exit;
417                    }
418
419                    $name    = $_POST['c_name'];
420                    $mail    = $_POST['c_mail'];
421                    $site    = $_POST['c_site'];
422                    $content = $_POST['c_content'];
423                    $preview = !empty($_POST['preview']);
424
425                    if ($content != '') {
426                        # --BEHAVIOR-- publicBeforeCommentTransform
427                        $buffer = $core->callBehavior('publicBeforeCommentTransform', $content);
428                        if ($buffer != '') {
429                            $content = $buffer;
430                        } else {
431                            if ($core->blog->settings->system->wiki_comments) {
432                                $core->initWikiComment();
433                            } else {
434                                $core->initWikiSimpleComment();
435                            }
436                            $content = $core->wikiTransform($content);
437                        }
438                        $content = $core->HTMLfilter($content);
439                    }
440
441                    $_ctx->comment_preview['content']    = $content;
442                    $_ctx->comment_preview['rawcontent'] = $_POST['c_content'];
443                    $_ctx->comment_preview['name']       = $name;
444                    $_ctx->comment_preview['mail']       = $mail;
445                    $_ctx->comment_preview['site']       = $site;
446
447                    if ($preview) {
448                        # --BEHAVIOR-- publicBeforeCommentPreview
449                        $core->callBehavior('publicBeforeCommentPreview', $_ctx->comment_preview);
450
451                        $_ctx->comment_preview['preview'] = true;
452                    } else {
453                        # Post the comment
454                        $cur                  = $core->con->openCursor($core->prefix . 'comment');
455                        $cur->comment_author  = $name;
456                        $cur->comment_site    = html::clean($site);
457                        $cur->comment_email   = html::clean($mail);
458                        $cur->comment_content = $content;
459                        $cur->post_id         = $_ctx->posts->post_id;
460                        $cur->comment_status  = $core->blog->settings->system->comments_pub ? 1 : -1;
461                        $cur->comment_ip      = http::realIP();
462
463                        $redir = $_ctx->posts->getURL();
464                        $redir .= $core->blog->settings->system->url_scan == 'query_string' ? '&' : '?';
465
466                        try
467                        {
468                            if (!text::isEmail($cur->comment_email)) {
469                                throw new Exception(__('You must provide a valid email address.'));
470                            }
471
472                            # --BEHAVIOR-- publicBeforeCommentCreate
473                            $core->callBehavior('publicBeforeCommentCreate', $cur);
474                            if ($cur->post_id) {
475                                $comment_id = $core->blog->addComment($cur);
476
477                                # --BEHAVIOR-- publicAfterCommentCreate
478                                $core->callBehavior('publicAfterCommentCreate', $cur, $comment_id);
479                            }
480
481                            if ($cur->comment_status == 1) {
482                                $redir_arg = 'pub=1';
483                            } else {
484                                $redir_arg = 'pub=0';
485                            }
486
487                            header('Location: ' . $redir . $redir_arg);
488                        } catch (Exception $e) {
489                            $_ctx->form_error = $e->getMessage();
490                            $_ctx->form_error;
491                        }
492                    }
493                }
494
495                # The entry
496                if ($_ctx->posts->trackbacksActive()) {
497                    header('X-Pingback: ' . $core->blog->url . $core->url->getURLFor("xmlrpc", $core->blog->id));
498                    header('Link: <' . $core->blog->url . $core->url->getURLFor('webmention') . '>; rel="webmention"');
499                }
500                self::serveDocument('post.html');
501            }
502        }
503    }
504
505    public static function preview($args)
506    {
507        $core = $GLOBALS['core'];
508        $_ctx = $GLOBALS['_ctx'];
509
510        if (!preg_match('#^(.+?)/([0-9a-z]{40})/(.+?)$#', $args, $m)) {
511            # The specified Preview URL is malformed.
512            self::p404();
513        } else {
514            $user_id  = $m[1];
515            $user_key = $m[2];
516            $post_url = $m[3];
517            if (!$core->auth->checkUser($user_id, null, $user_key)) {
518                # The user has no access to the entry.
519                self::p404();
520            } else {
521                $_ctx->preview = true;
522                if (defined("DC_ADMIN_URL")) {
523                    $_ctx->xframeoption = DC_ADMIN_URL;
524                }
525                self::post($post_url);
526            }
527        }
528    }
529
530    public static function feed($args)
531    {
532        $type     = null;
533        $comments = false;
534        $cat_url  = false;
535        $post_id  = null;
536        $subtitle = '';
537
538        $mime = 'application/xml';
539
540        $_ctx = &$GLOBALS['_ctx'];
541        $core = &$GLOBALS['core'];
542
543        if (preg_match('!^([a-z]{2}(-[a-z]{2})?)/(.*)$!', $args, $m)) {
544            $params = new ArrayObject(array('lang' => $m[1]));
545
546            $args = $m[3];
547
548            $core->callBehavior('publicFeedBeforeGetLangs', $params, $args);
549
550            $_ctx->langs = $core->blog->getLangs($params);
551
552            if ($_ctx->langs->isEmpty()) {
553                # The specified language does not exist.
554                self::p404();
555                return;
556            } else {
557                $_ctx->cur_lang = $m[1];
558            }
559        }
560
561        if (preg_match('#^rss2/xslt$#', $args, $m)) {
562            # RSS XSLT stylesheet
563            self::serveDocument('rss2.xsl', 'text/xml');
564            return;
565        } elseif (preg_match('#^(atom|rss2)/comments/([0-9]+)$#', $args, $m)) {
566            # Post comments feed
567            $type     = $m[1];
568            $comments = true;
569            $post_id  = (integer) $m[2];
570        } elseif (preg_match('#^(?:category/(.+)/)?(atom|rss2)(/comments)?$#', $args, $m)) {
571            # All posts or comments feed
572            $type     = $m[2];
573            $comments = !empty($m[3]);
574            if (!empty($m[1])) {
575                $cat_url = $m[1];
576            }
577        } else {
578            # The specified Feed URL is malformed.
579            self::p404();
580            return;
581        }
582
583        if ($cat_url) {
584            $params = new ArrayObject(array(
585                'cat_url'   => $cat_url,
586                'post_type' => 'post'));
587
588            $core->callBehavior('publicFeedBeforeGetCategories', $params, $args);
589
590            $_ctx->categories = $core->blog->getCategories($params);
591
592            if ($_ctx->categories->isEmpty()) {
593                # The specified category does no exist.
594                self::p404();
595                return;
596            }
597
598            $subtitle = ' - ' . $_ctx->categories->cat_title;
599        } elseif ($post_id) {
600            $params = new ArrayObject(array(
601                'post_id'   => $post_id,
602                'post_type' => ''));
603
604            $core->callBehavior('publicFeedBeforeGetPosts', $params, $args);
605
606            $_ctx->posts = $core->blog->getPosts($params);
607
608            if ($_ctx->posts->isEmpty()) {
609                # The specified post does not exist.
610                self::p404();
611                return;
612            }
613
614            $subtitle = ' - ' . $_ctx->posts->post_title;
615        }
616
617        $tpl = $type;
618        if ($comments) {
619            $tpl .= '-comments';
620            $_ctx->nb_comment_per_page = $core->blog->settings->system->nb_comment_per_feed;
621        } else {
622            $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_feed;
623            $_ctx->short_feed_items  = $core->blog->settings->system->short_feed_items;
624        }
625        $tpl .= '.xml';
626
627        if ($type == 'atom') {
628            $mime = 'application/atom+xml';
629        }
630
631        $_ctx->feed_subtitle = $subtitle;
632
633        header('X-Robots-Tag: ' . context::robotsPolicy($core->blog->settings->system->robots_policy, ''));
634        self::serveDocument($tpl, $mime);
635        if (!$comments && !$cat_url) {
636            $core->blog->publishScheduledEntries();
637        }
638    }
639
640    public static function trackback($args)
641    {
642        if (!preg_match('/^[0-9]+$/', $args)) {
643            # The specified trackback URL is not an number
644            self::p404();
645        } else {
646            $core = &$GLOBALS['core'];
647
648            // Save locally post_id from args
649            $post_id = (integer) $args;
650
651            if (!is_array($args)) {
652                $args = array();
653            }
654
655            $args['post_id'] = $post_id;
656            $args['type']    = 'trackback';
657
658            # --BEHAVIOR-- publicBeforeReceiveTrackback
659            $core->callBehavior('publicBeforeReceiveTrackback', $core, $args);
660
661            $tb = new dcTrackback($core);
662            $tb->receiveTrackback($post_id);
663        }
664    }
665
666    public static function webmention($args)
667    {
668        $core = &$GLOBALS['core'];
669        if (!is_array($args)) {
670            $args = array();
671        }
672
673        $args['type'] = 'webmention';
674
675        # --BEHAVIOR-- publicBeforeReceiveTrackback
676        $core->callBehavior('publicBeforeReceiveTrackback', $core, $args);
677
678        $tb = new dcTrackback($core);
679        $tb->receiveWebmention();
680    }
681
682    public static function rsd($args)
683    {
684        $core = &$GLOBALS['core'];
685        http::cache($GLOBALS['mod_files'], $GLOBALS['mod_ts']);
686
687        header('Content-Type: text/xml; charset=UTF-8');
688        echo
689        '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
690        '<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">' . "\n" .
691        "<service>\n" .
692        "  <engineName>Dotclear</engineName>\n" .
693        "  <engineLink>http://www.dotclear.org/</engineLink>\n" .
694        '  <homePageLink>' . html::escapeHTML($core->blog->url) . "</homePageLink>\n";
695
696        if ($core->blog->settings->system->enable_xmlrpc) {
697            $u = sprintf(DC_XMLRPC_URL, $core->blog->url, $core->blog->id);
698
699            echo
700                "  <apis>\n" .
701                '    <api name="WordPress" blogID="1" preferred="true" apiLink="' . $u . '"/>' . "\n" .
702                '    <api name="Movable Type" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
703                '    <api name="MetaWeblog" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
704                '    <api name="Blogger" blogID="1" preferred="false" apiLink="' . $u . '"/>' . "\n" .
705                "  </apis>\n";
706        }
707
708        echo
709            "</service>\n" .
710            "</rsd>\n";
711    }
712
713    public static function xmlrpc($args)
714    {
715        $core    = &$GLOBALS['core'];
716        $blog_id = preg_replace('#^([^/]*).*#', '$1', $args);
717        $server  = new dcXmlRpc($core, $blog_id);
718        $server->serve();
719    }
720}
Note: See TracBrowser for help on using the repository browser.

Sites map