Passed
Push — master ( bf1436...51e602 )
by Spuds
51s queued 11s
created

SmfCommonSourceStep1::fixTexts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
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 doSpecialTable($special_table, $params = null)
275
	{
276
		// Are we doing attachments? They're going to want a few things...
277
		if ($special_table == $this->config->to_prefix . 'attachments' && $params === null)
278
		{
279
			$this->config->destination->specialAttachments();
280
281
			return $params;
282
		}
283
		// Here we have various bits of custom code for some known problems global to all importers.
284
		elseif ($special_table == $this->config->to_prefix . 'members' && $params !== null)
285
			return $this->specialMembers($params);
286
287
		return $params;
288
	}
289
290
	/**
291
	 * Delete any defined db attachments that exist on disk for the source system
292
	 */
293
	public function removeAttachments()
294
	{
295
		$to_prefix = $this->config->to_prefix;
296
297
		// @todo This should probably be done in chunks too.
298
		// attachment_type = 1 are avatars.
299
		$result = $this->db->query("
300
			SELECT id_attach, filename, id_folder
301
			FROM {$to_prefix}attachments");
302
303
		while ($row = $this->db->fetch_assoc($result))
304
		{
305
			$enc_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], false);
306
307
			$attach_dir = $this->getAttachDir($row);
308
309
			if (file_exists($attach_dir . '/' . $enc_name))
310
				$filename = $attach_dir . '/' . $enc_name;
311
			else
312
			{
313
				// @todo this should not be here I think: it's SMF-specific, while this file shouldn't know anything about the source
314
				$clean_name = $this->config->destination->getLegacyAttachmentFilename($row['filename'], $row['id_attach'], true);
315
				$filename = $attach_dir . '/' . $clean_name;
316
			}
317
318
			if (is_file($filename))
319
				@unlink($filename);
320
		}
321
322
		$this->db->free_result($result);
323
324
		// This is valid for some of the sources (e.g. Elk/SMF/Wedge), but not for others
325
		if (method_exists($this->config->source, 'getAttachmentDirs'))
326
		{
327
			$this->createAttachFoldersStructure($this->config->source->getAttachmentDirs());
328
		}
329
	}
330
331
	/**
332
	 * Create the destination directory structure for the attachments
333
	 *
334
	 * @param string[] $folders
335
	 *
336
	 * @return bool
337
	 */
338
	protected function createAttachFoldersStructure($folders)
339
	{
340
		$source_base = $this->guessBase($folders);
341
		$destination_base = $this->guessBase($this->config->destination->getAllAttachDirs());
342
343
		// No idea where to start, better not mess with the filesystem
344
		// Though if $destination_base is empty it *is* a mess.
345
		if (empty($source_base) || empty($destination_base))
346
			return false;
347
348
		$dest_folders = str_replace($source_base, $destination_base, $folders);
349
350
		// Prepare the directory structure
351
		foreach ($dest_folders as $folder)
352
			create_folders_recursive($folder);
353
354
		// Save the new structure in the database
355
		$this->db->query("
356
			UPDATE {$this->config->to_prefix}settings
357
			SET value = '" . serialize($dest_folders) . "'
358
			WHERE variable = 'attachmentUploadDir'
359
			LIMIT 1");
360
361
		// Reload the new directories
362
		$this->config->destination->specialAttachments(true);
363
	}
364
365
	/**
366
	 * Determine the base / root folder for attachments
367
	 *
368
	 * @param string[] $folders
369
	 *
370
	 * @return bool|string
371
	 */
372
	protected function guessBase($folders)
373
	{
374
		foreach ($folders as $folder)
375
		{
376
			if ($this->isCommon($folder, $folders))
377
			{
378
				return $folder;
379
			}
380
		}
381
382
		foreach ($folders as $folder)
383
		{
384
			$dir = $folder;
385
			while (strlen($dir) > 4)
386
			{
387
				$dir = dirname($dir);
388
				if ($this->isCommon($dir, $folders))
389
					return $dir;
390
			}
391
		}
392
393
		return false;
394
	}
395
396
	protected function isCommon($dir, $folders)
397
	{
398
		foreach ($folders as $folder)
399
		{
400
			if (substr($folder, 0, strlen($dir)) !== $dir)
401
				return false;
402
		}
403
404
		return true;
405
	}
406
407
	public function getAttachDir($row)
408
	{
409
		return $this->config->destination->getAttachDir($row);
410
	}
411
412
	public function getAvatarDir($row)
413
	{
414
		return $this->config->destination->getAvatarDir($row);
415
	}
416
417
	public function getAvatarFolderId($row)
0 ignored issues
show
Unused Code introduced by
The parameter $row is not used and could be removed.

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

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