xmlsitemap.install 19.7 KB
Newer Older
Darren Oh's avatar
Darren Oh committed
1 2
<?php

3
/**
4 5 6 7 8 9 10 11 12 13 14 15 16
 * @file
 * Install, update and uninstall functions for the xmlsitemap module.
 *
 * @ingroup xmlsitemap
 */

/**
 * Implements hook_requirements().
 */
function xmlsitemap_requirements($phase) {
  $requirements = array();
  $t = get_t();

17 18 19 20 21 22 23 24 25
  // Check that required PHP extensions are enabled.
  // Note: Drupal 7 already requires the 'xml' extension.
  $required_extensions = array('xmlwriter');
  $missing_extensions = array_diff($required_extensions, array_filter($required_extensions, 'extension_loaded'));

  if (!empty($missing_extensions)) {
    $requirements['xmlsitemap_php_extensions'] = array(
      'title' => $t('XML sitemap PHP extensions'),
      'value' => $t('Disabled'),
26
      'severity' => REQUIREMENT_ERROR,
27 28 29
      '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(
        '@xmlsitemap_requirements' => 'http://drupal.org/documentation/modules/xmlsitemap/requirements',
      )) . theme('item_list', array('items' => $missing_extensions)),
30 31 32 33
    );
  }

  if ($phase == 'runtime') {
34 35 36 37 38 39 40 41 42 43 44
    // If clean URLs are disabled there must not be an actual sitemap.xml in
    // the root directory.
    if (variable_get('clean_url', 0) && file_exists(DRUPAL_ROOT . '/sitemap.xml')) {
      $requirements['xmlsitemap_file'] = array(
        'title' => $t('XML sitemap'),
        'value' => $t('Existing sitemap.xml file found.'),
        'severity' => REQUIREMENT_ERROR,
        'description' => $t('The XML sitemap module cannot display its XML output if there is an existing sitemap.xml file in your website root.'),
      );
    }

45
    // Check that the base directory and all its subdirectories are writable.
46 47 48 49
    $requirements['xmlsitemap_directory'] = array(
      'title' => $t('XML sitemap cache directory'),
      'value' => $t('Writable'),
    );
50
    if (!xmlsitemap_check_directory()) {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
      $requirements['xmlsitemap_directory']['value'] = $t('Not found or not writable');
      $requirements['xmlsitemap_directory']['severity'] = REQUIREMENT_ERROR;
      $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'));
    }
    else {
      $directories = xmlsitemap_check_all_directories();
      foreach ($directories as $directory => $writable) {
        if ($writable) {
          unset($directories[$directory]);
        }
      }
      if (!empty($directories)) {
        $requirements['xmlsitemap_directory']['value'] = $t('Not found or not writable');
        $requirements['xmlsitemap_directory']['severity'] = REQUIREMENT_ERROR;
        $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'));
      }
67
    }
68 69 70 71 72

    // The maximum number of links in a sitemap.
    $max_links = db_query("SELECT MAX(links) FROM {xmlsitemap_sitemap}")->fetchField();
    $max_links_limit = XMLSITEMAP_MAX_SITEMAP_LINKS * XMLSITEMAP_MAX_SITEMAP_LINKS;
    if ($max_links > $max_links_limit) {
73 74
      $requirements['xmlsitemap_link_count'] = array(
        'title' => $t('XML sitemap link count'),
75
        'value' => $max_links,
76 77 78 79
        'description' => $t('You have exceeded the number of links that your sitemap can contain (@num).', array('@num' => number_format($max_links))),
        'severity' => REQUIREMENT_ERROR,
      );
    }
80 81 82 83

    // The maximum number of chunks in a sitemap.
    $max_chunks = db_query("SELECT MAX(chunks) FROM {xmlsitemap_sitemap}")->fetchField();
    if ($max_chunks > XMLSITEMAP_MAX_SITEMAP_LINKS) {
84 85
      $requirements['xmlsitemap_chunk_count'] = array(
        'title' => $t('XML sitemap page count'),
86
        'value' => $max_chunks,
87
        'description' => $t('You have exceeded the number of sitemap pages (@number).', array('@number' => number_format(XMLSITEMAP_MAX_SITEMAP_LINKS))),
88 89 90 91 92 93
        'severity' => REQUIREMENT_ERROR,
      );
      if (!in_array(xmlsitemap_get_chunk_size(), array(50000, 'auto'))) {
        $requirements['xmlsitemap_chunk_count']['description'] .= ' ' . t('Please increase the number of links per page.');
      }
    }
94 95

    // Check maximum file size.
96
    $max_filesize = db_query("SELECT MAX(max_filesize) FROM {xmlsitemap_sitemap}")->fetchField();
97 98 99 100
    $requirements['xmlsitemap_file_size'] = array(
      'title' => $t('XML sitemap maximum file size'),
      'value' => format_size($max_filesize),
    );
101
    if ($max_filesize > XMLSITEMAP_MAX_SITEMAP_FILESIZE) {
102 103 104 105 106
      $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)));
      $requirements['xmlsitemap_file_size']['severity'] = REQUIREMENT_ERROR;
    }
    elseif (!variable_get('xmlsitemap_developer_mode', 0)) {
      unset($requirements['xmlsitemap_file_size']);
107
    }
