PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.3
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.3
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 4.0.3, at add-ons-free/labels-edit/potx.php

1,608 lines 67.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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