import.inc 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /**
  3. * @file
  4. * Browscap data import functions.
  5. */
  6. define('BROWSCAP_IMPORT_OK', 0);
  7. define('BROWSCAP_IMPORT_VERSION_ERROR', 1);
  8. define('BROWSCAP_IMPORT_NO_NEW_VERSION', 2);
  9. define('BROWSCAP_IMPORT_DATA_ERROR', 3);
  10. /**
  11. * Helper function to update the browscap data.
  12. *
  13. * @param bool $cron
  14. * Optional import environment. If false, display status messages to the user
  15. * in addition to logging information with the watchdog.
  16. *
  17. * @return int
  18. * A code indicating the result:
  19. * - BROWSCAP_IMPORT_OK: New data was imported.
  20. * - BROWSCAP_IMPORT_NO_NEW_VERSION: No new data version was available.
  21. * - BROWSCAP_IMPORT_VERSION_ERROR: Checking the current data version failed.
  22. * - BROWSCAP_IMPORT_DATA_ERROR: The data could not be downloaded or parsed.
  23. */
  24. function _browscap_import($cron = TRUE) {
  25. // Check the local browscap data version number.
  26. $local_version = variable_get('browscap_version', 0);
  27. watchdog('browscap', 'Checking for new browscap version...');
  28. // Retrieve the current browscap data version number using HTTP.
  29. $current_version = drupal_http_request('https://www.browscap.org/version-number');
  30. // Log an error if the browscap version number could not be retrieved.
  31. if (isset($current_version->error)) {
  32. // Log a message with the watchdog.
  33. watchdog('browscap', "Couldn't check version: %error", array('%error' => $current_version->error), WATCHDOG_ERROR);
  34. // Display a message to user if the update process was triggered manually.
  35. if ($cron == FALSE) {
  36. drupal_set_message(t("Couldn't check version: %error", array('%error' => $current_version->error)), 'error');
  37. }
  38. return BROWSCAP_IMPORT_VERSION_ERROR;
  39. }
  40. // Sanitize the returned version number.
  41. $current_version = check_plain(trim($current_version->data));
  42. // Compare the current and local version numbers to determine if the browscap
  43. // data requires updating.
  44. if ($current_version == $local_version) {
  45. // Log a message with the watchdog.
  46. watchdog('browscap', 'No new version of browscap to import');
  47. // Display a message to user if the update process was triggered manually.
  48. if ($cron == FALSE) {
  49. drupal_set_message(t('No new version of browscap to import'));
  50. }
  51. return BROWSCAP_IMPORT_NO_NEW_VERSION;
  52. }
  53. // Set options for downloading data with or without compression.
  54. if (function_exists('gzdecode')) {
  55. $options = array(
  56. 'headers' => array('Accept-Encoding' => 'gzip'),
  57. );
  58. }
  59. else {
  60. // The download takes over ten times longer without gzip, and may exceed
  61. // the default timeout of 30 seconds, so we increase the timeout.
  62. $options = array('timeout' => 600);
  63. }
  64. // Retrieve the browscap data using HTTP.
  65. $browscap_data = drupal_http_request('https://www.browscap.org/stream?q=PHP_BrowsCapINI', $options);
  66. // Log an error if the browscap data could not be retrieved.
  67. if (isset($browscap_data->error) || empty($browscap_data)) {
  68. // Log a message with the watchdog.
  69. watchdog('browscap', "Couldn't retrieve updated browscap: %error", array('%error' => $browscap_data->error), WATCHDOG_ERROR);
  70. // Display a message to user if the update process was triggered manually.
  71. if ($cron == FALSE) {
  72. drupal_set_message(t("Couldn't retrieve updated browscap: %error", array('%error' => $browscap_data->error)), 'error');
  73. }
  74. return BROWSCAP_IMPORT_DATA_ERROR;
  75. }
  76. // Decompress the downloaded data if it is compressed.
  77. if (function_exists('gzdecode')) {
  78. $browscap_data->data = gzdecode($browscap_data->data);
  79. }
  80. // Parse the returned browscap data.
  81. // The parse_ini_string function is preferred but only available in PHP 5.3.0.
  82. if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
  83. // Retrieve the browscap data.
  84. $browscap_data = $browscap_data->data;
  85. // Replace 'true' and 'false' with '1' and '0'
  86. $browscap_data = preg_replace(
  87. array(
  88. "/=\s*true\s*\n/",
  89. "/=\s*false\s*\n/",
  90. ),
  91. array(
  92. "=1\n",
  93. "=0\n",
  94. ),
  95. $browscap_data
  96. );
  97. // Parse the browscap data as a string.
  98. $browscap_data = parse_ini_string($browscap_data, TRUE, INI_SCANNER_RAW);
  99. }
  100. else {
  101. // Create a path and filename.
  102. $server = $_SERVER['SERVER_NAME'];
  103. $path = variable_get('file_temporary_path', '/tmp');
  104. $file = "$path/browscap_$server.ini";
  105. // Write the browscap data to a file.
  106. $browscap_file = fopen($file, "w");
  107. fwrite($browscap_file, $browscap_data->data);
  108. fclose($browscap_file);
  109. // Parse the browscap data as a file.
  110. $browscap_data = parse_ini_file($file, TRUE);
  111. }
  112. if ($browscap_data) {
  113. // Find the version information.
  114. // The version information is the first entry in the array.
  115. $version = array_shift($browscap_data);
  116. // Save parsed Browscap data.
  117. _browscap_save_parsed_data($browscap_data);
  118. // Clear the browscap data cache.
  119. cache_clear_all('*', 'cache_browscap', TRUE);
  120. // Update the browscap version and imported time.
  121. variable_set('browscap_version', $current_version);
  122. variable_set('browscap_imported', REQUEST_TIME);
  123. // Log a message with the watchdog.
  124. watchdog('browscap', 'New version of browscap imported: %version', array('%version' => $current_version));
  125. // Display a message to user if the update process was triggered manually.
  126. if ($cron == FALSE) {
  127. drupal_set_message(t('New version of browscap imported: %version', array('%version' => $current_version)));
  128. }
  129. return BROWSCAP_IMPORT_OK;
  130. }
  131. return BROWSCAP_IMPORT_DATA_ERROR;
  132. }
  133. /**
  134. * Saves parsed Browscap data.
  135. *
  136. * The purpose of this function is to perform the queries on the {browscap}
  137. * table as a transaction. This vastly improves performance with database
  138. * engines such as InnoDB and ensures that queries will work while new data
  139. * is being imported.
  140. *
  141. * @param array $browscap_data
  142. * Browscap data that has been parsed with parse_ini_string() or
  143. * parse_ini_file().
  144. */
  145. function _browscap_save_parsed_data(&$browscap_data) {
  146. // Start a transaction. The variable is unused. That's on purpose.
  147. $transaction = db_transaction();
  148. // Delete all data from database.
  149. db_delete('browscap')->execute();
  150. // Prepare the data for insertion.
  151. $import_data = array();
  152. foreach ($browscap_data as $key => $values) {
  153. // Store the current value.
  154. $original_values = $values;
  155. // Replace '*?' with '%_'.
  156. $user_agent = strtr($key, '*?', '%_');
  157. // Remove trailing spaces to prevent "duplicate entry" errors. Databases
  158. // such as MySQL do not preserve trailing spaces when storing VARCHARs.
  159. $user_agent = rtrim($user_agent);
  160. // Change all array keys to lowercase.
  161. $original_values = array_change_key_case($original_values);
  162. // Add to array of data to import.
  163. $import_data[$user_agent] = $original_values;
  164. // Remove processed data to reduce memory usage.
  165. unset($browscap_data[$key]);
  166. }
  167. // Get user agents to insert, removing case-insensitive duplicates.
  168. $user_agents = array_keys($import_data);
  169. $user_agents = array_intersect_key($user_agents, array_unique(array_map('strtolower', $user_agents)));
  170. // Insert all data about user agents into database in chunks.
  171. $user_agents_chunks = array_chunk($user_agents, 1000);
  172. foreach ($user_agents_chunks as $user_agents_chunk) {
  173. $query = db_insert('browscap')
  174. ->fields(array('useragent', 'data'));
  175. foreach ($user_agents_chunk as $user_agent) {
  176. $values = $import_data[$user_agent];
  177. // Recurse through the available user agent information.
  178. $previous_parent = NULL;
  179. $parent = isset($values['parent']) ? $values['parent'] : FALSE;
  180. while ($parent && $parent !== $previous_parent) {
  181. $parent_values = isset($import_data[$parent]) ? $import_data[$parent] : array();
  182. $values = array_merge($parent_values, $values);
  183. $previous_parent = $parent;
  184. $parent = isset($parent_values['parent']) ? $parent_values['parent'] : FALSE;
  185. }
  186. $query->values(array(
  187. 'useragent' => $user_agent,
  188. 'data' => serialize($values),
  189. ));
  190. }
  191. $query->execute();
  192. }
  193. }