PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.0.9
Post Views Counter v1.0.9
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 11 years ago counter.php 11 years ago cron.php 11 years ago frontend.php 11 years ago functions.php 11 years ago query.php 11 years ago settings.php 11 years ago update.php 11 years ago widgets.php 11 years ago
counter.php
494 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 public function delete_post_views( $post_id ) {
35 global $wpdb;
36
37 $wpdb->delete( $wpdb->prefix . 'post_views', array( 'id' => $post_id ), array( '%d' ) );
38 }
39
40 /**
41 * Check whether user has excluded roles.
42 */
43 public function is_user_roles_excluded( $option ) {
44 $user = wp_get_current_user();
45
46 if ( empty( $user ) )
47 return false;
48
49 $roles = (array) $user->roles;
50
51 if ( ! empty( $roles ) ) {
52 foreach ( $roles as $role ) {
53 if ( in_array( $role, $option, true ) )
54 return true;
55 }
56 }
57
58 return false;
59 }
60
61 /**
62 * Get timestamp convertion.
63 */
64 public function get_timestamp( $type, $number, $timestamp = true ) {
65 $converter = array(
66 'minutes' => 60,
67 'hours' => 3600,
68 'days' => 86400,
69 'weeks' => 604800,
70 'months' => 2592000,
71 'years' => 946080000
72 );
73
74 return ($timestamp ? current_time( 'timestamp', true ) : 0) + $number * $converter[$type];
75 }
76
77 /**
78 * Check whether to count visit via AJAX request.
79 */
80 public function check_post_ajax() {
81 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' ) {
82 // get countable post types
83 $post_types = Post_Views_Counter()->get_attribute( 'options', 'general', 'post_types_count' );
84
85 // get post type
86 $post_type = get_post_type( $post_id );
87
88 // whether to count this post type or not
89 if ( empty( $post_types ) || empty( $post_type ) || $post_type !== $_POST['post_type'] || ! in_array( $post_type, $post_types, true ) )
90 exit;
91
92 // get excluded ips
93 $excluded_ips = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude_ips' );
94
95 // whether to count this ip or not
96 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 ) )
97 exit;
98
99 // get groups to check them faster
100 $groups = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'groups' );
101
102 // whether to count this user
103 if ( is_user_logged_in() ) {
104 // exclude logged in users?
105 if ( in_array( 'users', $groups, true ) )
106 exit;
107 // exclude specific roles?
108 elseif ( in_array( 'roles', $groups, true ) && $this->is_user_roles_excluded( Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'roles' ) ) )
109 exit;
110 }
111 // exclude guests?
112 elseif ( in_array( 'guests', $groups, true ) )
113 exit;
114
115 // whether to count robots
116 if ( $this->is_robot() )
117 exit;
118
119 // cookie already existed?
120 if ( $this->cookie['exists'] ) {
121 // post already viewed but not expired?
122 if ( in_array( $post_id, array_keys( $this->cookie['visited_posts'] ), true ) && current_time( 'timestamp', true ) < $this->cookie['visited_posts'][$post_id] ) {
123 // updates cookie but do not count visit
124 $this->save_cookie( $post_id, $this->cookie, false );
125
126 exit;
127 } else
128 // updates cookie
129 $this->save_cookie( $post_id, $this->cookie );
130 } else {
131 // set new cookie
132 $this->save_cookie( $post_id );
133 }
134
135 // count visit
136 $this->count_visit( $post_id );
137 }
138
139 exit;
140 }
141
142 /**
143 * Check whether to count visit.
144 */
145 public function check_post() {
146 // do not count admin entries
147 if ( is_admin() )
148 return;
149
150 // do we use PHP as counter?
151 if ( Post_Views_Counter()->get_attribute( 'options', 'general', 'counter_mode' ) === 'php' ) {
152 $post_types = Post_Views_Counter()->get_attribute( 'options', 'general', 'post_types_count' );
153
154 // whether to count this post type
155 if ( empty( $post_types ) || ! is_singular( $post_types ) )
156 return;
157
158 $ips = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude_ips' );
159
160 // whether to count this ip
161 if ( ! empty( $ips ) && filter_var( preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) && in_array( $_SERVER['REMOTE_ADDR'], $ips, true ) )
162 return;
163
164 // get groups to check them faster
165 $groups = Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'groups' );
166
167 // whether to count this user
168 if ( is_user_logged_in() ) {
169 // exclude logged in users?
170 if ( in_array( 'users', $groups, true ) )
171 return;
172 // exclude specific roles?
173 elseif ( in_array( 'roles', $groups, true ) && $this->is_user_roles_excluded( Post_Views_Counter()->get_attribute( 'options', 'general', 'exclude', 'roles' ) ) )
174 return;
175 }
176 // exclude guests?
177 elseif ( in_array( 'guests', $groups, true ) )
178 return;
179
180 // whether to count robots
181 if ( $this->is_robot() )
182 return;
183
184 // get post id
185 $id = get_the_ID();
186
187 // cookie already existed?
188 if ( $this->cookie['exists'] ) {
189 // post already viewed but not expired?
190 if ( in_array( $id, array_keys( $this->cookie['visited_posts'] ), true ) && current_time( 'timestamp', true ) < $this->cookie['visited_posts'][$id] ) {
191 // update cookie but do not count visit
192 $this->save_cookie( $id, $this->cookie, false );
193
194 return;
195 } else
196 // update cookie
197 $this->save_cookie( $id, $this->cookie );
198 } else
199 // set new cookie
200 $this->save_cookie( $id );
201
202 // count visit
203 $this->count_visit( $id );
204 }
205 }
206
207 /**
208 * Initialize cookie session.
209 */
210 public function check_cookie() {
211 // do not run in admin except for ajax requests
212 if ( is_admin() && ! (defined( 'DOING_AJAX' ) && DOING_AJAX) )
213 return;
214
215 // is cookie set?
216 if ( isset( $_COOKIE['pvc_visits'] ) && ! empty( $_COOKIE['pvc_visits'] ) ) {
217 $visited_posts = $expirations = array();
218
219 foreach ( $_COOKIE['pvc_visits'] as $content ) {
220 // is cookie valid?
221 if ( preg_match( '/^(([0-9]+b[0-9]+a?)+)$/', $content ) === 1 ) {
222 // get single id with expiration
223 $expiration_ids = explode( 'a', $content );
224
225 // check every expiration => id pair
226 foreach ( $expiration_ids as $pair ) {
227 $pair = explode( 'b', $pair );
228 $expirations[] = (int) $pair[0];
229 $visited_posts[(int) $pair[1]] = (int) $pair[0];
230 }
231 }
232 }
233
234 $this->cookie = array(
235 'exists' => true,
236 'visited_posts' => $visited_posts,
237 'expiration' => max( $expirations )
238 );
239 }
240 }
241
242 /**
243 * Save cookie function.
244 */
245 private function save_cookie( $id, $cookie = array(), $expired = true ) {
246 $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' ) );
247
248 // is this a new cookie?
249 if ( empty( $cookie ) ) {
250 // set cookie
251 setcookie( 'pvc_visits[0]', $expiration . 'b' . $id, $expiration, COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
252 } else {
253 if ( $expired ) {
254 // add new id or chang expiration date if id already exists
255 $cookie['visited_posts'][$id] = $expiration;
256 }
257
258 // create copy for better foreach performance
259 $visited_posts_expirations = $cookie['visited_posts'];
260
261 // get current gmt time
262 $time = current_time( 'timestamp', true );
263
264 // check whether viewed id has expired - no need to keep it in cookie (less size)
265 foreach ( $visited_posts_expirations as $post_id => $post_expiration ) {
266 if ( $time > $post_expiration )
267 unset( $cookie['visited_posts'][$post_id] );
268 }
269
270 // set new last expiration date if needed
271 $cookie['expiration'] = max( $cookie['visited_posts'] );
272
273 $cookies = $imploded = array();
274
275 // create pairs
276 foreach ( $cookie['visited_posts'] as $id => $exp ) {
277 $imploded[] = $exp . 'b' . $id;
278 }
279
280 // split cookie into chunks (4000 bytes to make sure it is safe for every browser)
281 $chunks = str_split( implode( 'a', $imploded ), 4000 );
282
283 // more then one chunk?
284 if ( count( $chunks ) > 1 ) {
285 $last_id = '';
286
287 foreach ( $chunks as $chunk_id => $chunk ) {
288 // new chunk
289 $chunk_c = $last_id . $chunk;
290
291 // is it full-length chunk?
292 if ( strlen( $chunk ) === 4000 ) {
293 // get last part
294 $last_part = strrchr( $chunk_c, 'a' );
295
296 // get last id
297 $last_id = substr( $last_part, 1 );
298
299 // add new full-lenght chunk
300 $cookies[$chunk_id] = substr( $chunk_c, 0, strlen( $chunk_c ) - strlen( $last_part ) );
301 } else {
302 // add last chunk
303 $cookies[$chunk_id] = $chunk_c;
304 }
305 }
306 } else {
307 // only one chunk
308 $cookies[] = $chunks[0];
309 }
310
311 foreach ( $cookies as $key => $value ) {
312 // set cookie
313 setcookie( 'pvc_visits[' . $key . ']', $value, $cookie['expiration'], COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
314 }
315 }
316 }
317
318 /**
319 * Check if object cache is in use.
320 */
321 public function using_object_cache( $using = null ) {
322 $using = wp_using_ext_object_cache( $using );
323
324 if ( $using ) {
325 // check if explicitly disabled by flush_interval setting/option <= 0
326 $flush_interval_number = Post_Views_Counter()->get_attribute( 'options', 'general', 'flush_interval', 'number' );
327 $using = ( $flush_interval_number <= 0 ) ? false : true;
328 }
329
330 return $using;
331 }
332
333 /**
334 * Count visit function.
335 */
336 private function count_visit( $id ) {
337 global $wpdb;
338
339 $cache_key_names = array();
340 $using_object_cache = $this->using_object_cache();
341
342 // get day, week, month and year
343 $date = explode( '-', date( 'W-d-m-Y', current_time( 'timestamp' ) ) );
344
345 foreach ( array(
346 0 => $date[3] . $date[2] . $date[1], // day like 20140324
347 1 => $date[3] . $date[0], // week like 201439
348 2 => $date[3] . $date[2], // month like 201405
349 3 => $date[3], // year like 2014
350 4 => 'total' // total views
351 ) as $type => $period ) {
352 if ( $using_object_cache ) {
353 $cache_key = $id . self::CACHE_KEY_SEPARATOR . $type . self::CACHE_KEY_SEPARATOR . $period;
354 wp_cache_add( $cache_key, 0, self::GROUP );
355 wp_cache_incr( $cache_key, 1, self::GROUP );
356 $cache_key_names[] = $cache_key;
357 } else {
358 // hit the db directly
359 // @TODO: investigate queueing these queries on the 'shutdown' hook instead instead of running them instantly?
360 $this->db_insert( $id, $type, $period, 1 );
361 }
362 }
363
364 // update the list of cache keys to be flushed
365 if ( $using_object_cache && ! empty( $cache_key_names ) ) {
366 $this->update_cached_keys_list_if_needed( $cache_key_names );
367 }
368
369 do_action( 'pvc_after_count_visit', $id );
370
371 return true;
372 }
373
374 /**
375 * Update the single cache key which holds a list of all the cache keys
376 * that need to be flushed to the db.
377 *
378 * The value of that special cache key is a giant string containing key names separated with the `|` character.
379 * Each such key name then consists of 3 elements: $id, $type, $period (separated by a `.` character).
380 * Examples:
381 * 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
382 * A single key is `62053.0.20150327` and that key's data is: $id = 62053, $type = 0, $period = 20150327
383 *
384 * This data format proved more efficient (avoids the (un)serialization overhead completely + duplicates filtering is a string search now)
385 */
386 private function update_cached_keys_list_if_needed( $key_names = array() ) {
387 $existing_list = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
388 if ( ! $existing_list ) {
389 $existing_list = '';
390 }
391
392 $list_modified = false;
393
394 // modify the list contents if/when needed
395 if ( empty( $existing_list ) ) {
396 // the simpler case of an empty initial list where we just
397 // transform the specified key names into a string
398 $existing_list = implode( '|', $key_names );
399 $list_modified = true;
400 } else {
401 // search each specified key name and append it if it's not found
402 foreach ( $key_names as $key_name ) {
403 if ( false === strpos( $existing_list, $key_name ) ) {
404 $existing_list .= '|' . $key_name;
405 $list_modified = true;
406 }
407 }
408 }
409
410 // save modified list back in cache
411 if ( $list_modified ) {
412 wp_cache_set( self::NAME_ALLKEYS, $existing_list, self::GROUP );
413 }
414 }
415
416 /**
417 * Flush views data stored in the persistent object cache into
418 * our custom table and clear the object cache keys when done
419 */
420 public function flush_cache_to_db() {
421 global $wpdb;
422
423 $key_names = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
424
425 if ( ! $key_names ) {
426 $key_names = array();
427 } else {
428 // create an array out of a string that's stored in the cache
429 $key_names = explode( '|', $key_names );
430 }
431
432 foreach ( $key_names as $key_name ) {
433 // get values stored within the key name itself
434 list( $id, $type, $period ) = explode( self::CACHE_KEY_SEPARATOR, $key_name );
435 // get the cached count value
436 $count = wp_cache_get( $key_name, self::GROUP );
437
438 // store cached value in the db
439 $this->db_insert( $id, $type, $period, $count );
440
441 // clear the cache key we just flushed
442 wp_cache_delete( $key_name, self::GROUP );
443 }
444
445 // delete the key holding the list itself after we've successfully flushed it
446 if ( ! empty( $key_names ) ) {
447 wp_cache_delete( self::NAME_ALLKEYS, self::GROUP );
448 }
449
450 return true;
451 }
452
453 /*
454 * Insert or update views count.
455 */
456 private function db_insert( $id, $type, $period, $count = 1 ) {
457 global $wpdb;
458
459 $count = (int) $count;
460
461 if ( ! $count ) {
462 $count = 1;
463 }
464
465 return $wpdb->query(
466 $wpdb->prepare( "
467 INSERT INTO " . $wpdb->prefix . "post_views (id, type, period, count)
468 VALUES (%d, %d, %s, %d)
469 ON DUPLICATE KEY UPDATE count = count + %d", $id, $type, $period, $count, $count
470 )
471 );
472 }
473
474 /**
475 * Check whether visitor is a bot.
476 */
477 private function is_robot() {
478 if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) || (isset( $_SERVER['HTTP_USER_AGENT'] ) && trim( $_SERVER['HTTP_USER_AGENT'] ) === '') )
479 return false;
480
481 $robots = array(
482 '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'
483 );
484
485 foreach ( $robots as $robot ) {
486 if ( stripos( $_SERVER['HTTP_USER_AGENT'], $robot ) !== false )
487 return true;
488 }
489
490 return false;
491 }
492
493 }
494