Skip to content

Commit 768cd46

Browse files
feat(clean_tickets_command): Take merged tickets into account
1 parent 9a01e1f commit 768cd46

File tree

1 file changed

+138
-128
lines changed

1 file changed

+138
-128
lines changed

inc/command/cleanticketscommand.class.php

+138-128
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131

3232
namespace GlpiPlugin\Formcreator\Command;
3333

34+
use ITILFollowup;
35+
use QuerySubQuery;
3436
use Symfony\Component\Console\Command\Command;
37+
use Symfony\Component\Console\Helper\ProgressBar;
3538
use Symfony\Component\Console\Input\InputInterface;
3639
use Symfony\Component\Console\Output\OutputInterface;
3740
use Ticket;
3841
use Item_Ticket;
3942
use PluginFormcreatorFormAnswer;
40-
use Glpi\Toolbox\Sanitizer;
4143

4244
class CleanTicketsCommand extends Command
4345
{
@@ -48,66 +50,108 @@ protected function configure() {
4850
}
4951

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

5456
$this->fixBadForm_1($input, $output);
5557
$this->fixBadForm_2($input, $output);
5658
$this->fixBadForm_3($input, $output);
5759

58-
$output->writeln('<info>Done.</info>');
59-
return 0;
60+
$output->writeln("");
61+
$message = __("Done.", "formcreator");
62+
$output->writeln("<info>$message</info>");
63+
64+
return Command::SUCCESS;
6065
}
6166

