diff --git browscap.info browscap.info
index 46d3f74..a9594e8 100644
--- browscap.info
+++ browscap.info
@@ -1,4 +1,7 @@
 ; $Id$
 name = Browscap
 description = "Provides statistics on browsers and a replacement for PHPs get_browser() function."
-core = 6.x
+core = 7.x
+files[] = browscap.module
+files[] = browscap.install
+files[] = includes/admin.inc
diff --git browscap.install browscap.install
index 04a5c53..a5a7ac6 100644
--- browscap.install
+++ browscap.install
@@ -2,14 +2,7 @@
 // $Id$
 
 /**
- * Implementation of hook_install.
- */
-function browscap_install() {
-  drupal_install_schema('browscap');
-} // browscap_install
-
-/**
- * Implementation of hook_schema.
+ * Implements hook_schema().
  */
 function browscap_schema() {
   $schema['browscap'] = array(
@@ -42,31 +35,22 @@ function browscap_schema() {
     ),
   );
   return $schema;
-} // browscap_schema
+}
 
+/**
+ * Implements hook_uninstall().
+ */
 function browscap_uninstall() {
-  drupal_uninstall_schema('browscap');
   variable_del('browscap_monitor');
   variable_del('browscap_imported');
   variable_del('browscap_version');
-} // browscap_uninstall
-
-/**
- * Updates existing tables to UTF-8.
- */
-function browscap_update_1() {
-  return _system_update_utf8(array('browscap'));
-  return _system_update_utf8(array('browscap_statistics'));
-} // browscap_update_1
+}
 
 /**
- * Implementation of hook_update_N.
- * Update from D5 to D6.
+ * Update to Drupal 7.
+ * Will be solved later. Set the version number for new installs for now.
  */
