Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

tools/i18n/makepot.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
require_once dirname( __FILE__ ) . '/not-gettexted.php';
3
require_once dirname( __FILE__ ) . '/pot-ext-meta.php';
4
require_once dirname( __FILE__ ) . '/extract.php';
5
6
if ( !defined( 'STDERR' ) ) {
7
	define( 'STDERR', fopen( 'php://stderr', 'w' ) );
8
}
9
10
/**
11
 * Class to create POT files for
12
 *  - WordPress 3.4+
13
 *  - WordPress plugins
14
 *  - WordPress themes
15
 *  - GlotPress (standalone)
16
 *  - WordPress.org projects (Rosetta, forums, directories)
17
 *  - WordCamp.org
18
 *
19
 * Support for older projects can be found in the legacy branch:
20
 * https://i18n.trac.wordpress.org/browser/tools/branches/legacy
21
 */
22
class MakePOT {
23
	private $max_header_lines = 30;
24
25
	public $projects = array(
26
		'generic',
27
		'wp-frontend',
28
		'wp-admin',
29
		'wp-network-admin',
30
		'wp-tz',
31
		'wp-plugin',
32
		'wp-theme',
33
		'glotpress',
34
		'rosetta',
35
		'wporg-bb-forums',
36
		'wporg-themes',
37
		'wporg-plugins',
38
		'wporg-forums',
39
		'wordcamporg',
40
	);
41
42
	public $rules = array(
43
		'_' => array('string'),
44
		'__' => array('string'),
45
		'_e' => array('string'),
46
		'_c' => array('string'),
47
		'_n' => array('singular', 'plural'),
48
		'_n_noop' => array('singular', 'plural'),
49
		'_nc' => array('singular', 'plural'),
50
		'__ngettext' => array('singular', 'plural'),
51
		'__ngettext_noop' => array('singular', 'plural'),
52
		'_x' => array('string', 'context'),
53
		'_ex' => array('string', 'context'),
54
		'_nx' => array('singular', 'plural', null, 'context'),
55
		'_nx_noop' => array('singular', 'plural', 'context'),
56
		'_n_js' => array('singular', 'plural'),
57
		'_nx_js' => array('singular', 'plural', 'context'),
58
		'esc_attr__' => array('string'),
59
		'esc_html__' => array('string'),
60
		'esc_attr_e' => array('string'),
61
		'esc_html_e' => array('string'),
62
		'esc_attr_x' => array('string', 'context'),
63
		'esc_html_x' => array('string', 'context'),
64
		'comments_number_link' => array('string', 'singular', 'plural'),
65
	);
66
67
	private $ms_files = array(
68
		'ms-.*', '.*/ms-.*', '.*/my-.*', 'wp-activate\.php', 'wp-signup\.php',
69
		'wp-admin/network\.php', 'wp-admin/network/.*\.php', 'wp-admin/includes/ms\.php',
70
		'wp-admin/includes/class-wp-ms.*', 'wp-admin/includes/network\.php',
71
	);
72
73
	private $temp_files = array();
74
75
	public $meta = array(
76
		'default' => array(
77
			'from-code' => 'utf-8',
78
			'msgid-bugs-address' => 'https://make.wordpress.org/polyglots/',
79
			'language' => 'php',
80
			'add-comments' => 'translators',
81
			'comments' => "Copyright (C) {year} {package-name}\nThis file is distributed under the same license as the {package-name} package.",
82
		),
83
		'generic' => array(),
84
		'wp-frontend' => array(
85
			'description' => 'Translation of frontend strings in WordPress {version}',
86
			'copyright-holder' => 'WordPress',
87
			'package-name' => 'WordPress',
88
			'package-version' => '{version}',
89
		),
90
		'wp-admin' => array(
91
			'description' => 'Translation of site admin strings in WordPress {version}',
92
			'copyright-holder' => 'WordPress',
93
			'package-name' => 'WordPress',
94
			'package-version' => '{version}',
95
		),
96
		'wp-network-admin' => array(
97
			'description' => 'Translation of network admin strings in WordPress {version}',
98
			'copyright-holder' => 'WordPress',
99
			'package-name' => 'WordPress',
100
			'package-version' => '{version}',
101
		),
102
		'wp-tz' => array(
103
			'description' => 'Translation of timezone strings in WordPress {version}',
104
			'copyright-holder' => 'WordPress',
105
			'package-name' => 'WordPress',
106
			'package-version' => '{version}',
107
		),
108
		'wp-plugin' => array(
109
			'description' => 'Translation of the WordPress plugin {name} {version} by {author}',
110
			'msgid-bugs-address' => 'https://wordpress.org/support/plugin/{slug}',
111
			'copyright-holder' => '{author}',
112
			'package-name' => '{name}',
113
			'package-version' => '{version}',
114
		),
115
		'wp-theme' => array(
116
			'description' => 'Translation of the WordPress theme {name} {version} by {author}',
117
			'msgid-bugs-address' => 'https://wordpress.org/support/theme/{slug}',
118
			'copyright-holder' => '{author}',
119
			'package-name' => '{name}',
120
			'package-version' => '{version}',
121
			'comments' => 'Copyright (C) {year} {author}\nThis file is distributed under the same license as the {package-name} package.',
122
		),
123
		'glotpress' => array(
124
			'description' => 'Translation of GlotPress',
125
			'copyright-holder' => 'GlotPress',
126
			'package-name' => 'GlotPress',
127
		),
128
		'wporg-bb-forums' => array(
129
			'description' => 'WordPress.org International Forums',
130
			'copyright-holder' => 'WordPress',
131
			'package-name' => 'WordPress.org International Forums',
132
		),
133
		'wporg' => array(
134
			'description' => 'WordPress.org',
135
			'copyright-holder' => 'WordPress',
136
			'package-name' => 'WordPress.org',
137
		),
138
		'wordcamporg' => array(
139
			'description' => 'WordCamp.org',
140
			'copyright-holder' => 'WordPress',
141
			'package-name' => 'WordCamp.org',
142
		),
143
		'rosetta' => array(
144
			'description' => 'Rosetta (.wordpress.org locale sites)',
145
			'copyright-holder' => 'WordPress',
146
			'package-name' => 'Rosetta',
147
		),
148
	);
149
150
	public function __construct($deprecated = true) {
0 ignored issues
show
The parameter $deprecated is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
151
		$this->extractor = new StringExtractor( $this->rules );
0 ignored issues
show
The property extractor does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
152
	}
153
154
	public function __destruct() {
155
		foreach ( $this->temp_files as $temp_file )
156
			unlink( $temp_file );
157
	}
158
159
	private function tempnam( $file ) {
160
		$tempnam = tempnam( sys_get_temp_dir(), $file );
161
		$this->temp_files[] = $tempnam;
162
		return $tempnam;
163
	}
164
165
	private function realpath_missing($path) {
166
		return realpath(dirname($path)).DIRECTORY_SEPARATOR.basename($path);
167
	}
168
169
	private function xgettext($project, $dir, $output_file, $placeholders = array(), $excludes = array(), $includes = array()) {
170
		$meta = array_merge( $this->meta['default'], $this->meta[$project] );
171
		$placeholders = array_merge( $meta, $placeholders );
172
		$meta['output'] = $this->realpath_missing( $output_file );
173
		$placeholders['year'] = date( 'Y' );
174
		$placeholder_keys = array_map( create_function( '$x', 'return "{".$x."}";' ), array_keys( $placeholders ) );
175
		$placeholder_values = array_values( $placeholders );
176
		foreach($meta as $key => $value) {
177
			$meta[$key] = str_replace($placeholder_keys, $placeholder_values, $value);
178
		}
179
180
		$originals = $this->extractor->extract_from_directory( $dir, $excludes, $includes );
181
		$pot = new PO;
182
		$pot->entries = $originals->entries;
183
184
		$pot->set_header( 'Project-Id-Version', $meta['package-name'].' '.$meta['package-version'] );
185
		$pot->set_header( 'Report-Msgid-Bugs-To', $meta['msgid-bugs-address'] );
186
		$pot->set_header( 'POT-Creation-Date', gmdate( 'Y-m-d H:i:s+00:00' ) );
187
		$pot->set_header( 'MIME-Version', '1.0' );
188
		$pot->set_header( 'Content-Type', 'text/plain; charset=UTF-8' );
189
		$pot->set_header( 'Content-Transfer-Encoding', '8bit' );
190
		$pot->set_header( 'PO-Revision-Date', date( 'Y') . '-MO-DA HO:MI+ZONE' );
191
		$pot->set_header( 'Last-Translator', 'FULL NAME <EMAIL@ADDRESS>' );
192
		$pot->set_header( 'Language-Team', 'LANGUAGE <[email protected]>' );
193
		$pot->set_comment_before_headers( $meta['comments'] );
194
		$pot->export_to_file( $output_file );
195
		return true;
196
	}
197
198
	public function wp_generic($dir, $args) {
199
		$defaults = array(
200
			'project' => 'wp-core',
201
			'output' => null,
202
			'default_output' => 'wordpress.pot',
203
			'includes' => array(),
204
			'excludes' => array_merge(
205
				array( 'wp-admin/includes/continents-cities\.php', 'wp-content/themes/twenty.*', ),
206
				$this->ms_files
207
			),
208
			'extract_not_gettexted' => false,
209
			'not_gettexted_files_filter' => false,
210
		);
211
		$args = array_merge( $defaults, $args );
212
		extract( $args );
213
		$placeholders = array();
214
		if ( $wp_version = $this->wp_version( $dir ) )
215
			$placeholders['version'] = $wp_version;
216
		else
217
			return false;
218
		$output = is_null( $output )? $default_output : $output;
219
		$res = $this->xgettext( $project, $dir, $output, $placeholders, $excludes, $includes );
220
		if ( !$res ) return false;
221
222
		if ( $extract_not_gettexted ) {
223
			$old_dir = getcwd();
224
			$output = realpath( $output );
225
			chdir( $dir );
226
			$php_files = NotGettexted::list_php_files('.');
227
			$php_files = array_filter( $php_files, $not_gettexted_files_filter );
228
			$not_gettexted = new NotGettexted;
229
			$res = $not_gettexted->command_extract( $output, $php_files );
230
			chdir( $old_dir );
231
			/* Adding non-gettexted strings can repeat some phrases */
232
			$output_shell = escapeshellarg( $output );
233
			system( "msguniq --use-first $output_shell -o $output_shell" );
234
		}
235
		return $res;
236
	}
237
238
	public function wp_frontend( $dir, $output ) {
239
		if ( ! file_exists( "$dir/wp-admin/user/about.php" ) ) {
240
			return false;
241
		}
242
243
		$excludes = array( 'wp-admin/.*', 'wp-content/themes/.*', 'wp-includes/class-pop3\.php' );
244
245
		// Exclude Akismet all together for 3.9+.
246
		if ( file_exists( "$dir/wp-admin/css/about.css" ) ) {
247
			$excludes[] = 'wp-content/plugins/akismet/.*';
248
		}
249
250
		return $this->wp_generic( $dir, array(
251
			'project' => 'wp-frontend', 'output' => $output,
252
			'includes' => array(),
253
			'excludes' => $excludes,
254
			'default_output' => 'wordpress.pot',
255
		) );
256
	}
257
258
	public function wp_admin($dir, $output) {
259
		$frontend_pot = $this->tempnam( 'frontend.pot' );
260
		if ( false === $frontend_pot ) {
261
			return false;
262
		}
263
264
		$frontend_result = $this->wp_frontend( $dir, $frontend_pot );
265
		if ( ! $frontend_result ) {
266
			return false;
267
		}
268
269
		$network_admin_files = $this->get_wp_network_admin_files( $dir );
270
271
		$result = $this->wp_generic( $dir, array(
272
			'project' => 'wp-admin', 'output' => $output,
273
			'includes' => array( 'wp-admin/.*' ),
274
			'excludes' => array_merge( array( 'wp-admin/includes/continents-cities\.php' ), $network_admin_files ),
275
			'default_output' => 'wordpress-admin.pot',
276
		) );
277
		if ( ! $result ) {
278
			return false;
279
		}
280
281
		$potextmeta = new PotExtMeta;
282
283
		if ( ! file_exists( "$dir/wp-admin/css/about.css" ) ) { // < 3.9
284
			$result = $potextmeta->append( "$dir/wp-content/plugins/akismet/akismet.php", $output );
285
			if ( ! $result ) {
286
				return false;
287
			}
288
		}
289
290
		$result = $potextmeta->append( "$dir/wp-content/plugins/hello.php", $output );
291
		if ( ! $result ) {
292
			return false;
293
		}
294
295
		/* Adding non-gettexted strings can repeat some phrases */
296
		$output_shell = escapeshellarg( $output );
297
		system( "msguniq $output_shell -o $output_shell" );
298
299
		$common_pot = $this->tempnam( 'common.pot' );
300
		if ( ! $common_pot ) {
301
			return false;
302
		}
303
		$admin_pot = realpath( is_null( $output ) ? 'wordpress-admin.pot' : $output );
304
		system( "msgcat --more-than=1 --use-first $frontend_pot $admin_pot > $common_pot" );
305
		system( "msgcat -u --use-first $admin_pot $common_pot -o $admin_pot" );
306
		return true;
307
	}
308
309
	public function wp_network_admin($dir, $output) {
310
		if ( ! file_exists( "$dir/wp-admin/user/about.php" ) ) return false;
311
312
		$frontend_pot = $this->tempnam( 'frontend.pot' );
313
		if ( false === $frontend_pot ) return false;
314
315
		$frontend_result = $this->wp_frontend( $dir, $frontend_pot );
316
		if ( ! $frontend_result )
317
			return false;
318
319
		$admin_pot = $this->tempnam( 'admin.pot' );
320
		if ( false === $admin_pot ) return false;
321
322
		$admin_result = $this->wp_admin( $dir, $admin_pot );
323
		if ( ! $admin_result )
324
			return false;
325
326
		$result = $this->wp_generic( $dir, array(
327
			'project' => 'wp-network-admin', 'output' => $output,
328
			'includes' => $this->get_wp_network_admin_files( $dir ),
329
			'excludes' => array(),
330
			'default_output' => 'wordpress-admin-network.pot',
331
		) );
332
333
		if ( ! $result ) {
334
			return false;
335
		}
336
337
		$common_pot = $this->tempnam( 'common.pot' );
338
		if ( ! $common_pot )
339
			return false;
340
341
		$net_admin_pot = realpath( is_null( $output ) ? 'wordpress-network-admin.pot' : $output );
342
		system( "msgcat --more-than=1 --use-first $frontend_pot $admin_pot $net_admin_pot > $common_pot" );
343
		system( "msgcat -u --use-first $net_admin_pot $common_pot -o $net_admin_pot" );
344
		return true;
345
	}
346
347
	private function get_wp_network_admin_files( $dir ) {
348
		$wp_version = $this->wp_version( $dir );
349
350
		// https://core.trac.wordpress.org/ticket/19852
351
		$files = array( 'wp-admin/network/.*', 'wp-admin/network.php' );
352
353
		// https://core.trac.wordpress.org/ticket/34910
354
		if ( version_compare( $wp_version, '4.5-beta', '>=' ) ) {
355
			$files = array_merge( $files, array(
356
				'wp-admin/includes/class-wp-ms.*',
357
				'wp-admin/includes/network.php',
358
			) );
359
		}
360
361
		return $files;
362
	}
363
364
	public function wp_tz( $dir, $output ) {
365
		return $this->wp_generic( $dir, array(
366
			'project' => 'wp-tz', 'output' => $output,
367
			'includes' => array( 'wp-admin/includes/continents-cities\.php' ),
368
			'excludes' => array(),
369
			'default_output' => 'wordpress-continents-cities.pot',
370
		) );
371
	}
372
373
	private function wp_version( $dir ) {
374
		$version_php = $dir.'/wp-includes/version.php';
375
		if ( !is_readable( $version_php ) ) return false;
376
		return preg_match( '/\$wp_version\s*=\s*\'(.*?)\';/', file_get_contents( $version_php ), $matches )? $matches[1] : false;
377
	}
378
379
	public function get_first_lines($filename, $lines = 30) {
380
		$extf = fopen($filename, 'r');
381
		if (!$extf) return false;
382
		$first_lines = '';
383
		foreach(range(1, $lines) as $x) {
384
			$line = fgets($extf);
385
			if (feof($extf)) break;
386
			if (false === $line) {
387
				return false;
388
			}
389
			$first_lines .= $line;
390
		}
391
392
		// PHP will close file handle, but we are good citizens.
393
		fclose( $extf );
394
395
		// Make sure we catch CR-only line endings.
396
		$first_lines = str_replace( "\r", "\n", $first_lines );
397
398
		return $first_lines;
399
	}
400
401
	public function get_addon_header($header, &$source) {
402
		/*
403
		 * A few things this needs to handle:
404
		 * - 'Header: Value\n'
405
		 * - '// Header: Value'
406
		 * - '/* Header: Value * /'
407
		 * - '<?php // Header: Value ?>'
408
		 * - '<?php /* Header: Value * / $foo='bar'; ?>'
409
		 */
410
		if ( preg_match( '/^(?:[ \t]*<\?php)?[ \t\/*#@]*' . preg_quote( $header, '/' ) . ':(.*)$/mi', $source, $matches ) ) {
411
			return $this->_cleanup_header_comment( $matches[1] );
412
		} else {
413
			return false;
414
		}
415
	}
416
417
	/**
418
	 * Removes any trailing closing comment / PHP tags from the header value
419
	 */
420
	private function _cleanup_header_comment( $str ) {
421
		return trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $str ) );
