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..fc4dcaf 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() {
@@ -43,29 +36,21 @@ 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 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 aa87114..fa725c4 100644
--- browscap.module
+++ browscap.module
@@ -17,57 +17,56 @@
 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,
+  );
   $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/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,
   );
   $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,
   );
 
   // 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,
   );
 
-  $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',
+    'title callback' => 'browscap_useragent_title',
+    'title arguments' => array(4),
     'page callback' => 'browscap_useragent_properties',
     'page arguments' => array(4),
-    'access arguments' => array('access administration pages'),
-    'weight' => 5,
-    'type' => MENU_LOCAL_TASK
+    'access arguments' => array('access site reports'),
+    'weight' => 2,
+    'type' => MENU_LOCAL_TASK,
   );
   return $items;
 }
@@ -81,18 +80,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();
     }
   }
 }
@@ -103,13 +107,20 @@ 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);
   }
 }
 
 /**
+ * Title callback for the useragent page.
+ */
+function browscap_useragent_title(array $useragent) {
+  return check_plain($useragent['browser'] . ' ' . $useragent['version']);
+}
+
+/**
  * ******************** Menu Callbacks ************************
  */
 
@@ -120,33 +131,46 @@ function browscap_cron() {
  * @return array
  */
 function browscap_settings() {
+  $version = variable_get('browscap_version', 0);
   $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'),
-                   )),
+    '#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. View the reports in the <a href="!reports">Browscap reports</a> area.',
-                        array(
-                          '!reports' => url('admin/reports/browscap'),
-                        )),
+    '#description' => t('Monitor all user agents visiting the site.'),
   );
-  return system_settings_form($form);
+
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  $form['refresh'] = array(
+    '#type' => 'submit',
+    '#value' => t('Refresh browscap data'),
+  );
+
+  return $form;
 }
 
 /**
- * Simple page callback to manually refresh the data.
- *
+ * Submit handler for settings page.
  */
-function browscap_refresh() {
-  _browscap_import(FALSE);
-  drupal_goto('admin/settings/browscap');
+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;
+  }
+
 }
 
 /**
@@ -158,85 +182,88 @@ function browscap_refresh() {
  *   - "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 = $query->execute();
 
-  $result = pager_query($query, 50, 0, $query_cnt);
   $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;
 }
 
 /**
@@ -254,7 +281,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;
   }
@@ -262,9 +289,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;
@@ -279,7 +307,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.
@@ -289,36 +317,25 @@ function browscap_is_crawler($useragent = NULL) {
   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) {
+  // 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: '. $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');
@@ -333,14 +350,22 @@ 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;
   }
+
+  // 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);
@@ -365,43 +390,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
- *
- * 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.
  *
@@ -414,7 +422,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;
   }
@@ -426,14 +438,24 @@ 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'));
+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));
   }
-  $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;
 }
