1<?php
2
3/**
4 * DokuWiki Plugin authorstats (Syntax 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();
13if (!defined("DOKU_LF")) define("DOKU_LF", "\n");
14if (!defined("DOKU_TAB")) define("DOKU_TAB", "\t");
15if (!defined("DOKU_PLUGIN")) define("DOKU_PLUGIN", DOKU_INC . "lib/plugins/");
16
17class syntax_plugin_authorstats extends DokuWiki_Syntax_Plugin
18{
19    var $helpers = null;
20
21    public function __construct()
22    {
23        $this->helpers = $this->loadHelper("authorstats", true);
24    }
25
26    public function getType()
27    {
28        return "substition";
29    }
30
31    public function getPType()
32    {
33        return "stack";
34    }
35
36    public function getSort()
37    {
38        return 371;
39    }
40
41    public function connectTo($mode)
42    {
43        $this->Lexer->addSpecialPattern("<AUTHORSTATS>", $mode, "plugin_authorstats");
44        $this->Lexer->addSpecialPattern("<AUTHORSTATS [0-9]+>", $mode, "plugin_authorstats");
45        $this->Lexer->addSpecialPattern("<AUTHORSTATS YEARGRAPH>", $mode, "plugin_authorstats");
46        $this->Lexer->addSpecialPattern("<AUTHORSTATS YEARGRAPH\s+\d*\s*\w*>", $mode, "plugin_authorstats");
47    }
48
49    public function handle($match, $state, $pos, Doku_Handler $handler)
50    {
51        return array($match);
52    }
53
54    public function render($mode, Doku_Renderer $renderer, $data)
55    {
56
57        if ($mode == "metadata") {
58            $renderer->meta["authorstats-enabled"] = 1;
59            return true;
60        }
61
62        if ($mode == "xhtml") {
63            if (preg_match("/<AUTHORSTATS (?P<months>[0-9]+)>/", $data[0], $matches)) {
64                $renderer->doc .= $this->_getMonthlyStatsTable(intval($matches[1]));
65            } else if (preg_match("/<AUTHORSTATS YEARGRAPH\s*(?P<years>[0-9]+)*\s*(?P<sort>(:?asc|ascending|desc|descending|rev|reverse)*)>/", $data[0], $matches)) {
66                $renderer->doc .= $this->getYearGraph($matches);
67            } else {
68                $renderer->doc .= $this->_getStatsTable();
69            }
70        }
71    }
72
73    // Returns the number of author"s Contrib for a number of months
74    function _getLastMonthsContrib($author, $months)
75    {
76        $m = array();
77        $sum = 0;
78        // Get an array of months in the format used eg. 201208, 201209, 201210
79        for ($i = $months - 1; $i >= 0; $i--)
80            array_push($m, date("Ym", strtotime("-" . $i . " Months")));
81
82        // Sum the Contrib
83        foreach ($m as $month) {
84            if (array_key_exists($month, $author["pm"])) {
85                $sum += intval($author["pm"][$month]);
86            }
87        }
88        return $sum;
89    }
90
91    function _sortByContrib($a, $b)
92    {
93        return $this->_getTotalContrib($a) <= $this->_getTotalContrib($b) ? 1 : -1;
94    }
95
96    function _getTotalContrib($a)
97    {
98        return (intval($a["C"]) + intval($a["E"]) + intval($a["e"]) + intval($a["D"]) + intval($a["R"]));
99    }
100
101    function _sortByLastMonthsContrib($a, $b)
102    {
103        return $a["lmc"] >= $b["lmc"] ? -1 : 1;
104    }
105
106    function _getMonthlyContrib($authors, $yearmonth)
107    {
108        $sum = 0;
109        foreach ($authors as $author) {
110            if (array_key_exists($yearmonth, $author["pm"])) {
111                $sum += intval($author["pm"][$yearmonth]);
112            }
113        }
114        return $sum;
115    }
116
117    function getYearGraph($inopts)
118    {
119        global $conf;
120        if ($conf['allowdebug']) $start_time = microtime(true);
121        $output = "<h3>" . $this->getLang("yearly-contrib") . "</h3>";
122        $data = $this->helpers->readJSON();
123        $authors = $data["authors"];
124        if (!$authors) return $this->getLang("no-stats");
125        $totalpm = array();
126        $labels = array();
127
128        $max_months = 12;
129        if (isset($inopts["years"]) && $inopts["years"] > 0) {
130            $max_months = 12 * $inopts["years"];
131        }
132        for ($i = 0; $i <= $max_months; $i++) {
133            array_push($totalpm, $this->_getMonthlyContrib($authors, date("Ym", strtotime("-$i months"))));
134            array_push($labels,  date("Y-M", strtotime("-$i months")));
135        }
136
137        $totalpm = array_reverse($totalpm);       // For some odd reason the charting tool needs this is the reverse order of the labels...
138        if (isset($inopts["sort"])) {
139            if (preg_match("/^(desc|descending|rev|reverse)$/", $inopts["sort"])) {    // Reverse the sort order from the default
140                $totalpm = array_reverse($totalpm);
141                $labels  = array_reverse($labels);
142            }
143        }
144
145        // Append the parameters for the Axes Titles
146        $url  = "https://chart.googleapis.com/chart";
147        $url .= "?cht=bhs";                                                                // Chart type; https://developers.google.com/chart/image/docs/gallery/chart_gall
148        $url .= "&chs=500x600";                                                            // Chart size (width x height); The overall size is very limited, max total pixel has to be less than 300k.
149        $url .= "&chxt=y,y,x,x";                                                           // Visible axes
150        $url .= "&chco=0000F0";                                                            // Series colors
151        $url .= "&chds=a";                                                                 // Scale for text format with custom range; a == automatic scaling
152        $url .= "&chbh=a";                                                                 // Bar Width and Spacing; a == bars will resize to fit in the chart
153        $url .= "&chxr=0,1,12|1,0,100|3,0,100";                                            // Axis ranges
154        $url .= "&chxp=1,2,3,4,5,6,7,8,9,10,11,12|1,50|3,50";                              // Axis label positions
155        $url .= "&chxl=0:|" . implode("|", $labels) . "|1:|Yr-Mon|3:|Num_of_Contributions";  // Axis labels
156        $url .= "&chd=t:" . implode(",", $totalpm);                                           // Chart data string
157        if ($conf['allowdebug']) {
158            $end_time = microtime(true);
159            $execution_time = ($end_time - $start_time);
160            dbglog(__FUNCTION__ . " time:" . $execution_time, "AUTHORSTATS PLUGIN");
161        }
162        return $output . "<img src=\"" . $url . "\">";
163    }
164
165    function _makeAuthorLink($author, $name, $type)
166    {
167        if (!$this->getConf("enable-pagelist")) {
168            return $author[$type];
169        }
170        $url = wl("authorstats:" . $name, array("do" => "authorstats_pages", "name" => $name, "type" => $type));
171        $link = array(
172            "href" => $url,
173            "class" => "wikilink1",
174            "tooltip" => hsc($name),
175            "title" => hsc($author[$type])
176        );
177        $link = "<a href='" . $link["href"] . "' class='" . $link["class"] . "' title='" . $link["tooltip"] . "' rel='tag'>" . $link["title"] . "</a>";
178        return $link;
179    }
180
181    // Returns the HTML table with the authors and their stats
182    function _getStatsTable()
183    {
184        global $conf;
185        if ($conf['allowdebug']) $start_time = microtime(true);
186        $output = "<h3>" . $this->getLang("gen-stats") . "</h3><table class=\"authorstats-table\"><tr><th>" . $this->getLang("name") . "</th><th>" . $this->getLang("creates") . "</th><th>" . $this->getLang("edits") . "</th><th>" . $this->getLang("minor") . "</th><th>" . $this->getLang("deletes") . "</th><th>" . $this->getLang("reverts") . "</th><th>" . $this->getLang("contrib") . "</th></tr>";
187        $authors = $this->helpers->readJSON();
188        $authors = $authors["authors"];
189        if (!$authors) return  $this->getLang("no-stats");
190        uasort($authors, array($this, "_sortByContrib"));
191        foreach ($authors as $name => $author) {
192            $dname = $this->_getUser($name);
193            if ($dname == null) continue;
194            $output .= "<tr><th>" .
195                $dname . "</th><td>" .
196                $this->_makeAuthorLink($author, $name, "C") . "</td><td>" .
197                $this->_makeAuthorLink($author, $name, "E") . "</td><td>" .
198                $this->_makeAuthorLink($author, $name, "e") . "</td><td>" .
199                $this->_makeAuthorLink($author, $name, "D") . "</td><td>" .
200                $this->_makeAuthorLink($author, $name, "R") . "</td><td>" .
201                strval($this->_getTotalContrib($author)) . "</td></tr>";
202        }
203        $output .= "</table>";
204        if ($conf['allowdebug']) {
205            $end_time = microtime(true);
206            $execution_time = ($end_time - $start_time);
207            dbglog(__FUNCTION__ . " time:" . $execution_time, "AUTHORSTATS PLUGIN");
208        }
209        return $output;
210    }
211
212    // Returns the HTML table with the authors and their Contrib for the
213    // last <$months> months
214    function _getMonthlyStatsTable($months)
215    {
216        global $conf;
217        if ($conf['allowdebug']) $start_time = microtime(true);
218        $output = "<h3>" . $this->getLang("contrib-months") . " " . $months . " " . $this->getLang("months") . "</h3><table class=\"authorstats-table\"><tr><th>" . $this->getLang("name") . "</th><th>" . $this->getLang("contrib") . "</th></tr>";
219        $authors = $this->helpers->readJSON();
220        $authors = $authors["authors"];
221        if (!$authors) return  $this->getLang("no-stats");
222        foreach ($authors as $name => $author) {
223            $authors[$name]["lmc"] = $this->_getLastMonthsContrib($author, $months);
224        }
225        uasort($authors, array($this, "_sortByLastMonthsContrib"));
226        foreach ($authors as $name => $author) {
227            if ($authors[$name]["lmc"] > 0) {
228                $dname = $this->_getUser($name);
229                if ($dname == null) continue;
230                $output .= "<tr><th>" .
231                    $dname . "</th><td>" .
232                    strval($authors[$name]["lmc"]) . "</td></tr>";
233            }
234        }
235        $output .= "</table>";
236        if ($conf['allowdebug']) {
237            $end_time = microtime(true);
238            $execution_time = ($end_time - $start_time);
239            dbglog(__FUNCTION__ . " time:" . $execution_time, "AUTHORSTATS PLUGIN");
240        }
241        return $output;
242    }
243
244    function _getUser($name)
245    {
246        global $auth;
247        $user = $auth->getUserData($name);
248        if ($user !== false and $this->getConf("show-realname")) {
249            $dname = $user["name"];
250        } else if ($this->getConf("show-profile-links")) {
251        } else if ($user !== false) {
252            $dname = $name;
253        } else {
254            // Deleted user?
255            if (!$this->getConf("show-deleted-users")) return null;
256            $dname = "<i>($name)</i>";
257        }
258        return $dname;
259    }
260}
261