1<?php 2 3namespace dokuwiki\plugin\statistics; 4 5use helper_plugin_statistics; 6 7class Query 8{ 9 protected $hlp; 10 protected $from; 11 protected $to; 12 13 public function __construct(helper_plugin_statistics $hlp) 14 { 15 $this->hlp = $hlp; 16 $today = date('Y-m-d'); 17 $this->setTimeFrame($today, $today); 18 } 19 20 /** 21 * Set the time frame for all queries 22 */ 23 public function setTimeFrame($from, $to) 24 { 25 // fixme add better sanity checking here: 26 $from = preg_replace('/[^\d\-]+/', '', $from); 27 $to = preg_replace('/[^\d\-]+/', '', $to); 28 if (!$from) $from = date('Y-m-d'); 29 if (!$to) $to = date('Y-m-d'); 30 31 $this->from = $from. ' 00:00:00'; 32 $this->to = $to. ' 23:59:59'; 33 } 34 35 /** 36 * Return some aggregated statistics 37 */ 38 public function aggregate() 39 { 40 $data = []; 41 42 $sql = "SELECT ref_type, COUNT(*) as cnt 43 FROM access as A 44 WHERE A.dt >= ? AND A.dt <= ? 45 AND ua_type = ? 46 GROUP BY ref_type"; 47 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 48 49 if (is_array($result)) foreach ($result as $row) { 50 if ($row['ref_type'] == 'search') $data['search'] = $row['cnt']; 51 if ($row['ref_type'] == 'external') $data['external'] = $row['cnt']; 52 if ($row['ref_type'] == 'internal') $data['internal'] = $row['cnt']; 53 if ($row['ref_type'] == '') $data['direct'] = $row['cnt']; 54 } 55 56 // general user and session info 57 $sql = "SELECT COUNT(DISTINCT session) as sessions, 58 COUNT(session) as views, 59 COUNT(DISTINCT user) as users, 60 COUNT(DISTINCT uid) as visitors 61 FROM access as A 62 WHERE A.dt >= ? AND A.dt <= ? 63 AND ua_type = ?"; 64 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 65 66 $data['users'] = max($result[0]['users'] - 1, 0); // subtract empty user 67 $data['sessions'] = $result[0]['sessions']; 68 $data['pageviews'] = $result[0]['views']; 69 $data['visitors'] = $result[0]['visitors']; 70 71 // calculate bounce rate 72 if ($data['sessions']) { 73 $sql = "SELECT COUNT(*) as cnt 74 FROM session as A 75 WHERE A.dt >= ? AND A.dt <= ? 76 AND views = ?"; 77 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 1]); 78 $data['bouncerate'] = $result[0]['cnt'] * 100 / $data['sessions']; 79 $data['newvisitors'] = $result[0]['cnt'] * 100 / $data['sessions']; 80 } 81 82 // calculate avg. number of views per session 83 $sql = "SELECT AVG(views) as cnt 84 FROM session as A 85 WHERE A.dt >= ? AND A.dt <= ?"; 86 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 87 $data['avgpages'] = $result[0]['cnt']; 88 89 /* not used currently 90 $sql = "SELECT COUNT(id) as robots 91 FROM ".$this->hlp->prefix."access as A 92 WHERE $tlimit 93 AND ua_type = 'robot'"; 94 $result = $this->hlp->runSQL($sql); 95 $data['robots'] = $result[0]['robots']; 96 */ 97 98 // average time spent on the site 99 $sql = "SELECT AVG(end - dt)/60 as time 100 FROM session as A 101 WHERE A.dt >= ? AND A.dt <= ? 102 AND dt != end 103 AND DATE(dt) = DATE(end)"; 104 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 105 $data['timespent'] = $result[0]['time']; 106 107 // logins 108 $sql = "SELECT COUNT(*) as logins 109 FROM logins as A 110 WHERE A.dt >= ? AND A.dt <= ? 111 AND (type = ? OR type = ?)"; 112 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'l', 'p']); 113 $data['logins'] = $result[0]['logins']; 114 115 // registrations 116 $sql = "SELECT COUNT(*) as registrations 117 FROM logins as A 118 WHERE A.dt >= ? AND A.dt <= ? 119 AND type = ?"; 120 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'C']); 121 $data['registrations'] = $result[0]['registrations']; 122 123 // current users 124 $sql = "SELECT COUNT(*) as current 125 FROM lastseen 126 WHERE dt >= datetime('now', '-10 minutes')"; 127 $result = $this->hlp->getDB()->queryAll($sql); 128 $data['current'] = $result[0]['current']; 129 130 return $data; 131 } 132 133 /** 134 * standard statistics follow, only accesses made by browsers are counted 135 * for general stats like browser or OS only visitors not pageviews are counted 136 */ 137 138 /** 139 * Return some trend data about visits and edits in the wiki 140 */ 141 public function dashboardviews($hours = false) 142 { 143 if ($hours) { 144 $TIME = 'strftime(\'%H\', dt)'; 145 } else { 146 $TIME = 'DATE(dt)'; 147 } 148 149 $data = []; 150 151 // access trends 152 $sql = "SELECT $TIME as time, 153 COUNT(DISTINCT session) as sessions, 154 COUNT(session) as pageviews, 155 COUNT(DISTINCT uid) as visitors 156 FROM access as A 157 WHERE A.dt >= ? AND A.dt <= ? 158 AND ua_type = ? 159 GROUP BY $TIME 160 ORDER BY time"; 161 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 162 foreach ($result as $row) { 163 $data[$row['time']]['sessions'] = $row['sessions']; 164 $data[$row['time']]['pageviews'] = $row['pageviews']; 165 $data[$row['time']]['visitors'] = $row['visitors']; 166 } 167 return $data; 168 } 169 170 public function dashboardwiki($hours = false) 171 { 172 if ($hours) { 173 $TIME = 'strftime(\'%H\', dt)'; 174 } else { 175 $TIME = 'DATE(dt)'; 176 } 177 178 $data = []; 179 180 // edit trends 181 foreach (['E', 'C', 'D'] as $type) { 182 $sql = "SELECT $TIME as time, 183 COUNT(*) as cnt 184 FROM edits as A 185 WHERE A.dt >= ? AND A.dt <= ? 186 AND type = ? 187 GROUP BY $TIME 188 ORDER BY time"; 189 $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $type]); 190 foreach ($result as $row) { 191 $data[$row['time']][$type] = $row['cnt']; 192 } 193 } 194 ksort($data); 195 return $data; 196 } 197 198 public function history($info, $interval = false) 199 { 200 if ($interval == 'weeks') { 201 $TIME = 'strftime(\'%Y\', dt), strftime(\'%W\', dt)'; 202 } elseif ($interval == 'months') { 203 $TIME = 'strftime(\'%Y-%m\', dt)'; 204 } else { 205 $TIME = 'dt'; 206 } 207 208 $mod = 1; 209 if ($info == 'media_size' || $info == 'page_size') { 210 $mod = 1024 * 1024; 211 } 212 213 $sql = "SELECT $TIME as time, 214 AVG(value)/$mod as cnt 215 FROM history as A 216 WHERE A.dt >= ? AND A.dt <= ? 217 AND info = ? 218 GROUP BY $TIME 219 ORDER BY $TIME"; 220 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $info]); 221 } 222 223 public function searchengines($start = 0, $limit = 20) 224 { 225 $sql = "SELECT COUNT(*) as cnt, engine as eflag, engine 226 FROM search as A 227 WHERE A.dt >= ? AND A.dt <= ? 228 GROUP BY engine 229 ORDER BY cnt DESC, engine" . 230 $this->mklimit($start, $limit); 231 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 232 } 233 234 public function searchphrases($extern, $start = 0, $limit = 20) 235 { 236 if ($extern) { 237 $WHERE = "engine != ?"; 238 $engineParam = 'dokuwiki'; 239 $I = ''; 240 } else { 241 $WHERE = "engine = ?"; 242 $engineParam = 'dokuwiki'; 243 $I = 'i'; 244 } 245 $sql = "SELECT COUNT(*) as cnt, query, query as ${I}lookup 246 FROM search as A 247 WHERE A.dt >= ? AND A.dt <= ? 248 AND $WHERE 249 GROUP BY query 250 ORDER BY cnt DESC, query" . 251 $this->mklimit($start, $limit); 252 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $engineParam]); 253 } 254 255 public function searchwords($extern, $start = 0, $limit = 20) 256 { 257 if ($extern) { 258 $WHERE = "engine != ?"; 259 $engineParam = 'dokuwiki'; 260 $I = ''; 261 } else { 262 $WHERE = "engine = ?"; 263 $engineParam = 'dokuwiki'; 264 $I = 'i'; 265 } 266 $sql = "SELECT COUNT(*) as cnt, word, word as ${I}lookup 267 FROM search as A, 268 searchwords as B 269 WHERE A.dt >= ? AND A.dt <= ? 270 AND A.id = B.sid 271 AND $WHERE 272 GROUP BY word 273 ORDER BY cnt DESC, word" . 274 $this->mklimit($start, $limit); 275 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $engineParam]); 276 } 277 278 public function outlinks($start = 0, $limit = 20) 279 { 280 $sql = "SELECT COUNT(*) as cnt, link as url 281 FROM outlinks as A 282 WHERE A.dt >= ? AND A.dt <= ? 283 GROUP BY link 284 ORDER BY cnt DESC, link" . 285 $this->mklimit($start, $limit); 286 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 287 } 288 289 public function pages($start = 0, $limit = 20) 290 { 291 $sql = "SELECT COUNT(*) as cnt, page 292 FROM access as A 293 WHERE A.dt >= ? AND A.dt <= ? 294 AND ua_type = ? 295 GROUP BY page 296 ORDER BY cnt DESC, page" . 297 $this->mklimit($start, $limit); 298 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 299 } 300 301 public function edits($start = 0, $limit = 20) 302 { 303 $sql = "SELECT COUNT(*) as cnt, page 304 FROM edits as A 305 WHERE A.dt >= ? AND A.dt <= ? 306 GROUP BY page 307 ORDER BY cnt DESC, page" . 308 $this->mklimit($start, $limit); 309 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 310 } 311 312 public function images($start = 0, $limit = 20) 313 { 314 $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize 315 FROM media as A 316 WHERE A.dt >= ? AND A.dt <= ? 317 AND mime1 = ? 318 GROUP BY media 319 ORDER BY cnt DESC, media" . 320 $this->mklimit($start, $limit); 321 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']); 322 } 323 324 public function imagessum() 325 { 326 $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize 327 FROM media as A 328 WHERE A.dt >= ? AND A.dt <= ? 329 AND mime1 = ?"; 330 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']); 331 } 332 333 public function downloads($start = 0, $limit = 20) 334 { 335 $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize 336 FROM media as A 337 WHERE A.dt >= ? AND A.dt <= ? 338 AND mime1 != ? 339 GROUP BY media 340 ORDER BY cnt DESC, media" . 341 $this->mklimit($start, $limit); 342 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']); 343 } 344 345 public function downloadssum() 346 { 347 $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize 348 FROM media as A 349 WHERE A.dt >= ? AND A.dt <= ? 350 AND mime1 != ?"; 351 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']); 352 } 353 354 public function referer($start = 0, $limit = 20) 355 { 356 $sql = "SELECT COUNT(*) as cnt, ref as url 357 FROM access as A 358 WHERE A.dt >= ? AND A.dt <= ? 359 AND ua_type = ? 360 AND ref_type = ? 361 GROUP BY ref_md5 362 ORDER BY cnt DESC, url" . 363 $this->mklimit($start, $limit); 364 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 'external']); 365 } 366 367 public function newreferer($start = 0, $limit = 20) 368 { 369 $sql = "SELECT COUNT(*) as cnt, ref as url 370 FROM access as B, 371 refseen as A 372 WHERE B.dt >= ? AND B.dt <= ? 373 AND ua_type = ? 374 AND ref_type = ? 375 AND A.ref_md5 = B.ref_md5 376 GROUP BY A.ref_md5 377 ORDER BY cnt DESC, url" . 378 $this->mklimit($start, $limit); 379 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 'external']); 380 } 381 382 public function countries($start = 0, $limit = 20) 383 { 384 $sql = "SELECT COUNT(DISTINCT session) as cnt, B.code AS cflag, B.country 385 FROM access as A, 386 iplocation as B 387 WHERE A.dt >= ? AND A.dt <= ? 388 AND A.ip = B.ip 389 GROUP BY B.code 390 ORDER BY cnt DESC, B.country" . 391 $this->mklimit($start, $limit); 392 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]); 393 } 394 395 public function browsers($start = 0, $limit = 20, $ext = true) 396 { 397 if ($ext) { 398 $sel = 'ua_info as bflag, ua_info as browser, ua_ver'; 399 $grp = 'ua_info, ua_ver'; 400 } else { 401 $grp = 'ua_info'; 402 $sel = 'ua_info'; 403 } 404 405 $sql = "SELECT COUNT(DISTINCT session) as cnt, $sel 406 FROM access as A 407 WHERE A.dt >= ? AND A.dt <= ? 408 AND ua_type = ? 409 GROUP BY $grp 410 ORDER BY cnt DESC, ua_info" . 411 $this->mklimit($start, $limit); 412 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 413 } 414 415 public function os($start = 0, $limit = 20) 416 { 417 $sql = "SELECT COUNT(DISTINCT session) as cnt, os as osflag, os 418 FROM access as A 419 WHERE A.dt >= ? AND A.dt <= ? 420 AND ua_type = ? 421 GROUP BY os 422 ORDER BY cnt DESC, os" . 423 $this->mklimit($start, $limit); 424 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']); 425 } 426 427 public function topuser($start = 0, $limit = 20) 428 { 429 $sql = "SELECT COUNT(*) as cnt, user 430 FROM access as A 431 WHERE A.dt >= ? AND A.dt <= ? 432 AND ua_type = ? 433 AND user != ? 434 GROUP BY user 435 ORDER BY cnt DESC, user" . 436 $this->mklimit($start, $limit); 437 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', '']); 438 } 439 440 public function topeditor($start = 0, $limit = 20) 441 { 442 $sql = "SELECT COUNT(*) as cnt, user 443 FROM edits as A 444 WHERE A.dt >= ? AND A.dt <= ? 445 AND user != ? 446 GROUP BY user 447 ORDER BY cnt DESC, user" . 448 $this->mklimit($start, $limit); 449 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, '']); 450 } 451 452 public function topgroup($start = 0, $limit = 20) 453 { 454 $sql = "SELECT COUNT(*) as cnt, `group` 455 FROM groups as A 456 WHERE A.dt >= ? AND A.dt <= ? 457 AND `type` = ? 458 GROUP BY `group` 459 ORDER BY cnt DESC, `group`" . 460 $this->mklimit($start, $limit); 461 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'view']); 462 } 463 464 public function topgroupedit($start = 0, $limit = 20) 465 { 466 $sql = "SELECT COUNT(*) as cnt, `group` 467 FROM groups as A 468 WHERE A.dt >= ? AND A.dt <= ? 469 AND `type` = ? 470 GROUP BY `group` 471 ORDER BY cnt DESC, `group`" . 472 $this->mklimit($start, $limit); 473 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'edit']); 474 } 475 476 477 public function resolution($start = 0, $limit = 20) 478 { 479 $sql = "SELECT COUNT(DISTINCT uid) as cnt, 480 ROUND(screen_x/100)*100 as res_x, 481 ROUND(screen_y/100)*100 as res_y, 482 (ROUND(screen_x/100)*100 || 'x' || ROUND(screen_y/100)*100) as resolution 483 FROM access as A 484 WHERE A.dt >= ? AND A.dt <= ? 485 AND ua_type = ? 486 AND screen_x != ? 487 AND screen_y != ? 488 GROUP BY resolution 489 ORDER BY cnt DESC" . 490 $this->mklimit($start, $limit); 491 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]); 492 } 493 494 public function viewport($start = 0, $limit = 20) 495 { 496 $sql = "SELECT COUNT(DISTINCT uid) as cnt, 497 ROUND(view_x/100)*100 as res_x, 498 ROUND(view_y/100)*100 as res_y, 499 (ROUND(view_x/100)*100 || 'x' || ROUND(view_y/100)*100) as resolution 500 FROM access as A 501 WHERE A.dt >= ? AND A.dt <= ? 502 AND ua_type = ? 503 AND view_x != ? 504 AND view_y != ? 505 GROUP BY resolution 506 ORDER BY cnt DESC" . 507 $this->mklimit($start, $limit); 508 509 return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]); 510 } 511 512 public function seenusers($start = 0, $limit = 20) 513 { 514 $sql = "SELECT `user`, `dt` 515 FROM " . $this->hlp->prefix . "lastseen as A 516 ORDER BY `dt` DESC" . 517 $this->mklimit($start, $limit); 518 519 return $this->hlp->getDB()->queryAll($sql); 520 } 521 522 523 /** 524 * Builds a limit clause 525 */ 526 public function mklimit($start, $limit) 527 { 528 $start = (int)$start; 529 $limit = (int)$limit; 530 if ($limit) { 531 $limit += 1; 532 return " LIMIT $start,$limit"; 533 } elseif ($start) { 534 return " OFFSET $start"; 535 } 536 return ''; 537 } 538 539} 540