Passed
Push — master ( 655649...07d5b2 )
by Spuds
06:48 queued 05:03
created

SmfCommonSource::specialAttachments()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 41
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 41
rs 9.0111
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
		// Remove special accented characters - ie. sí (because they won't write to the filesystem well.)
169
		$clean_name = strtr($filename, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
170
		$clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));
171
172
		// Get rid of dots, spaces, and other weird characters.
173
		$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
174
175
		if ($legacy)
176
		{
177
			// @todo not sure about that one
178
			$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
179
180
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
181
		}
182
		else
183
		{
184
			return $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name) . '.' . $this->attach_extension;
185
		}
186
	}
187
188
	public function getAvatarDir($row)
189
	{
190
		if ($this->avatarUploadDir === null)
191
		{
192
			return $this->getAttachDir($row);
193
		}
194
195
		return $this->avatarUploadDir;
196
	}
197
198
	public function getAttachDir($row)
199
	{
200
		$this->specialAttachments();
201
202
		if (!empty($row['id_folder']) && !empty($this->attachmentUploadDirs[$row['id_folder']]))
203
		{
204
			return $this->attachmentUploadDirs[$row['id_folder']];
205
		}
206
207
		return $this->attachmentUploadDirs[1];
208
	}
209
210
	/**
211
	 * Determine the source attachment and avatar directories
212
	 *
213
	 * @param bool $force
214
	 */
215
	public function specialAttachments($force = false)
216
	{
217
		$to_prefix = $this->config->to_prefix;
218
219
		// If we don't know the attachment or avatar directories
220
		if ($force === true || !isset($this->id_attach, $this->attachmentUploadDirs, $this->avatarUploadDir))
221
		{
222
			// Attachment starting id
223
			$result = $this->db->query("
224
				SELECT MAX(id_attach) + 1
225
				FROM {$to_prefix}attachments");
226
			list ($this->id_attach) = $this->db->fetch_row($result);
227
			$this->db->free_result($result);
228
229
			// Attachment directories
230
			$result = $this->db->query("
231
				SELECT value
232
				FROM {$to_prefix}settings
233
				WHERE variable = 'attachmentUploadDir'
234
				LIMIT 1");
235
			list ($attachmentdir) = $this->db->fetch_row($result);
236
			$attachment_UploadDir = @unserialize($attachmentdir);
237
			$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...
238
239
			// Avatar directories
240
			$result = $this->db->query("
241
				SELECT value
242
				FROM {$to_prefix}settings
243
				WHERE variable = 'custom_avatar_dir'
244
				LIMIT 1");
245
			list ($this->avatarUploadDir) = $this->db->fetch_row($result);
246
			$this->db->free_result($result);
247
248
			if (empty($this->avatarUploadDir))
249
			{
250
				$this->avatarUploadDir = '';
251
			}
252
253
			if (empty($this->id_attach))
254
			{
255
				$this->id_attach = 1;
256
			}
257
		}
258
	}
259
260
	public function getAllAttachDirs()
261
	{
262
		if ($this->attachmentUploadDirs === null)
263
		{
264
			$this->specialAttachments();
265
		}
266
267
		return $this->attachmentUploadDirs;
268
	}
269
}
270
271
/**
272
 * Class SmfCommonSourceStep1
273
 *
274
 * @class SmfCommonSourceStep1
275
 */
276
abstract class SmfCommonSourceStep1 extends Step1BaseImporter
277
{
278
	public function doSpecialTable($special_table, $params = null)
279
	{
280
		// Are we doing attachments? They're going to want a few things...
281
		if ($special_table == $this->config->to_prefix . 'attachments' && $params === null)
282
		{
283
			$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

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

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