| 1 |
<?php |
| 2 |
// Exit if accessed directly |
| 3 |
if ( ! defined( 'ABSPATH' ) ) exit; |
| 4 |
|
| 5 |
// $Id: potx.inc,v 1.1.2.17.2.7.2.19.4.1 2009/07/19 12:54:42 goba Exp $ |
| 6 |
|
| 7 |
/** |
| 8 |
* @file |
| 9 |
* Extraction API used by the web and command line interface. |
| 10 |
* |
| 11 |
* This include file implements the default string and file version |
| 12 |
* storage as well as formatting of POT files for web download or |
| 13 |
* file system level creation. The strings, versions and file contents |
| 14 |
* are handled with global variables to reduce the possible memory overhead |
| 15 |
* and API clutter of passing them around. Custom string and version saving |
| 16 |
* functions can be implemented to use the functionality provided here as an |
| 17 |
* API for Drupal code to translatable string conversion. |
| 18 |
* |
| 19 |
* The potx-cli.php script can be used with this include file as |
| 20 |
* a command line interface to string extraction. The potx.module |
| 21 |
* can be used as a web interface for manual extraction. |
| 22 |
* |
| 23 |
* For a module using potx as an extraction API, but providing more |
| 24 |
* sophisticated functionality on top of it, look into the |
| 25 |
* 'Localization server' module: http://drupal.org/project/l10n_server |
| 26 |
*/ |
| 27 |
|
| 28 |
/** |
| 29 |
* Silence status reports. |
| 30 |
*/ |
| 31 |
define( 'WPPB_LE_POTX_STATUS_SILENT', 0 ); |
| 32 |
|
| 33 |
/** |
| 34 |
* Drupal message based status reports. |
| 35 |
*/ |
| 36 |
define( 'WPPB_LE_POTX_STATUS_MESSAGE', 1 ); |
| 37 |
|
| 38 |
/** |
| 39 |
* Command line status reporting. |
| 40 |
* |
| 41 |
* Status goes to standard output, errors to standard error. |
| 42 |
*/ |
| 43 |
define( 'WPPB_LE_POTX_STATUS_CLI', 2 ); |
| 44 |
|
| 45 |
/** |
| 46 |
* Structured array status logging. |
| 47 |
* |
| 48 |
* Useful for coder review status reporting. |
| 49 |
*/ |
| 50 |
define( 'WPPB_LE_POTX_STATUS_STRUCTURED', 3 ); |
| 51 |
|
| 52 |
/** |
| 53 |
* Core parsing mode: |
| 54 |
* - .info files folded into general.pot |
| 55 |
* - separate files generated for modules |
| 56 |
*/ |
| 57 |
define( 'WPPB_LE_POTX_BUILD_CORE', 0 ); |
| 58 |
|
| 59 |
/** |
| 60 |
* Multiple files mode: |
| 61 |
* - .info files folded into their module pot files |
| 62 |
* - separate files generated for modules |
| 63 |
*/ |
| 64 |
define( 'WPPB_LE_POTX_BUILD_MULTIPLE', 1 ); |
| 65 |
|
| 66 |
/** |
| 67 |
* Single file mode: |
| 68 |
* - all files folded into one pot file |
| 69 |
*/ |
| 70 |
define( 'WPPB_LE_POTX_BUILD_SINGLE', 2 ); |
| 71 |
|
| 72 |
/** |
| 73 |
* Save string to both installer and runtime collection. |
| 74 |
*/ |
| 75 |
define( 'WPPB_LE_POTX_STRING_BOTH', 0 ); |
| 76 |
|
| 77 |
/** |
| 78 |
* Save string to installer collection only. |
| 79 |
*/ |
| 80 |
define( 'WPPB_LE_POTX_STRING_INSTALLER', 1 ); |
| 81 |
|
| 82 |
/** |
| 83 |
* Save string to runtime collection only. |
| 84 |
*/ |
| 85 |
define( 'WPPB_LE_POTX_STRING_RUNTIME', 2 ); |
| 86 |
|
| 87 |
/** |
| 88 |
* Parse source files in Drupal 5.x format. |
| 89 |
*/ |
| 90 |
define( 'WPPB_LE_POTX_API_5', 5 ); |
| 91 |
|
| 92 |
/** |
| 93 |
* Parse source files in Drupal 6.x format. |
| 94 |
* |
| 95 |
* Changes since 5.x documented at http://drupal.org/node/114774 |
| 96 |
*/ |
| 97 |
define( 'WPPB_LE_POTX_API_6', 6 ); |
| 98 |
|
| 99 |
/** |
| 100 |
* Parse source files in Drupal 7.x format. |
| 101 |
* |
| 102 |
* Changes since 6.x documented at http://drupal.org/node/224333 |
| 103 |
*/ |
| 104 |
define( 'WPPB_LE_POTX_API_7', 7 ); |
| 105 |
|
| 106 |
/** |
| 107 |
* When no context is used. Makes it easy to look these up. |
| 108 |
*/ |
| 109 |
define( 'WPPB_LE_POTX_CONTEXT_NONE', NULL ); |
| 110 |
|
| 111 |
/** |
| 112 |
* When there was a context identification error. |
| 113 |
*/ |
| 114 |
define( 'WPPB_LE_POTX_CONTEXT_ERROR', FALSE ); |
| 115 |
|
| 116 |
|
| 117 |
/** |
| 118 |
* |
| 119 |
* |
| 120 |
* Main function that outputs the string to the screen |
| 121 |
* |
| 122 |
* @param $str |
| 123 |
* @return string |
| 124 |
* |
| 125 |
* |
| 126 |
*/ |
| 127 |
function _wppb_le_output_str ( $str ) |
| 128 |
{ |
| 129 |
|
| 130 |
$args = func_get_args (); |
| 131 |
|
| 132 |
$tpl = "\$lang['%s'] = '%s';\n"; |
| 133 |
|
| 134 |
$str = sprintf ( $tpl, $args[ 1 ], $args[ 0 ] ); |
| 135 |
echo nl2br ( stripslashes ( htmlentities ( $str ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 136 |
|
| 137 |
return $str; |
| 138 |
} |
| 139 |
|
| 140 |
/** |
| 141 |
* Process a file and put extracted information to the given parameters. |
| 142 |
* |
| 143 |
* @param $file_path |
| 144 |
* Comlete path to file to process. |
| 145 |
* @param $strip_prefix |
| 146 |
* An integer denoting the number of chars to strip from filepath for output. |
| 147 |
* @param $save_callback |
| 148 |
* Callback function to use to save the collected strings. |
| 149 |
* @param $version_callback |
| 150 |
* Callback function to use to save collected version numbers. |
| 151 |
* @param $api_version |
| 152 |
* Drupal API version to work with. |
| 153 |
*/ |
| 154 |
function _wppb_le_potx_process_file ( $file_path, $strip_prefix = 0, $save_callback = '_wppb_le_potx_save_string', $version_callback = '_wppb_le_potx_save_version', $api_version = WPPB_LE_POTX_API_6 ) |
| 155 |
{ |
| 156 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 157 |
|
| 158 |
// Figure out the basename and extension to select extraction method. |
| 159 |
$basename = basename ( $file_path ); |
| 160 |
$name_parts = pathinfo ( $basename ); |
| 161 |
|
| 162 |
// Always grab the CVS version number from the code |
| 163 |
$code = file_get_contents ( $file_path ); |
| 164 |
$file_name = $strip_prefix > 0 ? substr ( $file_path, $strip_prefix ) : $file_path; |
| 165 |
_wppb_le_potx_find_version_number ( $code, $file_name, $version_callback ); |
| 166 |
|
| 167 |
// Extract raw PHP language tokens. |
| 168 |
$raw_tokens = token_get_all ( $code ); |
| 169 |
unset( $code ); |
| 170 |
|
| 171 |
// Remove whitespace and possible HTML (the later in templates for example), |
| 172 |
// count line numbers so we can include them in the output. |
| 173 |
$_wppb_le_potx_tokens = array(); |
| 174 |
$_wppb_le_potx_lookup = array(); |
| 175 |
$token_number = 0; |
| 176 |
$line_number = 1; |
| 177 |
foreach ( $raw_tokens as $token ) { |
| 178 |
if ( ( !is_array ( $token ) ) || ( ( $token[ 0 ] != T_WHITESPACE ) && ( $token[ 0 ] != T_INLINE_HTML ) ) ) { |
| 179 |
if ( is_array ( $token ) ) { |
| 180 |
$token[ ] = $line_number; |
| 181 |
// Fill array for finding token offsets quickly. |
| 182 |
$src_tokens = array( |
| 183 |
'__', 'esc_attr__', 'esc_html__', '_e', 'esc_attr_e', 'esc_html_e', |
| 184 |
'_x', 'esc_attr_x', 'esc_html_x', '_ex', |
| 185 |
'_n', '_nx' |
| 186 |
); |
| 187 |
if ( $token[ 0 ] == T_STRING || ( $token[ 0 ] == T_VARIABLE && in_array ( $token[ 1 ], $src_tokens ) ) ) { |
| 188 |
if ( !isset( $_wppb_le_potx_lookup[ $token[ 1 ] ] ) ) { |
| 189 |
$_wppb_le_potx_lookup[ $token[ 1 ] ] = array(); |
| 190 |
} |
| 191 |
$_wppb_le_potx_lookup[ $token[ 1 ] ][ ] = $token_number; |
| 192 |
} |
| 193 |
} |
| 194 |
$_wppb_le_potx_tokens[ ] = $token; |
| 195 |
$token_number++; |
| 196 |
} |
| 197 |
// Collect line numbers. |
| 198 |
if ( is_array ( $token ) ) { |
| 199 |
$line_number += count ( explode ( "\n", $token[ 1 ] ) ) - 1; |
| 200 |
} else { |
| 201 |
$line_number += count ( explode ( "\n", $token ) ) - 1; |
| 202 |
} |
| 203 |
} |
| 204 |
unset( $raw_tokens ); |
| 205 |
|
| 206 |
// Drupal 7 onwards supports context on t(). |
| 207 |
if ( !empty( $src_tokens ) ) |
| 208 |
foreach ( $src_tokens as $tk ) { |
| 209 |
_wppb_le_potx_find_t_calls_with_context ( $file_name, $save_callback, $tk ); |
| 210 |
} |
| 211 |
|
| 212 |
} |
| 213 |
|
| 214 |
/** |
| 215 |
* Creates complete file strings with _wppb_le_potx_store() |
| 216 |
* |
| 217 |
* @param $string_mode |
| 218 |
* Strings to generate files for: WPPB_LE_POTX_STRING_RUNTIME or WPPB_LE_POTX_STRING_INSTALLER. |
| 219 |
* @param $build_mode |
| 220 |
* Storage mode used: single, multiple or core |
| 221 |
* @param $force_name |
| 222 |
* Forces a given file name to get used, if single mode is on, without extension |
| 223 |
* @param $save_callback |
| 224 |
* Callback used to save strings previously. |
| 225 |
* @param $version_callback |
| 226 |
* Callback used to save versions previously. |
| 227 |
* @param $header_callback |
| 228 |
* Callback to invoke to get the POT header. |
| 229 |
* @param $template_export_langcode |
| 230 |
* Language code if the template should have language dependent content |
| 231 |
* (like plural formulas and language name) included. |
| 232 |
* @param $translation_export_langcode |
| 233 |
* Language code if translations should also be exported. |
| 234 |
* @param $api_version |
| 235 |
* Drupal API version to work with. |
| 236 |
*/ |
| 237 |
function _wppb_le_potx_build_files ( $string_mode = WPPB_LE_POTX_STRING_RUNTIME, $build_mode = WPPB_LE_POTX_BUILD_SINGLE, $force_name = 'general', $save_callback = '_wppb_le_potx_save_string', $version_callback = '_wppb_le_potx_save_version', $header_callback = '_wppb_le_potx_get_header', $template_export_langcode = NULL, $translation_export_langcode = NULL, $api_version = WPPB_LE_POTX_API_6 ) |
| 238 |
{ |
| 239 |
global $_wppb_le_potx_store; |
| 240 |
|
| 241 |
// Get strings and versions by reference. |
| 242 |
$strings = $save_callback( NULL, NULL, NULL, 0, $string_mode ); |
| 243 |
$versions = $version_callback(); |
| 244 |
|
| 245 |
// We might not have any string recorded in this string mode. |
| 246 |
if ( !is_array ( $strings ) ) { |
| 247 |
return; |
| 248 |
} |
| 249 |
|
| 250 |
foreach ( $strings as $string => $string_info ) { |
| 251 |
foreach ( $string_info as $context => $file_info ) { |
| 252 |
// Build a compact list of files this string occured in. |
| 253 |
$occured = $file_list = array(); |
| 254 |
// Look for strings appearing in multiple directories (ie. |
| 255 |
// different subprojects). So we can include them in general.pot. |
| 256 |
$last_location = dirname ( array_shift ( array_keys ( $file_info ) ) ); |
| 257 |
$multiple_locations = FALSE; |
| 258 |
foreach ( $file_info as $file => $lines ) { |
| 259 |
$occured[ ] = "$file:" . join ( ';', $lines ); |
| 260 |
if ( isset( $versions[ $file ] ) ) { |
| 261 |
$file_list[ ] = $versions[ $file ]; |
| 262 |
} |
| 263 |
if ( dirname ( $file ) != $last_location ) { |
| 264 |
$multiple_locations = TRUE; |
| 265 |
} |
| 266 |
$last_location = dirname ( $file ); |
| 267 |
} |
| 268 |
|
| 269 |
// Mark duplicate strings (both translated in the app and in the installer). |
| 270 |
$comment = join ( " ", $occured ); |
| 271 |
if ( strpos ( $comment, '(dup)' ) !== FALSE ) { |
| 272 |
$comment = '(duplicate) ' . str_replace ( '(dup)', '', $comment ); |
| 273 |
} |
| 274 |
$output = "#: $comment\n"; |
| 275 |
|
| 276 |
if ( $build_mode == WPPB_LE_POTX_BUILD_SINGLE ) { |
| 277 |
// File name forcing in single mode. |
| 278 |
$file_name = $force_name; |
| 279 |
} elseif ( strpos ( $comment, '.info' ) ) { |
| 280 |
// Store .info file strings either in general.pot or the module pot file, |
| 281 |
// depending on the mode used. |
| 282 |
$file_name = ( $build_mode == WPPB_LE_POTX_BUILD_CORE ? 'general' : str_replace ( '.info', '.module', $file_name ) ); |
| 283 |
} elseif ( $multiple_locations ) { |
| 284 |
// Else if occured more than once, store in general.pot. |
| 285 |
$file_name = 'general'; |
| 286 |
} else { |
| 287 |
// Fold multiple files in the same folder into one. |
| 288 |
if ( empty( $last_location ) || $last_location == '.' ) { |
| 289 |
$file_name = 'root'; |
| 290 |
} else { |
| 291 |
$file_name = str_replace ( '/', '-', $last_location ); |
| 292 |
} |
| 293 |
} |
| 294 |
|
| 295 |
|
| 296 |
if ( strpos ( $string, "\0" ) !== FALSE ) { |
| 297 |
// Plural strings have a null byte delimited format. |
| 298 |
list( $singular, $plural ) = explode ( "\0", $string ); |
| 299 |
$output .= "msgid \"$singular\"\n"; |
| 300 |
$output .= "msgid_plural \"$plural\"\n"; |
| 301 |
if ( !empty( $context ) ) { |
| 302 |
$output .= "msgctxt \"$context\"\n"; |
| 303 |
} |
| 304 |
if ( isset( $translation_export_langcode ) ) { |
| 305 |
$output .= _wppb_le_potx_translation_export ( $translation_export_langcode, $singular, $plural, $api_version ); |
| 306 |
} else { |
| 307 |
$output .= "msgstr[0] \"\"\n"; |
| 308 |
$output .= "msgstr[1] \"\"\n"; |
| 309 |
} |
| 310 |
} else { |
| 311 |
// Simple strings. |
| 312 |
$output .= "msgid \"$string\"\n"; |
| 313 |
if ( !empty( $context ) ) { |
| 314 |
$output .= "msgctxt \"$context\"\n"; |
| 315 |
} |
| 316 |
if ( isset( $translation_export_langcode ) ) { |
| 317 |
$output .= _wppb_le_potx_translation_export ( $translation_export_langcode, $string, NULL, $api_version ); |
| 318 |
} else { |
| 319 |
$output .= "msgstr \"\"\n"; |
| 320 |
} |
| 321 |
} |
| 322 |
$output .= "\n"; |
| 323 |
|
| 324 |
// Store the generated output in the given file storage. |
| 325 |
if ( !isset( $_wppb_le_potx_store[ $file_name ] ) ) { |
| 326 |
$_wppb_le_potx_store[ $file_name ] = array( |
| 327 |
'header' => $header_callback( $file_name, $template_export_langcode, $api_version ), |
| 328 |
'sources' => $file_list, |
| 329 |
'strings' => $output, |
| 330 |
'count' => 1, |
| 331 |
); |
| 332 |
} else { |
| 333 |
// Maintain a list of unique file names. |
| 334 |
$_wppb_le_potx_store[ $file_name ][ 'sources' ] = array_unique ( array_merge ( $_wppb_le_potx_store[ $file_name ][ 'sources' ], $file_list ) ); |
| 335 |
$_wppb_le_potx_store[ $file_name ][ 'strings' ] .= $output; |
| 336 |
$_wppb_le_potx_store[ $file_name ][ 'count' ] += 1; |
| 337 |
} |
| 338 |
} |
| 339 |
} |
| 340 |
} |
| 341 |
|
| 342 |
/** |
| 343 |
* Export translations with a specific language. |
| 344 |
* |
| 345 |
* @param $translation_export_langcode |
| 346 |
* Language code if translations should also be exported. |
| 347 |
* @param $string |
| 348 |
* String or singular version if $plural was provided. |
| 349 |
* @param $plural |
| 350 |
* Plural version of singular string. |
| 351 |
* @param $api_version |
| 352 |
* Drupal API version to work with. |
| 353 |
*/ |
| 354 |
function _wppb_le_potx_translation_export ( $translation_export_langcode, $string, $plural = NULL, $api_version = WPPB_LE_POTX_API_6 ) |
| 355 |
{ |
| 356 |
include_once 'includes/locale.inc'; |
| 357 |
|
| 358 |
// Stip out slash escapes. |
| 359 |
$string = stripcslashes ( $string ); |
| 360 |
|
| 361 |
// Column and table name changed between versions. |
| 362 |
$language_column = $api_version > WPPB_LE_POTX_API_5 ? 'language' : 'locale'; |
| 363 |
$language_table = $api_version > WPPB_LE_POTX_API_5 ? 'languages' : 'locales_meta'; |
| 364 |
|
| 365 |
if ( !isset( $plural ) ) { |
| 366 |
// Single string to look translation up for. |
| 367 |
if ( $translation = db_result ( db_query ( "SELECT t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON t.lid = s.lid WHERE s.source = '%s' AND t.{$language_column} = '%s'", $string, $translation_export_langcode ) ) ) { |
| 368 |
return 'msgstr ' . _locale_export_string ( $translation ); |
| 369 |
} |
| 370 |
return "msgstr \"\"\n"; |
| 371 |
} else { |
| 372 |
// String with plural variants. Fill up source string array first. |
| 373 |
$plural = stripcslashes ( $plural ); |
| 374 |
$strings = array(); |
| 375 |
$number_of_plurals = db_result ( db_query ( 'SELECT plurals FROM {' . $language_table . "} WHERE {$language_column} = '%s'", $translation_export_langcode ) ); |
| 376 |
$plural_index = 0; |
| 377 |
while ( $plural_index < $number_of_plurals ) { |
| 378 |
if ( $plural_index == 0 ) { |
| 379 |
// Add the singular version. |
| 380 |
$strings[ ] = $string; |
| 381 |
} elseif ( $plural_index == 1 ) { |
| 382 |
// Only add plural version if required. |
| 383 |
$strings[ ] = $plural; |
| 384 |
} else { |
| 385 |
// More plural versions only if required, with the lookup source |
| 386 |
// string modified as imported into the database. |
| 387 |
$strings[ ] = str_replace ( '@count', '@count[' . $plural_index . ']', $plural ); |
| 388 |
} |
| 389 |
$plural_index++; |
| 390 |
} |
| 391 |
|
| 392 |
$output = ''; |
| 393 |
if ( count ( $strings ) ) { |
| 394 |
// Source string array was done, so export translations. |
| 395 |
foreach ( $strings as $index => $string ) { |
| 396 |
if ( $translation = db_result ( db_query ( "SELECT t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON t.lid = s.lid WHERE s.source = '%s' AND t.{$language_column} = '%s'", $string, $translation_export_langcode ) ) ) { |
| 397 |
$output .= 'msgstr[' . $index . '] ' . _locale_export_string ( _locale_export_remove_plural ( $translation ) ); |
| 398 |
} else { |
| 399 |
$output .= "msgstr[" . $index . "] \"\"\n"; |
| 400 |
} |
| 401 |
} |
| 402 |
} else { |
| 403 |
// No plural information was recorded, so export empty placeholders. |
| 404 |
$output .= "msgstr[0] \"\"\n"; |
| 405 |
$output .= "msgstr[1] \"\"\n"; |
| 406 |
} |
| 407 |
return $output; |
| 408 |
} |
| 409 |
} |
| 410 |
|
| 411 |
/** |
| 412 |
* Returns a header generated for a given file |
| 413 |
* |
| 414 |
* @param $file |
| 415 |
* Name of POT file to generate header for |
| 416 |
* @param $template_export_langcode |
| 417 |
* Language code if the template should have language dependent content |
| 418 |
* (like plural formulas and language name) included. |
| 419 |
* @param $api_version |
| 420 |
* Drupal API version to work with. |
| 421 |
*/ |
| 422 |
function _wppb_le_potx_get_header ( $file, $template_export_langcode = NULL, $api_version = WPPB_LE_POTX_API_6 ) |
| 423 |
{ |
| 424 |
// We only have language to use if we should export with that langcode. |
| 425 |
$language = NULL; |
| 426 |
if ( isset( $template_export_langcode ) ) { |
| 427 |
$language = db_fetch_object ( db_query ( $api_version > WPPB_LE_POTX_API_5 ? "SELECT language, name, plurals, formula FROM {languages} WHERE language = '%s'" : "SELECT locale, name, plurals, formula FROM {locales_meta} WHERE locale = '%s'", $template_export_langcode ) ); |
| 428 |
} |
| 429 |
|
| 430 |
$output = '# $' . 'Id' . '$' . "\n"; |
| 431 |
$output .= "#\n"; |
| 432 |
$output .= '# ' . ( isset( $language ) ? $language->name : 'LANGUAGE' ) . ' translation of Drupal (' . $file . ")\n"; |
| 433 |
$output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n"; |
| 434 |
$output .= "# --VERSIONS--\n"; |
| 435 |
$output .= "#\n"; |
| 436 |
$output .= "#, fuzzy\n"; |
| 437 |
$output .= "msgid \"\"\n"; |
| 438 |
$output .= "msgstr \"\"\n"; |
| 439 |
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; |
| 440 |
$output .= '"POT-Creation-Date: ' . date ( "Y-m-d H:iO" ) . "\\n\"\n"; |
| 441 |
$output .= '"PO-Revision-Date: ' . ( isset( $language ) ? date ( "Y-m-d H:iO" ) : 'YYYY-mm-DD HH:MM+ZZZZ' ) . "\\n\"\n"; |
| 442 |
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; |
| 443 |
$output .= "\"Language-Team: " . ( isset( $language ) ? $language->name : 'LANGUAGE' ) . " <EMAIL@ADDRESS>\\n\"\n"; |
| 444 |
$output .= "\"MIME-Version: 1.0\\n\"\n"; |
| 445 |
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; |
| 446 |
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; |
| 447 |
if ( isset( $language->formula ) && isset( $language->plurals ) ) { |
| 448 |
$output .= "\"Plural-Forms: nplurals=" . $language->plurals . "; plural=" . strtr ( $language->formula, array( '$' => '' ) ) . ";\\n\"\n\n"; |
| 449 |
} else { |
| 450 |
$output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n"; |
| 451 |
} |
| 452 |
return $output; |
| 453 |
} |
| 454 |
|
| 455 |
/** |
| 456 |
* Write out generated files to the current folder. |
| 457 |
* |
| 458 |
* @param $http_filename |
| 459 |
* File name for content-disposition header in case of usage |
| 460 |
* over HTTP. If not given, files are written to the local filesystem. |
| 461 |
* @param $content_disposition |
| 462 |
* See RFC2183. 'inline' or 'attachment', with a default of |
| 463 |
* 'inline'. Only used if $http_filename is set. |
| 464 |
* @todo |
| 465 |
* Look into whether multiple files can be output via HTTP. |
| 466 |
*/ |
| 467 |
function _wppb_le_potx_write_files ( $http_filename = NULL, $content_disposition = 'inline' ) |
| 468 |
{ |
| 469 |
global $_wppb_le_potx_store; |
| 470 |
|
| 471 |
// Generate file lists and output files. |
| 472 |
if ( is_array ( $_wppb_le_potx_store ) ) { |
| 473 |
foreach ( $_wppb_le_potx_store as $file => $contents ) { |
| 474 |
// Build replacement for file listing. |
| 475 |
if ( count ( $contents[ 'sources' ] ) > 1 ) { |
| 476 |
$filelist = "Generated from files:\n# " . join ( "\n# ", $contents[ 'sources' ] ); |
| 477 |
} elseif ( count ( $contents[ 'sources' ] ) == 1 ) { |
| 478 |
$filelist = "Generated from file: " . join ( '', $contents[ 'sources' ] ); |
| 479 |
} else { |
| 480 |
$filelist = 'No version information was available in the source files.'; |
| 481 |
} |
| 482 |
$output = str_replace ( '--VERSIONS--', $filelist, $contents[ 'header' ] . $contents[ 'strings' ] ); |
| 483 |
|
| 484 |
if ( $http_filename ) { |
| 485 |
// HTTP output. |
| 486 |
header ( 'Content-Type: text/plain; charset=utf-8' ); |
| 487 |
header ( 'Content-Transfer-Encoding: 8bit' ); |
| 488 |
header ( "Content-Disposition: $content_disposition; filename=$http_filename" ); |
| 489 |
print $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 490 |
return; |
| 491 |
} else { |
| 492 |
// Local file output, flatten directory structure. |
| 493 |
$file = str_replace ( '.', '-', preg_replace ( '![/]?([a-zA-Z_0-9]*/)*!', '', $file ) ) . '.pot'; |
| 494 |
$fp = fopen ( $file, 'w' ); |
| 495 |
fwrite ( $fp, $output ); |
| 496 |
fclose ( $fp ); |
| 497 |
} |
| 498 |
} |
| 499 |
} |
| 500 |
} |
| 501 |
|
| 502 |
/** |
| 503 |
* Escape quotes in a strings depending on the surrounding |
| 504 |
* quote type used. |
| 505 |
* |
| 506 |
* @param $str |
| 507 |
* The strings to escape |
| 508 |
*/ |
| 509 |
function _wppb_le_potx_format_quoted_string ( $str ) |
| 510 |
{ |
| 511 |
$quo = substr ( $str, 0, 1 ); |
| 512 |
$str = substr ( $str, 1, -1 ); |
| 513 |
if ( $quo == '"' ) { |
| 514 |
$str = stripcslashes ( $str ); |
| 515 |
} else { |
| 516 |
$str = strtr ( $str, array( "\\'" => "'", "\\\\" => "\\" ) ); |
| 517 |
} |
| 518 |
|
| 519 |
return addcslashes ( $str, "\0..\37\\\"" ); |
| 520 |
} |
| 521 |
|
| 522 |
/** |
| 523 |
* Output a marker error with an extract of where the error was found. |
| 524 |
* |
| 525 |
* @param $file |
| 526 |
* Name of file |
| 527 |
* @param $line |
| 528 |
* Line number of error |
| 529 |
* @param $marker |
| 530 |
* Function name with which the error was identified |
| 531 |
* @param $ti |
| 532 |
* Index on the token array |
| 533 |
* @param $error |
| 534 |
* Helpful error message for users. |
| 535 |
* @param $docs_url |
| 536 |
* Documentation reference. |
| 537 |
*/ |
| 538 |
function _wppb_le_potx_marker_error ( $file, $line, $marker, $ti, $error, $docs_url = NULL ) |
| 539 |
{ |
| 540 |
global $_wppb_le_potx_tokens; |
| 541 |
|
| 542 |
$tokens = ''; |
| 543 |
$ti += 2; |
| 544 |
$tc = count ( $_wppb_le_potx_tokens ); |
| 545 |
$par = 1; |
| 546 |
while ( ( ( $tc - $ti ) > 0 ) && $par ) { |
| 547 |
if ( is_array ( $_wppb_le_potx_tokens[ $ti ] ) ) { |
| 548 |
$tokens .= $_wppb_le_potx_tokens[ $ti ][ 1 ]; |
| 549 |
} else { |
| 550 |
$tokens .= $_wppb_le_potx_tokens[ $ti ]; |
| 551 |
if ( $_wppb_le_potx_tokens[ $ti ] == "(" ) { |
| 552 |
$par++; |
| 553 |
} else if ( $_wppb_le_potx_tokens[ $ti ] == ")" ) { |
| 554 |
$par--; |
| 555 |
} |
| 556 |
} |
| 557 |
$ti++; |
| 558 |
} |
| 559 |
_wppb_le_potx_status ( 'error', $error, $file, $line, $marker . '(' . $tokens, $docs_url ); |
| 560 |
} |
| 561 |
|
| 562 |
/** |
| 563 |
* Status notification function. |
| 564 |
* |
| 565 |
* @param $op |
| 566 |
* Operation to perform or type of message text. |
| 567 |
* - set: sets the reporting mode to $value |
| 568 |
* use one of the POTX_STATUS_* constants as $value |
| 569 |
* - get: returns the list of error messages recorded |
| 570 |
* if $value is true, it also clears the internal message cache |
| 571 |
* - error: sends an error message in $value with optional $file and $line |
| 572 |
* - status: sends a status message in $value |
| 573 |
* @param $value |
| 574 |
* Value depending on $op. |
| 575 |
* @param $file |
| 576 |
* Name of file the error message is related to. |
| 577 |
* @param $line |
| 578 |
* Number of line the error message is related to. |
| 579 |
* @param $excerpt |
| 580 |
* Excerpt of the code in question, if available. |
| 581 |
* @param $docs_url |
| 582 |
* URL to the guidelines to follow to fix the problem. |
| 583 |
*/ |
| 584 |
function _wppb_le_potx_status ( $op, $value = NULL, $file = NULL, $line = NULL, $excerpt = NULL, $docs_url = NULL ) |
| 585 |
{ |
| 586 |
static $mode = WPPB_LE_POTX_STATUS_CLI; |
| 587 |
static $messages = array(); |
| 588 |
|
| 589 |
switch ( $op ) { |
| 590 |
case 'set': |
| 591 |
// Setting the reporting mode. |
| 592 |
$mode = $value; |
| 593 |
return; |
| 594 |
|
| 595 |
case 'get': |
| 596 |
// Getting the errors. Optionally deleting the messages. |
| 597 |
$errors = $messages; |
| 598 |
if ( !empty( $value ) ) { |
| 599 |
$messages = array(); |
| 600 |
} |
| 601 |
return $errors; |
| 602 |
|
| 603 |
case 'error': |
| 604 |
case 'status': |
| 605 |
|
| 606 |
// Location information is required in 3 of the four possible reporting |
| 607 |
// modes as part of the error message. The structured mode needs the |
| 608 |
// file, line and excerpt info separately, not in the text. |
| 609 |
$location_info = ''; |
| 610 |
if ( ( $mode != WPPB_LE_POTX_STATUS_STRUCTURED ) && isset( $file ) ) { |
| 611 |
if ( isset( $line ) ) { |
| 612 |
if ( isset( $excerpt ) ) { |
| 613 |
$location_info = t ( 'At %excerpt in %file on line %line.', array( '%excerpt' => $excerpt, '%file' => $file, '%line' => $line ) ); |
| 614 |
} else { |
| 615 |
$location_info = t ( 'In %file on line %line.', array( '%file' => $file, '%line' => $line ) ); |
| 616 |
} |
| 617 |
} else { |
| 618 |
if ( isset( $excerpt ) ) { |
| 619 |
$location_info = t ( 'At %excerpt in %file.', array( '%excerpt' => $excerpt, '%file' => $file ) ); |
| 620 |
} else { |
| 621 |
$location_info = t ( 'In %file.', array( '%file' => $file ) ); |
| 622 |
} |
| 623 |
} |
| 624 |
} |
| 625 |
|
| 626 |
// Documentation helpers are provided as readable text in most modes. |
| 627 |
$read_more = ''; |
| 628 |
if ( ( $mode != WPPB_LE_POTX_STATUS_STRUCTURED ) && isset( $docs_url ) ) { |
| 629 |
$read_more = ( $mode == WPPB_LE_POTX_STATUS_CLI ) ? t ( 'Read more at @url', array( '@url' => $docs_url ) ) : t ( 'Read more at <a href="@url">@url</a>', array( '@url' => $docs_url ) ); |
| 630 |
} |
| 631 |
|
| 632 |
// Error message or progress text to display. |
| 633 |
switch ( $mode ) { |
| 634 |
case WPPB_LE_POTX_STATUS_MESSAGE: |
| 635 |
drupal_set_message ( join ( ' ', array( $value, $location_info, $read_more ) ), $op ); |
| 636 |
break; |
| 637 |
case WPPB_LE_POTX_STATUS_CLI: |
| 638 |
if ( defined ( 'STDERR' ) && defined ( 'STDOUT' ) ) { |
| 639 |
fwrite ( $op == 'error' ? STDERR : STDOUT, join ( "\n", array( $value, $location_info, $read_more ) ) . "\n\n" ); |
| 640 |
} |
| 641 |
break; |
| 642 |
case WPPB_LE_POTX_STATUS_SILENT: |
| 643 |
if ( $op == 'error' ) { |
| 644 |
$messages[ ] = join ( ' ', array( $value, $location_info, $read_more ) ); |
| 645 |
} |
| 646 |
break; |
| 647 |
case WPPB_LE_POTX_STATUS_STRUCTURED: |
| 648 |
if ( $op == 'error' ) { |
| 649 |
$messages[ ] = array( $value, $file, $line, $excerpt, $docs_url ); |
| 650 |
} |
| 651 |
break; |
| 652 |
} |
| 653 |
return; |
| 654 |
} |
| 655 |
} |
| 656 |
|
| 657 |
/** |
| 658 |
* Detect all occurances of t()-like calls. |
| 659 |
* |
| 660 |
* These sequences are searched for: |
| 661 |
* T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ")" |
| 662 |
* T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + "," |
| 663 |
* |
| 664 |
* @param $file |
| 665 |
* Name of file parsed. |
| 666 |
* @param $save_callback |
| 667 |
* Callback function used to save strings. |
| 668 |
* @param function_name |
| 669 |
* The name of the function to look for (could be 't', '$t', 'st' |
| 670 |
* or any other t-like function). |
| 671 |
* @param $string_mode |
| 672 |
* String mode to use: WPPB_LE_POTX_STRING_INSTALLER, WPPB_LE_POTX_STRING_RUNTIME or |
| 673 |
* WPPB_LE_POTX_STRING_BOTH. |
| 674 |
*/ |
| 675 |
function _wppb_le_potx_find_t_calls ( $file, $save_callback, $function_name = 't', $string_mode = WPPB_LE_POTX_STRING_RUNTIME ) |
| 676 |
{ |
| 677 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 678 |
|
| 679 |
// Lookup tokens by function name. |
| 680 |
if ( isset( $_wppb_le_potx_lookup[ $function_name ] ) ) { |
| 681 |
foreach ( $_wppb_le_potx_lookup[ $function_name ] as $ti ) { |
| 682 |
list( $ctok, $par, $mid, $rig ) = array( $_wppb_le_potx_tokens[ $ti ], $_wppb_le_potx_tokens[ $ti + 1 ], $_wppb_le_potx_tokens[ $ti + 2 ], $_wppb_le_potx_tokens[ $ti + 3 ] ); |
| 683 |
list( $type, $string, $line ) = $ctok; |
| 684 |
if ( $par == "(" ) { |
| 685 |
if ( in_array ( $rig, array( ")", "," ) ) |
| 686 |
&& ( is_array ( $mid ) && ( $mid[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) |
| 687 |
) { |
| 688 |
// This function is only used for context-less call types. |
| 689 |
$save_callback( _wppb_le_potx_format_quoted_string ( $mid[ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, $line, $string_mode ); |
| 690 |
} else { |
| 691 |
// $function_name() found, but inside is something which is not a string literal. |
| 692 |
_wppb_le_potx_marker_error ( $file, $line, $function_name, $ti, t ( 'The first parameter to @function() should be a literal string. There should be no variables, concatenation, constants or other non-literal strings there.', array( '@function' => $function_name ) ), 'http://drupal.org/node/322732' ); |
| 693 |
} |
| 694 |
} |
| 695 |
} |
| 696 |
} |
| 697 |
} |
| 698 |
|
| 699 |
/** |
| 700 |
* Detect all occurances of t()-like calls from Drupal 7 (with context). |
| 701 |
* |
| 702 |
* These sequences are searched for: |
| 703 |
* T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ")" |
| 704 |
* T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + "," |
| 705 |
* and then an optional value for the replacements and an optional array |
| 706 |
* for the options with an optional context key. |
| 707 |
* |
| 708 |
* @param $file |
| 709 |
* Name of file parsed. |
| 710 |
* @param $save_callback |
| 711 |
* Callback function used to save strings. |
| 712 |
* @param function_name |
| 713 |
* The name of the function to look for (could be 't', '$t', 'st' |
| 714 |
* or any other t-like function). Drupal 7 only supports context on t(). |
| 715 |
* @param $string_mode |
| 716 |
* String mode to use: WPPB_LE_POTX_STRING_INSTALLER, WPPB_LE_POTX_STRING_RUNTIME or |
| 717 |
* WPPB_LE_POTX_STRING_BOTH. |
| 718 |
*/ |
| 719 |
function _wppb_le_potx_find_t_calls_with_context ( $file, $save_callback, $function_name = '_e', $string_mode = WPPB_LE_POTX_STRING_RUNTIME ) |
| 720 |
{ |
| 721 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 722 |
|
| 723 |
// Lookup tokens by function name. |
| 724 |
if ( isset( $_wppb_le_potx_lookup[ $function_name ] ) ) { |
| 725 |
foreach ( $_wppb_le_potx_lookup[ $function_name ] as $ti ) { |
| 726 |
|
| 727 |
list( $ctok, $par, $mid, $rig ) = array( $_wppb_le_potx_tokens[ $ti ], $_wppb_le_potx_tokens[ $ti + 1 ], $_wppb_le_potx_tokens[ $ti + 2 ], $_wppb_le_potx_tokens[ $ti + 3 ] ); |
| 728 |
list( $type, $string, $line ) = $ctok; |
| 729 |
|
| 730 |
$slug = $_wppb_le_potx_tokens[ $ti + 4 ]; |
| 731 |
|
| 732 |
if ( $par == "(" ) { |
| 733 |
if ( in_array ( $rig, array( ")", "," ) ) |
| 734 |
&& ( is_array ( $mid ) && ( $mid[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) |
| 735 |
) { |
| 736 |
// By default, there is no context. |
| 737 |
$domain = WPPB_LE_POTX_CONTEXT_NONE; |
| 738 |
if ( $rig == ',' ) { |
| 739 |
// If there was a comma after the string, we need to look forward |
| 740 |
// to try and find the context. |
| 741 |
/*$context = _wppb_le_potx_find_context($ti, $ti + 4, $file, $function_name);*/ |
| 742 |
|
| 743 |
if ( $function_name == '_x' || $function_name == '_ex' ) { |
| 744 |
$domain_offset = 6; |
| 745 |
$context_offset = 4; |
| 746 |
$text = $mid[ 1 ]; |
| 747 |
} elseif ( $function_name == '_n' ) { |
| 748 |
$domain_offset = 10; |
| 749 |
$context_offset = false; |
| 750 |
$text_plural = $_wppb_le_potx_tokens[ $ti + 4 ][ 1 ]; |
| 751 |
} elseif ( $function_name == '_nx' ) { |
| 752 |
$domain_offset = 10; |
| 753 |
$context_offset = 8; |
| 754 |
$text_plural = $_wppb_le_potx_tokens[ $ti + 4 ][ 1 ]; |
| 755 |
} else { |
| 756 |
$domain_offset = 4; |
| 757 |
$context_offset = false; |
| 758 |
$text = $mid[ 1 ]; |
| 759 |
} |
| 760 |
|
| 761 |
if ( !isset( $_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ] ) ) return false; |
| 762 |
|
| 763 |
if ( !preg_match ( '#^(\'|")(.+)#', $_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ] ) ) { |
| 764 |
$constant_val = @constant ( $_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ] ); |
| 765 |
if ( !is_null ( $constant_val ) ) { |
| 766 |
$domain = $constant_val; |
| 767 |
} else { |
| 768 |
if ( function_exists ( $_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ] ) ) { |
| 769 |
$domain = @$_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ](); |
| 770 |
if ( empty( $domain ) ) { |
| 771 |
return false; |
| 772 |
} |
| 773 |
} else { |
| 774 |
return false; |
| 775 |
} |
| 776 |
|
| 777 |
} |
| 778 |
} else { |
| 779 |
$domain = trim ( $_wppb_le_potx_tokens[ $ti + $domain_offset ][ 1 ], "\"' " ); |
| 780 |
} |
| 781 |
|
| 782 |
// exception for gettext calls with contexts |
| 783 |
if ( false !== $context_offset ) { |
| 784 |
if ( !preg_match ( '#^(\'|")(.+)#', $_wppb_le_potx_tokens[ $ti + $context_offset ][ 1 ] ) ) { |
| 785 |
$constant_val = @constant ( $_wppb_le_potx_tokens[ $ti + $context_offset ][ 1 ] ); |
| 786 |
if ( !is_null ( $constant_val ) ) { |
| 787 |
$context = $constant_val; |
| 788 |
} else { |
| 789 |
if ( function_exists ( $_wppb_le_potx_tokens[ $ti + $context_offset ][ 1 ] ) ) { |
| 790 |
$context = @$_wppb_le_potx_tokens[ $ti + $context_offset ][ 1 ](); |
| 791 |
if ( empty( $context ) ) { |
| 792 |
return false; |
| 793 |
} |
| 794 |
} else { |
| 795 |
return false; |
| 796 |
} |
| 797 |
} |
| 798 |
} else { |
| 799 |
$context = trim ( $_wppb_le_potx_tokens[ $ti + $context_offset ][ 1 ], "\"' " ); |
| 800 |
} |
| 801 |
|
| 802 |
} else { |
| 803 |
$context = false; |
| 804 |
} |
| 805 |
|
| 806 |
|
| 807 |
} |
| 808 |
if ( $domain !== WPPB_LE_POTX_CONTEXT_ERROR ) { |
| 809 |
// Only save if there was no error in context parsing. |
| 810 |
$save_callback( _wppb_le_potx_format_quoted_string ( $mid[ 1 ] ), $domain, @strval ( $context ), $file, $line, $string_mode ); |
| 811 |
if ( isset( $text_plural ) ) { |
| 812 |
$save_callback( _wppb_le_potx_format_quoted_string ( $text_plural ), $domain, $context, $file, $line, $string_mode ); |
| 813 |
} |
| 814 |
} |
| 815 |
} else { |
| 816 |
// $function_name() found, but inside is something which is not a string literal. |
| 817 |
_wppb_le_potx_marker_error ( $file, $line, $function_name, $ti, t ( 'The first parameter to @function() should be a literal string. There should be no variables, concatenation, constants or other non-literal strings there.', array( '@function' => $function_name ) ), 'http://drupal.org/node/322732' ); |
| 818 |
} |
| 819 |
} |
| 820 |
} |
| 821 |
} |
| 822 |
} |
| 823 |
|
| 824 |
/** |
| 825 |
* Detect all occurances of watchdog() calls. Only from Drupal 6. |
| 826 |
* |
| 827 |
* These sequences are searched for: |
| 828 |
* watchdog + "(" + T_CONSTANT_ENCAPSED_STRING + "," + |
| 829 |
* T_CONSTANT_ENCAPSED_STRING + something |
| 830 |
* |
| 831 |
* @param $file |
| 832 |
* Name of file parsed. |
| 833 |
* @param $save_callback |
| 834 |
* Callback function used to save strings. |
| 835 |
*/ |
| 836 |
function _wppb_le_potx_find_watchdog_calls ( $file, $save_callback ) |
| 837 |
{ |
| 838 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 839 |
|
| 840 |
// Lookup tokens by function name. |
| 841 |
if ( isset( $_wppb_le_potx_lookup[ 'watchdog' ] ) ) { |
| 842 |
foreach ( $_wppb_le_potx_lookup[ 'watchdog' ] as $ti ) { |
| 843 |
list( $ctok, $par, $mtype, $comma, $message, $rig ) = array( $_wppb_le_potx_tokens[ $ti ], $_wppb_le_potx_tokens[ $ti + 1 ], $_wppb_le_potx_tokens[ $ti + 2 ], $_wppb_le_potx_tokens[ $ti + 3 ], $_wppb_le_potx_tokens[ $ti + 4 ], $_wppb_le_potx_tokens[ $ti + 5 ] ); |
| 844 |
list( $type, $string, $line ) = $ctok; |
| 845 |
if ( $par == '(' ) { |
| 846 |
// Both type and message should be a string literal. |
| 847 |
if ( in_array ( $rig, array( ')', ',' ) ) && $comma == ',' |
| 848 |
&& ( is_array ( $mtype ) && ( $mtype[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) |
| 849 |
&& ( is_array ( $message ) && ( $message[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) |
| 850 |
) { |
| 851 |
// Context is not supported on watchdog(). |
| 852 |
$save_callback( _wppb_le_potx_format_quoted_string ( $mtype[ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, $line ); |
| 853 |
$save_callback( _wppb_le_potx_format_quoted_string ( $message[ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, $line ); |
| 854 |
} else { |
| 855 |
// watchdog() found, but inside is something which is not a string literal. |
| 856 |
_wppb_le_potx_marker_error ( $file, $line, 'watchdog', $ti, t ( 'The first two watchdog() parameters should be literal strings. There should be no variables, concatenation, constants or even a t() call there.' ), 'http://drupal.org/node/323101' ); |
| 857 |
} |
| 858 |
} |
| 859 |
} |
| 860 |
} |
| 861 |
} |
| 862 |
|
| 863 |
/** |
| 864 |
* Detect all occurances of format_plural calls. |
| 865 |
* |
| 866 |
* These sequences are searched for: |
| 867 |
* T_STRING("format_plural") + "(" + ..anything (might be more tokens).. + |
| 868 |
* "," + T_CONSTANT_ENCAPSED_STRING + |
| 869 |
* "," + T_CONSTANT_ENCAPSED_STRING + parenthesis (or comma allowed from |
| 870 |
* Drupal 6) |
| 871 |
* |
| 872 |
* @param $file |
| 873 |
* Name of file parsed. |
| 874 |
* @param $save_callback |
| 875 |
* Callback function used to save strings. |
| 876 |
* @param $api_version |
| 877 |
* Drupal API version to work with. |
| 878 |
*/ |
| 879 |
function _wppb_le_potx_find_format_plural_calls ( $file, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 880 |
{ |
| 881 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 882 |
|
| 883 |
if ( isset( $_wppb_le_potx_lookup[ 'format_plural' ] ) ) { |
| 884 |
foreach ( $_wppb_le_potx_lookup[ 'format_plural' ] as $ti ) { |
| 885 |
list( $ctok, $par1 ) = array( $_wppb_le_potx_tokens[ $ti ], $_wppb_le_potx_tokens[ $ti + 1 ] ); |
| 886 |
list( $type, $string, $line ) = $ctok; |
| 887 |
if ( $par1 == "(" ) { |
| 888 |
// Eat up everything that is used as the first parameter |
| 889 |
$tn = $ti + 2; |
| 890 |
$depth = 0; |
| 891 |
while ( !( $_wppb_le_potx_tokens[ $tn ] == "," && $depth == 0 ) ) { |
| 892 |
if ( $_wppb_le_potx_tokens[ $tn ] == "(" ) { |
| 893 |
$depth++; |
| 894 |
} elseif ( $_wppb_le_potx_tokens[ $tn ] == ")" ) { |
| 895 |
$depth--; |
| 896 |
} |
| 897 |
$tn++; |
| 898 |
} |
| 899 |
// Get further parameters |
| 900 |
list( $comma1, $singular, $comma2, $plural, $par2 ) = array( $_wppb_le_potx_tokens[ $tn ], $_wppb_le_potx_tokens[ $tn + 1 ], $_wppb_le_potx_tokens[ $tn + 2 ], $_wppb_le_potx_tokens[ $tn + 3 ], $_wppb_le_potx_tokens[ $tn + 4 ] ); |
| 901 |
if ( ( $comma2 == ',' ) && ( $par2 == ')' || ( $par2 == ',' && $api_version > WPPB_LE_POTX_API_5 ) ) && |
| 902 |
( is_array ( $singular ) && ( $singular[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) && |
| 903 |
( is_array ( $plural ) && ( $plural[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) ) |
| 904 |
) { |
| 905 |
// By default, there is no context. |
| 906 |
$context = WPPB_LE_POTX_CONTEXT_NONE; |
| 907 |
if ( $par2 == ',' && $api_version > WPPB_LE_POTX_API_6 ) { |
| 908 |
// If there was a comma after the plural, we need to look forward |
| 909 |
// to try and find the context. |
| 910 |
$context = _wppb_le_potx_find_context ( $ti, $tn + 5, $file, 'format_plural' ); |
| 911 |
} |
| 912 |
if ( $context !== WPPB_LE_POTX_CONTEXT_ERROR ) { |
| 913 |
// Only save if there was no error in context parsing. |
| 914 |
$save_callback( |
| 915 |
_wppb_le_potx_format_quoted_string ( $singular[ 1 ] ) . "\0" . _wppb_le_potx_format_quoted_string ( $plural[ 1 ] ), |
| 916 |
$context, |
| 917 |
$file, |
| 918 |
$line |
| 919 |
); |
| 920 |
} |
| 921 |
} else { |
| 922 |
// format_plural() found, but the parameters are not correct. |
| 923 |
_wppb_le_potx_marker_error ( $file, $line, "format_plural", $ti, t ( 'In format_plural(), the singular and plural strings should be literal strings. There should be no variables, concatenation, constants or even a t() call there.' ), 'http://drupal.org/node/323072' ); |
| 924 |
} |
| 925 |
} |
| 926 |
} |
| 927 |
} |
| 928 |
} |
| 929 |
|
| 930 |
/** |
| 931 |
* Detect permission names from the hook_perm() implementations. |
| 932 |
* Note that this will get confused with a similar pattern in a comment, |
| 933 |
* and with dynamic permissions, which need to be accounted for. |
| 934 |
* |
| 935 |
* @param $file |
| 936 |
* Full path name of file parsed. |
| 937 |
* @param $filebase |
| 938 |
* Filenaname of file parsed. |
| 939 |
* @param $save_callback |
| 940 |
* Callback function used to save strings. |
| 941 |
*/ |
| 942 |
function _wppb_le_potx_find_perm_hook ( $file, $filebase, $save_callback ) |
| 943 |
{ |
| 944 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 945 |
|
| 946 |
if ( isset( $_wppb_le_potx_lookup[ $filebase . '_perm' ] ) ) { |
| 947 |
// Special case for node module, because it uses dynamic permissions. |
| 948 |
// Include the static permissions by hand. That's about all we can do here. |
| 949 |
if ( $filebase == 'node' ) { |
| 950 |
$line = $_wppb_le_potx_tokens[ $_wppb_le_potx_lookup[ 'node_perm' ][ 0 ] ][ 2 ]; |
| 951 |
// List from node.module 1.763 (checked in on 2006/12/29 at 21:25:36 by drumm) |
| 952 |
$nodeperms = array( 'administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions' ); |
| 953 |
foreach ( $nodeperms as $item ) { |
| 954 |
// hook_perm() is only ever found on a Drupal system which does not |
| 955 |
// support context. |
| 956 |
$save_callback( $item, WPPB_LE_POTX_CONTEXT_NONE, $file, $line ); |
| 957 |
} |
| 958 |
} else { |
| 959 |
$count = 0; |
| 960 |
foreach ( $_wppb_le_potx_lookup[ $filebase . '_perm' ] as $ti ) { |
| 961 |
$tn = $ti; |
| 962 |
while ( is_array ( $_wppb_le_potx_tokens[ $tn ] ) || $_wppb_le_potx_tokens[ $tn ] != '}' ) { |
| 963 |
if ( is_array ( $_wppb_le_potx_tokens[ $tn ] ) && $_wppb_le_potx_tokens[ $tn ][ 0 ] == T_CONSTANT_ENCAPSED_STRING ) { |
| 964 |
// hook_perm() is only ever found on a Drupal system which does not |
| 965 |
// support context. |
| 966 |
$save_callback( _wppb_le_potx_format_quoted_string ( $_wppb_le_potx_tokens[ $tn ][ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, $_wppb_le_potx_tokens[ $tn ][ 2 ] ); |
| 967 |
$count++; |
| 968 |
} |
| 969 |
$tn++; |
| 970 |
} |
| 971 |
} |
| 972 |
if ( !$count ) { |
| 973 |
wppb_potx_status ( 'error', t ( '%hook should have an array of literal string permission names.', array( '%hook' => $filebase . '_perm()' ) ), $file, NULL, NULL, 'http://drupal.org/node/323101' ); |
| 974 |
} |
| 975 |
} |
| 976 |
} |
| 977 |
} |
| 978 |
|
| 979 |
/** |
| 980 |
* Helper function to look up the token closing the current function. |
| 981 |
* |
| 982 |
* @param $here |
| 983 |
* The token at the function name |
| 984 |
*/ |
| 985 |
function _wppb_le_potx_find_end_of_function ( $here ) |
| 986 |
{ |
| 987 |
global $_wppb_le_potx_tokens; |
| 988 |
|
| 989 |
// Seek to open brace. |
| 990 |
while ( is_array ( $_wppb_le_potx_tokens[ $here ] ) || $_wppb_le_potx_tokens[ $here ] != '{' ) { |
| 991 |
$here++; |
| 992 |
} |
| 993 |
$nesting = 1; |
| 994 |
while ( $nesting > 0 ) { |
| 995 |
$here++; |
| 996 |
if ( !is_array ( $_wppb_le_potx_tokens[ $here ] ) ) { |
| 997 |
if ( $_wppb_le_potx_tokens[ $here ] == '}' ) { |
| 998 |
$nesting--; |
| 999 |
} |
| 1000 |
if ( $_wppb_le_potx_tokens[ $here ] == '{' ) { |
| 1001 |
$nesting++; |
| 1002 |
} |
| 1003 |
} |
| 1004 |
} |
| 1005 |
return $here; |
| 1006 |
} |
| 1007 |
|
| 1008 |
/** |
| 1009 |
* Helper to move past t() and format_plural() arguments in search of context. |
| 1010 |
* |
| 1011 |
* @param $here |
| 1012 |
* The token before the start of the arguments |
| 1013 |
*/ |
| 1014 |
function _wppb_le_potx_skip_args ( $here ) |
| 1015 |
{ |
| 1016 |
global $_wppb_le_potx_tokens; |
| 1017 |
|
| 1018 |
$nesting = 0; |
| 1019 |
// Go through to either the end of the function call or to a comma |
| 1020 |
// after the current position on the same nesting level. |
| 1021 |
while ( !( ( $_wppb_le_potx_tokens[ $here ] == ',' && $nesting == 0 ) || |
| 1022 |
( $_wppb_le_potx_tokens[ $here ] == ')' && $nesting == -1 ) ) ) { |
| 1023 |
$here++; |
| 1024 |
if ( !is_array ( $_wppb_le_potx_tokens[ $here ] ) ) { |
| 1025 |
if ( $_wppb_le_potx_tokens[ $here ] == ')' ) { |
| 1026 |
$nesting--; |
| 1027 |
} |
| 1028 |
if ( $_wppb_le_potx_tokens[ $here ] == '(' ) { |
| 1029 |
$nesting++; |
| 1030 |
} |
| 1031 |
} |
| 1032 |
} |
| 1033 |
// If we run out of nesting, it means we reached the end of the function call, |
| 1034 |
// so we skipped the arguments but did not find meat for looking at the |
| 1035 |
// specified context. |
| 1036 |
return ( $nesting == 0 ? $here : FALSE ); |
| 1037 |
} |
| 1038 |
|
| 1039 |
/** |
| 1040 |
* Helper to find the value for 'context' on t() and format_plural(). |
| 1041 |
* |
| 1042 |
* @param $tf |
| 1043 |
* Start position of the original function. |
| 1044 |
* @param $ti |
| 1045 |
* Start position where we should search from. |
| 1046 |
* @param $file |
| 1047 |
* Full path name of file parsed. |
| 1048 |
* @param function_name |
| 1049 |
* The name of the function to look for. Either 'format_plural' or 't' |
| 1050 |
* given that Drupal 7 only supports context on these. |
| 1051 |
*/ |
| 1052 |
function _wppb_le_potx_find_context ( $tf, $ti, $file, $function_name ) |
| 1053 |
{ |
| 1054 |
global $_wppb_le_potx_tokens; |
| 1055 |
|
| 1056 |
// Start from after the comma and skip the possible arguments for the function |
| 1057 |
// so we can look for the context. |
| 1058 |
if ( ( $ti = _wppb_le_potx_skip_args ( $ti ) ) && ( $_wppb_le_potx_tokens[ $ti ] == ',' ) ) { |
| 1059 |
// Now we actually might have some definition for a context. The $options |
| 1060 |
// argument is coming up, which might have a key for context. |
| 1061 |
echo "TI:" . $ti . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 1062 |
list( $com, $arr, $par ) = array( $_wppb_le_potx_tokens[ $ti ], $_wppb_le_potx_tokens[ $ti + 1 ], $_wppb_le_potx_tokens[ $ti + 2 ] ); |
| 1063 |
if ( $com == ',' && $arr[ 1 ] == 'array' && $par == '(' ) { |
| 1064 |
$nesting = 0; |
| 1065 |
$ti += 3; |
| 1066 |
// Go through to either the end of the array or to the key definition of |
| 1067 |
// context on the same nesting level. |
| 1068 |
while ( !( ( is_array ( $_wppb_le_potx_tokens[ $ti ] ) && ( in_array ( $_wppb_le_potx_tokens[ $ti ][ 1 ], array( '"context"', "'context'" ) ) ) && ( $_wppb_le_potx_tokens[ $ti ][ 0 ] == T_CONSTANT_ENCAPSED_STRING ) && ( $nesting == 0 ) ) || |
| 1069 |
( $_wppb_le_potx_tokens[ $ti ] == ')' && $nesting == -1 ) ) ) { |
| 1070 |
$ti++; |
| 1071 |
if ( !is_array ( $_wppb_le_potx_tokens[ $ti ] ) ) { |
| 1072 |
if ( $_wppb_le_potx_tokens[ $ti ] == ')' ) { |
| 1073 |
$nesting--; |
| 1074 |
} |
| 1075 |
if ( $_wppb_le_potx_tokens[ $ti ] == '(' ) { |
| 1076 |
$nesting++; |
| 1077 |
} |
| 1078 |
} |
| 1079 |
} |
| 1080 |
if ( $nesting == 0 ) { |
| 1081 |
// Found the 'context' key on the top level of the $options array. |
| 1082 |
list( $arw, $str ) = array( $_wppb_le_potx_tokens[ $ti + 1 ], $_wppb_le_potx_tokens[ $ti + 2 ] ); |
| 1083 |
if ( is_array ( $arw ) && $arw[ 1 ] == '=>' && is_array ( $str ) && $str[ 0 ] == T_CONSTANT_ENCAPSED_STRING ) { |
| 1084 |
return _wppb_le_potx_format_quoted_string ( $str[ 1 ] ); |
| 1085 |
} else { |
| 1086 |
list( $type, $string, $line ) = $_wppb_le_potx_tokens[ $ti ]; |
| 1087 |
// @todo: fix error reference. |
| 1088 |
_wppb_le_potx_marker_error ( $file, $line, $function_name, $tf, t ( 'The context element in the options array argument to @function() should be a literal string. There should be no variables, concatenation, constants or other non-literal strings there.', array( '@function' => $function_name ) ), 'http://drupal.org/node/322732' ); |
| 1089 |
// Return with error. |
| 1090 |
return WPPB_LE_POTX_CONTEXT_ERROR; |
| 1091 |
} |
| 1092 |
} else { |
| 1093 |
// Did not found 'context' key in $options array. |
| 1094 |
return WPPB_LE_POTX_CONTEXT_NONE; |
| 1095 |
} |
| 1096 |
} |
| 1097 |
} |
| 1098 |
|
| 1099 |
// After skipping args, we did not find a comma to look for $options. |
| 1100 |
return WPPB_LE_POTX_CONTEXT_NONE; |
| 1101 |
} |
| 1102 |
|
| 1103 |
/** |
| 1104 |
* List of menu item titles. Only from Drupal 6. |
| 1105 |
* |
| 1106 |
* @param $file |
| 1107 |
* Full path name of file parsed. |
| 1108 |
* @param $filebase |
| 1109 |
* Filenaname of file parsed. |
| 1110 |
* @param $save_callback |
| 1111 |
* Callback function used to save strings. |
| 1112 |
*/ |
| 1113 |
function _wppb_le_potx_find_menu_hook ( $file, $filebase, $save_callback ) |
| 1114 |
{ |
| 1115 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 1116 |
|
| 1117 |
if ( isset( $_wppb_le_potx_lookup[ $filebase . '_menu' ] ) && is_array ( $_wppb_le_potx_lookup[ $filebase . '_menu' ] ) ) { |
| 1118 |
// We have a menu hook in this file. |
| 1119 |
foreach ( $_wppb_le_potx_lookup[ $filebase . '_menu' ] as $ti ) { |
| 1120 |
$end = _wppb_le_potx_find_end_of_function ( $ti ); |
| 1121 |
$tn = $ti; |
| 1122 |
while ( $tn < $end ) { |
| 1123 |
// Look through the code until the end of the function. |
| 1124 |
if ( $_wppb_le_potx_tokens[ $tn ][ 0 ] == T_CONSTANT_ENCAPSED_STRING && in_array ( $_wppb_le_potx_tokens[ $tn ][ 1 ], array( "'title'", '"title"', "'description'", '"description"' ) ) && $_wppb_le_potx_tokens[ $tn + 1 ][ 0 ] == T_DOUBLE_ARROW ) { |
| 1125 |
if ( $_wppb_le_potx_tokens[ $tn + 2 ][ 0 ] == T_CONSTANT_ENCAPSED_STRING ) { |
| 1126 |
// Menu items support no context. |
| 1127 |
$save_callback( |
| 1128 |
_wppb_le_potx_format_quoted_string ( $_wppb_le_potx_tokens[ $tn + 2 ][ 1 ] ), |
| 1129 |
WPPB_LE_POTX_CONTEXT_NONE, |
| 1130 |
$file, |
| 1131 |
$_wppb_le_potx_tokens[ $tn + 2 ][ 2 ] |
| 1132 |
); |
| 1133 |
$tn += 2; // Jump forward by 2. |
| 1134 |
} else { |
| 1135 |
wppb_potx_status ( 'error', t ( 'Invalid menu %element definition found in %hook. Title and description keys of the menu array should be literal strings.', array( '%element' => $_wppb_le_potx_tokens[ $tn ][ 1 ], '%hook' => $filebase . '_menu()' ) ), $file, $_wppb_le_potx_tokens[ $tn ][ 2 ], NULL, 'http://drupal.org/node/323101' ); |
| 1136 |
} |
| 1137 |
} |
| 1138 |
$tn++; |
| 1139 |
} |
| 1140 |
} |
| 1141 |
} |
| 1142 |
} |
| 1143 |
|
| 1144 |
/** |
| 1145 |
* Get languages names from Drupal's locale.inc. |
| 1146 |
* |
| 1147 |
* @param $file |
| 1148 |
* Full path name of file parsed |
| 1149 |
* @param $save_callback |
| 1150 |
* Callback function used to save strings. |
| 1151 |
* @param $api_version |
| 1152 |
* Drupal API version to work with. |
| 1153 |
*/ |
| 1154 |
function _wppb_le_potx_find_language_names ( $file, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 1155 |
{ |
| 1156 |
global $_wppb_le_potx_tokens, $_wppb_le_potx_lookup; |
| 1157 |
|
| 1158 |
foreach ( $_wppb_le_potx_lookup[ $api_version > WPPB_LE_POTX_API_5 ? '_locale_get_predefined_list' : '_locale_get_iso639_list' ] as $ti ) { |
| 1159 |
// Search for the definition of _locale_get_predefined_list(), not where it is called. |
| 1160 |
if ( $_wppb_le_potx_tokens[ $ti - 1 ][ 0 ] == T_FUNCTION ) { |
| 1161 |
break; |
| 1162 |
} |
| 1163 |
} |
| 1164 |
|
| 1165 |
$end = _wppb_le_potx_find_end_of_function ( $ti ); |
| 1166 |
$ti += 7; // function name, (, ), {, return, array, ( |
| 1167 |
while ( $ti < $end ) { |
| 1168 |
while ( $_wppb_le_potx_tokens[ $ti ][ 0 ] != T_ARRAY ) { |
| 1169 |
if ( !is_array ( $_wppb_le_potx_tokens[ $ti ] ) && $_wppb_le_potx_tokens[ $ti ] == ';' ) { |
| 1170 |
// We passed the end of the list, break out to function level |
| 1171 |
// to prevent an infinite loop. |
| 1172 |
break 2; |
| 1173 |
} |
| 1174 |
$ti++; |
| 1175 |
} |
| 1176 |
$ti += 2; // array, ( |
| 1177 |
// Language names are context-less. |
| 1178 |
$save_callback( _wppb_le_potx_format_quoted_string ( $_wppb_le_potx_tokens[ $ti ][ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, $_wppb_le_potx_tokens[ $ti ][ 2 ] ); |
| 1179 |
} |
| 1180 |
} |
| 1181 |
|
| 1182 |
/** |
| 1183 |
* Get the exact CVS version number from the file, so we can |
| 1184 |
* push that into the generated output. |
| 1185 |
* |
| 1186 |
* @param $code |
| 1187 |
* Complete source code of the file parsed. |
| 1188 |
* @param $file |
| 1189 |
* Name of the file parsed. |
| 1190 |
* @param $version_callback |
| 1191 |
* Callback used to save the version information. |
| 1192 |
*/ |
| 1193 |
function _wppb_le_potx_find_version_number ( $code, $file, $version_callback ) |
| 1194 |
{ |
| 1195 |
// Prevent CVS from replacing this pattern with actual info. |
| 1196 |
if ( preg_match ( '!\\$I' . 'd: ([^\\$]+) Exp \\$!', $code, $version_info ) ) { |
| 1197 |
$version_callback( $version_info[ 1 ], $file ); |
| 1198 |
} else { |
| 1199 |
// Unknown version information. |
| 1200 |
$version_callback( $file . ': n/a', $file ); |
| 1201 |
} |
| 1202 |
} |
| 1203 |
|
| 1204 |
/** |
| 1205 |
* Add date strings, which cannot be extracted otherwise. |
| 1206 |
* This is called for locale.module. |
| 1207 |
* |
| 1208 |
* @param $file |
| 1209 |
* Name of the file parsed. |
| 1210 |
* @param $save_callback |
| 1211 |
* Callback function used to save strings. |
| 1212 |
* @param $api_version |
| 1213 |
* Drupal API version to work with. |
| 1214 |
*/ |
| 1215 |
function _wppb_le_potx_add_date_strings ( $file, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 1216 |
{ |
| 1217 |
for ( $i = 1; $i <= 12; $i++ ) { |
| 1218 |
$stamp = mktime ( 0, 0, 0, $i, 1, 1971 ); |
| 1219 |
if ( $api_version > WPPB_LE_POTX_API_6 ) { |
| 1220 |
// From Drupal 7, long month names are saved with this context. |
| 1221 |
$save_callback( date ( "F", $stamp ), 'Long month name', $file ); |
| 1222 |
} elseif ( $api_version > WPPB_LE_POTX_API_5 ) { |
| 1223 |
// Drupal 6 uses a little hack. No context. |
| 1224 |
$save_callback( '!long-month-name ' . date ( "F", $stamp ), WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1225 |
} else { |
| 1226 |
// Older versions just accept the confusion, no context. |
| 1227 |
$save_callback( date ( "F", $stamp ), WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1228 |
} |
| 1229 |
// Short month names lack a context anyway. |
| 1230 |
$save_callback( date ( "M", $stamp ), WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1231 |
} |
| 1232 |
for ( $i = 0; $i <= 7; $i++ ) { |
| 1233 |
$stamp = $i * 86400; |
| 1234 |
$save_callback( date ( "D", $stamp ), WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1235 |
$save_callback( date ( "l", $stamp ), WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1236 |
} |
| 1237 |
$save_callback( 'am', WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1238 |
$save_callback( 'pm', WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1239 |
$save_callback( 'AM', WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1240 |
$save_callback( 'PM', WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1241 |
} |
| 1242 |
|
| 1243 |
/** |
| 1244 |
* Add format_interval special strings, which cannot be |
| 1245 |
* extracted otherwise. This is called for common.inc |
| 1246 |
* |
| 1247 |
* @param $file |
| 1248 |
* Name of the file parsed. |
| 1249 |
* @param $save_callback |
| 1250 |
* Callback function used to save strings. |
| 1251 |
* @param $api_version |
| 1252 |
* Drupal API version to work with. |
| 1253 |
*/ |
| 1254 |
function _wppb_le_potx_add_format_interval_strings ( $file, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 1255 |
{ |
| 1256 |
$components = array( |
| 1257 |
'1 year' => '@count years', |
| 1258 |
'1 week' => '@count weeks', |
| 1259 |
'1 day' => '@count days', |
| 1260 |
'1 hour' => '@count hours', |
| 1261 |
'1 min' => '@count min', |
| 1262 |
'1 sec' => '@count sec' |
| 1263 |
); |
| 1264 |
if ( $api_version > WPPB_LE_POTX_API_6 ) { |
| 1265 |
// Month support added in Drupal 7. |
| 1266 |
$components[ '1 month' ] = '@count months'; |
| 1267 |
} |
| 1268 |
|
| 1269 |
foreach ( $components as $singular => $plural ) { |
| 1270 |
// Intervals support no context. |
| 1271 |
$save_callback( $singular . "\0" . $plural, WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1272 |
} |
| 1273 |
} |
| 1274 |
|
| 1275 |
/** |
| 1276 |
* Add default theme region names, which cannot be extracted otherwise. |
| 1277 |
* These default names are defined in system.module |
| 1278 |
* |
| 1279 |
* @param $file |
| 1280 |
* Name of the file parsed. |
| 1281 |
* @param $save_callback |
| 1282 |
* Callback function used to save strings. |
| 1283 |
* @param $api_version |
| 1284 |
* Drupal API version to work with. |
| 1285 |
*/ |
| 1286 |
function _wppb_le_potx_add_default_region_names ( $file, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 1287 |
{ |
| 1288 |
$regions = array( |
| 1289 |
'left' => 'Left sidebar', |
| 1290 |
'right' => 'Right sidebar', |
| 1291 |
'content' => 'Content', |
| 1292 |
'header' => 'Header', |
| 1293 |
'footer' => 'Footer', |
| 1294 |
); |
| 1295 |
if ( $api_version > WPPB_LE_POTX_API_6 ) { |
| 1296 |
// @todo: Update with final region list when D7 stabilizes. |
| 1297 |
$regions[ 'highlight' ] = 'Highlighted content'; |
| 1298 |
$regions[ 'help' ] = 'Help'; |
| 1299 |
$regions[ 'page_top' ] = 'Page top'; |
| 1300 |
} |
| 1301 |
foreach ( $regions as $region ) { |
| 1302 |
// Regions come with the default context. |
| 1303 |
$save_callback( $region, WPPB_LE_POTX_CONTEXT_NONE, $file ); |
| 1304 |
} |
| 1305 |
} |
| 1306 |
|
| 1307 |
/** |
| 1308 |
* Parse an .info file and add relevant strings to the list. |
| 1309 |
* |
| 1310 |
* @param $file_path |
| 1311 |
* Complete file path to load contents with. |
| 1312 |
* @param $file_name |
| 1313 |
* Stripped file name to use in outpout. |
| 1314 |
* @param $strings |
| 1315 |
* Current strings array |
| 1316 |
* @param $api_version |
| 1317 |
* Drupal API version to work with. |
| 1318 |
*/ |
| 1319 |
function _wppb_le_potx_find_info_file_strings ( $file_path, $file_name, $save_callback, $api_version = WPPB_LE_POTX_API_6 ) |
| 1320 |
{ |
| 1321 |
$info = array(); |
| 1322 |
|
| 1323 |
if ( file_exists ( $file_path ) ) { |
| 1324 |
$info = $api_version > WPPB_LE_POTX_API_5 ? drupal_parse_info_file ( $file_path ) : parse_ini_file ( $file_path ); |
| 1325 |
} |
| 1326 |
|
| 1327 |
// We need the name, description and package values. Others, |
| 1328 |
// like core and PHP compatibility, timestamps or versions |
| 1329 |
// are not to be translated. |
| 1330 |
foreach ( array( 'name', 'description', 'package' ) as $key ) { |
| 1331 |
if ( isset( $info[ $key ] ) ) { |
| 1332 |
// No context support for .info file strings. |
| 1333 |
$save_callback( $info[ $key ], WPPB_LE_POTX_CONTEXT_NONE, $file_name ); |
| 1334 |
} |
| 1335 |
} |
| 1336 |
|
| 1337 |
// Add regions names from themes. |
| 1338 |
if ( isset( $info[ 'regions' ] ) && is_array ( $info[ 'regions' ] ) ) { |
| 1339 |
foreach ( $info[ 'regions' ] as $region => $region_name ) { |
| 1340 |
// No context support for .info file strings. |
| 1341 |
$save_callback( $region_name, WPPB_LE_POTX_CONTEXT_NONE, $file_name ); |
| 1342 |
} |
| 1343 |
} |
| 1344 |
} |
| 1345 |
|
| 1346 |
/** |
| 1347 |
* Parse a JavaScript file for translatables. Only from Drupal 6. |
| 1348 |
* |
| 1349 |
* Extracts strings wrapped in Drupal.t() and Drupal.formatPlural() |
| 1350 |
* calls and inserts them into potx storage. |
| 1351 |
* |
| 1352 |
* Regex code lifted from _locale_parse_js_file(). |
| 1353 |
*/ |
| 1354 |
function _wppb_le_potx_parse_js_file ( $code, $file, $save_callback ) |
| 1355 |
{ |
| 1356 |
$js_string_regex = '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+'; |
| 1357 |
|
| 1358 |
// Match all calls to Drupal.t() in an array. |
| 1359 |
// Note: \s also matches newlines with the 's' modifier. |
| 1360 |
preg_match_all ( '~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . $js_string_regex . ')\s*[,\)]~s', $code, $t_matches, PREG_SET_ORDER ); |
| 1361 |
if ( isset( $t_matches ) && count ( $t_matches ) ) { |
| 1362 |
foreach ( $t_matches as $match ) { |
| 1363 |
// Remove match from code to help us identify faulty Drupal.t() calls. |
| 1364 |
$code = str_replace ( $match[ 0 ], '', $code ); |
| 1365 |
// @todo: figure out how to parse out context, once Drupal supports it. |
| 1366 |
$save_callback( _wppb_le_potx_parse_js_string ( $match[ 1 ] ), WPPB_LE_POTX_CONTEXT_NONE, $file, 0 ); |
| 1367 |
} |
| 1368 |
} |
| 1369 |
|
| 1370 |
// Match all Drupal.formatPlural() calls in another array. |
| 1371 |
preg_match_all ( '~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . $js_string_regex . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $code, $plural_matches, PREG_SET_ORDER ); |
| 1372 |
if ( isset( $plural_matches ) && count ( $plural_matches ) ) { |
| 1373 |
foreach ( $plural_matches as $index => $match ) { |
| 1374 |
// Remove match from code to help us identify faulty |
| 1375 |
// Drupal.formatPlural() calls later. |
| 1376 |
$code = str_replace ( $match[ 0 ], '', $code ); |
| 1377 |
// @todo: figure out how to parse out context, once Drupal supports it. |
| 1378 |
$save_callback( |
| 1379 |
_wppb_le_potx_parse_js_string ( $match[ 1 ] ) . "\0" . _wppb_le_potx_parse_js_string ( $match[ 2 ] ), |
| 1380 |
WPPB_LE_POTX_CONTEXT_NONE, |
| 1381 |
$file, |
| 1382 |
0 |
| 1383 |
); |
| 1384 |
} |
| 1385 |
} |
| 1386 |
|
| 1387 |
// Any remaining Drupal.t() or Drupal.formatPlural() calls are evil. This |
| 1388 |
// regex is not terribly accurate (ie. code wrapped inside will confuse |
| 1389 |
// the match), but we only need some unique part to identify the faulty calls. |
| 1390 |
preg_match_all ( '~[^\w]Drupal\s*\.\s*(t|formatPlural)\s*\([^)]+\)~s', $code, $faulty_matches, PREG_SET_ORDER ); |
| 1391 |
if ( isset( $faulty_matches ) && count ( $faulty_matches ) ) { |
| 1392 |
foreach ( $faulty_matches as $index => $match ) { |
| 1393 |
$message = ( $match[ 1 ] == 't' ) ? t ( 'Drupal.t() calls should have a single literal string as their first parameter.' ) : t ( 'The singular and plural string parameters on Drupal.formatPlural() calls should be literal strings, plural containing a @count placeholder.' ); |
| 1394 |
wppb_potx_status ( 'error', $message, $file, NULL, $match[ 0 ], 'http://drupal.org/node/323109' ); |
| 1395 |
} |
| 1396 |
} |
| 1397 |
} |
| 1398 |
|
| 1399 |
/** |
| 1400 |
* Clean up string found in JavaScript source code. Only from Drupal 6. |
| 1401 |
*/ |
| 1402 |
function _wppb_le_potx_parse_js_string ( $string ) |
| 1403 |
{ |
| 1404 |
return _wppb_le_potx_format_quoted_string ( implode ( '', preg_split ( '~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', $string ) ) ); |
| 1405 |
} |
| 1406 |
|
| 1407 |
/** |
| 1408 |
* Collect a list of file names relevant for extraction, |
| 1409 |
* starting from the given path. |
| 1410 |
* |
| 1411 |
* @param $path |
| 1412 |
* Where to start searching for files recursively. |
| 1413 |
* Provide non-empty path values with a trailing slash. |
| 1414 |
* @param $basename |
| 1415 |
* Allows the restriction of search to a specific basename |
| 1416 |
* (ie. to collect files for a specific module). |
| 1417 |
* @param $api_version |
| 1418 |
* Drupal API version to work with. |
| 1419 |
* @todo |
| 1420 |
* Add folder exceptions for other version control systems. |
| 1421 |
*/ |
| 1422 |
function _wppb_le_potx_explore_dir ( $path = '', $basename = '*', $api_version = WPPB_LE_POTX_API_6 ) |
| 1423 |
{ |
| 1424 |
// It would be so nice to just use GLOB_BRACE, but it is not available on all |
| 1425 |
// operarting systems, so we are working around the missing functionality. |
| 1426 |
$extensions = array( 'php', 'inc', 'module', 'engine', 'theme', 'install', 'info', 'profile' ); |
| 1427 |
if ( $api_version > WPPB_LE_POTX_API_5 ) { |
| 1428 |
$extensions[ ] = 'js'; |
| 1429 |
} |
| 1430 |
$files = array(); |
| 1431 |
foreach ( $extensions as $extension ) { |
| 1432 |
$files_here = glob ( $path . $basename . '.' . $extension ); |
| 1433 |
if ( is_array ( $files_here ) ) { |
| 1434 |
$files = array_merge ( $files, $files_here ); |
| 1435 |
} |
| 1436 |
if ( $basename != '*' ) { |
| 1437 |
// Basename was specific, so look for things like basename.admin.inc as well. |
| 1438 |
// If the basnename was *, the above glob() already covered this case. |
| 1439 |
$files_here = glob ( $path . $basename . '.*.' . $extension ); |
| 1440 |
if ( is_array ( $files_here ) ) { |
| 1441 |
$files = array_merge ( $files, $files_here ); |
| 1442 |
} |
| 1443 |
} |
| 1444 |
} |
| 1445 |
|
| 1446 |
// Grab subdirectories. |
| 1447 |
$dirs = glob ( $path . '*', GLOB_ONLYDIR ); |
| 1448 |
if ( is_array ( $dirs ) ) { |
| 1449 |
foreach ( $dirs as $dir ) { |
| 1450 |
if ( !preg_match ( "!(^|.+/)(CVS|.svn|.git)$!", $dir ) ) { |
| 1451 |
$files = array_merge ( $files, _wppb_le_potx_explore_dir ( "$dir/", $basename ) ); |
| 1452 |
} |
| 1453 |
} |
| 1454 |
} |
| 1455 |
// Skip our own files, because we don't want to get strings from them |
| 1456 |
// to appear in the output, especially with the command line interface. |
| 1457 |
// TODO: fix this to be able to autogenerate templates for potx itself. |
| 1458 |
foreach ( $files as $id => $file_name ) { |
| 1459 |
if ( preg_match ( '!(potx-cli.php|potx.php)$!', $file_name ) ) { |
| 1460 |
unset( $files[ $id ] ); |
| 1461 |
} |
| 1462 |
} |
| 1463 |
return $files; |
| 1464 |
} |
| 1465 |
|
| 1466 |
/** |
| 1467 |
* Default $version_callback used by the potx system. Saves values |
| 1468 |
* to a global array to reduce memory consumption problems when |
| 1469 |
* passing around big chunks of values. |
| 1470 |
* |
| 1471 |
* @param $value |
| 1472 |
* The ersion number value of $file. If NULL, the collected |
| 1473 |
* values are returned. |
| 1474 |
* @param $file |
| 1475 |
* Name of file where the version information was found. |
| 1476 |
*/ |
| 1477 |
function _wppb_le_potx_save_version ( $value = NULL, $file = NULL ) |
| 1478 |
{ |
| 1479 |
global $_wppb_le_potx_versions; |
| 1480 |
|
| 1481 |
if ( isset( $value ) ) { |
| 1482 |
$_wppb_le_potx_versions[ $file ] = $value; |
| 1483 |
} else { |
| 1484 |
return $_wppb_le_potx_versions; |
| 1485 |
} |
| 1486 |
} |
| 1487 |
|
| 1488 |
/** |
| 1489 |
* Default $save_callback used by the potx system. Saves values |
| 1490 |
* to global arrays to reduce memory consumption problems when |
| 1491 |
* passing around big chunks of values. |
| 1492 |
* |
| 1493 |
* @param $value |
| 1494 |
* The string value. If NULL, the array of collected values |
| 1495 |
* are returned for the given $string_mode. |
| 1496 |
* @param $context |
| 1497 |
* From Drupal 7, separate contexts are supported. WPPB_LE_POTX_CONTEXT_NONE is |
| 1498 |
* the default, if the code does not specify a context otherwise. |
| 1499 |
* @param $file |
| 1500 |
* Name of file where the string was found. |
| 1501 |
* @param $line |
| 1502 |
* Line number where the string was found. |
| 1503 |
* @param $string_mode |
| 1504 |
* String mode: WPPB_LE_POTX_STRING_INSTALLER, WPPB_LE_POTX_STRING_RUNTIME |
| 1505 |
* or WPPB_LE_POTX_STRING_BOTH. |
| 1506 |
*/ |
| 1507 |
function _wppb_le_potx_save_string ( $value = NULL, $context = NULL, $file = NULL, $line = 0, $string_mode = WPPB_LE_POTX_STRING_RUNTIME ) |
| 1508 |
{ |
| 1509 |
global $_wppb_le_potx_strings, $_wppb_le_potx_install; |
| 1510 |
|
| 1511 |
if ( isset( $value ) ) { |
| 1512 |
switch ( $string_mode ) { |
| 1513 |
case WPPB_LE_POTX_STRING_BOTH: |
| 1514 |
// Mark installer strings as duplicates of runtime strings if |
| 1515 |
// the string was both recorded in the runtime and in the installer. |
| 1516 |
$_wppb_le_potx_install[ $value ][ $context ][ $file ][ ] = $line . ' (dup)'; |
| 1517 |
// Break intentionally missing. |
| 1518 |
case WPPB_LE_POTX_STRING_RUNTIME: |
| 1519 |
// Mark runtime strings as duplicates of installer strings if |
| 1520 |
// the string was both recorded in the runtime and in the installer. |
| 1521 |
$_wppb_le_potx_strings[ $value ][ $context ][ $file ][ ] = $line . ( $string_mode == WPPB_LE_POTX_STRING_BOTH ? ' (dup)' : '' ); |
| 1522 |
break; |
| 1523 |
case WPPB_LE_POTX_STRING_INSTALLER: |
| 1524 |
$_wppb_le_potx_install[ $value ][ $context ][ $file ][ ] = $line; |
| 1525 |
break; |
| 1526 |
} |
| 1527 |
} else { |
| 1528 |
return ( $string_mode == WPPB_LE_POTX_STRING_RUNTIME ? $_wppb_le_potx_strings : $_wppb_le_potx_install ); |
| 1529 |
} |
| 1530 |
} |
| 1531 |
|
| 1532 |
if ( !function_exists ( 't' ) ) { |
| 1533 |
// If invoked outside of Drupal, t() will not exist, but |
| 1534 |
// used to format the error message, so we provide a replacement. |
| 1535 |
function t ( $string, $args = array() ) |
| 1536 |
{ |
| 1537 |
return strtr ( $string, $args ); |
| 1538 |
} |
| 1539 |
} |
| 1540 |
|
| 1541 |
if ( !function_exists ( 'drupal_parse_info_file' ) ) { |
| 1542 |
// If invoked outside of Drupal, drupal_parse_info_file() will not be available, |
| 1543 |
// but we need this function to properly parse Drupal 6/7 .info files. |
| 1544 |
// Directly copied from common.inc,v 1.704 2007/10/19 10:30:54 goba Exp. |
| 1545 |
function drupal_parse_info_file ( $filename ) |
| 1546 |
{ |
| 1547 |
$info = array(); |
| 1548 |
|
| 1549 |
if ( !file_exists ( $filename ) ) { |
| 1550 |
return $info; |
| 1551 |
} |
| 1552 |
|
| 1553 |
$data = file_get_contents ( $filename ); |
| 1554 |
if ( preg_match_all ( ' |
| 1555 |
@^\s* # Start at the beginning of a line, ignoring leading whitespace |
| 1556 |
((?: |
| 1557 |
[^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, |
| 1558 |
\[[^\[\]]*\] # unless they are balanced and not nested |
| 1559 |
)+?) |
| 1560 |
\s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space) |
| 1561 |
(?: |
| 1562 |
("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes |
| 1563 |
(\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes |
| 1564 |
([^\r\n]*?) # Non-quoted string |
| 1565 |
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace |
| 1566 |
@msx', $data, $matches, PREG_SET_ORDER ) |
| 1567 |
) { |
| 1568 |
foreach ( $matches as $match ) { |
| 1569 |
// Fetch the key and value string |
| 1570 |
$i = 0; |
| 1571 |
foreach ( array( 'key', 'value1', 'value2', 'value3' ) as $var ) { |
| 1572 |
$$var = isset( $match[ ++$i ] ) ? $match[ $i ] : ''; |
| 1573 |
} |
| 1574 |
$value = stripslashes ( substr ( $value1, 1, -1 ) ) . stripslashes ( substr ( $value2, 1, -1 ) ) . $value3; |
| 1575 |
|
| 1576 |
// Parse array syntax |
| 1577 |
$keys = preg_split ( '/\]?\[/', rtrim ( $key, ']' ) ); |
| 1578 |
$last = array_pop ( $keys ); |
| 1579 |
$parent = & $info; |
| 1580 |
|
| 1581 |
// Create nested arrays |
| 1582 |
foreach ( $keys as $key ) { |
| 1583 |
if ( $key == '' ) { |
| 1584 |
$key = count ( $parent ); |
| 1585 |
} |
| 1586 |
if ( !isset( $parent[ $key ] ) || !is_array ( $parent[ $key ] ) ) { |
| 1587 |
$parent[ $key ] = array(); |
| 1588 |
} |
| 1589 |
$parent = & $parent[ $key ]; |
| 1590 |
} |
| 1591 |
|
| 1592 |
// Handle PHP constants |
| 1593 |
if ( defined ( $value ) ) { |
| 1594 |
$value = constant ( $value ); |
| 1595 |
} |
| 1596 |
|
| 1597 |
// Insert actual value |
| 1598 |
if ( $last == '' ) { |
| 1599 |
$last = count ( $parent ); |
| 1600 |
} |
| 1601 |
$parent[ $last ] = $value; |
| 1602 |
} |
| 1603 |
} |
| 1604 |
|
| 1605 |
return $info; |
| 1606 |
} |
| 1607 |
} |
| 1608 |
|