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