? browscap-664424.patch Index: browscap.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/browscap/browscap.module,v retrieving revision 1.6 diff -u -p -r1.6 browscap.module --- browscap.module 13 Apr 2006 15:49:30 -0000 1.6 +++ browscap.module 1 Jan 2010 17:54:15 -0000 @@ -1,5 +1,5 @@ created > time() - 60*60*24)) { - // Found a fresh entry in the cache - $browserinfo = unserialize($cache->data); - } else { - // Note the 'backwards' use of LIKE - the useragent column contains - // the wildcarded pattern to match against our full-length string - // The ORDER BY chooses the most-specific matching pattern - $browserinfo = db_fetch_object(db_query_range( - "SELECT * from {browscap} WHERE '%s' LIKE useragent ORDER BY LENGTH(useragent) DESC", - $useragent, 0, 1)); - // A couple of fieldnames not in our database, provided for - // compatibility with PHP's get_browser() - $browserinfo->browser_name_pattern = strtr($browserinfo->useragent, '%_', '*?'); - $browserinfo->tables = $browserinfo->htmltables; - cache_set($cacheid, serialize($browserinfo)); - } - - if ($return_array) { - return get_object_vars($browserinfo); - } else { - return $browserinfo; - } -} - -/** - * Implementation of hook_cron(). - */ -function browscap_cron() { - // Has it been a week since the last (attempt to) import? - $last_imported = variable_get('browscap_imported', 0); - if (($last_imported + 60*60*24*7) < time()) { - _browscap_import(); - variable_set('browscap_imported', time()); - } -} - -// A numeric interpretation of browscap.csv's TRUE/FALSE/default fields -function _browscap_boolean($value) { - switch ($value) { - case 'TRUE': - case 'true': - return 1; - case 'FALSE': - case 'false': - case 'default': - default: - return 0; - } -} /** - * If there's a new version of browscap.csv, fetch it and update the - * database. + * Implementation of hook_menu(). + * + * @return array */ -function _browscap_import() { - // Politely check the version for updates before fetching the file - $versionpage = drupal_http_request('http://www.garykeith.com/browsers/version.asp'); - if ($versionpage->error) { - watchdog('browscap', "Couldn't check version: ".$versionpage->error); - return; - } - $browscapversion = trim($versionpage->data); - $oldversion = variable_get('browscap_version', 'Never fetched'); - if ($browscapversion == $oldversion) { - // No update, nothing to do here - watchdog('browscap', 'No new version of browscap to import'); - return; - } +function browscap_menu() { + // LOG PAGES + $items['admin/reports/browscap'] = array( + 'title' => t('Browscap'), + 'description' => t('Browser-specific site statistics.'), + 'page callback' => 'browscap_top_useragents', + 'page arguments' => array('all'), + 'access arguments' => array('access administration pages'), + 'weight' => 5); + $items['admin/reports/browscap/useragents'] = array( + 'title' => t('All user agents'), + 'access arguments' => array('access administration pages'), + 'weight' => 1, + 'type' => MENU_DEFAULT_LOCAL_TASK + ); + $items['admin/reports/browscap/browsers'] = array( + 'title' => t('Browsers'), + 'page callback' => 'browscap_top_useragents', + 'page arguments' => array('browsers'), + 'access arguments' => array('access administration pages'), + 'weight' => 2, + 'type' => MENU_LOCAL_TASK + ); + $items['admin/reports/browscap/crawlers'] = array( + 'title' => t('Crawlers'), + 'page callback' => 'browscap_top_useragents', + 'page arguments' => array('crawlers'), + 'access arguments' => array('access administration pages'), + 'weight' => 3, + 'type' => MENU_LOCAL_TASK + ); - // Fetch the new version, and dump it in the temp directory - $server = $_SERVER['SERVER_NAME']; - $path = variable_get('file_directory_temp', '/tmp'); - $browscapfile = "$path/browscap_$server.csv"; + // SETTINGS PAGE + $items['admin/settings/browscap'] = array( + 'title' => t('Browscap'), + 'description' => t('Enable browscap site statistics.'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('browscap_settings'), + 'access arguments' => array('administer site configuration'), + ); - // If we can, download the zipped version and extract the file - if (is_callable('zip_open')) { - $browscapzipfile = "$path/csv_browscap_$server.zip"; - $browscapzip = drupal_http_request('http://www.garykeith.com/browsers/stream.asp?CSV_BrowsCapZIP'); - if ($browscapzip->error or !trim($browscapzip->data)) { - watchdog('browscap', "Couldn't retrieve updated browscap: ".$browscapzip->error); - return; - } - $browscapzipfp = fopen($browscapzipfile, "w"); - fwrite($browscapzipfp, $browscapzip->data); - fclose($browscapzipfp); - $zip = zip_open($browscapzipfile); - if ($zip) { - while ($zip_entry = zip_read($zip)) { - if (zip_entry_name($zip_entry) == 'browscap.csv') { - if (zip_entry_open($zip, $zip_entry, 'rb')) { - $browscapfp = fopen($browscapfile, 'w'); - fwrite($browscapfp, zip_entry_read($zip_entry, zip_entry_filesize($zip_entry))); - fclose($browscapfp); - break; - } - } - } - zip_close($zip); - } - unlink($browscapzipfile); - // Can't handle zip, get the unzipped version - } else { - $browscap = drupal_http_request('http://www.garykeith.com/browsers/stream.asp?BrowsCapCSV'); - if ($browscap->error or !trim($browscap->data)) { - watchdog('browscap', "Couldn't retrieve updated browscap: ".$browscap->error); - return; - } - $browscapfp = fopen($browscapfile, "w"); - fwrite($browscapfp, $browscap->data); - fclose($browscapfp); - } - - // Parse the .csv file, importing each row (except the first two) - // into the {browscap} table - $browscapfp = fopen($browscapfile, 'r'); - if ($browscapfp) { - // Ignore the first two rows (column headers & version info) - fgetcsv($browscapfp, 1000); fgetcsv($browscapfp, 1000); - while (($browserinfo = fgetcsv($browscapfp, 1000)) != FALSE) { - // Strip brackets - $useragent = substr($browserinfo[0], 1, -1); - // Replace wildcards with SQL equivalents - $useragent = strtr($useragent, '*?', '%_'); - $parent = $browserinfo[1]; - $browser = $browserinfo[2]; - $version = $browserinfo[3]; - $majorversion = $browserinfo[4]; - $minorversion = $browserinfo[5]; - $platform = $browserinfo[6]; - $authenticodeupdate = $browserinfo[7]; - if ($browserinfo[8] == 'default') { - $cssversion = -1; - } else { - $cssversion = $browserinfo[8]; - } - $frames = _browscap_boolean($browserinfo[9]); - $iframes = _browscap_boolean($browserinfo[10]); - $htmltables = _browscap_boolean($browserinfo[11]); - $cookies = _browscap_boolean($browserinfo[12]); - $backgroundsounds = _browscap_boolean($browserinfo[13]); - $vbscript = _browscap_boolean($browserinfo[14]); - $javascript = _browscap_boolean($browserinfo[15]); - $javaapplets = _browscap_boolean($browserinfo[16]); - $activexcontrols = _browscap_boolean($browserinfo[17]); - $cdf = _browscap_boolean($browserinfo[18]); - $aol = _browscap_boolean($browserinfo[19]); - $beta = _browscap_boolean($browserinfo[20]); - $win16 = _browscap_boolean($browserinfo[21]); - $crawler = _browscap_boolean($browserinfo[22]); - $stripper = _browscap_boolean($browserinfo[23]); - $wap = _browscap_boolean($browserinfo[24]); - $netclr = _browscap_boolean($browserinfo[25]); - - db_query('REPLACE {browscap} (useragent,parent,browser,version,'. - 'majorversion,minorversion,platform,authenticodeupdate,'. - 'cssversion,frames,iframes,htmltables,cookies,backgroundsounds,'. - 'vbscript,javascript,javaapplets,activexcontrols,cdf,aol,'. - 'beta,win16,crawler,stripper,wap,netclr) '. - "VALUES('%s','%s','%s','%s','%s','%s','%s','%s',%d,%d,%d,%d,%d,". - '%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)', - $useragent, $parent, $browser, $version, - $majorversion, $minorversion, $platform, $authenticodeupdate, - $cssversion, $frames, $iframes, $htmltables, $cookies, $backgroundsounds, - $vbscript, $javascript, $javaapplets, $activexcontrols, $cdf, $aol, - $beta, $win16, $crawler, $stripper, $wap, $netclr); - } - fclose($browscapfp); - unlink($browscapfile); - - // Phase 2 - for every entry which isn't a top-level entry, plug - // in the data from its parent - $query = 'SELECT useragent,parent from {browscap} WHERE useragent <> parent'; - $result = db_query($query); - while ($destrow = db_fetch_object($result)) { - $query = "SELECT * FROM {browscap} WHERE useragent='%s'"; - $srcrow = db_fetch_object(db_query($query, $destrow->parent)); - db_query("UPDATE {browscap} SET browser='%s',version='%s',". - "majorversion='%s',minorversion='%s',platform='%s',". - "authenticodeupdate='%s',cssversion=%d,frames=%d,iframes=%d,". - "htmltables=%d,cookies=%d,backgroundsounds=%d,vbscript=%d,". - "javascript=%d,javaapplets=%d,activexcontrols=%d,cdf=%d,". - "aol=%d,beta=%d,win16=%d,crawler=%d,stripper=%d,wap=%d,netclr=%d ". - "WHERE useragent='%s'", - $srcrow->browser,$srcrow->version, - $srcrow->majorversion,$srcrow->minorversion,$srcrow->platform, - $srcrow->authenticodeupdate,$srcrow->cssversion,$srcrow->frames,$srcrow->iframes, - $srcrow->htmltables,$srcrow->cookies,$srcrow->backgroundsounds,$srcrow->vbscript, - $srcrow->javascript,$srcrow->javaapplets,$srcrow->activexcontrols,$srcrow->cdf, - $srcrow->aol,$srcrow->beta,$srcrow->win16,$srcrow->crawler,$srcrow->stripper,$srcrow->wap,$srcrow->netclr, - $destrow->useragent); - } - // All done updating the browscap info - invalidate cached data - // from the last version, and record the version we're currently - // using - cache_clear_all('browscap:', TRUE); - variable_set('browscap_version', $browscapversion); - watchdog('browscap', "New version of browscap imported: $browscapversion"); - } + $items['admin/settings/browscap/refresh'] = array( + 'title' => t('Browscap Refresh'), + 'page callback' => 'browscap_refresh', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + ); + $items['admin/reports/browscap/useragent/%browscap_useragent'] = array( + 'title' => 'Useragent details', + 'page callback' => 'browscap_useragent_properties', + 'page arguments' => array(4), + 'access arguments' => array('access administration pages'), + 'weight' => 5, + 'type' => MENU_LOCAL_TASK + ); + return $items; } /** @@ -230,105 +79,75 @@ function _browscap_import() { * Keep tabs on browsers that visit */ function browscap_exit() { - // No point if statistics aren't enabled - if (!module_exist('statistics')) { - return; - } - // If monitoring is enabled, record the browser if (variable_get('browscap_monitor', FALSE)) { - $browser = browscap_get_browser(); - $browserstring = substr(trim($browser->parent), 0, 255); - if ($browserstring == '' or $browserstring == 'Default Browser') { - $browserstring = trim($_SERVER['HTTP_USER_AGENT']); - } - db_query("UPDATE {browscap_statistics} SET counter = counter + 1, is_crawler=%d ". - "WHERE parent='%s'", $browser->crawler, $browserstring); - // If we affected 0 rows, this is the first time we've seen this browser - if (!db_affected_rows()) { - // We must create a new row to store counters for the new browser. - db_query('INSERT INTO {browscap_statistics} (parent,counter,is_crawler) '. - "VALUES('%s', 1, %d)", $browserstring, $browser->crawler); + if ($browser = browscap_get_browser()) { + $browserstring = substr(trim($browser['parent']), 0, 255); + if ($browserstring == '' or $browserstring == 'Default Browser') { + $browserstring = trim($_SERVER['HTTP_USER_AGENT']); + } + db_query("UPDATE {browscap_statistics} SET counter = counter + 1, is_crawler=%d ". + "WHERE parent='%s'", $browser['crawler'], $browserstring); + // If we affected 0 rows, this is the first time we've seen this browser + if (!db_affected_rows()) { + // We must create a new row to store counters for the new browser. + db_query('INSERT INTO {browscap_statistics} (parent,counter,is_crawler) '. + "VALUES('%s', 1, %d)", $browserstring, $browser['crawler']); + } } } } -/* - * Undo a recorded browser visit by request - * - * This function serves the statistics_filter module, enabling it - * to ignore visits from specified roles. +/** + * Implementation of hook_cron(). */ -function browscap_unmonitor() { - // No point if statistics aren't enabled - if (!module_exist('statistics')) { - return; - } - - // If monitoring is enabled, unrecord the browser - if (variable_get('browscap_monitor', FALSE)) { - $browser = browscap_get_browser(); - $browserstring = trim($browser->parent); - if ($browserstring == '' or $browserstring == 'Default Browser') { - $browserstring = trim($_SERVER['HTTP_USER_AGENT']); - } - db_query("UPDATE {browscap_statistics} SET counter = counter - 1, is_crawler=%d ". - "WHERE parent='%s'", $browser->crawler, $browserstring); +function browscap_cron() { + // Has it been a week since the last (attempt to) import? + $last_imported = variable_get('browscap_imported', 0); + if (($last_imported + 60*60*24*7) < time()) { + _browscap_import(); + variable_set('browscap_imported', time()); } } /** - * Implementation of hook_perm(). - * - * @return array + * ******************** Menu Callbacks ************************ */ -function browscap_perm() { - return array('administer browscap'); -} /** - * Turn monitor on or off + * Callback for settings form. + * Turn monitoring on or off and refresh the reference data. * * @return array */ function browscap_settings() { - // Restrict administration of this module - if (user_access('administer browscap') === false) { - return drupal_access_denied(); - } + $form['browscap_data_status'] = array( + '#value' => t('

Browscap data current as of %fileversion. [Refresh now]

', + array( + '%fileversion' => variable_get('browscap_version', t('Never fetched')), + '!refresh' => url('admin/settings/browscap/refresh'), + )), + ); $form['browscap_monitor'] = array( '#type' => 'checkbox', - '#prefix' => t('

Browscap data current as of %fileversion

', - array('%fileversion' => variable_get('browscap_version', 'Never fetched'))), '#title' => t('Monitor browsers'), '#default_value' => variable_get('browscap_monitor', FALSE), - '#description' => t('Monitor all user agents visiting the site.') + '#description' => t('Monitor all user agents visiting the site. View the reports in the Browscap reports area.', + array( + '!reports' => url('admin/reports/browscap'), + )), ); - return $form; + return system_settings_form($form); } /** - * Implementation of hook_menu(). + * Simple page callback to manually refresh the data. * - * @return array */ -function browscap_menu($may_cache) { - $items = array(); - - if ($may_cache && variable_get('browscap_monitor', FALSE)) { - $access = user_access('access statistics'); - - $items[] = array('path' => 'admin/logs/useragents', - 'title' => t('user agents'), 'callback' => 'browscap_top_useragents', - 'access' => $access, 'weight' => 5); - $items[] = array('path' => 'admin/logs/useragents/browsers', - 'title' => t('browsers'), 'access' => $access); - $items[] = array('path' => 'admin/logs/useragents/crawlers', - 'title' => t('crawlers'), 'access' => $access); - } - - return $items; +function browscap_refresh() { + _browscap_import(FALSE); + drupal_goto('admin/settings/browscap'); } /** @@ -366,7 +185,8 @@ function browscap_top_useragents($view = array('data' => t('Count'), 'field' => 'counter', 'sort' => 'desc'), array('data' => t('Percent'), 'field' => 'percent') ); - } else { + } + else { $result = db_query('SELECT SUM(counter) FROM {browscap_statistics} WHERE is_crawler=1'); $total = db_result($result); if (!$total) $total = 1; @@ -380,29 +200,241 @@ function browscap_top_useragents($view = ); } + drupal_set_title($title); + $query .= tablesort_sql($header); $result = pager_query($query, 50, 0, $query_cnt); + $rows = array(); while ($useragent = db_fetch_object($result)) { + if (db_result(db_query_range("SELECT useragent FROM {browscap} WHERE useragent = '%s'", $useragent->parent, 0, 1))) { + $parent = l($useragent->parent, 'admin/reports/browscap/useragent/'. urlencode($useragent->parent)); + } + else { + $parent = check_plain($useragent->parent); + } if ($view == 'all') { if ($useragent->is_crawler) { - $is_crawler = 'Yes'; - } else { - $is_crawler = 'No'; + $is_crawler = t('Yes'); } - $rows[] = array($useragent->parent, $useragent->counter, $useragent->percent, $is_crawler); - } else { - $rows[] = array($useragent->parent, $useragent->counter, $useragent->percent); + else { + $is_crawler = t('No'); + } + $rows[] = array($parent, $useragent->counter, $useragent->percent, $is_crawler); + } + else { + $rows[] = array($parent, $useragent->counter, $useragent->percent); } } if ($pager = theme('pager', NULL, 50, 0)) { $rows[] = array(array('data' => $pager, 'colspan' => 2)); } + $output = ''; + if (empty($rows)) { + $output .= t('It appears that your site has not recorded any visits. If you want to record the visitors to your site you can enable "Monitor browsers" on the Browscap settings screen.', array('!settings_uri' => url('admin/settings/browscap'))); + } $output .= theme('table', $header, $rows); print theme('page', $output, $title); } -?> +/** + * Provide data about the current browser or a known user agent string. + * + * @param string $useragent + * Optional user agent string to test. If empty use the value from the current request. + * @return array + * An array of data about the user agent. + */ +function browscap_get_browser($useragent = NULL) { + if (!$useragent) { + $useragent = $_SERVER['HTTP_USER_AGENT']; + } + // Cache the results + $cacheid = $useragent; + $cache = cache_get($cacheid, 'cache_browscap'); + if ((!empty($cache)) and ($cache->created > time() - 60*60*24)) { + // Found a fresh entry in the cache + $browserinfo = $cache->data; + } + else { + // Note the 'backwards' use of LIKE - the useragent column contains + // the wildcarded pattern to match against our full-length string + // The ORDER BY chooses the most-specific matching pattern + $browserinfo = db_fetch_object(db_query_range( + "SELECT * from {browscap} WHERE '%s' LIKE useragent ORDER BY LENGTH(useragent) DESC", + $useragent, 0, 1)); + // A couple of fieldnames not in our database, provided for + // compatibility with PHP's get_browser() + //$browserinfo->tables = $browserinfo->htmltables; + cache_set($cacheid, $browserinfo, 'cache_browscap'); + } + if (isset($browserinfo) && isset($browserinfo->data)) { + $info = unserialize($browserinfo->data); + $info['useragent'] = $useragent; + $info['browser_name_pattern'] = strtr($browserinfo->useragent, '%_', '*?'); + return $info; + } +} + +/** + * Determine whether the current visitor + * + * @param string $useragent + * Optional user agent string. + */ +function browscap_is_crawler($useragent = NULL) { + $browser = browscap_get_browser($useragent); + return (bool)$browser['crawler']; +} + +// A numeric interpretation of browscap.csv's TRUE/FALSE/default fields +function _browscap_boolean($value) { + switch ($value) { + case 'TRUE': + case 'true': + return 1; + case 'FALSE': + case 'false': + case 'default': + default: + return 0; + } +} + +/** + * If there's a new version of browscap.csv, fetch it and update the + * database. + */ +function _browscap_import($cron = TRUE) { + // Politely check the version for updates before fetching the file + $versionpage = drupal_http_request('http://browsers.garykeith.com/versions/version-number.asp'); + if (isset($versionpage->error)) { + watchdog('browscap', 'Couldn\'t check version: '. $versionpage->error); + if (!$cron) { + drupal_set_message(t('Couldn\'t check version: ') . $versionpage->error, 'error'); + } + return; + } + $browscapversion = trim($versionpage->data); + $oldversion = variable_get('browscap_version', 'Never fetched'); + if ($browscapversion == $oldversion) { + // No update, nothing to do here + watchdog('browscap', 'No new version of browscap to import'); + if (!$cron) { + drupal_set_message(t('No new version of browscap to import')); + } + return; + } + + // Fetch the new version, and dump it in the temp directory + $server = $_SERVER['SERVER_NAME']; + $path = variable_get('file_directory_temp', '/tmp'); + $browscapfile = "$path/browscap_$server.ini"; + + $browscap = drupal_http_request('http://browsers.garykeith.com/stream.asp?PHP_BrowsCapINI'); + if (isset($browscap->error) || empty($browscap)) { + watchdog('browscap', t("Couldn't retrieve updated browscap: ") . $browscap->error); + if (!$cron) { + drupal_set_message(t("Couldn't retrieve updated browscap: ") . $browscap->error); + } + return; + } + $browscapfp = fopen($browscapfile, "w"); + fwrite($browscapfp, $browscap->data); + fclose($browscapfp); + + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + $a = parse_ini_file($browscapfile, TRUE, INI_SCANNER_RAW); + }else{ + $a = parse_ini_file($browscapfile, TRUE); + } + + if ($a) { + // the first entry in the array is the version info + $version = array_shift($a); + foreach ($a as $key => $vals) { + $e = $vals; + // some recursive magic! + $last_parent = array(); + while (isset($vals['Parent']) && $vals['Parent'] !== $last_parent) { + $vals = isset($a[$vals['Parent']]) ? $a[$vals['Parent']] : array(); + $e = array_merge($vals, $e); + $last_parent = $vals; + } + $useragent = strtr($key, '*?', '%_'); + $e = array_change_key_case($e); + db_query("DELETE FROM {browscap} WHERE useragent = '%s'", $useragent); + db_query("INSERT INTO {browscap} (useragent, data) VALUES ('%s', '%s')", $useragent, serialize($e)); + } + + cache_clear_all('*', 'cache_browscap', TRUE); + variable_set('browscap_version', $browscapversion); + watchdog('browscap', 'New version of browscap imported: '. $browscapversion); + if (!$cron) { + drupal_set_message(t('New version of browscap imported: ') . $browscapversion); + } + } +} + +/* + * Undo a recorded browser visit by request + * + * This function serves the statistics_filter module, enabling it + * to ignore visits from specified roles. + */ +function browscap_unmonitor() { + // No point if statistics aren't enabled + if (!module_exists('statistics')) { + return; + } + + // If monitoring is enabled, unrecord the browser + if (variable_get('browscap_monitor', FALSE)) { + $browser = browscap_get_browser(); + $browserstring = trim($browser->parent); + if ($browserstring == '' or $browserstring == 'Default Browser') { + $browserstring = trim($_SERVER['HTTP_USER_AGENT']); + } + db_query("UPDATE {browscap_statistics} SET counter = counter - 1, is_crawler=%d ". + "WHERE parent='%s'", $browser->crawler, $browserstring); + } +} + +/** + * Loads details about a given useragent. Also used as a menu object loader. + * + * @param $useragent + * The name of the useragent to load. + * @return + * The useragent array, FALSE otherwise. + */ +function browscap_useragent_load($useragent = NULL) { + if (empty($useragent)) { + return FALSE; + } + $row = db_fetch_object(db_query('SELECT * FROM {browscap} WHERE useragent = "%s"', $useragent)); + if (!$row) { + return FALSE; + } + return unserialize($row->data); +} + +/** + * Page callback to show details about known useragents. + * + * @param $useragent + * The useragent object, loaded from the database. + * @return string an HTMl blob representing the data about this useragent. + */ +function browscap_useragent_properties($useragent = NULL) { + drupal_set_title(check_plain($useragent['browser'] .' '. $useragent['version'])); + $headers = array(t('Property'), t('Value')); + foreach ($useragent as $key => $val) { + $rows[] = array(check_plain($key), check_plain($val)); + } + $output = theme('table', $headers, $rows); + return $output; +}