PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.1.1
Post Views Counter v1.1.1
1.7.13 1.7.12 1.7.11 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.2 1.3.2.1 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.7.10 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9
post-views-counter / includes / counter.php
post-views-counter / includes Last commit date
columns.php 10 years ago counter.php 10 years ago cron.php 10 years ago frontend.php 10 years ago functions.php 10 years ago query.php 10 years ago settings.php 10 years ago update.php 10 years ago widgets.php 10 years ago
counter.php
528 lines
1 <?php
2 if ( ! defined( 'ABSPATH' ) )
3 exit;
4
5 new Post_Views_Counter_Counter();
6
7 class Post_Views_Counter_Counter {
8
9 const GROUP = 'pvc';
10 const NAME_ALLKEYS = 'cached_key_names';
11 const CACHE_KEY_SEPARATOR = '.';
12
13 private $cookie = array(
14 'exists' => false,
15 'visited_posts' => array(),
16 'expiration' => 0
17 );
18
19 public function __construct() {
20 // set instance
21 Post_Views_Counter()->add_instance( 'counter', $this );
22
23 // actions
24 add_action( 'plugins_loaded', array( &$this, 'check_cookie' ), 1 );
25 add_action( 'wp', array( &$this, 'check_post' ) );
26 add_action( 'deleted_post', array( &$this, 'delete_post_views' ) );
27 add_action( 'wp_ajax_pvc-check-post', array( &$this, 'check_post_ajax' ) );
28 add_action( 'wp_ajax_nopriv_pvc-check-post', array( &$this, 'check_post_ajax' ) );
29 }
30
31 /**
32 * Remove post views from database when post is deleted.
33 *
34 * @param int $post_id
35 */
36 public function delete_post_views( $post_id ) {
37 global $wpdb;
38
39 $wpdb->delete( $wpdb->prefix . 'post_views', array( 'id' => $post_id ), array( '%d' ) );
40 }
41
42 /**
43 * Check whether user has excluded roles.
44 *
45 * @param string $option
46 * @return bool
47 */
48 public function is_user_roles_excluded( $option ) {
49 $user = wp_get_current_user();
50
51 if ( empty( $user ) )
52 return false;
53
54 $roles = (array) $user->roles;
55
56 if ( ! empty( $roles ) ) {
57 foreach ( $roles as $role ) {
58 if ( in_array( $role, $option, true ) )
59 return true;
60 }
61 }
62
63 return false;
64 }
65
66 /**
67 *
68 * Get timestamp convertion.
69 *
70 * @param string $type
71 * @param int $number
72 * @param int $timestamp
73 * @return string
74 */
75 public function get_timestamp( $type, $number, $timestamp = true ) {
76 $converter = array(
77 'minutes' => 60,
78 'hours' => 3600,
79 'days' => 86400,
80 'weeks' => 604800,
81 'months' => 2592000,
82 'years' => 946080000
83 );
84
85 return ($timestamp ? current_time( 'timestamp', true ) : 0) + $number * $converter[$type];
86 }
87
88 /**
89 * Check whether to count visit via AJAX request.
90 */
91 public function check_post_ajax() {
92 if ( isset( $_POST['action'], $_POST['post_id'], $_POST['pvc_nonce'], $_POST['post_type'] ) && $_POST['action'] === 'pvc-check-post' && ($post_id = (int) $_POST['post_id']) > 0 && wp_verify_nonce( $_POST['pvc_nonce'], 'pvc-check-post' ) !== false && Post_Views_Counter()->get_attribute( 'options', 'general', 'counter_mode' ) === 'js' ) {
93 // get countable post types
94 $post_types = Post_Views_Counter()->get_attribute( 'options', 'general', 'post_types_count' );
95
96 // get post type
97 $post_type = get_post_type( $post_id );
98
99 // whether to count this post type or not
100 if ( empty( $post_types ) || empty( $post_type ) || $post_type !== $_POST['post_type'] || ! in_array( $post_type, $post_types, true ) )
101 exit;
102
103 // get excluded ips
104 $excluded_ips = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude_ips' );
105
106 // whether to count this ip or not
107 if ( ! empty( $excluded_ips ) && filter_var( preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) && in_array( $_SERVER['REMOTE_ADDR'], $excluded_ips, true ) )
108 exit;
109
110 // get groups to check them faster
111 $groups = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'groups' );
112
113 // whether to count this user
114 if ( is_user_logged_in() ) {
115 // exclude logged in users?
116 if ( in_array( 'users', $groups, true ) )
117 exit;
118 // exclude specific roles?
119 elseif ( in_array( 'roles', $groups, true ) && $this->is_user_roles_excluded( Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'roles' ) ) )
120 exit;
121 }
122 // exclude guests?
123 elseif ( in_array( 'guests', $groups, true ) )
124 exit;
125
126 // whether to count robots
127 if ( $this->is_robot() )
128 exit;
129
130 // cookie already existed?
131 if ( $this->cookie['exists'] ) {
132 // post already viewed but not expired?
133 if ( in_array( $post_id, array_keys( $this->cookie['visited_posts'] ), true ) && current_time( 'timestamp', true ) < $this->cookie['visited_posts'][$post_id] ) {
134 // updates cookie but do not count visit
135 $this->save_cookie( $post_id, $this->cookie, false );
136
137 exit;
138 } else
139 // updates cookie
140 $this->save_cookie( $post_id, $this->cookie );
141 } else {
142 // set new cookie
143 $this->save_cookie( $post_id );
144 }
145
146 // count visit
147 $this->count_visit( $post_id );
148 }
149
150 exit;
151 }
152
153 /**
154 * Check whether to count visit.
155 */
156 public function check_post() {
157 // do not count admin entries
158 if ( is_admin() )
159 return;
160
161 // do we use PHP as counter?
162 if ( Post_Views_Counter()->get_attribute( 'options', 'general', 'counter_mode' ) === 'php' ) {
163 $post_types = Post_Views_Counter()->get_attribute( 'options', 'general', 'post_types_count' );
164
165 // whether to count this post type
166 if ( empty( $post_types ) || ! is_singular( $post_types ) )
167 return;
168
169 $ips = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude_ips' );
170
171 // whether to count this ip
172 if ( ! empty( $ips ) && filter_var( preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) && in_array( $_SERVER['REMOTE_ADDR'], $ips, true ) )
173 return;
174
175 // get groups to check them faster
176 $groups = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'groups' );
177
178 // whether to count this user
179 if ( is_user_logged_in() ) {
180 // exclude logged in users?
181 if ( in_array( 'users', $groups, true ) )
182 return;
183 // exclude specific roles?
184 elseif ( in_array( 'roles', $groups, true ) && $this->is_user_roles_excluded( Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'roles' ) ) )
185 return;
186 }
187 // exclude guests?
188 elseif ( in_array( 'guests', $groups, true ) )
189 return;
190
191 // whether to count robots
192 if ( $this->is_robot() )
193 return;
194
195 // get post id
196 $id = get_the_ID();
197
198 // cookie already existed?
199 if ( $this->cookie['exists'] ) {
200 // post already viewed but not expired?
201 if ( in_array( $id, array_keys( $this->cookie['visited_posts'] ), true ) && current_time( 'timestamp', true ) < $this->cookie['visited_posts'][$id] ) {
202 // update cookie but do not count visit
203 $this->save_cookie( $id, $this->cookie, false );
204
205 return;
206 } else
207 // update cookie
208 $this->save_cookie( $id, $this->cookie );
209 } else
210 // set new cookie
211 $this->save_cookie( $id );
212
213 // count visit
214 $this->count_visit( $id );
215 }
216 }
217
218 /**
219 * Initialize cookie session.
220 */
221 public function check_cookie() {
222 // do not run in admin except for ajax requests
223 if ( is_admin() && ! (defined( 'DOING_AJAX' ) && DOING_AJAX) )
224 return;
225
226 // is cookie set?
227 if ( isset( $_COOKIE['pvc_visits'] ) && ! empty( $_COOKIE['pvc_visits'] ) ) {
228 $visited_posts = $expirations = array();
229
230 foreach ( $_COOKIE['pvc_visits'] as $content ) {
231 // is cookie valid?
232 if ( preg_match( '/^(([0-9]+b[0-9]+a?)+)$/', $content ) === 1 ) {
233 // get single id with expiration
234 $expiration_ids = explode( 'a', $content );
235
236 // check every expiration => id pair
237 foreach ( $expiration_ids as $pair ) {
238 $pair = explode( 'b', $pair );
239 $expirations[] = (int) $pair[0];
240 $visited_posts[(int) $pair[1]] = (int) $pair[0];
241 }
242 }
243 }
244
245 $this->cookie = array(
246 'exists' => true,
247 'visited_posts' => $visited_posts,
248 'expiration' => max( $expirations )
249 );
250 }
251 }
252
253 /**
254 * Save cookie function.
255 *
256 * @param int $id
257 * @param array $cookie
258 * @param bool $expired
259 */
260 private function save_cookie( $id, $cookie = array(), $expired = true ) {
261 $expiration = $this->get_timestamp( Post_Views_Counter()->get_attribute( 'options', 'general', 'time_between_counts', 'type' ), Post_Views_Counter()->get_attribute( 'options', 'general', 'time_between_counts', 'number' ) );
262
263 // is this a new cookie?
264 if ( empty( $cookie ) ) {
265 // set cookie
266 setcookie( 'pvc_visits[0]', $expiration . 'b' . $id, $expiration, COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
267 } else {
268 if ( $expired ) {
269 // add new id or chang expiration date if id already exists
270 $cookie['visited_posts'][$id] = $expiration;
271 }
272
273 // create copy for better foreach performance
274 $visited_posts_expirations = $cookie['visited_posts'];
275
276 // get current gmt time
277 $time = current_time( 'timestamp', true );
278
279 // check whether viewed id has expired - no need to keep it in cookie (less size)
280 foreach ( $visited_posts_expirations as $post_id => $post_expiration ) {
281 if ( $time > $post_expiration )
282 unset( $cookie['visited_posts'][$post_id] );
283 }
284
285 // set new last expiration date if needed
286 $cookie['expiration'] = max( $cookie['visited_posts'] );
287
288 $cookies = $imploded = array();
289
290 // create pairs
291 foreach ( $cookie['visited_posts'] as $id => $exp ) {
292 $imploded[] = $exp . 'b' . $id;
293 }
294
295 // split cookie into chunks (4000 bytes to make sure it is safe for every browser)
296 $chunks = str_split( implode( 'a', $imploded ), 4000 );
297
298 // more then one chunk?
299 if ( count( $chunks ) > 1 ) {
300 $last_id = '';
301
302 foreach ( $chunks as $chunk_id => $chunk ) {
303 // new chunk
304 $chunk_c = $last_id . $chunk;
305
306 // is it full-length chunk?
307 if ( strlen( $chunk ) === 4000 ) {
308 // get last part
309 $last_part = strrchr( $chunk_c, 'a' );
310
311 // get last id
312 $last_id = substr( $last_part, 1 );
313
314 // add new full-lenght chunk
315 $cookies[$chunk_id] = substr( $chunk_c, 0, strlen( $chunk_c ) - strlen( $last_part ) );
316 } else {
317 // add last chunk
318 $cookies[$chunk_id] = $chunk_c;
319 }
320 }
321 } else {
322 // only one chunk
323 $cookies[] = $chunks[0];
324 }
325
326 foreach ( $cookies as $key => $value ) {
327 // set cookie
328 setcookie( 'pvc_visits[' . $key . ']', $value, $cookie['expiration'], COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
329 }
330 }
331 }
332
333 /**
334 * Check if object cache is in use.
335 *
336 * @param bool $using
337 * @return bool
338 */
339 public function using_object_cache( $using = null ) {
340 $using = wp_using_ext_object_cache( $using );
341
342 if ( $using ) {
343 // check if explicitly disabled by flush_interval setting/option <= 0
344 $flush_interval_number = Post_Views_Counter()->get_attribute( 'options', 'general', 'flush_interval', 'number' );
345 $using = ( $flush_interval_number <= 0 ) ? false : true;
346 }
347
348 return $using;
349 }
350
351 /**
352 * Count visit function.
353 *
354 * @global object $wpdb
355 * @param int $id
356 * @return bool
357 */
358 private function count_visit( $id ) {
359 global $wpdb;
360
361 $cache_key_names = array();
362 $using_object_cache = $this->using_object_cache();
363
364 // get day, week, month and year
365 $date = explode( '-', date( 'W-d-m-Y', current_time( 'timestamp' ) ) );
366
367 foreach ( array(
368 0 => $date[3] . $date[2] . $date[1], // day like 20140324
369 1 => $date[3] . $date[0], // week like 201439
370 2 => $date[3] . $date[2], // month like 201405
371 3 => $date[3], // year like 2014
372 4 => 'total' // total views
373 ) as $type => $period ) {
374 if ( $using_object_cache ) {
375 $cache_key = $id . self::CACHE_KEY_SEPARATOR . $type . self::CACHE_KEY_SEPARATOR . $period;
376 wp_cache_add( $cache_key, 0, self::GROUP );
377 wp_cache_incr( $cache_key, 1, self::GROUP );
378 $cache_key_names[] = $cache_key;
379 } else {
380 // hit the db directly
381 // @TODO: investigate queueing these queries on the 'shutdown' hook instead instead of running them instantly?
382 $this->db_insert( $id, $type, $period, 1 );
383 }
384 }
385
386 // update the list of cache keys to be flushed
387 if ( $using_object_cache && ! empty( $cache_key_names ) ) {
388 $this->update_cached_keys_list_if_needed( $cache_key_names );
389 }
390
391 do_action( 'pvc_after_count_visit', $id );
392
393 return true;
394 }
395
396 /**
397 * Update the single cache key which holds a list of all the cache keys
398 * that need to be flushed to the db.
399 *
400 * The value of that special cache key is a giant string containing key names separated with the `|` character.
401 * Each such key name then consists of 3 elements: $id, $type, $period (separated by a `.` character).
402 * Examples:
403 * 62053.0.20150327|62053.1.201513|62053.2.201503|62053.3.2015|62053.4.total|62180.0.20150327|62180.1.201513|62180.2.201503|62180.3.2015|62180.4.total
404 * A single key is `62053.0.20150327` and that key's data is: $id = 62053, $type = 0, $period = 20150327
405 *
406 * This data format proved more efficient (avoids the (un)serialization overhead completely + duplicates filtering is a string search now)
407 *
408 * @param array $key_names
409 */
410 private function update_cached_keys_list_if_needed( $key_names = array() ) {
411 $existing_list = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
412 if ( ! $existing_list ) {
413 $existing_list = '';
414 }
415
416 $list_modified = false;
417
418 // modify the list contents if/when needed
419 if ( empty( $existing_list ) ) {
420 // the simpler case of an empty initial list where we just
421 // transform the specified key names into a string
422 $existing_list = implode( '|', $key_names );
423 $list_modified = true;
424 } else {
425 // search each specified key name and append it if it's not found
426 foreach ( $key_names as $key_name ) {
427 if ( false === strpos( $existing_list, $key_name ) ) {
428 $existing_list .= '|' . $key_name;
429 $list_modified = true;
430 }
431 }
432 }
433
434 // save modified list back in cache
435 if ( $list_modified ) {
436 wp_cache_set( self::NAME_ALLKEYS, $existing_list, self::GROUP );
437 }
438 }
439
440 /**
441 * Flush views data stored in the persistent object cache into
442 * our custom table and clear the object cache keys when done
443 *
444 * @global object $wpdb
445 * @return bool
446 */
447 public function flush_cache_to_db() {
448 global $wpdb;
449
450 $key_names = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
451
452 if ( ! $key_names ) {
453 $key_names = array();
454 } else {
455 // create an array out of a string that's stored in the cache
456 $key_names = explode( '|', $key_names );
457 }
458
459 foreach ( $key_names as $key_name ) {
460 // get values stored within the key name itself
461 list( $id, $type, $period ) = explode( self::CACHE_KEY_SEPARATOR, $key_name );
462 // get the cached count value
463 $count = wp_cache_get( $key_name, self::GROUP );
464
465 // store cached value in the db
466 $this->db_insert( $id, $type, $period, $count );
467
468 // clear the cache key we just flushed
469 wp_cache_delete( $key_name, self::GROUP );
470 }
471
472 // delete the key holding the list itself after we've successfully flushed it
473 if ( ! empty( $key_names ) ) {
474 wp_cache_delete( self::NAME_ALLKEYS, self::GROUP );
475 }
476
477 return true;
478 }
479
480 /**
481 * Insert or update views count.
482 *
483 * @global object $wpdb
484 * @param int $id
485 * @param string $type
486 * @param string $period
487 * @param int $count
488 * @return bool
489 */
490 private function db_insert( $id, $type, $period, $count = 1 ) {
491 global $wpdb;
492
493 $count = (int) $count;
494
495 if ( ! $count ) {
496 $count = 1;
497 }
498
499 return $wpdb->query(
500 $wpdb->prepare( "
501 INSERT INTO " . $wpdb->prefix . "post_views (id, type, period, count)
502 VALUES (%d, %d, %s, %d)
503 ON DUPLICATE KEY UPDATE count = count + %d", $id, $type, $period, $count, $count
504 )
505 );
506 }
507
508 /**
509 * Check whether visitor is a bot.
510 */
511 private function is_robot() {
512 if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) || (isset( $_SERVER['HTTP_USER_AGENT'] ) && trim( $_SERVER['HTTP_USER_AGENT'] ) === '') )
513 return false;
514
515 $robots = array(
516 'bot', 'b0t', 'Acme.Spider', 'Ahoy! The Homepage Finder', 'Alkaline', 'Anthill', 'Walhello appie', 'Arachnophilia', 'Arale', 'Araneo', 'ArchitextSpider', 'Aretha', 'ARIADNE', 'arks', 'AskJeeves', 'ASpider (Associative Spider)', 'ATN Worldwide', 'AURESYS', 'BackRub', 'Bay Spider', 'Big Brother', 'Bjaaland', 'BlackWidow', 'Die Blinde Kuh', 'Bloodhound', 'BSpider', 'CACTVS Chemistry Spider', 'Calif', 'Cassandra', 'Digimarc Marcspider/CGI', 'ChristCrawler.com', 'churl', 'cIeNcIaFiCcIoN.nEt', 'CMC/0.01', 'Collective', 'Combine System', 'Web Core / Roots', 'Cusco', 'CyberSpyder Link Test', 'CydralSpider', 'Desert Realm Spider', 'DeWeb(c) Katalog/Index', 'DienstSpider', 'Digger', 'Direct Hit Grabber', 'DownLoad Express', 'DWCP (Dridus\' Web Cataloging Project)', 'e-collector', 'EbiNess', 'Emacs-w3 Search Engine', 'ananzi', 'esculapio', 'Esther', 'Evliya Celebi', 'FastCrawler', 'Felix IDE', 'Wild Ferret Web Hopper #1, #2, #3', 'FetchRover', 'fido', 'KIT-Fireball', 'Fish search', 'Fouineur', 'Freecrawl', 'FunnelWeb', 'gammaSpider, FocusedCrawler', 'gazz', 'GCreep', 'GetURL', 'Golem', 'Grapnel/0.01 Experiment', 'Griffon', 'Gromit', 'Northern Light Gulliver', 'Harvest', 'havIndex', 'HI (HTML Index) Search', 'Hometown Spider Pro', 'ht://Dig', 'HTMLgobble', 'Hyper-Decontextualizer', 'IBM_Planetwide', 'Popular Iconoclast', 'Ingrid', 'Imagelock', 'IncyWincy', 'Informant', 'Infoseek Sidewinder', 'InfoSpiders', 'Inspector Web', 'IntelliAgent', 'Iron33', 'Israeli-search', 'JavaBee', 'JCrawler', 'Jeeves', 'JumpStation', 'image.kapsi.net', 'Katipo', 'KDD-Explorer', 'Kilroy', 'LabelGrabber', 'larbin', 'legs', 'Link Validator', 'LinkScan', 'LinkWalker', 'Lockon', 'logo.gif Crawler', 'Lycos', 'Mac WWWWorm', 'Magpie', 'marvin/infoseek', 'Mattie', 'MediaFox', 'MerzScope', 'NEC-MeshExplorer', 'MindCrawler', 'mnoGoSearch search engine software', 'moget', 'MOMspider', 'Monster', 'Motor', 'Muncher', 'Muninn', 'Muscat Ferret', 'Mwd.Search', 'Internet Shinchakubin', 'NDSpider', 'Nederland.zoek', 'NetCarta WebMap Engine', 'NetMechanic', 'NetScoop', 'newscan-online', 'NHSE Web Forager', 'Nomad', 'nzexplorer', 'ObjectsSearch', 'Occam', 'HKU WWW Octopus', 'OntoSpider', 'Openfind data gatherer', 'Orb Search', 'Pack Rat', 'PageBoy', 'ParaSite', 'Patric', 'pegasus', 'The Peregrinator', 'PerlCrawler 1.0', 'Phantom', 'PhpDig', 'PiltdownMan', 'Pioneer', 'html_analyzer', 'Portal Juice Spider', 'PGP Key Agent', 'PlumtreeWebAccessor', 'Poppi', 'PortalB Spider', 'GetterroboPlus Puu', 'Raven Search', 'RBSE Spider', 'RoadHouse Crawling System', 'ComputingSite Robi/1.0', 'RoboCrawl Spider', 'RoboFox', 'Robozilla', 'RuLeS', 'Scooter', 'Sleek', 'Search.Aus-AU.COM', 'SearchProcess', 'Senrigan', 'SG-Scout', 'ShagSeeker', 'Shai\'Hulud', 'Sift', 'Site Valet', 'SiteTech-Rover', 'Skymob.com', 'SLCrawler', 'Inktomi Slurp', 'Smart Spider', 'Snooper', 'Spanner', 'Speedy Spider', 'spider_monkey', 'Spiderline Crawler', 'SpiderMan', 'SpiderView(tm)', 'Site Searcher', 'Suke', 'suntek search engine', 'Sven', 'Sygol', 'TACH Black Widow', 'Tarantula', 'tarspider', 'Templeton', 'TeomaTechnologies', 'TITAN', 'TitIn', 'TLSpider', 'UCSD Crawl', 'UdmSearch', 'URL Check', 'URL Spider Pro', 'Valkyrie', 'Verticrawl', 'Victoria', 'vision-search', 'Voyager', 'W3M2', 'WallPaper (alias crawlpaper)', 'the World Wide Web Wanderer', 'w@pSpider by wap4.com', 'WebBandit Web Spider', 'WebCatcher', 'WebCopy', 'webfetcher', 'Webinator', 'weblayers', 'WebLinker', 'WebMirror', 'The Web Moose', 'WebQuest', 'Digimarc MarcSpider', 'WebReaper', 'webs', 'Websnarf', 'WebSpider', 'WebVac', 'webwalk', 'WebWalker', 'WebWatch', 'Wget', 'whatUseek Winona', 'Wired Digital', 'Weblog Monitor', 'w3mir', 'WebStolperer', 'The Web Wombat', 'The World Wide Web Worm', 'WWWC Ver 0.2.5', 'WebZinger', 'XGET'
517 );
518
519 foreach ( $robots as $robot ) {
520 if ( stripos( $_SERVER['HTTP_USER_AGENT'], $robot ) !== false )
521 return true;
522 }
523
524 return false;
525 }
526
527 }
528