Passed
Pull Request — master (#117)
by Spuds
07:11
created

Importer::init_db()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 45
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
c 2
b 0
f 0
nc 9
nop 0
dl 0
loc 45
ccs 0
cts 32
cp 0
crap 30
rs 9.3554
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 Importer creates the main XML object.
25
 * It detects and initializes the script to run.
26
 *
27
 * @class Importer
28
 */
29
class Importer
30
{
31
	/** @var Lang The "translator" (i.e. the Lang object) */
32
	public $lng;
33
34
	/** @var Configurator Contains any kind of configuration. */
35
	public $config;
36
37
	/** @var object The destination object. */
38
	public $destination;
39
40
	/** @var Template The template, basically our UI. */
41
	public $template;
42
43
	/** @var object The XML file which will be used from the importer (output from SimpleXMLElement) */
44
	public $xml;
45
46
	/** @var array Data used by the script and stored in session between reload and the following one. */
47
	public $data = array();
48
49
	/** @var object Holds the object that contains the settings of the source system */
50
	public $settings;
51
52
	/** @var Database This is our main database object. */
53
	protected $db;
54
55
	/** @var string The "base" class name of the destination system. */
56
	protected $_importer_base_class_name = '';
57
58
	/**
59
	 * Importer constructor.
60
	 * Initialize the main Importer object
61
	 *
62
	 * @param Configurator $config
63
	 * @param Lang $lang
64
	 * @param Template $template
65
	 */
66
	public function __construct($config, $lang, $template)
67
	{
68
		// initialize some objects
69
		$this->config = $config;
70
		$this->lng = $lang;
71
		$this->template = $template;
72
73
		// The current step - starts at 0.
74
		$_GET['step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0;
75
		$this->config->step = $_GET['step'];
76
		$_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
77
		$this->config->start = $_REQUEST['start'];
78
79
		if (!empty($this->config->script))
80
		{
81
			$this->_loadImporter(BASEDIR . DS . 'Importers' . DS . $this->config->script);
82
		}
83
	}
84
85
	protected function _loadImporter($file)
86
	{
87
		$this->_preparse_xml($file);
88
89
		// This is the helper class
90
		$source_helper = str_replace('.xml', '.php', $file);
91
		require_once($source_helper);
92
93
		// The "destination" php helper functions
94
		$path = dirname($file);
95
		$destination_helper = $path . DS . basename($path) . '_importer.php';
96
		require_once($destination_helper);
97
98
		// Initiate the class
99
		$this->_importer_base_class_name = str_replace('.', '_', basename($destination_helper, '.php'));
100
		$this->config->destination = new $this->_importer_base_class_name();
101
102
		$this->_loadSettings();
103
104
		$this->config->destination->setParam($this->db, $this->config);
105
	}
106
107
	/**
108
	 * loads the _importer.xml files
109
	 *
110
	 * @param string $file
111
	 * @throws ImportException
112
	 */
113
	private function _preparse_xml($file)
114
	{
115
		try
116
		{
117
			if (!$this->xml = simplexml_load_string(file_get_contents($file), 'SimpleXMLElement', LIBXML_NOCDATA))
118
			{
119
				throw new ImportException('XML-Syntax error in file: ' . $file);
120
			}
121
122
			$this->xml = simplexml_load_string(file_get_contents($file), 'SimpleXMLElement', LIBXML_NOCDATA);
123
		}
124
		catch (\Exception $e)
125
		{
126
			ImportException::exception_handler($e, $this->template);
127
		}
128
	}
129
130
	/**
131
	 * Prepare the importer with custom settings of the source
132
	 *
133
	 * @throws \Exception
134
	 */
135
	private function _loadSettings()
136
	{
137
		// Initiate the source class
138
		$class = (string) $this->xml->general->className;
139
		$this->config->source = new $class();
140
141
		// Defines / Globals
142
		$this->config->source->setDefines();
143
		$this->config->source->setGlobals();
144
145
		// Dirty hack
146
		if (isset($_SESSION['store_globals']))
147
		{
148
			foreach ($_SESSION['store_globals'] as $var_name => $value)
149
			{
150
				$GLOBALS[$var_name] = $value;
151
			}
152
		}
153
154
		// Load the settings from the source forum
155
		$this->loadSettings();
156
157
		// Any custom form elements to speak of?
158
		$this->init_form_data();
159
160
		// Check passwords, and paths?
161
		if (empty($this->config->path_to))
162
		{
163
			return;
164
		}
165
166
		$this->config->boardurl = $this->config->destination->getDestinationURL($this->config->path_to);
0 ignored issues
show
Bug introduced by
The method getDestinationURL() 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

166
		/** @scrutinizer ignore-call */ 
167
  $this->config->boardurl = $this->config->destination->getDestinationURL($this->config->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...
167
		if ($this->config->boardurl === false)
168
		{
169
			throw new \Exception($this->lng->get(array('settings_not_found', $this->config->destination->getName())));
170
		}
171
172
		if (!$this->config->destination->verifyDbPass($this->data['db_pass']))
173
		{
174
			throw new \Exception($this->lng->get('password_incorrect'));
175
		}
176
177
		// Check the steps that we have decided to go through.
178
		if (!isset($_POST['do_steps']) && !isset($_SESSION['do_steps']))
179
		{
180
			throw new \Exception($this->lng->get('select_step'));
181
		}
182
183
		if (isset($_POST['do_steps']))
184
		{
185
			$_SESSION['do_steps'] = array();
186
			foreach ($_POST['do_steps'] as $key => $step)
187
			{
188
				$_SESSION['do_steps'][$key] = $step;
189
			}
190
		}
191
192
		$this->init_db();
193
		$this->config->source->setUtils($this->db, $this->config);
194
195
		// @todo What is the use-case for these?
196
		// Custom variables from our importer?
197
		if (isset($this->xml->general->variables))
198
		{
199
			foreach ($this->xml->general->variables as $eval_me)
200
			{
201
				eval($eval_me);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
202
			}
203
		}
204
205
		$this->testTable();
206
	}
207
208
	/**
209
	 * Calls the loadSettings function of AbstractSourceImporter
210
	 * Used to load source forum settings
211
	 *
212
	 * @throws \Exception
213
	 */
214
	protected function loadSettings()
215
	{
216
		$found = true;
217
		if (!empty($this->config->path_from))
218
		{
219
			$found = $this->config->source->loadSettings($this->config->path_from);
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

219
			/** @scrutinizer ignore-call */ 
220
   $found = $this->config->source->loadSettings($this->config->path_from);

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...
220
		}
221
222
		if ($found === false)
223
		{
224
			if (@ini_get('open_basedir') !== '')
225
			{
226
				throw new \Exception($this->lng->get(array('open_basedir', (string) $this->xml->general->name)));
227
			}
228
229
			throw new \Exception($this->lng->get(array('config_not_found', (string) $this->xml->general->name)));
230
		}
231
	}
232
233
	protected function init_form_data()
234
	{
235
		if ($this->xml->general->form && !empty($_SESSION['import_parameters']))
236
		{
237
			foreach ($this->xml->general->form->children() as $param)
238
			{
239
				$check = $_POST['field' . $param['id']];
240
				if (isset($check))
241
				{
242
					$var = (string) $param;
243
					$_SESSION['import_parameters']['field' . $param['id']][$var] = $check;
244
				}
245
			}
246
247
			// Should already be global'd.
248
			foreach ($_SESSION['import_parameters'] as $id)
249
			{
250
				foreach ($id as $k => $v)
251
				{
252
					$GLOBALS[$k] = $v;
253
				}
254
			}
255
		}
256
		elseif ($this->xml->general->form)
257
		{
258
			$_SESSION['import_parameters'] = array();
259
			foreach ($this->xml->general->form->children() as $param)
260
			{
261
				$var = (string) $param;
262
				$check = $_POST['field' . $param['id']];
263
				if (isset($check))
264
				{
265
					$_SESSION['import_parameters']['field' . $param['id']][$var] = $check;
266
				}
267
				else
268
				{
269
					$_SESSION['import_parameters']['field' . $param['id']][$var] = null;
270
				}
271
			}
272
273
			foreach ($_SESSION['import_parameters'] as $id)
274
			{
275
				foreach ($id as $k => $v)
276
				{
277
					$GLOBALS[$k] = $v;
278
				}
279
			}
280
		}
281
	}
282
283
	protected function init_db()
284
	{
285
		try
286
		{
287
			list ($db_server, $db_user, $db_passwd, $db_persist, $db_prefix, $db_name) = $this->config->destination->dbConnectionData();
288
289
			$this->db = new Database($db_server, $db_user, $db_passwd, $db_persist);
290
291
			// We want UTF8 only, let's set our mysql connection to utf8
292
			$this->db->query('SET NAMES \'utf8\'');
293
		}
294
		catch (\Exception $e)
295
		{
296
			ImportException::exception_handler($e, $this->template);
297
			die();
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...
298
		}
299
300
		if (strpos($db_prefix, '.') !== false)
301
		{
302
			$this->config->to_prefix = $db_prefix;
303
		}
304
		elseif (is_numeric(substr($db_prefix, 0, 1)))
305
		{
306
			$this->config->to_prefix = $db_name . '.' . $db_prefix;
307
		}
308
		else
309
		{
310
			$this->config->to_prefix = '`' . $db_name . '`.' . $db_prefix;
311
		}
312
313
		$this->config->from_prefix = $this->config->source->getPrefix();
314
315
		if (preg_match('~^`[^`]+`.\d~', $this->config->from_prefix) !== 0)
316
		{
317
			$this->config->from_prefix = strtr($this->config->from_prefix, array('`' => ''));
318
		}
319
320
		// SQL_BIG_SELECTS: If set to 0, MySQL aborts SELECT statements that are
321
		// likely to take a very long time to execute (that is, statements for
322
		// which the optimizer estimates that the number of examined rows exceeds
323
		// the value of max_join_size)
324
		// Source:
325
		// https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_sql_big_selects
326
		$this->db->query("SET @@SQL_BIG_SELECTS = 1");
327
		$this->db->query("SET @@MAX_JOIN_SIZE = 18446744073709551615");
328
	}
329
330
	protected function testTable()
331
	{
332
		if ((int) $_REQUEST['start'] === 0
333
			&& empty($_GET['substep']) && ((int) $_GET['step'] === 1 || (int) $_GET['step'] === 2))
334
		{
335
			$result = $this->db->query('
336
				SELECT COUNT(*)
337
				FROM ' . $this->config->from_prefix . $this->config->source->getTableTest(), true);
338
339
			if ($result === false)
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
340
			{
341
				throw new \Exception($this->lng->get(array('permission_denied', $this->db->getLastError(), (string) $this->xml->general->name)));
342
			}
343
344
			$this->db->free_result($result);
345
		}
346
	}
347
348
	public function setData($data)
349
	{
350
		$this->data = $data;
351
	}
352
353
	public function reloadImporter()
354
	{
355
		if (!empty($this->config->script))
356
		{
357
			$this->_loadImporter(BASEDIR . DS . 'Importers' . DS . $this->config->script);
358
		}
359
	}
360
361
	/**
362
	 * @param Form $form
363
	 */
364
	public function populateFormFields($form)
365
	{
366
		// From forum path
367
		$form_path = $this->config->path_to ?? dirname(BASEDIR);
368
		$form->addOption($this->config->destination->getFormFields($form_path));
369
370
		$class = (string) $this->xml->general->className;
371
		$settings = new $class();
372
373
		// To path
374
		if (!isset($this->config->path_from))
375
		{
376
			$this->config->path_from = dirname(BASEDIR);
377
		}
378
379
		// Check if we can load the settings given the path from
380
		$path_from = $settings->loadSettings($this->config->path_from, true);
381
		if ($path_from !== null)
382
		{
383
			$form->addOption(array(
384
				'id' => 'path_from',
385
				'label' => array('path_from', $this->xml->general->name),
386
				'default' => $this->config->path_from,
387
				'type' => 'text',
388
				'correct' => $path_from ? 'change_path' : 'right_path',
389
				'validate' => true,
390
			));
391
		}
392
393
		// Any custom form elements?
394
		if ($this->xml->general->form)
395
		{
396
			foreach ($this->xml->general->form->children() as $field)
397
			{
398
				$form->addField($field);
399
			}
400
		}
401
402
		// We will want the db access password
403
		$form->addOption(array(
404
			'id' => 'db_pass',
405
			'label' => 'database_passwd',
406
			'correct' => 'database_verify',
407
			'type' => 'password',
408
		));
409
410
		$form->addSeparator();
411
412
		// How many steps are involved in this forum conversion
413
		$steps = $this->_find_steps();
414
415
		// Give the option to not perform certain steps
416
		if (!empty($steps))
417
		{
418
			foreach ($steps as $key => $step)
419
			{
420
				$steps[$key]['label'] = ucfirst(str_replace('importing ', '', $step['name']));
421
			}
422
423
			$form->addOption(array(
424
				'id' => 'do_steps',
425
				'label' => 'selected_only',
426
				'default' => $steps,
427
				'type' => 'steps',
428
			));
429
		}
430
	}
431
432
	/**
433
	 * Looks at the importer and returns the steps that it's able to make.
434
	 *
435
	 * @return array
436
	 */
437
	protected function _find_steps()
438
	{
439
		$steps = array();
440
		$steps_count = 0;
441
442
		foreach ($this->xml->step as $xml_steps)
443
		{
444
			$steps_count++;
445
446
			$steps[$steps_count] = array(
447
				'name' => (string) $xml_steps->title,
448
				'count' => $steps_count,
449
				'mandatory' => (string) $xml_steps->attributes()->{'type'},
450
				'checked' => (string) $xml_steps->attributes()->{'checked'} == 'false' ? '' : 'checked="checked"',
451
			);
452
		}
453
454
		return $steps;
455
	}
456
457
	/**
458
	 * Determines where we are in the overall process to update the UI
459
	 *
460
	 * @return array
461
	 */
462
	public function determineProgress()
463
	{
464
		$progress_counter = 0;
465
		$counter_current_step = 0;
466
		$current = 0;
467
		$import_steps = array();
468
469
		$xmlParser = new XmlProcessor($this->db, $this->config, $this->template, $this->xml);
470
471
		// Loop through each step
472
		foreach ($this->xml->step as $counts)
473
		{
474
			if ($counts->detect)
475
			{
476
				$count = $xmlParser->fix_params((string) $counts->detect);
477
				$request = $this->db->query("
478
					SELECT COUNT(*)
479
					FROM $count", true);
480
481
				if (!empty($request))
482
				{
483
					list ($current) = $this->db->fetch_row($request);
484
					$this->db->free_result($request);
485
				}
486
487
				$progress_counter += $current;
488
489
				$import_steps[$counter_current_step]['counter'] = $current;
490
			}
491
492
			$counter_current_step++;
493
		}
494
495
		return array($progress_counter, $import_steps);
496
	}
497
498
	/**
499
	 * The important one, transfer the content from the source forum to our
500
	 * destination system.
501
	 *
502
	 * @param int $do_steps
503
	 * @return boolean
504
	 */
505
	public function doStep1($do_steps)
506
	{
507
		$step1_importer_class = $this->_importer_base_class_name . '_step1';
508
		$step1_importer = new $step1_importer_class($this->db, $this->config);
509
510
		if ($this->xml->general->globals)
511
		{
512
			foreach (explode(',', $this->xml->general->globals) as $global)
513
			{
514
				global $$global;
515
			}
516
		}
517
518
		$substep = 0;
519
520
		$xmlParser = new XmlProcessor($this->db, $this->config, $this->template, $this->xml);
521
		$xmlParser->setImporter($step1_importer);
522
523
		foreach ($this->xml->step as $step)
524
		{
525
			$xmlParser->processSteps($step, $substep, $do_steps);
0 ignored issues
show
Bug introduced by
$do_steps of type integer is incompatible with the type array expected by parameter $do_steps of OpenImporter\XmlProcessor::processSteps(). ( Ignorable by Annotation )

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

525
			$xmlParser->processSteps($step, $substep, /** @scrutinizer ignore-type */ $do_steps);
Loading history...
526
		}
527
	}
528
529
	/**
530
	 * We have imported the old database, let's recalculate the forum statistics.
531
	 *
532
	 * @return boolean
533
	 * @global string $to_prefix
534
	 *
535
	 * @global Database $db
536
	 */
537
	public function doStep2()
538
	{
539
		$step2_importer_class = $this->_importer_base_class_name . '_step2';
540
		$instance = new $step2_importer_class($this->db, $this->config);
541
542
		$methods = get_class_methods($instance);
543
		$substeps = array();
544
		$substep = 0;
545
		foreach ($methods as $method)
546
		{
547
			if (strpos($method, 'substep') !== 0)
548
			{
549
				continue;
550
			}
551
552
			$substeps[substr($method, 7)] = $method;
553
		}
554
		ksort($substeps);
555
556
		foreach ($substeps as $key => $method)
557
		{
558
			if ($substep <= $key)
559
			{
560
				$instance->$method();
561
			}
562
563
			$substep++;
564
			pastTime($substep);
565
		}
566
567
		return $key ?? null;
568
	}
569
570
	/**
571
	 * we are done :)
572
	 *
573
	 * @return boolean
574
	 */
575
	public function doStep3()
576
	{
577
		$step3_importer_class = $this->_importer_base_class_name . '_step3';
578
		$instance = new $step3_importer_class($this->db, $this->config);
579
580
		$instance->run($this->lng->get(array('imported_from', $this->xml->general->name)));
581
	}
582
}
583