108 109

    // Check when the cached files were last generated.
110
    $generated_last = variable_get('xmlsitemap_generated_last', 0);
111 112 113
    $generated_ago = REQUEST_TIME - $generated_last;
    $requirements['xmlsitemap_generated'] = array(
      'title' => $t('XML sitemap'),
114
      '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.'),
115 116
      'severity' => REQUIREMENT_OK,
    );
117
    if (variable_get('xmlsitemap_rebuild_needed', FALSE) && _xmlsitemap_rebuild_form_access()) {
118
      $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
119
      $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')));
120
    }
121
    elseif (variable_get('xmlsitemap_regenerate_needed', FALSE)) {
122 123 124 125 126
      if ($max_filesize == 0) {
        // A maximum sitemap file size of 0 indicates an error in generation.
        $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
      }
      elseif ($generated_ago >= variable_get('cron_threshold_error', 1209600)) {
127 128 129 130 131 132
        $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_ERROR;
      }
      elseif ($generated_ago >= variable_get('cron_threshold_warning', 172800)) {
        $requirements['xmlsitemap_generated']['severity'] = REQUIREMENT_WARNING;
      }
      if ($requirements['xmlsitemap_generated']['severity']) {
133
        $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()))));
134 135 136
      }
    }
  }
137

138 139 140 141 142
  return $requirements;
}

/**
 * Implements hook_schema().
143 144
 */
