1<?php
2
3use dokuwiki\ErrorHandler;
4use dokuwiki\Extension\Plugin;
5use dokuwiki\HTTP\DokuHTTPClient;
6use dokuwiki\plugin\sqlite\SQLiteDB;
7use dokuwiki\plugin\statistics\DummyLogger;
8use dokuwiki\plugin\statistics\IgnoreException;
9use dokuwiki\plugin\statistics\IpResolverException;
10use dokuwiki\plugin\statistics\Logger;
11use dokuwiki\plugin\statistics\Query;
12use dokuwiki\plugin\statistics\StatisticsGraph;
13
14/**
15 * Statistics Plugin
16 *
17 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
18 * @author  Andreas Gohr <andi@splitbrain.org>
19 */
20class helper_plugin_statistics extends Plugin
21{
22    protected ?Query $oQuery = null;
23    protected ?StatisticsGraph $oGraph = null;
24    protected ?SQLiteDB $db = null;
25    public ?DokuHTTPClient $httpClient = null; // public for testing purposes
26
27    /**
28     * Get SQLiteDB instance
29     *
30     * @return SQLiteDB|null
31     * @throws Exception when SQLite initialization failed
32     */
33    public function getDB(): ?SQLiteDB
34    {
35        if (!$this->db instanceof SQLiteDB) {
36            if (!class_exists(SQLiteDB::class)) throw new Exception('SQLite Plugin missing');
37            $this->db = new SQLiteDB('statistics', DOKU_PLUGIN . 'statistics/db/');
38        }
39        return $this->db;
40    }
41
42
43    /**
44     * Return an instance of the query class
45     *
46     * @return Query
47     */
48    public function getQuery(): Query
49    {
50        if (is_null($this->oQuery)) {
51            $this->oQuery = new Query($this);
52        }
53        return $this->oQuery;
54    }
55
56    /**
57     * Return an instance of the logger class
58     *
59     * When the logger cannot be created for any reason a DummyLogger is returned
60     *
61     * @return Logger|DummyLogger
62     */
63    public function getLogger()
64    {
65        try {
66            return new Logger($this);
67        } catch (Exception $e) {
68            if (!$e instanceof IgnoreException) {
69                ErrorHandler::logException($e);
70            }
71
72            return new DummyLogger();
73        }
74    }
75
76    /**
77     * Return an instance of the Graph class
78     *
79     * @return StatisticsGraph
80     */
81    public function getGraph($from, $to, $width, $height)
82    {
83        if (is_null($this->oGraph)) {
84            $this->oGraph = new StatisticsGraph($this, $from, $to, $width, $height);
85        }
86        return $this->oGraph;
87    }
88
89    /**
90     * Just send a 1x1 pixel blank gif to the browser
91     *
92     * @called from dispatch.php
93     *
94     * @author Andreas Gohr <andi@splitbrain.org>
95     * @author Harry Fuecks <fuecks@gmail.com>
96     */
97    public function sendGIF($transparent = true)
98    {
99        if ($transparent) {
100            $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7');
101        } else {
102            $img = base64_decode('R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=');
103        }
104        header('Content-Type: image/gif');
105        header('Content-Length: ' . strlen($img));
106        header('Connection: Close');
107        echo $img;
108        flush();
109        // Browser should drop connection after this
110        // Thinks it got the whole image
111    }
112
113    /**
114     * Return the location information for an IP address
115     *
116     * @throws IpResolverException
117     * @noinspection HttpUrlsUsage
118     */
119    public function resolveIP($ip)
120    {
121        $http = $this->httpClient ?: new DokuHTTPClient();
122        $http->timeout = 7;
123        $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only
124
125        if (!$json) {
126            throw new IpResolverException('Failed talk to ip-api.com.');
127        }
128        try {
129            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
130        } catch (\JsonException $e) {
131            throw new IpResolverException('Failed to decode JSON from ip-api.com.', $e->getTrace(), 0, $e);
132        }
133        if (!isset($data['status'])) {
134            throw new IpResolverException('Invalid ip-api.com result for' . $ip, $data);
135        }
136        // we do not check for 'success' status here. when the API can't resolve the IP we still log it
137        // without location data, so we won't re-query it in the next 30 days.
138
139        return $data;
140    }
141}
142