1<?php
2/**
3 * Download Counter Action Plugin
4 *
5 * @license    GPLv3 (http://www.gnu.org/licenses/gpl.html)
6 * @link       http://www.dokuwiki.org/plugin:dlcount
7 * @author     Markus Birth <markus@birth-online.de>
8 */
9
10if(!defined('DOKU_INC')) die();
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'action.php');
13
14class action_plugin_dlcount extends DokuWiki_Action_Plugin {
15
16    const DATADIR = '_media';   // below $conf['metadir'], no trailing slash
17    const SUFFIX  = '.meta';
18
19    /**
20     * return some info
21     */
22    function getInfo(){
23        return confToHash(dirname(__FILE__).'/INFO.txt');
24    }
25
26    /*
27     * plugin should use this method to register its handlers with the dokuwiki's event controller
28     */
29    function register(&$controller) {
30        $controller->register_hook('MEDIA_SENDFILE', 'BEFORE', $this, '_countdl');
31        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, '_showcount');
32        $controller->register_hook('MEDIA_DELETE_FILE', 'AFTER', $this, '_delcounter');
33        $controller->register_hook('MEDIA_UPLOAD_FINISH', 'BEFORE', $this, '_delcounter2');
34    }
35
36    function _delcounter(&$event, $param) {
37        $metafn = $this->metaFnFromFullPath($event->data['path']);
38        if (file_exists($metafn)) @unlink($metafn);
39    }
40
41    function _delcounter2(&$event, $param) {
42        $metafn = $this->metaFnFromFullPath($event->data[1]);
43        // TODO: Maybe keep counter if updating file ($event->data[4] == 1, i.e. overwrite)
44        if (file_exists($metafn)) @unlink($metafn);
45    }
46
47    function _showcount(&$event, $param) {
48        global $conf;
49        if (!$this->getConf('show_dlcount') && !$this->getConf('show_filesize')) return;   // return if there's nothing to do
50        $html = &$event->data;
51        $matchct = preg_match_all('/\<a href="([^"]*)" class="[^"]*mediafile[^"]*"[^\>]*>[^\<]*\<\/a\>/', $html, $matches, PREG_OFFSET_CAPTURE|PREG_PATTERN_ORDER);
52        if ($matchct == 0) return;   // do nothing
53        $newhtml = '';
54        $lastoffset = 0;
55        //print_r($matches); die();
56        foreach ($matches[0] as $i=>$match) {
57            $href = $matches[1][$i][0];
58            $fn = false;
59            if (strpos($href, 'fetch.php?') !== false) {
60                // no rewrite (http://wiki.birth-online.de/lib/exe/fetch.php?media=software:php:dlcount.tar.gz)
61                $fn = '/' . substr($href, strpos($href, '?media=')+strlen('?media='));
62                $fn = str_replace(':', '/', $fn);
63            } elseif (strpos($href, 'fetch.php/') !== false) {
64                // no rewrite with useslash (http://wiki.birth-online.de/lib/exe/fetch.php/software/php/dlcount.tar.gz)
65                // THANKS TO: TMH 2009-02-22 via dokuwiki.org
66                $fn = '/' . substr($href, strpos($href, 'fetch.php/')+strlen('fetch.php/'));
67                $fn = str_replace(':', '/', $fn);
68            } else {
69                // rewrite (http://wiki.birth-online.de/_media/software/php/dlcount.tar.gz)
70                $fn = substr($href, strpos($href, '/_media/')+strlen('/_media/')-1);
71            }
72            $metafn = $conf['metadir'] . '/' . self::DATADIR . $fn . self::SUFFIX;
73            $meta = array('dlcount' => 0);
74            $txt = array();
75            if (file_exists($metafn)) $meta = unserialize(io_readFile($metafn, false));
76            if ($this->getConf('show_filesize')) $txt['filesize'] = $this->size_translate(filesize($conf['mediadir'] . '/' . $fn));
77            if ($this->getConf('show_lastmod')) {
78                $fmod = filemtime($conf['mediadir'] . '/' . $fn);
79                $txt['lastmod'] = '<acronym title="Modified: ' . date('Y-m-d H:i.s', $fmod) . '">' . reset(explode(' ', $this->time_translate(time()-$fmod))) . ' ago</acronym>';
80            }
81            if ($this->getConf('show_dlcount')) {
82                if ($meta['dlcount'] != 1) $s = 's'; else $s = '';
83                $txt['dlcount'] = $meta['dlcount'] . ' download' . $s;
84                if (isset($meta['lastdl'])) $txt['dlcount'] = '<acronym title="Last download: ' . $this->time_translate(time()-$meta['lastdl']). ' ago.">' . $txt['dlcount'] . '</acronym>';
85            }
86            $txt = ' (' . implode(', ', $txt) . ')';
87            $afteroffset = $match[1] + strlen($match[0]);
88            $newhtml .= substr($html, $lastoffset, $afteroffset-$lastoffset) . $txt;
89            $lastoffset = $afteroffset;
90        }
91        $newhtml .= substr($html, $lastoffset);
92        $html = $newhtml;
93    }
94
95    function _countdl(&$event, $param) {
96        if ($event->data['download'] != 1) return;   // skip embedded images (we don't want to count these)
97        $metafn = $this->metaFnFromFullPath($event->data['file']);
98
99        // read metafile
100        $ctr = 0;
101        $meta = array();
102        if (file_exists($metafn)) {
103            $metastring = io_readFile($metafn, false);
104            $meta = unserialize($metastring);
105            if (isset($meta['dlcount'])) {
106                $ctr = $meta['dlcount'];
107            }
108        }
109
110        // advance counter
111        $ctr++;
112
113        // more statistics
114        $meta['lastdl'] = time();
115        $meta['dlusers'][] = array(
116            'Time' => time(),
117            'IP' => $this->getClientIP(),
118            'Referer' => $_SERVER['HTTP_REFERER'],
119            'UserAgent' => $_SERVER['HTTP_USER_AGENT'],
120        );
121        $meta['dlusers'] = array_slice($meta['dlusers'], -50);   // only keep last 50 downloaders
122
123        // output to metafile
124        io_makeFileDir($metafn);
125        $meta['dlcount'] = $ctr;
126        io_saveFile($metafn, serialize($meta));
127    }
128
129    /**
130     * Returns the filename of the META file for specified MEDIA file
131     * @global array $conf Global Configuration
132     * @param string $fullpath Path to MEDIA file
133     * @return string Path to META file
134     */
135    function metaFnFromFullPath($fullpath) {
136        global $conf;
137        $mediadir = realpath($conf['mediadir']);
138        $fn = str_replace($mediadir, '', $fullpath);
139        return $conf['metadir'] . '/' . self::DATADIR . $fn . self::SUFFIX;
140    }
141
142    /**
143     * Checks for a valid non-127-IP
144     * @param string $ip IP-Address
145     * @return bool TRUE if IP is valid and not localnet, FALSE if invalid or local
146     */
147    function isValidIP($ip) {
148        $ip = trim($ip);
149        $isvalid = true;
150        $i = split('\.', $ip, 4);
151        $iv = array();
152
153        foreach ($i as $key=>$val) {
154            $iv[$key] = intval($val);
155            if (strlen($val)>3 || strlen($val)<=0 || $iv[$key]<0 || $iv[$key]>255) {
156                $isvalid = false;
157            }
158        }
159        if ($iv[0]==0) {
160            $isvalid = false;
161        }
162        if ($iv[0]==127 && $iv[1]==0 && $iv[2]==0 && $iv[3]==1) {
163            $isvalid = false;
164        }
165        return $isvalid;
166    }
167
168    /**
169     * Returns the IP of the accessing client PC, if possible
170     * @return string|bool The client IP or FALSE on error.
171     */
172    function getClientIP() {
173        $addr = $_SERVER['HTTP_X_FORWARDED_FOR'];
174        if (!empty($addr) && $this->isValidIP($addr)) return $addr;
175        $addr = $_SERVER['HTTP_CLIENT_IP'];
176        if (!empty($addr) && $this->isValidIP($addr)) return $addr;
177        $addr = $_SERVER['REMOTE_ADDR'];
178        if (!empty($addr) && $this->isValidIP($addr)) return $addr;
179        return false;
180    }
181
182    // BEGIN: borrowed and modified from http://de3.php.net/manual/en/function.filesize.php
183    function size_translate($filesize) {
184        $array = array(
185            'TiB' => 1024 * 1024 * 1024 * 1024,
186            'GiB' => 1024 * 1024 * 1024,
187            'MiB' => 1024 * 1024,
188            'KiB' => 1024,
189        );
190        if($filesize <= 1024) {
191            return $filesize . ' B';
192        }
193        foreach ($array as $name=>$size) {
194            if($filesize >= $size) {
195                return round((round($filesize / $size * 100) / 100), 2) . ' ' . $name;
196            }
197        }
198        return $filesize;
199    }
200    // END: borrowed and modified from http://de3.php.net/manual/en/function.filesize.php
201
202
203    // BEGIN: borrowed and modified from http://de3.php.net/manual/en/function.filesize.php
204    function time_translate($seconds) {
205        $array = array(
206            'y' => 60 * 60 * 24 * 365.25,
207            'M' => 60 * 60 * 24 * 30.5,
208            'w' => 60 * 60 * 24 * 7,
209            'd' => 60 * 60 * 24,
210            'h' => 60 * 60,
211            'm' => 60,
212            's' => 1,
213        );
214        foreach ($array as $name=>$secs) {
215            if ($seconds < $secs && $secs != end($array)) continue;
216            $resv = floor($seconds / $secs);
217            $res .= ' ' . $resv . $name;
218            $seconds -= $resv*$secs;
219        }
220        return trim($res);
221    }
222    // END: borrowed and modified from http://de3.php.net/manual/en/function.filesize.php
223
224}