OSDN Git Service

BugTrack/2540 Simplify INSTALL / UPDATING document
[pukiwiki/pukiwiki.git] / plugin / counter.inc.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // counter.inc.php
4 // Copyright
5 //   2002-2022 PukiWiki Development Team
6 //   2002 Y.MASUI GPL2 http://masui.net/pukiwiki/ masui@masui.net
7 // License: GPL2
8 //
9 // Counter plugin (per page)
10
11 // Counter file's suffix
12 define('PLUGIN_COUNTER_SUFFIX', '.count');
13 // Ignore REMOTE_ADDR : FALSE(0) or TRUE(1) for reverse proxy / load balancer environment
14 define('PLUGIN_COUNTER_IGNORE_REMOTE_ADDR', 0);
15 // Use Database (1) or not (0)
16 define('PLUGIN_COUNTER_USE_DB', 0);
17 // Database Connection setting
18 define('PLUGIN_COUNTER_DB_CONNECT_STRING', 'sqlite:counter/counter.db');
19 define('PLUGIN_COUNTER_DB_USERNAME', '');
20 define('PLUGIN_COUNTER_DB_PASSWORD', '');
21 global $plugin_counter_db_options;
22 $plugin_counter_db_options = null;
23 // For MySQL
24 // $plugin_counter_db_options = array(PDO::MYSQL_ATTR_INIT_COMMAND =>
25 //   "SET NAMES utf8mb4 COLLATE utf8mb4_bin");
26
27 define('PLUGIN_COUNTER_DB_TABLE_NAME_PREFIX', '');
28 // Async Counter by JavaScript: FALSE(0; Default) or TRUE(1)
29 define('PLUGIN_COUNTER_ASYNC', 0);
30
31 define('PLUGIN_COUNTER_ASYNC_HTML_TOTAL',
32         '<span class="_plugin_counter_item _plugin_counter_item_total"></span>');
33 define('PLUGIN_COUNTER_ASYNC_HTML_TODAY',
34         '<span class="_plugin_counter_item _plugin_counter_item_today"></span>');
35 define('PLUGIN_COUNTER_ASYNC_HTML_YESTERDAY',
36         '<span class="_plugin_counter_item _plugin_counter_item_yesterday"></span>');
37
38 if (PLUGIN_COUNTER_USE_DB) {
39         ini_set('default_socket_timeout', 2);
40 }
41
42 // Report one
43 function plugin_counter_inline()
44 {
45         global $vars;
46
47         // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
48         $args = func_get_args(); // with array_shift()
49
50         $arg = strtolower(array_shift($args));
51         if (PLUGIN_COUNTER_ASYNC) {
52                 switch ($arg) {
53                 case ''     : /*FALLTHROUGH*/
54                 case 'total':
55                         return PLUGIN_COUNTER_ASYNC_HTML_TOTAL;
56                 case 'today':
57                         return PLUGIN_COUNTER_ASYNC_HTML_TODAY;
58                 case 'yesterday':
59                         return PLUGIN_COUNTER_ASYNC_HTML_YESTERDAY;
60                 default:
61                         return htmlsc('&counter([total|today|yesterday]);');
62                 }
63         }
64         switch ($arg) {
65         case ''     : $arg = 'total'; /*FALLTHROUGH*/
66         case 'total': /*FALLTHROUGH*/
67         case 'today': /*FALLTHROUGH*/
68         case 'yesterday':
69                 $counter = plugin_counter_get_count($vars['page']);
70                 return $counter[$arg];
71         default:
72                 return htmlsc('&counter([total|today|yesterday]);');
73         }
74 }
75
76 // Report all
77 function plugin_counter_convert()
78 {
79         global $vars;
80         if (PLUGIN_COUNTER_ASYNC) {
81                 $total_html = PLUGIN_COUNTER_ASYNC_HTML_TOTAL;
82                 $today_html = PLUGIN_COUNTER_ASYNC_HTML_TODAY;
83                 $yesterday_html = PLUGIN_COUNTER_ASYNC_HTML_YESTERDAY;
84                 return <<<EOD
85 <div class="counter">
86 Counter: ${total_html},
87 today: ${today_html},
88 yesterday: ${yesterday_html}
89 </div>
90 EOD;
91         }
92         $counter = plugin_counter_get_count($vars['page']);
93         return <<<EOD
94 <div class="counter">
95 Counter: {$counter['total']},
96 today: {$counter['today']},
97 yesterday: {$counter['yesterday']}
98 </div>
99 EOD;
100 }
101
102 function plugin_counter_action()
103 {
104         global $vars;
105         if (! PLUGIN_COUNTER_ASYNC) {
106                 die('Invalid counter action');
107                 exit;
108         }
109         $page = isset($vars['page']) ? $vars['page'] : '';
110         if ($page && is_page($page) && is_page_readable($page)) {
111                 $c = plugin_counter_get_count($page);
112                 $obj = array(
113                         'page' => $page,
114                         'total' => '' . $c['total'],
115                         'today' => '' . $c['today'],
116                         'yesterday' => '' . $c['yesterday'],
117                 );
118         } else {
119                 $obj = array();
120         }
121         header('Content-Type: application/json; charset=UTF-8');
122         print(json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
123                 | JSON_FORCE_OBJECT));
124         exit;
125 }
126
127 // Return a summary
128 function plugin_counter_get_count($page)
129 {
130         global $vars, $plugin_counter_db_options, $plugin;
131         static $counters = array();
132         static $default;
133         $page_counter_t = PLUGIN_COUNTER_DB_TABLE_NAME_PREFIX . 'page_counter';
134
135         if (! isset($default))
136                 $default = array(
137                         'total'     => 0,
138                         'date'      => get_date('Y/m/d'),
139                         'today'     => 0,
140                         'yesterday' => 0,
141                         'ip'        => '');
142
143         if (! is_page($page)) return $default;
144         if (isset($counters[$page])) return $counters[$page];
145
146         // Set default
147         $counters[$page] = $default;
148         $modify = FALSE;
149         $c = & $counters[$page];
150
151         if (PLUGIN_COUNTER_USE_DB) {
152                 if (SOURCE_ENCODING !== 'UTF-8') {
153                         die('counter.inc.php: Database counter is only available in UTF-8 mode');
154                 }
155                 $is_new_page = false;
156                 try {
157                         $pdo = new PDO(PLUGIN_COUNTER_DB_CONNECT_STRING,
158                                 PLUGIN_COUNTER_DB_USERNAME, PLUGIN_COUNTER_DB_PASSWORD,
159                                 $plugin_counter_db_options);
160                         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
161                         $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
162                         $pdo->setAttribute(PDO::ATTR_TIMEOUT, 5);
163                         $stmt = $pdo->prepare(
164 "SELECT total, update_date,
165    today_viewcount, yesterday_viewcount, remote_addr
166  FROM $page_counter_t
167  WHERE page_name = ?"
168                         );
169                         $stmt->execute(array($page));
170                         $r = $stmt->fetch();
171                         if ($r === false) {
172                                 $is_new_page = true;
173                         } else {
174                                 $c['ip'] = $r['remote_addr'];
175                                 $c['date'] = $r['update_date'];
176                                 $c['yesterday'] = intval($r['yesterday_viewcount']);
177                                 $c['today'] = intval($r['today_viewcount']);
178                                 $c['total'] = intval($r['total']);
179                                 $stmt->closeCursor();
180                         }
181                 } catch (Exception $e) {
182                         // Error occurred
183                         $db_error = '(DBError)';
184                         return array(
185                                 'total' => $db_error,
186                                 'date' => $db_error,
187                                 'today' => $db_error,
188                                 'yesterday' => $db_error,
189                                 'ip' => $db_error);
190                 }
191         } else {
192                 // Open
193                 $file = COUNTER_DIR . encode($page) . PLUGIN_COUNTER_SUFFIX;
194                 pkwk_touch_file($file);
195                 $fp = fopen($file, 'r+')
196                         or die('counter.inc.php: Cannot open COUNTER_DIR/' . basename($file));
197                 set_file_buffer($fp, 0);
198                 flock($fp, LOCK_EX);
199                 rewind($fp);
200
201                 // Read
202                 foreach (array_keys($default) as $key) {
203                         // Update
204                         $c[$key] = rtrim(fgets($fp, 256));
205                         if (feof($fp)) break;
206                 }
207         }
208
209         // Anothoer day?
210         $remote_addr = $_SERVER['REMOTE_ADDR'];
211         if (! $remote_addr) {
212                 $remote_addr = '_';
213         }
214         $count_up = FALSE;
215         if ($c['date'] != $default['date']) {
216                 $modify = TRUE;
217                 $is_yesterday = ($c['date'] == get_date('Y/m/d', UTIME - 24 * 60 * 60));
218                 $c[$page]['ip']        = $remote_addr;
219                 $c['date']      = $default['date'];
220                 $c['yesterday'] = $is_yesterday ? $c['today'] : 0;
221                 $c['today']     = 1;
222                 $c['total']++;
223                 $count_up = TRUE;
224         } else if ($c['ip'] != $remote_addr || PLUGIN_COUNTER_IGNORE_REMOTE_ADDR) {
225                 // Not the same host
226                 $modify = TRUE;
227                 $c['ip']        = $remote_addr;
228                 $c['today']++;
229                 $c['total']++;
230                 $count_up = TRUE;
231         }
232         $is_count_target_plugin = ($plugin == 'read' || $plugin == 'counter');
233         if (PLUGIN_COUNTER_USE_DB) {
234                 if ($modify && $is_count_target_plugin) {
235                         try {
236                                 if ($is_new_page) {
237                                         // Insert
238                                         $add_stmt = $pdo->prepare(
239 "INSERT INTO $page_counter_t
240    (page_name, total, update_date, today_viewcount,
241    yesterday_viewcount, remote_addr)
242  VALUES (?, ?, ?, ?, ?, ?)"
243                                         );
244                                         $r_add = $add_stmt->execute(array($page, $c['total'],
245                                                 $c['date'], $c['today'], $c['yesterday'], $c['ip']));
246                                 } else if ($count_up) {
247                                         // Update on counting up 'total'
248                                         $upd_stmt = $pdo->prepare(
249 "UPDATE $page_counter_t
250  SET total = total + 1,
251    update_date = ?,
252    today_viewcount = ?,
253    yesterday_viewcount = ?,
254    remote_addr = ?
255  WHERE page_name = ?"
256                                         );
257                                         $r_upd = $upd_stmt->execute(array($c['date'],
258                                                 $c['today'], $c['yesterday'], $c['ip'], $page));
259                                 }
260                         } catch (PDOException $e) {
261                                 foreach (array_keys($c) as $key) {
262                                         $c[$key] .= '(DBError)';
263                                 }
264                         }
265                 }
266         } else {
267                 // Modify
268                 if ($modify && $is_count_target_plugin) {
269                         rewind($fp);
270                         ftruncate($fp, 0);
271                         foreach (array_keys($default) as $key)
272                                 fputs($fp, $c[$key] . "\n");
273                 }
274                 // Close
275                 flock($fp, LOCK_UN);
276                 fclose($fp);
277         }
278         return $c;
279 }
280
281 function plugin_counter_get_popular_list($today, $except, $max) {
282         if (PLUGIN_COUNTER_USE_DB === 0) {
283                 return plugin_counter_get_popular_list_file($today, $except, $max);
284         } else {
285                 return plugin_counter_get_popular_list_db($today, $except, $max);
286         }
287 }
288
289 function plugin_counter_get_popular_list_file($today, $except, $max) {
290         global $whatsnew;
291         $counters = array();
292         $except_quote = str_replace('#', '\#', $except);
293         foreach (get_existpages(COUNTER_DIR, '.count') as $file=>$page) {
294                 if (($except != '' && preg_match("#$except_quote#", $page)) ||
295                                 $page == $whatsnew || check_non_list($page) ||
296                                 ! is_page($page))
297                         continue;
298
299                 $array = file(COUNTER_DIR . $file);
300                 $count = rtrim($array[0]);
301                 $date  = rtrim($array[1]);
302                 $today_count = rtrim($array[2]);
303
304                 if ($today) {
305                         // $pageが数値に見える(たとえばencode('BBS')=424253)とき、
306                         // array_splice()によってキー値が変更されてしまうのを防ぐ
307                         // ため、キーに '_' を連結する
308                         if ($today == $date) $counters['_' . $page] = $today_count;
309                 } else {
310                         $counters['_' . $page] = $count;
311                 }
312         }
313
314         asort($counters, SORT_NUMERIC);
315
316         // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
317         $counters = array_reverse($counters, TRUE); // with array_splice()
318         $counters = array_splice($counters, 0, $max);
319         return $counters;
320 }
321
322 function plugin_counter_get_popular_list_db($today, $except, $max) {
323         global $whatsnew;
324         $page_counter_t = PLUGIN_COUNTER_DB_TABLE_NAME_PREFIX . 'page_counter';
325         if ($today) {
326                 $order_by_c = 'today_viewcount';
327         } else {
328                 $order_by_c = 'total';
329         }
330         $counters = array();
331         try {
332                 $pdo = new PDO(PLUGIN_COUNTER_DB_CONNECT_STRING,
333                         PLUGIN_COUNTER_DB_USERNAME, PLUGIN_COUNTER_DB_PASSWORD,
334                         $plugin_counter_db_options);
335                 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
336                 $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
337                 $pdo->setAttribute(PDO::ATTR_TIMEOUT, 5);
338                 if ($today) {
339                         $stmt = $pdo->prepare(
340 "SELECT page_name, total, update_date,
341    today_viewcount, yesterday_viewcount
342  FROM $page_counter_t
343  WHERE update_date = ?
344  ORDER BY $order_by_c DESC
345  LIMIT ?"
346                         );
347                 } else {
348                         $stmt = $pdo->prepare(
349 "SELECT page_name, total, update_date,
350    today_viewcount, yesterday_viewcount
351  FROM $page_counter_t
352  ORDER BY $order_by_c DESC
353  LIMIT ?"
354                         );
355                 }
356                 $except_quote = str_replace('#', '\#', $except);
357                 $limit = $max + 100;
358                 if ($today) {
359                         $stmt->execute(array($today, $limit));
360                 } else {
361                         $stmt->execute(array($limit));
362                 }
363                 foreach ($stmt as $r) {
364                         $page = $r['page_name'];
365                         if (($except != '' && preg_match("#$except_quote#", $page)) ||
366                                         $page == $whatsnew || check_non_list($page) ||
367                                         ! is_page($page)) {
368                                 continue;
369                         }
370                         if ($today) {
371                                 $counters['_' . $page] = $r['today_viewcount'];
372                         } else {
373                                 $counters['_' . $page] = $r['total'];
374                         }
375                 }
376                 $stmt->closeCursor();
377                 return array_splice($counters, 0, $max);
378         } catch (Exception $e) {
379                 die('counter.inc.php: Error occurred on getting pupular pages');
380         }
381 }
382
383 function plugin_counter_page_rename($pages) {
384         global $plugin_counter_db_options;
385         if (PLUGIN_COUNTER_USE_DB !== 0) {
386                 $page_counter_t = PLUGIN_COUNTER_DB_TABLE_NAME_PREFIX . 'page_counter';
387                 $pdo = new PDO(PLUGIN_COUNTER_DB_CONNECT_STRING,
388                         PLUGIN_COUNTER_DB_USERNAME, PLUGIN_COUNTER_DB_PASSWORD,
389                         $plugin_counter_db_options);
390                 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
391                 $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
392                 $stmt_delete = $pdo->prepare(
393 "DELETE FROM $page_counter_t
394  WHERE page_name = ?"
395                 );
396                 $stmt_rename = $pdo->prepare(
397 "UPDATE $page_counter_t
398  SET page_name = ?
399  WHERE page_name = ?"
400                 );
401                 foreach ($pages as $old_name=>$new_name) {
402                         $stmt_delete->execute(array($new_name));
403                         $stmt_rename->execute(array($new_name, $old_name));
404                 }
405         }
406 }
407
408 /**
409  * php -r "include 'plugin/counter.inc.php'; plugin_counter_tool_setup_table();"
410  */
411 function plugin_counter_tool_setup_table() {
412         global $plugin_counter_db_options;
413         $page_counter_t = PLUGIN_COUNTER_DB_TABLE_NAME_PREFIX . 'page_counter';
414         $pdo = new PDO(PLUGIN_COUNTER_DB_CONNECT_STRING,
415                 PLUGIN_COUNTER_DB_USERNAME, PLUGIN_COUNTER_DB_PASSWORD,
416                 $plugin_counter_db_options);
417         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
418         $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
419         $r = $pdo->exec(
420 "CREATE TABLE $page_counter_t (
421    page_name VARCHAR(190) PRIMARY KEY,
422    total INTEGER NOT NULL,
423    update_date VARCHAR(20) NOT NULL,
424    today_viewcount INTEGER NOT NULL,
425    yesterday_viewcount INTEGER NOT NULL,
426    remote_addr VARCHAR(100)
427  )"
428         );
429         echo "OK\n";
430 }