1<?php
2
3/**
4 * Pingback server for use with the DokuWiki Linkback Plugin.
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author     Gina Haeussge <osd@foosel.net>
8 * @link       http://wiki.foosel.net/snippets/dokuwiki/linkback
9 */
10
11use dokuwiki\Extension\PluginInterface;
12use IXR\Server\Server;
13use IXR\Message\Error;
14
15if (!defined('DOKU_INC'))
16    define('DOKU_INC', realpath(dirname(__FILE__) . '/../../../../') . '/');
17
18require_once (DOKU_INC . 'inc/init.php');
19
20require_once (DOKU_PLUGIN . 'linkback/tools.php');
21require_once (DOKU_PLUGIN . 'linkback/http.php');
22
23// Pingback Faultcodes
24const PINGBACK_ERROR_GENERIC = 0;
25const PINGBACK_ERROR_SOURCEURI_DOES_NOT_EXIST = 16;
26const PINGBACK_ERROR_SOURCEURI_DOES_NOT_CONTAIN_LINK = 17;
27const PINGBACK_ERROR_TARGETURI_DOES_NOT_EXIST = 32;
28const PINGBACK_ERROR_TARGETURI_CANNOT_BE_USED = 33;
29const PINGBACK_ERROR_PINGBACK_ALREADY_MADE = 48;
30const PINGBACK_ERROR_ACCESS_DENIED = 49;
31const PINGBACK_ERROR_NO_UPSTREAM = 50;
32
33class PingbackServer extends Server{
34
35    // helper instance
36    /** @var PluginInterface|tools_plugin_linkback  */
37    var $tools;
38
39    /**
40     * Register service and construct helper
41     */
42    function __construct() {
43        $this->tools = plugin_load('tools', 'linkback');  //TODO type 'tools'? is that possible?
44        parent::__construct(array (
45            'pingback.ping' => 'this:ping',
46
47        ));
48    }
49
50    /**
51     * @param $sourceUri
52     * @param $targetUri
53     * @return Error|void
54     */
55    function ping($sourceUri, $targetUri) {
56        // Plugin not enabled? Quit
57        if (plugin_isdisabled('linkback'))
58            return new Error(PINGBACK_ERROR_TARGETURI_CANNOT_BE_USED, '');
59
60        // pingback disabled? Quit
61        if (!$this->tools->getConf('enable_pingback'))
62            return new Error(PINGBACK_ERROR_TARGETURI_CANNOT_BE_USED, '');
63
64        // Given URLs are no urls? Quit
65        if (!preg_match("#^([a-z0-9\-\.+]+?)://.*#i", $sourceUri))
66            return new Error(PINGBACK_ERROR_GENERIC, '');
67        if (!preg_match("#^([a-z0-9\-\.+]+?)://.*#i", $targetUri))
68            return new Error(PINGBACK_ERROR_GENERIC, '');
69
70        // Source URL does not exist? Quit
71        $page = $this->tools->getPage($sourceUri);
72        if (!$page['success'] && ($page['status'] < 200 || $page['status'] >= 300))
73            return new Error(PINGBACK_ERROR_SOURCEURI_DOES_NOT_EXIST, '');
74
75        // Target URL does not match with request? Quit
76        $ID = substr($_SERVER['PATH_INFO'], 1);
77        if ($targetUri != wl($ID, '', true))
78            return new Error(PINGBACK_ERROR_GENERIC, '');
79
80        $file = metaFN($ID, '.linkbacks');
81        $data = array (
82            'send' => false,
83            'receive' => false,
84            'display' => false,
85            'sentpings' => array (),
86            'receivedpings' => array (),
87            'number' => 0,
88
89        );
90
91        if (@ file_exists($file))
92            $data = unserialize(io_readFile($file, false));
93
94        // Target URL is not pingback enabled? Quit
95        if (!$data['receive'])
96            return new Error(PINGBACK_ERROR_TARGETURI_CANNOT_BE_USED, '');
97
98        // Pingback already done? Quit
99        if ($data['receivedpings'][md5($sourceUri)])
100            return new Error(PINGBACK_ERROR_PINGBACK_ALREADY_MADE, '');
101
102        // Retrieve data from source
103        $linkback = $this->_getTrackbackData($sourceUri, $targetUri, $page);
104
105        // Source URL does not contain link to target? Quit
106        if (!$linkback)
107            return new Error(PINGBACK_ERROR_SOURCEURI_DOES_NOT_CONTAIN_LINK, '');
108
109        // Prepare event for Antispam plugins
110        $evt_data = array (
111            'linkback' => $linkback,
112            'page' => $page,
113            'target' => $targetUri,
114            'show' => true,
115            'log' => array(
116            	date('Y/m/d H:i', time()) . ': Received pingback from ' . $linkback['url'] . ' (' . $linkback['lid'] . ')',
117            ),
118        );
119        $event = new Doku_Event('ACTION_LINKBACK_RECEIVED', $evt_data);
120        if ($event->advise_before()) {
121            $linkback['show'] = $evt_data['show'];
122            if ($this->tools->getConf('usefavicon')) {
123                $linkback['favicon'] = $this->tools->getFavicon($linkback['url'], $page['body']);
124            }
125
126            // add pingback
127            $data['receivedpings'][$linkback['lid']] = $linkback;
128            if ($linkback['show'])
129                $data['number']++;
130
131            io_saveFile($file, serialize($data));
132            $this->tools->addLogEntry($linkback['received'], $ID, 'cl', '', $linkback['lid']);
133            $this->tools->notify($ID, $linkback);
134            if ($this->tools->getConf('log_processing'))
135                $this->tools->addProcessLogEntry($evt_data['log']);
136            $event->advise_after();
137        } else {
138            // Pingback was denied
139            if ($this->tools->getConf('log_processing'))
140                $this->tools->addProcessLogEntry($evt_data['log']);
141            $event->advise_after();
142            return new Error(PINGBACK_ERROR_ACCESS_DENIED, $this->tools->getLang('error_noreason'));
143        }
144    }
145
146    /**
147     * Constructs linkback data and checks if source contains a link to target and a title.
148     */
149    function _getTrackbackData($sourceUri, $targetUri, $page) {
150        // construct unique id for pingback
151        $lid = md5($sourceUri);
152        $linkback = array (
153            'lid' => $lid,
154            'title' => '',
155            'url' => $sourceUri,
156            'excerpt' => '',
157            'raw_excerpt' => '',
158            'blog_name' => '',
159            'received' => time(),
160            'submitter_ip' => $_SERVER['REMOTE_ADDR'],
161            'submitter_useragent' => $_SERVER['HTTP_USER_AGENT'],
162            'submitter_referer' => $_SERVER['HTTP_REFERER'],
163            'type' => 'pingback',
164            'show' => true,
165        );
166
167        $searchurl = preg_quote($targetUri, '!');
168        $regex = '!<a[^>]+?href="' . $searchurl . '"[^>]*?>(.*?)</a>!is';
169        $regex2 = '!\s(' . $searchurl . ')\s!is';
170        if (!preg_match($regex, $page['body'], $match) && !preg_match($regex2, $page['body'], $match)) {
171            if ($this->tools->getConf('ping_internal') && (strstr($targetUri, DOKU_URL) == $targetUri)) {
172                $ID = substr($_SERVER['PATH_INFO'], 1);
173                $searchurl = preg_quote(wl($ID), '!');
174
175                $regex = '!<a[^>]+?href="' . $searchurl . '"[^>]*?>(.*?)</a>!is';
176                $regex2 = '!\s(' . $searchurl . ')\s!is';
177                if (!preg_match($regex, $page['body'], $match) && !preg_match($regex2, $page['body'], $match))
178                    return false;
179            } else {
180                return false;
181            }
182        }
183        $linkback['raw_excerpt'] = '[...] ' . $match[1] . ' [...]';
184        $linkback['excerpt'] = '[...] ' . strip_tags($match[1]) . ' [...]';
185
186        $regex = '!<title.*?>(.*?)</title>!is';
187        if (!preg_match($regex, $page['body'], $match))
188            return false;
189        $linkback['title'] = strip_tags($match[1]);
190
191        return $linkback;
192    }
193
194}
195
196$server = new PingbackServer();
197