function xmlsitemap_schema() {
145
  // @todo Rename to xmlsitemap_link
146 147
  $schema['xmlsitemap'] = array(
    'description' => 'The base table for xmlsitemap links.',
148
    'fields' => array(
149 150 151 152 153 154 155 156 157 158
      'id' => array(
        'description' => 'Primary key with type; a unique id for the item.',
        'type' => 'int',
        'not null' => TRUE,
        'unsigned' => TRUE,
        'default' => 0,
      ),
      'type' => array(
        'description' => 'Primary key with id; the type of item (e.g. node, user, etc.).',
        'type' => 'varchar',
159
        'length' => 32,
160 161 162 163 164 165
        'not null' => TRUE,
        'default' => '',
      ),
      'subtype' => array(
        'description' => 'A sub-type identifier for the link (node type, menu name, term VID, etc.).',
        'type' => 'varchar',
166 167 168
        'length' => 128,
        'not null' => TRUE,
        'default' => '',
169 170 171 172 173 174 175 176 177 178
      ),
      'loc' => array(
        'description' => 'The URL to the item relative to the Drupal path.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      ),
      'language' => array(
        'description' => 'The {languages}.language of this link or an empty string if it is language-neutral.',
179
        'type' => 'varchar',
180
        'length' => 12,
181 182 183
        'not null' => TRUE,
        'default' => '',
      ),
184 185
      'access' => array(
        '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.',
186
        'type' => 'int',
187 188 189
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 1,
190
      ),
191 192
      'status' => array(
        'description' => 'An integer that represents if the item is included in the sitemap.',
193
        'type' => 'int',
194 195 196 197 198 199 200 201
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 1,
      ),
      'status_override' => array(
        'description' => 'A boolean that if TRUE means that the status field has been overridden from its default value.',
        'type' => 'int',
        'size' => 'tiny',
202 203 204
        'not null' => TRUE,
        'default' => 0,
      ),
205 206
      'lastmod' => array(
        'description' => 'The UNIX timestamp of last modification of the item.',
207 208 209 210 211 212
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'priority' => array(
213
        'description' => 'The priority of this URL relative to other URLs on your site. Valid values range from 0.0 to 1.0.',
214
        'type' => 'float',
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
        'default' => NULL,
        // @todo Convert this field to non-nullable.
        //'default' => 0.5,
        //'not null' => NULL,
      ),
      'priority_override' => array(
        'description' => 'A boolean that if TRUE means that the priority field has been overridden from its default value.',
        'type' => 'int',
        'size' => 'tiny',
        'not null' => TRUE,
        'default' => 0,
      ),
      'changefreq' => array(
        'description' => 'The average time in seconds between changes of this item.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'changecount' => array(
        'description' => 'The number of times this item has been changed. Used to help calculate the next changefreq value.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
240 241
      ),
    ),
242
    'primary key' => array('id', 'type'),
243
    'indexes' => array(
244 245 246 247
      'loc' => array('loc'),
      'access_status_loc' => array('access', 'status', 'loc'),
      'type_subtype' => array('type', 'subtype'),
      'language' => array('language'),
248 249 250
    ),
  );

251 252 253
  $schema['xmlsitemap_sitemap'] = array(
    'fields' => array(
      'smid' => array(
254
        'description' => 'The sitemap ID (the hashed value of {xmlsitemap}.context.',
255
        'type' => 'varchar',
256
        'length' => 64,
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
        'not null' => TRUE,
      ),
      'context' => array(
        'description' => 'Serialized array with the sitemaps context',
        'type' => 'text',
        'not null' => TRUE,
        'serialize' => TRUE,
      ),
      'updated' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'links' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'chunks' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
283 284 285 286 287 288
      'max_filesize' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
289 290 291 292 293 294 295
      //'queued' => array(
      //  'type' => 'int',
      //  'unsigned' => TRUE,
      //  'not null' => TRUE,
      //  'default' => 0,
      //  'description' => 'Time when this sitemap was queued for regeneration, 0 if not queued.',
      //),
296 297 298 299
    ),
    'primary key' => array('smid'),
  );

300 301 302
  return $schema;
}

303 304 305 306 307 308 309 310 311 312 313 314
/**
 * Implements hook_install().
 */
function xmlsitemap_install() {
  // Set this module's weight to 1 so xmlsitemap_cron() runs after all other
  // xmlsitemap_x_cron() runs.
  db_update('system')
    ->fields(array('weight' => 1))
    ->condition('type', 'module')
    ->condition('name', 'xmlsitemap')
    ->execute();

315
  // Load the module.
316 317 318 319 320 321 322 323 324 325
  drupal_load('module', 'xmlsitemap');

  // Insert the homepage link into the {xmlsitemap} table so we do not show an
  // empty sitemap after install.
  db_insert('xmlsitemap')
    ->fields(array(
      'type' => 'frontpage',
      'id' => 0,
      'loc' => '',
      'priority' => variable_get('xmlsitemap_frontpage_priority', 1.0),
326
      'changefreq' => variable_get('xmlsitemap_frontpage_changefreq', XMLSITEMAP_FREQUENCY_DAILY),
327 328 329 330 331
      'language' => LANGUAGE_NONE,
    ))
    ->execute();

  // Insert the default context sitemap.
332
  $context = array();
333 334
  db_insert('xmlsitemap_sitemap')
    ->fields(array(
335 336
      'smid' => xmlsitemap_sitemap_get_context_hash($context),
      'context' => serialize($context),
337 338
    ))
    ->execute();
339 340

  // @todo Does the sitemap show up on first install or is it a 404 page?
341 342 343 344 345

  // Create the link process the queue.
  /** @var DrupalReliableQueueInterface $queue */
  $queue = DrupalQueue::get('xmlsitemap_link_process', TRUE);
  $queue->createQueue();
346 347 348 349 350 351
}

/**
 * Implements hook_enable().
 */
function xmlsitemap_enable() {
352
  // Ensure the file cache directory is available and ready.
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
  xmlsitemap_check_directory();

  variable_set('xmlsitemap_regenerate_needed', TRUE);
}

/**
 * Implements hook_uninstall().
 */
function xmlsitemap_uninstall() {
  // Remove variables.
  drupal_load('module', 'xmlsitemap');
  $variables = array_keys(xmlsitemap_variables());
  foreach ($variables as $variable) {
    variable_del($variable);
  }

  // Remove the file cache directory.
370
  xmlsitemap_clear_directory(NULL, TRUE);
371 372 373 374 375

  // Remove the queue.
  /** @var DrupalReliableQueueInterface $queue */
  $queue = DrupalQueue::get('xmlsitemap_link_process', TRUE);
  $queue->deleteQueue();
376 377
}

378
/**
379
 * Implements hook_update_last_removed().
380
 */
381 382
function xmlsitemap_update_last_removed() {
  return 6201;
383
}
384 385

/**
386
 * Create the {xmlsitemap_sitemap} table and add the sitemap context data.
387
 */
388
function xmlsitemap_update_6202() {
389
  if (!db_table_exists('xmlsitemap_sitemap')) {
390 391 392 393 394
    $schema['xmlsitemap_sitemap'] = array(
      'fields' => array(
        'smid' => array(
          'description' => 'Sitemap ID',
          'type' => 'serial',
395
          'unsigned' => TRUE,
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
          'not null' => TRUE,
        ),
        'context_hash' => array(
          'description' => 'The MD5 hash of the context field.',
          'type' => 'varchar',
          'length' => 32,
          'not null' => TRUE,
          'default' => '',
        ),
        'context' => array(
          'description' => 'Serialized array with the sitemaps context',
          'type' => 'text',
          'not null' => TRUE,
        ),
        'updated' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'default' => 0,
        ),
        'links' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'default' => 0,
        ),
        'chunks' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'default' => 0,
        ),
428
      ),
429 430 431
      'primary key' => array('smid'),
      'unique keys' => array(
        'context_hash' => array('context_hash'),
432
      ),
433 434 435 436 437
    );
    db_create_table('xmlsitemap_sitemap', $schema['xmlsitemap_sitemap']);
  }

  // Add the default sitemap(s) and use language contexts if possible.