422
	}
423
424
	public function generic($dir, $output) {
425
		$output = is_null($output)? "generic.pot" : $output;
426
		return $this->xgettext('generic', $dir, $output, array());
427
	}
428
429
	private function guess_plugin_slug($dir) {
430
		if ('trunk' == basename($dir)) {
431
			$slug = basename(dirname($dir));
432
		} elseif (in_array(basename(dirname($dir)), array('branches', 'tags'))) {
433
			$slug = basename(dirname(dirname($dir)));
434
		} else {
435
			$slug = basename($dir);
436
		}
437
		return $slug;
438
	}
439
440
	public function wp_plugin( $dir, $output, $slug = null, $args = array() ) {
441
		$defaults = array(
442
			'excludes' => array(),
443
			'includes' => array(),
444
		);
445
		$args = array_merge( $defaults, $args );
446
		$placeholders = array();
447
		// guess plugin slug
448
		if (is_null($slug)) {
449
			$slug = $this->guess_plugin_slug($dir);
450
		}
451
452
		$plugins_dir = @opendir( $dir );
453
		$plugin_files = array();
454
		if ( $plugins_dir ) {
455
			while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
456
				if ( '.' === substr( $file, 0, 1 ) ) {
457
					continue;
458
				}
459
460
				if ( '.php' === substr( $file, -4 ) ) {
461
					$plugin_files[] = $file;
462
				}
463
			}
464
			closedir( $plugins_dir );
465
		}
