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; |
|
|
|
|
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
|
|
|
|
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths