PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/htdocs/wp-content/plugins/sitepress-multilingual-cms/inc/potx.php

https://bitbucket.org/dkrzos/phc
PHP | 1580 lines | 973 code | 97 blank | 510 comment | 254 complexity | b9b297a2bb5cb272406da9d444d6cf4b MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  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. * @file
  5. * Extraction API used by the web and command line interface.
  6. *
  7. * This include file implements the default string and file version
  8. * storage as well as formatting of POT files for web download or
  9. * file system level creation. The strings, versions and file contents
  10. * are handled with global variables to reduce the possible memory overhead
  11. * and API clutter of passing them around. Custom string and version saving
  12. * functions can be implemented to use the functionality provided here as an
  13. * API for Drupal code to translatable string conversion.
  14. *
  15. * The potx-cli.php script can be used with this include file as
  16. * a command line interface to string extraction. The potx.module
  17. * can be used as a web interface for manual extraction.
  18. *
  19. * For a module using potx as an extraction API, but providing more
  20. * sophisticated functionality on top of it, look into the
  21. * 'Localization server' module: http://drupal.org/project/l10n_server
  22. */
  23. /**
  24. * Silence status reports.
  25. */
  26. define('POTX_STATUS_SILENT', 0);
  27. /**
  28. * Drupal message based status reports.
  29. */
  30. define('POTX_STATUS_MESSAGE', 1);
  31. /**
  32. * Command line status reporting.
  33. *
  34. * Status goes to standard output, errors to standard error.
  35. */
  36. define('POTX_STATUS_CLI', 2);
  37. /**
  38. * Structured array status logging.
  39. *
  40. * Useful for coder review status reporting.
  41. */
  42. define('POTX_STATUS_STRUCTURED', 3);
  43. /**
  44. * Core parsing mode:
  45. * - .info files folded into general.pot
  46. * - separate files generated for modules
  47. */
  48. define('POTX_BUILD_CORE', 0);
  49. /**
  50. * Multiple files mode:
  51. * - .info files folded into their module pot files
  52. * - separate files generated for modules
  53. */
  54. define('POTX_BUILD_MULTIPLE', 1);
  55. /**
  56. * Single file mode:
  57. * - all files folded into one pot file
  58. */
  59. define('POTX_BUILD_SINGLE', 2);
  60. /**
  61. * Save string to both installer and runtime collection.
  62. */
  63. define('POTX_STRING_BOTH', 0);
  64. /**
  65. * Save string to installer collection only.
  66. */
  67. define('POTX_STRING_INSTALLER', 1);
  68. /**
  69. * Save string to runtime collection only.
  70. */
  71. define('POTX_STRING_RUNTIME', 2);
  72. /**
  73. * Parse source files in Drupal 5.x format.
  74. */
  75. define('POTX_API_5', 5);
  76. /**
  77. * Parse source files in Drupal 6.x format.
  78. *
  79. * Changes since 5.x documented at http://drupal.org/node/114774
  80. */
  81. define('POTX_API_6', 6);
  82. /**
  83. * Parse source files in Drupal 7.x format.
  84. *
  85. * Changes since 6.x documented at http://drupal.org/node/224333
  86. */
  87. define('POTX_API_7', 7);
  88. /**
  89. * When no context is used. Makes it easy to look these up.
  90. */
  91. define('POTX_CONTEXT_NONE', NULL);
  92. /**
  93. * When there was a context identification error.
  94. */
  95. define('POTX_CONTEXT_ERROR', FALSE);
  96. /**
  97. * Process a file and put extracted information to the given parameters.
  98. *
  99. * @param $file_path
  100. * Comlete path to file to process.
  101. * @param $strip_prefix
  102. * An integer denoting the number of chars to strip from filepath for output.
  103. * @param $save_callback
  104. * Callback function to use to save the collected strings.
  105. * @param $version_callback
  106. * Callback function to use to save collected version numbers.
  107. * @param $api_version
  108. * Drupal API version to work with.
  109. */
  110. function _potx_process_file($file_path, $strip_prefix = 0, $save_callback = '_potx_save_string', $version_callback = '_potx_save_version', $api_version = POTX_API_6) {
  111. global $_potx_tokens, $_potx_lookup;
  112. // Figure out the basename and extension to select extraction method.
  113. $basename = basename($file_path);
  114. $name_parts = pathinfo($basename);
  115. // Always grab the CVS version number from the code
  116. $code = file_get_contents($file_path);
  117. $file_name = $strip_prefix > 0 ? substr($file_path, $strip_prefix) : $file_path;
  118. _potx_find_version_number($code, $file_name, $version_callback);
  119. // Extract raw PHP language tokens.
  120. $raw_tokens = token_get_all($code);
  121. unset($code);
  122. // Remove whitespace and possible HTML (the later in templates for example),
  123. // count line numbers so we can include them in the output.
  124. $_potx_tokens = array();
  125. $_potx_lookup = array();
  126. $token_number = 0;
  127. $line_number = 1;
  128. foreach ($raw_tokens as $token) {
  129. if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
  130. if (is_array($token)) {
  131. $token[] = $line_number;
  132. // Fill array for finding token offsets quickly.
  133. $src_tokens = array(
  134. '__', 'esc_attr__', 'esc_html__', '_e', 'esc_attr_e', 'esc_html_e',
  135. '_x', 'esc_attr_x', 'esc_html_x', '_ex',
  136. '_n', '_nx'
  137. );
  138. if ($token[0] == T_STRING || ($token[0] == T_VARIABLE && in_array($token[1], $src_tokens))) {
  139. if (!isset($_potx_lookup[$token[1]])) {
  140. $_potx_lookup[$token[1]] = array();
  141. }
  142. $_potx_lookup[$token[1]][] = $token_number;
  143. }
  144. }
  145. $_potx_tokens[] = $token;
  146. $token_number++;
  147. }
  148. // Collect line numbers.
  149. if (is_array($token)) {
  150. $line_number += count(explode("\n", $token[1])) - 1;
  151. }
  152. else {
  153. $line_number += count(explode("\n", $token)) - 1;
  154. }
  155. }
  156. unset($raw_tokens);
  157. // Drupal 7 onwards supports context on t().
  158. if(!empty($src_tokens))
  159. foreach($src_tokens as $tk){
  160. _potx_find_t_calls_with_context($file_name, $save_callback, $tk);
  161. }
  162. }
  163. /**
  164. * Creates complete file strings with _potx_store()
  165. *
  166. * @param $string_mode
  167. * Strings to generate files for: POTX_STRING_RUNTIME or POTX_STRING_INSTALLER.
  168. * @param $build_mode
  169. * Storage mode used: single, multiple or core
  170. * @param $force_name
  171. * Forces a given file name to get used, if single mode is on, without extension
  172. * @param $save_callback
  173. * Callback used to save strings previously.
  174. * @param $version_callback
  175. * Callback used to save versions previously.
  176. * @param $header_callback
  177. * Callback to invoke to get the POT header.
  178. * @param $template_export_langcode
  179. * Language code if the template should have language dependent content
  180. * (like plural formulas and language name) included.
  181. * @param $translation_export_langcode
  182. * Language code if translations should also be exported.
  183. * @param $api_version
  184. * Drupal API version to work with.
  185. */
  186. function _potx_build_files($string_mode = POTX_STRING_RUNTIME, $build_mode = POTX_BUILD_SINGLE, $force_name = 'general', $save_callback = '_potx_save_string', $version_callback = '_potx_save_version', $header_callback = '_potx_get_header', $template_export_langcode = NULL, $translation_export_langcode = NULL, $api_version = POTX_API_6) {
  187. global $_potx_store;
  188. // Get strings and versions by reference.
  189. $strings = $save_callback(NULL, NULL, NULL, 0, $string_mode);
  190. $versions = $version_callback();
  191. // We might not have any string recorded in this string mode.
  192. if (!is_array($strings)) {
  193. return;
  194. }
  195. foreach ($strings as $string => $string_info) {
  196. foreach ($string_info as $context => $file_info) {
  197. // Build a compact list of files this string occured in.
  198. $occured = $file_list = array();
  199. // Look for strings appearing in multiple directories (ie.
  200. // different subprojects). So we can include them in general.pot.
  201. $last_location = dirname(array_shift(array_keys($file_info)));
  202. $multiple_locations = FALSE;
  203. foreach ($file_info as $file => $lines) {
  204. $occured[] = "$file:". join(';', $lines);
  205. if (isset($versions[$file])) {
  206. $file_list[] = $versions[$file];
  207. }
  208. if (dirname($file) != $last_location) {
  209. $multiple_locations = TRUE;
  210. }
  211. $last_location = dirname($file);
  212. }
  213. // Mark duplicate strings (both translated in the app and in the installer).
  214. $comment = join(" ", $occured);
  215. if (strpos($comment, '(dup)') !== FALSE) {
  216. $comment = '(duplicate) '. str_replace('(dup)', '', $comment);
  217. }
  218. $output = "#: $comment\n";
  219. if ($build_mode == POTX_BUILD_SINGLE) {
  220. // File name forcing in single mode.
  221. $file_name = $force_name;
  222. }
  223. elseif (strpos($comment, '.info')) {
  224. // Store .info file strings either in general.pot or the module pot file,
  225. // depending on the mode used.
  226. $file_name = ($build_mode == POTX_BUILD_CORE ? 'general' : str_replace('.info', '.module', $file_name));
  227. }
  228. elseif ($multiple_locations) {
  229. // Else if occured more than once, store in general.pot.
  230. $file_name = 'general';
  231. }
  232. else {
  233. // Fold multiple files in the same folder into one.
  234. if (empty($last_location) || $last_location == '.') {
  235. $file_name = 'root';
  236. }
  237. else {
  238. $file_name = str_replace('/', '-', $last_location);
  239. }
  240. }
  241. if (strpos($string, "\0") !== FALSE) {
  242. // Plural strings have a null byte delimited format.
  243. list($singular, $plural) = explode("\0", $string);
  244. $output .= "msgid \"$singular\"\n";
  245. $output .= "msgid_plural \"$plural\"\n";
  246. if (!empty($context)) {
  247. $output .= "msgctxt \"$context\"\n";
  248. }
  249. if (isset($translation_export_langcode)) {
  250. $output .= _potx_translation_export($translation_export_langcode, $singular, $plural, $api_version);
  251. }
  252. else {
  253. $output .= "msgstr[0] \"\"\n";
  254. $output .= "msgstr[1] \"\"\n";
  255. }
  256. }
  257. else {
  258. // Simple strings.
  259. $output .= "msgid \"$string\"\n";
  260. if (!empty($context)) {
  261. $output .= "msgctxt \"$context\"\n";
  262. }
  263. if (isset($translation_export_langcode)) {
  264. $output .= _potx_translation_export($translation_export_langcode, $string, NULL, $api_version);
  265. }
  266. else {
  267. $output .= "msgstr \"\"\n";
  268. }
  269. }
  270. $output .= "\n";
  271. // Store the generated output in the given file storage.
  272. if (!isset($_potx_store[$file_name])) {
  273. $_potx_store[$file_name] = array(
  274. 'header' => $header_callback($file_name, $template_export_langcode, $api_version),
  275. 'sources' => $file_list,
  276. 'strings' => $output,
  277. 'count' => 1,
  278. );
  279. }
  280. else {
  281. // Maintain a list of unique file names.
  282. $_potx_store[$file_name]['sources'] = array_unique(array_merge($_potx_store[$file_name]['sources'], $file_list));
  283. $_potx_store[$file_name]['strings'] .= $output;
  284. $_potx_store[$file_name]['count'] += 1;
  285. }
  286. }
  287. }
  288. }
  289. /**
  290. * Export translations with a specific language.
  291. *
  292. * @param $translation_export_langcode
  293. * Language code if translations should also be exported.
  294. * @param $string
  295. * String or singular version if $plural was provided.
  296. * @param $plural
  297. * Plural version of singular string.
  298. * @param $api_version
  299. * Drupal API version to work with.
  300. */
  301. function _potx_translation_export($translation_export_langcode, $string, $plural = NULL, $api_version = POTX_API_6) {
  302. include_once 'includes/locale.inc';
  303. // Stip out slash escapes.
  304. $string = stripcslashes($string);
  305. // Column and table name changed between versions.
  306. $language_column = $api_version > POTX_API_5 ? 'language' : 'locale';
  307. $language_table = $api_version > POTX_API_5 ? 'languages' : 'locales_meta';
  308. if (!isset($plural)) {
  309. // Single string to look translation up for.
  310. 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))) {
  311. return 'msgstr '. _locale_export_string($translation);
  312. }
  313. return "msgstr \"\"\n";
  314. }
  315. else {
  316. // String with plural variants. Fill up source string array first.
  317. $plural = stripcslashes($plural);
  318. $strings = array();
  319. $number_of_plurals = db_result(db_query('SELECT plurals FROM {'. $language_table ."} WHERE {$language_column} = '%s'", $translation_export_langcode));
  320. $plural_index = 0;
  321. while ($plural_index < $number_of_plurals) {
  322. if ($plural_index == 0) {
  323. // Add the singular version.
  324. $strings[] = $string;
  325. }
  326. elseif ($plural_index == 1) {
  327. // Only add plural version if required.
  328. $strings[] = $plural;
  329. }
  330. else {
  331. // More plural versions only if required, with the lookup source
  332. // string modified as imported into the database.
  333. $strings[] = str_replace('@count', '@count['. $plural_index .']', $plural);
  334. }
  335. $plural_index++;
  336. }
  337. $output = '';
  338. if (count($strings)) {
  339. // Source string array was done, so export translations.
  340. foreach ($strings as $index => $string) {
  341. 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))) {
  342. $output .= 'msgstr['. $index .'] '. _locale_export_string(_locale_export_remove_plural($translation));
  343. }
  344. else {
  345. $output .= "msgstr[". $index ."] \"\"\n";
  346. }
  347. }
  348. }
  349. else {
  350. // No plural information was recorded, so export empty placeholders.
  351. $output .= "msgstr[0] \"\"\n";
  352. $output .= "msgstr[1] \"\"\n";
  353. }
  354. return $output;
  355. }
  356. }
  357. /**
  358. * Returns a header generated for a given file
  359. *
  360. * @param $file
  361. * Name of POT file to generate header for
  362. * @param $template_export_langcode
  363. * Language code if the template should have language dependent content
  364. * (like plural formulas and language name) included.
  365. * @param $api_version
  366. * Drupal API version to work with.
  367. */
  368. function _potx_get_header($file, $template_export_langcode = NULL, $api_version = POTX_API_6) {
  369. // We only have language to use if we should export with that langcode.
  370. $language = NULL;
  371. if (isset($template_export_langcode)) {
  372. $language = db_fetch_object(db_query($api_version > 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));
  373. }
  374. $output = '# $'.'Id'.'$'."\n";
  375. $output .= "#\n";
  376. $output .= '# '. (isset($language) ? $language->name : 'LANGUAGE') .' translation of Drupal ('. $file .")\n";
  377. $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
  378. $output .= "# --VERSIONS--\n";
  379. $output .= "#\n";
  380. $output .= "#, fuzzy\n";
  381. $output .= "msgid \"\"\n";
  382. $output .= "msgstr \"\"\n";
  383. $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
  384. $output .= '"POT-Creation-Date: '. date("Y-m-d H:iO") ."\\n\"\n";
  385. $output .= '"PO-Revision-Date: '. (isset($language) ? date("Y-m-d H:iO") : 'YYYY-mm-DD HH:MM+ZZZZ') ."\\n\"\n";
  386. $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
  387. $output .= "\"Language-Team: ". (isset($language) ? $language->name : 'LANGUAGE') ." <EMAIL@ADDRESS>\\n\"\n";
  388. $output .= "\"MIME-Version: 1.0\\n\"\n";
  389. $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
  390. $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
  391. if (isset($language->formula) && isset($language->plurals)) {
  392. $output .= "\"Plural-Forms: nplurals=". $language->plurals ."; plural=". strtr($language->formula, array('$' => '')) .";\\n\"\n\n";
  393. }
  394. else {
  395. $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
  396. }
  397. return $output;
  398. }
  399. /**
  400. * Write out generated files to the current folder.
  401. *
  402. * @param $http_filename
  403. * File name for content-disposition header in case of usage
  404. * over HTTP. If not given, files are written to the local filesystem.
  405. * @param $content_disposition
  406. * See RFC2183. 'inline' or 'attachment', with a default of
  407. * 'inline'. Only used if $http_filename is set.
  408. * @todo
  409. * Look into whether multiple files can be output via HTTP.
  410. */
  411. function _potx_write_files($http_filename = NULL, $content_disposition = 'inline') {
  412. global $_potx_store;
  413. // Generate file lists and output files.
  414. if (is_array($_potx_store)) {
  415. foreach ($_potx_store as $file => $contents) {
  416. // Build replacement for file listing.
  417. if (count($contents['sources']) > 1) {
  418. $filelist = "Generated from files:\n# " . join("\n# ", $contents['sources']);
  419. }
  420. elseif (count($contents['sources']) == 1) {
  421. $filelist = "Generated from file: " . join('', $contents['sources']);
  422. }
  423. else {
  424. $filelist = 'No version information was available in the source files.';
  425. }
  426. $output = str_replace('--VERSIONS--', $filelist, $contents['header'] . $contents['strings']);
  427. if ($http_filename) {
  428. // HTTP output.
  429. header('Content-Type: text/plain; charset=utf-8');
  430. header('Content-Transfer-Encoding: 8bit');
  431. header("Content-Disposition: $content_disposition; filename=$http_filename");
  432. print $output;
  433. return;
  434. }
  435. else {
  436. // Local file output, flatten directory structure.
  437. $file = str_replace('.', '-', preg_replace('![/]?([a-zA-Z_0-9]*/)*!', '', $file)) .'.pot';
  438. $fp = fopen($file, 'w');
  439. fwrite($fp, $output);
  440. fclose($fp);
  441. }
  442. }
  443. }
  444. }
  445. /**
  446. * Escape quotes in a strings depending on the surrounding
  447. * quote type used.
  448. *
  449. * @param $str
  450. * The strings to escape
  451. */
  452. function _potx_format_quoted_string($str) {
  453. $quo = substr($str, 0, 1);
  454. $str = substr($str, 1, -1);
  455. if ($quo == '"') {
  456. $str = stripcslashes($str);
  457. }
  458. else {
  459. $str = strtr($str, array("\\'" => "'", "\\\\" => "\\"));
  460. }
  461. return addcslashes($str, "\0..\37\\\"");
  462. }
  463. /**
  464. * Output a marker error with an extract of where the error was found.
  465. *
  466. * @param $file
  467. * Name of file
  468. * @param $line
  469. * Line number of error
  470. * @param $marker
  471. * Function name with which the error was identified
  472. * @param $ti
  473. * Index on the token array
  474. * @param $error
  475. * Helpful error message for users.
  476. * @param $docs_url
  477. * Documentation reference.
  478. */
  479. function _potx_marker_error($file, $line, $marker, $ti, $error, $docs_url = NULL) {
  480. global $_potx_tokens;
  481. $tokens = '';
  482. $ti += 2;
  483. $tc = count($_potx_tokens);
  484. $par = 1;
  485. while ((($tc - $ti) > 0) && $par) {
  486. if (is_array($_potx_tokens[$ti])) {
  487. $tokens .= $_potx_tokens[$ti][1];
  488. }
  489. else {
  490. $tokens .= $_potx_tokens[$ti];
  491. if ($_potx_tokens[$ti] == "(") {
  492. $par++;
  493. }
  494. else if ($_potx_tokens[$ti] == ")") {
  495. $par--;
  496. }
  497. }
  498. $ti++;
  499. }
  500. potx_status('error', $error, $file, $line, $marker .'('. $tokens, $docs_url);
  501. }
  502. /**
  503. * Status notification function.
  504. *
  505. * @param $op
  506. * Operation to perform or type of message text.
  507. * - set: sets the reporting mode to $value
  508. * use one of the POTX_STATUS_* constants as $value
  509. * - get: returns the list of error messages recorded
  510. * if $value is true, it also clears the internal message cache
  511. * - error: sends an error message in $value with optional $file and $line
  512. * - status: sends a status message in $value
  513. * @param $value
  514. * Value depending on $op.
  515. * @param $file
  516. * Name of file the error message is related to.
  517. * @param $line
  518. * Number of line the error message is related to.
  519. * @param $excerpt
  520. * Excerpt of the code in question, if available.
  521. * @param $docs_url
  522. * URL to the guidelines to follow to fix the problem.
  523. */
  524. function potx_status($op, $value = NULL, $file = NULL, $line = NULL, $excerpt = NULL, $docs_url = NULL) {
  525. static $mode = POTX_STATUS_CLI;
  526. static $messages = array();
  527. switch ($op) {
  528. case 'set':
  529. // Setting the reporting mode.
  530. $mode = $value;
  531. return;
  532. case 'get':
  533. // Getting the errors. Optionally deleting the messages.
  534. $errors = $messages;
  535. if (!empty($value)) {
  536. $messages = array();
  537. }
  538. return $errors;
  539. case 'error':
  540. case 'status':
  541. // Location information is required in 3 of the four possible reporting
  542. // modes as part of the error message. The structured mode needs the
  543. // file, line and excerpt info separately, not in the text.
  544. $location_info = '';
  545. if (($mode != POTX_STATUS_STRUCTURED) && isset($file)) {
  546. if (isset($line)) {
  547. if (isset($excerpt)) {
  548. $location_info = t('At %excerpt in %file on line %line.', array('%excerpt' => $excerpt, '%file' => $file, '%line' => $line));
  549. }
  550. else {
  551. $location_info = t('In %file on line %line.', array('%file' => $file, '%line' => $line));
  552. }
  553. }
  554. else {
  555. if (isset($excerpt)) {
  556. $location_info = t('At %excerpt in %file.', array('%excerpt' => $excerpt, '%file' => $file));
  557. }
  558. else {
  559. $location_info = t('In %file.', array('%file' => $file));
  560. }
  561. }
  562. }
  563. // Documentation helpers are provided as readable text in most modes.
  564. $read_more = '';
  565. if (($mode != POTX_STATUS_STRUCTURED) && isset($docs_url)) {
  566. $read_more = ($mode == 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));
  567. }
  568. // Error message or progress text to display.
  569. switch ($mode) {
  570. case POTX_STATUS_MESSAGE:
  571. drupal_set_message(join(' ', array($value, $location_info, $read_more)), $op);
  572. break;
  573. case POTX_STATUS_CLI:
  574. if(defined('STDERR') && defined('STDOUT')){
  575. fwrite($op == 'error' ? STDERR : STDOUT, join("\n", array($value, $location_info, $read_more)) ."\n\n");
  576. }
  577. break;
  578. case POTX_STATUS_SILENT:
  579. if ($op == 'error') {
  580. $messages[] = join(' ', array($value, $location_info, $read_more));
  581. }
  582. break;
  583. case POTX_STATUS_STRUCTURED:
  584. if ($op == 'error') {
  585. $messages[] = array($value, $file, $line, $excerpt, $docs_url);
  586. }
  587. break;
  588. }
  589. return;
  590. }
  591. }
  592. /**
  593. * Detect all occurances of t()-like calls.
  594. *
  595. * These sequences are searched for:
  596. * T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ")"
  597. * T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ","
  598. *
  599. * @param $file
  600. * Name of file parsed.
  601. * @param $save_callback
  602. * Callback function used to save strings.
  603. * @param function_name
  604. * The name of the function to look for (could be 't', '$t', 'st'
  605. * or any other t-like function).
  606. * @param $string_mode
  607. * String mode to use: POTX_STRING_INSTALLER, POTX_STRING_RUNTIME or
  608. * POTX_STRING_BOTH.
  609. */
  610. function _potx_find_t_calls($file, $save_callback, $function_name = 't', $string_mode = POTX_STRING_RUNTIME) {
  611. global $_potx_tokens, $_potx_lookup;
  612. // Lookup tokens by function name.
  613. if (isset($_potx_lookup[$function_name])) {
  614. foreach ($_potx_lookup[$function_name] as $ti) {
  615. list($ctok, $par, $mid, $rig) = array($_potx_tokens[$ti], $_potx_tokens[$ti+1], $_potx_tokens[$ti+2], $_potx_tokens[$ti+3]);
  616. list($type, $string, $line) = $ctok;
  617. if ($par == "(") {
  618. if (in_array($rig, array(")", ","))
  619. && (is_array($mid) && ($mid[0] == T_CONSTANT_ENCAPSED_STRING))) {
  620. // This function is only used for context-less call types.
  621. $save_callback(_potx_format_quoted_string($mid[1]), POTX_CONTEXT_NONE, $file, $line, $string_mode);
  622. }
  623. else {
  624. // $function_name() found, but inside is something which is not a string literal.
  625. _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');
  626. }
  627. }
  628. }
  629. }
  630. }
  631. /**
  632. * Detect all occurances of t()-like calls from Drupal 7 (with context).
  633. *
  634. * These sequences are searched for:
  635. * T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ")"
  636. * T_STRING("$function_name") + "(" + T_CONSTANT_ENCAPSED_STRING + ","
  637. * and then an optional value for the replacements and an optional array
  638. * for the options with an optional context key.
  639. *
  640. * @param $file
  641. * Name of file parsed.
  642. * @param $save_callback
  643. * Callback function used to save strings.
  644. * @param function_name
  645. * The name of the function to look for (could be 't', '$t', 'st'
  646. * or any other t-like function). Drupal 7 only supports context on t().
  647. * @param $string_mode
  648. * String mode to use: POTX_STRING_INSTALLER, POTX_STRING_RUNTIME or
  649. * POTX_STRING_BOTH.
  650. */
  651. function _potx_find_t_calls_with_context($file, $save_callback, $function_name = '_e', $string_mode = POTX_STRING_RUNTIME) {
  652. global $_potx_tokens, $_potx_lookup;
  653. // Lookup tokens by function name.
  654. if (isset($_potx_lookup[$function_name])) {
  655. foreach ($_potx_lookup[$function_name] as $ti) {
  656. list($ctok, $par, $mid, $rig) = array($_potx_tokens[$ti], $_potx_tokens[$ti+1], $_potx_tokens[$ti+2], $_potx_tokens[$ti+3]);
  657. list($type, $string, $line) = $ctok;
  658. if ($par == "(") {
  659. if (in_array($rig, array(")", ","))
  660. && (is_array($mid) && ($mid[0] == T_CONSTANT_ENCAPSED_STRING))) {
  661. // By default, there is no context.
  662. $domain = POTX_CONTEXT_NONE;
  663. if ($rig == ',') {
  664. // If there was a comma after the string, we need to look forward
  665. // to try and find the context.
  666. /*$context = _potx_find_context($ti, $ti + 4, $file, $function_name);*/
  667. if($function_name == '_x' || $function_name == '_ex'){
  668. $domain_offset = 6;
  669. $context_offset = 4;
  670. $text = $mid[1];
  671. }elseif($function_name == '_n'){
  672. $domain_offset = 10;
  673. $context_offset = false;
  674. $text_plural = $_potx_tokens[$ti+4][1];
  675. }elseif($function_name == '_nx'){
  676. $domain_offset = 10;
  677. $context_offset = 8;
  678. $text_plural = $_potx_tokens[$ti+4][1];
  679. }else{
  680. $domain_offset = 4;
  681. $context_offset = false;
  682. $text = $mid[1];
  683. }
  684. if(!isset($_potx_tokens[$ti+$domain_offset][1])) return false;
  685. if(!preg_match('#^(\'|")(.+)#', $_potx_tokens[$ti+$domain_offset][1])){
  686. $constant_val = @constant($_potx_tokens[$ti+$domain_offset][1]);
  687. if(!is_null($constant_val)){
  688. $domain = $constant_val;
  689. }else{
  690. if(function_exists($_potx_tokens[$ti+$domain_offset][1])){
  691. $domain = @$_potx_tokens[$ti+$domain_offset][1]();
  692. if(empty($domain)){
  693. return false;
  694. }
  695. }else{
  696. return false;
  697. }
  698. }
  699. }else{
  700. $domain = trim($_potx_tokens[$ti+$domain_offset][1],"\"' ");
  701. }
  702. // exception for gettext calls with contexts
  703. if(false !== $context_offset){
  704. if(!preg_match('#^(\'|")(.+)#', $_potx_tokens[$ti+$context_offset][1])){
  705. $constant_val = @constant($_potx_tokens[$ti+$context_offset][1]);
  706. if(!is_null($constant_val)){
  707. $context = $constant_val;
  708. }else{
  709. if(function_exists($_potx_tokens[$ti+$context_offset][1])){
  710. $context = @$_potx_tokens[$ti+$context_offset][1]();
  711. if(empty($context)){
  712. return false;
  713. }
  714. }else{
  715. return false;
  716. }
  717. }
  718. }else{
  719. $context = trim($_potx_tokens[$ti+$context_offset][1],"\"' ");
  720. }
  721. }else{
  722. $context = false;
  723. }
  724. }
  725. if ($domain !== POTX_CONTEXT_ERROR) {
  726. // Only save if there was no error in context parsing.
  727. $save_callback(_potx_format_quoted_string($mid[1]), $domain, @strval($context), $file, $line, $string_mode);
  728. if(isset($text_plural)){
  729. $save_callback(_potx_format_quoted_string($text_plural), $domain, $context, $file, $line, $string_mode);
  730. }
  731. }
  732. }
  733. else {
  734. // $function_name() found, but inside is something which is not a string literal.
  735. _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');
  736. }
  737. }
  738. }
  739. }
  740. }
  741. /**
  742. * Detect all occurances of watchdog() calls. Only from Drupal 6.
  743. *
  744. * These sequences are searched for:
  745. * watchdog + "(" + T_CONSTANT_ENCAPSED_STRING + "," +
  746. * T_CONSTANT_ENCAPSED_STRING + something
  747. *
  748. * @param $file
  749. * Name of file parsed.
  750. * @param $save_callback
  751. * Callback function used to save strings.
  752. */
  753. function _potx_find_watchdog_calls($file, $save_callback) {
  754. global $_potx_tokens, $_potx_lookup;
  755. // Lookup tokens by function name.
  756. if (isset($_potx_lookup['watchdog'])) {
  757. foreach ($_potx_lookup['watchdog'] as $ti) {
  758. list($ctok, $par, $mtype, $comma, $message, $rig) = array($_potx_tokens[$ti], $_potx_tokens[$ti+1], $_potx_tokens[$ti+2], $_potx_tokens[$ti+3], $_potx_tokens[$ti+4], $_potx_tokens[$ti+5]);
  759. list($type, $string, $line) = $ctok;
  760. if ($par == '(') {
  761. // Both type and message should be a string literal.
  762. if (in_array($rig, array(')', ',')) && $comma == ','
  763. && (is_array($mtype) && ($mtype[0] == T_CONSTANT_ENCAPSED_STRING))
  764. && (is_array($message) && ($message[0] == T_CONSTANT_ENCAPSED_STRING))) {
  765. // Context is not supported on watchdog().
  766. $save_callback(_potx_format_quoted_string($mtype[1]), POTX_CONTEXT_NONE, $file, $line);
  767. $save_callback(_potx_format_quoted_string($message[1]), POTX_CONTEXT_NONE, $file, $line);
  768. }
  769. else {
  770. // watchdog() found, but inside is something which is not a string literal.
  771. _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');
  772. }
  773. }
  774. }
  775. }
  776. }
  777. /**
  778. * Detect all occurances of format_plural calls.
  779. *
  780. * These sequences are searched for:
  781. * T_STRING("format_plural") + "(" + ..anything (might be more tokens).. +
  782. * "," + T_CONSTANT_ENCAPSED_STRING +
  783. * "," + T_CONSTANT_ENCAPSED_STRING + parenthesis (or comma allowed from
  784. * Drupal 6)
  785. *
  786. * @param $file
  787. * Name of file parsed.
  788. * @param $save_callback
  789. * Callback function used to save strings.
  790. * @param $api_version
  791. * Drupal API version to work with.
  792. */
  793. function _potx_find_format_plural_calls($file, $save_callback, $api_version = POTX_API_6) {
  794. global $_potx_tokens, $_potx_lookup;
  795. if (isset($_potx_lookup['format_plural'])) {
  796. foreach ($_potx_lookup['format_plural'] as $ti) {
  797. list($ctok, $par1) = array($_potx_tokens[$ti], $_potx_tokens[$ti+1]);
  798. list($type, $string, $line) = $ctok;
  799. if ($par1 == "(") {
  800. // Eat up everything that is used as the first parameter
  801. $tn = $ti + 2;
  802. $depth = 0;
  803. while (!($_potx_tokens[$tn] == "," && $depth == 0)) {
  804. if ($_potx_tokens[$tn] == "(") {
  805. $depth++;
  806. }
  807. elseif ($_potx_tokens[$tn] == ")") {
  808. $depth--;
  809. }
  810. $tn++;
  811. }
  812. // Get further parameters
  813. list($comma1, $singular, $comma2, $plural, $par2) = array($_potx_tokens[$tn], $_potx_tokens[$tn+1], $_potx_tokens[$tn+2], $_potx_tokens[$tn+3], $_potx_tokens[$tn+4]);
  814. if (($comma2 == ',') && ($par2 == ')' || ($par2 == ',' && $api_version > POTX_API_5)) &&
  815. (is_array($singular) && ($singular[0] == T_CONSTANT_ENCAPSED_STRING)) &&
  816. (is_array($plural) && ($plural[0] == T_CONSTANT_ENCAPSED_STRING))) {
  817. // By default, there is no context.
  818. $context = POTX_CONTEXT_NONE;
  819. if ($par2 == ',' && $api_version > POTX_API_6) {
  820. // If there was a comma after the plural, we need to look forward
  821. // to try and find the context.
  822. $context = _potx_find_context($ti, $tn + 5, $file, 'format_plural');
  823. }
  824. if ($context !== POTX_CONTEXT_ERROR) {
  825. // Only save if there was no error in context parsing.
  826. $save_callback(
  827. _potx_format_quoted_string($singular[1]) ."\0". _potx_format_quoted_string($plural[1]),
  828. $context,
  829. $file,
  830. $line
  831. );
  832. }
  833. }
  834. else {
  835. // format_plural() found, but the parameters are not correct.
  836. _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');
  837. }
  838. }
  839. }
  840. }
  841. }
  842. /**
  843. * Detect permission names from the hook_perm() implementations.
  844. * Note that this will get confused with a similar pattern in a comment,
  845. * and with dynamic permissions, which need to be accounted for.
  846. *
  847. * @param $file
  848. * Full path name of file parsed.
  849. * @param $filebase
  850. * Filenaname of file parsed.
  851. * @param $save_callback
  852. * Callback function used to save strings.
  853. */
  854. function _potx_find_perm_hook($file, $filebase, $save_callback) {
  855. global $_potx_tokens, $_potx_lookup;
  856. if (isset($_potx_lookup[$filebase .'_perm'])) {
  857. // Special case for node module, because it uses dynamic permissions.
  858. // Include the static permissions by hand. That's about all we can do here.
  859. if ($filebase == 'node') {
  860. $line = $_potx_tokens[$_potx_lookup['node_perm'][0]][2];
  861. // List from node.module 1.763 (checked in on 2006/12/29 at 21:25:36 by drumm)
  862. $nodeperms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions');
  863. foreach ($nodeperms as $item) {
  864. // hook_perm() is only ever found on a Drupal system which does not
  865. // support context.
  866. $save_callback($item, POTX_CONTEXT_NONE, $file, $line);
  867. }
  868. }
  869. else {
  870. $count = 0;
  871. foreach ($_potx_lookup[$filebase .'_perm'] as $ti) {
  872. $tn = $ti;
  873. while (is_array($_potx_tokens[$tn]) || $_potx_tokens[$tn] != '}') {
  874. if (is_array($_potx_tokens[$tn]) && $_potx_tokens[$tn][0] == T_CONSTANT_ENCAPSED_STRING) {
  875. // hook_perm() is only ever found on a Drupal system which does not
  876. // support context.
  877. $save_callback(_potx_format_quoted_string($_potx_tokens[$tn][1]), POTX_CONTEXT_NONE, $file, $_potx_tokens[$tn][2]);
  878. $count++;
  879. }
  880. $tn++;
  881. }
  882. }
  883. if (!$count) {
  884. 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');
  885. }
  886. }
  887. }
  888. }
  889. /**
  890. * Helper function to look up the token closing the current function.
  891. *
  892. * @param $here
  893. * The token at the function name
  894. */
  895. function _potx_find_end_of_function($here) {
  896. global $_potx_tokens;
  897. // Seek to open brace.
  898. while (is_array($_potx_tokens[$here]) || $_potx_tokens[$here] != '{') {
  899. $here++;
  900. }
  901. $nesting = 1;
  902. while ($nesting > 0) {
  903. $here++;
  904. if (!is_array($_potx_tokens[$here])) {
  905. if ($_potx_tokens[$here] == '}') {
  906. $nesting--;
  907. }
  908. if ($_potx_tokens[$here] == '{') {
  909. $nesting++;
  910. }
  911. }
  912. }
  913. return $here;
  914. }
  915. /**
  916. * Helper to move past t() and format_plural() arguments in search of context.
  917. *
  918. * @param $here
  919. * The token before the start of the arguments
  920. */
  921. function _potx_skip_args($here) {
  922. global $_potx_tokens;
  923. $nesting = 0;
  924. // Go through to either the end of the function call or to a comma
  925. // after the current position on the same nesting level.
  926. while (!(($_potx_tokens[$here] == ',' && $nesting == 0) ||
  927. ($_potx_tokens[$here] == ')' && $nesting == -1))) {
  928. $here++;
  929. if (!is_array($_potx_tokens[$here])) {
  930. if ($_potx_tokens[$here] == ')') {
  931. $nesting--;
  932. }
  933. if ($_potx_tokens[$here] == '(') {
  934. $nesting++;
  935. }
  936. }
  937. }
  938. // If we run out of nesting, it means we reached the end of the function call,
  939. // so we skipped the arguments but did not find meat for looking at the
  940. // specified context.
  941. return ($nesting == 0 ? $here : FALSE);
  942. }
  943. /**
  944. * Helper to find the value for 'context' on t() and format_plural().
  945. *
  946. * @param $tf
  947. * Start position of the original function.
  948. * @param $ti
  949. * Start position where we should search from.
  950. * @param $file
  951. * Full path name of file parsed.
  952. * @param function_name
  953. * The name of the function to look for. Either 'format_plural' or 't'
  954. * given that Drupal 7 only supports context on these.
  955. */
  956. function _potx_find_context($tf, $ti, $file, $function_name) {
  957. global $_potx_tokens;
  958. // Start from after the comma and skip the possible arguments for the function
  959. // so we can look for the context.
  960. if (($ti = _potx_skip_args($ti)) && ($_potx_tokens[$ti] == ',')) {
  961. // Now we actually might have some definition for a context. The $options
  962. // argument is coming up, which might have a key for context.
  963. echo "TI:" . $ti."\n";
  964. list($com, $arr, $par) = array($_potx_tokens[$ti], $_potx_tokens[$ti+1], $_potx_tokens[$ti+2]);
  965. if ($com == ',' && $arr[1] == 'array' && $par == '(') {
  966. $nesting = 0;
  967. $ti += 3;
  968. // Go through to either the end of the array or to the key definition of
  969. // context on the same nesting level.
  970. while (!((is_array($_potx_tokens[$ti]) && (in_array($_potx_tokens[$ti][1], array('"context"', "'context'"))) && ($_potx_tokens[$ti][0] == T_CONSTANT_ENCAPSED_STRING) && ($nesting == 0)) ||
  971. ($_potx_tokens[$ti] == ')' && $nesting == -1))) {
  972. $ti++;
  973. if (!is_array($_potx_tokens[$ti])) {
  974. if ($_potx_tokens[$ti] == ')') {
  975. $nesting--;
  976. }
  977. if ($_potx_tokens[$ti] == '(') {
  978. $nesting++;
  979. }
  980. }
  981. }
  982. if ($nesting == 0) {
  983. // Found the 'context' key on the top level of the $options array.
  984. list($arw, $str) = array($_potx_tokens[$ti+1], $_potx_tokens[$ti+2]);
  985. if (is_array($arw) && $arw[1] == '=>' && is_array($str) && $str[0] == T_CONSTANT_ENCAPSED_STRING) {
  986. return _potx_format_quoted_string($str[1]);
  987. }
  988. else {
  989. list($type, $string, $line) = $_potx_tokens[$ti];
  990. // @todo: fix error reference.
  991. _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');
  992. // Return with error.
  993. return POTX_CONTEXT_ERROR;
  994. }
  995. }
  996. else {
  997. // Did not found 'context' key in $options array.
  998. return POTX_CONTEXT_NONE;
  999. }
  1000. }
  1001. }
  1002. // After skipping args, we did not find a comma to look for $options.
  1003. return POTX_CONTEXT_NONE;
  1004. }
  1005. /**
  1006. * List of menu item titles. Only from Drupal 6.
  1007. *
  1008. * @param $file
  1009. * Full path name of file parsed.
  1010. * @param $filebase
  1011. * Filenaname of file parsed.
  1012. * @param $save_callback
  1013. * Callback function used to save strings.
  1014. */
  1015. function _potx_find_menu_hook($file, $filebase, $save_callback) {
  1016. global $_potx_tokens, $_potx_lookup;
  1017. if (isset($_potx_lookup[$filebase .'_menu']) && is_array($_potx_lookup[$filebase .'_menu'])) {
  1018. // We have a menu hook in this file.
  1019. foreach ($_potx_lookup[$filebase .'_menu'] as $ti) {
  1020. $end = _potx_find_end_of_function($ti);
  1021. $tn = $ti;
  1022. while ($tn < $end) {
  1023. // Look through the code until the end of the function.
  1024. if ($_potx_tokens[$tn][0] == T_CONSTANT_ENCAPSED_STRING && in_array($_potx_tokens[$tn][1], array("'title'", '"title"', "'description'", '"description"')) && $_potx_tokens[$tn+1][0] == T_DOUBLE_ARROW) {
  1025. if ($_potx_tokens[$tn+2][0] == T_CONSTANT_ENCAPSED_STRING) {
  1026. // Menu items support no context.
  1027. $save_callback(
  1028. _potx_format_quoted_string($_potx_tokens[$tn+2][1]),
  1029. POTX_CONTEXT_NONE,
  1030. $file,
  1031. $_potx_tokens[$tn+2][2]
  1032. );
  1033. $tn+=2; // Jump forward by 2.
  1034. }
  1035. else {
  1036. 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' => $_potx_tokens[$tn][1], '%hook' => $filebase .'_menu()')), $file, $_potx_tokens[$tn][2], NULL, 'http://drupal.org/node/323101');
  1037. }
  1038. }
  1039. $tn++;
  1040. }
  1041. }
  1042. }
  1043. }
  1044. /**
  1045. * Get languages names from Drupal's locale.inc.
  1046. *
  1047. * @param $file
  1048. * Full path name of file parsed
  1049. * @param $save_callback
  1050. * Callback function used to save strings.
  1051. * @param $api_version
  1052. * Drupal API version to work with.
  1053. */
  1054. function _potx_find_language_names($file, $save_callback, $api_version = POTX_API_6) {
  1055. global $_potx_tokens, $_potx_lookup;
  1056. foreach ($_potx_lookup[$api_version > POTX_API_5 ? '_locale_get_predefined_list' : '_locale_get_iso639_list'] as $ti) {
  1057. // Search for the definition of _locale_get_predefined_list(), not where it is called.
  1058. if ($_potx_tokens[$ti-1][0] == T_FUNCTION) {
  1059. break;
  1060. }
  1061. }
  1062. $end = _potx_find_end_of_function($ti);
  1063. $ti += 7; // function name, (, ), {, return, array, (
  1064. while ($ti < $end) {
  1065. while ($_potx_tokens[$ti][0] != T_ARRAY) {
  1066. if (!is_array($_potx_tokens[$ti]) && $_potx_tokens[$ti] == ';') {
  1067. // We passed the end of the list, break out to function level
  1068. // to prevent an infinite loop.
  1069. break 2;
  1070. }
  1071. $ti++;
  1072. }
  1073. $ti += 2; // array, (
  1074. // Language names are context-less.
  1075. $save_callback(_potx_format_quoted_string($_potx_tokens[$ti][1]), POTX_CONTEXT_NONE, $file, $_potx_tokens[$ti][2]);
  1076. }
  1077. }
  1078. /**
  1079. * Get the exact CVS version number from the file, so we can
  1080. * push that into the generated output.
  1081. *
  1082. * @param $code
  1083. * Complete source code of the file parsed.
  1084. * @param $file
  1085. * Name of the file parsed.
  1086. * @param $version_callback
  1087. * Callback used to save the version information.
  1088. */
  1089. function _potx_find_version_number($code, $file, $version_callback) {
  1090. // Prevent CVS from replacing this pattern with actual info.
  1091. if (preg_match('!\\$I'.'d: ([^\\$]+) Exp \\$!', $code, $version_info)) {
  1092. $version_callback($version_info[1], $file);
  1093. }
  1094. else {
  1095. // Unknown version information.
  1096. $version_callback($file .': n/a', $file);
  1097. }
  1098. }
  1099. /**
  1100. * Add date strings, which cannot be extracted otherwise.
  1101. * This is called for locale.module.
  1102. *
  1103. * @param $file
  1104. * Name of the file parsed.
  1105. * @param $save_callback
  1106. * Callback function used to save strings.
  1107. * @param $api_version
  1108. * Drupal API version to work with.
  1109. */
  1110. function _potx_add_date_strings($file, $save_callback, $api_version = POTX_API_6) {
  1111. for ($i = 1; $i <= 12; $i++) {
  1112. $stamp = mktime(0, 0, 0, $i, 1, 1971);
  1113. if ($api_version > POTX_API_6) {
  1114. // From Drupal 7, long month names are saved with this context.
  1115. $save_callback(date("F", $stamp), 'Long month name', $file);
  1116. }
  1117. elseif ($api_version > POTX_API_5) {
  1118. // Drupal 6 uses a little hack. No context.
  1119. $save_callback('!long-month-name '. date("F", $stamp), POTX_CONTEXT_NONE, $file);
  1120. }
  1121. else {
  1122. // Older versions just accept the confusion, no context.
  1123. $save_callback(date("F", $stamp), POTX_CONTEXT_NONE, $file);
  1124. }
  1125. // Short month names lack a context anyway.
  1126. $save_callback(date("M", $stamp), POTX_CONTEXT_NONE, $file);
  1127. }
  1128. for ($i = 0; $i <= 7; $i++) {
  1129. $stamp = $i * 86400;
  1130. $save_callback(date("D", $stamp), POTX_CONTEXT_NONE, $file);
  1131. $save_callback(date("l", $stamp), POTX_CONTEXT_NONE, $file);
  1132. }
  1133. $save_callback('am', POTX_CONTEXT_NONE, $file);
  1134. $save_callback('pm', POTX_CONTEXT_NONE, $file);
  1135. $save_callback('AM', POTX_CONTEXT_NONE, $file);
  1136. $save_callback('PM', POTX_CONTEXT_NONE, $file);
  1137. }
  1138. /**
  1139. * Add format_interval special strings, which cannot be
  1140. * extracted otherwise. This is called for common.inc
  1141. *
  1142. * @param $file
  1143. * Name of the file parsed.
  1144. * @param $save_callback
  1145. * Callback function used to save strings.
  1146. * @param $api_version
  1147. * Drupal API version to work with.
  1148. */
  1149. function _potx_add_format_interval_strings($file, $save_callback, $api_version = POTX_API_6) {
  1150. $components = array(
  1151. '1 year' => '@count years',
  1152. '1 week' => '@count weeks',
  1153. '1 day' => '@count days',
  1154. '1 hour' => '@count hours',
  1155. '1 min' => '@count min',
  1156. '1 sec' => '@count sec'
  1157. );
  1158. if ($api_version > POTX_API_6) {
  1159. // Month support added in Drupal 7.
  1160. $components['1 month'] = '@count months';
  1161. }
  1162. foreach ($components as $singular => $plural) {
  1163. // Intervals support no context.
  1164. $save_callback($singular ."\0". $plural, POTX_CONTEXT_NONE, $file);
  1165. }
  1166. }
  1167. /**
  1168. * Add default theme region names, which cannot be extracted otherwise.
  1169. * These default names are defined in system.module
  1170. *
  1171. * @param $file
  1172. * Name of the file parsed.
  1173. * @param $save_callback
  1174. * Callback function used to save strings.
  1175. * @param $api_version
  1176. * Drupal API version to work with.
  1177. */
  1178. function _potx_add_default_region_names($file, $save_callback, $api_version = POTX_API_6) {
  1179. $regions = array(
  1180. 'left' => 'Left sidebar',
  1181. 'right' => 'Right sidebar',
  1182. 'content' => 'Content',
  1183. 'header' => 'Header',
  1184. 'footer' => 'Footer',
  1185. );
  1186. if ($api_version > POTX_API_6) {
  1187. // @todo: Update with final region list when D7 stabilizes.
  1188. $regions['highlight'] = 'Highlighted content';
  1189. $regions['help'] = 'Help';
  1190. $regions['page_top'] = 'Page top';
  1191. }
  1192. foreach ($regions as $region) {
  1193. // Regions come with the default context.
  1194. $save_callback($region, POTX_CONTEXT_NONE, $file);
  1195. }
  1196. }
  1197. /**
  1198. * Parse an .info file and add relevant strings to the list.
  1199. *
  1200. * @param $file_path
  1201. * Complete file path to load contents with.
  1202. * @param $file_name
  1203. * Stripped file name to use in outpout.
  1204. * @param $strings
  1205. * Current strings array
  1206. * @param $api_version
  1207. * Drupal API version to work with.
  1208. */
  1209. function _potx_find_info_file_strings($file_path, $file_name, $save_callback, $api_version = POTX_API_6) {
  1210. $info = array();
  1211. if (file_exists($file_path)) {
  1212. $info = $api_version > POTX_API_5 ? drupal_parse_info_file($file_path) : parse_ini_file($file_path);
  1213. }
  1214. // We need the name, description and package values. Others,
  1215. // like core and PHP compatibility, timestamps or versions
  1216. // are not to be translated.
  1217. foreach (array('name', 'description', 'package') as $key) {
  1218. if (isset($info[$key])) {
  1219. // No context support for .info file strings.
  1220. $save_callback($info[$key], POTX_CONTEXT_NONE, $file_name);
  1221. }
  1222. }
  1223. // Add regions names from themes.
  1224. if (isset($info['regions']) && is_array($info['regions'])) {
  1225. foreach ($info['regions'] as $region => $region_name) {
  1226. // No context support for .info file strings.
  1227. $save_callback($region_name, POTX_CONTEXT_NONE, $file_name);
  1228. }
  1229. }
  1230. }
  1231. /**
  1232. * Parse a JavaScript file for translatables. Only from Drupal 6.
  1233. *
  1234. * Extracts strings wrapped in Drupal.t() and Drupal.formatPlural()
  1235. * calls and inserts them into potx storage.
  1236. *
  1237. * Regex code lifted from _locale_parse_js_file().
  1238. */
  1239. function _potx_parse_js_file($code, $file, $save_callback) {
  1240. $js_string_regex = '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+';
  1241. // Match all calls to Drupal.t() in an array.
  1242. // Note: \s also matches newlines with the 's' modifier.
  1243. preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*('. $js_string_regex .')\s*[,\)]~s', $code, $t_matches, PREG_SET_ORDER);
  1244. if (isset($t_matches) && count($t_matches)) {
  1245. foreach ($t_matches as $match) {
  1246. // Remove match from code to help us identify faulty Drupal.t() calls.
  1247. $code = str_replace($match[0], '', $code);
  1248. // @todo: figure out how to parse out context, once Drupal supports it.
  1249. $save_callback(_potx_parse_js_string($match[1]), POTX_CONTEXT_NONE, $file, 0);
  1250. }
  1251. }
  1252. // Match all Drupal.formatPlural() calls in another array.
  1253. 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);
  1254. if (isset($plural_matches) && count($plural_matches)) {
  1255. foreach ($plural_matches as $index => $match) {
  1256. // Remove match from code to help us identify faulty
  1257. // Drupal.formatPlural() calls later.
  1258. $code = str_replace($match[0], '', $code);
  1259. // @todo: figure out how to parseā€¦

Large files files are truncated, but you can click here to view the full file