PageRenderTime 63ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/core/Tiki/Command/IndexCompareEnginesCommand.php

https://gitlab.com/ElvisAns/tiki
PHP | 266 lines | 205 code | 48 blank | 13 comment | 27 complexity | fca62a282a6197ccd58be4dd9dd023d9 MD5 | raw file
  1. <?php
  2. // (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
  3. //
  4. // All Rights Reserved. See copyright.txt for details and a complete list of authors.
  5. // Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
  6. // $Id$
  7. namespace Tiki\Command;
  8. use Symfony\Component\Console\Command\Command;
  9. use Symfony\Component\Console\Input\InputInterface;
  10. use Symfony\Component\Console\Input\InputOption;
  11. use Symfony\Component\Console\Output\OutputInterface;
  12. use Symfony\Component\Console\Style\SymfonyStyle;
  13. use SebastianBergmann\Diff\Differ;
  14. use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
  15. use TikiLib;
  16. class IndexCompareEnginesCommand extends Command
  17. {
  18. /**
  19. * Add or remove plugins to this array to be considered when checking the results
  20. */
  21. const PLUGINS_TO_CHECK = ['list', 'listexecute', 'pivottable'];
  22. protected function configure()
  23. {
  24. $this
  25. ->setName('index:compare-engines')
  26. ->setDescription('Compare search engine results in wikiplugins')
  27. ->setHelp(
  28. 'Check unified search plugin results inside wiki pages by comparing Elasticsearch results with its MySQL fallback counterpart.
  29. Only plugins that use the unified search results are verified.'
  30. )
  31. ->addOption(
  32. 'page',
  33. null,
  34. InputOption::VALUE_REQUIRED,
  35. 'The page name to check',
  36. )
  37. ->addOption(
  38. 'html',
  39. null,
  40. InputOption::VALUE_NONE,
  41. 'Export the differences found in a well formatted HTML file'
  42. )
  43. ->addOption(
  44. 'reindex',
  45. null,
  46. InputOption::VALUE_NONE,
  47. 'Reindex search engines before running this script'
  48. );
  49. }
  50. protected function execute(InputInterface $input, OutputInterface $output)
  51. {
  52. global $prefs, $tikidomainslash;
  53. $io = new SymfonyStyle($input, $output);
  54. if ($prefs['unified_engine'] != 'elastic' || $prefs['unified_elastic_mysql_search_fallback'] != 'y') {
  55. $io->error(
  56. 'To execute this script you need your unified search engine configured with Elasticsearch and MySQL fallback has to be enabled.'
  57. );
  58. return 1;
  59. }
  60. $tikiLib = TikiLib::lib('tiki');
  61. if ($page = $input->getOption('page')) {
  62. $pageInfo = $tikiLib->get_page_info($page) ?: null;
  63. $pages = [$pageInfo];
  64. } else {
  65. $allPages = $tikiLib->list_pages();
  66. $pages = $allPages['data'] ?: [];
  67. }
  68. if (! $pages) {
  69. $io->writeln('There are no wiki pages to check.');
  70. return 0;
  71. }
  72. $reindex = $input->getOption('reindex');
  73. $unifiedSearchLib = TikiLib::lib('unifiedsearch');
  74. $elasticStatus = $unifiedSearchLib->checkElasticsearch();
  75. if ($elasticStatus['error']) {
  76. $io->error('Elasticsearch Error' . PHP_EOL . $elasticStatus['feedback']);
  77. exit(1);
  78. }
  79. if (! $reindex && ! $unifiedSearchLib->getIndex()->exists()) {
  80. $io->error('Elasticsearch index not found. Use --reindex to rebuild the index.');
  81. exit(1);
  82. }
  83. $prefs['unified_engine'] = 'mysql'; // Check if mysql index table exists
  84. $mysqlStatus = $unifiedSearchLib->checkMySql();
  85. if ($mysqlStatus['error'] && ! $reindex) {
  86. $io->error($mysqlStatus['feedback']);
  87. exit(1);
  88. }
  89. $prefs['unified_engine'] = 'elastic'; // Restore original value
  90. if ($input->getOption('reindex')) {
  91. $io->writeln('Rebuilding index, please wait...');
  92. @TikiLib::lib('unifiedsearch')->rebuild();
  93. $io->writeln('Index rebuild finished.');
  94. $io->newLine(2);
  95. }
  96. $parserLib = TikiLib::lib('parser');
  97. $differentOutputs = [];
  98. // Disable fallback, in case of elastic failure does not use the mysql results
  99. $prefs['unified_elastic_mysql_search_fallback'] = 'n';
  100. foreach ($pages as $page) {
  101. $plugins = \WikiParser_PluginMatcher::match($page['data']);
  102. if (! $plugins->count()) {
  103. continue;
  104. }
  105. for ($i = 0; $i < $plugins->count(); $i++) {
  106. $plugin = $plugins->next();
  107. $rawPlugin = strval($plugin);
  108. if (! in_array($plugin->getName(), static::PLUGINS_TO_CHECK)) {
  109. continue;
  110. }
  111. $pluginName = $plugin->getName();
  112. \Search_Formatter_Factory::$counter = 0; // Reset counter index
  113. $elasticOutput = @$parserLib->parse_data($rawPlugin);
  114. TikiLib::lib('unifiedsearch')->invalidateIndicesCache();
  115. $prefs['unified_engine'] = 'mysql';
  116. \Search_Formatter_Factory::$counter = 0; // Reset counter index (different engine)
  117. $fallbackOutput = @$parserLib->parse_data($rawPlugin);
  118. $prefs['unified_engine'] = 'elastic';
  119. // Remove static $id usage to avoid differences used by pivottable plugin
  120. if ($pluginName == 'pivottable') {
  121. $regex = '/pivottable.?\d+/';
  122. $elasticOutput = preg_replace($regex, 'pivottable', $elasticOutput);
  123. $fallbackOutput = preg_replace($regex, 'pivottable', $fallbackOutput);
  124. }
  125. // Remove static $id usage to avoid differences used by listexecute plugin
  126. if ($pluginName == 'listexecute') {
  127. $regex = '/listexecute.?\d+/';
  128. $elasticOutput = preg_replace($regex, 'listexecute', $elasticOutput);
  129. $fallbackOutput = preg_replace($regex, 'listexecute', $fallbackOutput);
  130. $regex = '/objects\d+\[\]/';
  131. $elasticOutput = preg_replace($regex, 'objects[]', $elasticOutput);
  132. $fallbackOutput = preg_replace($regex, 'objects[]', $fallbackOutput);
  133. }
  134. // Remove static $id usage to avoid differences used by list plugin
  135. if ($pluginName == 'list') {
  136. $regex = '/list.?(\d+)/';
  137. $elasticOutput = preg_replace($regex, 'list', $elasticOutput);
  138. $fallbackOutput = preg_replace($regex, 'list', $fallbackOutput);
  139. }
  140. if ($elasticOutput !== $fallbackOutput) {
  141. $differentOutputs[] = [
  142. 'page' => $page['pageName'],
  143. 'plugin' => $rawPlugin,
  144. 'elastic' => $elasticOutput,
  145. 'fallback' => $fallbackOutput,
  146. ];
  147. }
  148. }
  149. }
  150. if (empty($differentOutputs)) {
  151. $io->writeln('Plugin outputs using Elasticsearch and the MySQL fallback are identical.');
  152. return 0;
  153. }
  154. if ($input->getOption('html')) {
  155. include_once 'lib/diff/difflib.php';
  156. include_once 'lib/wiki-plugins/wikiplugin_code.php';
  157. $htmlOutput = "";
  158. foreach ($differentOutputs as $output) {
  159. $pageName = $output['page'];
  160. $pluginCode = wikiplugin_code($output['plugin'], ['colors' => 'tiki'], null, []);
  161. $diff = diff2($output['elastic'], $output['fallback']);
  162. $htmlOutput .= <<<HTML
  163. <table class='table table-striped' style='margin-top: 40px'>
  164. <tbody>
  165. <tr>
  166. <td>Wiki page</td>
  167. <td>{$pageName}</td>
  168. </tr>
  169. <tr>
  170. <td>Plugin Declaration</td>
  171. <td>{$pluginCode}</td>
  172. </tr>
  173. <tr>
  174. <td>Output diff (Elastic/MySQL)</td>
  175. <td><table style='width:100%'>{$diff}</table></td>
  176. </tr>
  177. </tbody>
  178. </table>
  179. HTML;
  180. }
  181. // Inject the CSS, so the output file can be used as standalone HTML file
  182. $tikiBaseCSS = file_get_contents('themes/base_files/css/tiki_base.css');
  183. $defaultCSS = file_get_contents('themes/default/css/default.css');
  184. $htmlOutput = <<<HTML
  185. <!DOCTYPE html>
  186. <html>
  187. <head>
  188. <style>{$tikiBaseCSS}</style>
  189. <style>{$defaultCSS}</style>
  190. </head>
  191. <body style='margin-left: 20%; margin-right: 20%; margin-top: 20px'>
  192. <h4>Check unified search script results</h4>
  193. {$htmlOutput}
  194. </body>
  195. </html>
  196. HTML;
  197. $filename = sprintf('index-compare-engines_results_%s.html', date('YmdHi'));
  198. $finalPath = 'temp/' . $tikidomainslash . $filename;
  199. if (file_exists($finalPath)) {
  200. unlink($finalPath);
  201. }
  202. file_put_contents($finalPath, $htmlOutput);
  203. $io->writeln("Plugin differences found. Please check the file '$finalPath' for more details.");
  204. return 1;
  205. }
  206. $builder = new UnifiedDiffOutputBuilder("--- Elastic\n+++ MySQL\n");
  207. $differ = new Differ($builder);
  208. foreach ($differentOutputs as $output) {
  209. $io->section('Tiki Page - ' . $output['page']);
  210. $io->writeln('Plugin Declaration:');
  211. $io->writeln($output['plugin']);
  212. $io->newLine(2);
  213. $diff = $differ->diff($output['elastic'], $output['fallback']);
  214. $io->writeln($diff);
  215. }
  216. return 1;
  217. }
  218. }