diff --git README.txt README.txt
index 550a621..b953c06 100644
--- README.txt
+++ README.txt
@@ -27,6 +27,16 @@ browser types for visitors to your site.
 5. The report itself is visible at Administer > Reports > Browscap and 
 several other tabs in that area.
 
+Upgrades
+--------
+
+Upgrading from 6.x to 7.x:
+
+* The upgrade path is untested so far.
+* To view browscap reports in the 6.x module required the
+  "access administration pages" permission.  The 7.x module requires the
+  more appropriate "access site reports" permission.
+
 API
 ---
 
diff --git browscap.info browscap.info
index 3233728..cd3858c 100644
--- browscap.info
+++ browscap.info
@@ -1,3 +1,7 @@
 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
+configure = admin/reports/browscap/settings
\ No newline at end of file
diff --git browscap.install browscap.install
index 1e4bfa1..a28d43d 100644
--- browscap.install
+++ browscap.install
@@ -1,13 +1,6 @@
 <?php
 
 /**
- * Implementation of hook_install.
- */
-function browscap_install() {
-  drupal_install_schema('browscap');
-} // browscap_install
-
-/**
  * Implementation of hook_schema.
  */
 function browscap_schema() {
@@ -44,7 +37,6 @@ function browscap_schema() {
 } // browscap_schema
 
 function browscap_uninstall() {
-  drupal_uninstall_schema('browscap');
   variable_del('browscap_monitor');
   variable_del('browscap_imported');
   variable_del('browscap_version');
@@ -53,19 +45,21 @@ function browscap_uninstall() {
 /**
  * Updates existing tables to UTF-8.
  */
+/**
+ * Does not meet the hook_update_N specification.
+ * @see http://api.drupal.org/api/drupal/modules--system--system.api.php/function/hook_update_N/7
 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 from D6 to D7.
+ * @todo 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 02b023c..e6e4a46 100644
--- browscap.module
+++ browscap.module
@@ -21,47 +21,50 @@ function browscap_menu() {
     '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,
+  );
   $items['admin/reports/browscap/useragents'] = array(
     'title' => 'All user agents',
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('access site reports'),
     'weight' => 1,
-    'type' => MENU_DEFAULT_LOCAL_TASK
+    'type' => MENU_DEFAULT_LOCAL_TASK,
   );
   $items['admin/reports/browscap/browsers'] = array(
     'title' => 'Browsers',
     'page callback' => 'browscap_top_useragents',
     'page arguments' => array('browsers'),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('access site reports'),
     'weight' => 2,
-    'type' => MENU_LOCAL_TASK
+    'type' => MENU_LOCAL_TASK,
   );
   $items['admin/reports/browscap/crawlers'] = array(
     'title' => 'Crawlers',
     'page callback' => 'browscap_top_useragents',
     'page arguments' => array('crawlers'),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('access site reports'),
     'weight' => 3,
-    'type' => MENU_LOCAL_TASK
+    'type' => MENU_LOCAL_TASK,
   );
 
   // SETTINGS PAGE
-  $items['admin/settings/browscap'] = array(
-    'title' => 'Browscap',
+  $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,
   );
 
   $items['admin/reports/browscap/useragent/%browscap_useragent'] = array(
-    'title' => 'Useragent details',
+    'title callback' => 'Useragent details',
     'page callback' => 'browscap_useragent_properties',
     'page arguments' => array(4),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('access site reports'),
     'weight' => 5,
-    'type' => MENU_LOCAL_TASK
+    'type' => MENU_LOCAL_TASK,
   );
   return $items;
 }
@@ -75,18 +78,23 @@ 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'] ? 1 : 0,
+        ))
+        ->expression('counter', 'counter + 1')
+        ->execute();
     }
   }
 }
@@ -97,9 +105,9 @@ function browscap_exit() {
 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) {
     _browscap_import();
-    variable_set('browscap_imported', time());
+    variable_set('browscap_imported', REQUEST_TIME);
   }
 }
 
@@ -114,6 +122,7 @@ function browscap_cron() {
  * @return array
  */
 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>',
@@ -138,7 +147,7 @@ function browscap_settings() {
     '#type' => 'submit',
     '#value' => t('Refresh browscap data'),
   );
-  return system_settings_form($form);
+  return $form;
 }
 
 /**
@@ -166,85 +175,87 @@ function browscap_settings_submit($form, &$form_state) {
  *   - "all": Display all user agents.
  */
 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')
-    );
-  }
+  $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);
 
