Skip to content

Commit

Permalink
feat(clean_tickets_command): Take merged tickets into account
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrienClairembault authored May 3, 2023
1 parent 9a01e1f commit 768cd46
Showing 1 changed file with 138 additions and 128 deletions.
266 changes: 138 additions & 128 deletions inc/command/cleanticketscommand.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -48,66 +50,108 @@ protected function configure() {
}

protected function execute(InputInterface $input, OutputInterface $output) {
$output->write("<info>-> Search tickets to clean...</info>");
$output->writeln("");
$message = __("Searching for invalid items...", "formcreator");
$output->writeln("<info>$message</info>");

$this->fixBadForm_1($input, $output);
$this->fixBadForm_2($input, $output);
$this->fixBadForm_3($input, $output);

$output->writeln('<info>Done.</info>');
return 0;
$output->writeln("");
$message = __("Done.", "formcreator");
$output->writeln("<info>$message</info>");

return Command::SUCCESS;
}

/**
* fix HTML tags double encoded
* <p> => &lt;p&gt; => &#38;lt;p&#38;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
// &#38;lt;p&#38;gt;Hello world&#38;lt;/p&#38;gt;
// Hello world is between <p> and </p>, but with wrong escaping
$itemTicketTable = Item_Ticket::getTable();
$ticketTable = Ticket::getTable();
$pattern = '&#38;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('<info>-> No ticket to fix.</info>');
$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("<info>-> Found $count tickets to clean (double encoded < and > signs)</info>");
$output->writeln("");
$output->write("<info>-> Cleaning tickets...</info>");
return $DB->request(new \QueryUnion([$tickets_query, $followup_query]));
}

/**
* fix HTML tags double encoded
* <p> => &lt;p&gt; => &#38;lt;p&#38;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("<info>$message</info>");

// Search tickets having HTML tags in content in the following form
// &#38;lt;p&#38;gt;Hello world&#38;lt;/p&#38;gt;
// Hello world is between <p> and </p>, but with wrong escaping
$items = $this->getInvalidData('&#38;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 = [
'/&#38;lt;([a-z0-9]+?)&#38;gt;/',
'/&#38;lt;(\/[a-z0-9]+?)&#38;gt;/',
Expand All @@ -116,18 +160,16 @@ protected function fixBadForm_1(InputInterface $input, OutputInterface $output)
'&#60;$1&#62;',
'&#60;$1&#62;',
];
$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("");
}

/**
Expand All @@ -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("<info>$message</info>");

// Search tickets having HTML tags <br />
$itemTicketTable = Item_Ticket::getTable();
$ticketTable = Ticket::getTable();
$pattern = '<br />';
$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('<br />'); // Matches bad encoding for 'br /'

$count = $tickets->count();
if ($count < 1) {
$output->writeln('<info>-> No ticket to fix.</info>');
$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("<info>-> Found $count tickets to clean (literal BR tag)</info>");
$output->writeln("");
$output->write("<info>-> Cleaning tickets...</info>");
$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 = [
'<br />',
];
// Determine if we must use legacy or new encoding
// @see Sanitizer::sanitize()
$replace = null;
if (strpos($row['content'], '&lt;') !== false && strpos($row['content'], '#60;') === false) {
if (strpos($item['content'], '&lt;') !== false && strpos($item['content'], '#60;') === false) {
$replace = [
'&lt;br /&gt;',
];
} else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '&lt;') === false) {
} else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '&lt;') === false) {
$replace = [
'&#60;br /&#62;',
];
}
if ($replace === null) {
$output->write("<error>-> Unable to determine the encoding type of ticket ID: " . $row['id']. "</error>");
$message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator");
$output->writeln("<error>" . sprintf($message, $item['id']) . "</error>");
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("");
}

/**
Expand All @@ -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("<info>$message</info>");

// Search tickets having HTML tags <br />
$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('<info>-> No ticket to fix.</info>');
$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("<info>-> Found $count tickets to clean (litteral > sign)</info>");
$output->writeln("");
$output->write("<info>-> Cleaning tickets...</info>");
$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'], '&lt;') !== false && strpos($row['content'], '#60;') === false) {
if (strpos($item['content'], '&lt;') !== false && strpos($item['content'], '#60;') === false) {
$replace = [
' &gt; ',
];
} else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '&lt;') === false) {
} else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '&lt;') === false) {
$replace = [
' &#38; ',
];
}
if ($replace === null) {
$output->write("<error>-> Unable to determine the encoding type of ticket ID: " . $row['id']. "</error>");
$message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator");
$output->writeln("<error>" . sprinf($message, $item['id']) . "</error>");
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("");
}
}

0 comments on commit 768cd46

Please sign in to comment.