ExportLatex.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Set of methods used to build dumps of tables as Latex
  5. *
  6. * @package PhpMyAdmin-Export
  7. * @subpackage Latex
  8. */
  9. namespace PhpMyAdmin\Plugins\Export;
  10. use PhpMyAdmin\DatabaseInterface;
  11. use PhpMyAdmin\Export;
  12. use PhpMyAdmin\Plugins\ExportPlugin;
  13. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  14. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
  15. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
  16. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  17. use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
  18. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  19. use PhpMyAdmin\Relation;
  20. use PhpMyAdmin\Transformations;
  21. use PhpMyAdmin\Util;
  22. /**
  23. * Handles the export for the Latex format
  24. *
  25. * @package PhpMyAdmin-Export
  26. * @subpackage Latex
  27. */
  28. class ExportLatex extends ExportPlugin
  29. {
  30. /**
  31. * Constructor
  32. */
  33. public function __construct()
  34. {
  35. parent::__construct();
  36. // initialize the specific export sql variables
  37. $this->initSpecificVariables();
  38. $this->setProperties();
  39. }
  40. /**
  41. * Initialize the local variables that are used for export Latex
  42. *
  43. * @return void
  44. */
  45. protected function initSpecificVariables()
  46. {
  47. /* Messages used in default captions */
  48. $GLOBALS['strLatexContent'] = __('Content of table @TABLE@');
  49. $GLOBALS['strLatexContinued'] = __('(continued)');
  50. $GLOBALS['strLatexStructure'] = __('Structure of table @TABLE@');
  51. }
  52. /**
  53. * Sets the export Latex properties
  54. *
  55. * @return void
  56. */
  57. protected function setProperties()
  58. {
  59. global $plugin_param;
  60. $hide_structure = false;
  61. if ($plugin_param['export_type'] == 'table'
  62. && !$plugin_param['single_table']
  63. ) {
  64. $hide_structure = true;
  65. }
  66. $exportPluginProperties = new ExportPluginProperties();
  67. $exportPluginProperties->setText('LaTeX');
  68. $exportPluginProperties->setExtension('tex');
  69. $exportPluginProperties->setMimeType('application/x-tex');
  70. $exportPluginProperties->setOptionsText(__('Options'));
  71. // create the root group that will be the options field for
  72. // $exportPluginProperties
  73. // this will be shown as "Format specific options"
  74. $exportSpecificOptions = new OptionsPropertyRootGroup(
  75. "Format Specific Options"
  76. );
  77. // general options main group
  78. $generalOptions = new OptionsPropertyMainGroup("general_opts");
  79. // create primary items and add them to the group
  80. $leaf = new BoolPropertyItem(
  81. "caption",
  82. __('Include table caption')
  83. );
  84. $generalOptions->addProperty($leaf);
  85. // add the main group to the root group
  86. $exportSpecificOptions->addProperty($generalOptions);
  87. // what to dump (structure/data/both) main group
  88. $dumpWhat = new OptionsPropertyMainGroup(
  89. "dump_what", __('Dump table')
  90. );
  91. // create primary items and add them to the group
  92. $leaf = new RadioPropertyItem("structure_or_data");
  93. $leaf->setValues(
  94. array(
  95. 'structure' => __('structure'),
  96. 'data' => __('data'),
  97. 'structure_and_data' => __('structure and data'),
  98. )
  99. );
  100. $dumpWhat->addProperty($leaf);
  101. // add the main group to the root group
  102. $exportSpecificOptions->addProperty($dumpWhat);
  103. // structure options main group
  104. if (!$hide_structure) {
  105. $structureOptions = new OptionsPropertyMainGroup(
  106. "structure", __('Object creation options')
  107. );
  108. $structureOptions->setForce('data');
  109. // create primary items and add them to the group
  110. $leaf = new TextPropertyItem(
  111. "structure_caption",
  112. __('Table caption:')
  113. );
  114. $leaf->setDoc('faq6-27');
  115. $structureOptions->addProperty($leaf);
  116. $leaf = new TextPropertyItem(
  117. "structure_continued_caption",
  118. __('Table caption (continued):')
  119. );
  120. $leaf->setDoc('faq6-27');
  121. $structureOptions->addProperty($leaf);
  122. $leaf = new TextPropertyItem(
  123. "structure_label",
  124. __('Label key:')
  125. );
  126. $leaf->setDoc('faq6-27');
  127. $structureOptions->addProperty($leaf);
  128. if (!empty($GLOBALS['cfgRelation']['relation'])) {
  129. $leaf = new BoolPropertyItem(
  130. "relation",
  131. __('Display foreign key relationships')
  132. );
  133. $structureOptions->addProperty($leaf);
  134. }
  135. $leaf = new BoolPropertyItem(
  136. "comments",
  137. __('Display comments')
  138. );
  139. $structureOptions->addProperty($leaf);
  140. if (!empty($GLOBALS['cfgRelation']['mimework'])) {
  141. $leaf = new BoolPropertyItem(
  142. "mime",
  143. __('Display MIME types')
  144. );
  145. $structureOptions->addProperty($leaf);
  146. }
  147. // add the main group to the root group
  148. $exportSpecificOptions->addProperty($structureOptions);
  149. }
  150. // data options main group
  151. $dataOptions = new OptionsPropertyMainGroup(
  152. "data", __('Data dump options')
  153. );
  154. $dataOptions->setForce('structure');
  155. // create primary items and add them to the group
  156. $leaf = new BoolPropertyItem(
  157. "columns",
  158. __('Put columns names in the first row:')
  159. );
  160. $dataOptions->addProperty($leaf);
  161. $leaf = new TextPropertyItem(
  162. "data_caption",
  163. __('Table caption:')
  164. );
  165. $leaf->setDoc('faq6-27');
  166. $dataOptions->addProperty($leaf);
  167. $leaf = new TextPropertyItem(
  168. "data_continued_caption",
  169. __('Table caption (continued):')
  170. );
  171. $leaf->setDoc('faq6-27');
  172. $dataOptions->addProperty($leaf);
  173. $leaf = new TextPropertyItem(
  174. "data_label",
  175. __('Label key:')
  176. );
  177. $leaf->setDoc('faq6-27');
  178. $dataOptions->addProperty($leaf);
  179. $leaf = new TextPropertyItem(
  180. 'null',
  181. __('Replace NULL with:')
  182. );
  183. $dataOptions->addProperty($leaf);
  184. // add the main group to the root group
  185. $exportSpecificOptions->addProperty($dataOptions);
  186. // set the options for the export plugin property item
  187. $exportPluginProperties->setOptions($exportSpecificOptions);
  188. $this->properties = $exportPluginProperties;
  189. }
  190. /**
  191. * Outputs export header
  192. *
  193. * @return bool Whether it succeeded
  194. */
  195. public function exportHeader()
  196. {
  197. global $crlf;
  198. global $cfg;
  199. $head = '% phpMyAdmin LaTeX Dump' . $crlf
  200. . '% version ' . PMA_VERSION . $crlf
  201. . '% https://www.phpmyadmin.net/' . $crlf
  202. . '%' . $crlf
  203. . '% ' . __('Host:') . ' ' . $cfg['Server']['host'];
  204. if (!empty($cfg['Server']['port'])) {
  205. $head .= ':' . $cfg['Server']['port'];
  206. }
  207. $head .= $crlf
  208. . '% ' . __('Generation Time:') . ' '
  209. . Util::localisedDate() . $crlf
  210. . '% ' . __('Server version:') . ' ' . $GLOBALS['dbi']->getVersionString() . $crlf
  211. . '% ' . __('PHP Version:') . ' ' . phpversion() . $crlf;
  212. return Export::outputHandler($head);
  213. }
  214. /**
  215. * Outputs export footer
  216. *
  217. * @return bool Whether it succeeded
  218. */
  219. public function exportFooter()
  220. {
  221. return true;
  222. }
  223. /**
  224. * Outputs database header
  225. *
  226. * @param string $db Database name
  227. * @param string $db_alias Aliases of db
  228. *
  229. * @return bool Whether it succeeded
  230. */
  231. public function exportDBHeader($db, $db_alias = '')
  232. {
  233. if (empty($db_alias)) {
  234. $db_alias = $db;
  235. }
  236. global $crlf;
  237. $head = '% ' . $crlf
  238. . '% ' . __('Database:') . ' ' . '\'' . $db_alias . '\'' . $crlf
  239. . '% ' . $crlf;
  240. return Export::outputHandler($head);
  241. }
  242. /**
  243. * Outputs database footer
  244. *
  245. * @param string $db Database name
  246. *
  247. * @return bool Whether it succeeded
  248. */
  249. public function exportDBFooter($db)
  250. {
  251. return true;
  252. }
  253. /**
  254. * Outputs CREATE DATABASE statement
  255. *
  256. * @param string $db Database name
  257. * @param string $export_type 'server', 'database', 'table'
  258. * @param string $db_alias Aliases of db
  259. *
  260. * @return bool Whether it succeeded
  261. */
  262. public function exportDBCreate($db, $export_type, $db_alias = '')
  263. {
  264. return true;
  265. }
  266. /**
  267. * Outputs the content of a table in JSON format
  268. *
  269. * @param string $db database name
  270. * @param string $table table name
  271. * @param string $crlf the end of line sequence
  272. * @param string $error_url the url to go back in case of error
  273. * @param string $sql_query SQL query for obtaining data
  274. * @param array $aliases Aliases of db/table/columns
  275. *
  276. * @return bool Whether it succeeded
  277. */
  278. public function exportData(
  279. $db,
  280. $table,
  281. $crlf,
  282. $error_url,
  283. $sql_query,
  284. array $aliases = array()
  285. ) {
  286. $db_alias = $db;
  287. $table_alias = $table;
  288. $this->initAlias($aliases, $db_alias, $table_alias);
  289. $result = $GLOBALS['dbi']->tryQuery(
  290. $sql_query,
  291. DatabaseInterface::CONNECT_USER,
  292. DatabaseInterface::QUERY_UNBUFFERED
  293. );
  294. $columns_cnt = $GLOBALS['dbi']->numFields($result);
  295. $columns = array();
  296. $columns_alias = array();
  297. for ($i = 0; $i < $columns_cnt; $i++) {
  298. $columns[$i] = $col_as = $GLOBALS['dbi']->fieldName($result, $i);
  299. if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  300. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  301. }
  302. $columns_alias[$i] = $col_as;
  303. }
  304. $buffer = $crlf . '%' . $crlf . '% ' . __('Data:') . ' ' . $table_alias
  305. . $crlf . '%' . $crlf . ' \\begin{longtable}{|';
  306. for ($index = 0; $index < $columns_cnt; $index++) {
  307. $buffer .= 'l|';
  308. }
  309. $buffer .= '} ' . $crlf;
  310. $buffer .= ' \\hline \\endhead \\hline \\endfoot \\hline ' . $crlf;
  311. if (isset($GLOBALS['latex_caption'])) {
  312. $buffer .= ' \\caption{'
  313. . Util::expandUserString(
  314. $GLOBALS['latex_data_caption'],
  315. array(
  316. 'texEscape',
  317. get_class($this),
  318. ),
  319. array('table' => $table_alias, 'database' => $db_alias)
  320. )
  321. . '} \\label{'
  322. . Util::expandUserString(
  323. $GLOBALS['latex_data_label'],
  324. null,
  325. array('table' => $table_alias, 'database' => $db_alias)
  326. )
  327. . '} \\\\';
  328. }
  329. if (!Export::outputHandler($buffer)) {
  330. return false;
  331. }
  332. // show column names
  333. if (isset($GLOBALS['latex_columns'])) {
  334. $buffer = '\\hline ';
  335. for ($i = 0; $i < $columns_cnt; $i++) {
  336. $buffer .= '\\multicolumn{1}{|c|}{\\textbf{'
  337. . self::texEscape(stripslashes($columns_alias[$i])) . '}} & ';
  338. }
  339. $buffer = mb_substr($buffer, 0, -2) . '\\\\ \\hline \hline ';
  340. if (!Export::outputHandler($buffer . ' \\endfirsthead ' . $crlf)) {
  341. return false;
  342. }
  343. if (isset($GLOBALS['latex_caption'])) {
  344. if (!Export::outputHandler(
  345. '\\caption{'
  346. . Util::expandUserString(
  347. $GLOBALS['latex_data_continued_caption'],
  348. array(
  349. 'texEscape',
  350. get_class($this),
  351. ),
  352. array('table' => $table_alias, 'database' => $db_alias)
  353. )
  354. . '} \\\\ '
  355. )
  356. ) {
  357. return false;
  358. }
  359. }
  360. if (!Export::outputHandler($buffer . '\\endhead \\endfoot' . $crlf)) {
  361. return false;
  362. }
  363. } else {
  364. if (!Export::outputHandler('\\\\ \hline')) {
  365. return false;
  366. }
  367. }
  368. // print the whole table
  369. while ($record = $GLOBALS['dbi']->fetchAssoc($result)) {
  370. $buffer = '';
  371. // print each row
  372. for ($i = 0; $i < $columns_cnt; $i++) {
  373. if ((!function_exists('is_null')
  374. || !is_null($record[$columns[$i]]))
  375. && isset($record[$columns[$i]])
  376. ) {
  377. $column_value = self::texEscape(
  378. stripslashes($record[$columns[$i]])
  379. );
  380. } else {
  381. $column_value = $GLOBALS['latex_null'];
  382. }
  383. // last column ... no need for & character
  384. if ($i == ($columns_cnt - 1)) {
  385. $buffer .= $column_value;
  386. } else {
  387. $buffer .= $column_value . " & ";
  388. }
  389. }
  390. $buffer .= ' \\\\ \\hline ' . $crlf;
  391. if (!Export::outputHandler($buffer)) {
  392. return false;
  393. }
  394. }
  395. $buffer = ' \\end{longtable}' . $crlf;
  396. if (!Export::outputHandler($buffer)) {
  397. return false;
  398. }
  399. $GLOBALS['dbi']->freeResult($result);
  400. return true;
  401. } // end getTableLaTeX
  402. /**
  403. * Outputs table's structure
  404. *
  405. * @param string $db database name
  406. * @param string $table table name
  407. * @param string $crlf the end of line sequence
  408. * @param string $error_url the url to go back in case of error
  409. * @param string $export_mode 'create_table', 'triggers', 'create_view',
  410. * 'stand_in'
  411. * @param string $export_type 'server', 'database', 'table'
  412. * @param bool $do_relation whether to include relation comments
  413. * @param bool $do_comments whether to include the pmadb-style column
  414. * comments as comments in the structure;
  415. * this is deprecated but the parameter is
  416. * left here because export.php calls
  417. * exportStructure() also for other
  418. * export types which use this parameter
  419. * @param bool $do_mime whether to include mime comments
  420. * @param bool $dates whether to include creation/update/check dates
  421. * @param array $aliases Aliases of db/table/columns
  422. *
  423. * @return bool Whether it succeeded
  424. */
  425. public function exportStructure(
  426. $db,
  427. $table,
  428. $crlf,
  429. $error_url,
  430. $export_mode,
  431. $export_type,
  432. $do_relation = false,
  433. $do_comments = false,
  434. $do_mime = false,
  435. $dates = false,
  436. array $aliases = array()
  437. ) {
  438. $db_alias = $db;
  439. $table_alias = $table;
  440. $this->initAlias($aliases, $db_alias, $table_alias);
  441. global $cfgRelation;
  442. /* We do not export triggers */
  443. if ($export_mode == 'triggers') {
  444. return true;
  445. }
  446. /**
  447. * Get the unique keys in the table
  448. */
  449. $unique_keys = array();
  450. $keys = $GLOBALS['dbi']->getTableIndexes($db, $table);
  451. foreach ($keys as $key) {
  452. if ($key['Non_unique'] == 0) {
  453. $unique_keys[] = $key['Column_name'];
  454. }
  455. }
  456. /**
  457. * Gets fields properties
  458. */
  459. $GLOBALS['dbi']->selectDb($db);
  460. // Check if we can use Relations
  461. list($res_rel, $have_rel) = $this->relation->getRelationsAndStatus(
  462. $do_relation && !empty($cfgRelation['relation']),
  463. $db,
  464. $table
  465. );
  466. /**
  467. * Displays the table structure
  468. */
  469. $buffer = $crlf . '%' . $crlf . '% ' . __('Structure:') . ' '
  470. . $table_alias . $crlf . '%' . $crlf . ' \\begin{longtable}{';
  471. if (!Export::outputHandler($buffer)) {
  472. return false;
  473. }
  474. $alignment = '|l|c|c|c|';
  475. if ($do_relation && $have_rel) {
  476. $alignment .= 'l|';
  477. }
  478. if ($do_comments) {
  479. $alignment .= 'l|';
  480. }
  481. if ($do_mime && $cfgRelation['mimework']) {
  482. $alignment .= 'l|';
  483. }
  484. $buffer = $alignment . '} ' . $crlf;
  485. $header = ' \\hline ';
  486. $header .= '\\multicolumn{1}{|c|}{\\textbf{' . __('Column')
  487. . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Type')
  488. . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Null')
  489. . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Default') . '}}';
  490. if ($do_relation && $have_rel) {
  491. $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Links to') . '}}';
  492. }
  493. if ($do_comments) {
  494. $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Comments') . '}}';
  495. $comments = $this->relation->getComments($db, $table);
  496. }
  497. if ($do_mime && $cfgRelation['mimework']) {
  498. $header .= ' & \\multicolumn{1}{|c|}{\\textbf{MIME}}';
  499. $mime_map = Transformations::getMIME($db, $table, true);
  500. }
  501. // Table caption for first page and label
  502. if (isset($GLOBALS['latex_caption'])) {
  503. $buffer .= ' \\caption{'
  504. . Util::expandUserString(
  505. $GLOBALS['latex_structure_caption'],
  506. array(
  507. 'texEscape',
  508. get_class($this),
  509. ),
  510. array('table' => $table_alias, 'database' => $db_alias)
  511. )
  512. . '} \\label{'
  513. . Util::expandUserString(
  514. $GLOBALS['latex_structure_label'],
  515. null,
  516. array('table' => $table_alias, 'database' => $db_alias)
  517. )
  518. . '} \\\\' . $crlf;
  519. }
  520. $buffer .= $header . ' \\\\ \\hline \\hline' . $crlf
  521. . '\\endfirsthead' . $crlf;
  522. // Table caption on next pages
  523. if (isset($GLOBALS['latex_caption'])) {
  524. $buffer .= ' \\caption{'
  525. . Util::expandUserString(
  526. $GLOBALS['latex_structure_continued_caption'],
  527. array(
  528. 'texEscape',
  529. get_class($this),
  530. ),
  531. array('table' => $table_alias, 'database' => $db_alias)
  532. )
  533. . '} \\\\ ' . $crlf;
  534. }
  535. $buffer .= $header . ' \\\\ \\hline \\hline \\endhead \\endfoot ' . $crlf;
  536. if (!Export::outputHandler($buffer)) {
  537. return false;
  538. }
  539. $fields = $GLOBALS['dbi']->getColumns($db, $table);
  540. foreach ($fields as $row) {
  541. $extracted_columnspec = Util::extractColumnSpec($row['Type']);
  542. $type = $extracted_columnspec['print_type'];
  543. if (empty($type)) {
  544. $type = ' ';
  545. }
  546. if (!isset($row['Default'])) {
  547. if ($row['Null'] != 'NO') {
  548. $row['Default'] = 'NULL';
  549. }
  550. }
  551. $field_name = $col_as = $row['Field'];
  552. if (!empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  553. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  554. }
  555. $local_buffer = $col_as . "\000" . $type . "\000"
  556. . (($row['Null'] == '' || $row['Null'] == 'NO')
  557. ? __('No') : __('Yes'))
  558. . "\000" . (isset($row['Default']) ? $row['Default'] : '');
  559. if ($do_relation && $have_rel) {
  560. $local_buffer .= "\000";
  561. $local_buffer .= $this->getRelationString(
  562. $res_rel,
  563. $field_name,
  564. $db,
  565. $aliases
  566. );
  567. }
  568. if ($do_comments && $cfgRelation['commwork']) {
  569. $local_buffer .= "\000";
  570. if (isset($comments[$field_name])) {
  571. $local_buffer .= $comments[$field_name];
  572. }
  573. }
  574. if ($do_mime && $cfgRelation['mimework']) {
  575. $local_buffer .= "\000";
  576. if (isset($mime_map[$field_name])) {
  577. $local_buffer .= str_replace(
  578. '_',
  579. '/',
  580. $mime_map[$field_name]['mimetype']
  581. );
  582. }
  583. }
  584. $local_buffer = self::texEscape($local_buffer);
  585. if ($row['Key'] == 'PRI') {
  586. $pos = mb_strpos($local_buffer, "\000");
  587. $local_buffer = '\\textit{'
  588. .
  589. mb_substr($local_buffer, 0, $pos)
  590. . '}' .
  591. mb_substr($local_buffer, $pos);
  592. }
  593. if (in_array($field_name, $unique_keys)) {
  594. $pos = mb_strpos($local_buffer, "\000");
  595. $local_buffer = '\\textbf{'
  596. .
  597. mb_substr($local_buffer, 0, $pos)
  598. . '}' .
  599. mb_substr($local_buffer, $pos);
  600. }
  601. $buffer = str_replace("\000", ' & ', $local_buffer);
  602. $buffer .= ' \\\\ \\hline ' . $crlf;
  603. if (!Export::outputHandler($buffer)) {
  604. return false;
  605. }
  606. } // end while
  607. $buffer = ' \\end{longtable}' . $crlf;
  608. return Export::outputHandler($buffer);
  609. } // end of the 'exportStructure' method
  610. /**
  611. * Escapes some special characters for use in TeX/LaTeX
  612. *
  613. * @param string $string the string to convert
  614. *
  615. * @return string the converted string with escape codes
  616. */
  617. public static function texEscape($string)
  618. {
  619. $escape = array('$', '%', '{', '}', '&', '#', '_', '^');
  620. $cnt_escape = count($escape);
  621. for ($k = 0; $k < $cnt_escape; $k++) {
  622. $string = str_replace($escape[$k], '\\' . $escape[$k], $string);
  623. }
  624. return $string;
  625. }
  626. }