466
467
		if ( empty( $plugin_files ) ) {
468
			return false;
469
		}
470
471
		$main_file = '';
472
		foreach ( $plugin_files as $plugin_file ) {
473
			if ( ! is_readable( "$dir/$plugin_file" ) ) {
474
				continue;
475
			}
476
477
			$source = $this->get_first_lines( "$dir/$plugin_file", $this->max_header_lines );
478
479
			// Stop when we find a file with a plugin name header in it.
480
			if ( $this->get_addon_header( 'Plugin Name', $source ) != false ) {
0 ignored issues
show
It seems like you are loosely comparing $this->get_addon_header('Plugin Name', $source) of type string|false against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
481
				$main_file = "$dir/$plugin_file";
482
				break;
483
			}
484
		}
485
486
		if ( empty( $main_file ) ) {
487
			return false;
488
		}
489
490
		$placeholders['version'] = $this->get_addon_header('Version', $source);
491
		$placeholders['author'] = $this->get_addon_header('Author', $source);
492
		$placeholders['name'] = $this->get_addon_header('Plugin Name', $source);
493
		$placeholders['slug'] = $slug;
494
495
		$output = is_null($output)? "$slug.pot" : $output;
496
		$res = $this->xgettext( 'wp-plugin', $dir, $output, $placeholders, $args['excludes'], $args['includes'] );
