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