Dotclear

source: inc/public/lib.urlhandlers.php @ 3692:9decf2422f49

Revision 3692:9decf2422f49, 24.5 KB checked in by franck <carnet.franck.paul@…>, 8 years ago (diff)

PHP 7.2 compliance (deprecated each() replaced by foreach…)

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

Sites map