diff --git a/inc/command/cleanticketscommand.class.php b/inc/command/cleanticketscommand.class.php index cbcd37702..b8c2a2371 100644 --- a/inc/command/cleanticketscommand.class.php +++ b/inc/command/cleanticketscommand.class.php @@ -31,13 +31,15 @@ namespace GlpiPlugin\Formcreator\Command; +use ITILFollowup; +use QuerySubQuery; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Ticket; use Item_Ticket; use PluginFormcreatorFormAnswer; -use Glpi\Toolbox\Sanitizer; class CleanTicketsCommand extends Command { @@ -48,66 +50,108 @@ protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { - $output->write("-> Search tickets to clean..."); - $output->writeln(""); + $message = __("Searching for invalid items...", "formcreator"); + $output->writeln("$message"); $this->fixBadForm_1($input, $output); $this->fixBadForm_2($input, $output); $this->fixBadForm_3($input, $output); - $output->writeln('Done.'); - return 0; + $output->writeln(""); + $message = __("Done.", "formcreator"); + $output->writeln("$message"); + + return Command::SUCCESS; } /** - * fix HTML tags double encoded - *

=> <p> => &lt;p&gt; + * Get invalid data using a specific regex pattern to detect invalid content * - * @param InputInterface $input - * @param OutputInterface $output - * @return void + * @param string $invalid_content_pattern + * + * @return iterable */ - protected function fixBadForm_1(InputInterface $input, OutputInterface $output) { + protected function getInvalidData(string $invalid_content_pattern): iterable { global $DB; - // Search tickets having HTML tags in content in the following form - // &lt;p&gt;Hello world&lt;/p&gt; - // Hello world is between

and

, but with wrong escaping - $itemTicketTable = Item_Ticket::getTable(); - $ticketTable = Ticket::getTable(); - $pattern = '&lt;'; - // $pattern = str_replace(';', '\\;', $pattern); - $tickets = $DB->request([ - 'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']], - 'FROM' => $ticketTable, + $item_ticket_table = Item_Ticket::getTable(); + $ticket_table = Ticket::getTable(); + $followup_table = ITILFollowup::getTable(); + + // First source: tickets description + $tickets_query = new QuerySubQuery([ + 'SELECT' => [ + new \QueryExpression($DB->quoteValue(Ticket::getType()) . ' AS ' . $DB->quoteName('itemtype')), + $ticket_table => [Ticket::getIndexName(), 'content'] + ], + 'FROM' => $ticket_table, 'INNER JOIN' => [ - $itemTicketTable => [ + $item_ticket_table => [ 'FKEY' => [ - $ticketTable => Ticket::getIndexName(), - $itemTicketTable => Ticket::getForeignKeyField(), + $ticket_table => Ticket::getIndexName(), + $item_ticket_table => Ticket::getForeignKeyField(), ], 'AND' => [ - "$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(), + "$item_ticket_table.itemtype" => PluginFormcreatorFormAnswer::getType(), ] ], ], 'WHERE' => [ - "$ticketTable.content" => ['LIKE', '%' . $pattern . '%'], // Matches bad encoding for '<' + "$ticket_table.content" => ['LIKE', '%' . $invalid_content_pattern . '%'], ], ]); - $count = $tickets->count(); - if ($count < 1) { - $output->writeln('-> No ticket to fix.'); - $output->writeln(""); - return 0; - } + // Second source: tickets that where merged into other tickets as a followup + // These followups may have been a former ticket generated by formcreator + $followup_query = new QuerySubquery([ + 'SELECT' => [ + new \QueryExpression($DB->quoteValue(ITILFollowup::getType()) . ' AS ' . $DB->quoteName('itemtype')), + ITILFollowup::getIndexName(), + 'content' + ], + 'FROM' => $followup_table, + 'WHERE' => [ + "sourceitems_id" => [">", 0], // Former tickets merged as followups + "$followup_table.content" => ['LIKE', '%' . $invalid_content_pattern . '%'], + ], + ]); - $output->write("-> Found $count tickets to clean (double encoded < and > signs)"); - $output->writeln(""); - $output->write("-> Cleaning tickets..."); + return $DB->request(new \QueryUnion([$tickets_query, $followup_query])); + } + + /** + * fix HTML tags double encoded + *

=> <p> => &lt;p&gt; + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function fixBadForm_1(InputInterface $input, OutputInterface $output) { + global $DB; + + // Print step info $output->writeln(""); - foreach ($tickets as $row) { + $message = __("Step 1: double encoded < and > signs.", "formcreator"); + $output->writeln("$message"); + + // Search tickets having HTML tags in content in the following form + // &lt;p&gt;Hello world&lt;/p&gt; + // Hello world is between

and

, but with wrong escaping + $items = $this->getInvalidData('&lt;'); // Matches bad encoding for '<' + + // No items found, nothing to do + $count = $items->count(); + if ($count === 0) { + $output->writeln(__("No invalid items found.", "formcreator")); + return; + } + + // Init progress bar + $output->writeln(__("Found $count item(s) to clean.", "formcreator")); + $progress_bar = new ProgressBar($output); + + foreach ($progress_bar->iterate($items) as $item) { $pattern = [ '/&lt;([a-z0-9]+?)&gt;/', '/&lt;(\/[a-z0-9]+?)&gt;/', @@ -116,18 +160,16 @@ protected function fixBadForm_1(InputInterface $input, OutputInterface $output) '<$1>', '<$1>', ]; - $row['content'] = preg_replace($pattern, $replace, $row['content']); + $item['content'] = preg_replace($pattern, $replace, $item['content']); + // Direct write to the table to avoid alteration of other fields $DB->update( - $ticketTable, - [ - 'content' => $DB->escape($row['content']) - ], - [ - 'id' => $row['id'], - ] + $item['itemtype']::getTable(), + ['content' => $DB->escape($item['content'])], + ['id' => $item['id']] ); } + $output->writeln(""); } /** @@ -140,72 +182,56 @@ protected function fixBadForm_1(InputInterface $input, OutputInterface $output) protected function fixBadForm_2(InputInterface $input, OutputInterface $output) { global $DB; + // Print step info + $output->writeln(""); + $message = __("Step 2: literal BR tag.", "formcreator"); + $output->writeln("$message"); + // Search tickets having HTML tags
- $itemTicketTable = Item_Ticket::getTable(); - $ticketTable = Ticket::getTable(); - $pattern = '
'; - $tickets = $DB->request([ - 'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']], - 'FROM' => $ticketTable, - 'INNER JOIN' => [ - $itemTicketTable => [ - 'FKEY' => [ - $ticketTable => Ticket::getIndexName(), - $itemTicketTable => Ticket::getForeignKeyField(), - ], - 'AND' => [ - "$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(), - ] - ], - ], - 'WHERE' => [ - "$ticketTable.content" => ['LIKE', '%' . $pattern . '%'], // Matches bad encoding for 'br /' - ], - ]); + $items = $this->getInvalidData('
'); // Matches bad encoding for 'br /' - $count = $tickets->count(); - if ($count < 1) { - $output->writeln('-> No ticket to fix.'); - $output->writeln(""); - return 0; + // No items found, nothing to do + $count = $items->count(); + if ($count === 0) { + $output->writeln(__("No invalid items found.", "formcreator")); + return; } - $output->write("-> Found $count tickets to clean (literal BR tag)"); - $output->writeln(""); - $output->write("-> Cleaning tickets..."); - $output->writeln(""); - foreach ($tickets as $row) { + // Init progress bar + $output->writeln(__("Found $count item(s) to clean.", "formcreator")); + $progress_bar = new ProgressBar($output); + + foreach ($progress_bar->iterate($items) as $item) { $pattern = [ '
', ]; // Determine if we must use legacy or new encoding // @see Sanitizer::sanitize() $replace = null; - if (strpos($row['content'], '<') !== false && strpos($row['content'], '#60;') === false) { + if (strpos($item['content'], '<') !== false && strpos($item['content'], '#60;') === false) { $replace = [ '<br />', ]; - } else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '<') === false) { + } else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '<') === false) { $replace = [ '<br />', ]; } if ($replace === null) { - $output->write("-> Unable to determine the encoding type of ticket ID: " . $row['id']. ""); + $message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator"); + $output->writeln("" . sprintf($message, $item['id']) . ""); continue; } - $row['content'] = str_replace($pattern, $replace, $row['content']); + $item['content'] = str_replace($pattern, $replace, $item['content']); + // Direct write to the table to avoid alteration of other fields $DB->update( - $ticketTable, - [ - 'content' => $DB->escape($row['content']) - ], - [ - 'id' => $row['id'], - ] + $item['itemtype']::getTable(), + ['content' => $DB->escape($item['content'])], + ['id' => $item['id']] ); } + $output->writeln(""); } /** @@ -220,71 +246,55 @@ protected function fixBadForm_2(InputInterface $input, OutputInterface $output) protected function fixBadForm_3(InputInterface $input, OutputInterface $output) { global $DB; + // Print step info + $output->writeln(""); + $message = __("Step 3: litteral > sign.", "formcreator"); + $output->writeln("$message"); + // Search tickets having HTML tags
- $itemTicketTable = Item_Ticket::getTable(); - $ticketTable = Ticket::getTable(); - $pattern = ' > '; // greater than sign with a space before and after - $tickets = $DB->request([ - 'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']], - 'FROM' => $ticketTable, - 'INNER JOIN' => [ - $itemTicketTable => [ - 'FKEY' => [ - $ticketTable => Ticket::getIndexName(), - $itemTicketTable => Ticket::getForeignKeyField(), - ], - 'AND' => [ - "$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(), - ] - ], - ], - 'WHERE' => [ - "$ticketTable.content" => ['LIKE', '%' . $pattern . '%'], - ], - ]); + $items = $this->getInvalidData(' > '); - $count = $tickets->count(); - if ($count < 1) { - $output->writeln('-> No ticket to fix.'); - $output->writeln(""); - return 0; + // No items found, nothing to do + $count = $items->count(); + if ($count === 0) { + $output->writeln(__("No invalid items found.", "formcreator")); + return; } - $output->write("-> Found $count tickets to clean (litteral > sign)"); - $output->writeln(""); - $output->write("-> Cleaning tickets..."); - $output->writeln(""); - foreach ($tickets as $row) { + // Init progress bar + $output->writeln(__("Found $count item(s) to clean.", "formcreator")); + $progress_bar = new ProgressBar($output); + + foreach ($progress_bar->iterate($items) as $item) { $pattern = [ ' > ', ]; // Determine if we must use legacy or new encoding // @see Sanitizer::sanitize() $replace = null; - if (strpos($row['content'], '<') !== false && strpos($row['content'], '#60;') === false) { + if (strpos($item['content'], '<') !== false && strpos($item['content'], '#60;') === false) { $replace = [ ' > ', ]; - } else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '<') === false) { + } else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '<') === false) { $replace = [ ' & ', ]; } if ($replace === null) { - $output->write("-> Unable to determine the encoding type of ticket ID: " . $row['id']. ""); + $message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator"); + $output->writeln("" . sprinf($message, $item['id']) . ""); continue; } - $row['content'] = str_replace($pattern, $replace, $row['content']); + $item['content'] = str_replace($pattern, $replace, $item['content']); + // Direct write to the table to avoid alteration of other fields $DB->update( - $ticketTable, - [ - 'content' => $DB->escape($row['content']) - ], - [ - 'id' => $row['id'], - ] + $item['itemtype']::getTable(), + ['content' => $DB->escape($item['content'])], + ['id' => $item['id']] ); } + $output->writeln(""); } }