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