Completed
Push — master ( 8c4a5f...d5eaf6 )
by Thorsten
02:24
created

SmfCommonSourceStep2   C

Complexity

Total Complexity 70

Size/Duplication

Total Lines 585
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 70
lcom 1
cbo 3
dl 0
loc 585
rs 5.6163
c 2
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
substep0() 0 1 ?
B substep1() 0 53 6
B setBoardProperty() 0 24 4
B substep2() 0 28 3
A substep3() 0 58 2
B substep4() 0 36 4
B substep5() 0 34 4
B substep6() 0 40 5
B substep7() 0 42 4
A getMsgMemberID() 0 23 2
C substep8() 0 66 14
B fixInexistentCategories() 0 32 5
B substep9() 0 30 6
B substep11() 0 54 9
A avatarFullPath() 0 18 2

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 http://opensource.org/licenses/BSD-3-Clause
6
 *
7
 * @version 1.0 Alpha
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 Importers;
17
18
use OpenImporter\Configurator;
19
use OpenImporter\Database;
20
21
/**
22
 * Class SmfCommonSource
23
 * The class contains code that allows the Importer to obtain settings
24
 * from software that still has an SMF heritage.
25
 *
26
 * @package Importers
27
 */
28
abstract class SmfCommonSource
29
{
30
	/**
31
	 * The attachment extension
32
	 * @var string
33
	 */
34
	public $attach_extension = '';
35
36
	/**
37
	 * Path to the forum
38
	 * @var null
39
	 */
40
	protected $path = null;
41
42
	/**
43
	 * @var null|int
44
	 */
45
	public $id_attach = null;
46
47
	/**
48
	 * @var null|string
49
	 */
50
	public $attachmentUploadDirs = null;
51
52
	/**
53
	 * @var null|string
54
	 */
55
	public $avatarUploadDir = null;
56
57
	/**
58
	 * @var Configurator
59
	 */
60
	protected $config = null;
61
62
	/**
63
	 * @var Database
64
	 */
65
	protected $db = null;
66
67
	/**
68
	 * Set the database and configuration to the class
69
	 *
70
	 * @param Database $db
71
	 * @param Configurator $config
72
	 */
73
	public function setParam($db, $config)
74
	{
75
		$this->db = $db;
76
		$this->config = $config;
77
	}
78
79
	abstract public function getName();
80
81
	/**
82
	 * Check that the Settings.php file exists
83
	 *
84
	 * @param $path
85
	 *
86
	 * @return bool
87
	 */
88
	public function checkSettingsPath($path)
89
	{
90
		$found = file_exists($path . '/Settings.php');
91
92
		if ($found && $this->path === null)
93
			$this->path = $path;
94
95
		return $found;
96
	}
97
98
	/**
99
	 * The path to our destination forum
100
	 *
101
	 * @param $path
102
	 *
103
	 * @return bool|string
104
	 */
105
	public function getDestinationURL($path)
106
	{
107
		// Cannot find Settings.php?
108
		if (!$this->checkSettingsPath($path))
109
			return false;
110
111
		// Everything should be alright now... no cross server includes, we hope...
112
		return $this->fetchSetting('boardurl');
113
	}
114
115
	public function getFormFields($path_to = '')
116
	{
117
		return array(
118
			'id' => 'path_to',
119
			'label' => 'path_to_destination',
120
			'type' => 'text',
121
			'default' => htmlspecialchars($path_to),
122
			'correct' => $this->checkSettingsPath($path_to) ? 'right_path' : 'change_path',
123
			'validate' => true,
124
		);
125
	}
126
127
	public function verifyDbPass($pwd_to_verify)
128
	{
129
		if ($this->path === null)
130
			return false;
131
132
		$db_passwd = $this->fetchSetting('db_passwd');
133
134
		return $db_passwd == $pwd_to_verify;
135
	}
136
137
	public function dbConnectionData()
138
	{
139
		if ($this->path === null)
140
			return false;
141
142
		$db_server = $this->fetchSetting('db_server');
143
		$db_user = $this->fetchSetting('db_user');
144
		$db_passwd = $this->fetchSetting('db_passwd');
145
		$db_persist = $this->fetchSetting('db_persist');
146
		$db_prefix = $this->fetchSetting('db_prefix');
147
		$db_name = $this->fetchSetting('db_name');
148
149
		return array($db_server, $db_user, $db_passwd, $db_persist, $db_prefix, $db_name);
150
	}
151
152
	protected function fetchSetting($name)
153
	{
154
		static $content = null;
155
156
		if ($content === null)
157
			$content = file_get_contents($this->path . '/Settings.php');
158
159
		$match = array();
160
		preg_match('~\$' . $name . '\s*=\s*\'(.*?)\';~', $content, $match);
161
162
		return isset($match[1]) ? $match[1] : '';
163
	}
164
165
	/**
166
	 * Helper function for old (SMF) attachments and some new ones
167
	 *
168
	 * @param string $filename
169
	 * @param int $attachment_id
170
	 * @param bool $legacy if true returns legacy SMF file name (default true)
171
	 * @return string
172
	 */
173
	public function getLegacyAttachmentFilename($filename, $attachment_id, $legacy = true)
174
	{
175
		// Remove special accented characters - ie. sí (because they won't write to the filesystem well.)
176
		$clean_name = strtr($filename, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
177
		$clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));
178
179
			// Get rid of dots, spaces, and other weird characters.
180
		$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
181
182
		if ($legacy)
183
		{
184
			// @todo not sure about that one
185
			$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
186
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
187
		}
188
		else
189
		{
190
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name) . '.' . $this->attach_extension;
191
		}