-  $query .= tablesort_sql($header);
+  $total = $query_total
+    ->execute()
+    ->fetchField();
+  if (!$total) {
+    $total = 1;
+  }
 
-  $result = pager_query($query, 50, 0, $query_cnt);
+  $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();
 
-  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));
+    if ($exists) {
+      $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);
+    $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');
     }
-    else {
-      $rows[] = array($parent, $useragent->counter, $useragent->percent);
-    }
-  }
-  if ($pager = theme('pager', NULL, 50, 0)) {
-    $rows[] = array(array('data' => $pager, 'colspan' => 2));
+    $rows[] = $tablerow;
   }
 
-  $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')));
-  }
-  $output .= theme('table', $header, $rows);
+  $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',
+  );
 
-  print theme('page', $output, $title);
+  return $build;
 }
 
 /**
@@ -262,7 +273,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)) and ($cache->created > REQUEST_TIME - 60*60*24)) {
     // Found a fresh entry in the cache
     $browserinfo = $cache->data;
   }
@@ -270,9 +281,10 @@ 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;
@@ -287,7 +299,7 @@ function browscap_get_browser($useragent = NULL) {
 }
 
 /**
- * Determine whether the current visitor
+ * Determine whether the current visitor is a bot.
  *
  * @param string $useragent
  *   Optional user agent string.
@@ -319,14 +331,14 @@ 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);
+    watchdog('browscap', 'Couldn\'t check version: %error', array('%error' => $versionpage->error), WATCHDOG_ERROR);
     if (!$cron) {
-      drupal_set_message(t('Couldn\'t check version: ') . $versionpage->error, 'error');
+      drupal_set_message(t('Couldn\'t check version: %error', array('%error' => $versionpage->error)), 'error');
     }
     return;
   }
   $browscapversion = trim($versionpage->data);
-  $oldversion = variable_get('browscap_version', 'Never fetched');
+  $oldversion = variable_get('browscap_version', 0);
   if ($browscapversion == $oldversion) {
     // No update, nothing to do here
     watchdog('browscap', 'No new version of browscap to import');
@@ -341,11 +353,12 @@ function _browscap_import($cron = TRUE) {
   $path = variable_get('file_directory_temp', '/tmp');
   $browscapfile = "$path/browscap_$server.ini";
 
+  // @todo 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', t("Couldn't retrieve updated browscap: ") . $browscap->error);
+    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: ") . $browscap->error);
+      drupal_set_message(t("Couldn't retrieve updated browscap: %error", array('%error' => $browscap->error)), 'error');
     }
     return;
   }
@@ -380,19 +393,26 @@ function _browscap_import($cron = TRUE) {
       }
       $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));
+      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: '. $browscapversion);
+    watchdog('browscap', 'New version of browscap imported: %version', array('%version' => $browscapversion));
     if (!$cron) {
-      drupal_set_message(t('New version of browscap imported: ') . $browscapversion);
+      drupal_set_message(t('New version of browscap imported: %version', array('%version' => $browscapversion)));
     }
   }
 }
-
+ 
 /*
  * Undo a recorded browser visit by request
  *
@@ -429,7 +449,11 @@ function browscap_useragent_load($useragent = NULL) {
   if (empty($useragent)) {
     return FALSE;
   }
-  $row = db_fetch_object(db_query("SELECT * FROM {browscap} WHERE useragent = '%s'", $useragent));
+  $row = db_select('browscap', 'b')
+    ->fields('b')
+    ->condition('useragent', $useragent)
+    ->execute()
+    ->fetchObject();
   if (!$row) {
     return FALSE;
   }
@@ -441,14 +465,25 @@ function browscap_useragent_load($useragent = NULL) {
  *
  * @param $useragent
  *   The useragent object, loaded from the database.
- * @return string an HTMl blob representing the data about this useragent.
+ * @return array
+ *   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'));
+  $useragent = (array)$useragent;
+  $header = array(
+    t('Property'),
+    t('Value'),
+  );
+  $rows = array();
   foreach ($useragent as $key => $val) {
     $rows[] = array(check_plain($key), check_plain($val));
   }
-  $output = theme('table', $headers, $rows);
-  return $output;
+  $build = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#attributes' => array('id' => 'browscap-useragent'),
+    '#empty' => t('No useragent properties available.'),
+  );
+  return $build;
 }
