PageRenderTime 43ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/classes/Plugins.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 617 lines | 421 code | 72 blank | 124 comment | 65 complexity | 507d2e041bc8e133326874fbafedbff9 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use FilesystemIterator;
  5. use PhpMyAdmin\Html\MySQLDocumentation;
  6. use PhpMyAdmin\Plugins\AuthenticationPlugin;
  7. use PhpMyAdmin\Plugins\ExportPlugin;
  8. use PhpMyAdmin\Plugins\ImportPlugin;
  9. use PhpMyAdmin\Plugins\Plugin;
  10. use PhpMyAdmin\Plugins\SchemaPlugin;
  11. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
  12. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  13. use PhpMyAdmin\Properties\Options\Items\DocPropertyItem;
  14. use PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem;
  15. use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
  16. use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
  17. use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
  18. use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
  19. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  20. use PhpMyAdmin\Properties\Options\OptionsPropertyItem;
  21. use SplFileInfo;
  22. use Throwable;
  23. use function __;
  24. use function class_exists;
  25. use function count;
  26. use function get_class;
  27. use function htmlspecialchars;
  28. use function mb_strpos;
  29. use function mb_strtolower;
  30. use function mb_strtoupper;
  31. use function mb_substr;
  32. use function method_exists;
  33. use function preg_match_all;
  34. use function sprintf;
  35. use function str_replace;
  36. use function str_starts_with;
  37. use function strcasecmp;
  38. use function strcmp;
  39. use function strtolower;
  40. use function ucfirst;
  41. use function usort;
  42. class Plugins
  43. {
  44. /**
  45. * Instantiates the specified plugin type for a certain format
  46. *
  47. * @param string $type the type of the plugin (import, export, etc)
  48. * @param string $format the format of the plugin (sql, xml, et )
  49. * @param array|string|null $param parameter to plugin by which they can decide whether they can work
  50. * @psalm-param array{export_type: string, single_table: bool}|string|null $param
  51. *
  52. * @return object|null new plugin instance
  53. */
  54. public static function getPlugin(string $type, string $format, $param = null): ?object
  55. {
  56. global $plugin_param;
  57. $plugin_param = $param;
  58. $pluginType = mb_strtoupper($type[0]) . mb_strtolower(mb_substr($type, 1));
  59. $pluginFormat = mb_strtoupper($format[0]) . mb_strtolower(mb_substr($format, 1));
  60. $class = sprintf('PhpMyAdmin\\Plugins\\%s\\%s%s', $pluginType, $pluginType, $pluginFormat);
  61. if (! class_exists($class)) {
  62. return null;
  63. }
  64. return new $class();
  65. }
  66. /**
  67. * @param string $type server|database|table|raw
  68. *
  69. * @return ExportPlugin[]
  70. */
  71. public static function getExport(string $type, bool $singleTable): array
  72. {
  73. global $plugin_param;
  74. $plugin_param = ['export_type' => $type, 'single_table' => $singleTable];
  75. return self::getPlugins('Export');
  76. }
  77. /**
  78. * @param string $type server|database|table
  79. *
  80. * @return ImportPlugin[]
  81. */
  82. public static function getImport(string $type): array
  83. {
  84. global $plugin_param;
  85. $plugin_param = $type;
  86. return self::getPlugins('Import');
  87. }
  88. /**
  89. * @return SchemaPlugin[]
  90. */
  91. public static function getSchema(): array
  92. {
  93. return self::getPlugins('Schema');
  94. }
  95. /**
  96. * Reads all plugin information
  97. *
  98. * @param string $type the type of the plugin (import, export, etc)
  99. *
  100. * @return array list of plugin instances
  101. */
  102. private static function getPlugins(string $type): array
  103. {
  104. try {
  105. $files = new FilesystemIterator(ROOT_PATH . 'libraries/classes/Plugins/' . $type);
  106. } catch (Throwable $e) {
  107. return [];
  108. }
  109. $plugins = [];
  110. /** @var SplFileInfo $fileInfo */
  111. foreach ($files as $fileInfo) {
  112. if (! $fileInfo->isReadable() || ! $fileInfo->isFile() || $fileInfo->getExtension() !== 'php') {
  113. continue;
  114. }
  115. if (! str_starts_with($fileInfo->getFilename(), $type)) {
  116. continue;
  117. }
  118. $class = sprintf('PhpMyAdmin\\Plugins\\%s\\%s', $type, $fileInfo->getBasename('.php'));
  119. if (! class_exists($class)) {
  120. continue;
  121. }
  122. $plugin = new $class();
  123. if (! ($plugin instanceof Plugin) || ! $plugin->isAvailable()) {
  124. continue;
  125. }
  126. $plugins[] = $plugin;
  127. }
  128. usort($plugins, static function (Plugin $plugin1, Plugin $plugin2): int {
  129. return strcasecmp($plugin1->getProperties()->getText(), $plugin2->getProperties()->getText());
  130. });
  131. return $plugins;
  132. }
  133. /**
  134. * Returns locale string for $name or $name if no locale is found
  135. *
  136. * @param string|null $name for local string
  137. *
  138. * @return string locale string for $name
  139. */
  140. public static function getString($name)
  141. {
  142. return $GLOBALS[$name] ?? $name ?? '';
  143. }
  144. /**
  145. * Returns html input tag option 'checked' if plugin $opt
  146. * should be set by config or request
  147. *
  148. * @param string $section name of config section in
  149. * $GLOBALS['cfg'][$section] for plugin
  150. * @param string $opt name of option
  151. * @psalm-param 'Export'|'Import'|'Schema' $section
  152. *
  153. * @return string html input tag option 'checked'
  154. */
  155. public static function checkboxCheck($section, $opt)
  156. {
  157. // If the form is being repopulated using $_GET data, that is priority
  158. if (
  159. isset($_GET[$opt])
  160. || ! isset($_GET['repopulate'])
  161. && ((! empty($GLOBALS['timeout_passed']) && isset($_REQUEST[$opt]))
  162. || ! empty($GLOBALS['cfg'][$section][$opt]))
  163. ) {
  164. return ' checked="checked"';
  165. }
  166. return '';
  167. }
  168. /**
  169. * Returns default value for option $opt
  170. *
  171. * @param string $section name of config section in
  172. * $GLOBALS['cfg'][$section] for plugin
  173. * @param string $opt name of option
  174. * @psalm-param 'Export'|'Import'|'Schema' $section
  175. *
  176. * @return string default value for option $opt
  177. */
  178. public static function getDefault($section, $opt)
  179. {
  180. if (isset($_GET[$opt])) {
  181. // If the form is being repopulated using $_GET data, that is priority
  182. return htmlspecialchars($_GET[$opt]);
  183. }
  184. if (isset($GLOBALS['timeout_passed'], $_REQUEST[$opt]) && $GLOBALS['timeout_passed']) {
  185. return htmlspecialchars($_REQUEST[$opt]);
  186. }
  187. if (! isset($GLOBALS['cfg'][$section][$opt])) {
  188. return '';
  189. }
  190. $matches = [];
  191. /* Possibly replace localised texts */
  192. if (! preg_match_all('/(str[A-Z][A-Za-z0-9]*)/', (string) $GLOBALS['cfg'][$section][$opt], $matches)) {
  193. return htmlspecialchars((string) $GLOBALS['cfg'][$section][$opt]);
  194. }
  195. $val = $GLOBALS['cfg'][$section][$opt];
  196. foreach ($matches[0] as $match) {
  197. if (! isset($GLOBALS[$match])) {
  198. continue;
  199. }
  200. $val = str_replace($match, $GLOBALS[$match], $val);
  201. }
  202. return htmlspecialchars($val);
  203. }
  204. /**
  205. * @param ExportPlugin[]|ImportPlugin[]|SchemaPlugin[] $list
  206. *
  207. * @return array<int, array<string, bool|string>>
  208. * @psalm-return list<array{name: non-empty-lowercase-string, text: string, is_selected: bool, force_file: bool}>
  209. */
  210. public static function getChoice(array $list, string $default): array
  211. {
  212. $return = [];
  213. foreach ($list as $plugin) {
  214. $pluginName = $plugin->getName();
  215. $properties = $plugin->getProperties();
  216. $return[] = [
  217. 'name' => $pluginName,
  218. 'text' => self::getString($properties->getText()),
  219. 'is_selected' => $pluginName === $default,
  220. 'force_file' => $properties->getForceFile(),
  221. ];
  222. }
  223. return $return;
  224. }
  225. /**
  226. * Returns single option in a list element
  227. *
  228. * @param string $section name of config section in $GLOBALS['cfg'][$section] for plugin
  229. * @param string $plugin_name unique plugin name
  230. * @param OptionsPropertyItem $propertyGroup options property main group instance
  231. * @param bool $is_subgroup if this group is a subgroup
  232. * @psalm-param 'Export'|'Import'|'Schema' $section
  233. *
  234. * @return string table row with option
  235. */
  236. public static function getOneOption(
  237. $section,
  238. $plugin_name,
  239. &$propertyGroup,
  240. $is_subgroup = false
  241. ) {
  242. $ret = "\n";
  243. $properties = null;
  244. if (! $is_subgroup) {
  245. // for subgroup headers
  246. if (mb_strpos(get_class($propertyGroup), 'PropertyItem')) {
  247. $properties = [$propertyGroup];
  248. } else {
  249. // for main groups
  250. $ret .= '<div class="export_sub_options" id="' . $plugin_name . '_'
  251. . $propertyGroup->getName() . '">';
  252. $text = null;
  253. if (method_exists($propertyGroup, 'getText')) {
  254. $text = $propertyGroup->getText();
  255. }
  256. if ($text != null) {
  257. $ret .= '<h4>' . self::getString($text) . '</h4>';
  258. }
  259. $ret .= '<ul>';
  260. }
  261. }
  262. if (! isset($properties)) {
  263. $not_subgroup_header = true;
  264. if (method_exists($propertyGroup, 'getProperties')) {
  265. $properties = $propertyGroup->getProperties();
  266. }
  267. }
  268. $property_class = null;
  269. if (isset($properties)) {
  270. /** @var OptionsPropertySubgroup $propertyItem */
  271. foreach ($properties as $propertyItem) {
  272. $property_class = get_class($propertyItem);
  273. // if the property is a subgroup, we deal with it recursively
  274. if (mb_strpos($property_class, 'Subgroup')) {
  275. // for subgroups
  276. // each subgroup can have a header, which may also be a form element
  277. /** @var OptionsPropertyItem|null $subgroup_header */
  278. $subgroup_header = $propertyItem->getSubgroupHeader();
  279. if ($subgroup_header !== null) {
  280. $ret .= self::getOneOption($section, $plugin_name, $subgroup_header);
  281. }
  282. $ret .= '<li class="subgroup"><ul';
  283. if ($subgroup_header !== null) {
  284. $ret .= ' id="ul_' . $subgroup_header->getName() . '">';
  285. } else {
  286. $ret .= '>';
  287. }
  288. $ret .= self::getOneOption($section, $plugin_name, $propertyItem, true);
  289. continue;
  290. }
  291. // single property item
  292. $ret .= self::getHtmlForProperty($section, $plugin_name, $propertyItem);
  293. }
  294. }
  295. if ($is_subgroup) {
  296. // end subgroup
  297. $ret .= '</ul></li>';
  298. } else {
  299. // end main group
  300. if (! empty($not_subgroup_header)) {
  301. $ret .= '</ul></div>';
  302. }
  303. }
  304. if (method_exists($propertyGroup, 'getDoc')) {
  305. $doc = $propertyGroup->getDoc();
  306. if ($doc != null) {
  307. if (count($doc) === 3) {
  308. $ret .= MySQLDocumentation::show($doc[1], false, null, null, $doc[2]);
  309. } elseif (count($doc) === 1) {
  310. $ret .= MySQLDocumentation::showDocumentation('faq', $doc[0]);
  311. } else {
  312. $ret .= MySQLDocumentation::show($doc[1]);
  313. }
  314. }
  315. }
  316. // Close the list element after $doc link is displayed
  317. if ($property_class !== null) {
  318. if (
  319. $property_class == BoolPropertyItem::class
  320. || $property_class == MessageOnlyPropertyItem::class
  321. || $property_class == SelectPropertyItem::class
  322. || $property_class == TextPropertyItem::class
  323. ) {
  324. $ret .= '</li>';
  325. }
  326. }
  327. return $ret . "\n";
  328. }
  329. /**
  330. * Get HTML for properties items
  331. *
  332. * @param string $section name of config section in
  333. * $GLOBALS['cfg'][$section] for plugin
  334. * @param string $plugin_name unique plugin name
  335. * @param OptionsPropertyItem $propertyItem Property item
  336. * @psalm-param 'Export'|'Import'|'Schema' $section
  337. *
  338. * @return string
  339. */
  340. public static function getHtmlForProperty(
  341. $section,
  342. $plugin_name,
  343. $propertyItem
  344. ) {
  345. $ret = '';
  346. $property_class = get_class($propertyItem);
  347. switch ($property_class) {
  348. case BoolPropertyItem::class:
  349. $ret .= '<li>' . "\n";
  350. $ret .= '<input type="checkbox" name="' . $plugin_name . '_'
  351. . $propertyItem->getName() . '"'
  352. . ' value="something" id="checkbox_' . $plugin_name . '_'
  353. . $propertyItem->getName() . '"'
  354. . ' '
  355. . self::checkboxCheck(
  356. $section,
  357. $plugin_name . '_' . $propertyItem->getName()
  358. );
  359. if ($propertyItem->getForce() != null) {
  360. // Same code is also few lines lower, update both if needed
  361. $ret .= ' onclick="if (!this.checked &amp;&amp; '
  362. . '(!document.getElementById(\'checkbox_' . $plugin_name
  363. . '_' . $propertyItem->getForce() . '\') '
  364. . '|| !document.getElementById(\'checkbox_'
  365. . $plugin_name . '_' . $propertyItem->getForce()
  366. . '\').checked)) '
  367. . 'return false; else return true;"';
  368. }
  369. $ret .= '>';
  370. $ret .= '<label for="checkbox_' . $plugin_name . '_'
  371. . $propertyItem->getName() . '">'
  372. . self::getString($propertyItem->getText()) . '</label>';
  373. break;
  374. case DocPropertyItem::class:
  375. echo DocPropertyItem::class;
  376. break;
  377. case HiddenPropertyItem::class:
  378. $ret .= '<li><input type="hidden" name="' . $plugin_name . '_'
  379. . $propertyItem->getName() . '"'
  380. . ' value="' . self::getDefault(
  381. $section,
  382. $plugin_name . '_' . $propertyItem->getName()
  383. )
  384. . '"></li>';
  385. break;
  386. case MessageOnlyPropertyItem::class:
  387. $ret .= '<li>' . "\n";
  388. $ret .= '<p>' . self::getString($propertyItem->getText()) . '</p>';
  389. break;
  390. case RadioPropertyItem::class:
  391. /**
  392. * @var RadioPropertyItem $pitem
  393. */
  394. $pitem = $propertyItem;
  395. $default = self::getDefault(
  396. $section,
  397. $plugin_name . '_' . $pitem->getName()
  398. );
  399. foreach ($pitem->getValues() as $key => $val) {
  400. $ret .= '<li><input type="radio" name="' . $plugin_name
  401. . '_' . $pitem->getName() . '" value="' . $key
  402. . '" id="radio_' . $plugin_name . '_'
  403. . $pitem->getName() . '_' . $key . '"';
  404. if ($key == $default) {
  405. $ret .= ' checked="checked"';
  406. }
  407. $ret .= '><label for="radio_' . $plugin_name . '_'
  408. . $pitem->getName() . '_' . $key . '">'
  409. . self::getString($val) . '</label></li>';
  410. }
  411. break;
  412. case SelectPropertyItem::class:
  413. /**
  414. * @var SelectPropertyItem $pitem
  415. */
  416. $pitem = $propertyItem;
  417. $ret .= '<li>' . "\n";
  418. $ret .= '<label for="select_' . $plugin_name . '_'
  419. . $pitem->getName() . '" class="desc">'
  420. . self::getString($pitem->getText()) . '</label>';
  421. $ret .= '<select name="' . $plugin_name . '_'
  422. . $pitem->getName() . '"'
  423. . ' id="select_' . $plugin_name . '_'
  424. . $pitem->getName() . '">';
  425. $default = self::getDefault(
  426. $section,
  427. $plugin_name . '_' . $pitem->getName()
  428. );
  429. foreach ($pitem->getValues() as $key => $val) {
  430. $ret .= '<option value="' . $key . '"';
  431. if ($key == $default) {
  432. $ret .= ' selected="selected"';
  433. }
  434. $ret .= '>' . self::getString($val) . '</option>';
  435. }
  436. $ret .= '</select>';
  437. break;
  438. case TextPropertyItem::class:
  439. /**
  440. * @var TextPropertyItem $pitem
  441. */
  442. $pitem = $propertyItem;
  443. $ret .= '<li>' . "\n";
  444. $ret .= '<label for="text_' . $plugin_name . '_'
  445. . $pitem->getName() . '" class="desc">'
  446. . self::getString($pitem->getText()) . '</label>';
  447. $ret .= '<input type="text" name="' . $plugin_name . '_'
  448. . $pitem->getName() . '"'
  449. . ' value="' . self::getDefault(
  450. $section,
  451. $plugin_name . '_' . $pitem->getName()
  452. ) . '"'
  453. . ' id="text_' . $plugin_name . '_'
  454. . $pitem->getName() . '"'
  455. . ($pitem->getSize() != null
  456. ? ' size="' . $pitem->getSize() . '"'
  457. : '')
  458. . ($pitem->getLen() != null
  459. ? ' maxlength="' . $pitem->getLen() . '"'
  460. : '')
  461. . '>';
  462. break;
  463. case NumberPropertyItem::class:
  464. $ret .= '<li>' . "\n";
  465. $ret .= '<label for="number_' . $plugin_name . '_'
  466. . $propertyItem->getName() . '" class="desc">'
  467. . self::getString($propertyItem->getText()) . '</label>';
  468. $ret .= '<input type="number" name="' . $plugin_name . '_'
  469. . $propertyItem->getName() . '"'
  470. . ' value="' . self::getDefault(
  471. $section,
  472. $plugin_name . '_' . $propertyItem->getName()
  473. ) . '"'
  474. . ' id="number_' . $plugin_name . '_'
  475. . $propertyItem->getName() . '"'
  476. . ' min="0"'
  477. . '>';
  478. break;
  479. default:
  480. break;
  481. }
  482. return $ret;
  483. }
  484. /**
  485. * Returns html div with editable options for plugin
  486. *
  487. * @param string $section name of config section in $GLOBALS['cfg'][$section]
  488. * @param ExportPlugin[]|ImportPlugin[]|SchemaPlugin[] $list array with plugin instances
  489. * @psalm-param 'Export'|'Import'|'Schema' $section
  490. *
  491. * @return string html fieldset with plugin options
  492. */
  493. public static function getOptions($section, array $list)
  494. {
  495. $ret = '';
  496. // Options for plugins that support them
  497. foreach ($list as $plugin) {
  498. $properties = $plugin->getProperties();
  499. $text = null;
  500. $options = null;
  501. if ($properties != null) {
  502. $text = $properties->getText();
  503. $options = $properties->getOptions();
  504. }
  505. $plugin_name = $plugin->getName();
  506. $ret .= '<div id="' . $plugin_name
  507. . '_options" class="format_specific_options">';
  508. $ret .= '<h3>' . self::getString($text) . '</h3>';
  509. $no_options = true;
  510. if ($options !== null && count($options) > 0) {
  511. foreach ($options->getProperties() as $propertyMainGroup) {
  512. // check for hidden properties
  513. $no_options = true;
  514. foreach ($propertyMainGroup->getProperties() as $propertyItem) {
  515. if (strcmp(HiddenPropertyItem::class, get_class($propertyItem))) {
  516. $no_options = false;
  517. break;
  518. }
  519. }
  520. $ret .= self::getOneOption($section, $plugin_name, $propertyMainGroup);
  521. }
  522. }
  523. if ($no_options) {
  524. $ret .= '<p>' . __('This format has no options') . '</p>';
  525. }
  526. $ret .= '</div>';
  527. }
  528. return $ret;
  529. }
  530. public static function getAuthPlugin(): AuthenticationPlugin
  531. {
  532. global $cfg;
  533. /** @psalm-var class-string $class */
  534. $class = 'PhpMyAdmin\\Plugins\\Auth\\Authentication' . ucfirst(strtolower($cfg['Server']['auth_type']));
  535. if (! class_exists($class)) {
  536. Core::fatalError(
  537. __('Invalid authentication method set in configuration:')
  538. . ' ' . $cfg['Server']['auth_type']
  539. );
  540. }
  541. /** @var AuthenticationPlugin $plugin */
  542. $plugin = new $class();
  543. return $plugin;
  544. }
  545. }