xmlsitemap.install 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. <?php
  2. /**
  3. * @file
  4. * Install, update and uninstall functions for the xmlsitemap module.
  5. *
  6. * @ingroup xmlsitemap
  7. */
  8. /**
  9. * Implements hook_requirements().
  10. */
  11. function xmlsitemap_requirements($phase) {
  12. $requirements = array();
  13. $t = get_t();
  14. // Check that required PHP extensions are enabled.
  15. // Note: Drupal 7 already requires the 'xml' extension.
  16. $required_extensions = array('xmlwriter');
  17. $missing_extensions = array_diff($required_extensions, array_filter($required_extensions, 'extension_loaded'));
  18. if (!empty($missing_extensions)) {
  19. $requirements['xmlsitemap_php_extensions'] = array(
  20. 'title' => $t('XML sitemap PHP extensions'),
  21. 'value' => $t('Disabled'),
  22. 'severity' => REQUIREMENT_ERROR,
  23. 'description' => $t("The XML sitemap module requires you to enable the PHP extensions in the following list (see the <a href=\"@xmlsitemap_requirements\">module's system requirements page</a> for more information):", array(
  24. '@xmlsitemap_requirements' => 'http://drupal.org/documentation/modules/xmlsitemap/requirements',
  25. )) . theme('item_list', array('items' => $missing_extensions)),
  26. );
  27. }
  28. if ($phase == 'runtime') {
  29. // If clean URLs are disabled there must not be an actual sitemap.xml in
  30. // the root directory.
  31. if (variable_get('clean_url', 0) && file_exists(DRUPAL_ROOT . '/sitemap.xml')) {
  32. $requirements['xmlsitemap_file'] = array(
  33. 'title' => $t('XML sitemap'),
  34. 'value' => $t('Existing sitemap.xml file found.'),
  35. 'severity' => REQUIREMENT_ERROR,
  36. 'description' => $t('The XML sitemap module cannot display its XML output if there is an existing sitemap.xml file in your website root.'),
  37. );
  38. }
  39. // Check that the base directory and all its subdirectories are writable.
  40. $requirements['xmlsitemap_directory'] = array(
  41. 'title' => $t('XML sitemap cache directory'),
  42. 'value' => $t('Writable'),
  43. );
  44. if (!xmlsitemap_check_directory()) {
  45. $requirements['xmlsitemap_directory']['value'] = $t('Not found or not writable');
  46. $requirements['xmlsitemap_directory']['severity'] = REQUIREMENT_ERROR;
  47. $requirements['xmlsitemap_directory']['description'] = $t('The directory %directory was not found or is not writable by the server. See <a href="@docpage">@docpage</a> for more information.', array('%directory' => xmlsitemap_get_directory(), '@docpage' => 'http://drupal.org/node/34025'));
  48. }
  49. else {
  50. $directories = xmlsitemap_check_all_directories();
  51. foreach ($directories as $directory => $writable) {
  52. if ($writable) {
  53. unset($directories[$directory]);
  54. }
  55. }
  56. if (!empty($directories)) {
  57. $requirements['xmlsitemap_directory']['value'] = $t('Not found or not writable');
  58. $requirements['xmlsitemap_directory']['severity'] = REQUIREMENT_ERROR;
  59. $requirements['xmlsitemap_directory']['description'] = $t('The following directories were not found or are not writable by the server. See <a href="@docpage">@docpage</a> for more information. !directories', array('!directories' => theme('item_list', array('items' => array_keys($directories))), '@docpage' => 'http://drupal.org/node/34025'));
  60. }
  61. }
  62. // The maximum number of links in a sitemap.
  63. $max_links = db_query("SELECT MAX(links) FROM {xmlsitemap_sitemap}")->fetchField();
  64. $max_links_limit = XMLSITEMAP_MAX_SITEMAP_LINKS * XMLSITEMAP_MAX_SITEMAP_LINKS;
  65. if ($max_links > $max_links_limit) {
  66. $requirements['xmlsitemap_link_count'] = array(
  67. 'title' => $t('XML sitemap link count'),
  68. 'value' => $max_links,
  69. 'description' => $t('You have exceeded the number of links that your sitemap can contain (@num).', array('@num' => number_format($max_links))),
  70. 'severity' => REQUIREMENT_ERROR,
  71. );
  72. }
  73. // The maximum number of chunks in a sitemap.
  74. $max_chunks = db_query("SELECT MAX(chunks) FROM {xmlsitemap_sitemap}")->fetchField();
  75. if ($max_chunks > XMLSITEMAP_MAX_SITEMAP_LINKS) {
  76. $requirements['xmlsitemap_chunk_count'] = array(
  77. 'title' => $t('XML sitemap page count'),
  78. 'value' => $max_chunks,
  79. 'description' => $t('You have exceeded the number of sitemap pages (@number).', array('@number' => number_format(XMLSITEMAP_MAX_SITEMAP_LINKS))),
  80. 'severity' => REQUIREMENT_ERROR,
  81. );
  82. if (!in_array(xmlsitemap_get_chunk_size(), array(50000, 'auto'))) {
  83. $requirements['xmlsitemap_chunk_count']['description'] .= ' ' . t('Please increase the number of links per page.');
  84. }
  85. }
  86. // Check maximum file size.
  87. $max_filesize = db_query("SELECT MAX(max_filesize) FROM {xmlsitemap_sitemap}")->fetchField();
  88. $requirements['xmlsitemap_file_size'] = array(
  89. 'title' => $t('XML sitemap maximum file size'),
  90. 'value' => format_size($max_filesize),
  91. );
  92. if ($max_filesize > XMLSITEMAP_MAX_SITEMAP_FILESIZE) {
  93. $requirements['xmlsitemap_file_size']['description'] = $t('You have exceeded the maximum sitemap file size of @size. If possible, decrease the number of links per sitemap page.', array('@size' => format_size(XMLSITEMAP_MAX_SITEMAP_FILESIZE)));
  94. $requirements['xmlsitemap_file_size']['severity'] = REQUIREMENT_ERROR;
  95. }
  96. elseif (!variable_get('xmlsitemap_developer_mode', 0)) {
  97. unset($requirements['xmlsitemap_file_size']);
  98. }
  99. // Check when the cached files were last generated.
  100. $generated_last = variable_get('xmlsitemap_generated_last', 0);
  101. $generated_ago = REQUEST_TIME - $generated_last;
  102. $requirements['xmlsitemap_generated'] = array(
  103. 'title' => $t('XML sitemap'),
  104. 'value' => $generated_last ? $t('Last attempted generation on !date (!interval ago).', array('!date' => format_date($generated_last, 'small'), '!interval' => format_interval($generated_ago))) : $t('Cached files have not been generated yet.'),
  105. 'severity' => REQUIREMENT_OK,
  106. );
  107. if (variable_get('xmlsitemap_rebuild_needed', FALSE) && _xmlsitemap_rebuild_form_access()) {
  108. $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
  109. $requirements['xmlsitemap_generated']['description'] = $t('The XML sitemap data is out of sync and needs to be <a href="@link-rebuild">completely rebuilt<a>.', array('@link-rebuild' => url('admin/config/search/xmlsitemap/rebuild')));
  110. }
  111. elseif (variable_get('xmlsitemap_regenerate_needed', FALSE)) {
  112. if ($max_filesize == 0) {
  113. // A maximum sitemap file size of 0 indicates an error in generation.
  114. $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
  115. }
  116. elseif ($generated_ago >= variable_get('cron_threshold_error', 1209600)) {
  117. $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
  118. }
  119. elseif ($generated_ago >= variable_get('cron_threshold_warning', 172800)) {
  120. $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_WARNING;
  121. }
  122. if ($requirements['xmlsitemap_generated']['severity']) {
  123. $requirements['xmlsitemap_generated']['description'] = $t('The XML cached files are out of date and need to be regenerated. You can <a href="@link-cron">run cron manually</a> to regenerate the sitemap files.', array('@link-cron' => url('admin/reports/status/run-cron', array('query' => drupal_get_destination()))));
  124. }
  125. }
  126. }
  127. return $requirements;
  128. }
  129. /**
  130. * Implements hook_schema().
  131. */
  132. function xmlsitemap_schema() {
  133. // @todo Rename to xmlsitemap_link
  134. $schema['xmlsitemap'] = array(
  135. 'description' => 'The base table for xmlsitemap links.',
  136. 'fields' => array(
  137. 'id' => array(
  138. 'description' => 'Primary key with type; a unique id for the item.',
  139. 'type' => 'int',
  140. 'not null' => TRUE,
  141. 'unsigned' => TRUE,
  142. 'default' => 0,
  143. ),
  144. 'type' => array(
  145. 'description' => 'Primary key with id; the type of item (e.g. node, user, etc.).',
  146. 'type' => 'varchar',
  147. 'length' => 32,
  148. 'not null' => TRUE,
  149. 'default' => '',
  150. ),
  151. 'subtype' => array(
  152. 'description' => 'A sub-type identifier for the link (node type, menu name, term VID, etc.).',
  153. 'type' => 'varchar',
  154. 'length' => 128,
  155. 'not null' => TRUE,
  156. 'default' => '',
  157. ),
  158. 'loc' => array(
  159. 'description' => 'The URL to the item relative to the Drupal path.',
  160. 'type' => 'varchar',
  161. 'length' => 255,
  162. 'not null' => TRUE,
  163. 'default' => '',
  164. ),
  165. 'language' => array(
  166. 'description' => 'The {languages}.language of this link or an empty string if it is language-neutral.',
  167. 'type' => 'varchar',
  168. 'length' => 12,
  169. 'not null' => TRUE,
  170. 'default' => '',
  171. ),
  172. 'access' => array(
  173. 'description' => 'A boolean that represents if the item is viewable by the anonymous user. This field is useful to store the result of node_access() so we can retain changefreq and priority_override information.',
  174. 'type' => 'int',
  175. 'size' => 'tiny',
  176. 'not null' => TRUE,
  177. 'default' => 1,
  178. ),
  179. 'status' => array(
  180. 'description' => 'An integer that represents if the item is included in the sitemap.',
  181. 'type' => 'int',
  182. 'size' => 'tiny',
  183. 'not null' => TRUE,
  184. 'default' => 1,
  185. ),
  186. 'status_override' => array(
  187. 'description' => 'A boolean that if TRUE means that the status field has been overridden from its default value.',
  188. 'type' => 'int',
  189. 'size' => 'tiny',
  190. 'not null' => TRUE,
  191. 'default' => 0,
  192. ),
  193. 'lastmod' => array(
  194. 'description' => 'The UNIX timestamp of last modification of the item.',
  195. 'type' => 'int',
  196. 'unsigned' => TRUE,
  197. 'not null' => TRUE,
  198. 'default' => 0,
  199. ),
  200. 'priority' => array(
  201. 'description' => 'The priority of this URL relative to other URLs on your site. Valid values range from 0.0 to 1.0.',
  202. 'type' => 'float',
  203. 'default' => NULL,
  204. // @todo Convert this field to non-nullable.
  205. //'default' => 0.5,
  206. //'not null' => NULL,
  207. ),
  208. 'priority_override' => array(
  209. 'description' => 'A boolean that if TRUE means that the priority field has been overridden from its default value.',
  210. 'type' => 'int',
  211. 'size' => 'tiny',
  212. 'not null' => TRUE,
  213. 'default' => 0,
  214. ),
  215. 'changefreq' => array(
  216. 'description' => 'The average time in seconds between changes of this item.',
  217. 'type' => 'int',
  218. 'unsigned' => TRUE,
  219. 'not null' => TRUE,
  220. 'default' => 0,
  221. ),
  222. 'changecount' => array(
  223. 'description' => 'The number of times this item has been changed. Used to help calculate the next changefreq value.',
  224. 'type' => 'int',
  225. 'unsigned' => TRUE,
  226. 'not null' => TRUE,
  227. 'default' => 0,
  228. ),
  229. ),
  230. 'primary key' => array('id', 'type'),
  231. 'indexes' => array(
  232. 'loc' => array('loc'),
  233. 'access_status_loc' => array('access', 'status', 'loc'),
  234. 'type_subtype' => array('type', 'subtype'),
  235. 'language' => array('language'),
  236. ),
  237. );
  238. $schema['xmlsitemap_sitemap'] = array(
  239. 'fields' => array(
  240. 'smid' => array(
  241. 'description' => 'The sitemap ID (the hashed value of {xmlsitemap}.context.',
  242. 'type' => 'varchar',
  243. 'length' => 64,
  244. 'not null' => TRUE,
  245. ),
  246. 'context' => array(
  247. 'description' => 'Serialized array with the sitemaps context',
  248. 'type' => 'text',
  249. 'not null' => TRUE,
  250. 'serialize' => TRUE,
  251. ),
  252. 'updated' => array(
  253. 'type' => 'int',
  254. 'unsigned' => TRUE,
  255. 'not null' => TRUE,
  256. 'default' => 0,
  257. ),
  258. 'links' => array(
  259. 'type' => 'int',
  260. 'unsigned' => TRUE,
  261. 'not null' => TRUE,
  262. 'default' => 0,
  263. ),
  264. 'chunks' => array(
  265. 'type' => 'int',
  266. 'unsigned' => TRUE,
  267. 'not null' => TRUE,
  268. 'default' => 0,
  269. ),
  270. 'max_filesize' => array(
  271. 'type' => 'int',
  272. 'unsigned' => TRUE,
  273. 'not null' => TRUE,
  274. 'default' => 0,
  275. ),
  276. //'queued' => array(
  277. // 'type' => 'int',
  278. // 'unsigned' => TRUE,
  279. // 'not null' => TRUE,
  280. // 'default' => 0,
  281. // 'description' => 'Time when this sitemap was queued for regeneration, 0 if not queued.',
  282. //),
  283. ),
  284. 'primary key' => array('smid'),
  285. );
  286. return $schema;
  287. }
  288. /**
  289. * Implements hook_install().
  290. */
  291. function xmlsitemap_install() {
  292. // Set this module's weight to 1 so xmlsitemap_cron() runs after all other
  293. // xmlsitemap_x_cron() runs.
  294. db_update('system')
  295. ->fields(array('weight' => 1))
  296. ->condition('type', 'module')
  297. ->condition('name', 'xmlsitemap')
  298. ->execute();
  299. // Load the module.
  300. drupal_load('module', 'xmlsitemap');
  301. // Insert the homepage link into the {xmlsitemap} table so we do not show an
  302. // empty sitemap after install.
  303. db_insert('xmlsitemap')
  304. ->fields(array(
  305. 'type' => 'frontpage',
  306. 'id' => 0,
  307. 'loc' => '',
  308. 'priority' => variable_get('xmlsitemap_frontpage_priority', 1.0),
  309. 'changefreq' => variable_get('xmlsitemap_frontpage_changefreq', XMLSITEMAP_FREQUENCY_DAILY),
  310. 'language' => LANGUAGE_NONE,
  311. ))
  312. ->execute();
  313. // Insert the default context sitemap.
  314. $context = array();
  315. db_insert('xmlsitemap_sitemap')
  316. ->fields(array(
  317. 'smid' => xmlsitemap_sitemap_get_context_hash($context),
  318. 'context' => serialize($context),
  319. ))
  320. ->execute();
  321. // @todo Does the sitemap show up on first install or is it a 404 page?
  322. }
  323. /**
  324. * Implements hook_enable().
  325. */
  326. function xmlsitemap_enable() {
  327. // Ensure the file cache directory is available and ready.
  328. xmlsitemap_check_directory();
  329. variable_set('xmlsitemap_regenerate_needed', TRUE);
  330. }
  331. /**
  332. * Implements hook_uninstall().
  333. */
  334. function xmlsitemap_uninstall() {
  335. // Remove variables.
  336. drupal_load('module', 'xmlsitemap');
  337. $variables = array_keys(xmlsitemap_variables());
  338. foreach ($variables as $variable) {
  339. variable_del($variable);
  340. }
  341. // Remove the file cache directory.
  342. xmlsitemap_clear_directory(NULL, TRUE);
  343. }
  344. /**
  345. * Implements hook_update_last_removed().
  346. */
  347. function xmlsitemap_update_last_removed() {
  348. return 6201;
  349. }
  350. /**
  351. * Create the {xmlsitemap_sitemap} table and add the sitemap context data.
  352. */
  353. function xmlsitemap_update_6202() {
  354. if (!db_table_exists('xmlsitemap_sitemap')) {
  355. $schema['xmlsitemap_sitemap'] = array(
  356. 'fields' => array(
  357. 'smid' => array(
  358. 'description' => 'Sitemap ID',
  359. 'type' => 'serial',
  360. 'unsigned' => TRUE,
  361. 'not null' => TRUE,
  362. ),
  363. 'context_hash' => array(
  364. 'description' => 'The MD5 hash of the context field.',
  365. 'type' => 'varchar',
  366. 'length' => 32,
  367. 'not null' => TRUE,
  368. 'default' => '',
  369. ),
  370. 'context' => array(
  371. 'description' => 'Serialized array with the sitemaps context',
  372. 'type' => 'text',
  373. 'not null' => TRUE,
  374. ),
  375. 'updated' => array(
  376. 'type' => 'int',
  377. 'unsigned' => TRUE,
  378. 'not null' => TRUE,
  379. 'default' => 0,
  380. ),
  381. 'links' => array(
  382. 'type' => 'int',
  383. 'unsigned' => TRUE,
  384. 'not null' => TRUE,
  385. 'default' => 0,
  386. ),
  387. 'chunks' => array(
  388. 'type' => 'int',
  389. 'unsigned' => TRUE,
  390. 'not null' => TRUE,
  391. 'default' => 0,
  392. ),
  393. ),
  394. 'primary key' => array('smid'),
  395. 'unique keys' => array(
  396. 'context_hash' => array('context_hash'),
  397. ),
  398. );
  399. db_create_table('xmlsitemap_sitemap', $schema['xmlsitemap_sitemap']);
  400. }
  401. // Add the default sitemap(s) and use language contexts if possible.
  402. if (!db_query_range("SELECT 1 FROM {xmlsitemap_sitemap}", 0, 1)->fetchField()) {
  403. // Refresh the schema and load the module if it's disabled.
  404. drupal_get_schema(NULL, TRUE);
  405. drupal_load('module', 'xmlsitemap');
  406. if (module_exists('xmlsitemap_i18n') && $languages = variable_get('xmlsitemap_languages', array())) {
  407. foreach ($languages as $language) {
  408. $sitemap = new stdClass();
  409. $sitemap->context = array('language' => $language);
  410. xmlsitemap_sitemap_save($sitemap);
  411. }
  412. }
  413. else {
  414. $sitemap = new stdClass();
  415. $sitemap->context = array();
  416. xmlsitemap_sitemap_save($sitemap);
  417. }
  418. }
  419. // Language variable is no longer needed, so go ahead and delete it.
  420. variable_del('xmlsitemap_languages');
  421. // Ensure that the sitemaps will be refreshed on next cron.
  422. variable_set('xmlsitemap_generated_last', 0);
  423. variable_set('xmlsitemap_regenerate_needed', TRUE);
  424. }
  425. /**
  426. * Convert the xmlsitemap_max_filesize variable to a max_filesize column
  427. * per-sitemap.
  428. */
  429. function xmlsitemap_update_6203() {
  430. if (db_field_exists('xmlsitemap_sitemap', 'max_filesize')) {
  431. return;
  432. }
  433. // Add the max_filesize column.
  434. $field = array(
  435. 'type' => 'int',
  436. 'unsigned' => TRUE,
  437. 'not null' => TRUE,
  438. 'default' => 0,
  439. );
  440. db_add_field('xmlsitemap_sitemap', 'max_filesize', $field);
  441. // Scan each sitemap directory for the largest file.
  442. drupal_load('module', 'xmlsitemap');
  443. $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  444. foreach ($sitemaps as $sitemap) {
  445. xmlsitemap_sitemap_get_max_filesize($sitemap);
  446. db_update('xmlsitemap_sitemap')
  447. ->fields(array('max_filesize' => $sitemap->max_filesize))
  448. ->condition('smid', $sitemap->smid)
  449. ->execute();
  450. }
  451. variable_del('xmlsitemap_max_filesize');
  452. variable_del('xmlsitemap_max_chunks');
  453. }
  454. /**
  455. * Convert {xmlsitemap}.context_hash to replace {xmlsitemap}.smid.
  456. */
  457. function xmlsitemap_update_6204() {
  458. if (db_field_exists('xmlsitemap_sitemap', 'context_hash')) {
  459. db_drop_unique_key('xmlsitemap_sitemap', 'context_hash');
  460. db_drop_field('xmlsitemap_sitemap', 'smid');
  461. // Rename context_hash to the new smid column.
  462. $smid_field = array(
  463. 'description' => 'The sitemap ID (the hashed value of {xmlsitemap}.context.',
  464. 'type' => 'varchar',
  465. 'length' => 64,
  466. 'not null' => TRUE,
  467. );
  468. db_change_field('xmlsitemap_sitemap', 'context_hash', 'smid', $smid_field);
  469. // Re-add the primary key now that the smid field is changed.
  470. // We don't need to drop the primary key since we already dropped the field
  471. // that was the primary key.
  472. db_add_primary_key('xmlsitemap_sitemap', array('smid'));
  473. }
  474. _xmlsitemap_sitemap_rehash_all();
  475. }
  476. /**
  477. * Update empty string languages to LANGUAGE_NONE.
  478. */
  479. function xmlsitemap_update_7200() {
  480. db_update('xmlsitemap')
  481. ->fields(array('language' => LANGUAGE_NONE))
  482. ->condition('language', '')
  483. ->execute();
  484. }
  485. /**
  486. * Re-run xmlsitemap_update_6202() to ensure sitemap data has been added.
  487. */
  488. function xmlsitemap_update_7201() {
  489. xmlsitemap_update_6202();
  490. }
  491. /**
  492. * Convert the xmlsitemap_max_filesize variable to a max_filesize column
  493. * per-sitemap.
  494. */
  495. function xmlsitemap_update_7202() {
  496. xmlsitemap_update_6203();
  497. }
  498. /**
  499. * Convert {xmlsitemap}.context_hash to replace {xmlsitemap}.smid.
  500. */
  501. function xmlsitemap_update_7203() {
  502. xmlsitemap_update_6204();
  503. _xmlsitemap_sitemap_rehash_all();
  504. }
  505. function _xmlsitemap_sitemap_rehash_all() {
  506. // Reload the schema cache and reprocess all sitemap hashes into smids.
  507. drupal_load('module', 'xmlsitemap');
  508. drupal_get_schema(NULL, TRUE);
  509. // Force a rehash of all sitemaps.
  510. $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  511. foreach ($sitemaps as $sitemap) {
  512. $hash = xmlsitemap_sitemap_get_context_hash($sitemap->context);
  513. if ($hash != $sitemap->smid) {
  514. xmlsitemap_sitemap_save($sitemap);
  515. }
  516. }
  517. }