438
  if (!db_query_range("SELECT 1 FROM {xmlsitemap_sitemap}", 0, 1)->fetchField()) {
439
    // Refresh the schema and load the module if it's disabled.
440
    drupal_get_schema(NULL, TRUE);
441
    drupal_load('module', 'xmlsitemap');
442 443 444

    if (module_exists('xmlsitemap_i18n') && $languages = variable_get('xmlsitemap_languages', array())) {
      foreach ($languages as $language) {
445 446
        $sitemap = new stdClass();
        $sitemap->context = array('language' => $language);
447 448 449 450
        xmlsitemap_sitemap_save($sitemap);
      }
    }
    else {
451 452
      $sitemap = new stdClass();
      $sitemap->context = array();
453 454 455 456 457 458
      xmlsitemap_sitemap_save($sitemap);
    }
  }

  // Language variable is no longer needed, so go ahead and delete it.
  variable_del('xmlsitemap_languages');
459 460 461 462

  // Ensure that the sitemaps will be refreshed on next cron.
  variable_set('xmlsitemap_generated_last', 0);
  variable_set('xmlsitemap_regenerate_needed', TRUE);
463
}
464

465 466 467 468 469
/**
 * Convert the xmlsitemap_max_filesize variable to a max_filesize column
 * per-sitemap.
 */
