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 @@ '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('
Browscap data current as of %fileversion. [Refresh now]
', - array( - '%fileversion' => variable_get('browscap_version', t('Never fetched')), - '!refresh' => url('admin/settings/browscap/refresh'), - )), + '#markup' => '' . t('Current browscap data version: %fileversion.', + array('%fileversion' => $version ? $version : t('Never fetched'))) . '
', ); $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 Browscap reports 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 Browscap settings screen.', 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 Browscap settings screen.', 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("/(?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; }