Passed
Push — development ( 2bb3b4...48f606 )
by Emanuele
53s
created

ImportManager::loadPaths()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 26
rs 6.7272
cc 7
eloc 11
nc 12
nop 1
1
<?php
2
/**
3
 * @name      OpenImporter
4
 * @copyright OpenImporter contributors
5
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
6
 *
7
 * @version 2.0 Alpha
8
 */
9
10
namespace OpenImporter\Core;
11
12
/**
13
 * Class ImportManager
14
 * Loads the main importer.
15
 * It handles all steps to completion.
16
 */
17
class ImportManager
18
{
19
	/**
20
	 * The importer that will act as interface between the manager and the
21
	 * files that will do the actual import
22
	 * @var object
23
	 */
24
	public $importer;
25
26
	/**
27
	 * The configurator that holds all the settings
28
	 * @var object
29
	 */
30
	public $config;
31
32
	/**
33
	 * Data used by the script and stored in session between a reload and the
34
	 * following one.
35
	 * @var mixed[]
36
	 */
37
	public $data = array();
38
39
	/**
40
	 * The response object.
41
	 * @var object
42
	 */
43
	protected $response;
44
45
	/**
46
	 * The language object.
47
	 * @var object
48
	 */
49
	protected $lng;
50
51
	/**
52
	 * ImportManager constructor.
53
	 * Initialize the main Importer object
54
	 *
55
	 * @param Configurator $config
56
	 * @param Importer $importer
57
	 * @param HttpResponse $response
58
	 */
59
	public function __construct(Configurator $config, Importer $importer, HttpResponse $response)
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
60
	{
61
		$this->config = $config;
62
		$this->importer = $importer;
63
		$this->response = $response;
64
65
		$this->loadFromSession();
66
67
		if (!empty($_POST['do_steps']))
68
		{
69
			$this->data['do_steps'] = $_POST['do_steps'];
70
		}
71
		else
72
		{
73
			$this->data['do_steps'] = array();
74
		}
75
76
		if ($this->config->action == 'reset')
77
		{
78
			$this->resetImporter();
79
			$this->data = array('import_script' => '');
0 ignored issues
show
Documentation Bug introduced by
It seems like array('import_script' => '') of type array<string,string,{"import_script":"string"}> is incompatible with the declared type array<integer,*> of property $data.

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...
80
		}
81
	}
82
83
	protected function loadFromSession()
0 ignored issues
show
Coding Style introduced by
loadFromSession uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadFromSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
84
	{
85
		if (isset($_REQUEST['start']))
86
		{
87
			$this->config->progress->start = (int) $_REQUEST['start'];
88
		}
89
		else
90
		{
91
			$this->config->progress->start = 0;
92
		}
93
94
		if (!empty($_SESSION['importer_data']))
95
		{
96
			$this->data = $_SESSION['importer_data'];
97
		}
98
99
		if (!empty($_SESSION['importer_progress_status']))
100
		{
101
			$this->config->store = new ValuesBag($_SESSION['importer_progress_status']);
102
		}
103
	}
104
105
	protected function resetImporter()
106
	{
107
		unset($this->config->store['importer_data']);
108
		unset($this->config->store['importer_progress_status']);
109
		unset($this->config->store['import_progress']);
110
	}
111
112
	public function setupScripts($data)
113
	{
114
		$this->findScript($data);
115
116
		$this->loadPass($data);
117
118
		$this->loadPaths($data);
119
120
		if (!empty($this->config->script))
121
		{
122
			$this->importer->reloadImporter();
123
		}
124
	}
125
126
	/**
127
	 * Finds the script either in the session or in request
128
	 */
129
	protected function findScript($data)
130
	{
131
		// Save here so it doesn't get overwritten when sessions are restarted.
132
		if (isset($data['destination']) && isset($data['source']))
133
		{
134
			$this->data['import_script'] = $this->config->script = array(
135
				'destination' => str_replace('..', '', preg_replace('~[^a-zA-Z0-9\-_\.]~', '', $data['destination'])),
136
				'source' => str_replace('..', '', preg_replace('~[^a-zA-Z0-9\-_\.]~', '', $data['source'])),
137
			);
138
		}
139
		elseif (isset($this->data['import_script']))
140
		{
141
			$this->config->script = $this->data['import_script'] = $this->validateScript($this->data['import_script']);
142
		}
143
		else
144
		{
145
			$this->config->script = array();
146
			$this->data['import_script'] = null;
147
		}
148
	}
149
150
	/**
151
	 * Verifies the scripts exist.
152
	 *
153
	 * @param string[] $scripts Destination and source script in an associative
154
	 *   array:
155
	 *   	array(
156
	 *      	'destination' => 'destination_name.php',
157
	 *          'source' => 'source_name.xml',
158
	 *       )
159
	 *
160
	 * @return bool|mixed[]
161
	 */
162
	protected function validateScript($scripts)
163
	{
164
		$return = array();
165
166
		foreach ((array) $scripts as $key => $script)
167
		{
168
			$script = preg_replace('~[\.]+~', '.', $script);
169
			$key = preg_replace('~[\.]+~', '.', $key);
170
171
			if ($key === 'source')
172
			{
173
				if (!file_exists($this->config->importers_dir . DS . $key . 's' . DS . $script) || preg_match('~_Importer\.(xml|php)$~', $script) == 0)
174
				{
175
					return false;
176
				}
177
			}
178
			else
179
			{
180
				if (!file_exists($this->config->importers_dir . DS . $key . 's' . DS . $script . DS . 'Importer.php'))
181
				{
182
					return false;
183
				}
184
			}
185
186
			$return[$key] = $script;
187
		}
188
189
		return $return;
190
	}
191
192
	protected function loadPass($data)
193
	{
194
		// Check for the password...
195
		if (isset($data['db_pass']))
196
		{
197
			$this->data['db_pass'] = $data['db_pass'];
198
		}
199
	}
200
201
	protected function loadPaths($data)
202
	{
203
		if (isset($data['path_from']) || isset($data['path_to']))
204
		{
205
			if (isset($data['path_from']))
206
			{
207
				$this->config->path_from = rtrim($data['path_from'], '\\/');
208
			}
209
210
			if (isset($data['path_to']))
211
			{
212
				$this->config->path_to = rtrim($data['path_to'], '\\/');
213
			}
214
215
			$this->data['import_paths'] = array($this->config->path_from, $this->config->path_to);
216
		}
217
		elseif (isset($this->data['import_paths']))
218
		{
219
			list ($this->config->path_from, $this->config->path_to) = $this->data['import_paths'];
220
		}
221
222
		if (!empty($this->data))
223
		{
224
			$this->importer->setData($this->data);
225
		}
226
	}
227
228
	public function __destruct()
229
	{
230
		$this->saveInSession();
231
	}
232
233
	protected function saveInSession()
0 ignored issues
show
Coding Style introduced by
saveInSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
234
	{
235
		$_SESSION['importer_data'] = $this->data;
236
		$_SESSION['importer_progress_status'] = $this->config->store;
237
	}
238
239
	/**
240
	 * Prepares the response to send to the template system
241
	 *
242
	 * @param $data
243
	 *
244
	 * @return object|HttpResponse
245
	 */
246
	public function process($data)
247
	{
248
		$this->populateResponseDetails();
249
250
		// This is really quite simple; if ?delete is on the URL, delete the importer...
251
		switch ($this->config->action)
252
		{
253
			case 'delete':
254
				$this->response->is_xml = true;
255
				$this->response->addHeader('Content-Type', 'text/xml');
256
				$this->response->addTemplate('validate');
257
				$this->response->valid = $this->uninstall();
258
				break;
259
260
			case 'validate':
261
				$this->validateFields($data);
262
				$this->response->addHeader('Content-Type', 'text/xml');
263
				$this->response->is_xml = true;
264
				$this->response->addTemplate('validate');
265
				break;
266
267
			default:
268
				$this->response->is_page = true;
269
				if (method_exists($this, 'doStep' . $this->config->progress->step))
270
				{
271
					call_user_func(array($this, 'doStep' . $this->config->progress->step));
272
				}
273
				else
274
				{
275
					$this->doStep0();
276
				}
277
		}
278
279
		$this->populateResponseDetails();
280
281
		return $this->response;
282
	}
283
284
	public function populateResponseDetails()
285
	{
286
		if (isset($this->importer->xml->general->name) && isset($this->importer->config->destination->scriptname))
287
		{
288
			$this->response->page_title = $this->importer->xml->general->name . ' ' . $this->response->lng->to . ' ' . $this->importer->config->destination->scriptname;
289
		}
290
		else
291
		{
292
			$this->response->page_title = 'OpenImporter';
293
		}
294
295
		$this->response->source = !empty($this->response->script['source']) ? addslashes($this->response->script['source']) : '';
296
		$this->response->destination = !empty($this->response->script['destination']) ? addslashes($this->response->script['destination']) : '';
297
// 		$this->response->from = $this->importer->settings : null
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
298
		$this->response->script = $this->config->script;
299
// 		$this->response->
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
300
// 		$this->response->
301
// 		$this->response->
302
	}
303
304
	/**
305
	 * Deletes the importer files from the server
306
	 * @todo doesn't know yet about the new structure.
307
	 */
308
	protected function uninstall()
309
	{
310
		// Just in case
311
		$this->resetImporter();
312
313
		@unlink(__FILE__);
314
		if (preg_match('~_importer\.xml$~', $this->data['import_script']) != 0)
315
		{
316
			@unlink(BASEDIR . DS . $this->data['import_script']);
317
		}
318
		$this->data['import_script'] = null;
319
	}
320
321
	protected function validateFields($data)
322
	{
323
		$this->detectScripts();
324
325
		$this->importer->reloadImporter();
326
327
		if (isset($data['path_to']))
328
		{
329
			$this->response->valid = $this->config->destination->testPath($data['path_to']);
330
		}
331
		elseif (isset($data['path_from']))
332
		{
333
			$this->response->valid = $this->config->source->loadSettings($data['path_from'], true);
334
		}
335
		else
336
		{
337
			$this->response->valid = false;
338
		}
339
	}
340
341
	/**
342
	 * - checks,  if we have already specified an importer script
343
	 * - checks the file system for importer definition files
344
	 * @return boolean
345
	 */
346
	protected function detectScripts()
347
	{
348
		if ($this->config->script !== null)
349
		{
350
			$this->config->script = $this->data['import_script'] = $this->validateScript($this->data['import_script']);
351
		}
352
		$destination_names = $this->findDestinations();
353
354
		$scripts = $this->findSources();
355
		$count_scripts = count($scripts);
356
357
		if (!empty($this->data['import_script']))
358
		{
359
			return false;
360
		}
361
362
		if ($count_scripts == 1)
363
		{
364
			$this->data['import_script'] = basename($scripts[0]['path']);
365
			if (substr($this->data['import_script'], -4) == '.xml')
366
			{
367
				$this->importer->reloadImporter();
368
			}
369
370
			return false;
371
		}
372
373
		$this->response->addTemplate('selectScript', array('scripts' => $scripts, 'destination_names' => $destination_names));
374
375
		return true;
376
	}
377
378
	/**
379
	 * Simply scans the Importers/destinations directory looking for destination
380
	 * files.
381
	 *
382
	 * @return string[]
383
	 */
384
	protected function findDestinations()
385
	{
386
		$destinations = array();
387
388
		$iterator = new \GlobIterator($this->config->importers_dir . DS . 'destinations' . DS . '*', GLOB_ONLYDIR);
389
		foreach ($iterator as $possible_dir)
390
		{
391
			$namespace = $possible_dir->getBasename();
392
			$class_name = '\\OpenImporter\\Importers\\destinations\\' . $namespace . '\\Importer';
393
394
			if (class_exists($class_name))
395
			{
396
				$obj = new $class_name();
397
				$destinations[$namespace] = $obj->getName();
398
			}
399
		}
400
401
		asort($destinations);
402
403
		return $destinations;
404
	}
405
406
	/**
407
	 * Simply scans the Importers/sources directory looking for source files.
408
	 *
409
	 * @return string[]
410
	 */
411
	protected function findSources()
412
	{
413
		$scripts = array();
414
415
		// Silence simplexml errors
416
		libxml_use_internal_errors(true);
417
		$iterator = new \GlobIterator($this->config->importers_dir . DS . 'sources' . DS . '*_Importer.xml');
418
		foreach ($iterator as $entry)
419
		{
420
			// If a script is broken simply skip it.
421
			if (!$xmlObj = simplexml_load_file($entry->getPathname(), 'SimpleXMLElement', LIBXML_NOCDATA))
422
			{
423
				continue;
424
			}
425
426
			$file_name = $entry->getBasename();
427
428
			$scripts[$file_name] = array(
429
				'path' => $file_name,
430
				'name' => (string) $xmlObj->general->name
431
			);
432
		}
433
434
		usort($scripts, function ($v1, $v2)
435
		{
436
			return strcasecmp($v1['name'], $v2['name']);
437
		});
438
439
		return $scripts;
440
	}
441
442
	/**
443
	 * Collects all the important things, the importer can't do anything
444
	 * without this information.
445
	 *
446
	 * @return boolean|null
447
	 */
448
	public function doStep0()
449
	{
450
		//previously imported? we need to clean some variables ..
451
		$this->resetImporter();
452
453
		if ($this->detectScripts())
454
		{
455
			return true;
456
		}
457
458
		try
459
		{
460
			$this->importer->reloadImporter();
461
		}
462
		catch (Exception $e)
463
		{
464
			$this->response->template_error = true;
465
			$this->response->addErrorParam($e->getMessage());
466
		}
467
468
		$this->response->source_name = (string) $this->importer->xml->general->name;
469
		$this->response->destination_name = (string) $this->config->destination->scriptname;
470
		if (($this->response->template_error && $this->response->noTemplates()) || empty($this->response->template_error))
471
		{
472
			$this->response->addTemplate('step0', array('form' => $this->getFormStructure()));
473
		}
474
475
		return;
476
	}
477
478
	protected function getFormStructure()
479
	{
480
		$form = new Form($this->response->lng);
481
		$this->prepareStep0Form($form);
482
483
		return $form;
484
	}
485
486
	protected function prepareStep0Form($form)
487
	{
488
		$form->action_url = $this->response->scripturl . '?step=1';
489
490
		$this->importer->populateFormFields($form);
491
492
		return $form;
493
	}
494
495
	/**
496
	 * The important one, transfer the content from the source forum to our
497
	 * destination system
498
	 *
499
	 * @throws \Exception
500
	 * @return boolean
501
	 */
502
	public function doStep1()
503
	{
504
		$this->response->step = 1;
505
506
		try
507
		{
508
			$this->importer->doStep1();
509
		}
510
		catch (DatabaseException $e)
511
		{
512
			$trace = $e->getTrace();
513
			$this->response->addErrorParam('', isset($trace[0]['args'][1]) ? $trace[0]['args'][1] : null, $e->getLine(), $e->getFile(), array(
514
				'query' => $e->getQuery(),
515
				'error' => $e->getErrorString(),
516
				'action_url' => $this->response->scripturl . $this->buildActionQuery(),
517
			));
518
			$this->response->is_page = true;
519
			$this->response->template_error = true;
520
521
			// Forward back to the original caller to terminate the script
522
			throw new StepException($e->getMessage());
523
		}
524
525
		$this->config->progress->start = 0;
526
527
		return $this->doStep2();
528
	}
529
530
	/**
531
	 * Puts together the url used in the DatabaseException of sendError to go
532
	 * back to the last step.
533
	 *
534
	 * @return string
535
	 */
536
	protected function buildActionQuery()
0 ignored issues
show
Coding Style introduced by
buildActionQuery uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
buildActionQuery uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
537
	{
538
		// @todo $_GET and $_REQUEST
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
539
		// Get the query string so we pass everything.
540
		if (isset($_REQUEST['start']))
541
		{
542
			$_GET['start'] = $_REQUEST['start'];
543
		}
544
545
		$query_string = '';
546
		foreach ($_GET as $k => $v)
547
		{
548
			$query_string .= '&' . $k . '=' . $v;
549
		}
550
551
		if (strlen($query_string) != 0)
552
		{
553
			$query_string = '?' . strtr(substr($query_string, 1), array('&' => '&amp;'));
554
		}
555
556
		return $query_string;
557
	}
558
559
	/**
560
	 * We have imported the old database, let's recalculate the forum statistics.
561
	 *
562
	 * @throws \Exception
563
	 * @return boolean
564
	 */
565
	public function doStep2()
566
	{
567
		$this->response->step = '2';
568
		$this->config->progress->resetStep();
569
570
		try
571
		{
572
			$this->importer->doStep2($this->config->progress->substep);
573
		}
574
		catch (DatabaseException $e)
575
		{
576
			$trace = $e->getTrace();
577
			$this->response->addErrorParam($e->getMessage(), isset($trace[0]['args'][1]) ? $trace[0]['args'][1] : null, $e->getLine(), $e->getFile());
578
			$this->response->is_page = true;
579
			$this->response->template_error = true;
580
581
			// Forward back to the original caller to terminate the script
582
			throw new StepException($e->getMessage());
583
		}
584
585
		$this->response->status(1, $this->response->lng->get('recalculate'));
586
587
		return $this->doStep3();
588
	}
589
590
	/**
591
	 * we are done :)
592
	 *
593
	 * @return boolean
594
	 */
595
	public function doStep3()
596
	{
597
		$this->importer->doStep3();
598
599
		$writable = (is_writable(BASEDIR) && is_writable(__FILE__));
600
601
		$this->response->addTemplate('step3', array('name' => $this->importer->xml->general->name, 'writable' => $writable));
602
603
		$this->resetImporter();
604
		$this->data = array();
605
		$this->config->store = new ValuesBag();
606
607
		return true;
608
	}
609
}