497
		if (!$res) return false;
498
		$potextmeta = new PotExtMeta;
499
		$res = $potextmeta->append($main_file, $output);
500
		/* Adding non-gettexted strings can repeat some phrases */
501
		$output_shell = escapeshellarg($output);
502
		system("msguniq $output_shell -o $output_shell");
503
		return $res;
504
	}
505
506
	public function wp_theme($dir, $output, $slug = null) {
507
		$placeholders = array();
508
		// guess plugin slug
509
		if (is_null($slug)) {
510
			$slug = $this->guess_plugin_slug($dir);
511
		}
512
		$main_file = $dir.'/style.css';
513
		$source = $this->get_first_lines($main_file, $this->max_header_lines);
514
515
		$placeholders['version'] = $this->get_addon_header('Version', $source);
516
		$placeholders['author'] = $this->get_addon_header('Author', $source);
517
		$placeholders['name'] = $this->get_addon_header('Theme Name', $source);
518
		$placeholders['slug'] = $slug;
519
520
		$license = $this->get_addon_header( 'License', $source );
521
		if ( $license )
0 ignored issues
show
Bug Best Practice introduced by
The expression $license of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
522
			$this->meta['wp-theme']['comments'] = "Copyright (C) {year} {author}\nThis file is distributed under the {$license}.";
523
		else
524
			$this->meta['wp-theme']['comments'] = "Copyright (C) {year} {author}\nThis file is distributed under the same license as the {package-name} package.";
525
526
		$output = is_null($output)? "$slug.pot" : $output;
527
		$res = $this->xgettext('wp-theme', $dir, $output, $placeholders);
528
		if (! $res )
529
			return false;
530
		$potextmeta = new PotExtMeta;
531
		$res = $potextmeta->append( $main_file, $output, array( 'Theme Name', 'Theme URI', 'Description', 'Author', 'Author URI' ) );
532
		if ( ! $res )
533
			return false;
534
		// If we're dealing with a pre-3.4 default theme, don't extract page templates before 3.4.
535
		$extract_templates = ! in_array( $slug, array( 'twentyten', 'twentyeleven', 'default', 'classic' ) );
536
		if ( ! $extract_templates ) {
537
			$wp_dir = dirname( dirname( dirname( $dir ) ) );
538
			$extract_templates = file_exists( "$wp_dir/wp-admin/user/about.php" ) || ! file_exists( "$wp_dir/wp-load.php" );
539
		}
540
		if ( $extract_templates ) {
541
			$res = $potextmeta->append( $dir, $output, array( 'Template Name' ) );
542
			if ( ! $res )
543
				return false;
544
			$files = scandir( $dir );
545
			foreach ( $files as $file ) {
546
				if ( '.' == $file[0] || 'CVS' == $file )
547
					continue;
548
				if ( is_dir( $dir . '/' . $file ) ) {
549
					$res = $potextmeta->append( $dir . '/' . $file, $output, array( 'Template Name' ) );
550
					if ( ! $res )
551
						return false;
552
				}
553
			}
554
		}
555
		/* Adding non-gettexted strings can repeat some phrases */
556
		$output_shell = escapeshellarg($output);
557
		system("msguniq $output_shell -o $output_shell");
558
		return $res;
559
	}