192
	}
193
194
	/**
195
	 * Determine the source attachment and avatar directories
196
	 *
197
	 * @param bool $force
198
	 */
199
	public function specialAttachments($force = false)
200
	{
201
		$to_prefix = $this->config->to_prefix;
202
203
		// If we don't know the attachment or avatar directories
204
		if ($force === true || !isset($this->id_attach, $this->attachmentUploadDirs, $this->avatarUploadDir))
205
		{
206
			// Attachment starting id
207
			$result = $this->db->query("
208
				SELECT MAX(id_attach) + 1
209
				FROM {$to_prefix}attachments");
210
			list ($this->id_attach) = $this->db->fetch_row($result);
211
			$this->db->free_result($result);
212
213
			// Attachment directories
214
			$result = $this->db->query("
215
				SELECT value
216
				FROM {$to_prefix}settings
217
				WHERE variable = 'attachmentUploadDir'
218
				LIMIT 1");
219
			list ($attachmentdir) = $this->db->fetch_row($result);
220
			$attachment_UploadDir = @unserialize($attachmentdir);
221
			$this->attachmentUploadDirs = !empty($attachment_UploadDir) ? $attachment_UploadDir : array(1 => $attachmentdir);
222
223
			// Avatar directories
224
			$result = $this->db->query("
225
				SELECT value
226
				FROM {$to_prefix}settings
227
				WHERE variable = 'custom_avatar_dir'
228
				LIMIT 1");
229
			list ($this->avatarUploadDir) = $this->db->fetch_row($result);
230
			$this->db->free_result($result);
231
232
			if (empty($this->avatarUploadDir))
233
				$this->avatarUploadDir = '';
234
235
			if (empty($this->id_attach))
236
				$this->id_attach = 1;
237
		}
238
	}
239
240
	public function getAvatarDir($row)
241
	{
242
		if ($this->avatarUploadDir === null)
243
			return $this->getAttachDir($row);
244
		else
245
			return $this->avatarUploadDir;
246
	}
247
248
	public function getAttachDir($row)
249
	{
250
		$this->specialAttachments();
251
252
		if (!empty($row['id_folder']) && !empty($this->attachmentUploadDirs[$row['id_folder']]))
253
			return $this->attachmentUploadDirs[$row['id_folder']];
254
		else
255
			return $this->attachmentUploadDirs[1];
256
	}
257
258
	public function getAllAttachDirs()
259
	{
260
		if ($this->attachmentUploadDirs === null)
261
			$this->specialAttachments();
262
263
		return $this->attachmentUploadDirs;
264
	}
265
}
266
267
/**
268
 * Class SmfCommonSourceStep1
269
 *
270
 * @package Importers
271
 */
272
abstract class SmfCommonSourceStep1 extends Step1BaseImporter
273
{
274
	public function fixTexts($row)
275
	{
276
		// If we have a message here, we'll want to convert <br /> to <br>.
277
		if (isset($row['body']))
278
		{
279
			$row['body'] = str_replace(array(
280
					'<br />', '&#039;', '&#39;', '&quot;'
281
				), array(
282
					'<br>', '\'', '\'', '"'
283
				), $row['body']
284
			);
285
		}
286
287
		return $row;
288
	}
289
290
	public function doSpecialTable($special_table, $params = null)
291
	{
292
		// Are we doing attachments? They're going to want a few things...
293
		if ($special_table == $this->config->to_prefix . 'attachments' && $params === null)
294
		{
295
			$this->config->destination->specialAttachments();
296
297
			return $params;
298
		}
299
		// Here we have various bits of custom code for some known problems global to all importers.
300
		elseif ($special_table == $this->config->to_prefix . 'members' && $params !== null)
301
			return $this->specialMembers($params);
302
303
		return $params;
304
	}
305
306
	/**
307
	 * Delete any defined db attachments that exist on disk for the source system
308
	 */
309
	public function removeAttachments()
310
	{
311
		$to_prefix = $this->config->to_prefix;
312
313
		// @todo This should probably be done in chunks too.
314
		// attachment_type = 1 are avatars.
315
		$result = $this->db->query("
316
			SELECT id_attach, filename, id_folder
317
			FROM {$to_prefix}attachments");
318
319
		while ($row = $this->db->fetch_assoc($result))
320
		{
321
			$enc_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], false);
322
323
			$attach_dir = $this->getAttachDir($row);
324
325
			if (file_exists($attach_dir . '/' . $enc_name))
326
				$filename = $attach_dir . '/' . $enc_name;
327
			else
328
			{
329
				// @todo this should not be here I think: it's SMF-specific, while this file shouldn't know anything about the source
330
				$clean_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], true);
331
				$filename = $attach_dir . '/' . $clean_name;
332
			}
333
334
			if (is_file($filename))
335
				@unlink($filename);
336
		}
337
338
		$this->db->free_result($result);
339
340
		// This is valid for some of the sources (e.g. Elk/SMF/Wedge), but not for others
341
		if (method_exists($this->config->source, 'getAttachmentDirs'))
342
		{
343
			$this->createAttachFoldersStructure($this->config->source->getAttachmentDirs());
344
		}
345
	}