function xmlsitemap_update_6203() {
470 471 472
  if (db_field_exists('xmlsitemap_sitemap', 'max_filesize')) {
    return;
  }
473

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  // Add the max_filesize column.
  $field = array(
    'type' => 'int',
    'unsigned' => TRUE,
    'not null' => TRUE,
    'default' => 0,
  );
  db_add_field('xmlsitemap_sitemap', 'max_filesize', $field);

  // Scan each sitemap directory for the largest file.
  drupal_load('module', 'xmlsitemap');
  $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  foreach ($sitemaps as $sitemap) {
    xmlsitemap_sitemap_get_max_filesize($sitemap);
    db_update('xmlsitemap_sitemap')
489 490
      ->fields(array('max_filesize' => $sitemap->max_filesize))
      ->condition('smid', $sitemap->smid)
491
      ->execute();
492 493 494 495 496 497
  }

  variable_del('xmlsitemap_max_filesize');
  variable_del('xmlsitemap_max_chunks');
}

498 499 500 501
/**
 * Convert {xmlsitemap}.context_hash to replace {xmlsitemap}.smid.
 */
function xmlsitemap_update_6204() {
502 503 504 505 506 507 508 509 510 511 512 513
  if (db_field_exists('xmlsitemap_sitemap', 'context_hash')) {
    db_drop_unique_key('xmlsitemap_sitemap', 'context_hash');
    db_drop_field('xmlsitemap_sitemap', 'smid');

    // Rename context_hash to the new smid column.
    $smid_field = array(
      'description' => 'The sitemap ID (the hashed value of {xmlsitemap}.context.',
      'type' => 'varchar',
      'length' => 64,
      'not null' => TRUE,
    );
    db_change_field('xmlsitemap_sitemap', 'context_hash', 'smid', $smid_field);
514

515 516 517
    // Re-add the primary key now that the smid field is changed.
    // We don't need to drop the primary key since we already dropped the field
    // that was the primary key.
518
    db_add_primary_key('xmlsitemap_sitemap', array('smid'));
519
  }
520 521

  _xmlsitemap_sitemap_rehash_all();
522 523
}

524 525 526 527 528 529 530 531 532
/**
 * Update empty string languages to LANGUAGE_NONE.
 */
function xmlsitemap_update_7200() {
  db_update('xmlsitemap')
    ->fields(array('language' => LANGUAGE_NONE))
    ->condition('language', '')
    ->execute();
}
533 534 535 536 537 538 539

/**
 * Re-run xmlsitemap_update_6202() to ensure sitemap data has been added.
 */
function xmlsitemap_update_7201() {
  xmlsitemap_update_6202();
}
540 541 542 543 544 545 546 547

/**
 * Convert the xmlsitemap_max_filesize variable to a max_filesize column
 * per-sitemap.
 */
function xmlsitemap_update_7202() {
  xmlsitemap_update_6203();
}
548 549 550 551 552 553

/**
 * Convert {xmlsitemap}.context_hash to replace {xmlsitemap}.smid.
 */
function xmlsitemap_update_7203() {
  xmlsitemap_update_6204();
554 555 556 557 558 559 560 561 562 563 564
  _xmlsitemap_sitemap_rehash_all();
}

function _xmlsitemap_sitemap_rehash_all() {
  // Reload the schema cache and reprocess all sitemap hashes into smids.
  drupal_load('module', 'xmlsitemap');
  drupal_get_schema(NULL, TRUE);

  // Force a rehash of all sitemaps.
  $sitemaps = xmlsitemap_sitemap_load_multiple(FALSE);
  foreach ($sitemaps as $sitemap) {
565 566
    $hash = xmlsitemap_sitemap_get_context_hash($sitemap->context);
    if ($hash != $sitemap->smid) {
567 568 569
      xmlsitemap_sitemap_save($sitemap);
    }
  }
570
}