PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 3.9.9
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v3.9.9
4.0.3 4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 341 releases
profile-builder / add-ons-free / labels-edit / potx.php

potx.php in User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor 3.9.9, at add-ons-free/labels-edit/potx.php

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