XmlProcessor   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 497
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 9
Bugs 1 Features 0
Metric Value
eloc 163
c 9
b 1
f 0
dl 0
loc 497
rs 3.36
ccs 0
cts 299
cp 0
wmc 63

21 Methods

Rating   Name   Duplication   Size   Complexity  
A doPresqlStep() 0 29 5
A insertStatement() 0 21 3
A setImporter() 0 3 1
A prepareRow() 0 16 2
A shoudNotAdd() 0 3 2
A prepareSpecialResult() 0 9 2
A shouldReplace() 0 3 2
A fix_params() 0 16 4
A doDetect() 0 7 2
A advanceSubstep() 0 9 2
A ignoreSlashes() 0 3 2
A updateStatus() 0 33 5
A fixCurrentData() 0 8 2
A insertRows() 0 30 4
A detect() 0 25 3
A shouldIgnore() 0 8 3
B doSql() 0 80 8
A processSteps() 0 32 6
A getPreparsecode() 0 8 2
A __construct() 0 6 1
A doCode() 0 20 2

How to fix   Complexity   

Complex Class

Complex classes like XmlProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @name      OpenImporter
4
 * @copyright OpenImporter contributors
5
 * @license   BSD https://opensource.org/licenses/BSD-3-Clause
6
 *
7
 * @version 1.0
8
 *
9
 * This file contains code based on:
10
 *
11
 * Simple Machines Forum (SMF)
