xmlsitemap.module

  1. 7.x xmlsitemap/xmlsitemap.module
  2. 6.x xmlsitemap/xmlsitemap.module

Main file for the xmlsitemap module.

Functions

Namesort descending Description
xmlsitemap_calculate_changefreq Calculates the average interval between UNIX timestamps.
xmlsitemap_check_all_directories
xmlsitemap_check_directory Check that the sitemap files directory exists and is writable.
xmlsitemap_clear_directory
xmlsitemap_cron Implements hook_cron().
xmlsitemap_directory_move Move a directory to a new location.
xmlsitemap_entity_query_alter Implements hook_entity_query_alter().
xmlsitemap_field_attach_delete_bundle Implements hook_field_attach_delete_bundle().
xmlsitemap_field_attach_rename_bundle Implements hook_field_attach_rename_bundle().
xmlsitemap_form_submit_flag_regenerate Submit handler; Set the regenerate needed flag if variables have changed.
xmlsitemap_get_bundle_path
xmlsitemap_get_changefreq Determine the frequency of updates to a link.
xmlsitemap_get_changefreq_options @todo Document this function. @todo Make these translatable
xmlsitemap_get_chunk_count Get the current number of sitemap chunks.
xmlsitemap_get_chunk_size Get the sitemap chunk size.
xmlsitemap_get_context_info
xmlsitemap_get_current_context Get the sitemap context of the current request.
xmlsitemap_get_directory
xmlsitemap_get_link_count Get the current number of sitemap links.
xmlsitemap_get_link_info Returns information about supported sitemap link types.
xmlsitemap_get_link_type_enabled_bundles
xmlsitemap_get_link_type_indexed_status
xmlsitemap_get_operation_link
xmlsitemap_help Implements hook_help().
xmlsitemap_hook_info Implements hook_hook_info().
xmlsitemap_language_load Load a language object by its language code.
xmlsitemap_link_bundle_access
xmlsitemap_link_bundle_delete
xmlsitemap_link_bundle_load
xmlsitemap_link_bundle_rename
xmlsitemap_link_bundle_settings_form_submit
xmlsitemap_link_bundle_settings_save
xmlsitemap_link_delete Delete a specific sitemap link from the database.
xmlsitemap_link_delete_multiple Delete multiple sitemap links from the database.
xmlsitemap_link_load Load a specific sitemap link from the database.
xmlsitemap_link_load_multiple Load sitemap links from the database.
xmlsitemap_link_save Saves or updates a sitemap link.
xmlsitemap_link_type_rename Rename a link type.
xmlsitemap_link_update_multiple Perform a mass update of sitemap data.
xmlsitemap_menu Implements hook_menu().
xmlsitemap_modules_disabled Implements hook_modules_disabled().
xmlsitemap_modules_enabled Implements hook_modules_enabled().
xmlsitemap_permission Implements hook_perm().
xmlsitemap_process_form_link_options
xmlsitemap_recalculate_changefreq Recalculate the changefreq of a sitemap link.
xmlsitemap_restore_user Restore the user that was originally loaded.
xmlsitemap_robotstxt Implements hook_robotstxt().
xmlsitemap_run_unprogressive_batch Run a not-progressive batch operation.
xmlsitemap_sitemap_delete Delete an XML sitemap.
xmlsitemap_sitemap_delete_multiple Delete multiple XML sitemaps.
xmlsitemap_sitemap_get_context_hash
xmlsitemap_sitemap_get_file Return the expected file path for a specific sitemap chunk.
xmlsitemap_sitemap_get_max_filesize Find the maximum file size of all a sitemap's XML files.
xmlsitemap_sitemap_load Load an XML sitemap array from the database.
xmlsitemap_sitemap_load_by_context Load an XML sitemap array from the database based on its context.
xmlsitemap_sitemap_load_multiple Load multiple XML sitemaps from the database.
xmlsitemap_sitemap_save Save changes to an XML sitemap or add a new XML sitemap.
xmlsitemap_sitemap_uri Returns the uri elements of an XML sitemap.
xmlsitemap_switch_user Set the current user stored in $GLOBALS['user'].
xmlsitemap_var Internal implementation of variable_get().
xmlsitemap_variables Internal default variables for xmlsitemap_var().
_xmlsitemap_check_changed_link Check if there is sitemap link is changed from the existing data.
_xmlsitemap_check_changed_links Check if there is a visible sitemap link given a certain set of conditions.
_xmlsitemap_delete_recursive Recursively delete all files and folders in the specified filepath.
_xmlsitemap_rebuild_form_access Menu access callback; determines if the user can use the rebuild links page.
_xmlsitemap_set_breadcrumb Workaround for missing breadcrumbs on callback and action paths.
_xmlsitemap_sitemap_context_summary

Constants

Namesort descending Description
XMLSITEMAP_FREQUENCY_ALWAYS
XMLSITEMAP_FREQUENCY_DAILY
XMLSITEMAP_FREQUENCY_HOURLY
XMLSITEMAP_FREQUENCY_MONTHLY
XMLSITEMAP_FREQUENCY_WEEKLY
XMLSITEMAP_FREQUENCY_YEARLY
XMLSITEMAP_LASTMOD_LONG Long lastmod timestamp format.
XMLSITEMAP_LASTMOD_MEDIUM Medium lastmod timestamp format.
XMLSITEMAP_LASTMOD_SHORT Short lastmod timestamp format.
XMLSITEMAP_MAX_SITEMAP_FILESIZE The maximum filesize of a sitemap chunk file.
XMLSITEMAP_MAX_SITEMAP_LINKS The maximum number of links in one sitemap chunk file.
XMLSITEMAP_PRIORITY_DEFAULT The default priority for link types in the sitemaps.
XMLSITEMAP_STATUS_DEFAULT The default inclusion status for link types in the sitemaps.

File

