Passed
Push — master ( 37473d...30ff7d )
by Maurício
11:30 queued 14s
created

ExportLatex::exportStructure()   F

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 190
Code Lines 105

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 105
CRAP Score 36.122

Importance

Changes 0
Metric Value
cc 36
eloc 105
nc 2655748
nop 10
dl 0
loc 190
rs 0
c 0
b 0
f 0
ccs 105
cts 110
cp 0.9545
crap 36.122

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Set of methods used to build dumps of tables as Latex
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\Plugins\Export;
9
10
use PhpMyAdmin\DatabaseInterface;
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\DatabaseInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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