12
 * copyright:    2011 Simple Machines (http://www.simplemachines.org)
13
 * license:    BSD, See included LICENSE.TXT for terms and conditions.
14
 */
15
16
namespace OpenImporter;
17
18
/**
19
 * Class XmlProcessor
20
 * Object Importer creates the main XML object.
21
 * It detects and initializes the script to run.
22
 *
23
 * @class XmlProcessor
24
 *
25
 */
26
class XmlProcessor
27
{
28
	/** @var \OpenImporter\Database This is our main database object. */
29
	protected $db;
30
31
	/** @var \OpenImporter\Configurator Contains any kind of configuration. */
32
	public $config;
33
34
	/** @var \OpenImporter\Template The template */
35
	public $template;
36
37
	/** @var object The xml object containing the settings. Required (for now) to convert IPs (v4/6) */
38
	public $xml;
39
40
	/** @var object The step running in this very moment, right here, right now. */
41
	public $current_step;
42
43
	/** @var object Holds all the methods required to perform the conversion. */
44
	public $step1_importer;
45
46
	/** @var string[] Holds the row result from the query */
47
	public $row;
48
49
	/** @var mixed Holds the processed rows */
50
	public $rows;
51
52
	/** @var mixed Holds the (processed) row keys */
53
	public $keys;
54
55
	/**
56
	 * XmlProcessor constructor.
57
	 *
58
	 * Initialize the main Importer object
59
	 *
60
	 * @param Database $db
61
	 * @param Configurator $config
62
	 * @param Template $template
63
	 * @param object $xml (output from SimpleXMLElement)
64
	 */
65
	public function __construct($db, $config, $template, $xml)
66
	{
67
		$this->db = $db;
68
		$this->config = $config;
69
		$this->template = $template;
70
		$this->xml = $xml;
71
	}
72
73
	public function setImporter($step1_importer)
74
	{
75
		$this->step1_importer = $step1_importer;
76
	}
77
78
	/**
79
	 * Loop through each of the steps in the XML file
80
	 *
81
	 * @param int $step
82
	 * @param int $substep
83
	 * @param array $do_steps
84
	 */
85
	public function processSteps($step, &$substep, $do_steps)
86
	{
87
		$this->current_step = $step;
0 ignored issues
show
Documentation Bug introduced by
It seems like $step of type integer is incompatible with the declared type object of property $current_step.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
88
		$table_test = $this->updateStatus($substep, $do_steps);
89
90
		// Do we need to skip this step?
91
		if ($table_test === false || !in_array($substep, $do_steps))
92
		{
93
			return;
94
		}
95
96
		// Pre sql queries (<presql> & <presqlMethod>) first!!
97
		$this->doPresqlStep($substep);
98
99
		// Codeblock? (<code></code>) Then no query.
100
		if ($this->doCode($substep))
101
		{
102
			$this->advanceSubstep($substep);
103
104
			return;
105
		}
106
107
		// sql (<query></query>) block?
108
		// @todo $_GET
109
		if ($substep >= $_GET['substep'] && isset($this->current_step->query))
110
		{
111
			$this->doSql($substep);
112
113
			$_REQUEST['start'] = 0;
114
		}
115
116
		$this->advanceSubstep($substep);
117
	}
118
119
	/**
120
	 * Perform query's as defined in the XML
121
	 *
122
	 * @param int $substep
123
	 */
124
	protected function doSql($substep)
125
	{
126
		// These are temporarily needed to support the current xml importers
127
		// a.k.a. There is more important stuff to do.
128
		// a.k.a. I'm too lazy to change all of them now. :P
129
		// @todo remove
130
		// Both used in eval'ed code
131
		$to_prefix = $this->config->to_prefix;
0 ignored issues
show
Unused Code introduced by
The assignment to $to_prefix is dead and can be removed.
Loading history...
132
		$db = $this->db;
0 ignored issues
show
Unused Code introduced by
The assignment to $db is dead and can be removed.
Loading history...
133
134
		// Update {$from_prefix} and {$to_prefix} in the query
135
		$current_data = substr(rtrim($this->fix_params((string) $this->current_step->query)), 0, -1);
136
		$current_data = $this->fixCurrentData($current_data);
137
138
		$this->doDetect($substep);
139
140
		if (isset($this->current_step->destination))
141
		{
142
			// Prepare the <query>
143
			$special_table = strtr(trim((string) $this->current_step->destination), array('{$to_prefix}' => $this->config->to_prefix));
144
145
			// Any specific LIMIT set
146
			$special_limit = $this->current_step->options->limit ?? 500;
147
148
			// Any <preparsecode> code? Loaded here to be used later.
149
			$special_code = $this->getPreparsecode();
150
151
			// Create some handy shortcuts
152
			$no_add = $this->shoudNotAdd($this->current_step->options);
153
154
			$this->step1_importer->doSpecialTable($special_table);
155
156
			while (true)
157
			{
158
				pastTime($substep);
159
160
				$special_result = $this->prepareSpecialResult($current_data, $special_limit);
161
162
				$this->rows = array();
163
				$this->keys = array();
164
165
				if (isset($this->current_step->detect))
166
				{
167
					$_SESSION['import_progress'] += $special_limit;
168
				}
169
170
				while ($this->row = $this->db->fetch_assoc($special_result))
171
				{
172
					if ($no_add)
173
					{
174
						eval($special_code);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
175
					}
176
					else
177
					{
178
						// Process the row (preparse, charset, etc)
179
						$this->prepareRow($special_code, $special_table);
180
						$this->rows[] = $this->row;
181
182
						if (empty($this->keys))
183
						{
184
							$this->keys = array_keys($this->row);
185
						}
186
					}
187
				}
188
189
				$this->insertRows($special_table);
190
191
				$_REQUEST['start'] += $special_limit;
192
193
				if ($this->db->num_rows($special_result) < $special_limit)
194
				{
195
					break;
196
				}
197
198
				$this->db->free_result($special_result);
199
			}
200
		}
201
		else
202
		{
203
			$this->db->query($current_data);
204
		}
205
	}
206
207
	protected function fixCurrentData($current_data)
208
	{
209
		if (strpos($current_data, '{$') !== false)
210
		{
211
			$current_data = eval('return "' . addcslashes($current_data, '\\"') . '";');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
212
		}
213
214
		return $current_data;
215
	}
216
217
	protected function insertRows($special_table)
218
	{
219
		// Nothing to insert?
220
		if (empty($this->rows))
221
		{
222
			return;
223
		}
224
225
		$insert_statement = $this->insertStatement($this->current_step->options);
226
		$ignore_slashes = $this->ignoreSlashes($this->current_step->options);
227
228
		// Build the insert
229
		$insert_rows = array();
230
		foreach ($this->rows as $row)
231
		{
232
			if (empty($ignore_slashes))
233
			{
234
				$insert_rows[] = "'" . implode("', '", addslashes_recursive($row)) . "'";
0 ignored issues
show
Bug introduced by
It seems like addslashes_recursive($row) can also be of type string; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
				$insert_rows[] = "'" . implode("', '", /** @scrutinizer ignore-type */ addslashes_recursive($row)) . "'";
Loading history...
235
			}
236
			else
237
			{
238
				$insert_rows[] = "'" . implode("', '", $row) . "'";
239
			}
240
		}
241
242
		$this->db->query("
243
			$insert_statement $special_table
244
				(" . implode(', ', $this->keys) . ")
245
			VALUES (" . implode('),
246
				(', $insert_rows) . ")");
247
	}
248
249
	protected function prepareRow($special_code, $special_table)
250
	{
251
		// Take case of preparsecode
252
		if ($special_code !== null)
253
		{
254
			$row = $this->row;
255
			eval($special_code);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
256
			$this->row = $row;
257
		}
258
259
		$this->row = $this->step1_importer->doSpecialTable($special_table, $this->row);
260
261
		// Fixing the charset, we need proper utf-8
262
		$this->row = fix_charset($this->row);
0 ignored issues
show
Documentation Bug introduced by
It seems like fix_charset($this->row) can also be of type string. However, the property $row is declared as type string[]. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
263
264
		$this->row = $this->step1_importer->fixTexts($this->row);
265
	}
266
267
	/**
268
	 * Return any <preparsecode></preparsecode> for the step
269
	 *
270
	 * @return null|string
271
	 */
272
	protected function getPreparsecode()
273
	{
274
		if (!empty($this->current_step->preparsecode))
275
		{
276
			return $this->fix_params((string) $this->current_step->preparsecode);
277
		}
278
279
		return null;
280
	}
281
282
	protected function advanceSubstep($substep)
283
	{
284
		if ($_SESSION['import_steps'][$substep]['status'] == 0)
285
		{
286
			$this->template->status($substep, 1, false, true);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $title of OpenImporter\Template::status(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

286
			$this->template->status($substep, 1, /** @scrutinizer ignore-type */ false, true);
Loading history...
287
		}
288
289
		$_SESSION['import_steps'][$substep]['status'] = 1;
290
		flush();
291
	}
292
293
	/**
294
	 * Used to replace {$from_prefix} and {$to_prefix} with its real values.
295
	 *
296
	 * @param string string in which parameters are replaced
297
	 *
298
	 * @return string
299
	 */
300
	public function fix_params($string)
301
	{
302
		if (isset($_SESSION['import_parameters']))
303
		{
304
			foreach ($_SESSION['import_parameters'] as $param)
305
			{
306
				foreach ($param as $key => $value)
307
				{
308
					$string = strtr($string, array('{$' . $key . '}' => $value));
309
				}
310
			}
311
		}
312
313
		$string = strtr($string, array('{$from_prefix}' => $this->config->from_prefix, '{$to_prefix}' => $this->config->to_prefix));
314
315
		return trim($string);
316
	}
317
318
	protected function updateStatus(&$substep, $do_steps)
319
	{
320
		$table_test = true;
321
322
		// Increase the substep slightly...
323
		pastTime(++$substep);
324
325
		$_SESSION['import_steps'][$substep]['title'] = (string) $this->current_step->title;
326
		if (!isset($_SESSION['import_steps'][$substep]['status']))
327
		{
328
			$_SESSION['import_steps'][$substep]['status'] = 0;
329
		}
330
331
		if (!in_array($substep, $do_steps))
332
		{
333
			$_SESSION['import_steps'][$substep]['status'] = 2;
334
			$_SESSION['import_steps'][$substep]['presql'] = true;
335
		}
336
		// Detect the table, then count rows..
337
		elseif ($this->current_step->detect)
338
		{
339
			$table_test = $this->detect((string) $this->current_step->detect);
340
341
			if ($table_test === false)
342
			{
343
				$_SESSION['import_steps'][$substep]['status'] = 3;
344
				$_SESSION['import_steps'][$substep]['presql'] = true;
345
			}
346
		}
347
348
		$this->template->status($substep, $_SESSION['import_steps'][$substep]['status'], $_SESSION['import_steps'][$substep]['title']);
349
350
		return $table_test;
351
	}
352
353
	/**
354
	 * Runs any presql commands or calls any presqlMethods
355
	 *
356
	 * @param array $substep
357
	 */
358
	protected function doPresqlStep($substep)
359
	{
360
		if (!isset($this->current_step->presql))
361
		{
362
			return;
363
		}
364
365
		if (isset($_SESSION['import_steps'][$substep]['presql']))
366
		{
367
			return;
368
		}
369
370
		// Calling a pre method?
371
		if (isset($this->current_step->presqlMethod))
372
		{
373
			$this->step1_importer->beforeSql((string) $this->current_step->presqlMethod);
374
		}
375
376
		// Prepare presql commands for the query
377
		$presql = $this->fix_params((string) $this->current_step->presql);
378
		$presql_array = array_filter(explode(';', $presql));
379
380
		foreach ($presql_array as $exec)
381
		{
382
			$this->db->query($exec . ';');
383
		}
384
385
		// Don't do this twice.
386
		$_SESSION['import_steps'][$substep]['presql'] = true;
387
	}
388
389
	/**
390
	 * Process a <detect> statement
391
	 * @param $substep
392
	 */
393
	protected function doDetect($substep)
394
	{
395
		global $oi_import;
396
397
		if (isset($this->current_step->detect, $oi_import->count))
398
		{
399
			$oi_import->count->$substep = $this->detect((string) $this->current_step->detect);
400
		}
401
	}
402
403
	/**
404
	 * Run a <code> block
405
	 *
406
	 * @return bool
407
	 */
408
	protected function doCode($substep)
409
	{
410
		if (isset($this->current_step->code))
411
		{
412
			// These are temporarily needed to support the current xml importers
413
			// a.k.a. There is more important stuff to do.
414
			// a.k.a. I'm too lazy to change all of them now. :P
415
			// @todo remove
416
			// Both used in eval'ed code
417
			$to_prefix = $this->config->to_prefix;
0 ignored issues
show
Unused Code introduced by
The assignment to $to_prefix is dead and can be removed.
Loading history...
418
			$db = $this->db;
0 ignored issues
show
Unused Code introduced by
The assignment to $db is dead and can be removed.
Loading history...
419
420
			// Execute our code block
421
			$special_code = $this->fix_params((string) $this->current_step->code);
422
			eval($special_code);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
423
424
			return true;
425
		}
426
427
		return false;
428
	}
429
430
	/**
431
	 * Process <detect> statements from the xml file
432
	 *
433
	 * {$from_prefix}node WHERE node_type_id = 'Category'
434
	 *
435
	 * @param string $table
436
	 *
437
	 * @return bool
438
	 */
439
	protected function detect($table)
440
	{
441
		// Query substitutes
442
		$table = $this->fix_params($table);
443
		$table = preg_replace('/^`[\w\-_]*`\./', '', $this->fix_params($table));
444
445
		// Database name
446
		$db_name_str = $this->config->source->getDbName();
0 ignored issues
show
Bug introduced by
The method getDbName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

446
		/** @scrutinizer ignore-call */ 
447
  $db_name_str = $this->config->source->getDbName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
447
448
		// Simple table check or something more complex?
449
		if (strpos($table, 'WHERE') !== false)
450
		{
451
			$result = $this->db->query("
452
				SELECT COUNT(*)
453
				FROM `{$db_name_str}`.{$table}");
454
		}
455
		else
456
		{
457
			$result = $this->db->query("
458
				SHOW TABLES
459
				FROM `{$db_name_str}`
460
				LIKE '{$table}'");
461
		}
462
463
		return !($result === false || $this->db->num_rows($result) === 0);
464
	}
465
466
	protected function shouldIgnore($options)
467
	{
468
		if (isset($options->ignore) && (string) $options->ignore === "false")
469
		{
470
			return false;
471
		}
472
473
		return !isset($options->replace);
474
	}
475
476
	protected function shouldReplace($options)
477
	{
478
		return isset($options->replace) && (string) $options->replace === "true";
479
	}
480
481
	protected function shoudNotAdd($options)
482
	{
483
		return isset($options->no_add) && (string) $options->no_add === "true";
484
	}
485
486
	protected function ignoreSlashes($options)
487
	{
488
		return isset($options->ignore_slashes) && (string) $options->ignore_slashes === "true";
489
	}
490
491
	protected function insertStatement($options)
492
	{
493
		if ($this->shouldIgnore($options))
494
		{
495
			$ignore = 'IGNORE';
496
		}
497
		else
498
		{
499
			$ignore = '';
500
		}
501
502
		if ($this->shouldReplace($options))
503
		{
504
			$replace = 'REPLACE';
505
		}
506
		else
507
		{
508
			$replace = 'INSERT';
509
		}
510
511
		return $replace . ' ' . $ignore . ' INTO';
512
	}
513
514
	protected function prepareSpecialResult($current_data, $special_limit)
515
	{
516
		// @todo $_REQUEST
517
		if (strpos($current_data, '%d') !== false)
518
		{
519
			return $this->db->query(sprintf($current_data, $_REQUEST['start'], $_REQUEST['start'] + $special_limit - 1) . "\n" . 'LIMIT ' . $special_limit);
520
		}
521
522
		return $this->db->query($current_data . "\n" . 'LIMIT ' . $_REQUEST['start'] . ', ' . $special_limit);
523
	}
524
}
525