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