Plugins.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Generic plugin interface.
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
  11. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  12. use PhpMyAdmin\Properties\Options\Items\DocPropertyItem;
  13. use PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem;
  14. use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
  15. use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
  16. use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
  17. use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
  18. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  19. use PhpMyAdmin\Properties\Options\OptionsPropertyItem;
  20. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  21. use PhpMyAdmin\Properties\Plugins\PluginPropertyItem;
  22. use PhpMyAdmin\Properties\Plugins\SchemaPluginProperties;
  23. use PhpMyAdmin\Util;
  24. /**
  25. * PhpMyAdmin\Plugins class
  26. *
  27. * @package PhpMyAdmin
  28. */
  29. class Plugins
  30. {
  31. /**
  32. * Includes and instantiates the specified plugin type for a certain format
  33. *
  34. * @param string $plugin_type the type of the plugin (import, export, etc)
  35. * @param string $plugin_format the format of the plugin (sql, xml, et )
  36. * @param string $plugins_dir directory with plugins
  37. * @param mixed $plugin_param parameter to plugin by which they can
  38. * decide whether they can work
  39. *
  40. * @return object|null new plugin instance
  41. */
  42. public static function getPlugin(
  43. $plugin_type,
  44. $plugin_format,
  45. $plugins_dir,
  46. $plugin_param = false
  47. ) {
  48. $GLOBALS['plugin_param'] = $plugin_param;
  49. $class_name = mb_strtoupper($plugin_type[0])
  50. . mb_strtolower(mb_substr($plugin_type, 1))
  51. . mb_strtoupper($plugin_format[0])
  52. . mb_strtolower(mb_substr($plugin_format, 1));
  53. $file = $class_name . ".php";
  54. if (is_file($plugins_dir . $file)) {
  55. //include_once $plugins_dir . $file;
  56. $fqnClass = 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr($plugins_dir, 18)) . $class_name;
  57. // check if class exists, could be caused by skip_import
  58. if (class_exists($fqnClass)) {
  59. return new $fqnClass;
  60. }
  61. }
  62. return null;
  63. }
  64. /**
  65. * Reads all plugin information from directory $plugins_dir
  66. *
  67. * @param string $plugin_type the type of the plugin (import, export, etc)
  68. * @param string $plugins_dir directory with plugins
  69. * @param mixed $plugin_param parameter to plugin by which they can
  70. * decide whether they can work
  71. *
  72. * @return array list of plugin instances
  73. */
  74. public static function getPlugins($plugin_type, $plugins_dir, $plugin_param)
  75. {
  76. $GLOBALS['plugin_param'] = $plugin_param;
  77. /* Scan for plugins */
  78. $plugin_list = [];
  79. if (! ($handle = @opendir($plugins_dir))) {
  80. return $plugin_list;
  81. }
  82. $namespace = 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr($plugins_dir, 18));
  83. $class_type = mb_strtoupper($plugin_type[0], 'UTF-8')
  84. . mb_strtolower(mb_substr($plugin_type, 1), 'UTF-8');
  85. $prefix_class_name = $namespace . $class_type;
  86. while ($file = @readdir($handle)) {
  87. // In some situations, Mac OS creates a new file for each file
  88. // (for example ._csv.php) so the following regexp
  89. // matches a file which does not start with a dot but ends
  90. // with ".php"
  91. if (is_file($plugins_dir . $file)
  92. && preg_match(
  93. '@^' . $class_type . '([^\.]+)\.php$@i',
  94. $file,
  95. $matches
  96. )
  97. ) {
  98. $GLOBALS['skip_import'] = false;
  99. include_once $plugins_dir . $file;
  100. if (! $GLOBALS['skip_import']) {
  101. $class_name = $prefix_class_name . $matches[1];
  102. $plugin = new $class_name;
  103. if (null !== $plugin->getProperties()) {
  104. $plugin_list[] = $plugin;
  105. }
  106. }
  107. }
  108. }
  109. usort($plugin_list, function ($cmp_name_1, $cmp_name_2) {
  110. return strcasecmp(
  111. $cmp_name_1->getProperties()->getText(),
  112. $cmp_name_2->getProperties()->getText()
  113. );
  114. });
  115. return $plugin_list;
  116. }
  117. /**
  118. * Returns locale string for $name or $name if no locale is found
  119. *
  120. * @param string $name for local string
  121. *
  122. * @return string locale string for $name
  123. */
  124. public static function getString($name)
  125. {
  126. return isset($GLOBALS[$name]) ? $GLOBALS[$name] : $name;
  127. }
  128. /**
  129. * Returns html input tag option 'checked' if plugin $opt
  130. * should be set by config or request
  131. *
  132. * @param string $section name of config section in
  133. * $GLOBALS['cfg'][$section] for plugin
  134. * @param string $opt name of option
  135. *
  136. * @return string html input tag option 'checked'
  137. */
  138. public static function checkboxCheck($section, $opt)
  139. {
  140. // If the form is being repopulated using $_GET data, that is priority
  141. if (isset($_GET[$opt])
  142. || ! isset($_GET['repopulate'])
  143. && ((! empty($GLOBALS['timeout_passed']) && isset($_REQUEST[$opt]))
  144. || ! empty($GLOBALS['cfg'][$section][$opt]))
  145. ) {
  146. return ' checked="checked"';
  147. }
  148. return '';
  149. }
  150. /**
  151. * Returns default value for option $opt
  152. *
  153. * @param string $section name of config section in
  154. * $GLOBALS['cfg'][$section] for plugin
  155. * @param string $opt name of option
  156. *
  157. * @return string default value for option $opt
  158. */
  159. public static function getDefault($section, $opt)
  160. {
  161. if (isset($_GET[$opt])) {
  162. // If the form is being repopulated using $_GET data, that is priority
  163. return htmlspecialchars($_GET[$opt]);
  164. }
  165. if (isset($GLOBALS['timeout_passed'])
  166. && $GLOBALS['timeout_passed']
  167. && isset($_REQUEST[$opt])
  168. ) {
  169. return htmlspecialchars($_REQUEST[$opt]);
  170. }
  171. if (! isset($GLOBALS['cfg'][$section][$opt])) {
  172. return '';
  173. }
  174. $matches = [];
  175. /* Possibly replace localised texts */
  176. if (! preg_match_all(
  177. '/(str[A-Z][A-Za-z0-9]*)/',
  178. (string) $GLOBALS['cfg'][$section][$opt],
  179. $matches
  180. )) {
  181. return htmlspecialchars((string) $GLOBALS['cfg'][$section][$opt]);
  182. }
  183. $val = $GLOBALS['cfg'][$section][$opt];
  184. foreach ($matches[0] as $match) {
  185. if (isset($GLOBALS[$match])) {
  186. $val = str_replace($match, $GLOBALS[$match], $val);
  187. }
  188. }
  189. return htmlspecialchars($val);
  190. }
  191. /**
  192. * Returns html select form element for plugin choice
  193. * and hidden fields denoting whether each plugin must be exported as a file
  194. *
  195. * @param string $section name of config section in
  196. * $GLOBALS['cfg'][$section] for plugin
  197. * @param string $name name of select element
  198. * @param array $list array with plugin instances
  199. * @param string $cfgname name of config value, if none same as $name
  200. *
  201. * @return string html select tag
  202. */
  203. public static function getChoice($section, $name, array $list, $cfgname = null)
  204. {
  205. if (! isset($cfgname)) {
  206. $cfgname = $name;
  207. }
  208. $ret = '<select id="plugins" name="' . $name . '">';
  209. $default = self::getDefault($section, $cfgname);
  210. $hidden = null;
  211. foreach ($list as $plugin) {
  212. $elem = explode('\\', get_class($plugin));
  213. $plugin_name = array_pop($elem);
  214. unset($elem);
  215. $plugin_name = mb_strtolower(
  216. mb_substr(
  217. $plugin_name,
  218. mb_strlen($section)
  219. )
  220. );
  221. $ret .= '<option';
  222. // If the form is being repopulated using $_GET data, that is priority
  223. if (isset($_GET[$name])
  224. && $plugin_name == $_GET[$name]
  225. || ! isset($_GET[$name])
  226. && $plugin_name == $default
  227. ) {
  228. $ret .= ' selected="selected"';
  229. }
  230. /** @var PluginPropertyItem $properties */
  231. $properties = $plugin->getProperties();
  232. $text = null;
  233. if ($properties != null) {
  234. $text = $properties->getText();
  235. }
  236. $ret .= ' value="' . $plugin_name . '">'
  237. . self::getString($text)
  238. . '</option>' . "\n";
  239. // Whether each plugin has to be saved as a file
  240. $hidden .= '<input type="hidden" id="force_file_' . $plugin_name
  241. . '" value="';
  242. /** @var ExportPluginProperties|SchemaPluginProperties $properties */
  243. $properties = $plugin->getProperties();
  244. if (! strcmp($section, 'Import')
  245. || ($properties != null && $properties->getForceFile() != null)
  246. ) {
  247. $hidden .= 'true';
  248. } else {
  249. $hidden .= 'false';
  250. }
  251. $hidden .= '">' . "\n";
  252. }
  253. $ret .= '</select>' . "\n" . $hidden;
  254. return $ret;
  255. }
  256. /**
  257. * Returns single option in a list element
  258. *
  259. * @param string $section name of config section in $GLOBALS['cfg'][$section] for plugin
  260. * @param string $plugin_name unique plugin name
  261. * @param OptionsPropertyItem $propertyGroup options property main group instance
  262. * @param boolean $is_subgroup if this group is a subgroup
  263. *
  264. * @return string table row with option
  265. */
  266. public static function getOneOption(
  267. $section,
  268. $plugin_name,
  269. &$propertyGroup,
  270. $is_subgroup = false
  271. ) {
  272. $ret = "\n";
  273. $properties = null;
  274. if (! $is_subgroup) {
  275. // for subgroup headers
  276. if (mb_strpos(get_class($propertyGroup), "PropertyItem")) {
  277. $properties = [$propertyGroup];
  278. } else {
  279. // for main groups
  280. $ret .= '<div class="export_sub_options" id="' . $plugin_name . '_'
  281. . $propertyGroup->getName() . '">';
  282. $text = null;
  283. if (method_exists($propertyGroup, 'getText')) {
  284. $text = $propertyGroup->getText();
  285. }
  286. if ($text != null) {
  287. $ret .= '<h4>' . self::getString($text) . '</h4>';
  288. }
  289. $ret .= '<ul>';
  290. }
  291. }
  292. if (! isset($properties)) {
  293. $not_subgroup_header = true;
  294. if (method_exists($propertyGroup, 'getProperties')) {
  295. $properties = $propertyGroup->getProperties();
  296. }
  297. }
  298. if (isset($properties)) {
  299. /** @var OptionsPropertySubgroup $propertyItem */
  300. foreach ($properties as $propertyItem) {
  301. $property_class = get_class($propertyItem);
  302. // if the property is a subgroup, we deal with it recursively
  303. if (mb_strpos($property_class, "Subgroup")) {
  304. // for subgroups
  305. // each subgroup can have a header, which may also be a form element
  306. /** @var OptionsPropertyItem $subgroup_header */
  307. $subgroup_header = $propertyItem->getSubgroupHeader();
  308. if ($subgroup_header !== null) {
  309. $ret .= self::getOneOption(
  310. $section,
  311. $plugin_name,
  312. $subgroup_header
  313. );
  314. }
  315. $ret .= '<li class="subgroup"><ul';
  316. if ($subgroup_header !== null) {
  317. $ret .= ' id="ul_' . $subgroup_header->getName() . '">';
  318. } else {
  319. $ret .= '>';
  320. }
  321. $ret .= self::getOneOption(
  322. $section,
  323. $plugin_name,
  324. $propertyItem,
  325. true
  326. );
  327. continue;
  328. }
  329. // single property item
  330. $ret .= self::getHtmlForProperty(
  331. $section,
  332. $plugin_name,
  333. $propertyItem
  334. );
  335. }
  336. }
  337. if ($is_subgroup) {
  338. // end subgroup
  339. $ret .= '</ul></li>';
  340. } else {
  341. // end main group
  342. if (! empty($not_subgroup_header)) {
  343. $ret .= '</ul></div>';
  344. }
  345. }
  346. if (method_exists($propertyGroup, "getDoc")) {
  347. $doc = $propertyGroup->getDoc();
  348. if ($doc != null) {
  349. if (count($doc) === 3) {
  350. $ret .= Util::showMySQLDocu(
  351. $doc[1],
  352. false,
  353. null,
  354. null,
  355. $doc[2]
  356. );
  357. } elseif (count($doc) === 1) {
  358. $ret .= Util::showDocu('faq', $doc[0]);
  359. } else {
  360. $ret .= Util::showMySQLDocu(
  361. $doc[1]
  362. );
  363. }
  364. }
  365. }
  366. // Close the list element after $doc link is displayed
  367. if (isset($property_class)) {
  368. if ($property_class == 'PhpMyAdmin\Properties\Options\Items\BoolPropertyItem'
  369. || $property_class == 'PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem'
  370. || $property_class == 'PhpMyAdmin\Properties\Options\Items\SelectPropertyItem'
  371. || $property_class == 'PhpMyAdmin\Properties\Options\Items\TextPropertyItem'
  372. ) {
  373. $ret .= '</li>';
  374. }
  375. }
  376. $ret .= "\n";
  377. return $ret;
  378. }
  379. /**
  380. * Get HTML for properties items
  381. *
  382. * @param string $section name of config section in
  383. * $GLOBALS['cfg'][$section] for plugin
  384. * @param string $plugin_name unique plugin name
  385. * @param OptionsPropertyItem $propertyItem Property item
  386. *
  387. * @return string
  388. */
  389. public static function getHtmlForProperty(
  390. $section,
  391. $plugin_name,
  392. $propertyItem
  393. ) {
  394. $ret = null;
  395. $property_class = get_class($propertyItem);
  396. switch ($property_class) {
  397. case BoolPropertyItem::class:
  398. $ret .= '<li>' . "\n";
  399. $ret .= '<input type="checkbox" name="' . $plugin_name . '_'
  400. . $propertyItem->getName() . '"'
  401. . ' value="something" id="checkbox_' . $plugin_name . '_'
  402. . $propertyItem->getName() . '"'
  403. . ' '
  404. . self::checkboxCheck(
  405. $section,
  406. $plugin_name . '_' . $propertyItem->getName()
  407. );
  408. if ($propertyItem->getForce() != null) {
  409. // Same code is also few lines lower, update both if needed
  410. $ret .= ' onclick="if (!this.checked &amp;&amp; '
  411. . '(!document.getElementById(\'checkbox_' . $plugin_name
  412. . '_' . $propertyItem->getForce() . '\') '
  413. . '|| !document.getElementById(\'checkbox_'
  414. . $plugin_name . '_' . $propertyItem->getForce()
  415. . '\').checked)) '
  416. . 'return false; else return true;"';
  417. }
  418. $ret .= '>';
  419. $ret .= '<label for="checkbox_' . $plugin_name . '_'
  420. . $propertyItem->getName() . '">'
  421. . self::getString($propertyItem->getText()) . '</label>';
  422. break;
  423. case DocPropertyItem::class:
  424. echo DocPropertyItem::class;
  425. break;
  426. case HiddenPropertyItem::class:
  427. $ret .= '<li><input type="hidden" name="' . $plugin_name . '_'
  428. . $propertyItem->getName() . '"'
  429. . ' value="' . self::getDefault(
  430. $section,
  431. $plugin_name . '_' . $propertyItem->getName()
  432. )
  433. . '"></li>';
  434. break;
  435. case MessageOnlyPropertyItem::class:
  436. $ret .= '<li>' . "\n";
  437. $ret .= '<p>' . self::getString($propertyItem->getText()) . '</p>';
  438. break;
  439. case RadioPropertyItem::class:
  440. /**
  441. * @var RadioPropertyItem $pitem
  442. */
  443. $pitem = $propertyItem;
  444. $default = self::getDefault(
  445. $section,
  446. $plugin_name . '_' . $pitem->getName()
  447. );
  448. foreach ($pitem->getValues() as $key => $val) {
  449. $ret .= '<li><input type="radio" name="' . $plugin_name
  450. . '_' . $pitem->getName() . '" value="' . $key
  451. . '" id="radio_' . $plugin_name . '_'
  452. . $pitem->getName() . '_' . $key . '"';
  453. if ($key == $default) {
  454. $ret .= ' checked="checked"';
  455. }
  456. $ret .= '><label for="radio_' . $plugin_name . '_'
  457. . $pitem->getName() . '_' . $key . '">'
  458. . self::getString($val) . '</label></li>';
  459. }
  460. break;
  461. case SelectPropertyItem::class:
  462. /**
  463. * @var SelectPropertyItem $pitem
  464. */
  465. $pitem = $propertyItem;
  466. $ret .= '<li>' . "\n";
  467. $ret .= '<label for="select_' . $plugin_name . '_'
  468. . $pitem->getName() . '" class="desc">'
  469. . self::getString($pitem->getText()) . '</label>';
  470. $ret .= '<select name="' . $plugin_name . '_'
  471. . $pitem->getName() . '"'
  472. . ' id="select_' . $plugin_name . '_'
  473. . $pitem->getName() . '">';
  474. $default = self::getDefault(
  475. $section,
  476. $plugin_name . '_' . $pitem->getName()
  477. );
  478. foreach ($pitem->getValues() as $key => $val) {
  479. $ret .= '<option value="' . $key . '"';
  480. if ($key == $default) {
  481. $ret .= ' selected="selected"';
  482. }
  483. $ret .= '>' . self::getString($val) . '</option>';
  484. }
  485. $ret .= '</select>';
  486. break;
  487. case TextPropertyItem::class:
  488. /**
  489. * @var TextPropertyItem $pitem
  490. */
  491. $pitem = $propertyItem;
  492. $ret .= '<li>' . "\n";
  493. $ret .= '<label for="text_' . $plugin_name . '_'
  494. . $pitem->getName() . '" class="desc">'
  495. . self::getString($pitem->getText()) . '</label>';
  496. $ret .= '<input type="text" name="' . $plugin_name . '_'
  497. . $pitem->getName() . '"'
  498. . ' value="' . self::getDefault(
  499. $section,
  500. $plugin_name . '_' . $pitem->getName()
  501. ) . '"'
  502. . ' id="text_' . $plugin_name . '_'
  503. . $pitem->getName() . '"'
  504. . ($pitem->getSize() != null
  505. ? ' size="' . $pitem->getSize() . '"'
  506. : '')
  507. . ($pitem->getLen() != null
  508. ? ' maxlength="' . $pitem->getLen() . '"'
  509. : '')
  510. . '>';
  511. break;
  512. case NumberPropertyItem::class:
  513. $ret .= '<li>' . "\n";
  514. $ret .= '<label for="number_' . $plugin_name . '_'
  515. . $propertyItem->getName() . '" class="desc">'
  516. . self::getString($propertyItem->getText()) . '</label>';
  517. $ret .= '<input type="number" name="' . $plugin_name . '_'
  518. . $propertyItem->getName() . '"'
  519. . ' value="' . self::getDefault(
  520. $section,
  521. $plugin_name . '_' . $propertyItem->getName()
  522. ) . '"'
  523. . ' id="number_' . $plugin_name . '_'
  524. . $propertyItem->getName() . '"'
  525. . ' min="0"'
  526. . '>';
  527. break;
  528. default:
  529. break;
  530. }
  531. return $ret;
  532. }
  533. /**
  534. * Returns html div with editable options for plugin
  535. *
  536. * @param string $section name of config section in $GLOBALS['cfg'][$section]
  537. * @param array $list array with plugin instances
  538. *
  539. * @return string html fieldset with plugin options
  540. */
  541. public static function getOptions($section, array $list)
  542. {
  543. $ret = '';
  544. // Options for plugins that support them
  545. foreach ($list as $plugin) {
  546. $properties = $plugin->getProperties();
  547. $text = null;
  548. $options = null;
  549. if ($properties != null) {
  550. $text = $properties->getText();
  551. $options = $properties->getOptions();
  552. }
  553. $elem = explode('\\', get_class($plugin));
  554. $plugin_name = array_pop($elem);
  555. unset($elem);
  556. $plugin_name = mb_strtolower(
  557. mb_substr(
  558. $plugin_name,
  559. mb_strlen($section)
  560. )
  561. );
  562. $ret .= '<div id="' . $plugin_name
  563. . '_options" class="format_specific_options">';
  564. $ret .= '<h3>' . self::getString($text) . '</h3>';
  565. $no_options = true;
  566. if ($options !== null && count($options) > 0) {
  567. foreach ($options->getProperties() as $propertyMainGroup) {
  568. // check for hidden properties
  569. $no_options = true;
  570. foreach ($propertyMainGroup->getProperties() as $propertyItem) {
  571. if (strcmp(HiddenPropertyItem::class, get_class($propertyItem))) {
  572. $no_options = false;
  573. break;
  574. }
  575. }
  576. $ret .= self::getOneOption(
  577. $section,
  578. $plugin_name,
  579. $propertyMainGroup
  580. );
  581. }
  582. }
  583. if ($no_options) {
  584. $ret .= '<p>' . __('This format has no options') . '</p>';
  585. }
  586. $ret .= '</div>';
  587. }
  588. return $ret;
  589. }
  590. }