SmfCommonSourceStep2   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 627
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 270
c 1
b 0
f 0
dl 0
loc 627
rs 2.8
wmc 70

14 Methods

Rating   Name   Duplication   Size   Complexity  
A substep5() 0 36 4
A getMsgMemberID() 0 26 2
B substep11() 0 58 9
A substep9() 0 33 6
A substep7() 0 42 4
A setBoardProperty() 0 25 4
A substep3() 0 57 2
A avatarFullPath() 0 19 2
A substep2() 0 29 3
C substep8() 0 71 14
A fixInexistentCategories() 0 34 5
A substep6() 0 42 5
B substep1() 0 51 6
A substep4() 0 39 4

How to fix   Complexity   

Complex Class

Complex classes like SmfCommonSourceStep2 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 SmfCommonSourceStep2, 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
 * There are (4) abstract classes in this file
16
 * - SmfCommonSource, SmfCommonSourceStep1, SmfCommonSourceStep2, SmfCommonSourceStep3
17
 */
18
19
namespace Importers;
20
21
use OpenImporter\Configurator;
22
use OpenImporter\Database;
23
24
/**
25
 * Class SmfCommonSource
26
 *
27
 * The class contains code that allows the Importer to obtain settings
28
 * from software that still has an SMF heritage.
29
 *
30
 * @class SmfCommonSource
31
 */
32
abstract class SmfCommonSource
33
{
34
	/** @var string The attachment extension */
35
	public $attach_extension = '';
36
	/** @var int */
37
	public $id_attach;
38
	/** @var string */
39
	public $attachmentUploadDirs;
40
	/** @var string */
41
	public $avatarUploadDir;
42
	/** @var string Path to the forum */
43
	protected $path;
44
	/** @var \OpenImporter\Configurator */
45
	protected $config;
46
	/** @var \OpenImporter\Database */
47
	protected $db;
48
49
	/**
50
	 * Set the database and configuration to the class
51
	 *
52
	 * @param Database $db
53
	 * @param Configurator $config
54
	 */
55
	public function setParam($db, $config)
56
	{
57
		$this->db = $db;
58
		$this->config = $config;
59
	}
60
61
	abstract public function getName();
62
63
	/**
64
	 * The path to our destination forum
65
	 *
66
	 * @param $path
67
	 *
68
	 * @return bool|string
69
	 */
70
	public function getDestinationURL($path)
71
	{
72
		// Cannot find Settings.php?
73
		if (!$this->checkSettingsPath($path))
74
		{
75
			return false;
76
		}
77
78
		// Everything should be alright now... no cross server includes, we hope...
79
		return $this->fetchSetting('boardurl');
80
	}
81
82
	/**
83
	 * Check that the Settings.php file exists
84
	 *
85
	 * @param $path
86
	 *
87
	 * @return bool
88
	 */
89
	public function checkSettingsPath($path)
90
	{
91
		$found = file_exists($path . '/Settings.php');
92
93
		if ($found && $this->path === null)
94
		{
95
			$this->path = $path;
96
		}
97
98
		return $found;
99
	}
100
101
	public function fetchSetting($name)
102
	{
103
		static $content = null;
104
105
		if ($content === null)
106
		{
107
			$content = file_get_contents($this->path . '/Settings.php');
108
		}
109
110
		$match = array();
111
		preg_match('~\$' . $name . '\s*=\s*\'(.*?)\';~', $content, $match);
112
113
		return isset($match[1]) ? $match[1] : '';
114
	}
115
116
	public function getFormFields($path_to = '')
117
	{
118
		return array(
119
			'id' => 'path_to',
120
			'label' => 'path_to_destination',
121
			'type' => 'text',
122
			'default' => htmlspecialchars($path_to),
123
			'correct' => $this->checkSettingsPath($path_to) ? 'right_path' : 'change_path',
124
			'validate' => true,
125
		);
126
	}
127
128
	public function verifyDbPass($pwd_to_verify)
129
	{
130
		if ($this->path === null)
131
		{
132
			return false;
133
		}
134
135
		$db_passwd = $this->fetchSetting('db_passwd');
136
137
		return $db_passwd === $pwd_to_verify;
138
	}
139
140
	public function dbConnectionData()
141
	{
142
		if ($this->path === null)
143
		{
144
			return false;
145
		}
146
147
		$db_server = $this->fetchSetting('db_server');
148
		$db_user = $this->fetchSetting('db_user');
149
		$db_passwd = $this->fetchSetting('db_passwd');
150
		$db_persist = $this->fetchSetting('db_persist');
151
		$db_prefix = $this->fetchSetting('db_prefix');
152
		$db_name = $this->fetchSetting('db_name');
153
154
		return array($db_server, $db_user, $db_passwd, $db_persist, $db_prefix, $db_name);
155
	}
156
157
	/**
158
	 * Helper function for old (SMF) attachments and some new ones
159
	 *
160
	 * @param string $filename
161
	 * @param int $attachment_id
162
	 * @param bool $legacy if true returns legacy SMF file name (default true)
163
	 *
164
	 * @return string
165
	 */
166
	public function getLegacyAttachmentFilename($filename, $attachment_id, $legacy = true)
167
	{
168
		$se = array("ä", "ö", "ü", "Ä", "Ö", "Ü", "ß");
169
		$repl = array("a", "o", "u", "A", "O", "U", "ss");
170
		$filename = str_replace($se, $repl, $filename);
171
172
		// Remove special accented characters - ie. sí (because they won't write to the filesystem well.)
173
		$clean_name = strtr($filename, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
174
		$clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));
175
176
		// Get rid of dots, spaces, and other weird characters.
177
		$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
178
179
		if ($legacy)
180
		{
181
			// @todo not sure about that one
182
			$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
183
184
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
185
		}
186
		else
187
		{
188
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name) . '.' . $this->attach_extension;
189
		}