xmlsitemap/xmlsitemap.module
View source
  1. <?php
  2. /**
  3. * @defgroup xmlsitemap XML sitemap
  4. */
  5. /**
  6. * @file
  7. * Main file for the xmlsitemap module.
  8. */
  9. /**
  10. * The maximum number of links in one sitemap chunk file.
  11. */
  12. define('XMLSITEMAP_MAX_SITEMAP_LINKS', 50000);
  13. /**
  14. * The maximum filesize of a sitemap chunk file.
  15. */
  16. define('XMLSITEMAP_MAX_SITEMAP_FILESIZE', 10485760);
  17. define('XMLSITEMAP_FREQUENCY_YEARLY', 31449600); // 60 * 60 * 24 * 7 * 52
  18. define('XMLSITEMAP_FREQUENCY_MONTHLY', 2419200); // 60 * 60 * 24 * 7 * 4
  19. define('XMLSITEMAP_FREQUENCY_WEEKLY', 604800); // 60 * 60 * 24 * 7
  20. define('XMLSITEMAP_FREQUENCY_DAILY', 86400); // 60 * 60 * 24
  21. define('XMLSITEMAP_FREQUENCY_HOURLY', 3600); // 60 * 60
  22. define('XMLSITEMAP_FREQUENCY_ALWAYS', 60);
  23. /**
  24. * Short lastmod timestamp format.
  25. */
  26. define('XMLSITEMAP_LASTMOD_SHORT', 'Y-m-d');
  27. /**
  28. * Medium lastmod timestamp format.
  29. */
  30. define('XMLSITEMAP_LASTMOD_MEDIUM', 'Y-m-d\TH:i\Z');
  31. /**
  32. * Long lastmod timestamp format.
  33. */
  34. define('XMLSITEMAP_LASTMOD_LONG', 'c');
  35. /**
  36. * The default inclusion status for link types in the sitemaps.
  37. */
  38. define('XMLSITEMAP_STATUS_DEFAULT', 0);
  39. /**
  40. * The default priority for link types in the sitemaps.
  41. */
  42. define('XMLSITEMAP_PRIORITY_DEFAULT', 0.5);
  43. /**
  44. * Implements hook_hook_info().
  45. */
  46. function xmlsitemap_hook_info() {
  47. $hooks = array(
  48. 'xmlsitemap_link_info',
  49. 'xmlsitemap_link_info_alter',
  50. 'xmlsitemap_link_alter',
  51. 'xmlsitemap_index_links',
  52. 'xmlsitemap_context_info',
  53. 'xmlsitemap_context_info_alter',
  54. 'xmlsitemap_context_url_options',
  55. 'xmlsitemap_context',
  56. 'xmlsitemap_sitemap_insert',
  57. 'xmlsitemap_sitemap_update',
  58. 'xmlsitemap_sitemap_operations',
  59. 'xmlsitemap_sitemap_delete',
  60. 'xmlsitemap_sitemap_link_url_options_alter',
  61. 'query_xmlsitemap_generate_alter',
  62. 'query_xmlsitemap_link_bundle_access_alter',
  63. 'form_xmlsitemap_sitemap_edit_form_alter',
  64. );
  65. $hooks = array_combine($hooks, $hooks);
  66. foreach ($hooks as $hook => $info) {
  67. $hooks[$hook] = array('group' => 'xmlsitemap');
  68. }
  69. return $hooks;
  70. }
  71. /**
  72. * Implements hook_help().
  73. */
  74. function xmlsitemap_help($path, $arg) {
  75. $output = '';
  76. switch ($path) {
  77. case 'admin/help/xmlsitemap':
  78. case 'admin/config/search/xmlsitemap/settings/%/%/%':
  79. case 'admin/config/search/xmlsitemap/edit/%':
  80. case 'admin/config/search/xmlsitemap/delete/%':
  81. return;
  82. case 'admin/help#xmlsitemap':
  83. break;
  84. case 'admin/config/search/xmlsitemap':
  85. break;
  86. case 'admin/config/search/xmlsitemap/rebuild':
  87. $output .= '<p>' . t("This action rebuilds your site's XML sitemap and regenerates the cached files, and may be a lengthy process. If you just installed XML sitemap, this can be helpful to import all your site's content into the sitemap. Otherwise, this should only be used in emergencies.") . '</p>';
  88. }
  89. if (arg(0) == 'admin' && strpos($path, 'xmlsitemap') !== FALSE && user_access('administer xmlsitemap')) {
  90. module_load_include('inc', 'xmlsitemap');
  91. if ($arg[1] == 'config') {
  92. // Alert the user to any potential problems detected by hook_requirements.
  93. xmlsitemap_check_status();
  94. }
  95. $output .= _xmlsitemap_get_blurb();
  96. }
  97. return $output;
  98. }
  99. /**
  100. * Implements hook_perm().
  101. */
  102. function xmlsitemap_permission() {
  103. $permissions['administer xmlsitemap'] = array(
  104. 'title' => t('Administer XML sitemap settings.'),
  105. );
  106. return $permissions;
  107. }
  108. /**
  109. * Implements hook_menu().
  110. */
  111. function xmlsitemap_menu() {
  112. $items['admin/config/search/xmlsitemap'] = array(
  113. 'title' => 'XML sitemap',
  114. 'description' => "Configure your site's XML sitemaps to help search engines find and index pages on your site.",
  115. 'page callback' => 'drupal_get_form',
  116. 'page arguments' => array('xmlsitemap_sitemap_list_form'),
  117. 'access arguments' => array('administer xmlsitemap'),
  118. 'file' => 'xmlsitemap.admin.inc',
  119. );
  120. $items['admin/config/search/xmlsitemap/list'] = array(
  121. 'title' => 'List',
  122. 'type' => MENU_DEFAULT_LOCAL_TASK,
  123. 'weight' => -10,
  124. );
  125. $items['admin/config/search/xmlsitemap/add'] = array(
  126. 'title' => 'Add new XML sitemap',
  127. 'page callback' => 'drupal_get_form',
  128. 'page arguments' => array('xmlsitemap_sitemap_edit_form'),
  129. 'access arguments' => array('administer xmlsitemap'),
  130. 'type' => MENU_LOCAL_ACTION,
  131. 'file' => 'xmlsitemap.admin.inc',
  132. 'modal' => TRUE,
  133. 'options' => array('modal' => TRUE),
  134. );
  135. $items['admin/config/search/xmlsitemap/edit/%xmlsitemap_sitemap'] = array(
  136. 'title' => 'Edit XML sitemap',
  137. 'page callback' => 'drupal_get_form',
  138. 'page arguments' => array('xmlsitemap_sitemap_edit_form', 5),
  139. 'access arguments' => array('administer xmlsitemap'),
  140. 'file' => 'xmlsitemap.admin.inc',
  141. 'modal' => TRUE,
  142. );
  143. $items['admin/config/search/xmlsitemap/delete/%xmlsitemap_sitemap'] = array(
  144. 'title' => 'Delete XML sitemap',
  145. 'page callback' => 'drupal_get_form',
  146. 'page arguments' => array('xmlsitemap_sitemap_delete_form', 5),
  147. 'access arguments' => array('administer xmlsitemap'),
  148. 'file' => 'xmlsitemap.admin.inc',
  149. 'modal' => TRUE,
  150. );
  151. $items['admin/config/search/xmlsitemap/settings'] = array(
  152. 'title' => 'Settings',
  153. 'page callback' => 'drupal_get_form',
  154. 'page arguments' => array('xmlsitemap_settings_form'),
  155. 'access arguments' => array('administer xmlsitemap'),
  156. 'type' => MENU_LOCAL_TASK,
  157. 'file' => 'xmlsitemap.admin.inc',
  158. 'weight' => 10,
  159. );
  160. $items['admin/config/search/xmlsitemap/settings/%xmlsitemap_link_bundle/%'] = array(
  161. 'load arguments' => array(6),
  162. 'page callback' => 'drupal_get_form',
  163. 'page arguments' => array('xmlsitemap_link_bundle_settings_form', 5),
  164. 'access callback' => 'xmlsitemap_link_bundle_access',
  165. 'access arguments' => array(5),
  166. 'file' => 'xmlsitemap.admin.inc',
  167. 'modal' => TRUE,
  168. );
  169. $items['admin/config/search/xmlsitemap/rebuild'] = array(
  170. 'title' => 'Rebuild links',
  171. 'description' => 'Rebuild the site map.',
  172. 'page callback' => 'drupal_get_form',
  173. 'page arguments' => array('xmlsitemap_rebuild_form'),
  174. 'access callback' => '_xmlsitemap_rebuild_form_access',
  175. 'type' => MENU_LOCAL_TASK,
  176. 'file' => 'xmlsitemap.admin.inc',
  177. 'weight' => 20,
  178. );
  179. $items['sitemap.xml'] = array(
  180. 'page callback' => 'xmlsitemap_output_chunk',
  181. 'access callback' => TRUE,
  182. 'type' => MENU_CALLBACK,
  183. 'file' => 'xmlsitemap.pages.inc',
  184. );
  185. $items['sitemap.xsl'] = array(
  186. 'page callback' => 'xmlsitemap_output_xsl',
  187. 'access callback' => TRUE,
  188. 'type' => MENU_CALLBACK,
  189. 'file' => 'xmlsitemap.pages.inc',
  190. );
  191. return $items;
  192. }
  193. /**
  194. * Menu access callback; determines if the user can use the rebuild links page.
  195. */
  196. function _xmlsitemap_rebuild_form_access() {
  197. module_load_include('generate.inc', 'xmlsitemap');
  198. $rebuild_types = xmlsitemap_get_rebuildable_link_types();
  199. return !empty($rebuild_types) && user_access('administer xmlsitemap');
  200. }
  201. /**
  202. * Implements hook_cron().
  203. *
  204. * @todo Use new Queue system. Need to add {sitemap}.queued.
  205. * @todo Regenerate one at a time?
  206. */
  207. function xmlsitemap_cron() {
  208. // If there were no new or changed links, skip.
  209. if (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
  210. return;
  211. }
  212. // If the minimum sitemap lifetime hasn't been passed, skip.
  213. $lifetime = REQUEST_TIME - variable_get('xmlsitemap_generated_last', 0);
  214. if ($lifetime < variable_get('xmlsitemap_minimum_lifetime', 0)) {
  215. return;
  216. }
  217. // Regenerate the sitemap XML files.
  218. module_load_include('generate.inc', 'xmlsitemap');
  219. xmlsitemap_run_unprogressive_batch('xmlsitemap_regenerate_batch');
  220. }
  221. /**
  222. * Implements hook_modules_enabled().
  223. */
  224. function xmlsitemap_modules_enabled(array $modules) {
  225. cache_clear_all('xmlsitemap:', 'cache', TRUE);
  226. }
  227. /**
  228. * Implements hook_modules_disabled().
  229. */
  230. function xmlsitemap_modules_disabled(array $modules) {
  231. cache_clear_all('xmlsitemap:', 'cache', TRUE);
  232. }
  233. /**
  234. * Implements hook_robotstxt().
  235. */
  236. function xmlsitemap_robotstxt() {
  237. if ($sitemap = xmlsitemap_sitemap_load_by_context()) {
  238. $robotstxt[] = 'Sitemap: ' . url($sitemap->uri['path'], $sitemap->uri['options']);
  239. return $robotstxt;
  240. }
  241. }
  242. /**
  243. * Internal default variables for xmlsitemap_var().
  244. */
  245. function xmlsitemap_variables() {
  246. return array(
  247. 'xmlsitemap_rebuild_needed' => FALSE,
  248. 'xmlsitemap_regenerate_needed' => FALSE,
  249. 'xmlsitemap_minimum_lifetime' => 0,
  250. 'xmlsitemap_generated_last' => 0,
  251. 'xmlsitemap_xsl' => 1,
  252. 'xmlsitemap_prefetch_aliases' => 1,
  253. 'xmlsitemap_chunk_size' => 'auto',
  254. 'xmlsitemap_batch_limit' => 100,
  255. 'xmlsitemap_path' => 'xmlsitemap',
  256. 'xmlsitemap_base_url' => $GLOBALS['base_url'],
  257. 'xmlsitemap_developer_mode' => 0,
  258. 'xmlsitemap_frontpage_priority' => 1.0,
  259. 'xmlsitemap_frontpage_changefreq' => XMLSITEMAP_FREQUENCY_DAILY,
  260. 'xmlsitemap_lastmod_format' => XMLSITEMAP_LASTMOD_MEDIUM,
  261. 'xmlsitemap_gz' => FALSE,
  262. // Removed variables are set to NULL so they can still be deleted.
  263. 'xmlsitemap_regenerate_last' => NULL,
  264. 'xmlsitemap_custom_links' => NULL,
  265. 'xmlsitemap_priority_default' => NULL,
  266. 'xmlsitemap_languages' => NULL,
  267. 'xmlsitemap_max_chunks' => NULL,
  268. 'xmlsitemap_max_filesize' => NULL,
  269. );
  270. }
  271. /**
  272. * Internal implementation of variable_get().
  273. */
  274. function xmlsitemap_var($name, $default = NULL) {
  275. $defaults = &drupal_static(__FUNCTION__);
  276. if (!isset($defaults)) {
  277. $defaults = xmlsitemap_variables();
  278. }
  279. $name = 'xmlsitemap_' . $name;
  280. // @todo Remove when stable.
  281. if (!isset($defaults[$name])) {
  282. trigger_error(strtr('Default variable for %variable not found.', array('%variable' => drupal_placeholder($name))));
  283. }
  284. return variable_get($name, isset($default) || !isset($defaults[$name]) ? $default : $defaults[$name]);
  285. }
  286. /**
  287. * @defgroup xmlsitemap_api XML sitemap API.
  288. * @{
  289. * This is the XML sitemap API to be used by modules wishing to work with
  290. * XML sitemap and/or link data.
  291. */
  292. /**
  293. * Load an XML sitemap array from the database.
  294. *
  295. * @param $smid
  296. * An XML sitemap ID.
  297. *
  298. * @return
  299. * The XML sitemap object.
  300. */
  301. function xmlsitemap_sitemap_load($smid) {
  302. $sitemap = xmlsitemap_sitemap_load_multiple(array($smid));
  303. return $sitemap ? reset($sitemap) : FALSE;
  304. }
  305. /**
  306. * Load multiple XML sitemaps from the database.
  307. *
  308. * @param $smids
  309. * An array of XML sitemap IDs, or FALSE to load all XML sitemaps.
  310. * @param $conditions
  311. * An array of conditions in the form 'field' => $value.
  312. *
  313. * @return
  314. * An array of XML sitemap objects.
  315. */
  316. function xmlsitemap_sitemap_load_multiple($smids = array(), array $conditions = array()) {
  317. if ($smids !== FALSE) {
  318. $conditions['smid'] = $smids;
  319. }
  320. $query = db_select('xmlsitemap_sitemap');
  321. $query->fields('xmlsitemap_sitemap');
  322. foreach ($conditions as $field => $value) {
  323. $query->condition($field, $value);
  324. }
  325. $sitemaps = $query->execute()->fetchAllAssoc('smid');
  326. foreach ($sitemaps as $smid => $sitemap) {
  327. $sitemaps[$smid]->context = unserialize($sitemap->context);
  328. $sitemaps[$smid]->uri = xmlsitemap_sitemap_uri($sitemaps[$smid]);
  329. }
  330. return $sitemaps;
  331. }
  332. /**
  333. * Load an XML sitemap array from the database based on its context.
  334. *
  335. * @param $context
  336. * An optional XML sitemap context array to use to find the correct XML
  337. * sitemap. If not provided, the current site's context will be used.
  338. *
  339. * @see xmlsitemap_get_current_context()
  340. */
  341. function xmlsitemap_sitemap_load_by_context(array $context = NULL) {
  342. if (!isset($context)) {
  343. $context = xmlsitemap_get_current_context();
  344. }
  345. $hash = xmlsitemap_sitemap_get_context_hash($context);
  346. $smid = db_query_range("SELECT smid FROM {xmlsitemap_sitemap} WHERE smid = :hash", 0, 1, array(':hash' => $hash))->fetchField();
  347. return xmlsitemap_sitemap_load($smid);
  348. }
  349. /**
  350. * Save changes to an XML sitemap or add a new XML sitemap.
  351. *
  352. * @param $sitemap
  353. * The XML sitemap array to be saved. If $sitemap->smid is omitted, a new
  354. * XML sitemap will be added.
  355. *
  356. * @todo Save the sitemap's URL as a column?
  357. */
  358. function xmlsitemap_sitemap_save(stdClass $sitemap) {
  359. if (!isset($sitemap->context)) {
  360. $sitemap->context = array();
  361. }
  362. // Make sure context is sorted before saving the hash.
  363. $sitemap->is_new = empty($sitemap->smid);
  364. $sitemap->old_smid = $sitemap->is_new ? NULL : $sitemap->smid;
  365. $sitemap->smid = xmlsitemap_sitemap_get_context_hash($sitemap->context);
  366. // If the context was changed, we need to perform additional actions.
  367. if (!$sitemap->is_new && $sitemap->smid != $sitemap->old_smid) {
  368. // Rename the files directory so the sitemap does not break.
  369. $old_sitemap = (object) array('smid' => $sitemap->old_smid);
  370. $old_dir = xmlsitemap_get_directory($old_sitemap);
  371. $new_dir = xmlsitemap_get_directory($sitemap);
  372. xmlsitemap_directory_move($old_dir, $new_dir);
  373. // Change the smid field so drupal_write_record() does not fail.
  374. db_update('xmlsitemap_sitemap')
  375. ->fields(array('smid' => $sitemap->smid))
  376. ->condition('smid', $sitemap->old_smid)
  377. ->execute();
  378. // Mark the sitemaps as needing regeneration.
  379. variable_set('xmlsitemap_regenerate_needed', TRUE);
  380. }
  381. if ($sitemap->is_new) {
  382. drupal_write_record('xmlsitemap_sitemap', $sitemap);
  383. module_invoke_all('xmlsitemap_sitemap_insert', $sitemap);
  384. }
  385. else {
  386. drupal_write_record('xmlsitemap_sitemap', $sitemap, array('smid'));
  387. module_invoke_all('xmlsitemap_sitemap_update', $sitemap);
  388. }
  389. return $sitemap;
  390. }
  391. /**
  392. * Delete an XML sitemap.
  393. *
  394. * @param $smid
  395. * An XML sitemap ID.
  396. */
  397. function xmlsitemap_sitemap_delete($smid) {
  398. xmlsitemap_sitemap_delete_multiple(array($smid));
  399. }
  400. /**
  401. * Delete multiple XML sitemaps.
  402. *
  403. * @param $smids
  404. * An array of XML sitemap IDs.
  405. */
  406. function xmlsitemap_sitemap_delete_multiple(array $smids) {
  407. if (!empty($smids)) {
  408. $sitemaps = xmlsitemap_sitemap_load_multiple($smids);
  409. db_delete('xmlsitemap_sitemap')
  410. ->condition('smid', $smids)
  411. ->execute();
  412. foreach ($sitemaps as $sitemap) {
  413. xmlsitemap_clear_directory($sitemap, TRUE);
  414. module_invoke_all('xmlsitemap_sitemap_delete', $sitemap);
  415. }
  416. }
  417. }
  418. /**
  419. * Return the expected file path for a specific sitemap chunk.
  420. *
  421. * @param $sitemap
  422. * An XML sitemap array.
  423. * @param $chunk
  424. * An optional specific chunk in the sitemap. Defaults to the index page.
  425. */
  426. function xmlsitemap_sitemap_get_file(stdClass $sitemap, $chunk = 'index') {
  427. return xmlsitemap_get_directory($sitemap) . "/{$chunk}.xml";
  428. }
  429. /**
  430. * Find the maximum file size of all a sitemap's XML files.
  431. *
  432. * @param $sitemap
  433. * The XML sitemap array.
  434. */
  435. function xmlsitemap_sitemap_get_max_filesize(stdClass $sitemap) {
  436. $dir = xmlsitemap_get_directory($sitemap);
  437. $sitemap->max_filesize = 0;
  438. foreach (file_scan_directory($dir, '/\.xml$/') as $file) {
  439. $sitemap->max_filesize = max($sitemap->max_filesize, filesize($file->uri));
  440. }
  441. return $sitemap->max_filesize;
  442. }
  443. function xmlsitemap_sitemap_get_context_hash(array &$context) {
  444. asort($context);
  445. return drupal_hash_base64(serialize($context));
  446. }
  447. /**
  448. * Returns the uri elements of an XML sitemap.
  449. *
  450. * @param $sitemap
  451. * An unserialized data array for an XML sitemap.
  452. * @return
  453. * An array containing the 'path' and 'options' keys used to build the uri of
  454. * the XML sitemap, and matching the signature of url().
  455. */
  456. function xmlsitemap_sitemap_uri(stdClass $sitemap) {
  457. $uri['path'] = 'sitemap.xml';
  458. $uri['options'] = module_invoke_all('xmlsitemap_context_url_options', $sitemap->context);
  459. drupal_alter('xmlsitemap_context_url_options', $uri['options'], $sitemap->context);
  460. $uri['options'] += array(
  461. 'absolute' => TRUE,
  462. 'base_url' => variable_get('xmlsitemap_base_url', $GLOBALS['base_url']),
  463. );
  464. return $uri;
  465. }
  466. /**
  467. * Load a specific sitemap link from the database.
  468. *
  469. * @param $entity_type
  470. * A string with the entity type.
  471. * @param $entity_id
  472. * An integer with the entity ID.
  473. * @return
  474. * A sitemap link (array) or FALSE if the conditions were not found.
  475. */
  476. function xmlsitemap_link_load($entity_type, $entity_id) {
  477. $link = xmlsitemap_link_load_multiple(array('type' => $entity_type, 'id' => $entity_id));
  478. return $link ? reset($link) : FALSE;
  479. }
  480. /**
  481. * Load sitemap links from the database.
  482. *
  483. * @param $conditions
  484. * An array of conditions on the {xmlsitemap} table in the form
  485. * 'field' => $value.
  486. * @return
  487. * An array of sitemap link arrays.
  488. */
  489. function xmlsitemap_link_load_multiple(array $conditions = array()) {
  490. $query = db_select('xmlsitemap');
  491. $query->fields('xmlsitemap');
  492. foreach ($conditions as $field => $value) {
  493. $query->condition($field, $value);
  494. }
  495. $links = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
  496. return $links;
  497. }
  498. /**
  499. * Saves or updates a sitemap link.
  500. *
  501. * @param $link
  502. * An array with a sitemap link.
  503. */
  504. function xmlsitemap_link_save(array $link) {
  505. $link += array(
  506. 'access' => 1,
  507. 'status' => 1,
  508. 'status_override' => 0,
  509. 'lastmod' => 0,
  510. 'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
  511. 'priority_override' => 0,
  512. 'changefreq' => 0,
  513. 'changecount' => 0,
  514. 'language' => LANGUAGE_NONE,
  515. );
  516. // Allow other modules to alter the link before saving.
  517. drupal_alter('xmlsitemap_link', $link);
  518. // Temporary validation checks.
  519. // @todo Remove in final?
  520. if ($link['priority'] < 0 || $link['priority'] > 1) {
  521. trigger_error(t('Invalid sitemap link priority %priority.<br />@link', array('%priority' => $link['priority'], '@link' => var_export($link, TRUE))), E_USER_ERROR);
  522. }
  523. if ($link['changecount'] < 0) {
  524. trigger_error(t('Negative changecount value. Please report this to <a href="@516928">@516928</a>.<br />@link', array('@516928' => 'http://drupal.org/node/516928', '@link' => var_export($link, TRUE))), E_USER_ERROR);
  525. $link['changecount'] = 0;
  526. }
  527. $existing = db_query_range("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id", 0, 1, array(':type' => $link['type'], ':id' => $link['id']))->fetchAssoc();
  528. // Check if this is a changed link and set the regenerate flag if necessary.
  529. if (!variable_get('xmlsitemap_regenerate_needed', FALSE)) {
  530. _xmlsitemap_check_changed_link($link, $existing, TRUE);
  531. }
  532. // Save the link and allow other modules to respond to the link being saved.
  533. if ($existing) {
  534. drupal_write_record('xmlsitemap', $link, array('type', 'id'));
  535. module_invoke_all('xmlsitemap_link_update', $link);
  536. }
  537. else {
  538. drupal_write_record('xmlsitemap', $link);
  539. module_invoke_all('xmlsitemap_link_insert', $link);
  540. }
  541. return $link;
  542. }
  543. /**
  544. * Perform a mass update of sitemap data.
  545. *
  546. * If visible links are updated, this will automatically set the regenerate
  547. * needed flag to TRUE.
  548. *
  549. * @param $updates
  550. * An array of values to update fields to, keyed by field name.
  551. * @param $conditions
  552. * An array of values to match keyed by field.
  553. * @return
  554. * The number of links that were updated.
  555. */
  556. function xmlsitemap_link_update_multiple($updates = array(), $conditions = array(), $check_flag = TRUE) {
  557. // If we are going to modify a visible sitemap link, we will need to set
  558. // the regenerate needed flag.
  559. if ($check_flag && !variable_get('xmlsitemap_regenerate_needed', FALSE)) {
  560. _xmlsitemap_check_changed_links($conditions, $updates, TRUE);
  561. }
  562. // Process updates.
  563. $query = db_update('xmlsitemap');
  564. $query->fields($updates);
  565. foreach ($conditions as $field => $value) {
  566. $query->condition($field, $value);
  567. }
  568. return $query->execute();
  569. }
  570. /**
  571. * Delete a specific sitemap link from the database.
  572. *
  573. * If a visible sitemap link was deleted, this will automatically set the
  574. * regenerate needed flag.
  575. *
  576. * @param $entity_type
  577. * A string with the entity type.
  578. * @param $entity_id
  579. * An integer with the entity ID.
  580. * @return
  581. * The number of links that were deleted.
  582. */
  583. function xmlsitemap_link_delete($entity_type, $entity_id) {
  584. $conditions = array('type' => $entity_type, 'id' => $entity_id);
  585. return xmlsitemap_link_delete_multiple($conditions);
  586. }
  587. /**
  588. * Delete multiple sitemap links from the database.
  589. *
  590. * If visible sitemap links were deleted, this will automatically set the
  591. * regenerate needed flag.
  592. *
  593. * @param $conditions
  594. * An array of conditions on the {xmlsitemap} table in the form
  595. * 'field' => $value.
  596. * @return
  597. * The number of links that were deleted.
  598. */
  599. function xmlsitemap_link_delete_multiple(array $conditions) {
  600. // Because this function is called from sub-module uninstall hooks, we have
  601. // to manually check if the table exists since it could have been removed
  602. // in xmlsitemap_uninstall().
  603. // @todo Remove this check when http://drupal.org/node/151452 is fixed.
  604. if (!db_table_exists('xmlsitemap')) {
  605. return FALSE;
  606. }
  607. if (!variable_get('xmlsitemap_regenerate_needed', TRUE)) {
  608. _xmlsitemap_check_changed_links($conditions, array(), TRUE);
  609. }
  610. // @todo Add a hook_xmlsitemap_link_delete() hook invoked here.
  611. $query = db_delete('xmlsitemap');
  612. foreach ($conditions as $field => $value) {
  613. $query->condition($field, $value);
  614. }
  615. return $query->execute();
  616. }
  617. /**
  618. * Check if there is a visible sitemap link given a certain set of conditions.
  619. *
  620. * @param $conditions
  621. * An array of values to match keyed by field.
  622. * @param $flag
  623. * An optional boolean that if TRUE, will set the regenerate needed flag if
  624. * there is a match. Defaults to FALSE.
  625. * @return
  626. * TRUE if there is a visible link, or FALSE otherwise.
  627. */
  628. function _xmlsitemap_check_changed_links(array $conditions = array(), array $updates = array(), $flag = FALSE) {
  629. // If we are changing status or access, check for negative current values.
  630. $conditions['status'] = (!empty($updates['status']) && empty($conditions['status'])) ? 0 : 1;
  631. $conditions['access'] = (!empty($updates['access']) && empty($conditions['access'])) ? 0 : 1;
  632. $query = db_select('xmlsitemap');
  633. $query->addExpression('1');
  634. foreach ($conditions as $field => $value) {
  635. $query->condition($field, $value);
  636. }
  637. $query->range(0, 1);
  638. $changed = $query->execute()->fetchField();
  639. if ($changed && $flag) {
  640. variable_set('xmlsitemap_regenerate_needed', TRUE);
  641. }
  642. return $changed;
  643. }
  644. /**
  645. * Check if there is sitemap link is changed from the existing data.
  646. *
  647. * @param $link
  648. * An array of the sitemap link.
  649. * @param $original_link
  650. * An optional array of the existing data. This should only contain the
  651. * fields necessary for comparison. If not provided the existing data will be
  652. * loaded from the database.
  653. * @param $flag
  654. * An optional boolean that if TRUE, will set the regenerate needed flag if
  655. * there is a match. Defaults to FALSE.
  656. * @return
  657. * TRUE if the link is changed, or FALSE otherwise.
  658. */
  659. function _xmlsitemap_check_changed_link(array $link, $original_link = NULL, $flag = FALSE) {
  660. $changed = FALSE;
  661. if ($original_link === NULL) {
  662. // Load only the fields necessary for data to be changed in the sitemap.
  663. $original_link = db_query_range("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id", 0, 1, array(':type' => $link['type'], ':id' => $link['id']))->fetchAssoc();
  664. }
  665. if (!$original_link) {
  666. if ($link['access'] && $link['status']) {
  667. // Adding a new visible link.
  668. $changed = TRUE;
  669. }
  670. }
  671. else {
  672. if (!($original_link['access'] && $original_link['status']) && $link['access'] && $link['status']) {
  673. // Changing a non-visible link to a visible link.
  674. $changed = TRUE;
  675. }
  676. elseif ($original_link['access'] && $original_link['status'] && array_diff_assoc($original_link, $link)) {
  677. // Changing a visible link
  678. $changed = TRUE;
  679. }
  680. }
  681. if ($changed && $flag) {
  682. variable_set('xmlsitemap_regenerate_needed', TRUE);
  683. }
  684. return $changed;
  685. }
  686. /**
  687. * @} End of "defgroup xmlsitemap_api"
  688. */
  689. function xmlsitemap_get_directory(stdClass $sitemap = NULL) {
  690. $directory = &drupal_static(__FUNCTION__);
  691. if (!isset($directory)) {
  692. $directory = variable_get('xmlsitemap_path', 'xmlsitemap');
  693. }
  694. if (!empty($sitemap->smid)) {
  695. return file_build_uri($directory . '/' . $sitemap->smid);
  696. }
  697. else {
  698. return file_build_uri($directory);
  699. }
  700. }
  701. /**
  702. * Check that the sitemap files directory exists and is writable.
  703. */
  704. function xmlsitemap_check_directory(stdClass $sitemap = NULL) {
  705. $directory = xmlsitemap_get_directory($sitemap);
  706. $result = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  707. if (!$result) {
  708. watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $directory), WATCHDOG_ERROR);
  709. }
  710. return $result;
  711. }
  712. function xmlsitemap_check_all_directories() {
  713. $directories = array();
  714. $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  715. foreach ($sitemaps as $smid => $sitemap) {
  716. $directory = xmlsitemap_get_directory($sitemap);
  717. $directories[$directory] = $directory;
  718. }
  719. foreach ($directories as $directory) {
  720. $result = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  721. if ($result) {
  722. $directories[$directory] = TRUE;
  723. }
  724. else {
  725. $directories[$directory] = FALSE;
  726. }
  727. }
  728. return $directories;
  729. }
  730. function xmlsitemap_clear_directory(stdClass $sitemap = NULL, $delete = FALSE) {
  731. $directory = xmlsitemap_get_directory($sitemap);
  732. return _xmlsitemap_delete_recursive($directory, $delete);
  733. }
  734. /**
  735. * Move a directory to a new location.
  736. *
  737. * @param $old_dir
  738. * A string specifying the filepath or URI of the original directory.
  739. * @param $new_dir
  740. * A string specifying the filepath or URI of the new directory.
  741. * @param $replace
  742. * Replace behavior when the destination file already exists.
  743. *
  744. * @return
  745. * TRUE if the directory was moved successfully. FALSE otherwise.
  746. */
  747. function xmlsitemap_directory_move($old_dir, $new_dir, $replace = FILE_EXISTS_REPLACE) {
  748. $success = file_prepare_directory($new_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  749. $old_path = drupal_realpath($old_dir);
  750. $new_path = drupal_realpath($new_dir);
  751. if (!is_dir($old_path) || !is_dir($new_path) || !$success) {
  752. return FALSE;
  753. }
  754. $files = file_scan_directory($old_dir, '/.*/');
  755. foreach ($files as $file) {
  756. $file->uri_new = $new_dir . '/' . basename($file->filename);
  757. $success &= (bool) file_unmanaged_move($file->uri, $file->uri_new, $replace);
  758. }
  759. // The remove the directory.
  760. $success &= drupal_rmdir($old_dir);
  761. return $success;
  762. }
  763. /**
  764. * Recursively delete all files and folders in the specified filepath.
  765. *
  766. * This is a backport of Drupal 7's file_unmanaged_delete_recursive().
  767. *
  768. * Note that this only deletes visible files with write permission.
  769. *
  770. * @param $path
  771. * A filepath relative to the Drupal root directory.
  772. * @param $delete_root
  773. * A boolean if TRUE will delete the $path directory afterwards.
  774. */
  775. function _xmlsitemap_delete_recursive($path, $delete_root = FALSE) {
  776. // Resolve streamwrapper URI to local path.
  777. $path = drupal_realpath($path);
  778. if (is_dir($path)) {
  779. $dir = dir($path);
  780. while (($entry = $dir->read()) !== FALSE) {
  781. if ($entry == '.' || $entry == '..') {
  782. continue;
  783. }
  784. $entry_path = $path . '/' . $entry;
  785. file_unmanaged_delete_recursive($entry_path, TRUE);
  786. }
  787. $dir->close();
  788. return $delete_root ? drupal_rmdir($path) : TRUE;
  789. }
  790. return file_unmanaged_delete($path);
  791. }
  792. /**
  793. * Returns information about supported sitemap link types.
  794. *
  795. * @param $type
  796. * (optional) The link type to return information for. If omitted,
  797. * information for all link types is returned.
  798. * @param $reset
  799. * (optional) Boolean whether to reset the static cache and do nothing. Only
  800. * used for tests.
  801. *
  802. * @see hook_xmlsitemap_link_info()
  803. * @see hook_xmlsitemap_link_info_alter()
  804. */
  805. function xmlsitemap_get_link_info($type = NULL, $reset = FALSE) {
  806. global $language;
  807. $link_info = &drupal_static(__FUNCTION__);
  808. if ($reset) {
  809. $link_info = NULL;
  810. cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
  811. }
  812. if (!isset($link_info)) {
  813. $cid = 'xmlsitemap:link_info:' . $language->language;
  814. if ($cache = cache_get($cid)) {
  815. $link_info = $cache->data;
  816. }
  817. else {
  818. entity_info_cache_clear();
  819. $link_info = entity_get_info();
  820. foreach ($link_info as $key => $info) {
  821. if (empty($info['uri callback']) || !isset($info['xmlsitemap'])) {
  822. // Remove any non URL-able or XML sitemap un-supported entites.
  823. unset($link_info[$key]);
  824. }
  825. foreach ($info['bundles'] as $bundle_key => $bundle) {
  826. if (!isset($bundle['xmlsitemap'])) {
  827. // Remove any un-supported entity bundles.
  828. //unset($link_info[$key]['bundles'][$bundle_key]);
  829. }
  830. }
  831. }
  832. $link_info = array_merge($link_info, module_invoke_all('xmlsitemap_link_info'));
  833. foreach ($link_info as $key => &$info) {
  834. $info += array(
  835. 'type' => $key,
  836. 'base table' => FALSE,
  837. 'bundles' => array(),
  838. 'xmlsitemap' => array(),
  839. );
  840. if (!isset($info['xmlsitemap']['rebuild callback']) && !empty($info['base table']) && !empty($info['entity keys']['id']) && !empty($info['xmlsitemap']['process callback'])) {
  841. $info['xmlsitemap']['rebuild callback'] = 'xmlsitemap_rebuild_batch_fetch';
  842. }
  843. foreach ($info['bundles'] as $bundle => &$bundle_info) {
  844. $bundle_info += array(
  845. 'xmlsitemap' => array(),
  846. );
  847. $bundle_info['xmlsitemap'] += xmlsitemap_link_bundle_load($key, $bundle, FALSE);
  848. }
  849. }
  850. drupal_alter('xmlsitemap_link_info', $link_info);
  851. ksort($link_info);
  852. // Cache by language since this info contains translated strings.
  853. cache_set($cid, $link_info);
  854. }
  855. }
  856. if (isset($type)) {
  857. return isset($link_info[$type]) ? $link_info[$type] : NULL;
  858. }
  859. return $link_info;
  860. }
  861. function xmlsitemap_get_link_type_enabled_bundles($entity_type) {
  862. $bundles = array();
  863. $info = xmlsitemap_get_link_info($entity_type);
  864. foreach ($info['bundles'] as $bundle => $bundle_info) {
  865. $settings = xmlsitemap_link_bundle_load($entity_type, $bundle);
  866. if (!empty($settings['status'])) {
  867. //if (!empty($bundle_info['xmlsitemap']['status'])) {
  868. $bundles[] = $bundle;
  869. }
  870. }
  871. return $bundles;
  872. }
  873. function xmlsitemap_get_link_type_indexed_status($entity_type, $bundle = '') {
  874. $info = xmlsitemap_get_link_info($entity_type);
  875. $status['indexed'] = db_query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle", array(':entity' => $entity_type, ':bundle' => $bundle))->fetchField();
  876. $status['visible'] = db_query("SELECT COUNT(id) FROM {xmlsitemap} WHERE type = :entity AND subtype = :bundle AND status = 1 AND access = 1", array(':entity' => $entity_type, ':bundle' => $bundle))->fetchField();
  877. $total = new EntityFieldQuery();
  878. $total->entityCondition('entity_type', $entity_type);
  879. $total->entityCondition('bundle', $bundle);
  880. $total->entityCondition('entity_id', 0, '>');
  881. //$total->addTag('xmlsitemap_link_bundle_access');
  882. $total->addTag('xmlsitemap_link_indexed_status');
  883. $total->addMetaData('entity', $entity_type);
  884. $total->addMetaData('bundle', $bundle);
  885. $total->addMetaData('entity_info', $info);
  886. $total->count();
  887. $status['total'] = $total->execute();
  888. return $status;
  889. }
  890. /**
  891. * Implements hook_entity_query_alter().
  892. *
  893. * @todo Remove when http://drupal.org/node/1054168 is fixed.
  894. */
  895. function xmlsitemap_entity_query_alter($query) {
  896. $conditions = &$query->entityConditions;
  897. // Alter user entity queries only.
  898. if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'user' && isset($conditions['bundle'])) {
  899. unset($conditions['bundle']);
  900. }
  901. }
  902. function xmlsitemap_link_bundle_settings_save($entity, $bundle, array $settings, $update_links = TRUE) {
  903. if ($update_links) {
  904. $old_settings = xmlsitemap_link_bundle_load($entity, $bundle);
  905. if ($settings['status'] != $old_settings['status']) {
  906. xmlsitemap_link_update_multiple(array('status' => $settings['status']), array('type' => $entity, 'subtype' => $bundle, 'status_override' => 0));
  907. }
  908. if ($settings['priority'] != $old_settings['priority']) {
  909. xmlsitemap_link_update_multiple(array('priority' => $settings['priority']), array('type' => $entity, 'subtype' => $bundle, 'priority_override' => 0));
  910. }
  911. }
  912. variable_set("xmlsitemap_settings_{$entity}_{$bundle}", $settings);
  913. cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
  914. //xmlsitemap_get_link_info(NULL, TRUE);
  915. }
  916. function xmlsitemap_link_bundle_rename($entity, $bundle_old, $bundle_new) {
  917. if ($bundle_old != $bundle_new) {
  918. $settings = xmlsitemap_link_bundle_load($entity, $bundle_old);
  919. variable_del("xmlsitemap_settings_{$entity}_{$bundle_old}");
  920. xmlsitemap_link_bundle_settings_save($entity, $bundle_new, $settings, FALSE);
  921. xmlsitemap_link_update_multiple(array('subtype' => $bundle_new), array('type' => $entity, 'subtype' => $bundle_old));
  922. }
  923. }
  924. /**
  925. * Rename a link type.
  926. */
  927. function xmlsitemap_link_type_rename($entity_old, $entity_new, $bundles = NULL) {
  928. $variables = db_query("SELECT name FROM {variable} WHERE name LIKE :pattern", array(':pattern' => db_like('xmlsitemap_settings_' . $entity_old . '_') . '%'))->fetchCol();
  929. foreach ($variables as $variable) {
  930. $value = variable_get($variable);
  931. variable_del($variable);
  932. if (isset($value)) {
  933. $variable_new = str_replace('xmlsitemap_settings_' . $entity_old, 'xmlsitemap_settings_' . $entity_new, $variable);
  934. variable_set($variable_new, $value);
  935. }
  936. }
  937. xmlsitemap_link_update_multiple(array('type' => $entity_new), array('type' => $entity_old), FALSE);
  938. xmlsitemap_get_link_info(NULL, TRUE);
  939. }
  940. function xmlsitemap_link_bundle_load($entity, $bundle, $load_bundle_info = TRUE) {
  941. $info = array(
  942. 'entity' => $entity,
  943. 'bundle' => $bundle,
  944. );
  945. if ($load_bundle_info) {
  946. $entity_info = xmlsitemap_get_link_info($entity);
  947. if (isset($entity_info['bundles'][$bundle])) {
  948. $info['info'] = $entity_info['bundles'][$bundle];
  949. }
  950. }
  951. $info += variable_get("xmlsitemap_settings_{$entity}_{$bundle}", array());
  952. $info += array(
  953. 'status' => XMLSITEMAP_STATUS_DEFAULT,
  954. 'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
  955. );
  956. return $info;
  957. }
  958. function xmlsitemap_link_bundle_delete($entity, $bundle, $delete_links = TRUE) {
  959. variable_del("xmlsitemap_settings_{$entity}_{$bundle}");
  960. if ($delete_links) {
  961. xmlsitemap_link_delete_multiple(array('type' => $entity, 'subtype' => $bundle));
  962. }
  963. cache_clear_all('xmlsitemap:link_info:', 'cache', TRUE);
  964. //xmlsitemap_get_link_info(NULL, TRUE);
  965. }
  966. function xmlsitemap_link_bundle_access($entity, $bundle = NULL) {
  967. if (is_array($entity) && !isset($bundle)) {
  968. $bundle = $entity;
  969. }
  970. else {
  971. $bundle = xmlsitemap_link_bundle_load($entity, $bundle);
  972. }
  973. if (isset($bundle['info']['admin'])) {
  974. $admin = $bundle['info']['admin'];
  975. $admin += array('access arguments' => array());
  976. if (!isset($admin['access callback']) && count($admin['access arguments']) == 1) {
  977. $admin['access callback'] = 'user_access';
  978. }
  979. if (!empty($admin['access callback'])) {
  980. return call_user_func_array($admin['access callback'], $admin['access arguments']);
  981. }
  982. }
  983. return FALSE;
  984. }
  985. function xmlsitemap_get_bundle_path($entity, $bundle) {
  986. $info = xmlsitemap_get_link_info($entity);
  987. if (!empty($info['bundles'][$bundle]['admin']['real path'])) {
  988. return $info['bundles'][$bundle]['admin']['real path'];
  989. }
  990. elseif (!empty($info['bundles'][$bundle]['admin']['path'])) {
  991. return $info['bundles'][$bundle]['admin']['path'];
  992. }
  993. else {
  994. return FALSE;
  995. }
  996. }
  997. /**
  998. * Implements hook_field_attach_rename_bundle().
  999. */
  1000. function xmlsitemap_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
  1001. xmlsitemap_link_bundle_rename($entity_type, $bundle_old, $bundle_new);
  1002. }
  1003. /**
  1004. * Implements hook_field_attach_delete_bundle().
  1005. */
  1006. function xmlsitemap_field_attach_delete_bundle($entity_type, $bundle, $instances) {
  1007. xmlsitemap_link_bundle_delete($entity_type, $bundle, TRUE);
  1008. }
  1009. /**
  1010. * Determine the frequency of updates to a link.
  1011. *
  1012. * @param $interval
  1013. * An interval value in seconds.
  1014. * @return
  1015. * A string representing the update frequency according to the sitemaps.org
  1016. * protocol.
  1017. */
  1018. function xmlsitemap_get_changefreq($interval) {
  1019. if ($interval <= 0 || !is_numeric($interval)) {
  1020. return FALSE;
  1021. }
  1022. foreach (xmlsitemap_get_changefreq_options() as $value => $frequency) {
  1023. if ($interval <= $value) {
  1024. return $frequency;
  1025. }
  1026. }
  1027. return 'never';
  1028. }
  1029. /**
  1030. * Get the current number of sitemap chunks.
  1031. */
  1032. function xmlsitemap_get_chunk_count($reset = FALSE) {
  1033. static $chunks;
  1034. if (!isset($chunks) || $reset) {
  1035. $count = max(xmlsitemap_get_link_count($reset), 1);
  1036. $chunks = ceil($count / xmlsitemap_get_chunk_size($reset));
  1037. }
  1038. return $chunks;
  1039. }
  1040. /**
  1041. * Get the current number of sitemap links.
  1042. */
  1043. function xmlsitemap_get_link_count($reset = FALSE) {
  1044. static $count;
  1045. if (!isset($count) || $reset) {
  1046. $count = db_query("SELECT COUNT(id) FROM {xmlsitemap} WHERE access = 1 AND status = 1")->fetchField();
  1047. }
  1048. return $count;
  1049. }
  1050. /**
  1051. * Get the sitemap chunk size.
  1052. *
  1053. * This function is useful with the chunk size is set to automatic as it will
  1054. * calculate the appropriate value. Use this function instead of @code
  1055. * xmlsitemap_var('chunk_size') @endcode when the actual value is needed.
  1056. *
  1057. * @param $reset
  1058. * A boolean to reset the saved, static result. Defaults to FALSE.
  1059. * @return
  1060. * An integer with the number of links in each sitemap page.
  1061. */
  1062. function xmlsitemap_get_chunk_size($reset = FALSE) {
  1063. static $size;
  1064. if (!isset($size) || $reset) {
  1065. $size = xmlsitemap_var('chunk_size');
  1066. if ($size === 'auto') {
  1067. $count = max(xmlsitemap_get_link_count($reset), 1); // Prevent divide by zero.
  1068. $size = min(ceil($count / 10000) * 5000, XMLSITEMAP_MAX_SITEMAP_LINKS);
  1069. }
  1070. }
  1071. return $size;
  1072. }
  1073. /**
  1074. * Recalculate the changefreq of a sitemap link.
  1075. *
  1076. * @param $link
  1077. * A sitemap link array.
  1078. */
  1079. function xmlsitemap_recalculate_changefreq(&$link) {
  1080. $link['changefreq'] = round((($link['changefreq'] * $link['changecount']) + (REQUEST_TIME - $link['lastmod'])) / ($link['changecount'] + 1));
  1081. $link['changecount']++;
  1082. $link['lastmod'] = REQUEST_TIME;
  1083. }
  1084. /**
  1085. * Calculates the average interval between UNIX timestamps.
  1086. *
  1087. * @param $timestamps
  1088. * An array of UNIX timestamp integers.
  1089. * @return
  1090. * An integer of the average interval.
  1091. */
  1092. function xmlsitemap_calculate_changefreq($timestamps) {
  1093. sort($timestamps);
  1094. $count = count($timestamps) - 1;
  1095. $diff = 0;
  1096. for ($i = 0; $i < $count; $i++) {
  1097. $diff += $timestamps[$i + 1] - $timestamps[$i];
  1098. }
  1099. return $count > 0 ? round($diff / $count) : 0;
  1100. }
  1101. /**
  1102. * Submit handler; Set the regenerate needed flag if variables have changed.
  1103. *
  1104. * This function needs to be called before system_settings_form_submit() or any
  1105. * calls to variable_set().
  1106. */
  1107. function xmlsitemap_form_submit_flag_regenerate($form, $form_state) {
  1108. foreach ($form_state['values'] as $variable => $value) {
  1109. $stored_value = variable_get($variable, 'not_a_variable');
  1110. if (is_array($value) && !empty($form_state['values']['array_filter'])) {
  1111. $value = array_keys(array_filter($value));
  1112. }
  1113. if ($stored_value != 'not_a_variable' && $stored_value != $value) {
  1114. variable_set('xmlsitemap_regenerate_needed', TRUE);
  1115. drupal_set_message(t('XML sitemap settings have been modified and the files should be regenerated. You can <a href="@run-cron">run cron manually</a> to regenerate the cached files.', array('@run-cron' => url('admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'warning', FALSE);
  1116. return;
  1117. }
  1118. }
  1119. }
  1120. /**
  1121. * Set the current user stored in $GLOBALS['user'].
  1122. *
  1123. * @todo Remove when http://drupal.org/node/287292 is fixed.
  1124. */
  1125. function xmlsitemap_switch_user($new_user = NULL) {
  1126. global $user;
  1127. $user_original = &drupal_static(__FUNCTION__);
  1128. if (!isset($new_user)) {
  1129. if (isset($user_original)) {
  1130. // Restore the original user.
  1131. $user = $user_original;
  1132. $user_original = NULL;
  1133. drupal_save_session(TRUE);
  1134. }
  1135. else {
  1136. return FALSE;
  1137. }
  1138. }
  1139. elseif (is_numeric($new_user) && $user->uid != $new_user) {
  1140. // Get the full user object.
  1141. if (!$new_user) {
  1142. $new_user = drupal_anonymous_user();
  1143. }
  1144. elseif (!$new_user = user_load($new_user)) {
  1145. return FALSE;
  1146. }
  1147. // Backup the original user object.
  1148. if (!isset($user_original)) {
  1149. $user_original = $user;
  1150. drupal_save_session(FALSE);
  1151. }
  1152. $user = $new_user;
  1153. }
  1154. elseif (is_object($new_user) && $user->uid != $new_user->uid) {
  1155. // Backup the original user object.
  1156. if (!isset($user_original)) {
  1157. $user_original = $user;
  1158. drupal_save_session(FALSE);
  1159. }
  1160. $user = $new_user;
  1161. }
  1162. else {
  1163. return FALSE;
  1164. }
  1165. return $user;
  1166. }
  1167. /**
  1168. * Restore the user that was originally loaded.
  1169. *
  1170. * @return
  1171. * Current user.
  1172. */
  1173. function xmlsitemap_restore_user() {
  1174. return xmlsitemap_switch_user();
  1175. }
  1176. function xmlsitemap_process_form_link_options($form, &$form_state) {
  1177. $link = &$form_state['values']['xmlsitemap'];
  1178. $fields = array('status' => XMLSITEMAP_STATUS_DEFAULT, 'priority' => XMLSITEMAP_PRIORITY_DEFAULT);
  1179. foreach ($fields as $field => $default) {
  1180. if ($link[$field] === 'default') {
  1181. $link[$field] = isset($link[$field . '_default']) ? $link[$field . '_default'] : $default;
  1182. $link[$field . '_override'] = 0;
  1183. }
  1184. else {
  1185. $link[$field . '_override'] = 1;
  1186. }
  1187. }
  1188. }
  1189. function xmlsitemap_link_bundle_settings_form_submit($form, &$form_state) {
  1190. $entity = $form['xmlsitemap']['#entity'];
  1191. $bundle = $form['xmlsitemap']['#bundle'];
  1192. // Handle new bundles by fetching the proper bundle key value from the form
  1193. // state values.
  1194. if (empty($bundle)) {
  1195. $entity_info = $form['xmlsitemap']['#entity_info'];
  1196. if (isset($entity_info['bundle keys']['bundle'])) {
  1197. $bundle_key = $entity_info['bundle keys']['bundle'];
  1198. if (isset($form_state['values'][$bundle_key])) {
  1199. $bundle = $form_state['values'][$bundle_key];
  1200. $form['xmlsitemap']['#bundle'] = $bundle;
  1201. }
  1202. }
  1203. }
  1204. xmlsitemap_link_bundle_settings_save($entity, $bundle, $form_state['values']['xmlsitemap']);
  1205. $entity_info = $form['xmlsitemap']['#entity_info'];
  1206. if (!empty($form['xmlsitemap']['#show_message'])) {
  1207. drupal_set_message(t('XML sitemap settings for the @bundle-label %bundle have been saved.', array('@bundle-label' => drupal_strtolower($entity_info['bundle label']), '%bundle' => $entity_info['bundles'][$bundle]['label'])));
  1208. }
  1209. // Unset the form values since we have already saved the bundle settings and
  1210. // we don't want these values to get saved as variables in-case this form
  1211. // also uses system_settings_form().
  1212. unset($form_state['values']['xmlsitemap']);
  1213. }
  1214. /**
  1215. * @todo Document this function.
  1216. * @todo Make these translatable
  1217. */
  1218. function xmlsitemap_get_changefreq_options() {
  1219. return array(
  1220. XMLSITEMAP_FREQUENCY_ALWAYS => 'always',
  1221. XMLSITEMAP_FREQUENCY_HOURLY => 'hourly',
  1222. XMLSITEMAP_FREQUENCY_DAILY => 'daily',
  1223. XMLSITEMAP_FREQUENCY_WEEKLY => 'weekly',
  1224. XMLSITEMAP_FREQUENCY_MONTHLY => 'monthly',
  1225. XMLSITEMAP_FREQUENCY_YEARLY => 'yearly',
  1226. );
  1227. }
  1228. /**
  1229. * Load a language object by its language code.
  1230. *
  1231. * @todo Remove when http://drupal.org/node/660736 is fixed in Drupal core.
  1232. *
  1233. * @param $language
  1234. * A language code. If not provided the default language will be returned.
  1235. * @return
  1236. * A language object.
  1237. */
  1238. function xmlsitemap_language_load($language = LANGUAGE_NONE) {
  1239. $languages = &drupal_static(__FUNCTION__);
  1240. if (!isset($languages)) {
  1241. $languages = language_list();
  1242. $languages[LANGUAGE_NONE] = NULL;
  1243. }
  1244. return isset($languages[$language]) ? $languages[$language] : NULL;
  1245. }
  1246. /**
  1247. * @defgroup xmlsitemap_context_api XML sitemap API for sitemap contexts.
  1248. * @{
  1249. */
  1250. function xmlsitemap_get_context_info($context = NULL, $reset = FALSE) {
  1251. global $language;
  1252. $info = &drupal_static(__FUNCTION__);
  1253. if ($reset) {
  1254. $info = NULL;
  1255. }
  1256. elseif ($cached = cache_get('xmlsitemap:context_info:' . $language->language)) {
  1257. $info = $cached->data;
  1258. }
  1259. if (!isset($info)) {
  1260. $info = module_invoke_all('xmlsitemap_context_info');
  1261. drupal_alter('xmlsitemap_context_info', $info);
  1262. ksort($info);
  1263. // Cache by language since this info contains translated strings.
  1264. cache_set('xmlsitemap:context_info:' . $language->language, $info);
  1265. }
  1266. if (isset($context)) {
  1267. return isset($info[$context]) ? $info[$context] : NULL;
  1268. }
  1269. return $info;
  1270. }
  1271. /**
  1272. * Get the sitemap context of the current request.
  1273. */
  1274. function xmlsitemap_get_current_context() {
  1275. $context = &drupal_static(__FUNCTION__);
  1276. if (!isset($context)) {
  1277. $context = module_invoke_all('xmlsitemap_context');
  1278. drupal_alter('xmlsitemap_context', $context);
  1279. asort($context);
  1280. }
  1281. return $context;
  1282. }
  1283. function _xmlsitemap_sitemap_context_summary(stdClass $sitemap, $context_key, array $context_info) {
  1284. $context_value = isset($sitemap->context[$context_key]) ? $sitemap->context[$context_key] : NULL;
  1285. if (!isset($context_value)) {
  1286. return t('Default');
  1287. }
  1288. elseif (!empty($context_info['summary callback'])) {
  1289. return $context_info['summary callback']($context_value);
  1290. }
  1291. else {
  1292. return $context_value;
  1293. }
  1294. }
  1295. /**
  1296. * @} End of "defgroup xmlsitemap_context_api"
  1297. */
  1298. /**
  1299. * Run a not-progressive batch operation.
  1300. */
  1301. function xmlsitemap_run_unprogressive_batch() {
  1302. $batch = batch_get();
  1303. if (!empty($batch)) {
  1304. // If there is already something in the batch, don't run.
  1305. return FALSE;
  1306. }
  1307. $args = func_get_args();
  1308. $batch_callback = array_shift($args);
  1309. if (!lock_acquire($batch_callback)) {
  1310. return FALSE;
  1311. }
  1312. // Attempt to increase the execution time.
  1313. drupal_set_time_limit(240);
  1314. // Build the batch array.
  1315. $batch = call_user_func_array($batch_callback, $args);
  1316. batch_set($batch);
  1317. // We need to manually set the progressive variable again.
  1318. // @todo Remove when http://drupal.org/node/638712 is fixed.
  1319. $batch =& batch_get();
  1320. $batch['progressive'] = FALSE;
  1321. // Run the batch process.
  1322. batch_process();
  1323. lock_release($batch_callback);
  1324. return TRUE;
  1325. }
  1326. /**
  1327. * Workaround for missing breadcrumbs on callback and action paths.
  1328. *
  1329. * @todo Remove when http://drupal.org/node/576290 is fixed.
  1330. */
  1331. function _xmlsitemap_set_breadcrumb($path = 'admin/config/search/xmlsitemap') {
  1332. $breadcrumb = array();
  1333. $path = explode('/', $path);
  1334. do {
  1335. $menu_path = implode('/', $path);
  1336. $menu_item = menu_get_item($menu_path);
  1337. array_unshift($breadcrumb, l($menu_item['title'], $menu_path));
  1338. } while (array_pop($path) && !empty($path));
  1339. array_unshift($breadcrumb, l(t('Home'), NULL));
  1340. drupal_set_breadcrumb($breadcrumb);
  1341. }
  1342. function xmlsitemap_get_operation_link($url, $options = array()) {
  1343. static $destination;
  1344. if (!isset($destination)) {
  1345. $destination = drupal_get_destination();
  1346. }
  1347. $link = array('href' => $url) + $options;
  1348. // Fetch the item's menu router link info and title.
  1349. if (!isset($link['title'])) {
  1350. $item = menu_get_item($url);
  1351. $link['title'] = $item['title'];
  1352. }
  1353. $link += array('query' => $destination);
  1354. return $link;
  1355. }