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}