190
	}
191
192
	public function getAvatarDir($row)
193
	{
194
		if ($this->avatarUploadDir === null)
195
		{
196
			return $this->getAttachDir($row);
197
		}
198
199
		return $this->avatarUploadDir;
200
	}
201
202
	public function getAttachDir($row)
203
	{
204
		$this->specialAttachments();
205
206
		if (!empty($row['id_folder']) && !empty($this->attachmentUploadDirs[$row['id_folder']]))
207
		{
208
			return $this->attachmentUploadDirs[$row['id_folder']];
209
		}
210
211
		return $this->attachmentUploadDirs[1];
212
	}
213
214
	/**
215
	 * Determine the source attachment and avatar directories
216
	 *
217
	 * @param bool $force
218
	 */
219
	public function specialAttachments($force = false)
220
	{
221
		$to_prefix = $this->config->to_prefix;
222
223
		// If we don't know the attachment or avatar directories
224
		if ($force === true || !isset($this->id_attach, $this->attachmentUploadDirs, $this->avatarUploadDir))
225
		{
226
			// Attachment starting id
227
			$result = $this->db->query("
228
				SELECT MAX(id_attach) + 1
229
				FROM {$to_prefix}attachments");
230
			list ($this->id_attach) = $this->db->fetch_row($result);
231
			$this->db->free_result($result);
232
233
			// Attachment directories
234
			$result = $this->db->query("
235
				SELECT value
236
				FROM {$to_prefix}settings
237
				WHERE variable = 'attachmentUploadDir'
238
				LIMIT 1");
239
			list ($attachmentdir) = $this->db->fetch_row($result);
240
			$attachment_UploadDir = @unserialize($attachmentdir);
241
			$this->attachmentUploadDirs = !empty($attachment_UploadDir) ? $attachment_UploadDir : array(1 => $attachmentdir);
0 ignored issues
show
Documentation Bug introduced by
It seems like ! empty($attachment_Uplo...ay(1 => $attachmentdir) can also be of type array. However, the property $attachmentUploadDirs 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...
242
243
			// Avatar directories
244
			$result = $this->db->query("
245
				SELECT value
246
				FROM {$to_prefix}settings
247
				WHERE variable = 'custom_avatar_dir'
248
				LIMIT 1");
249
			list ($this->avatarUploadDir) = $this->db->fetch_row($result);
250
			$this->db->free_result($result);
251
252
			if (empty($this->avatarUploadDir))
253
			{
254
				$this->avatarUploadDir = '';
255
			}
256
257
			if (empty($this->id_attach))
258
			{
259
				$this->id_attach = 1;
260
			}
261
		}
262
	}
263
264
	public function getAllAttachDirs()
265
	{
266
		if ($this->attachmentUploadDirs === null)
267
		{
268
			$this->specialAttachments();
269
		}
270
271
		return $this->attachmentUploadDirs;
272
	}
273
}
274
275
/**
276
 * Class SmfCommonSourceStep1
277
 *
278
 * @class SmfCommonSourceStep1
279
 */
280
abstract class SmfCommonSourceStep1 extends Step1BaseImporter
281
{
282
	public function doSpecialTable($special_table, $params = null)
283
	{
284
		// Are we doing attachments? They're going to want a few things...
285
		if ($special_table == $this->config->to_prefix . 'attachments' && $params === null)
286
		{
287
			$this->config->destination->specialAttachments();
0 ignored issues
show
Bug introduced by
The method specialAttachments() 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

287
			$this->config->destination->/** @scrutinizer ignore-call */ 
288
                               specialAttachments();

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...
288
289
			return $params;
290
		}
291
292
		// Here we have various bits of custom code for some known problems global to all importers.
293
		if ($special_table == $this->config->to_prefix . 'members' && $params !== null)
294
		{
295
			return $this->specialMembers($params);
296
		}
297
298
		return $params;
299
	}
300
301
	protected function specialMembers($row)
302
	{
303
		// Let's ensure there are no illegal characters.
304
		$row['member_name'] = preg_replace('/[<>&"\'=\\\]/is', '', $row['member_name']);
305
		$row['real_name'] = trim($row['real_name'], " \t\n\r\x0B\0\xA0");
306
307
		if (strpos($row['real_name'], '<') !== false || strpos($row['real_name'], '>') !== false || strpos($row['real_name'], '& ') !== false)
308
		{
309
			$row['real_name'] = htmlspecialchars($row['real_name'], ENT_QUOTES);
310
		}
311
		else
312
		{
313
			$row['real_name'] = strtr($row['real_name'], array('\'' => '&#039;'));
314
		}
315
316
		return $row;
317
	}
318
319
	/**
320
	 * Delete any defined db attachments that exist on disk for the source system
321
	 */
322
	public function removeAttachments()
323
	{
324
		$to_prefix = $this->config->to_prefix;
325
326
		// @todo This should probably be done in chunks too.
327
		// attachment_type = 1 are avatars.
328
		$result = $this->db->query("
329
			SELECT id_attach, filename, id_folder
330
			FROM {$to_prefix}attachments");
331
332
		while ($row = $this->db->fetch_assoc($result))
333
		{
334
			$enc_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], false);
335
336
			$attach_dir = $this->getAttachDir($row);
337
338
			if (file_exists($attach_dir . '/' . $enc_name))
339
			{
340
				$filename = $attach_dir . '/' . $enc_name;
341
			}
342
			else
343
			{
344
				// @todo this should not be here I think: it's SMF-specific, while this file shouldn't know anything about the source
345
				$clean_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], true);
346
				$filename = $attach_dir . '/' . $clean_name;
347
			}
348
349
			if (is_file($filename))
350
			{
351
				@unlink($filename);
352
			}
353
		}
354
355
		$this->db->free_result($result);
356
357
		// This is valid for some of the sources (e.g. Elk/SMF/Wedge), but not for others
358
		if (method_exists($this->config->source, 'getAttachmentDirs'))
359
		{
360
			$this->createAttachFoldersStructure($this->config->source->getAttachmentDirs());
361
		}
362
	}
363
364
	public function getAttachDir($row)
365
	{
366
		return $this->config->destination->getAttachDir($row);
367
	}
368
369
	/**
370
	 * Create the destination directory structure for the attachments
371
	 *
372
	 * @param string[] $folders
373
	 *
374
	 * @return bool
375
	 */
376
	protected function createAttachFoldersStructure($folders)
377
	{
378
		$source_base = $this->guessBase($folders);
379
		$destination_base = $this->guessBase($this->config->destination->getAllAttachDirs());
380
381
		// No idea where to start, better not mess with the filesystem
382
		// Though if $destination_base is empty it *is* a mess.
383
		if (empty($source_base) || empty($destination_base))
384
		{
385
			return false;
386
		}
387
388
		$dest_folders = str_replace($source_base, $destination_base, $folders);
389
390
		// Prepare the directory structure
391
		foreach ($dest_folders as $folder)
392
		{
393
			create_folders_recursive($folder);
394
		}
395
396
		// Save the new structure in the database
397
		$this->db->query("
398
			UPDATE {$this->config->to_prefix}settings
399
			SET value = '" . serialize($dest_folders) . "'
400
			WHERE variable = 'attachmentUploadDir'
401
			LIMIT 1");
402
403
		// Reload the new directories
404
		$this->config->destination->specialAttachments(true);
405
	}
406
407
	/**
408
	 * Determine the base / root folder for attachments
409
	 *
410
	 * @param string[] $folders
411
	 *
412
	 * @return bool|string
413
	 */
414
	protected function guessBase($folders)
415
	{
416
		foreach ($folders as $folder)
417
		{
418
			if ($this->isCommon($folder, $folders))
419
			{
420
				return $folder;
421
			}
422
		}
423
424
		foreach ($folders as $folder)
425
		{
426
			$dir = $folder;
427
			while (strlen($dir) > 4)
428
			{
429
				$dir = dirname($dir);
430
				if ($this->isCommon($dir, $folders))
431
				{
432
					return $dir;
433
				}
434
			}
435
		}
436
437
		return false;
438
	}
439
440
	protected function isCommon($dir, $folders)
441
	{
442
		foreach ($folders as $folder)
443
		{
444
			if (substr($folder, 0, strlen($dir)) !== $dir)
445
			{
446
				return false;
447
			}
448
		}
449
450
		return true;
451
	}
452
453
	public function moveAvatar($row, $source, $filename)
454
	{
455
		$avatar_attach_folder = $this->getAvatarFolderId($row);
456
457
		if ($avatar_attach_folder === false)
458
		{
459
			$extensions = array(
460
				'1' => 'gif',
461
				'2' => 'jpg',
462
				'3' => 'png',
463
				'6' => 'bmp'
464
			);
465
466
			$sizes = @getimagesize($source);
467
			$extension = isset($extensions[$sizes[2]]) ? $extensions[$sizes[2]] : 'bmp';
468
			$file_hash = 'avatar_' . $row['id_member'] . '_' . time() . '.' . $extension;
469
470
			$this->db->query("
471
				UPDATE {$this->config->to_prefix}members
472
				SET avatar = '$file_hash'
473
				WHERE id_member = $row[id_member]");
474
475
			$destination = $this->getAvatarDir($row) . '/' . $file_hash;
476
477
			$return = false;
478
		}
479
		else
480
		{
481
			$file_hash = createAttachmentFileHash($filename);
482
			$id_attach = $this->newIdAttach();
483
484
			$destination = $this->getAvatarDir($row) . '/' . $id_attach . '_' . $file_hash . '.' . $this->config->destination->attach_extension;
485
486
			$return = array(
487
				'id_attach' => $id_attach,
488
				'size' => filesize($destination),
489
				'filename' => '\'' . $row['filename'] . '\'',
490
				'file_hash' => '\'' . $file_hash . '\'',
491
				'id_member' => $row['id_member'],
492
				'id_folder' => $avatar_attach_folder,
493
			);
494
		}
495
496
		copy_file($source, $destination);
497
498
		return $return;
499
	}
500
501
	public function getAvatarFolderId($row)
0 ignored issues
show
Unused Code introduced by
The parameter $row is not used and could be removed. ( Ignorable by Annotation )

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

501
	public function getAvatarFolderId(/** @scrutinizer ignore-unused */ $row)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
502
	{
503
		// @todo in theory we could be able to find the "current" directory
504
		if ($this->config->destination->avatarUploadDir === null)
505
		{
506
			return 1;
507
		}
508
509
		return false;
510
	}
511
512
	public function getAvatarDir($row)
513
	{
514
		return $this->config->destination->getAvatarDir($row);
515
	}
516
517
	public function newIdAttach()
518
	{
519
		// The one to return
520
		$current = $this->config->destination->id_attach;
521
522
		// Increase preparing for the next one
523
		$this->config->destination->id_attach++;
524
525
		return $current;
526
	}
527
}
528
529
/**
530
 * Class SmfCommonSourceStep2
531
 *
532
 * @class SmfCommonSourceStep2
533
 */
534
abstract class SmfCommonSourceStep2 extends Step2BaseImporter
535
{
536
	abstract public function substep0();
537
538
	public function substep1()
539
	{
540
		$to_prefix = $this->config->to_prefix;
541
542
		$request = $this->db->query("
543
			SELECT
544
				id_board, MAX(id_msg) AS id_last_msg, MAX(modified_time) AS last_edited
545
			FROM {$to_prefix}messages
546
			GROUP BY id_board");
547
		$modifyData = array();
548
		$modifyMsg = array();
549
		while ($row = $this->db->fetch_assoc($request))
550
		{
551
			$this->setBoardProperty($row['id_board'], array('id_last_msg' => $row['id_last_msg'], 'id_msg_updated' => $row['id_last_msg']));
552
553
			$modifyData[$row['id_board']] = array(
554
				'last_msg' => $row['id_last_msg'],
555
				'last_edited' => $row['last_edited'],
556
			);
557
			$modifyMsg[] = $row['id_last_msg'];
558
		}
559
		$this->db->free_result($request);
560
561
		// Are there any boards where the updated message is not the last?
562
		if (!empty($modifyMsg))
563
		{
564
			$request = $this->db->query("
565
				SELECT id_board, id_msg, modified_time, poster_time
566
				FROM {$to_prefix}messages
567
				WHERE id_msg IN (" . implode(',', $modifyMsg) . ")");
568
			while ($row = $this->db->fetch_assoc($request))
569
			{
570
				// Have we got a message modified before this was posted?
571
				if (max($row['modified_time'], $row['poster_time']) < $modifyData[$row['id_board']]['last_edited'])
572
				{
573
					// Work out the ID of the message (This seems long but it won't happen much.
574
					$request2 = $this->db->query("
575
						SELECT id_msg
576
						FROM {$to_prefix}messages
577
						WHERE modified_time = " . $modifyData[$row['id_board']]['last_edited'] . "
578
						LIMIT 1");
579
					if ($this->db->num_rows($request2) != 0)
580
					{
581
						list ($id_msg) = $this->db->fetch_row($request2);
582
583
						$this->setBoardProperty($row['id_board'], array('id_msg_updated' => $id_msg));
584
					}
585
					$this->db->free_result($request2);
586
				}
587
			}
588
			$this->db->free_result($request);
589
		}
590
	}
591
592
	protected function setBoardProperty($board, $property, $where = null)
593
	{
594
		$to_prefix = $this->config->to_prefix;
595
596
		$sets = array();
597
		foreach ($property as $k => $v)
598
		{
599
			$sets[] = $k . ' = ' . $v;
600
		}
601
		$set = implode(', ', $sets);
602
603
		if ($where === null)
604
		{
605
			if (empty($board))
606
			{
607
				return;
608
			}
609
610
			$where = "id_board = $board";
611
		}
612
613
		$this->db->query("
614
			UPDATE {$to_prefix}boards
615
			SET $set
616
			WHERE $where");
617
	}
618
619
	public function substep2()
620
	{
621
		$to_prefix = $this->config->to_prefix;
622
623
		$request = $this->db->query("
624
			SELECT
625
				id_group
626
			FROM {$to_prefix}membergroups
627
			WHERE min_posts = -1");
628
629
		$all_groups = array();
630
		while ($row = $this->db->fetch_assoc($request))
631
		{
632
			$all_groups[] = $row['id_group'];
633
		}
634
		$this->db->free_result($request);
635
636
		$request = $this->db->query("
637
			SELECT
638
				id_board, member_groups
639
			FROM {$to_prefix}boards
640
			WHERE FIND_IN_SET(0, member_groups)");
641
642
		while ($row = $this->db->fetch_assoc($request))
643
		{
644
			$member_groups = "'" . implode(',', array_unique(array_merge($all_groups, explode(',', $row['member_groups'])))) . "'";
645
			$this->setBoardProperty($row['id_board'], array('member_groups' => $member_groups));
646
		}
647
		$this->db->free_result($request);
648
	}
649
650
	/**
651
	 * Fix various system totals
652
	 */
653
	public function substep3()
654
	{
655
		$to_prefix = $this->config->to_prefix;
656
657
		// Get the number of messages...
658
		$result = $this->db->query("
659
			SELECT
660
				COUNT(*) AS totalMessages, MAX(id_msg) AS maxMsgID
661
			FROM {$to_prefix}messages");
662
		$row = $this->db->fetch_assoc($result);
663
		$this->db->free_result($result);
664
665
		// Update the latest member. (Highest ID_MEMBER)
666
		$result = $this->db->query("
667
			SELECT
668
				id_member AS latestMember, real_name AS latestreal_name
669
			FROM {$to_prefix}members
670
			ORDER BY id_member DESC
671
			LIMIT 1");
672
673
		if ($this->db->num_rows($result))
674
		{
675
			$row += $this->db->fetch_assoc($result);
676
		}
677
		else
678
		{
679
			$row += array('latestMember' => '', 'latestreal_name' => '');
680
		}
681
682
		$this->db->free_result($result);
683
684
		// Update the member count.
685
		$result = $this->db->query("
686
			SELECT
687
				COUNT(*) AS totalMembers
688
			FROM {$to_prefix}members");
689
		$row += $this->db->fetch_assoc($result);
690
		$this->db->free_result($result);
691
692
		// Get the number of topics.
693
		$result = $this->db->query("
694
			SELECT
695
				COUNT(*) AS totalTopics
696
			FROM {$to_prefix}topics");
697
		$row += $this->db->fetch_assoc($result);
698
		$this->db->free_result($result);
699
700
		$this->db->query("
701
			REPLACE INTO {$to_prefix}settings
702
				(variable, value)
703
			VALUES ('latestMember', '$row[latestMember]'),
704
				('latestreal_name', '$row[latestreal_name]'),
705
				('totalMembers', '$row[totalMembers]'),
706
				('totalMessages', '$row[totalMessages]'),
707
				('maxMsgID', '$row[maxMsgID]'),
708
				('totalTopics', '$row[totalTopics]'),
709
				('disableHashTime', " . (time() + 7776000) . ")");
710
	}
711
712
	/**
713
	 * Fix the post-based membergroups
714
	 */
715
	public function substep4()
716
	{
717
		$to_prefix = $this->config->to_prefix;
718
719
		$request = $this->db->query("
720
			SELECT
721
				id_group, min_posts
722
			FROM {$to_prefix}membergroups
723
			WHERE min_posts != -1
724
			ORDER BY min_posts DESC");
725
726
		$post_groups = array();
727
		$max = $this->db->fetch_assoc($request);
728
		while ($row = $this->db->fetch_assoc($request))
729
		{
730
			$post_groups[] = $row;
731
		}
732
		$this->db->free_result($request);
733
734
		$case = "CASE WHEN posts > " . $max['min_posts'] . " THEN " . $max['id_group'];
735
736
		$first = true;
737
		foreach ($post_groups as $id => $group)
738
		{
739
			if ($first)
740
			{
741
				$case .= " WHEN posts BETWEEN " . $group['min_posts'] . " AND " . $max['min_posts'] . " THEN " . $group['id_group'];
742
				$first = false;
743
			}
744
			else
745
			{
746
				$case .= " WHEN posts BETWEEN " . $group['min_posts'] . " AND " . $post_groups[$id - 1]['min_posts'] . " THEN " . $group['id_group'];
747
			}
748
		}
749
		$case .= " ELSE 4 END";
750
751
		$this->db->query("
752
			UPDATE {$to_prefix}members
753
			SET id_post_group = $case");
754
	}
755
756
	/**
757
	 * Fix the boards total posts and topics.
758
	 */
759
	public function substep5()
760
	{
761
		$to_prefix = $this->config->to_prefix;
762
763
		$result_topics = $this->db->query("
764
			SELECT
765
				id_board, COUNT(*) as num_topics
766
			FROM {$to_prefix}topics
767
			GROUP BY id_board");
768
769
		$updates = array();
770
		while ($row = $this->db->fetch_assoc($result_topics))
771
		{
772
			$updates[$row['id_board']] = array(
773
				'num_topics' => $row['num_topics']
774
			);
775
		}
776
		$this->db->free_result($result_topics);
777
778
		// Find how many messages are in the board.
779
		$result_posts = $this->db->query("
780
			SELECT
781
				id_board, COUNT(*) as num_posts
782
			FROM {$to_prefix}messages
783
			GROUP BY id_board");
784
785
		while ($row = $this->db->fetch_assoc($result_posts))
786
		{
787
			$updates[$row['id_board']]['num_posts'] = $row['num_posts'];
788
		}
789
		$this->db->free_result($result_posts);
790
791
		// Fix the board's totals.
792
		foreach ($updates as $id_board => $vals)
793
		{
794
			$this->setBoardProperty($id_board, $vals);
795
		}
796
	}
797
798
	public function substep6()
799
	{
800
		$to_prefix = $this->config->to_prefix;
801
802
		while (true)
803
		{
804
			$resultTopic = $this->db->query("
805
				SELECT
806
					t.id_topic, COUNT(m.id_msg) AS num_msg
807
				FROM {$to_prefix}topics AS t
808
					LEFT JOIN {$to_prefix}messages AS m ON (m.id_topic = t.id_topic)
809
				GROUP BY t.id_topic
810
				HAVING num_msg = 0
811
				LIMIT $_REQUEST[start], 200");
812
813
			$numRows = $this->db->num_rows($resultTopic);
814
815
			if ($numRows > 0)
816
			{
817
				$stupidTopics = array();
818
				while ($topicArray = $this->db->fetch_assoc($resultTopic))
819
				{
820
					$stupidTopics[] = $topicArray['id_topic'];
821
				}
822
				$this->db->query("
823
					DELETE FROM {$to_prefix}topics
824
					WHERE id_topic IN (" . implode(',', $stupidTopics) . ')
825
					LIMIT ' . count($stupidTopics));
826
				$this->db->query("
827
					DELETE FROM {$to_prefix}log_topics
828
					WHERE id_topic IN (" . implode(',', $stupidTopics) . ')');
829
			}
830
			$this->db->free_result($resultTopic);
831
832
			if ($numRows < 200)
833
			{
834
				break;
835
			}
836
837
			// @todo this should not deal with $_REQUEST and alike
838
			$_REQUEST['start'] += 200;
839
			pastTime(6);
840
		}
841
	}
842
843
	public function substep7()
844
	{
845
		$to_prefix = $this->config->to_prefix;
846
847
		while (true)
848
		{
849
			$resultTopic = $this->db->query("
850
				SELECT
851
					t.id_topic, MIN(m.id_msg) AS myid_first_msg, t.id_first_msg,
852
					MAX(m.id_msg) AS myid_last_msg, t.id_last_msg, COUNT(m.id_msg) - 1 AS my_num_replies,
853
					t.num_replies
854
				FROM {$to_prefix}topics AS t
855
					LEFT JOIN {$to_prefix}messages AS m ON (m.id_topic = t.id_topic)
856
				GROUP BY t.id_topic
857
				HAVING id_first_msg != myid_first_msg OR id_last_msg != myid_last_msg OR num_replies != my_num_replies
858
				LIMIT $_REQUEST[start], 200");
859
860
			$numRows = $this->db->num_rows($resultTopic);
861
862
			while ($topicArray = $this->db->fetch_assoc($resultTopic))
863
			{
864
				$memberStartedID = $this->getMsgMemberID($topicArray['myid_first_msg']);
865
				$memberUpdatedID = $this->getMsgMemberID($topicArray['myid_last_msg']);
866
867
				$this->db->query("
868
					UPDATE {$to_prefix}topics
869
					SET id_first_msg = '$topicArray[myid_first_msg]',
870
						id_member_started = '$memberStartedID', id_last_msg = '$topicArray[myid_last_msg]',
871
						id_member_updated = '$memberUpdatedID', num_replies = '$topicArray[my_num_replies]'
872
					WHERE id_topic = $topicArray[id_topic]
873
					LIMIT 1");
874
			}
875
			$this->db->free_result($resultTopic);
876
877
			if ($numRows < 200)
878
			{
879
				break;
880
			}
881
882
			// @todo this should not deal with $_REQUEST and alike
883
			$_REQUEST['start'] += 100;
884
			pastTime(7);
885
		}
886
	}
887
888
	/**
889
	 *
890
	 * Get the id_member associated with the specified message.
891
	 *
892
	 * @param int $messageID
893
	 * @return int
894
	 */
895
	protected function getMsgMemberID($messageID)
896
	{
897
		$to_prefix = $this->config->to_prefix;
898
899
		// Find the topic and make sure the member still exists.
900
		$result = $this->db->query("
901
			SELECT
902
				IFNULL(mem.id_member, 0)
903
			FROM {$to_prefix}messages AS m
904
			LEFT JOIN {$to_prefix}members AS mem ON (mem.id_member = m.id_member)
905
			WHERE m.id_msg = " . (int) $messageID . "
906
			LIMIT 1");
907
908
		if ($this->db->num_rows($result) > 0)
909
		{
910
			list ($memberID) = $this->db->fetch_row($result);
911
		}
912
		// The message doesn't even exist.
913
		else
914
		{
915
			$memberID = 0;
916
		}
917
918
		$this->db->free_result($result);
919
920
		return $memberID;
921
	}
922
923
	/**
924
	 * Fix the board parents.
925
	 */
926
	public function substep8()
927
	{
928
		$to_prefix = $this->config->to_prefix;
929
930
		// First, let's get an array of boards and parents.
931
		$request = $this->db->query("
932
			SELECT
933
				id_board, id_parent, id_cat
934
			FROM {$to_prefix}boards");
935
936
		$child_map = array();
937
		$cat_map = array();
938
		while ($row = $this->db->fetch_assoc($request))
939
		{
940
			$child_map[$row['id_parent']][] = $row['id_board'];
941
			$cat_map[$row['id_board']] = $row['id_cat'];
942
		}
943
		$this->db->free_result($request);
944
945
		// Let's look for any boards with obviously invalid parents...
946
		foreach ($child_map as $parent => $dummy)
947
		{
948
			if ($parent != 0 && !isset($cat_map[$parent]))
949
			{
950
				// Perhaps it was supposed to be their id_cat?
951
				foreach ($dummy as $board)
952
				{
953
					if (empty($cat_map[$board]))
954
					{
955
						$cat_map[$board] = $parent;
956
					}
957
				}
958
959
				$child_map[0] = array_merge(isset($child_map[0]) ? $child_map[0] : array(), $dummy);
960
				unset($child_map[$parent]);
961
			}
962
		}
963
964
		// The above id_parents and id_cats may all be wrong; we know id_parent = 0 is right.
965
		$solid_parents = array(array(0, 0));
966
		$fixed_boards = array();
967
		while (!empty($solid_parents))
968
		{
969
			list ($parent, $level) = array_pop($solid_parents);
970
			if (!isset($child_map[$parent]))
971
			{
972
				continue;
973
			}
974
975
			// Fix all of this board's children.
976
			foreach ($child_map[$parent] as $board)
977
			{
978
				if ($parent != 0)
979
				{
980
					$cat_map[$board] = $cat_map[$parent];
981
				}
982
983
				$this->setBoardProperty((int) $board, array('id_parent' => (int) $parent, 'id_cat' => (int) $cat_map[$board], 'child_level' => (int) $level));
984
985
				$fixed_boards[] = $board;
986
				$solid_parents[] = array($board, $level + 1);
987
			}
988
		}
989
990
		// Leftovers should be brought to the root. They had weird parents we couldn't find.
991
		if (count($fixed_boards) < count($cat_map))
992
		{
993
			$this->setBoardProperty(0, array('child_level' => 0, 'id_parent' => 0, 'child_level' => (int) $level), empty($fixed_boards) ? "1=1" : "id_board NOT IN (" . implode(', ', $fixed_boards) . ")");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $level does not seem to be defined for all execution paths leading up to this point.
Loading history...
994
		}
995
996
		$this->fixInexistentCategories($cat_map);
997
	}
998
999
	/**
1000
	 * Assigns any board belonging to a category that doesn't exist
1001
	 * to a newly created category.
1002
	 */
1003
	protected function fixInexistentCategories($cat_map)
1004
	{
1005
		$to_prefix = $this->config->to_prefix;
1006
1007
		// Last check: any boards not in a good category?
1008
		$request = $this->db->query("
1009
			SELECT
1010
				id_cat
1011
			FROM {$to_prefix}categories");
1012
		$real_cats = array();
1013
		while ($row = $this->db->fetch_assoc($request))
1014
		{
1015
			$real_cats[] = $row['id_cat'];
1016
		}
1017
		$this->db->free_result($request);
1018
1019
		$fix_cats = array();
1020
		foreach ($cat_map as $cat)
1021
		{
1022
			if (!in_array($cat, $real_cats))
1023
			{
1024
				$fix_cats[] = $cat;
1025
			}
1026
		}
1027
1028
		if (!empty($fix_cats))
1029
		{
1030
			$this->db->query("
1031
				INSERT INTO {$to_prefix}categories
1032
					(name)
1033
				VALUES ('General Category')");
1034
			$catch_cat = mysqli_insert_id($this->db->con);
0 ignored issues
show
Bug introduced by
The property con does not seem to exist on OpenImporter\Database.
Loading history...
1035
1036
			$this->setBoardProperty(0, array('id_cat' => (int) $catch_cat), "id_cat IN (" . implode(', ', array_unique($fix_cats)) . ")");
1037
		}
1038
	}
1039
1040
	/**
1041
	 * Adjust boards and categories orders.
1042
	 */
1043
	public function substep9()
1044
	{
1045
		$to_prefix = $this->config->to_prefix;
1046
1047
		$request = $this->db->query("
1048
			SELECT
1049
				c.id_cat, c.cat_order, b.id_board, b.board_order
1050
			FROM {$to_prefix}categories AS c
1051
				LEFT JOIN {$to_prefix}boards AS b ON (b.id_cat = c.id_cat)
1052
			ORDER BY c.cat_order, b.child_level, b.board_order, b.id_board");
1053
		$cat_order = -1;
1054
		$board_order = -1;
1055
		$curCat = -1;
1056
		while ($row = $this->db->fetch_assoc($request))
1057
		{
1058
			if ($curCat != $row['id_cat'])
1059
			{
1060
				$curCat = $row['id_cat'];
1061
				if (++$cat_order != $row['cat_order'])
1062
				{
1063
					$this->db->query("
1064
						UPDATE {$to_prefix}categories
1065
						SET cat_order = $cat_order
1066
						WHERE id_cat = $row[id_cat]
1067
						LIMIT 1");
1068
				}
1069
			}
1070
			if (!empty($row['id_board']) && ++$board_order != $row['board_order'])
1071
			{
1072
				$this->setBoardProperty($row['id_board'], array('board_order' => $board_order));
1073
			}
1074
		}
1075
		$this->db->free_result($request);
1076
	}
1077
1078
	/**
1079
	 * Fix attachment size, W & H values
1080
	 */
1081
	public function substep11()
1082
	{
1083
		$to_prefix = $this->config->to_prefix;
1084
1085
		$request = $this->db->query("
1086
			SELECT
1087
				COUNT(*)
1088
			FROM {$to_prefix}attachments");
1089
		list ($attachments) = $this->db->fetch_row($request);
1090
		$this->db->free_result($request);
1091
1092
		while ($_REQUEST['start'] < $attachments)
1093
		{
1094
			$request = $this->db->query("
1095
				SELECT
1096
					id_attach, filename, attachment_type, id_folder
1097
				FROM {$to_prefix}attachments
1098
				WHERE id_thumb = 0
1099
					AND (RIGHT(filename, 4) IN ('.gif', '.jpg', '.png', '.bmp') OR RIGHT(filename, 5) = '.jpeg')
1100
					AND width = 0
1101
					AND height = 0
1102
				LIMIT $_REQUEST[start], 500");
1103
1104
			if ($this->db->num_rows($request) == 0)
1105
			{
1106
				break;
1107
			}
1108
1109
			while ($row = $this->db->fetch_assoc($request))
1110
			{
1111
				$filename = $this->avatarFullPath($row);
1112
1113
				// Probably not one of the imported ones, then?
1114
				if (!file_exists($filename))
1115
				{
1116
					continue;
1117
				}
1118
1119
				$size = @getimagesize($filename);
1120
				$filesize = @filesize($filename);
1121
				if (!empty($size) && !empty($size[0]) && !empty($size[1]) && !empty($filesize))
1122
				{
1123
					$this->db->query("
1124
						UPDATE {$to_prefix}attachments
1125
						SET
1126
							size = " . (int) $filesize . ",
1127
							width = " . (int) $size[0] . ",
1128
							height = " . (int) $size[1] . "
1129
						WHERE id_attach = $row[id_attach]
1130
						LIMIT 1");
1131
				}
1132
			}
1133
			$this->db->free_result($request);
1134
1135
			// More?
1136
			// We can't keep importing the same files over and over again!
1137
			$_REQUEST['start'] += 500;
1138
			pastTime(11);
1139
		}
1140
	}
1141
1142
	protected function avatarFullPath($row)
1143
	{
1144
		$dir = $this->config->destination->getAvatarDir($row);
1145
1146
		if ($row['attachment_type'] == 1)
1147
		{
1148
			// @todo Honestly I'm not sure what the final name looks like
1149
			// I'm pretty sure there could be at least three options:
1150
			//   1) filename
1151
			//   2) avatar_{id_member}_{time()}.{file_extension}
1152
			//   3) {id_attach}_{file_hash}.{->attach_extension}
1153
			$filename = $row['filename'];
1154
		}
1155
		else
1156
		{
1157
			$filename = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach']);
1158
		}
1159
1160
		return $dir . '/' . $filename;
1161
	}
1162
}
1163
1164
/**
1165
 * Class SmfCommonSourceStep3
1166
 *
1167
 * @class SmfCommonSourceStep3
1168
 */
1169
abstract class SmfCommonSourceStep3 extends Step3BaseImporter
1170
{
1171
	public function run($import_script)
1172
	{
1173
		$to_prefix = $this->config->to_prefix;
1174
1175
		// add some importer information.
1176
		$this->db->query("
1177
			REPLACE INTO {$to_prefix}settings (variable, value)
1178
				VALUES ('import_time', " . time() . "),
1179
					('enable_password_conversion', '1'),
1180
					('imported_from', '" . $import_script . "')");
1181
	}
1182
}
1183