1<?php
2
3/**
4 * Internal helper functions 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
11// must be run within Dokuwiki
12if (!defined('DOKU_INC'))
13    die();
14
15if (!defined('DOKU_LF'))
16    define('DOKU_LF', "\n");
17if (!defined('DOKU_TAB'))
18    define('DOKU_TAB', "\t");
19
20require_once (DOKU_INC . 'inc/init.php');
21require_once (DOKU_INC . 'inc/common.php');
22require_once (DOKU_INC . 'inc/mail.php');
23require_once (DOKU_PLUGIN . 'linkback/http.php');
24
25class tools_plugin_linkback extends DokuWiki_Plugin {
26
27    function DokuWiki_Linkback_Interface() {
28        $this->DokuWiki_Plugin();
29    }
30
31    /**
32     * Retrieves a favicon for the given page.
33     */
34    function getFavicon($url, $page) {
35        $urlparts = parse_url($url);
36
37        $regex = '!<link rel="(shortcut )?icon" href="([^"]+)" ?/?>!';
38        if (preg_match($regex, $page, $match)) {
39            $icon = $match[2];
40            if (!preg_match("#^(http://)?[^/]+#i", $icon)) {
41                $icon = $urlparts['scheme'] . '://' . $urlparts['host'] . (($icon[0]) == '/' ? '' : '/') . $icon;
42            }
43            return $icon;
44        }
45
46        $icon = $urlparts['scheme'] . '://' . $urlparts['host'] . '/favicon.ico';
47
48        $http_client = new LinkbackHTTPClient();
49        $http_client->sendRequest($icon, array (), 'HEAD');
50        if ($http_client->status == 200)
51            return $icon;
52        else
53            return $this->getConf('favicon_default');
54    }
55
56    /**
57     * Retrieves $conf['range']kB of $url and returns headers and retrieved body.
58     */
59    function getPage($url) {
60        $range = $this->getConf('range') * 1024;
61
62        $http_client = new LinkbackHTTPClient();
63        $http_client->headers['Range'] = 'bytes=0-' . $range;
64        $http_client->max_bodysize = $range;
65        $http_client->max_bodysize_limit = true;
66
67        $retval = $http_client->get($url, true);
68
69        return array (
70            'success' => $retval,
71            'headers' => $http_client->resp_headers,
72            'body' => $http_client->resp_body,
73            'error' => $http_client->error,
74            'status' => $http_client->status,
75        );
76    }
77
78    /**
79     * Sends a notify mail on new linkback
80     *
81     * @param  string $ID       id of the wiki page for which the
82     *                          linkback was received
83     * @param  array  $comment  data array of the new linkback
84     *
85     * @author Andreas Gohr <andi@splitbrain.org>
86     * @author Esther Brunner <wikidesign@gmail.com>
87     * @author Gina Haeussge <osd@foosel.net>
88     */
89    function notify($ID, $linkback) {
90        global $conf;
91
92        if (!$conf['notify'])
93            return;
94        $to = $conf['notify'];
95        $text = io_readFile($this->localFN('subscribermail'));
96
97        $search = array (
98            '@PAGE@',
99            '@TITLE@',
100            '@DATE@',
101            '@URL@',
102            '@TEXT@',
103            '@UNSUBSCRIBE@',
104            '@DOKUWIKIURL@',
105            '@PAGEURL@',
106
107        );
108        $replace = array (
109            $ID,
110            $conf['title'],
111            strftime($conf['dformat'], $linkback['received']),
112            $linkback['url'],
113            $linkback['excerpt'],
114            wl($ID, 'do=unsubscribe', true, '&'),
115            DOKU_URL,
116            wl($ID, '', true),
117        );
118        $text = str_replace($search, $replace, $text);
119
120        $subject = '[' . $conf['title'] . '] ' . $this->getLang('mail_newlinkback');
121
122        mail_send($to, $subject, $text, $conf['mailfrom'], '');
123
124    }
125
126    /**
127     * Adds an entry to the linkbacks changelog
128     *
129     * @author Esther Brunner <wikidesign@gmail.com>
130     * @author Ben Coburn <btcoburn@silicodon.net>
131     * @author Gina Haeussge <osd@foosel.net>
132     */
133    function addLogEntry($date, $id, $type = 'cl', $summary = '', $extra = '') {
134        global $conf;
135
136        $changelog = $conf['metadir'] . '/_linkbacks.changes';
137
138        if (!$date)
139            $date = time(); //use current time if none supplied
140        $remote = $_SERVER['REMOTE_ADDR'];
141        $user = $_SERVER['REMOTE_USER'];
142
143        $strip = array (
144            "\t",
145            "\n"
146        );
147        $logline = array (
148            'date' => $date,
149            'ip' => $remote,
150            'type' => str_replace($strip,
151            '',
152            $type
153        ), 'id' => $id, 'user' => $user, 'sum' => str_replace($strip, '', $summary), 'extra' => str_replace($strip, '', $extra));
154
155        // add changelog line
156        $logline = implode("\t", $logline) . "\n";
157        io_saveFile($changelog, $logline, true); //global changelog cache
158        $this->_trimRecentCommentsLog($changelog);
159    }
160
161    /**
162     * Trims the recent comments cache to the last $conf['changes_days'] recent
163     * changes or $conf['recent'] items, which ever is larger.
164     * The trimming is only done once a day.
165     *
166     * @author Ben Coburn <btcoburn@silicodon.net>
167     */
168    function _trimRecentCommentsLog($changelog) {
169        global $conf;
170
171        if (@ file_exists($changelog) && (filectime($changelog) + 86400) < time() && !@ file_exists($changelog .
172            '_tmp')) {
173
174            io_lock($changelog);
175            $lines = file($changelog);
176            if (count($lines) < $conf['recent']) {
177                // nothing to trim
178                io_unlock($changelog);
179                return true;
180            }
181
182            io_saveFile($changelog . '_tmp', ''); // presave tmp as 2nd lock
183            $trim_time = time() - $conf['recent_days'] * 86400;
184            $out_lines = array ();
185
186            $linecount = count($lines);
187            for ($i = 0; $i < $linecount; $i++) {
188                $log = parseChangelogLine($lines[$i]);
189                if ($log === false)
190                    continue; // discard junk
191                if ($log['date'] < $trim_time) {
192                    $old_lines[$log['date'] . ".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions)
193                } else {
194                    $out_lines[$log['date'] . ".$i"] = $lines[$i]; // definitely keep these lines
195                }
196            }
197
198            // sort the final result, it shouldn't be necessary,
199            // however the extra robustness in making the changelog cache self-correcting is worth it
200            ksort($out_lines);
201            $extra = $conf['recent'] - count($out_lines); // do we need extra lines do bring us up to minimum
202            if ($extra > 0) {
203                ksort($old_lines);
204                $out_lines = array_merge(array_slice($old_lines, - $extra), $out_lines);
205            }
206
207            // save trimmed changelog
208            io_saveFile($changelog . '_tmp', implode('', $out_lines));
209            @ unlink($changelog);
210            if (!rename($changelog . '_tmp', $changelog)) {
211                // rename failed so try another way...
212                io_unlock($changelog);
213                io_saveFile($changelog, implode('', $out_lines));
214                @ unlink($changelog . '_tmp');
215            } else {
216                io_unlock($changelog);
217            }
218            return true;
219        }
220    }
221
222    function addProcessLogEntry($data) {
223        global $conf;
224
225        io_saveFile($conf['cachedir'].'/linkback.log',join("\n",$data)."\n\n",true);
226    }
227
228}
229