560
561
	public function glotpress( $dir, $output ) {
562
		$output = is_null( $output ) ? "glotpress.pot" : $output;
563
		return $this->xgettext( 'glotpress', $dir, $output );
564
	}
565
566
	public function wporg_bb_forums( $dir, $output ) {
567
		$output = is_null( $output ) ? 'wporg.pot' : $output;
568
		return $this->xgettext( 'wporg-bb-forums', $dir, $output, array(), array(
569
			'bb-plugins/elfakismet/.*',
570
			'bb-plugins/support-forum/.*',
571
			'themes/.*',
572
		) );
573
	}
574
575 View Code Duplication
	public function wporg_themes( $dir, $output ) {
576
		$output = is_null( $output ) ? 'wporg-themes.pot' : $output;
577
		return $this->xgettext( 'wporg', $dir, $output, array(), array(), array(
578
			'plugins/theme-directory/.*',
579
			'themes/pub/wporg-themes/.*'
580
		) );
581
	}
582
583 View Code Duplication
	public function wporg_plugins( $dir, $output ) {
584
		$output = is_null( $output ) ? 'wporg-plugins.pot' : $output;
585
		return $this->xgettext( 'wporg', $dir, $output, array(), array(
586
			'plugins/svn-track/i18n-tools/.*'
587
			), array(
588
			'.*\.php',
589
		) );
590
	}
