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 = new Mailer();
107        $mail->to($to);
108        $mail->subject($subject);
109        $mail->setBody($text);
110        $mail->send();
111    }
112
113    /**
114     * Adds an entry to the linkbacks changelog
115     *
116     * @author Esther Brunner <wikidesign@gmail.com>
117     * @author Ben Coburn <btcoburn@silicodon.net>
118     * @author Gina Haeussge <osd@foosel.net>
119     */
120    function addLogEntry($date, $id, $type = 'cl', $summary = '', $extra = '') {
121        global $conf;
122
123        $changelog = $conf['metadir'] . '/_linkbacks.changes';
124
125        if (!$date) {
126            $date = time(); //use current time if none supplied
127        }
128        $remote = $_SERVER['REMOTE_ADDR'];
129        $user = $_SERVER['REMOTE_USER'];
130
131        $strip = array (
132            "\t",
133            "\n"
134        );
135        $logline = array (
136            'date' => $date,
137            'ip' => $remote,
138            'type' => str_replace($strip,
139            '',
140            $type
141        ), 'id' => $id, 'user' => $user, 'sum' => str_replace($strip, '', $summary), 'extra' => str_replace($strip, '', $extra));
142
143        // add changelog line
144        $logline = implode("\t", $logline) . "\n";
145        io_saveFile($changelog, $logline, true); //global changelog cache
146        $this->_trimRecentCommentsLog($changelog);
147    }
148
149    /**
150     * Trims the recent comments cache to the last $conf['changes_days'] recent
151     * changes or $conf['recent'] items, which ever is larger.
152     * The trimming is only done once a day.
153     *
154     * @author Ben Coburn <btcoburn@silicodon.net>
155     */
156    function _trimRecentCommentsLog($changelog) {
157        global $conf;
158
159        if (@ file_exists($changelog) && (filectime($changelog) + 86400) < time() && !@ file_exists($changelog .
160            '_tmp')) {
161
162            io_lock($changelog);
163            $lines = file($changelog);
164            if (count($lines) < $conf['recent']) {
165                // nothing to trim
166                io_unlock($changelog);
167                return true;
168            }
169
170            io_saveFile($changelog . '_tmp', ''); // presave tmp as 2nd lock
171            $trim_time = time() - $conf['recent_days'] * 86400;
172            $out_lines = array ();
173
174            $linecount = count($lines);
175            for ($i = 0; $i < $linecount; $i++) {
176                $log = parseChangelogLine($lines[$i]);
177                if ($log === false)
178                    continue; // discard junk
179                if ($log['date'] < $trim_time) {
180                    $old_lines[$log['date'] . ".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions)
181                } else {
182                    $out_lines[$log['date'] . ".$i"] = $lines[$i]; // definitely keep these lines
183                }
184            }
185
186            // sort the final result, it shouldn't be necessary,
187            // however the extra robustness in making the changelog cache self-correcting is worth it
188            ksort($out_lines);
189            $extra = $conf['recent'] - count($out_lines); // do we need extra lines do bring us up to minimum
190            if ($extra > 0) {
191                ksort($old_lines);
192                $out_lines = array_merge(array_slice($old_lines, - $extra), $out_lines);
193            }
194
195            // save trimmed changelog
196            io_saveFile($changelog . '_tmp', implode('', $out_lines));
197            @ unlink($changelog);
198            if (!rename($changelog . '_tmp', $changelog)) {
199                // rename failed so try another way...
200                io_unlock($changelog);
201                io_saveFile($changelog, implode('', $out_lines));
202                @ unlink($changelog . '_tmp');
203            } else {
204                io_unlock($changelog);
205            }
206            return true;
207        }
208    }
209
210    function addProcessLogEntry($data) {
211        global $conf;
212
213        io_saveFile($conf['cachedir'].'/linkback.log',join("\n",$data)."\n\n",true);
214    }
215
216}
217