1<?php 2 3/** 4 * DokuWiki Plugin authorstats (Action Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author George Chatzisofroniou <sophron@latthi.com> 8 * @author Constantinos Xanthopoulos <conx@xanthopoulos.info> 9 */ 10 11// must be run within Dokuwiki 12if (!defined("DOKU_INC")) die(); 13 14if (!defined("DOKU_LF")) define("DOKU_LF", "\n"); 15if (!defined("DOKU_TAB")) define("DOKU_TAB", "\t"); 16if (!defined("DOKU_PLUGIN")) define("DOKU_PLUGIN", DOKU_INC . "lib/plugins/"); 17 18class action_plugin_authorstats extends DokuWiki_Action_Plugin 19{ 20 var $helpers = null; 21 22 function __construct() 23 { 24 $this->helpers = $this->loadHelper("authorstats", true); 25 if (!$this->helpers->statsFileExists()) 26 $this->_initializeData(); 27 } 28 29 var $supportedModes = array("xhtml", "metadata"); 30 31 function register(Doku_Event_Handler $controller) 32 { 33 $controller->register_hook("ACTION_SHOW_REDIRECT", "BEFORE", $this, "_updateSavedStats"); 34 $controller->register_hook("PARSER_CACHE_USE", "BEFORE", $this, "_cachePrepare"); 35 $controller->register_hook("ACTION_ACT_PREPROCESS", "BEFORE", $this, "_allow_show_author_pages"); 36 $controller->register_hook("TPL_ACT_UNKNOWN", "BEFORE", $this, "_show_author_pages"); 37 } 38 39 function _allow_show_author_pages(Doku_Event $event, $param) 40 { 41 if ($event->data != "authorstats_pages") return; 42 $event->preventDefault(); 43 } 44 45 function _show_author_pages(Doku_Event $event, $param) 46 { 47 if ($event->data != "authorstats_pages") return; 48 $event->preventDefault(); 49 $flags = explode(",", str_replace(" ", "", $this->getConf("pagelist_flags"))); 50 $name = hsc($_REQUEST["name"]); 51 $usd = $this->helpers->readUserJSON($name); 52 $ids = $usd["pages"][$_REQUEST["type"]]; 53 54 if ((!$pagelist = $this->loadHelper("pagelist"))) { 55 return false; 56 } 57 58 /* @var helper_plugin_pagelist $pagelist */ 59 $pagelist->setFlags($flags); 60 $pagelist->startList(); 61 foreach ($ids as $key => $value) { 62 $page = array("id" => urldecode($key)); 63 $pagelist->addPage($page); 64 } 65 $type = ""; 66 switch ($_REQUEST["type"]) { 67 case "C": 68 $type = "Creates"; 69 break; 70 case "E": 71 $type = "Edits"; 72 break; 73 case "e": 74 $type = "Minor edits"; 75 break; 76 case "D": 77 $type = "Deletes"; 78 break; 79 case "R": 80 $type = "Reverts"; 81 break; 82 } 83 print "<h1>Pages[" . $type . "]: " . userlink($_REQUEST["name"], true) . "</h1>" . DOKU_LF; 84 print "<div class=\"level1\">" . DOKU_LF; 85 print $pagelist->finishList(); 86 print "</div>" . DOKU_LF; 87 } 88 89 function _initializeData() 90 { 91 global $conf; 92 if ($conf['allowdebug']) $start_time = microtime(true); 93 global $conf; 94 $dir = $conf["metadir"] . "/"; 95 96 $this->helpers->createDirIfMissing("data"); 97 // Delete JSON files 98 $lastchange = (-1 * PHP_INT_MAX) - 1; 99 array_map("unlink", glob($this->helpers->basedir . "/*.json")); 100 $sd = array(); 101 // Update everything 102 $files = $this->_getChangeLogs($dir); 103 foreach ($files as $file) { 104 $this->_updateStats($file, $sd, $lastchange, false); 105 } 106 if ($conf['allowdebug']) { 107 $end_time = microtime(true); 108 $execution_time = ($end_time - $start_time); 109 dbglog(__FUNCTION__ . " time:" . $execution_time, "AUTHORSTATS PLUGIN"); 110 } 111 } 112 113 // Updates the saved statistics by checking the last lines 114 // in the /data/meta/ directory 115 function _updateSavedStats(Doku_Event $event) 116 { 117 global $conf; 118 if ($conf['allowdebug']) $start_time = microtime(true); 119 120 // Read saved data from JSON file 121 $sd = $this->helpers->readJSON(); 122 // Get last change 123 $lastchange = empty($sd) ? (-1 * PHP_INT_MAX) - 1 : (int) $sd["lastchange"]; 124 $file = $this->_getChangesFileForPage($event->data["id"]); 125 $this->_updateStats($file, $sd, $lastchange); 126 if ($conf['allowdebug']) { 127 $end_time = microtime(true); 128 $execution_time = ($end_time - $start_time); 129 dbglog(__FUNCTION__ . " time:" . $execution_time, "AUTHORSTATS PLUGIN"); 130 } 131 } 132 133 // If the page is no more recent than the modification of the JSON file, refresh the page. 134 public function _cachePrepare(&$event, $param) 135 { 136 $cache = &$event->data; 137 138 if (!isset($cache->page)) return; 139 if (!isset($cache->mode) || !in_array($cache->mode, $this->supportedModes)) return; 140 141 $enabled = p_get_metadata($cache->page, "authorstats-enabled"); 142 if (isset($enabled)) { 143 if (@filemtime($cache->cache) < @filemtime($this->helpers->summaryfile)) { 144 $event->preventDefault(); 145 $event->stopPropagation(); 146 $event->result = false; 147 } 148 } 149 } 150 151 function _getChangeLogs($dir, &$files = array()) 152 { 153 $files = $this->helpers->rglob($dir . "[^_]*.changes", GLOB_NOSORT); 154 return $files; 155 } 156 157 function _parseChange($line) 158 { 159 $record = array(); 160 $parts = explode(DOKU_TAB, $line); 161 if ($parts && $parts[4] != "") { 162 $record["timestamp"] = $parts[0]; 163 $record["type"] = $parts[2]; 164 $record["author"] = $parts[4]; 165 $record["date"] = date("Ym", $parts[0]); 166 } 167 return $record; 168 } 169 170 function _getChangesFileForPage($page_id) 171 { 172 global $conf; 173 $page = preg_replace("[:]", "/", $page_id); 174 return $conf["metadir"] . "/" . $page . ".changes"; 175 } 176 177 function _updateStats($change_file, &$sd, &$lastchange, $skip = true) 178 { 179 global $conf; 180 $metadir = $conf["metadir"] . "/"; 181 $newlast = $lastchange; 182 if (is_readable($change_file)) 183 $file_contents = array_reverse(file($change_file)); 184 else { 185 dbglog("ERROR: " . __FUNCTION__ . " - Couldn't open file:" . var_export($change_file, true), "AUTHORSTATS PLUGIN"); 186 return; 187 } 188 189 foreach ($file_contents as $line) { 190 $r = $this->_parseChange($line); 191 192 if ($r["author"] == "") 193 continue; 194 195 if ($r["timestamp"] <= $lastchange && $skip) 196 break; 197 198 // Update the last if there is a more recent change 199 $newlast = max($newlast, $r["timestamp"]); 200 201 // If the author is not in the array, initialize their stats 202 if (!isset($sd["authors"][$r["author"]])) { 203 $sd["authors"][$r["author"]]["C"] = 0; 204 $sd["authors"][$r["author"]]["E"] = 0; 205 $sd["authors"][$r["author"]]["e"] = 0; 206 $sd["authors"][$r["author"]]["D"] = 0; 207 $sd["authors"][$r["author"]]["R"] = 0; 208 $sd["authors"][$r["author"]]["pm"] = array(); 209 } else { 210 // Initialize month if doesn't exist 211 // else increment it 212 if (!isset($sd["authors"][$r["author"]]["pm"][$r["date"]])) 213 $sd["authors"][$r["author"]]["pm"][$r["date"]] = 1; 214 else 215 $sd["authors"][$r["author"]]["pm"][$r["date"]]++; 216 } 217 $sd["authors"][$r["author"]][$r["type"]]++; 218 $usd = $this->helpers->readUserJSON($r["author"]); 219 $key = str_replace($metadir, "", $change_file); 220 $key = str_replace(".changes", "", $key); 221 $key = str_replace("/", ":", $key); 222 $usd["pages"][$r["type"]][$key] = 1; 223 $this->helpers->saveUserJSON($r["author"], $usd); 224 } 225 $lastchange = $newlast; 226 $sd["lastchange"] = $newlast; 227 $this->helpers->saveJSON($sd); 228 } 229} 230