6267
/**
63-
* fix HTML tags double encoded
64-
* <p> => &lt;p&gt; => &#38;lt;p&#38;gt;
68+
* Get invalid data using a specific regex pattern to detect invalid content
6569
*
66-
* @param InputInterface $input
67-
* @param OutputInterface $output
68-
* @return void
70+
* @param string $invalid_content_pattern
71+
*
72+
* @return iterable
6973
*/
70-
protected function fixBadForm_1(InputInterface $input, OutputInterface $output) {
74+
protected function getInvalidData(string $invalid_content_pattern): iterable {
7175
global $DB;
7276

73-
// Search tickets having HTML tags in content in the following form
74-
// &#38;lt;p&#38;gt;Hello world&#38;lt;/p&#38;gt;
75-
// Hello world is between <p> and </p>, but with wrong escaping
76-
$itemTicketTable = Item_Ticket::getTable();
77-
$ticketTable = Ticket::getTable();
78-
$pattern = '&#38;lt;';
79-
// $pattern = str_replace(';', '\\;', $pattern);
80-
$tickets = $DB->request([
81-
'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']],
82-
'FROM' => $ticketTable,
77+
$item_ticket_table = Item_Ticket::getTable();
78+
$ticket_table = Ticket::getTable();
79+
$followup_table = ITILFollowup::getTable();
80+
81+
// First source: tickets description
82+
$tickets_query = new QuerySubQuery([
83+
'SELECT' => [
84+
new \QueryExpression($DB->quoteValue(Ticket::getType()) . ' AS ' . $DB->quoteName('itemtype')),
85+
$ticket_table => [Ticket::getIndexName(), 'content']
86+
],
87+
'FROM' => $ticket_table,
8388
'INNER JOIN' => [
84-
$itemTicketTable => [
89+
$item_ticket_table => [
8590
'FKEY' => [
86-
$ticketTable => Ticket::getIndexName(),
87-
$itemTicketTable => Ticket::getForeignKeyField(),
91+
$ticket_table => Ticket::getIndexName(),
92+
$item_ticket_table => Ticket::getForeignKeyField(),
8893
],
8994
'AND' => [
90-
"$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(),
95+
"$item_ticket_table.itemtype" => PluginFormcreatorFormAnswer::getType(),
9196
]
9297
],
9398
],
9499
'WHERE' => [
95-
"$ticketTable.content" => ['LIKE', '%' . $pattern . '%'], // Matches bad encoding for '<'
100+
"$ticket_table.content" => ['LIKE', '%' . $invalid_content_pattern . '%'],
96101
],
97102
]);
98103

99-
$count = $tickets->count();
100-
if ($count < 1) {
101-
$output->writeln('<info>-> No ticket to fix.</info>');
102-
$output->writeln("");
103-
return 0;
104-
}
104+
// Second source: tickets that where merged into other tickets as a followup
105+
// These followups may have been a former ticket generated by formcreator
106+
$followup_query = new QuerySubquery([
107+
'SELECT' => [
108+
new \QueryExpression($DB->quoteValue(ITILFollowup::getType()) . ' AS ' . $DB->quoteName('itemtype')),
109+
ITILFollowup::getIndexName(),
110+
'content'
111+
],
112+
'FROM' => $followup_table,
113+
'WHERE' => [
114+
"sourceitems_id" => [">", 0], // Former tickets merged as followups
115+
"$followup_table.content" => ['LIKE', '%' . $invalid_content_pattern . '%'],
116+
],
117+
]);
105118

106-
$output->write("<info>-> Found $count tickets to clean (double encoded < and > signs)</info>");
107-
$output->writeln("");
108-
$output->write("<info>-> Cleaning tickets...</info>");
119+
return $DB->request(new \QueryUnion([$tickets_query, $followup_query]));
120+
}
121+
122+
/**
123+
* fix HTML tags double encoded
124+
* <p> => &lt;p&gt; => &#38;lt;p&#38;gt;
125+
*
126+
* @param InputInterface $input
127+
* @param OutputInterface $output
128+
* @return void
129+
*/
130+
protected function fixBadForm_1(InputInterface $input, OutputInterface $output) {
131+
global $DB;
132+
133+
// Print step info
109134
$output->writeln("");
110-
foreach ($tickets as $row) {
135+
$message = __("Step 1: double encoded < and > signs.", "formcreator");
136+
$output->writeln("<info>$message</info>");
137+
138+
// Search tickets having HTML tags in content in the following form
139+
// &#38;lt;p&#38;gt;Hello world&#38;lt;/p&#38;gt;
140+
// Hello world is between <p> and </p>, but with wrong escaping
141+
$items = $this->getInvalidData('&#38;lt;'); // Matches bad encoding for '<'
142+
143+
// No items found, nothing to do
144+
$count = $items->count();
145+
if ($count === 0) {
146+
$output->writeln(__("No invalid items found.", "formcreator"));
147+
return;
148+
}
149+
150+
// Init progress bar
151+
$output->writeln(__("Found $count item(s) to clean.", "formcreator"));
152+
$progress_bar = new ProgressBar($output);
153+
154+
foreach ($progress_bar->iterate($items) as $item) {
111155
$pattern = [
112156
'/&#38;lt;([a-z0-9]+?)&#38;gt;/',
113157
'/&#38;lt;(\/[a-z0-9]+?)&#38;gt;/',
@@ -116,18 +160,16 @@ protected function fixBadForm_1(InputInterface $input, OutputInterface $output)
116160
'&#60;$1&#62;',
117161
'&#60;$1&#62;',
118162
];
119-
$row['content'] = preg_replace($pattern, $replace, $row['content']);
163+
$item['content'] = preg_replace($pattern, $replace, $item['content']);
164+
120165
// Direct write to the table to avoid alteration of other fields
121166
$DB->update(
122-
$ticketTable,
123-
[
124-
'content' => $DB->escape($row['content'])
125-
],
126-
[
127-
'id' => $row['id'],
128-
]
167+
$item['itemtype']::getTable(),
168+
['content' => $DB->escape($item['content'])],
169+
['id' => $item['id']]
129170
);
130171
}
172+
$output->writeln("");
131173
}
132174

133175
/**
@@ -140,72 +182,56 @@ protected function fixBadForm_1(InputInterface $input, OutputInterface $output)
140182
protected function fixBadForm_2(InputInterface $input, OutputInterface $output) {
141183
global $DB;
142184

185+
// Print step info
186+
$output->writeln("");
187+
$message = __("Step 2: literal BR tag.", "formcreator");
188+
$output->writeln("<info>$message</info>");
189+
143190
// Search tickets having HTML tags <br />
144-
$itemTicketTable = Item_Ticket::getTable();
145-
$ticketTable = Ticket::getTable();
146-
$pattern = '<br />';
147-
$tickets = $DB->request([
148-
'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']],
149-
'FROM' => $ticketTable,
150-
'INNER JOIN' => [
151-
$itemTicketTable => [
152-
'FKEY' => [
153-
$ticketTable => Ticket::getIndexName(),
154-
$itemTicketTable => Ticket::getForeignKeyField(),
155-
],
156-
'AND' => [
157-
"$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(),
158-
]
159-
],
160-
],
161-
'WHERE' => [
162-
"$ticketTable.content" => ['LIKE', '%' . $pattern . '%'], // Matches bad encoding for 'br /'
163-
],
164-
]);
191+
$items = $this->getInvalidData('<br />'); // Matches bad encoding for 'br /'
165192

166-
$count = $tickets->count();
167-
if ($count < 1) {
168-
$output->writeln('<info>-> No ticket to fix.</info>');
169-
$output->writeln("");
170-
return 0;
193+
// No items found, nothing to do
194+
$count = $items->count();
195+
if ($count === 0) {
196+
$output->writeln(__("No invalid items found.", "formcreator"));
197+
return;
171198
}
172199

173-
$output->write("<info>-> Found $count tickets to clean (literal BR tag)</info>");
174-
$output->writeln("");
175-
$output->write("<info>-> Cleaning tickets...</info>");
176-
$output->writeln("");
177-
foreach ($tickets as $row) {
200+
// Init progress bar
201+
$output->writeln(__("Found $count item(s) to clean.", "formcreator"));
202+
$progress_bar = new ProgressBar($output);
203+
204+
foreach ($progress_bar->iterate($items) as $item) {
178205
$pattern = [
179206
'<br />',
180207
];
181208
// Determine if we must use legacy or new encoding
182209
// @see Sanitizer::sanitize()
183210
$replace = null;
184-
if (strpos($row['content'], '&lt;') !== false && strpos($row['content'], '#60;') === false) {
211+
if (strpos($item['content'], '&lt;') !== false && strpos($item['content'], '#60;') === false) {
185212
$replace = [
186213
'&lt;br /&gt;',
187214
];
188-
} else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '&lt;') === false) {
215+
} else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '&lt;') === false) {
189216
$replace = [
190217
'&#60;br /&#62;',
191218
];
192219
}
193220
if ($replace === null) {
194-
$output->write("<error>-> Unable to determine the encoding type of ticket ID: " . $row['id']. "</error>");
221+
$message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator");
222+
$output->writeln("<error>" . sprintf($message, $item['id']) . "</error>");
195223
continue;
196224
}
197-
$row['content'] = str_replace($pattern, $replace, $row['content']);
225+
$item['content'] = str_replace($pattern, $replace, $item['content']);
226+
198227
// Direct write to the table to avoid alteration of other fields
199228
$DB->update(
200-
$ticketTable,
201-
[
202-
'content' => $DB->escape($row['content'])
203-
],
204-
[
205-
'id' => $row['id'],
206-
]
229+
$item['itemtype']::getTable(),
230+
['content' => $DB->escape($item['content'])],
231+
['id' => $item['id']]
207232
);
208233
}
234+
$output->writeln("");
209235
}
210236

211237
/**
@@ -220,71 +246,55 @@ protected function fixBadForm_2(InputInterface $input, OutputInterface $output)
220246
protected function fixBadForm_3(InputInterface $input, OutputInterface $output) {
221247
global $DB;
222248

249+
// Print step info
250+
$output->writeln("");
251+
$message = __("Step 3: litteral > sign.", "formcreator");
252+
$output->writeln("<info>$message</info>");
253+
223254
// Search tickets having HTML tags <br />
224-
$itemTicketTable = Item_Ticket::getTable();
225-
$ticketTable = Ticket::getTable();
226-
$pattern = ' > '; // greater than sign with a space before and after
227-
$tickets = $DB->request([
228-
'SELECT' => [$ticketTable => [Ticket::getIndexName(), 'content']],
229-
'FROM' => $ticketTable,
230-
'INNER JOIN' => [
231-
$itemTicketTable => [
232-
'FKEY' => [
233-
$ticketTable => Ticket::getIndexName(),
234-
$itemTicketTable => Ticket::getForeignKeyField(),
235-
],
236-
'AND' => [
237-
"$itemTicketTable.itemtype" => PluginFormcreatorFormAnswer::getType(),
238-
]
239-
],
240-
],
241-
'WHERE' => [
242-
"$ticketTable.content" => ['LIKE', '%' . $pattern . '%'],
243-
],
244-
]);
255+
$items = $this->getInvalidData(' > ');
245256

246-
$count = $tickets->count();
247-
if ($count < 1) {
248-
$output->writeln('<info>-> No ticket to fix.</info>');
249-
$output->writeln("");
250-
return 0;
257+
// No items found, nothing to do
258+
$count = $items->count();
259+
if ($count === 0) {
260+
$output->writeln(__("No invalid items found.", "formcreator"));
261+
return;
251262
}
252263

253-
$output->write("<info>-> Found $count tickets to clean (litteral > sign)</info>");
254-
$output->writeln("");
255-
$output->write("<info>-> Cleaning tickets...</info>");
256-
$output->writeln("");
257-
foreach ($tickets as $row) {
264+
// Init progress bar
265+
$output->writeln(__("Found $count item(s) to clean.", "formcreator"));
266+
$progress_bar = new ProgressBar($output);
267+
268+
foreach ($progress_bar->iterate($items) as $item) {
258269
$pattern = [
259270
' > ',
260271
];
261272
// Determine if we must use legacy or new encoding
262273
// @see Sanitizer::sanitize()
263274
$replace = null;
264-
if (strpos($row['content'], '&lt;') !== false && strpos($row['content'], '#60;') === false) {
275+
if (strpos($item['content'], '&lt;') !== false && strpos($item['content'], '#60;') === false) {
265276
$replace = [
266277
' &gt; ',
267278
];
268-
} else if (strpos($row['content'], '#60') !== false && strpos($row['content'], '&lt;') === false) {
279+
} else if (strpos($item['content'], '#60') !== false && strpos($item['content'], '&lt;') === false) {
269280
$replace = [
270281
' &#38; ',
271282
];
272283
}
273284
if ($replace === null) {
274-
$output->write("<error>-> Unable to determine the encoding type of ticket ID: " . $row['id']. "</error>");
285+
$message = __("Unable to determine the encoding type of item ID: %1$d", "formcreator");
286+
$output->writeln("<error>" . sprinf($message, $item['id']) . "</error>");
275287
continue;
276288
}
277-
$row['content'] = str_replace($pattern, $replace, $row['content']);
289+
$item['content'] = str_replace($pattern, $replace, $item['content']);
290+
278291
// Direct write to the table to avoid alteration of other fields
279292
$DB->update(
280-
$ticketTable,
281-
[
282-
'content' => $DB->escape($row['content'])
283-
],
284-
[
285-
'id' => $row['id'],
286-
]
293+
$item['itemtype']::getTable(),
294+
['content' => $DB->escape($item['content'])],
295+
['id' => $item['id']]
287296
);
288297
}
298+
$output->writeln("");
289299
}
290300
}

0 commit comments

Comments
 (0)