Deprecate broken accessor and add a new one
[mediawiki.git] / maintenance / generateSitemap.php
blob737c4eba615b73fa217edbaa8d36b8120d1f4bf4
1 <?php
2 define( 'GS_MAIN', -2 );
3 define( 'GS_TALK', -1 );
4 /**
5 * Creates a sitemap for the site
7 * @ingroup Maintenance
9 * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
10 * @copyright Copyright © 2005, Jens Frank <jeluf@gmx.de>
11 * @copyright Copyright © 2005, Brion Vibber <brion@pobox.com>
13 * @see http://www.sitemaps.org/
14 * @see http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd
16 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
19 class GenerateSitemap {
20 /**
21 * The maximum amount of urls in a sitemap file
23 * @link http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd
25 * @var int
27 var $url_limit;
29 /**
30 * The maximum size of a sitemap file
32 * @link http://www.sitemaps.org/faq.php#faq_sitemap_size
34 * @var int
36 var $size_limit;
38 /**
39 * The path to prepend to the filename
41 * @var string
43 var $fspath;
45 /**
46 * The path to append to the domain name
48 * @var string
50 var $path;
52 /**
53 * Whether or not to use compression
55 * @var bool
57 var $compress;
59 /**
60 * The number of entries to save in each sitemap file
62 * @var array
64 var $limit = array();
66 /**
67 * Key => value entries of namespaces and their priorities
69 * @var array
71 var $priorities = array(
72 // Custom main namespaces
73 GS_MAIN => '0.5',
74 // Custom talk namesspaces
75 GS_TALK => '0.1',
76 // MediaWiki standard namespaces
77 NS_MAIN => '1.0',
78 NS_TALK => '0.1',
79 NS_USER => '0.5',
80 NS_USER_TALK => '0.1',
81 NS_PROJECT => '0.5',
82 NS_PROJECT_TALK => '0.1',
83 NS_FILE => '0.5',
84 NS_FILE_TALK => '0.1',
85 NS_MEDIAWIKI => '0.0',
86 NS_MEDIAWIKI_TALK => '0.1',
87 NS_TEMPLATE => '0.0',
88 NS_TEMPLATE_TALK => '0.1',
89 NS_HELP => '0.5',
90 NS_HELP_TALK => '0.1',
91 NS_CATEGORY => '0.5',
92 NS_CATEGORY_TALK => '0.1',
95 /**
96 * A one-dimensional array of namespaces in the wiki
98 * @var array
100 var $namespaces = array();
103 * When this sitemap batch was generated
105 * @var string
107 var $timestamp;
110 * A database slave object
112 * @var object
114 var $dbr;
117 * A resource pointing to the sitemap index file
119 * @var resource
121 var $findex;
125 * A resource pointing to a sitemap file
127 * @var resource
129 var $file;
132 * A resource pointing to php://stderr
134 * @var resource
136 var $stderr;
139 * Constructor
141 * @param string $fspath The path to prepend to the filenames, used to
142 * save them somewhere else than in the root directory
143 * @param string $path The path to append to the domain name
144 * @param bool $compress Whether to compress the sitemap files
146 function GenerateSitemap( $fspath, $compress ) {
147 global $wgScriptPath;
149 $this->url_limit = 50000;
150 $this->size_limit = pow( 2, 20 ) * 10;
151 $this->fspath = self::init_path( $fspath );
153 $this->compress = $compress;
155 $this->stderr = fopen( 'php://stderr', 'wt' );
156 $this->dbr = wfGetDB( DB_SLAVE );
157 $this->generateNamespaces();
158 $this->timestamp = wfTimestamp( TS_ISO_8601, wfTimestampNow() );
161 $this->findex = fopen( "{$this->fspath}sitemap-index-" . wfWikiID() . ".xml", 'wb' );
165 * Create directory if it does not exist and return pathname with a trailing slash
167 private static function init_path( $fspath ) {
168 if( !isset( $fspath ) ) {
169 return null;
171 # Create directory if needed
172 if( $fspath && !is_dir( $fspath ) ) {
173 mkdir( $fspath, 0755 ) or die("Can not create directory $fspath.\n");
176 return realpath( $fspath ). DIRECTORY_SEPARATOR ;
180 * Generate a one-dimensional array of existing namespaces
182 function generateNamespaces() {
183 $fname = 'GenerateSitemap::generateNamespaces';
185 // Only generate for specific namespaces if $wgSitemapNamespaces is an array.
186 global $wgSitemapNamespaces;
187 if( is_array( $wgSitemapNamespaces ) ) {
188 $this->namespaces = $wgSitemapNamespaces;
189 return;
192 $res = $this->dbr->select( 'page',
193 array( 'page_namespace' ),
194 array(),
195 $fname,
196 array(
197 'GROUP BY' => 'page_namespace',
198 'ORDER BY' => 'page_namespace',
202 while ( $row = $this->dbr->fetchObject( $res ) )
203 $this->namespaces[] = $row->page_namespace;
207 * Get the priority of a given namespace
209 * @param int $namespace The namespace to get the priority for
211 * @return string
214 function priority( $namespace ) {
215 return isset( $this->priorities[$namespace] ) ? $this->priorities[$namespace] : $this->guessPriority( $namespace );
219 * If the namespace isn't listed on the priority list return the
220 * default priority for the namespace, varies depending on whether it's
221 * a talkpage or not.
223 * @param int $namespace The namespace to get the priority for
225 * @return string
227 function guessPriority( $namespace ) {
228 return MWNamespace::isMain( $namespace ) ? $this->priorities[GS_MAIN] : $this->priorities[GS_TALK];
232 * Return a database resolution of all the pages in a given namespace
234 * @param int $namespace Limit the query to this namespace
236 * @return resource
238 function getPageRes( $namespace ) {
239 $fname = 'GenerateSitemap::getPageRes';
241 return $this->dbr->select( 'page',
242 array(
243 'page_namespace',
244 'page_title',
245 'page_touched',
247 array( 'page_namespace' => $namespace ),
248 $fname
253 * Main loop
255 * @access public
257 function main() {
258 global $wgContLang;
260 fwrite( $this->findex, $this->openIndex() );
262 foreach ( $this->namespaces as $namespace ) {
263 $res = $this->getPageRes( $namespace );
264 $this->file = false;
265 $this->generateLimit( $namespace );
266 $length = $this->limit[0];
267 $i = $smcount = 0;
269 $fns = $wgContLang->getFormattedNsText( $namespace );
270 $this->debug( "$namespace ($fns)" );
271 while ( $row = $this->dbr->fetchObject( $res ) ) {
272 if ( $i++ === 0 || $i === $this->url_limit + 1 || $length + $this->limit[1] + $this->limit[2] > $this->size_limit ) {
273 if ( $this->file !== false ) {
274 $this->write( $this->file, $this->closeFile() );
275 $this->close( $this->file );
277 $filename = $this->sitemapFilename( $namespace, $smcount++ );
278 $this->file = $this->open( $this->fspath . $filename, 'wb' );
279 $this->write( $this->file, $this->openFile() );
280 fwrite( $this->findex, $this->indexEntry( $filename ) );
281 $this->debug( "\t$this->fspath$filename" );
282 $length = $this->limit[0];
283 $i = 1;
285 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
286 $date = wfTimestamp( TS_ISO_8601, $row->page_touched );
287 $entry = $this->fileEntry( $title->getFullURL(), $date, $this->priority( $namespace ) );
288 $length += strlen( $entry );
289 $this->write( $this->file, $entry );
290 // generate pages for language variants
291 if($wgContLang->hasVariants()){
292 $variants = $wgContLang->getVariants();
293 foreach($variants as $vCode){
294 if($vCode==$wgContLang->getCode()) continue; // we don't want default variant
295 $entry = $this->fileEntry( $title->getFullURL('',$vCode), $date, $this->priority( $namespace ) );
296 $length += strlen( $entry );
297 $this->write( $this->file, $entry );
301 if ( $this->file ) {
302 $this->write( $this->file, $this->closeFile() );
303 $this->close( $this->file );
306 fwrite( $this->findex, $this->closeIndex() );
307 fclose( $this->findex );
311 * gzopen() / fopen() wrapper
313 * @return resource
315 function open( $file, $flags ) {
316 return $this->compress ? gzopen( $file, $flags ) : fopen( $file, $flags );
320 * gzwrite() / fwrite() wrapper
322 function write( &$handle, $str ) {
323 if ( $this->compress )
324 gzwrite( $handle, $str );
325 else
326 fwrite( $handle, $str );
330 * gzclose() / fclose() wrapper
332 function close( &$handle ) {
333 if ( $this->compress )
334 gzclose( $handle );
335 else
336 fclose( $handle );
340 * Get a sitemap filename
342 * @static
344 * @param int $namespace The namespace
345 * @param int $count The count
347 * @return string
349 function sitemapFilename( $namespace, $count ) {
350 $ext = $this->compress ? '.gz' : '';
351 return "sitemap-".wfWikiID()."-NS_$namespace-$count.xml$ext";
355 * Return the XML required to open an XML file
357 * @static
359 * @return string
361 function xmlHead() {
362 return '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
366 * Return the XML schema being used
368 * @static
370 * @returns string
372 function xmlSchema() {
373 return 'http://www.sitemaps.org/schemas/sitemap/0.9';
377 * Return the XML required to open a sitemap index file
379 * @return string
381 function openIndex() {
382 return $this->xmlHead() . '<sitemapindex xmlns="' . $this->xmlSchema() . '">' . "\n";
386 * Return the XML for a single sitemap indexfile entry
388 * @static
390 * @param string $filename The filename of the sitemap file
392 * @return string
394 function indexEntry( $filename ) {
395 return
396 "\t<sitemap>\n" .
397 "\t\t<loc>$filename</loc>\n" .
398 "\t\t<lastmod>{$this->timestamp}</lastmod>\n" .
399 "\t</sitemap>\n";
403 * Return the XML required to close a sitemap index file
405 * @static
407 * @return string
409 function closeIndex() {
410 return "</sitemapindex>\n";
414 * Return the XML required to open a sitemap file
416 * @return string
418 function openFile() {
419 return $this->xmlHead() . '<urlset xmlns="' . $this->xmlSchema() . '">' . "\n";
423 * Return the XML for a single sitemap entry
425 * @static
427 * @param string $url An RFC 2396 compliant URL
428 * @param string $date A ISO 8601 date
429 * @param string $priority A priority indicator, 0.0 - 1.0 inclusive with a 0.1 stepsize
431 * @return string
433 function fileEntry( $url, $date, $priority ) {
434 return
435 "\t<url>\n" .
436 "\t\t<loc>$url</loc>\n" .
437 "\t\t<lastmod>$date</lastmod>\n" .
438 "\t\t<priority>$priority</priority>\n" .
439 "\t</url>\n";
443 * Return the XML required to close sitemap file
445 * @static
446 * @return string
448 function closeFile() {
449 return "</urlset>\n";
453 * Write a string to stderr followed by a UNIX newline
455 function debug( $str ) {
456 fwrite( $this->stderr, "$str\n" );
460 * Populate $this->limit
462 function generateLimit( $namespace ) {
463 $title = Title::makeTitle( $namespace, str_repeat( "\xf0\xa8\xae\x81", 63 ) . "\xe5\x96\x83" );
465 $this->limit = array(
466 strlen( $this->openFile() ),
467 strlen( $this->fileEntry( $title->getFullUrl(), wfTimestamp( TS_ISO_8601, wfTimestamp() ), $this->priority( $namespace ) ) ),
468 strlen( $this->closeFile() )
473 if ( in_array( '--help', $argv ) ) {
474 echo <<<EOT
475 Usage: php generateSitemap.php [options]
476 --help show this message
478 --fspath=<path> The file system path to save to, e.g /tmp/sitemap
479 Saves to current directory if not given.
481 --server=<server> The protocol and server name to use in URLs, e.g.
482 http://en.wikipedia.org. This is sometimes necessary because
483 server name detection may fail in command line scripts.
485 --compress=[yes|no] compress the sitemap files, default yes
487 EOT;
488 die( -1 );
491 $optionsWithArgs = array( 'fspath', 'server', 'compress' );
492 require_once( dirname( __FILE__ ) . '/commandLine.inc' );
494 if ( isset( $options['server'] ) ) {
495 $wgServer = $options['server'];
498 $gs = new GenerateSitemap( @$options['fspath'], @$options['compress'] !== 'no' );
499 $gs->main();