346
347
	/**
348
	 * Create the destination directory structure for the attachments
349
	 *
350
	 * @param string[] $folders
351
	 *
352
	 * @return bool
353
	 */
354
	protected function createAttachFoldersStructure($folders)
355
	{
356
		$source_base = $this->guessBase($folders);
357
		$destination_base = $this->guessBase($this->config->destination->getAllAttachDirs());
358
359
		// No idea where to start, better not mess with the filesystem
360
		// Though if $destination_base is empty it *is* a mess.
361
		if (empty($source_base) || empty($destination_base))
362
			return false;
363
364
		$dest_folders = str_replace($source_base, $destination_base, $folders);
365
366
		// Prepare the directory structure
367
		foreach ($dest_folders as $folder)
368
			create_folders_recursive($folder);
369
370
		// Save the new structure in the database
371
		$this->db->query("
372
			UPDATE {$this->config->to_prefix}settings
373
			SET value = '" . serialize($dest_folders) . "'
374
			WHERE variable = 'attachmentUploadDir'
375
			LIMIT 1");
376
377
		// Reload the new directories
378
		$this->config->destination->specialAttachments(true);
379
	}
380
381
	/**
382
	 * Determine the base / root folder for attachments
383
	 *
384
	 * @param string[] $folders
385
	 *
386
	 * @return bool|string
387
	 */
388
	protected function guessBase($folders)
389
	{
390
		foreach ($folders as $folder)
391
		{
392
			if ($this->isCommon($folder, $folders))
393
			{
394
				return $folder;
395
			}
396
		}
397
398
		foreach ($folders as $folder)
399
		{
400
			$dir = $folder;
401
			while (strlen($dir) > 4)
402
			{
403
				$dir = dirname($dir);
404
				if ($this->isCommon($dir, $folders))
405
					return $dir;
406
			}
407
		}
408
409
		return false;
410
	}
411
412
	protected function isCommon($dir, $folders)
413
	{
414
		foreach ($folders as $folder)
415
		{
416
			if (substr($folder, 0, strlen($dir)) !== $dir)
417
				return false;
418
		}
419
420
		return true;
421
	}
422
423
	public function getAttachDir($row)
424
	{
425
		return $this->config->destination->getAttachDir($row);
426
	}
427
428
	public function getAvatarDir($row)
429
	{
430
		return $this->config->destination->getAvatarDir($row);
431
	}
432
433
	public function getAvatarFolderId($row)
434
	{
435
		// @todo in theory we could be able to find the "current" directory
436
		if ($this->config->destination->avatarUploadDir === null)
437
			return 1;
438
		else
439
			return false;
440
	}
441
442
	public function newIdAttach()
443
	{
444
		// The one to return
445
		$current = $this->config->destination->id_attach;
446
447
		// Increase preparing for the next one
448
		$this->config->destination->id_attach++;
449
450
		return $current;
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
	protected function specialMembers($row)
502
	{
503
		// Let's ensure there are no illegal characters.
504
		$row['member_name'] = preg_replace('/[<>&"\'=\\\]/is', '', $row['member_name']);
505
		$row['real_name'] = trim($row['real_name'], " \t\n\r\x0B\0\xA0");
506
507
		if (strpos($row['real_name'], '<') !== false || strpos($row['real_name'], '>') !== false || strpos($row['real_name'], '& ') !== false)
508
			$row['real_name'] = htmlspecialchars($row['real_name'], ENT_QUOTES);
509
		else
510
			$row['real_name'] = strtr($row['real_name'], array('\'' => '&#039;'));
511
512
		return $row;
513
	}
514
}
515
516
/**
517
 * Class SmfCommonSourceStep2
518
 *
519
 * @package Importers
520
 */
521
abstract class SmfCommonSourceStep2 extends Step2BaseImporter
522
{
523
	abstract public function substep0();
524
525
	public function substep1()
526
	{
527
		$to_prefix = $this->config->to_prefix;
528
529
		$request = $this->db->query("
530
			SELECT
531
				id_board, MAX(id_msg) AS id_last_msg, MAX(modified_time) AS last_edited
532
			FROM {$to_prefix}messages
533
			GROUP BY id_board");
534
		$modifyData = array();
535
		$modifyMsg = array();
536
		while ($row = $this->db->fetch_assoc($request))
537
		{
538
			$this->setBoardProperty($row['id_board'], array('id_last_msg' => $row['id_last_msg'], 'id_msg_updated' => $row['id_last_msg']));
539
540
			$modifyData[$row['id_board']] = array(
541
				'last_msg' => $row['id_last_msg'],
542
				'last_edited' => $row['last_edited'],
543
			);
544
			$modifyMsg[] = $row['id_last_msg'];
545
		}
546
		$this->db->free_result($request);
547
548
		// Are there any boards where the updated message is not the last?
549
		if (!empty($modifyMsg))
550
		{
551
			$request = $this->db->query("
552
				SELECT id_board, id_msg, modified_time, poster_time
553
				FROM {$to_prefix}messages
554
				WHERE id_msg IN (" . implode(',', $modifyMsg) . ")");
555
			while ($row = $this->db->fetch_assoc($request))
556
			{
557
				// Have we got a message modified before this was posted?
558
				if (max($row['modified_time'], $row['poster_time']) < $modifyData[$row['id_board']]['last_edited'])
559
				{
560
					// Work out the ID of the message (This seems long but it won't happen much.
561
					$request2 = $this->db->query("
562
						SELECT id_msg
563
						FROM {$to_prefix}messages
564
						WHERE modified_time = " . $modifyData[$row['id_board']]['last_edited'] . "
565
						LIMIT 1");
566
					if ($this->db->num_rows($request2) != 0)
567
					{
568
						list ($id_msg) = $this->db->fetch_row($request2);
569
570
						$this->setBoardProperty($row['id_board'], array('id_msg_updated' => $id_msg));
571
					}
572
					$this->db->free_result($request2);
573
				}
574
			}
575
			$this->db->free_result($request);
576
		}
577
	}
578
579
	protected function setBoardProperty($board, $property, $where = null)
580
	{
581
		$to_prefix = $this->config->to_prefix;
582
583
		$sets = array();
584
		foreach ($property as $k => $v)
585
		{
586
			$sets[] = $k . ' = ' . $v;
587
		}
588
		$set = implode(', ', $sets);
589
590
		if ($where === null)
591
		{
592
			if (empty($board))
593
				return;
594
595
			$where = "id_board = $board";
596
		}
597
598
		$this->db->query("
599
			UPDATE {$to_prefix}boards
600
			SET $set
601
			WHERE $where");
602
	}
603
604
	public function substep2()
605
	{
606
		$to_prefix = $this->config->to_prefix;
607
608
		$request = $this->db->query("
609
			SELECT
610
				id_group
611
			FROM {$to_prefix}membergroups
612
			WHERE min_posts = -1");
613
614
		$all_groups = array();
615
		while ($row = $this->db->fetch_assoc($request))
616
			$all_groups[] = $row['id_group'];
617
		$this->db->free_result($request);
618
619
		$request = $this->db->query("
620
			SELECT
621
				id_board, member_groups
622
			FROM {$to_prefix}boards
623
			WHERE FIND_IN_SET(0, member_groups)");
624
625
		while ($row = $this->db->fetch_assoc($request))
626
		{
627
			$member_groups = "'" . implode(',', array_unique(array_merge($all_groups, explode(',', $row['member_groups'])))) . "'";
628
			$this->setBoardProperty($row['id_board'], array('member_groups' => $member_groups));
629
		}
630
		$this->db->free_result($request);
631
	}
632
633
	/**
634
	 * Fix various system totals
635
	 */
636
	public function substep3()
637
	{
638
		$to_prefix = $this->config->to_prefix;
639
640
		// Get the number of messages...
641
		$result = $this->db->query("
642
			SELECT
643
				COUNT(*) AS totalMessages, MAX(id_msg) AS maxMsgID
644
			FROM {$to_prefix}messages");
645
		$row = $this->db->fetch_assoc($result);
646
		$this->db->free_result($result);
647
648
		// Update the latest member. (Highest ID_MEMBER)
649
		$result = $this->db->query("
650
			SELECT
651
				id_member AS latestMember, real_name AS latestreal_name
652
			FROM {$to_prefix}members
653
			ORDER BY id_member DESC
654
			LIMIT 1");
655
656
		if ($this->db->num_rows($result))
657
		{
658
			$row += $this->db->fetch_assoc($result);
659
		}
660
		else
661
		{
662
			$row += array('latestMember' => '', 'latestreal_name' => '');
663
		}
664
665
		$this->db->free_result($result);
666
667
		// Update the member count.
668
		$result = $this->db->query("
669
			SELECT
670
				COUNT(*) AS totalMembers
671
			FROM {$to_prefix}members");
672
		$row += $this->db->fetch_assoc($result);
673
		$this->db->free_result($result);
674
675
		// Get the number of topics.
676
		$result = $this->db->query("
677
			SELECT
678
				COUNT(*) AS totalTopics
679
			FROM {$to_prefix}topics");
680
		$row += $this->db->fetch_assoc($result);
681
		$this->db->free_result($result);
682
683
		$this->db->query("
684
			REPLACE INTO {$to_prefix}settings
685
				(variable, value)
686
			VALUES ('latestMember', '$row[latestMember]'),
687
				('latestreal_name', '$row[latestreal_name]'),
688
				('totalMembers', '$row[totalMembers]'),
689
				('totalMessages', '$row[totalMessages]'),
690
				('maxMsgID', '$row[maxMsgID]'),
691
				('totalTopics', '$row[totalTopics]'),
692
				('disableHashTime', " . (time() + 7776000) . ")");
693
	}
694
695
	/**
696
	 * Fix the post-based membergroups
697
	 */
698
	public function substep4()
699
	{
700
		$to_prefix = $this->config->to_prefix;
701
702
		$request = $this->db->query("
703
			SELECT
704
				id_group, min_posts
705
			FROM {$to_prefix}membergroups
706
			WHERE min_posts != -1
707
			ORDER BY min_posts DESC");
708
709
		$post_groups = array();
710
		$max = $this->db->fetch_assoc($request);
711
		while ($row = $this->db->fetch_assoc($request))
712
			$post_groups[] = $row;
713
		$this->db->free_result($request);
714
715
		$case = "CASE WHEN posts > " . $max['min_posts'] . " THEN " . $max['id_group'];
716
717
		$first = true;
718
		foreach ($post_groups as $id => $group)
719
		{
720
			if ($first)
721
			{
722
				$case .= " WHEN posts BETWEEN " . $group['min_posts'] . " AND " . $max['min_posts'] . " THEN " . $group['id_group'];
723
				$first = false;
724
			}
725
			else
726
				$case .= " WHEN posts BETWEEN " . $group['min_posts'] . " AND " . $post_groups[$id - 1]['min_posts'] . " THEN " . $group['id_group'];
727
		}
728
		$case .= " ELSE 4 END";
729
730
		$this->db->query("
731
			UPDATE {$to_prefix}members
732
			SET id_post_group = $case");
733
	}
734
735
	/**
736
	 * Fix the boards total posts and topics.
737
	 */
738
	public function substep5()
739
	{
740
		$to_prefix = $this->config->to_prefix;
741
742
		$result_topics = $this->db->query("
743
			SELECT
744
				id_board, COUNT(*) as num_topics
745
			FROM {$to_prefix}topics
746
			GROUP BY id_board");
747
748
		$updates = array();
749
		while ($row = $this->db->fetch_assoc($result_topics))
750
			$updates[$row['id_board']] = array(
751
				'num_topics' => $row['num_topics']
752
			);
753
		$this->db->free_result($result_topics);
754
755
		// Find how many messages are in the board.
756
		$result_posts = $this->db->query("
757
			SELECT
758
				id_board, COUNT(*) as num_posts
759
			FROM {$to_prefix}messages
760
			GROUP BY id_board");
761
762
		while ($row = $this->db->fetch_assoc($result_posts))
763
			$updates[$row['id_board']]['num_posts'] = $row['num_posts'];
764
		$this->db->free_result($result_posts);
765
766
		// Fix the board's totals.
767
		foreach ($updates as $id_board => $vals)
768
		{
769
			$this->setBoardProperty($id_board, $vals);
770
		}
771
	}
772
773
	public function substep6()
774
	{
775
		$to_prefix = $this->config->to_prefix;
776
777
		while (true)
778
		{
779
			$resultTopic = $this->db->query("
780
				SELECT
781
					t.id_topic, COUNT(m.id_msg) AS num_msg
782
				FROM {$to_prefix}topics AS t
783
					LEFT JOIN {$to_prefix}messages AS m ON (m.id_topic = t.id_topic)
784
				GROUP BY t.id_topic
785
				HAVING num_msg = 0
786
				LIMIT $_REQUEST[start], 200");
787
788
			$numRows = $this->db->num_rows($resultTopic);
789
790
			if ($numRows > 0)
791
			{
792
				$stupidTopics = array();
793
				while ($topicArray = $this->db->fetch_assoc($resultTopic))
794
					$stupidTopics[] = $topicArray['id_topic'];
795
				$this->db->query("
796
					DELETE FROM {$to_prefix}topics
797
					WHERE id_topic IN (" . implode(',', $stupidTopics) . ')
798
					LIMIT ' . count($stupidTopics));
799
				$this->db->query("
800
					DELETE FROM {$to_prefix}log_topics
801
					WHERE id_topic IN (" . implode(',', $stupidTopics) . ')');
802
			}
803
			$this->db->free_result($resultTopic);
804
805
			if ($numRows < 200)
806
				break;
807
808
			// @todo this should not deal with $_REQUEST and alike
809
			$_REQUEST['start'] += 200;
810
			pastTime(6);
811
		}
812
	}
813
814
	public function substep7()
815
	{
816
		$to_prefix = $this->config->to_prefix;
817
818
		while (true)
819
		{
820
			$resultTopic = $this->db->query("
821
				SELECT
822
					t.id_topic, MIN(m.id_msg) AS myid_first_msg, t.id_first_msg,
823
					MAX(m.id_msg) AS myid_last_msg, t.id_last_msg, COUNT(m.id_msg) - 1 AS my_num_replies,
824
					t.num_replies
825
				FROM {$to_prefix}topics AS t
826
					LEFT JOIN {$to_prefix}messages AS m ON (m.id_topic = t.id_topic)
827
				GROUP BY t.id_topic
828
				HAVING id_first_msg != myid_first_msg OR id_last_msg != myid_last_msg OR num_replies != my_num_replies
829
				LIMIT $_REQUEST[start], 200");
830
831
			$numRows = $this->db->num_rows($resultTopic);
832
833
			while ($topicArray = $this->db->fetch_assoc($resultTopic))
834
			{
835
				$memberStartedID = $this->getMsgMemberID($topicArray['myid_first_msg']);
836
				$memberUpdatedID = $this->getMsgMemberID($topicArray['myid_last_msg']);
837
838
				$this->db->query("
839
					UPDATE {$to_prefix}topics
840
					SET id_first_msg = '$topicArray[myid_first_msg]',
841
						id_member_started = '$memberStartedID', id_last_msg = '$topicArray[myid_last_msg]',
842
						id_member_updated = '$memberUpdatedID', num_replies = '$topicArray[my_num_replies]'
843
					WHERE id_topic = $topicArray[id_topic]
844
					LIMIT 1");
845
			}
846
			$this->db->free_result($resultTopic);
847
848
			if ($numRows < 200)
849
				break;
850
851
			// @todo this should not deal with $_REQUEST and alike
852
			$_REQUEST['start'] += 100;
853
			pastTime(7);
854
		}
855
	}
856
857
	/**
858
	 *
859
	 * Get the id_member associated with the specified message.
860
	 *
861
	 * @param int $messageID
862
	 * @return int
863
	 */
864
	protected function getMsgMemberID($messageID)
865
	{
866
		$to_prefix = $this->config->to_prefix;
867
868
			// Find the topic and make sure the member still exists.
869
		$result = $this->db->query("
870
			SELECT
871
				IFNULL(mem.id_member, 0)
872
			FROM {$to_prefix}messages AS m
873
			LEFT JOIN {$to_prefix}members AS mem ON (mem.id_member = m.id_member)
874
			WHERE m.id_msg = " . (int) $messageID . "
875
			LIMIT 1");
876
877
		if ($this->db->num_rows($result) > 0)
878
			list ($memberID) = $this->db->fetch_row($result);
879
		// The message doesn't even exist.
880
		else
881
			$memberID = 0;
882
883
		$this->db->free_result($result);
884
885
		return $memberID;
886
	}
887
888
	/**
889
	 * Fix the board parents.
890
	 */
891
	public function substep8()
892
	{
893
		$to_prefix = $this->config->to_prefix;
894
895
		// First, let's get an array of boards and parents.
896
		$request = $this->db->query("
897
			SELECT
898
				id_board, id_parent, id_cat
899
			FROM {$to_prefix}boards");
900
901
		$child_map = array();
902
		$cat_map = array();
903
		while ($row = $this->db->fetch_assoc($request))
904
		{
905
			$child_map[$row['id_parent']][] = $row['id_board'];
906
			$cat_map[$row['id_board']] = $row['id_cat'];
907
		}
908
		$this->db->free_result($request);
909
910
		// Let's look for any boards with obviously invalid parents...
911
		foreach ($child_map as $parent => $dummy)
912
		{
913
			if ($parent != 0 && !isset($cat_map[$parent]))
914
			{
915
				// Perhaps it was supposed to be their id_cat?
916
				foreach ($dummy as $board)
917
				{
918
					if (empty($cat_map[$board]))
919
						$cat_map[$board] = $parent;
920
				}
921
922
				$child_map[0] = array_merge(isset($child_map[0]) ? $child_map[0] : array(), $dummy);
923
				unset($child_map[$parent]);
924
			}
925
		}
926
927
		// The above id_parents and id_cats may all be wrong; we know id_parent = 0 is right.
928
		$solid_parents = array(array(0, 0));
929
		$fixed_boards = array();
930
		while (!empty($solid_parents))
931
		{
932
			list ($parent, $level) = array_pop($solid_parents);
933
			if (!isset($child_map[$parent]))
934
				continue;
935
936
			// Fix all of this board's children.
937
			foreach ($child_map[$parent] as $board)
938
			{
939
				if ($parent != 0)
940
					$cat_map[$board] = $cat_map[$parent];
941
942
				$this->setBoardProperty((int) $board, array('id_parent' => (int) $parent, 'id_cat' => (int) $cat_map[$board], 'child_level' => (int) $level));
943
944
				$fixed_boards[] = $board;
945
				$solid_parents[] = array($board, $level + 1);
946
			}
947
		}
948
949
		// Leftovers should be brought to the root. They had weird parents we couldn't find.
950
		if (count($fixed_boards) < count($cat_map))
951
		{
952
			$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) . ")");
953
		}
954
955
		$this->fixInexistentCategories($cat_map);
956
	}
957
958
	/**
959
	 * Assigns any board belonging to a category that doesn't exist
960
	 * to a newly created category.
961
	 */
962
	protected function fixInexistentCategories($cat_map)
963
	{
964
		$to_prefix = $this->config->to_prefix;
965
966
		// Last check: any boards not in a good category?
967
		$request = $this->db->query("
968
			SELECT
969
				id_cat
970
			FROM {$to_prefix}categories");
971
		$real_cats = array();
972
		while ($row = $this->db->fetch_assoc($request))
973
			$real_cats[] = $row['id_cat'];
974
		$this->db->free_result($request);
975
976
		$fix_cats = array();
977
		foreach ($cat_map as $cat)
978
		{
979
			if (!in_array($cat, $real_cats))
980
				$fix_cats[] = $cat;
981
		}
982
983
		if (!empty($fix_cats))
984
		{
985
			$this->db->query("
986
				INSERT INTO {$to_prefix}categories
987
					(name)
988
				VALUES ('General Category')");
989
			$catch_cat = mysqli_insert_id($this->db->con);
990
991
			$this->setBoardProperty(0, array('id_cat' => (int) $catch_cat), "id_cat IN (" . implode(', ', array_unique($fix_cats)) . ")");
992
		}
993
	}
994
995
	/**
996
	 * Adjust boards and categories orders.
997
	 */
998
	public function substep9()
999
	{
1000
		$to_prefix = $this->config->to_prefix;
1001
1002
		$request = $this->db->query("
1003
			SELECT
1004
				c.id_cat, c.cat_order, b.id_board, b.board_order
1005
			FROM {$to_prefix}categories AS c
1006
				LEFT JOIN {$to_prefix}boards AS b ON (b.id_cat = c.id_cat)
1007
			ORDER BY c.cat_order, b.child_level, b.board_order, b.id_board");
1008
		$cat_order = -1;
1009
		$board_order = -1;
1010
		$curCat = -1;
1011
		while ($row = $this->db->fetch_assoc($request))
1012
		{
1013
			if ($curCat != $row['id_cat'])
1014
			{
1015
				$curCat = $row['id_cat'];
1016
				if (++$cat_order != $row['cat_order'])
1017
					$this->db->query("
1018
						UPDATE {$to_prefix}categories
1019
						SET cat_order = $cat_order
1020
						WHERE id_cat = $row[id_cat]
1021
						LIMIT 1");
1022
			}
1023
			if (!empty($row['id_board']) && ++$board_order != $row['board_order'])
1024
				$this->setBoardProperty($row['id_board'], array('board_order' => $board_order));
1025
		}
1026
		$this->db->free_result($request);
1027
	}
1028
1029
	/**
1030
	 * Fix attachment size, W & H values
1031
	 */
1032
	public function substep11()
1033
	{
1034
		$to_prefix = $this->config->to_prefix;
1035
1036
		$request = $this->db->query("
1037
			SELECT
1038
				COUNT(*)
1039
			FROM {$to_prefix}attachments");
1040
		list ($attachments) = $this->db->fetch_row($request);
1041
		$this->db->free_result($request);
1042
1043
		while ($_REQUEST['start'] < $attachments)
1044
		{
1045
			$request = $this->db->query("
1046
				SELECT
1047
					id_attach, filename, attachment_type, id_folder
1048
				FROM {$to_prefix}attachments
1049
				WHERE id_thumb = 0
1050
					AND (RIGHT(filename, 4) IN ('.gif', '.jpg', '.png', '.bmp') OR RIGHT(filename, 5) = '.jpeg')
1051
					AND width = 0
1052
					AND height = 0
1053
				LIMIT $_REQUEST[start], 500");
1054
1055
			if ($this->db->num_rows($request) == 0)
1056
				break;
1057
1058
			while ($row = $this->db->fetch_assoc($request))
1059
			{
1060
				$filename = $this->avatarFullPath($row);
1061
1062
				// Probably not one of the imported ones, then?
1063
				if (!file_exists($filename))
1064
					continue;
1065
1066
				$size = @getimagesize($filename);
1067
				$filesize = @filesize($filename);
1068
				if (!empty($size) && !empty($size[0]) && !empty($size[1]) && !empty($filesize))
1069
					$this->db->query("
1070
						UPDATE {$to_prefix}attachments
1071
						SET
1072
							size = " . (int) $filesize . ",
1073
							width = " . (int) $size[0] . ",
1074
							height = " . (int) $size[1] . "
1075
						WHERE id_attach = $row[id_attach]
1076
						LIMIT 1");
1077
			}
1078
			$this->db->free_result($request);
1079
1080
			// More?
1081
			// We can't keep importing the same files over and over again!
1082
			$_REQUEST['start'] += 500;
1083
			pastTime(11);
1084
		}
1085
	}
1086
1087
	protected function avatarFullPath($row)
1088
	{
1089
		$dir = $this->config->destination->getAvatarDir($row);
1090
1091
		if ($row['attachment_type'] == 1)
1092
		{
1093
			// @todo Honestly I'm not sure what the final name looks like
1094
			// I'm pretty sure there could be at least three options:
1095
			//   1) filename
1096
			//   2) avatar_{id_member}_{time()}.{file_extension}
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% 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...
1097
			//   3) {id_attach}_{file_hash}.{->attach_extension}
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
1098
			$filename = $row['filename'];
1099
		}
1100
		else
1101
			$filename = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach']);
1102
1103
		return $dir . '/' . $filename;
1104
	}
1105
}
1106
1107
/**
1108
 * Class SmfCommonSourceStep3
1109
 *
1110
 * @package Importers
1111
 */
1112
abstract class SmfCommonSourceStep3 extends Step3BaseImporter
1113
{
1114
	public function run($import_script)
1115
	{
1116
		$to_prefix = $this->config->to_prefix;
1117
1118
		// add some importer information.
1119
		$this->db->query("
1120
			REPLACE INTO {$to_prefix}settings (variable, value)
1121
				VALUES ('import_time', " . time() . "),
1122
					('enable_password_conversion', '1'),
1123
					('imported_from', '" . $import_script . "')");
1124
	}
1125
}