591
592
	public function wporg_forums( $dir, $output ) {
593
		$output = is_null( $output ) ? 'wporg-forums.pot' : $output;
594
		return $this->xgettext( 'wporg', $dir, $output, array(), array(), array(
595
			'.*\.php',
596
		) );
597
	}
598
599
	public function wordcamporg( $dir, $output ) {
600
		$output = is_null( $output ) ? 'wordcamporg.pot' : $output;
601
		return $this->xgettext( 'wordcamporg', $dir, $output, array(), array(), array(
602
			'.*\.php',
603
		) );
604
	}
605
606
	public function rosetta( $dir, $output ) {
607
		$output = is_null( $output )? 'rosetta.pot' : $output;
608
		return $this->xgettext( 'rosetta', $dir, $output, array(), array(
609
			'mu-plugins/rosetta/i18n-tools/.*',
610
			'mu-plugins/rosetta/locales/.*',
611
			), array(
612
			'mu-plugins/(roles|showcase|downloads)/.*\.php',
613
			'mu-plugins/jetpack-settings.php',
614
			'mu-plugins/rosetta.*\.php',
615
			'mu-plugins/rosetta/[^/]+\.php',
616
			'mu-plugins/rosetta/tmpl/.*\.php',
617
			'themes/rosetta/.*\.php',
618
		) );
619
	}
620
}
621
622
// run the CLI only if the file
623
// wasn't included
624
$included_files = get_included_files();
625
if ($included_files[0] == __FILE__) {
626
	$makepot = new MakePOT;
627
	if ((3 == count($argv) || 4 == count($argv)) && in_array($method = str_replace('-', '_', $argv[1]), get_class_methods($makepot))) {
628
		$res = call_user_func(array($makepot, $method), realpath($argv[2]), isset($argv[3])? $argv[3] : null);
629
		if (false === $res) {
630
			fwrite(STDERR, "Couldn't generate POT file!\n");
631
		}
632
	} else {
633
		$usage  = "Usage: php makepot.php PROJECT DIRECTORY [OUTPUT]\n\n";
634
		$usage .= "Generate POT file from the files in DIRECTORY [OUTPUT]\n";
635
		$usage .= "Available projects: ".implode(', ', $makepot->projects)."\n";
636
		fwrite(STDERR, $usage);
637
		exit(1);
638
	}
639
}
640