Merge branch 'origin/master' into Weblate.
[phpmyadmin.git] / src / Transformations.php
blob9b58bf74c5db3a1cfd70fe264df6e0a77506000a
1 <?php
2 /**
3 * Set of functions used with the relation and pdf feature
5 * This file also provides basic functions to use in other plugins!
6 * These are declared in the 'GLOBAL Plugin functions' section
8 * Please use short and expressive names.
9 * For now, special characters which aren't allowed in
10 * filenames or functions should not be used.
12 * Please provide a comment for your function,
13 * what it does and what parameters are available.
16 declare(strict_types=1);
18 namespace PhpMyAdmin;
20 use PhpMyAdmin\ConfigStorage\Relation;
21 use PhpMyAdmin\Dbal\Connection;
22 use PhpMyAdmin\Plugins\TransformationsInterface;
24 use function array_shift;
25 use function class_exists;
26 use function closedir;
27 use function count;
28 use function explode;
29 use function ltrim;
30 use function mb_strtolower;
31 use function mb_substr;
32 use function opendir;
33 use function preg_match;
34 use function preg_replace;
35 use function readdir;
36 use function rtrim;
37 use function sort;
38 use function str_contains;
39 use function str_replace;
40 use function stripslashes;
41 use function strlen;
42 use function trim;
43 use function ucfirst;
44 use function ucwords;
46 /**
47 * Transformations class
49 class Transformations
51 /**
52 * Returns array of options from string with options separated by comma,
53 * removes quotes
55 * <code>
56 * getOptions("'option ,, quoted',abd,'2,3',");
57 * // array {
58 * // 'option ,, quoted',
59 * // 'abc',
60 * // '2,3',
61 * // '',
62 * // }
63 * </code>
65 * @param string $optionString comma separated options
67 * @return string[]
69 public function getOptions(string $optionString): array
71 if ($optionString === '') {
72 return [];
75 $transformOptions = explode(',', $optionString);
77 $result = [];
79 while (($option = array_shift($transformOptions)) !== null) {
80 $trimmed = trim($option);
81 if (strlen($trimmed) > 1 && $trimmed[0] == "'" && $trimmed[strlen($trimmed) - 1] == "'") {
82 // '...'
83 $option = mb_substr($trimmed, 1, -1);
84 } elseif (isset($trimmed[0]) && $trimmed[0] == "'") {
85 // '...,
86 $trimmed = ltrim($option);
87 $rtrimmed = '';
88 /** @infection-ignore-all */
89 while (($option = array_shift($transformOptions)) !== null) {
90 // ...,
91 $trimmed .= ',' . $option;
92 $rtrimmed = rtrim($trimmed);
93 if ($rtrimmed[strlen($rtrimmed) - 1] == "'") {
94 // ,...'
95 break;
99 $option = mb_substr($rtrimmed, 1, -1);
102 $result[] = stripslashes($option);
105 return $result;
109 * Gets all available MIME-types
111 * @return mixed[] array[mimetype], array[transformation]
113 * @staticvar array $stack
115 public function getAvailableMimeTypes(): array
117 static $stack = null;
119 if ($stack !== null) {
120 return $stack;
123 $stack = [];
124 $subDirs = ['Input/' => 'input_', 'Output/' => '', '' => ''];
126 foreach ($subDirs as $sd => $prefix) {
127 $handle = opendir(ROOT_PATH . 'src/Plugins/Transformations/' . $sd);
129 if (! $handle) {
130 $stack[$prefix . 'transformation'] = [];
131 $stack[$prefix . 'transformation_file'] = [];
132 continue;
135 $filestack = [];
136 while ($file = readdir($handle)) {
137 // Ignore hidden files
138 if ($file[0] === '.') {
139 continue;
142 // Ignore old plugins (.class in filename)
143 if (str_contains($file, '.class')) {
144 continue;
147 $filestack[] = $file;
150 closedir($handle);
151 sort($filestack);
153 foreach ($filestack as $file) {
154 if (preg_match('|^[^.].*_.*_.*\.php$|', $file)) {
155 // File contains transformation functions.
156 $parts = explode('_', str_replace('.php', '', $file));
157 $mimetype = $parts[0] . '/' . $parts[1];
158 $stack['mimetype'][$mimetype] = $mimetype;
160 $stack[$prefix . 'transformation'][] = $mimetype . ': ' . $parts[2];
161 $stack[$prefix . 'transformation_file'][] = $sd . $file;
162 if ($sd === '') {
163 $stack['input_transformation'][] = $mimetype . ': ' . $parts[2];
164 $stack['input_transformation_file'][] = $sd . $file;
166 } elseif (preg_match('|^[^.].*\.php$|', $file)) {
167 // File is a plain mimetype, no functions.
168 $base = str_replace('.php', '', $file);
170 if ($base !== 'global') {
171 $mimetype = str_replace('_', '/', $base);
172 $stack['mimetype'][$mimetype] = $mimetype;
173 $stack['empty_mimetype'][$mimetype] = $mimetype;
179 return $stack;
183 * Returns the class name of the transformation
185 * @param string $filename transformation file name
187 * @return string the class name of transformation
189 public function getClassName(string $filename): string
191 return 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr(explode('.php', $filename)[0], strlen('src/')));
195 * Returns the description of the transformation
197 * @param string $file transformation file
199 * @return string the description of the transformation
201 public function getDescription(string $file): string
203 $includeFile = 'src/Plugins/Transformations/' . $file;
204 /** @psalm-var class-string<TransformationsInterface> $className */
205 $className = $this->getClassName($includeFile);
206 if (class_exists($className)) {
207 return $className::getInfo();
210 return '';
214 * Returns the name of the transformation
216 * @param string $file transformation file
218 * @return string the name of the transformation
220 public function getName(string $file): string
222 $includeFile = 'src/Plugins/Transformations/' . $file;
223 /** @psalm-var class-string<TransformationsInterface> $className */
224 $className = $this->getClassName($includeFile);
225 if (class_exists($className)) {
226 return $className::getName();
229 return '';
233 * Fixups old MIME or transformation name to new one
235 * - applies some hardcoded fixups
236 * - adds spaces after _ and numbers
237 * - capitalizes words
238 * - removes back spaces
240 * @param string $value Value to fixup
242 public function fixUpMime(string $value): string
244 $value = str_replace(
245 ['jpeg', 'png'],
246 ['JPEG', 'PNG'],
247 $value,
250 return str_replace(
251 ' ',
253 ucwords(
254 (string) preg_replace('/([0-9_]+)/', '$1 ', $value),
260 * Gets the mimetypes for all columns of a table
262 * @param string $db the name of the db to check for
263 * @param string $table the name of the table to check for
264 * @param bool $strict whether to include only results having a mimetype set
265 * @param bool $fullName whether to use full column names as the key
267 * @psalm-return array<string, array{
268 * column_name: string,
269 * mimetype: string,
270 * transformation: string,
271 * transformation_options: string,
272 * input_transformation: string,
273 * input_transformation_options: string
274 * }>|null
276 public function getMime(string $db, string $table, bool $strict = false, bool $fullName = false): array|null
278 $dbi = DatabaseInterface::getInstance();
279 $relation = new Relation($dbi);
280 $browserTransformationFeature = $relation->getRelationParameters()->browserTransformationFeature;
281 if ($browserTransformationFeature === null) {
282 return null;
285 $comQry = '';
286 if ($fullName) {
287 $comQry .= 'SELECT CONCAT(`db_name`, \'.\', `table_name`, \'.\', `column_name`) AS column_name, ';
288 } else {
289 $comQry = 'SELECT `column_name`, ';
292 $comQry .= '`mimetype`, '
293 . '`transformation`, '
294 . '`transformation_options`, '
295 . '`input_transformation`, '
296 . '`input_transformation_options`'
297 . ' FROM ' . Util::backquote($browserTransformationFeature->database) . '.'
298 . Util::backquote($browserTransformationFeature->columnInfo)
299 . ' WHERE `db_name` = ' . $dbi->quoteString($db, Connection::TYPE_CONTROL)
300 . ' AND `table_name` = ' . $dbi->quoteString($table, Connection::TYPE_CONTROL)
301 . ' AND ( `mimetype` != \'\'' . (! $strict ?
302 ' OR `transformation` != \'\''
303 . ' OR `transformation_options` != \'\''
304 . ' OR `input_transformation` != \'\''
305 . ' OR `input_transformation_options` != \'\'' : '') . ')';
308 * @psalm-var array<string, array{
309 * column_name: string,
310 * mimetype: string,
311 * transformation: string,
312 * transformation_options: string,
313 * input_transformation: string,
314 * input_transformation_options: string
315 * }> $result
317 $result = $dbi->fetchResult($comQry, 'column_name', null, Connection::TYPE_CONTROL);
319 foreach ($result as $column => $values) {
320 // convert mimetype to new format (f.e. Text_Plain, etc)
321 $values['mimetype'] = $this->fixUpMime($values['mimetype']);
323 // For transformation of form
324 // output/image_jpeg__inline.inc.php
325 // extract dir part.
326 $dir = explode('/', $values['transformation']);
327 $subdir = '';
328 if (count($dir) === 2) {
329 $subdir = ucfirst($dir[0]) . '/';
330 $values['transformation'] = $dir[1];
333 $values['transformation'] = $this->fixUpMime($values['transformation']);
334 $values['transformation'] = $subdir . $values['transformation'];
335 $result[$column] = $values;
338 return $result;
342 * Set a single mimetype to a certain value.
344 * @param string $db the name of the db
345 * @param string $table the name of the table
346 * @param string $key the name of the column
347 * @param string $mimetype the mimetype of the column
348 * @param string $transformation the transformation of the column
349 * @param string $transformationOpts the transformation options of the column
350 * @param string $inputTransform the input transformation of the column
351 * @param string $inputTransformOpts the input transformation options of the column
352 * @param bool $forcedelete force delete, will erase any existing
353 * comments for this column
355 public function setMime(
356 string $db,
357 string $table,
358 string $key,
359 string $mimetype,
360 string $transformation,
361 string $transformationOpts,
362 string $inputTransform,
363 string $inputTransformOpts,
364 bool $forcedelete = false,
365 ): bool {
366 $dbi = DatabaseInterface::getInstance();
367 $relation = new Relation($dbi);
368 $browserTransformationFeature = $relation->getRelationParameters()->browserTransformationFeature;
369 if ($browserTransformationFeature === null) {
370 return false;
373 // lowercase mimetype & transformation
374 $mimetype = mb_strtolower($mimetype);
375 $transformation = mb_strtolower($transformation);
377 // Do we have any parameter to set?
378 $hasValue = (
379 strlen($mimetype) > 0 ||
380 strlen($transformation) > 0 ||
381 strlen($transformationOpts) > 0 ||
382 strlen($inputTransform) > 0 ||
383 strlen($inputTransformOpts) > 0
386 $testQry = '
387 SELECT `mimetype`,
388 `comment`
389 FROM ' . Util::backquote($browserTransformationFeature->database) . '.'
390 . Util::backquote($browserTransformationFeature->columnInfo) . '
391 WHERE `db_name` = ' . $dbi->quoteString($db, Connection::TYPE_CONTROL) . '
392 AND `table_name` = ' . $dbi->quoteString($table, Connection::TYPE_CONTROL) . '
393 AND `column_name` = ' . $dbi->quoteString($key, Connection::TYPE_CONTROL);
395 $testRs = $dbi->queryAsControlUser($testQry);
397 if ($testRs->numRows() > 0) {
398 $row = $testRs->fetchAssoc();
400 if (! $forcedelete && ($hasValue || strlen($row['comment']) > 0)) {
401 $updQuery = 'UPDATE '
402 . Util::backquote($browserTransformationFeature->database) . '.'
403 . Util::backquote($browserTransformationFeature->columnInfo)
404 . ' SET '
405 . '`mimetype` = '
406 . $dbi->quoteString($mimetype, Connection::TYPE_CONTROL) . ', '
407 . '`transformation` = '
408 . $dbi->quoteString($transformation, Connection::TYPE_CONTROL) . ', '
409 . '`transformation_options` = '
410 . $dbi->quoteString($transformationOpts, Connection::TYPE_CONTROL) . ', '
411 . '`input_transformation` = '
412 . $dbi->quoteString($inputTransform, Connection::TYPE_CONTROL) . ', '
413 . '`input_transformation_options` = '
414 . $dbi->quoteString($inputTransformOpts, Connection::TYPE_CONTROL);
415 } else {
416 $updQuery = 'DELETE FROM '
417 . Util::backquote($browserTransformationFeature->database)
418 . '.' . Util::backquote($browserTransformationFeature->columnInfo);
421 $updQuery .= '
422 WHERE `db_name` = ' . $dbi->quoteString($db, Connection::TYPE_CONTROL) . '
423 AND `table_name` = ' . $dbi->quoteString($table, Connection::TYPE_CONTROL) . '
424 AND `column_name` = ' . $dbi->quoteString($key, Connection::TYPE_CONTROL);
425 } elseif ($hasValue) {
426 $updQuery = 'INSERT INTO '
427 . Util::backquote($browserTransformationFeature->database)
428 . '.' . Util::backquote($browserTransformationFeature->columnInfo)
429 . ' (db_name, table_name, column_name, mimetype, '
430 . 'transformation, transformation_options, '
431 . 'input_transformation, input_transformation_options) '
432 . ' VALUES('
433 . $dbi->quoteString($db, Connection::TYPE_CONTROL) . ','
434 . $dbi->quoteString($table, Connection::TYPE_CONTROL) . ','
435 . $dbi->quoteString($key, Connection::TYPE_CONTROL) . ','
436 . $dbi->quoteString($mimetype, Connection::TYPE_CONTROL) . ','
437 . $dbi->quoteString($transformation, Connection::TYPE_CONTROL) . ','
438 . $dbi->quoteString($transformationOpts, Connection::TYPE_CONTROL) . ','
439 . $dbi->quoteString($inputTransform, Connection::TYPE_CONTROL) . ','
440 . $dbi->quoteString($inputTransformOpts, Connection::TYPE_CONTROL) . ')';
443 if (isset($updQuery)) {
444 return (bool) $dbi->queryAsControlUser($updQuery);
447 return false;
451 * GLOBAL Plugin functions
455 * Delete related transformation details
456 * after deleting database. table or column
458 * @param string $db Database name
459 * @param string $table Table name
460 * @param string $column Column name
462 public function clear(string $db, string $table = '', string $column = ''): bool
464 $dbi = DatabaseInterface::getInstance();
465 $relation = new Relation($dbi);
466 $browserTransformationFeature = $relation->getRelationParameters()->browserTransformationFeature;
467 if ($browserTransformationFeature === null) {
468 return false;
471 $deleteSql = 'DELETE FROM '
472 . Util::backquote($browserTransformationFeature->database) . '.'
473 . Util::backquote($browserTransformationFeature->columnInfo)
474 . ' WHERE ';
476 if (($column != '') && ($table != '')) {
477 $deleteSql .= '`db_name` = \'' . $db . '\' AND '
478 . '`table_name` = \'' . $table . '\' AND '
479 . '`column_name` = \'' . $column . '\' ';
480 } elseif ($table != '') {
481 $deleteSql .= '`db_name` = \'' . $db . '\' AND '
482 . '`table_name` = \'' . $table . '\' ';
483 } else {
484 $deleteSql .= '`db_name` = \'' . $db . '\' ';
487 return (bool) $dbi->tryQuery($deleteSql);