-function browscap_update_6000() {
+function browscap_update_7000() {
   $ret = array();
-  if (!db_column_exists('cache_browscap', 'serialized')) {
-    db_add_field($ret, 'cache_browscap', 'serialized', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
-  }
   return $ret;
 }
diff --git browscap.module browscap.module
index 5cf1cae..6af387a 100644
--- browscap.module
+++ browscap.module
@@ -7,74 +7,77 @@
  */
 
 /**
- * ******************** Drupal Hooks ************************
- */
-
-/**
- * Implementation of hook_menu().
- *
- * @return array
+ * Implements hook_menu().
  */
 function browscap_menu() {
-  // LOG PAGES
   $items['admin/reports/browscap'] = array(
-    'title' => t('Browscap'),
-    'description' => t('Browser-specific site statistics.'),
+    'title' => 'Browscap',
+    'description' => 'Browser-specific site statistics.',
     'page callback' => 'browscap_top_useragents',
     'page arguments' => array('all'),
-    'access arguments' => array('access administration pages'),
-    'weight' => 5);
+    'access arguments' => array('access site reports'),
+    'weight' => 5,
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'includes/admin.inc',
+  );
   $items['admin/reports/browscap/useragents'] = array(
-    'title' => t('All user agents'),
-    'access arguments' => array('access administration pages'),
+    'title' => 'All user agents',
+    'access arguments' => array('access site reports'),
     'weight' => 1,
-    'type' => MENU_DEFAULT_LOCAL_TASK
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/reports/browscap/useragent/%browscap_useragent'] = array(
+    'title callback' => 'browscap_useragent_title',
+    'title arguments' => array(4),
+    'page callback' => 'browscap_useragent_properties',
+    'page arguments' => array(4),
+    'access arguments' => array('access site reports'),
+    'weight' => 2,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
   );
   $items['admin/reports/browscap/browsers'] = array(
-    'title' => t('Browsers'),
+    'title' => 'Browsers',
     'page callback' => 'browscap_top_useragents',
     'page arguments' => array('browsers'),
-    'access arguments' => array('access administration pages'),
-    'weight' => 2,
-    'type' => MENU_LOCAL_TASK
+    'access arguments' => array('access site reports'),
+    'weight' => 3,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
   );
   $items['admin/reports/browscap/crawlers'] = array(
-    'title' => t('Crawlers'),
+    'title' => 'Crawlers',
     'page callback' => 'browscap_top_useragents',
     'page arguments' => array('crawlers'),
-    'access arguments' => array('access administration pages'),
-    'weight' => 3,
-    'type' => MENU_LOCAL_TASK
+    'access arguments' => array('access site reports'),
+    'weight' => 4,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
   );
 
-  // SETTINGS PAGE
-  $items['admin/settings/browscap'] = array(
-    'title' => t('Browscap'),
-    'description' => t('Enable browscap site statistics.'),
+  $items['admin/reports/browscap/settings'] = array(
+    'title' => 'Settings',
+    'description' => 'Enable browscap site statistics.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('browscap_settings'),
     'access arguments' => array('administer site configuration'),
+    'weight' => 100,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/admin.inc',
   );
 
-  $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;
 }
 
 /**
- * Implementation of hook_exit().
+ * Title callback for the useragent page.
+ */
+function browscap_useragent_title(array $useragent) {
+  return check_plain($useragent['browser'] . ' ' . $useragent['version']);
+}
+
+/**
+ * Implements hook_exit().
  *
  * Keep tabs on browsers that visit
  */
@@ -82,162 +85,61 @@ function browscap_exit() {
   // If monitoring is enabled, record the browser
   if (variable_get('browscap_monitor', FALSE)) {
     if ($browser = browscap_get_browser()) {
-      $browserstring = substr(trim($browser['parent']), 0, 255);
-      if ($browserstring == '' or $browserstring == 'Default Browser') {
+      $browserstring = empty($browser['parent'])
+	? 'Default Browser'
+	: substr(trim($browser['parent']), 0, 255);
+      if ($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 (empty($browser['crawler'])) {
+        $browser['crawler'] = 0;
       }
+      db_merge('browscap_statistics')
+        ->key(array('parent' => $browserstring))
+        ->fields(array(
+          'counter' => 1,
+          'is_crawler' => $browser['crawler'],
+        ))
+        ->expression('counter', 'counter + 1')
+        ->execute();
     }
   }
 }
 
 /**
- * Implementation of hook_cron().
+ * Implements 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()) {
+  if (($last_imported + 60*60*24*7) < REQUEST_TIME) {
+    require_once(drupal_get_path('module','browscap') . '/includes/admin.inc');
     _browscap_import();
-    variable_set('browscap_imported', time());
+    variable_set('browscap_imported', REQUEST_TIME);
   }
 }
 
 /**
- * ******************** Menu Callbacks ************************
- */
-
-/**
- * Callback for settings form.
- * Turn monitoring on or off and refresh the reference data.
- *
- * @return array
- */
-function browscap_settings() {
-  $form['browscap_data_status'] = array(
-    '#value' => t('<p>Browscap data current as of %fileversion. [<a href="!refresh">Refresh now</a>]</p>',
-                   array(
-                     '%fileversion' => variable_get('browscap_version', t('Never fetched')),
-                     '!refresh' => url('admin/settings/browscap/refresh'),
-                   )),
-  );
-
-  $form['browscap_monitor'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Monitor browsers'),
-    '#default_value' => variable_get('browscap_monitor', FALSE),
-    '#description' => t('Monitor all user agents visiting the site. View the reports in the <a href="!reports">Browscap reports</a> area.',
-                        array(
-                          '!reports' => url('admin/reports/browscap'),
-                        )),
-  );
-  return system_settings_form($form);
-}
-
-/**
- * Simple page callback to manually refresh the data.
- *
- */
-function browscap_refresh() {
-  _browscap_import(FALSE);
-  drupal_goto('admin/settings/browscap');
-}
-
-/**
- * Menu callback; presents the user agents monitoring page.
+ * Loads details about a given useragent. Also used as a menu object loader.
  *
- * @param $view
- *   - "browsers": Only display "real" browsers
- *   - "crawlers": Only display search engine crawlers
- *   - "all": Display all user agents.
+ * @param $useragent
+ *   The name of the useragent to load.
+ * @return
+ *   The useragent array, FALSE otherwise.
  */
-function browscap_top_useragents($view = 'all') {
-  if ($view == 'all') {
-    $result = db_query('SELECT SUM(counter) FROM {browscap_statistics}');
-    $total = db_result($result);
-    if (!$total) $total = 1;
-    $query = "SELECT parent,counter,(100*counter)/$total as percent,is_crawler FROM {browscap_statistics}";
-    $query_cnt = 'SELECT COUNT(parent) FROM {browscap_statistics}';
-    $title = t('Top user agents');
-    $header = array(
-      array('data' => t('User agent'), 'field' => 'parent'),
-      array('data' => t('Count'), 'field' => 'counter', 'sort' => 'desc'),
-      array('data' => t('Percent'), 'field' => 'percent'),
-      array('data' => t('Crawler?'), 'field' => 'is_crawler')
-    );
-  }
-  elseif ($view == 'browsers') {
-    $result = db_query('SELECT SUM(counter) FROM {browscap_statistics} WHERE is_crawler=0');
-    $total = db_result($result);
-    if (!$total) $total = 1;
-    $query = "SELECT parent,counter,(100*counter)/$total as percent FROM {browscap_statistics} WHERE is_crawler=0";
-    $query_cnt = 'SELECT COUNT(parent) FROM {browscap_statistics} WHERE is_crawler=0';
-    $title = t('Top browsers');
-    $header = array(
-      array('data' => t('Browser'), 'field' => 'parent'),
-      array('data' => t('Count'), 'field' => 'counter', 'sort' => 'desc'),
-      array('data' => t('Percent'), 'field' => 'percent')
-    );
-  }
-  else {
-    $result = db_query('SELECT SUM(counter) FROM {browscap_statistics} WHERE is_crawler=1');
-    $total = db_result($result);
-    if (!$total) $total = 1;
-    $query = "SELECT parent,counter,(100*counter)/$total as percent FROM {browscap_statistics} WHERE is_crawler=1";
-    $query_cnt = 'SELECT COUNT(parent) FROM {browscap_statistics} WHERE is_crawler=1';
-    $title = t('Top crawlers');
-    $header = array(
-      array('data' => t('Crawler'), 'field' => 'parent'),
-      array('data' => t('Count'), 'field' => 'counter', 'sort' => 'desc'),
-      array('data' => t('Percent'), 'field' => 'percent')
-    );
-  }
-
-  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 = t('Yes');
-      }
-      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));
+function browscap_useragent_load($useragent = NULL) {
+  if (empty($useragent)) {
+    return FALSE;
   }
-
-  $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 <a href="!settings_uri">Browscap settings screen</a>.', array('!settings_uri' => url('admin/settings/browscap')));
+  $record = db_select('browscap', 'b')
+    ->fields('b')
+    ->condition('useragent', $useragent)
+    ->execute()
+    ->fetchObject();
+  if (!$record) {
+    return FALSE;
   }
-  $output .= theme('table', $header, $rows);
-
-  print theme('page', $output, $title);
+  return unserialize($record->data);
 }
 
 /**
@@ -255,7 +157,7 @@ function browscap_get_browser($useragent = NULL) {
   // Cache the results
   $cacheid = $useragent;
   $cache = cache_get($cacheid, 'cache_browscap');
-  if ((!empty($cache)) and ($cache->created > time() - 60*60*24)) {
+  if ((!empty($cache)) && ($cache->created > REQUEST_TIME - 60*60*24)) {
     // Found a fresh entry in the cache
     $browserinfo = $cache->data;
   }
@@ -263,9 +165,9 @@ function browscap_get_browser($useragent = NULL) {
     // 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));
+    $browserinfo = db_query("SELECT * FROM {browscap} WHERE :useragent LIKE useragent ORDER BY LENGTH(useragent) DESC",
+      array(':useragent' => $useragent))
+      ->fetchObject();
     // A couple of fieldnames not in our database, provided for
     // compatibility with PHP's get_browser()
     //$browserinfo->tables = $browserinfo->htmltables;
@@ -289,152 +191,3 @@ 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', '>=')) {
-    $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', %b)", $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;
-}
diff --git includes/admin.inc includes/admin.inc
new file mode 100644
index 0000000..153fe7d
--- /dev/null
+++ includes/admin.inc
@@ -0,0 +1,257 @@
+<?php
+// $Id$
+
+/**
+ * Callback for settings form.
+ * Turn monitoring on or off and refresh the reference data.
+ */
+function browscap_settings() {
+  $version = variable_get('browscap_version', 0);
+  $form['browscap_data_status'] = array(
+    '#markup' => '<p>' . t('Current browscap data version: %fileversion.',
+                   array('%fileversion' => $version ? $version : t('Never fetched'))) . '</p>',
+  );
+
+  $form['browscap_monitor'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Monitor browsers'),
+    '#default_value' => variable_get('browscap_monitor', FALSE),
+    '#description' => t('Monitor all user agents visiting the site.'),
+  );
+
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  $form['refresh'] = array(
+    '#type' => 'submit',
+    '#value' => t('Refresh browscap data'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for settings page.
+ */
+function browscap_settings_submit($form, &$form_state) {
+  switch ($form_state['clicked_button']['#id']) {
+    case 'edit-save':
+      variable_set('browscap_monitor', $form_state['values']['browscap_monitor']);
+      break;
+
+    case 'edit-refresh':
+      _browscap_import(FALSE);
+      break;
+  }
+
+}
+
+/**
+ * Menu callback; presents the user agents monitoring page.
+ *
+ * @param $view
+ *   - "browsers": Only display "real" browsers
+ *   - "crawlers": Only display search engine crawlers
+ *   - "all": Display all user agents.
+ */
+function browscap_top_useragents($view = 'all') {
+  $header = array(
+    0 => array('data' => t('User agent'), 'field' => 'parent'),
+    1 => array('data' => t('Count'), 'field' => 'counter', 'sort' => 'desc'),
+    2 => array('data' => t('Percent'), 'field' => 'counter'),
+    3 => array('data' => t('Crawler?'), 'field' => 'is_crawler')
+  );
+
+  $query_total = db_select('browscap_statistics', 'bs');
+  $query_total->addExpression('SUM(bs.counter)');
+
+  $query = db_select('browscap_statistics', 'bs')
+    ->fields('bs', array('parent', 'counter', 'is_crawler'))
+    ->extend('PagerDefault')
+    ->extend('TableSort')
+    ->limit(50)
+    ->orderByHeader($header);
+
+  switch ($view) {
+    case 'browsers':
+      $title = t('Top browsers');
+      $header[0]['data'] = t('Browser');
+      unset($header[3]);
+      $query->condition('is_crawler', 0);
+      $query_total->condition('is_crawler', 0);
+      break;
+
+    case 'crawlers':
+      $title = t('Top crawlers');
+      $header[0]['data'] = t('Crawler');
+      unset($header[3]);
+      $query->condition('is_crawler', 1);
+      $query_total->condition('is_crawler', 1);
+      break;
+
+    default:
+      $title = t('Top user agents');
+      break;
+  }
+  drupal_set_title($title);
+
+  $total = $query_total
+    ->execute()
+    ->fetchField();
+  if (!$total) {
+    $total = 1;
+  }
+
+  $result = $query->execute();
+
+  $rows = array();
+  foreach ($result as $useragent) {
+    $exists = (bool) db_query_range('SELECT 1 FROM {browscap} WHERE useragent = :useragent', 0, 1,
+      array(':useragent' => $useragent->parent))
+      ->fetchField();
+
+    if ($exists) {
+      $parent = l($useragent->parent, 'admin/reports/browscap/useragent/' . urlencode($useragent->parent));
+    }
+    else {
+      $parent = check_plain($useragent->parent);
+    }
+    $tablerow = array();
+    $tablerow[] = $parent;
+    $tablerow[] = $useragent->counter;
+    $tablerow[] = round(100 * $useragent->counter / $total, 4);
+    if (!in_array($view, array('browsers', 'crawlers'))) {
+      $tablerow[] = $useragent->is_crawler ? t('Yes') : t('No');
+    }
+    $rows[] = $tablerow;
+  }
+
+  $build['browscap_statistics_table'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => 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 <a href="!settings_uri">Browscap settings screen</a>.', array('!settings_uri' => url('admin/reports/browscap/settings'))),
+  );
+  $build['browscap_statistics_pager'] = array(
+    '#theme' => 'pager',
+  );
+
+  return $build;
+}
+
+/**
+ * If there's a new version of browscap.csv, fetch it and update the database.
+ */
+function _browscap_import($cron = TRUE) {
+  // Try to allocate enough time to parse and import the data.
+  drupal_set_time_limit(240);
+
+  // 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: %error", array('%error' => $versionpage->error), WATCHDOG_ERROR);
+    if (!$cron) {
+      drupal_set_message(t("Couldn't check version: %error", array('%error' => $versionpage->error)), 'error');
+    }
+    return;
+  }
+
+  $browscapversion = trim($versionpage->data);
+  $oldversion = variable_get('browscap_version', 0);
+  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";
+
+  // This probably ought to be in a settings variable.
+  $browscap = drupal_http_request('http://browsers.garykeith.com/stream.asp?PHP_BrowsCapINI');
+  if (isset($browscap->error) || empty($browscap)) {
+    watchdog('browscap', "Couldn't retrieve updated browscap: %error", array('%error' => $browscap->error), WATCHDOG_ERROR);
+    if (!$cron) {
+      drupal_set_message(t("Couldn't retrieve updated browscap: %error", array('%error' => $browscap->error)), 'error');
+    }
+    return;
+  }
+
+  // The file as downloaded from browser.garykeith.com has semicolon (;) and
+  // single-quote (') characters in the section strings, either of which will
+  // cause parse_ini_file() to abort and return FALSE.  To fix this, simply
+  // escape them with the backslash character.
+  $browscap->data = preg_replace(array("/(?<![;\n])([;'])/",'/\A\\\\/'),array('\\\\$1',''),$browscap->data);
+
+  $browscapfp = fopen($browscapfile, "w");
+  fwrite($browscapfp, $browscap->data);
+  fclose($browscapfp);
+ 
+  $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_delete('browscap')
+        ->condition('useragent', $useragent)
+        ->execute();
+      db_insert('browscap')
+        ->fields(array(
+         'useragent' => $useragent,
+         'data' => serialize($e)
+        ))
+        ->execute();
+    }
+
+    cache_clear_all('*', 'cache_browscap', TRUE);
+    variable_set('browscap_version', $browscapversion);
+    watchdog('browscap', 'New version of browscap imported: %version', array('%version' => $browscapversion));
+    if (!$cron) {
+      drupal_set_message(t('New version of browscap imported: %version', array('%version' => $browscapversion)));
+    }
+  }
+}
+
+/**
+ * Page callback to show details about known useragents.
+ *
+ * @param $useragent
+ *   The useragent object, loaded from the database.
+ * @return array
+ *   the data about this useragent.
+ */
+function browscap_useragent_properties(array $useragent) {
+  $header = array(
+    t('Property'),
+    t('Value'),
+  );
+  $rows = array();
+  foreach ($useragent as $key => $val) {
+    $rows[] = array(check_plain($key), check_plain($val));
+  }
+  $build = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#attributes' => array('id' => 'browscap-useragent'),
+    '#empty' => t('No useragent properties available.'),
+  );
+  return $build;
+}
