ImportManager::doStep3()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 13
ccs 0
cts 9
cp 0
crap 6
rs 10
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
if (!defined('DS'))
19
{
20
	define('DS', DIRECTORY_SEPARATOR);
21
}
22
23
/**
24
 * Object ImportManager loads the main importer.
25
 * It handles all steps to completion.
26
 *
27
 * @todo path_to should be source-specific (i.e. in /Importers/whatever/source_importer.php
28
 * @todo path_from should be destination-specific (i.e. in /Importers/whatever/whatever_importer.php
29
 *
30
 * @class ImportManager
31
 *
32
 */
33
class ImportManager
34
{
35
	/** @var \OpenImporter\Importer The importer that will act as interface */
36
	public $importer;
37
38
	/** @var \OpenImporter\Cookie Our cookie settings */
39
	protected $cookie;
40
41
	/** @var \OpenImporter\Configurator The configurator that holds all the settings */
42
	protected $config;
43
44
	/** @var \OpenImporter\Template The template, basically our UI. */
45
	public $template;
46
47
	/** @var \OpenImporter\HttpResponse The response object. */
48
	protected $response;
49
50
	/** @var \OpenImporter\Lang The language object. */
51
	protected $language;
52
53
	/** @var array An array of possible importer scripts */
54
	public $sources;
55
56
	/** @var array Data used by the script and stored in session between reload and the following one */
57
	public $data = array();
58
59
	/** @var string This is the URL from our Installation. */
60
	protected $_boardurl = '';
61
62
	/** @var string The database password */
63
	protected $db_pass = '';
64
65
	/**
66
	 * ImportManager constructor.
67
	 * Initialize the main Importer object
68
	 *
69
	 * @param Configurator $config
70
	 * @param Importer $importer
71
	 * @param Template $template
72
	 * @param Cookie $cookie
73
	 * @param HttpResponse $response
74
	 */
75
	public function __construct($config, $importer, $template, $cookie, $response)
76
	{
77
		global $time_start;
78
79
		require_once(BASEDIR . '/OpenImporter/Utils.php');
80
81
		$time_start = time();
82
83
		$this->loadFromSession();
84
85
		$this->config = $config;
86
		$this->importer = $importer;
87
		$this->cookie = $cookie;
88
		$this->template = $template;
89
		$this->response = $response;
90
		$this->language = $importer->lng;
91
		$this->response->lng = $importer->lng;
92
93
		$this->_findScript();
94
95
		// The current step - starts at 0.
96
		$_GET['step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0;
97
		$this->response->step = $_GET['step'];
98
		$_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
99
100
		$this->loadPass();
101
102
		$this->loadPaths();
103
104
		if (!empty($this->config->script))
105
		{
106
			$this->importer->reloadImporter();
107
		}
108
	}
109
110
	/**
111
	 * On close, save our session
112
	 */
113
	public function __destruct()
114
	{
115
		$this->saveInSession();
116
	}
117
118
	/**
119
	 * Need the db password to do anything useful
120
	 */
121
	protected function loadPass()
122
	{
123
		// Check for the password...
124
		if (isset($_POST['db_pass']))
125
		{
126
			$this->data['db_pass'] = $_POST['db_pass'];
127
		}
128
129
		if (isset($this->data['db_pass']))
130
		{
131
			$this->db_pass = $this->data['db_pass'];
132
		}
133
	}
134
135
	/**
136
	 * Load in the source and destination forum paths
137
	 */
138
	protected function loadPaths()
139
	{
140
		if (isset($_POST['path_from']) || isset($_POST['path_to']))
141
		{
142
			if (isset($_POST['path_from']))
143
			{
144
				$this->config->path_from = rtrim($_POST['path_from'], '\\/');
145
			}
146
147
			if (isset($_POST['path_to']))
148
			{
149
				$this->config->path_to = rtrim($_POST['path_to'], '\\/');
150
			}
151
152
			$this->data['import_paths'] = array($this->config->path_from, $this->config->path_to);
153
		}
154
		elseif (isset($this->data['import_paths']))
155
		{
156
			list ($this->config->path_from, $this->config->path_to) = $this->data['import_paths'];
157
		}
158
159
		if (!empty($this->data))
160
		{
161
			$this->importer->setData($this->data);
162
		}
163
	}
164
165
	/**
166
	 * Load session data
167
	 */
168
	protected function loadFromSession()
169
	{
170
		if (empty($_SESSION['importer_data']))
171
		{
172
			return;
173
		}
174
175
		$this->data = $_SESSION['importer_data'];
176
	}
177
178
	/**
179
	 * Save the session data
180
	 */
181
	protected function saveInSession()
182
	{
183
		$_SESSION['importer_data'] = $this->data;
184
	}
185
186
	/**
187
	 * Finds the script either in the session or in request
188
	 */
189
	protected function _findScript()
190
	{
191
		// Save here, so it doesn't get overwritten when sessions are restarted.
192
		if (isset($_REQUEST['import_script']))
193
		{
194
			$this->config->script = (string) $_REQUEST['import_script'];
195
			$this->data['import_script'] = $this->config->script;
196
		}
197
		elseif (isset($this->data['import_script']))
198
		{
199
			$this->data['import_script'] = $this->validateScript($this->data['import_script']);
200
			$this->config->script = $this->data['import_script'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->data['import_script'] can also be of type boolean. However, the property $script 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...
201
		}
202
		else
203
		{
204
			$this->config->script = '';
205
			$this->data['import_script'] = null;
206
		}
207
	}
208
209
	/**
210
	 * Prepares the response to send to the template system
211
	 */
212
	public function process()
213
	{
214
		// This is really quite simple; if ?delete is on the URL, delete the importer...
215
		if (isset($_GET['delete']))
216
		{
217
			$this->uninstall();
218
219
			$this->response->no_template = true;
0 ignored issues
show
Documentation Bug introduced by
The property $no_template was declared of type string, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
220
		}
221
222
		$this->populateResponseDetails();
223
224
		// An ajax response
225
		if (isset($_GET['xml']))
226
		{
227
			$this->response->addHeader('Content-Type', 'text/xml');
228
			$this->response->is_xml = true;
229
		}
230
		else
231
		{
232
			$this->template->header();
233
		}
234
235
		if (isset($_GET['action']) && $_GET['action'] === 'validate')
236
		{
237
			$this->validateFields();
238
		}
239
		elseif (method_exists($this, 'doStep' . $_GET['step']))
240
		{
241
			$this->{'doStep' . $_GET['step']}();
242
		}
243
		else
244
		{
245
			$this->doStep0();
246
		}
247
248
		$this->populateResponseDetails();
249
250
		$this->template->render();
251
252
		if (!isset($_GET['xml']))
253
		{
254
			$this->template->footer();
255
		}
256
	}
257
258
	/**
259
	 * Validate the step0 input fields
260
	 */
261
	protected function validateFields()
262
	{
263
		$this->_detect_scripts();
264
265
		try
266
		{
267
			$this->importer->reloadImporter();
268
		}
269
		catch (\Exception $e)
270
		{
271
			// Do nothing, let the code die
272
		}
273
274
		if (isset($_GET['path_to']))
275
		{
276
			$this->response->valid = $this->config->destination->checkSettingsPath($_GET['path_to']);
0 ignored issues
show
Bug introduced by
The method checkSettingsPath() 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

276
			/** @scrutinizer ignore-call */ 
277
   $this->response->valid = $this->config->destination->checkSettingsPath($_GET['path_to']);

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...
277
		}
278
		elseif (isset($_GET['path_from']))
279
		{
280
			$this->response->valid = $this->config->source->loadSettings($_GET['path_from'], true);
0 ignored issues
show
Bug introduced by
The method loadSettings() 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

280
			/** @scrutinizer ignore-call */ 
281
   $this->response->valid = $this->config->source->loadSettings($_GET['path_from'], true);

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...
281
		}
282
		else
283
		{
284
			$this->response->valid = false;
285
		}
286
	}
287
288
	protected function populateResponseDetails()
289
	{
290
		if (isset($this->importer->xml->general->name))
291
		{
292
			$this->response->page_title = $this->importer->xml->general->name . ' to ' . $this->config->destination->getName();
293
		}
294
		else
295
		{
296
			$this->response->page_title = 'OpenImporter';
297
		}
298
299
// 		$this->response->from = $this->importer->settings : null
300
		$this->response->script = $this->config->script;
301
	}
302
303
	/**
304
	 * Deletes the importer files from the server
305
	 *
306
	 * @todo doesn't know yet about the new structure.
307
	 */
308
	protected function uninstall()
309
	{
310
		@unlink(__FILE__);
311
312
		if (preg_match('~_importer\.xml$~', $this->data['import_script']) !== 0)
313
		{
314
			@unlink(BASEDIR . DS . $this->data['import_script']);
315
		}
316
317
		$this->data['import_script'] = null;
318
	}
319
320
	/**
321
	 * File validation, existence and format checking
322
	 *
323
	 * @param string $script
324
	 *
325
	 * @return bool|mixed
326
	 */
327
	protected function validateScript($script)
328
	{
329
		if (empty($script))
330
		{
331
			return false;
332
		}
333
334
		$script = preg_replace('~\.+~', '.', $script);
335
336
		if (file_exists(BASEDIR . DS . 'Importers' . DS . $script)
337
			&& preg_match('~_importer\.xml$~', $script) !== 0)
338
		{
339
			return $script;
340
		}
341
342
		return false;
343
	}
344
345
	/**
346
	 * Finds import scripts that we can offer for usage
347
	 *
348
	 * - checks,  if we have already specified an importer script
349
	 * - checks the file system for importer definition files
350
	 *
351
	 * @return boolean
352
	 */
353
	private function _detect_scripts()
354
	{
355
		if ($this->config->script !== null)
356
		{
357
			$this->data['import_script'] = $this->validateScript($this->data['import_script']);
358
			$this->config->script = $this->data['import_script'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->data['import_script'] can also be of type boolean. However, the property $script 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...
359
		}
360
361
		$sources = glob(BASEDIR . DS . 'Importers' . DS . '*', GLOB_ONLYDIR);
362
		$count_scripts = 0;
363
		$scripts = array();
364
		$destination_names = array();
365
		$from = '';
366
		foreach ($sources as $source)
367
		{
368
			$from = basename($source);
369
			$scripts[$from] = array();
370
			$possible_scripts = glob($source . DS . '*_importer.xml');
371
372
			// Silence simplexml errors
373
			libxml_use_internal_errors(true);
374
			foreach ($possible_scripts as $entry)
375
			{
376
				// If a script is broken simply skip it.
377
				if (!$xmlObj = simplexml_load_string(file_get_contents($entry), 'SimpleXMLElement', LIBXML_NOCDATA))
378
				{
379
					continue;
380
				}
381
382
				$scripts[$from][] = array('path' => $from . DS . basename($entry), 'name' => (string) $xmlObj->general->name);
383
				$destination_names[$from] = (string) $xmlObj->general->version;
384
				$count_scripts++;
385
			}
386
		}
387
388
		if (!empty($this->data['import_script']))
389
		{
390
			if ($count_scripts > 1)
391
			{
392
				$this->sources[$from] = $scripts[$from];
393
			}
394
395
			return false;
396
		}
397
398
		if ($count_scripts === 1)
399
		{
400
			$this->data['import_script'] = basename($scripts[$from][0]['path']);
401
			if (substr($this->data['import_script'], -4) === '.xml')
402
			{
403
				try
404
				{
405
					$this->importer->reloadImporter();
406
				}
407
				catch (\Exception $e)
408
				{
409
					$this->data['import_script'] = null;
410
				}
411
			}
412
413
			return false;
414
		}
415
416
		foreach ($scripts as $key => $val)
417
		{
418
			usort($scripts[$key], static function ($v1, $v2)
419
			{
420
				return strcasecmp($v1['name'], $v2['name']);
421
			});
422
		}
423
424
		$this->response->use_template = 'select_script';
425
		$this->response->params_template = array($scripts, $destination_names);
426
427
		return true;
428
	}
429
430
	/**
431
	 * Collects all the important things, the importer can't do anything
432
	 * without this information.
433
	 *
434
	 * @param string|null $error_message
435
	 * @param object|null|bool $object
436
	 *
437
	 * @return boolean|null
438
	 */
439
	public function doStep0($error_message = null, $object = false)
440
	{
441
		global $oi_import;
442
443
		$oi_import = $object ?? false;
444
		$this->cookie->destroy();
445
446
		// Previously imported? we need to clean some variables ..
447
		unset($_SESSION['import_overall'], $_SESSION['import_steps']);
448
449
		if ($this->_detect_scripts())
450
		{
451
			return true;
452
		}
453
454
		try
455
		{
456
			$this->importer->reloadImporter();
457
		}
458
		catch (\Exception $e)
459
		{
460
			$this->response->template_error = true;
461
			$this->response->addErrorParam($e->getMessage());
462
		}
463
464
		// Show the step0 template, used to define source/destination directories
465
		$form = new Form($this->language);
466
		$this->_prepareStep0Form($form);
467
		$this->response->use_template = 'step0';
468
		$this->response->params_template = array($this, $form);
469
470
		// If we have errors, it's the end of the road
471
		if ($error_message !== null)
472
		{
473
			$this->template->footer();
474
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
475
		}
476
477
		return null;
478
	}
479
480
	protected function _prepareStep0Form($form)
481
	{
482
		$form->action_url = $_SERVER['PHP_SELF'] . '?step=1';
483
484
		$this->importer->populateFormFields($form);
485
486
		return $form;
487
	}
488
489
	protected function testFiles($files, $path)
490
	{
491
		$files = (array) $files;
492
493
		$test = empty($files);
494
495
		foreach ($files as $file)
496
		{
497
			$test |= @file_exists($path . DS . $file);
498
		}
499
500
		return $test;
501
	}
502
503
	/**
504
	 * The important one, transfer the content from the source forum to our
505
	 * destination system
506
	 *
507
	 * @throws \Exception
508
	 *
509
	 * @return boolean
510
	 */
511
	public function doStep1()
512
	{
513
		$this->cookie->set(array($this->config->path_to, $this->config->path_from));
514
515
		$do_steps = $this->step1Progress();
516
517
		try
518
		{
519
			$this->importer->doStep1($do_steps);
0 ignored issues
show
Bug introduced by
$do_steps of type array is incompatible with the type integer expected by parameter $do_steps of OpenImporter\Importer::doStep1(). ( Ignorable by Annotation )

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

519
			$this->importer->doStep1(/** @scrutinizer ignore-type */ $do_steps);
Loading history...
520
		}
521
		catch (DatabaseException $e)
522
		{
523
			$trace = $e->getTrace();
524
			$this->template->error($e->getMessage(), $trace[0]['args'][1] ?? null, $e->getLine(), $e->getFile());
525
526
			// Forward back to the original caller to terminate the script
527
			throw new \Exception($e->getMessage());
528
		}
529
530
		$_GET['substep'] = 0;
531
		$_REQUEST['start'] = 0;
532
533
		return $this->doStep2();
534
	}
535
536
	/**
537
	 * Show the progress being made in step 1, the main import process
538
	 *
539
	 * @return array
540
	 */
541
	protected function step1Progress()
542
	{
543
		$_GET['substep'] = isset($_GET['substep']) ? (int) @$_GET['substep'] : 0;
544
		// @TODO: check if this is needed
545
		//$progress = ($_GET['substep'] ==  0 ? 1 : $_GET['substep']);
546
547
		// Skipping steps?
548
		$do_steps = $_SESSION['do_steps'] ?? array();
549
550
		// Calculate our overall time and create the progress bar
551
		if (!isset($_SESSION['import_overall']))
552
		{
553
			list ($_SESSION['import_overall'], $_SESSION['import_steps']) = $this->importer->determineProgress();
554
		}
555
556
		if (!isset($_SESSION['import_progress']))
557
		{
558
			$_SESSION['import_progress'] = 0;
559
		}
560
561
		return $do_steps;
562
	}
563
564
	/**
565
	 * We have imported the old database, let's recalculate the forum statistics.
566
	 *
567
	 * @throws \Exception
568
	 * @return boolean
569
	 */
570
	public function doStep2()
571
	{
572
		$_GET['step'] = '2';
573
		$this->response->step = '2';
0 ignored issues
show
Documentation Bug introduced by
The property $step was declared of type integer, but '2' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
574
575
		$this->template->step2();
576
577
		try
578
		{
579
			$key = $this->importer->doStep2();
580
		}
581
		catch (DatabaseException $e)
582
		{
583
			$trace = $e->getTrace();
584
			$this->template->error($e->getMessage(), $trace[0]['args'][1] ?? null, $e->getLine(), $e->getFile());
585
586
			// Forward back to the original caller to terminate the script
587
			throw new \Exception($e->getMessage());
588
		}
589
590
		$this->template->status($key + 1, 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

590
		$this->template->status($key + 1, 1, /** @scrutinizer ignore-type */ false, true);
Loading history...
591
592
		return $this->doStep3();
593
	}
594
595
	/**
596
	 * We are done :)
597
	 *
598
	 * @return boolean
599
	 */
600
	public function doStep3()
601
	{
602
		$this->importer->doStep3();
603
604
		$writable = (is_writable(BASEDIR) && is_writable(__FILE__));
605
606
		$this->response->use_template = 'step3';
607
		$this->response->params_template = array($this->importer->xml->general->name, $this->_boardurl, $writable);
608
609
		unset ($_SESSION['import_steps'], $_SESSION['import_progress'], $_SESSION['import_overall']);
610
		$this->data = array();
611
612
		return true;
613
	}
614
}
615