Completed
Push — release-2.1 ( 4c82a0...64d581 )
by Rick
09:29
created

ManageAttachments.php ➔ ManageAttachmentPaths()   F

Complexity

Conditions 94
Paths > 20000

Size

Total Lines 527
Code Lines 307

Duplication

Lines 22
Ratio 4.17 %

Importance

Changes 0
Metric Value
cc 94
eloc 307
nc 429496.7295
nop 0
dl 22
loc 527
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file doing the job of attachments and avatars maintenance and management.
5
 * @todo refactor as controller-model
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2016 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 3
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * The main 'Attachments and Avatars' management function.
22
 * This function is the entry point for index.php?action=admin;area=manageattachments
23
 * and it calls a function based on the sub-action.
24
 * It requires the manage_attachments permission.
25
 *
26
 * @uses ManageAttachments template.
27
 * @uses Admin language file.
28
 * @uses template layer 'manage_files' for showing the tab bar.
29
 *
30
 */
31
function ManageAttachments()
32
{
33
	global $txt, $context;
34
35
	// You have to be able to moderate the forum to do this.
36
	isAllowedTo('manage_attachments');
37
38
	// Setup the template stuff we'll probably need.
39
	loadTemplate('ManageAttachments');
40
41
	// If they want to delete attachment(s), delete them. (otherwise fall through..)
42
	$subActions = array(
43
		'attachments' => 'ManageAttachmentSettings',
44
		'attachpaths' => 'ManageAttachmentPaths',
45
		'avatars' => 'ManageAvatarSettings',
46
		'browse' => 'BrowseFiles',
47
		'byAge' => 'RemoveAttachmentByAge',
48
		'bySize' => 'RemoveAttachmentBySize',
49
		'maintenance' => 'MaintainFiles',
50
		'repair' => 'RepairAttachments',
51
		'remove' => 'RemoveAttachment',
52
		'removeall' => 'RemoveAllAttachments',
53
		'transfer' => 'TransferAttachments',
54
	);
55
56
	// This uses admin tabs - as it should!
57
	$context[$context['admin_menu_name']]['tab_data'] = array(
58
		'title' => $txt['attachments_avatars'],
59
		'help' => 'manage_files',
60
		'description' => $txt['attachments_desc'],
61
	);
62
63
	call_integration_hook('integrate_manage_attachments', array(&$subActions));
64
65
	// Pick the correct sub-action.
66
	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
67
		$context['sub_action'] = $_REQUEST['sa'];
68
	else
69
		$context['sub_action'] = 'browse';
70
71
	// Default page title is good.
72
	$context['page_title'] = $txt['attachments_avatars'];
73
74
	// Finally fall through to what we are doing.
75
	call_helper($subActions[$context['sub_action']]);
76
}
77
78
/**
79
 * Allows to show/change attachment settings.
80
 * This is the default sub-action of the 'Attachments and Avatars' center.
81
 * Called by index.php?action=admin;area=manageattachments;sa=attachments.
82
 *
83
 * @param bool $return_config Whether to return the array of config variables (used for admin search)
84
 * @return void|array If $return_config is true, simply returns the config_vars array, otherwise returns nothing
85
 * @uses 'attachments' sub template.
86
 */
87
88
function ManageAttachmentSettings($return_config = false)
89
{
90
	global $txt, $modSettings, $scripturl, $context, $sourcedir, $boarddir;
91
92
	require_once($sourcedir . '/Subs-Attachments.php');
93
94
	$context['attachmentUploadDir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
95
96
	// If not set, show a default path for the base directory
97
	if (!isset($_GET['save']) && empty($modSettings['basedirectory_for_attachments']))
98 View Code Duplication
		if (is_dir($modSettings['attachmentUploadDir'][1]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
			$modSettings['basedirectory_for_attachments'] = $modSettings['attachmentUploadDir'][1];
100
101
	else
102
		$modSettings['basedirectory_for_attachments'] = $context['attachmentUploadDir'];
103
104
	$context['valid_upload_dir'] = is_dir($context['attachmentUploadDir']) && is_writable($context['attachmentUploadDir']);
105
106
	if (!empty($modSettings['automanage_attachments']))
107
		$context['valid_basedirectory'] =  !empty($modSettings['basedirectory_for_attachments']) && is_writable($modSettings['basedirectory_for_attachments']);
108
109
	else
110
		$context['valid_basedirectory'] = true;
111
112
	// A bit of razzle dazzle with the $txt strings. :)
113
	$txt['attachment_path'] = $context['attachmentUploadDir'];
114
	$txt['basedirectory_for_attachments_path']= isset($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
115
	$txt['use_subdirectories_for_attachments_note'] = empty($modSettings['attachment_basedirectories']) || empty($modSettings['use_subdirectories_for_attachments']) ? $txt['use_subdirectories_for_attachments_note'] : '';
116
	$txt['attachmentUploadDir_multiple_configure'] = '<a href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">[' . $txt['attachmentUploadDir_multiple_configure'] . ']</a>';
117
	$txt['attach_current_dir'] = empty($modSettings['automanage_attachments']) ? $txt['attach_current_dir'] : $txt['attach_last_dir'];
118
	$txt['attach_current_dir_warning'] = $txt['attach_current_dir'] . $txt['attach_current_dir_warning'];
119
	$txt['basedirectory_for_attachments_warning'] = $txt['basedirectory_for_attachments_current'] . $txt['basedirectory_for_attachments_warning'];
120
121
	// Perform a test to see if the GD module or ImageMagick are installed.
122
	$testImg = get_extension_funcs('gd') || class_exists('Imagick') || get_extension_funcs('MagickWand');
123
124
	// See if we can find if the server is set up to support the attachment limits
125
	$post_max_size = ini_get('post_max_size');
126
	$upload_max_filesize = ini_get('upload_max_filesize');
127
	$testPM = !empty($post_max_size) ? (memoryReturnBytes($post_max_size) >= (isset($modSettings['attachmentPostLimit']) ? $modSettings['attachmentPostLimit'] * 1024 : 0)) : true;
128
	$testUM = !empty($upload_max_filesize) ? (memoryReturnBytes($upload_max_filesize) >= (isset($modSettings['attachmentSizeLimit']) ? $modSettings['attachmentSizeLimit'] * 1024 : 0)) : true;
129
130
	$config_vars = array(
131
		array('title', 'attachment_manager_settings'),
132
			// Are attachments enabled?
133
			array('select', 'attachmentEnable', array($txt['attachmentEnable_deactivate'], $txt['attachmentEnable_enable_all'], $txt['attachmentEnable_disable_new'])),
134
		'',
135
			// Directory and size limits.
136
			array('select', 'automanage_attachments', array(0 => $txt['attachments_normal'], 1 => $txt['attachments_auto_space'], 2 => $txt['attachments_auto_years'], 3 => $txt['attachments_auto_months'], 4 => $txt['attachments_auto_16'])),
137
			array('check', 'use_subdirectories_for_attachments', 'subtext' => $txt['use_subdirectories_for_attachments_note']),
138
			(empty($modSettings['attachment_basedirectories']) ? array('text', 'basedirectory_for_attachments', 40,) : array('var_message', 'basedirectory_for_attachments', 'message' => 'basedirectory_for_attachments_path', 'invalid' => empty($context['valid_basedirectory']), 'text_label' => (!empty($context['valid_basedirectory']) ? $txt['basedirectory_for_attachments_current'] : $txt['basedirectory_for_attachments_warning']))),
139
			empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && count($modSettings['attachmentUploadDir']) == 1	? array('json', 'attachmentUploadDir', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir'], 'disabled' => true) : array('var_message', 'attach_current_directory', 'subtext' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), 'text_label' => (!empty($context['valid_upload_dir']) ? $txt['attach_current_dir'] : $txt['attach_current_dir_warning'])),
140
			array('int', 'attachmentDirFileLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
141
			array('int', 'attachmentDirSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
142
			array('check', 'dont_show_attach_under_post', 'subtext' => $txt['dont_show_attach_under_post_sub']),
143
		'',
144
			// Posting limits
145
			array('int', 'attachmentPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
146
			array('warning', empty($testPM) ? 'attachment_postsize_warning' : ''),
147
			array('int', 'attachmentSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
148
			array('warning', empty($testUM) ? 'attachment_filesize_warning' : ''),
149
			array('int', 'attachmentNumPerPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
150
			// Security Items
151
		array('title', 'attachment_security_settings'),
152
			// Extension checks etc.
153
			array('check', 'attachmentCheckExtensions'),
154
			array('text', 'attachmentExtensions', 40),
155
		'',
156
			// Image checks.
157
			array('warning', empty($testImg) ? 'attachment_img_enc_warning' : ''),
158
			array('check', 'attachment_image_reencode'),
159
		'',
160
			array('warning', 'attachment_image_paranoid_warning'),
161
			array('check', 'attachment_image_paranoid'),
162
			// Thumbnail settings.
163
		array('title', 'attachment_thumbnail_settings'),
164
			array('check', 'attachmentShowImages'),
165
			array('check', 'attachmentThumbnails'),
166
			array('check', 'attachment_thumb_png'),
167
			array('check', 'attachment_thumb_memory'),
168
			array('warning', 'attachment_thumb_memory_note'),
169
			array('text', 'attachmentThumbWidth', 6),
170
			array('text', 'attachmentThumbHeight', 6),
171
		'',
172
			array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']),
173
			array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']),
174
	);
175
176
	$context['settings_post_javascript'] = '
177
	var storing_type = document.getElementById(\'automanage_attachments\');
178
	var base_dir = document.getElementById(\'use_subdirectories_for_attachments\');
179
180
	createEventListener(storing_type)
181
	storing_type.addEventListener("change", toggleSubDir, false);
182
	createEventListener(base_dir)
183
	base_dir.addEventListener("change", toggleSubDir, false);
184
	toggleSubDir();';
185
186
	call_integration_hook('integrate_modify_attachment_settings', array(&$config_vars));
187
188
	if ($return_config)
189
		return $config_vars;
190
191
	// These are very likely to come in handy! (i.e. without them we're doomed!)
192
	require_once($sourcedir . '/ManagePermissions.php');
193
	require_once($sourcedir . '/ManageServer.php');
194
195
	// Saving settings?
196
	if (isset($_GET['save']))
197
	{
198
		checkSession();
199
200
		if (isset($_POST['attachmentUploadDir']))
201
			unset($_POST['attachmentUploadDir']);
202
203
		if (!empty($_POST['use_subdirectories_for_attachments']))
204
		{
205
			if (isset($_POST['use_subdirectories_for_attachments']) && empty($_POST['basedirectory_for_attachments']))
206
				$_POST['basedirectory_for_attachments'] = (!empty($modSettings['basedirectory_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
207
208 View Code Duplication
			if (!empty($_POST['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209
			{
210
				if (!is_array($modSettings['attachment_basedirectories']))
211
					$modSettings['attachment_basedirectories'] = smf_json_decode($modSettings['attachment_basedirectories'], true);
212
			}
213
			else
214
				$modSettings['attachment_basedirectories'] = array();
215
216
			if (!empty($_POST['use_subdirectories_for_attachments']) && !empty($_POST['basedirectory_for_attachments']) && !in_array($_POST['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']))
217
			{
218
				$currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir'];
219
220
				if (!in_array($_POST['basedirectory_for_attachments'], $modSettings['attachmentUploadDir']))
221
				{
222
					if (!automanage_attachments_create_directory($_POST['basedirectory_for_attachments']))
223
						$_POST['basedirectory_for_attachments'] = $modSettings['basedirectory_for_attachments'];
224
				}
225
226
				if (!in_array($_POST['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']))
227
				{
228
					$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $_POST['basedirectory_for_attachments'];
229
					updateSettings(array(
230
						'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
231
						'currentAttachmentUploadDir' => $currentAttachmentUploadDir,
232
					));
233
234
					$_POST['use_subdirectories_for_attachments'] = 1;
235
					$_POST['attachmentUploadDir'] = json_encode($modSettings['attachmentUploadDir']);
236
237
				}
238
			}
239
		}
240
241
		call_integration_hook('integrate_save_attachment_settings');
242
243
		saveDBSettings($config_vars);
244
		$_SESSION['adm-save'] = true;
245
		redirectexit('action=admin;area=manageattachments;sa=attachments');
246
	}
247
248
	$context['post_url'] = $scripturl . '?action=admin;area=manageattachments;save;sa=attachments';
249
	prepareDBSettingContext($config_vars);
250
251
	$context['sub_template'] = 'show_settings';
252
}
253
254
/**
255
 * This allows to show/change avatar settings.
256
 * Called by index.php?action=admin;area=manageattachments;sa=avatars.
257
 * Show/set permissions for permissions: 'profile_server_avatar',
258
 * 	'profile_upload_avatar' and 'profile_remote_avatar'.
259
 *
260
 * @param bool $return_config Whether to return the config_vars array (used for admin search)
261
 * @return void|array Returns the config_vars array if $return_config is true, otherwise returns nothing
262
 * @uses 'avatars' sub template.
263
 */
264
function ManageAvatarSettings($return_config = false)
265
{
266
	global $txt, $context, $modSettings, $sourcedir, $scripturl;
267
	global $boarddir, $boardurl;
268
269
	// Perform a test to see if the GD module or ImageMagick are installed.
270
	$testImg = get_extension_funcs('gd') || class_exists('Imagick');
271
272
	$context['valid_avatar_dir'] = is_dir($modSettings['avatar_directory']);
273
	$context['valid_custom_avatar_dir'] = !empty($modSettings['custom_avatar_dir']) && is_dir($modSettings['custom_avatar_dir']) && is_writable($modSettings['custom_avatar_dir']);
274
275
	$config_vars = array(
276
		// Server stored avatars!
277
		array('title', 'avatar_server_stored'),
278
			array('warning', empty($testImg) ? 'avatar_img_enc_warning' : ''),
279
			array('permissions', 'profile_server_avatar', 0, $txt['avatar_server_stored_groups']),
280
			array('warning', !$context['valid_avatar_dir'] ? 'avatar_directory_wrong' : ''),
281
			array('text', 'avatar_directory', 40, 'invalid' => !$context['valid_avatar_dir']),
282
			array('text', 'avatar_url', 40),
283
		// External avatars?
284
		array('title', 'avatar_external'),
285
			array('permissions', 'profile_remote_avatar', 0, $txt['avatar_external_url_groups']),
286
			array('check', 'avatar_download_external', 0, 'onchange' => 'fUpdateStatus();'),
287
			array('text', 'avatar_max_width_external', 'subtext' => $txt['zero_for_no_limit'], 6),
288
			array('text', 'avatar_max_height_external', 'subtext' => $txt['zero_for_no_limit'], 6),
289
			array('select', 'avatar_action_too_large',
290
				array(
291
					'option_refuse' => $txt['option_refuse'],
292
					'option_css_resize' => $txt['option_css_resize'],
293
					'option_download_and_resize' => $txt['option_download_and_resize'],
294
				),
295
			),
296
		// Uploadable avatars?
297
		array('title', 'avatar_upload'),
298
			array('permissions', 'profile_upload_avatar', 0, $txt['avatar_upload_groups']),
299
			array('text', 'avatar_max_width_upload', 'subtext' => $txt['zero_for_no_limit'], 6),
300
			array('text', 'avatar_max_height_upload', 'subtext' => $txt['zero_for_no_limit'], 6),
301
			array('check', 'avatar_resize_upload', 'subtext' => $txt['avatar_resize_upload_note']),
302
			array('check', 'avatar_download_png'),
303
			array('check', 'avatar_reencode'),
304
		'',
305
			array('warning', 'avatar_paranoid_warning'),
306
			array('check', 'avatar_paranoid'),
307
		'',
308
			array('warning', !$context['valid_custom_avatar_dir'] ? 'custom_avatar_dir_wrong' : ''),
309
			array('text', 'custom_avatar_dir', 40, 'subtext' => $txt['custom_avatar_dir_desc'], 'invalid' => !$context['valid_custom_avatar_dir']),
310
			array('text', 'custom_avatar_url', 40),
311
		// Grvatars?
312
		array('title', 'gravatar_settings'),
313
			array('check', 'gravatarEnabled'),
314
			array('check', 'gravatarOverride'),
315
			array('check', 'gravatarAllowExtraEmail'),
316
		'',
317
			array('select', 'gravatarMaxRating',
318
				array(
319
					'G' => $txt['gravatar_maxG'],
320
					'PG' => $txt['gravatar_maxPG'],
321
					'R' => $txt['gravatar_maxR'],
322
					'X' => $txt['gravatar_maxX'],
323
				),
324
			),
325
			array('select', 'gravatarDefault',
326
				array(
327
					'mm' => $txt['gravatar_mm'],
328
					'identicon' => $txt['gravatar_identicon'],
329
					'monsterid' => $txt['gravatar_monsterid'],
330
					'wavatar' => $txt['gravatar_wavatar'],
331
					'retro' => $txt['gravatar_retro'],
332
					'blank' => $txt['gravatar_blank'],
333
				),
334
			),
335
	);
336
337
	call_integration_hook('integrate_modify_avatar_settings', array(&$config_vars));
338
339
	if ($return_config)
340
		return $config_vars;
341
342
	// We need this file for the settings template.
343
	require_once($sourcedir . '/ManageServer.php');
344
345
	// Saving avatar settings?
346
	if (isset($_GET['save']))
347
	{
348
		checkSession();
349
350
		// These settings cannot be left empty!
351
		if (empty($_POST['custom_avatar_dir']))
352
			$_POST['custom_avatar_dir'] = $boarddir .'/custom_avatar';
353
354
		if (empty($_POST['custom_avatar_url']))
355
			$_POST['custom_avatar_url'] = $boardurl .'/custom_avatar';
356
357
		if (empty($_POST['avatar_directory']))
358
			$_POST['avatar_directory'] = $boarddir .'/avatars';
359
360
		if (empty($_POST['avatar_url']))
361
			$_POST['avatar_url'] = $boardurl .'/avatars';
362
363
		call_integration_hook('integrate_save_avatar_settings');
364
365
		saveDBSettings($config_vars);
366
		$_SESSION['adm-save'] = true;
367
		redirectexit('action=admin;area=manageattachments;sa=avatars');
368
	}
369
370
	// Attempt to figure out if the admin is trying to break things.
371
	$context['settings_save_onclick'] = 'return (document.getElementById(\'custom_avatar_dir\').value == \'\' || document.getElementById(\'custom_avatar_url\').value == \'\') ? confirm(\'' . $txt['custom_avatar_check_empty'] . '\') : true;';
372
373
	// We need this for the in-line permissions
374
	createToken('admin-mp');
375
376
	// Prepare the context.
377
	$context['post_url'] = $scripturl . '?action=admin;area=manageattachments;save;sa=avatars';
378
	prepareDBSettingContext($config_vars);
379
380
	// Add a layer for the javascript.
381
	$context['template_layers'][] = 'avatar_settings';
382
	$context['sub_template'] = 'show_settings';
383
}
384
385
/**
386
 * Show a list of attachment or avatar files.
387
 * Called by ?action=admin;area=manageattachments;sa=browse for attachments
388
 *  and ?action=admin;area=manageattachments;sa=browse;avatars for avatars.
389
 * Allows sorting by name, date, size and member.
390
 * Paginates results.
391
 */
392
function BrowseFiles()
393
{
394
	global $context, $txt, $scripturl, $modSettings;
395
	global $smcFunc, $sourcedir, $settings;
396
397
	// Attachments or avatars?
398
	$context['browse_type'] = isset($_REQUEST['avatars']) ? 'avatars' : (isset($_REQUEST['thumbs']) ? 'thumbs' : 'attachments');
399
400
	$titles = array(
401
		'attachments' => array('?action=admin;area=manageattachments;sa=browse', $txt['attachment_manager_attachments']),
402
		'avatars' => array('?action=admin;area=manageattachments;sa=browse;avatars', $txt['attachment_manager_avatars']),
403
		'thumbs' => array('?action=admin;area=manageattachments;sa=browse;thumbs', $txt['attachment_manager_thumbs']),
404
	);
405
406
	$list_title = $txt['attachment_manager_browse_files'] . ': ';
407
	foreach ($titles as $browse_type => $details)
408
	{
409
		if ($browse_type != 'attachments')
410
			$list_title .= ' | ';
411
412
		if ($context['browse_type'] == $browse_type)
413
			$list_title .= '<img src="' . $settings['images_url'] . '/selected.png" alt="&gt;"> ';
414
415
		$list_title .= '<a href="' . $scripturl . $details[0] . '">' . $details[1] . '</a>';
416
	}
417
418
	// Set the options for the list component.
419
	$listOptions = array(
420
		'id' => 'file_list',
421
		'title' => $list_title,
422
		'items_per_page' => $modSettings['defaultMaxListItems'],
423
		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
424
		'default_sort_col' => 'name',
425
		'no_items_label' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ( $context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments')) . '_no_entries'],
426
		'get_items' => array(
427
			'function' => 'list_getFiles',
428
			'params' => array(
429
				$context['browse_type'],
430
			),
431
		),
432
		'get_count' => array(
433
			'function' => 'list_getNumFiles',
434
			'params' => array(
435
				$context['browse_type'],
436
			),
437
		),
438
		'columns' => array(
439
			'name' => array(
440
				'header' => array(
441
					'value' => $txt['attachment_name'],
442
				),
443
				'data' => array(
444
					'function' => function ($rowData) use ($modSettings, $context, $scripturl, $smcFunc)
445
					{
446
						$link = '<a href="';
447
448
						// In case of a custom avatar URL attachments have a fixed directory.
449
						if ($rowData['attachment_type'] == 1)
450
							$link .= sprintf('%1$s/%2$s', $modSettings['custom_avatar_url'], $rowData['filename']);
451
452
						// By default avatars are downloaded almost as attachments.
453
						elseif ($context['browse_type'] == 'avatars')
454
							$link .= sprintf('%1$s?action=dlattach;type=avatar;attach=%2$d', $scripturl, $rowData['id_attach']);
455
456
						// Normal attachments are always linked to a topic ID.
457
						else
458
							$link .= sprintf('%1$s?action=dlattach;topic=%2$d.0;attach=%3$d', $scripturl, $rowData['id_topic'], $rowData['id_attach']);
459
460
						$link .= '"';
461
462
						// Show a popup on click if it's a picture and we know its dimensions.
463
						if (!empty($rowData['width']) && !empty($rowData['height']))
464
							$link .= sprintf(' onclick="return reqWin(this.href' . ($rowData['attachment_type'] == 1 ? '' : ' + \';image\'') . ', %1$d, %2$d, true);"', $rowData['width'] + 20, $rowData['height'] + 20);
465
466
						$link .= sprintf('>%1$s</a>', preg_replace('~&amp;#(\\\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\\\1;', $smcFunc['htmlspecialchars']($rowData['filename'])));
467
468
						// Show the dimensions.
469
						if (!empty($rowData['width']) && !empty($rowData['height']))
470
							$link .= sprintf(' <span class="smalltext">%1$dx%2$d</span>', $rowData['width'], $rowData['height']);
471
472
						return $link;
473
					},
474
				),
475
				'sort' => array(
476
					'default' => 'a.filename',
477
					'reverse' => 'a.filename DESC',
478
				),
479
			),
480
			'filesize' => array(
481
				'header' => array(
482
					'value' => $txt['attachment_file_size'],
483
				),
484
				'data' => array(
485
					'function' => function ($rowData) use ($txt)
486
					{
487
						return sprintf('%1$s%2$s', round($rowData['size'] / 1024, 2), $txt['kilobyte']);
488
					},
489
				),
490
				'sort' => array(
491
					'default' => 'a.size',
492
					'reverse' => 'a.size DESC',
493
				),
494
			),
495
			'member' => array(
496
				'header' => array(
497
					'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_member'] : $txt['posted_by'],
498
				),
499
				'data' => array(
500
					'function' => function ($rowData) use ($scripturl, $smcFunc)
501
					{
502
						// In case of an attachment, return the poster of the attachment.
503
						if (empty($rowData['id_member']))
504
							return $smcFunc['htmlspecialchars']($rowData['poster_name']);
505
506
						// Otherwise it must be an avatar, return the link to the owner of it.
507
						else
508
							return sprintf('<a href="%1$s?action=profile;u=%2$d">%3$s</a>', $scripturl, $rowData['id_member'], $rowData['poster_name']);
509
					},
510
				),
511
				'sort' => array(
512
					'default' => 'mem.real_name',
513
					'reverse' => 'mem.real_name DESC',
514
				),
515
			),
516
			'date' => array(
517
				'header' => array(
518
					'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_last_active'] : $txt['date'],
519
				),
520
				'data' => array(
521
					'function' => function ($rowData) use ($txt, $context, $scripturl)
522
					{
523
						// The date the message containing the attachment was posted or the owner of the avatar was active.
524
						$date = empty($rowData['poster_time']) ? $txt['never'] : timeformat($rowData['poster_time']);
525
526
						// Add a link to the topic in case of an attachment.
527
						if ($context['browse_type'] !== 'avatars')
528
							$date .= sprintf('<br>%1$s <a href="%2$s?topic=%3$d.msg%4$d#msg%4$d">%5$s</a>', $txt['in'], $scripturl, $rowData['id_topic'], $rowData['id_msg'], $rowData['subject']);
529
530
						return $date;
531
					},
532
				),
533
				'sort' => array(
534
					'default' => $context['browse_type'] === 'avatars' ? 'mem.last_login' : 'm.id_msg',
535
					'reverse' => $context['browse_type'] === 'avatars' ? 'mem.last_login DESC' : 'm.id_msg DESC',
536
				),
537
			),
538
			'downloads' => array(
539
				'header' => array(
540
					'value' => $txt['downloads'],
541
				),
542
				'data' => array(
543
					'db' => 'downloads',
544
					'comma_format' => true,
545
				),
546
				'sort' => array(
547
					'default' => 'a.downloads',
548
					'reverse' => 'a.downloads DESC',
549
				),
550
			),
551
			'check' => array(
552
				'header' => array(
553
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check">',
554
					'class' => 'centercol',
555
				),
556
				'data' => array(
557
					'sprintf' => array(
558
						'format' => '<input type="checkbox" name="remove[%1$d]" class="input_check">',
559
						'params' => array(
560
							'id_attach' => false,
561
						),
562
					),
563
					'class' => 'centercol',
564
				),
565
			),
566
		),
567
		'form' => array(
568
			'href' => $scripturl . '?action=admin;area=manageattachments;sa=remove' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
569
			'include_sort' => true,
570
			'include_start' => true,
571
			'hidden_fields' => array(
572
				'type' => $context['browse_type'],
573
			),
574
		),
575
		'additional_rows' => array(
576
			array(
577
				'position' => 'above_table_headers',
578
				'value' => '<input type="submit" name="remove_submit" class="button_submit you_sure" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['confirm_delete_attachments'] . '">',
579
			),
580
			array(
581
				'position' => 'below_table_data',
582
				'value' => '<input type="submit" name="remove_submit" class="button_submit you_sure" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['confirm_delete_attachments'] . '">',
583
			),
584
		),
585
	);
586
587
	// Does a hook want to display their attachments better?
588
	call_integration_hook('integrate_attachments_browse', array(&$listOptions, &$titles, &$list_title));
589
590
	// Create the list.
591
	require_once($sourcedir . '/Subs-List.php');
592
	createList($listOptions);
593
594
	$context['sub_template'] = 'show_list';
595
	$context['default_list'] = 'file_list';
596
}
597
598
/**
599
 * Returns the list of attachments files (avatars or not), recorded
600
 * in the database, per the parameters received.
601
 *
602
 * @param int $start The item to start with
603
 * @param int $items_per_page How many items to show per page
604
 * @param string $sort A string indicating how to sort results
605
 * @param string $browse_type can be one of 'avatars' or ... not. :P
606
 * @return array An array of file info
607
 */
608
function list_getFiles($start, $items_per_page, $sort, $browse_type)
609
{
610
	global $smcFunc, $txt;
611
612
	// Choose a query depending on what we are viewing.
613
	if ($browse_type === 'avatars')
614
		$request = $smcFunc['db_query']('', '
615
			SELECT
616
				{string:blank_text} AS id_msg, COALESCE(mem.real_name, {string:not_applicable_text}) AS poster_name,
617
				mem.last_login AS poster_time, 0 AS id_topic, a.id_member, a.id_attach, a.filename, a.file_hash, a.attachment_type,
618
				a.size, a.width, a.height, a.downloads, {string:blank_text} AS subject, 0 AS id_board
619
			FROM {db_prefix}attachments AS a
620
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)
621
			WHERE a.id_member != {int:guest_id}
622
			ORDER BY {raw:sort}
623
			LIMIT {int:start}, {int:per_page}',
624
			array(
625
				'guest_id' => 0,
626
				'blank_text' => '',
627
				'not_applicable_text' => $txt['not_applicable'],
628
				'sort' => $sort,
629
				'start' => $start,
630
				'per_page' => $items_per_page,
631
			)
632
		);
633
	else
634
		$request = $smcFunc['db_query']('', '
635
			SELECT
636
				m.id_msg, COALESCE(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.id_topic, m.id_member,
637
				a.id_attach, a.filename, a.file_hash, a.attachment_type, a.size, a.width, a.height, a.downloads, mf.subject, t.id_board
638
			FROM {db_prefix}attachments AS a
639
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
640
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
641
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
642
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
643
			WHERE a.attachment_type = {int:attachment_type}
644
			ORDER BY {raw:sort}
645
			LIMIT {int:start}, {int:per_page}',
646
			array(
647
				'attachment_type' => $browse_type == 'thumbs' ? '3' : '0',
648
				'sort' => $sort,
649
				'start' => $start,
650
				'per_page' => $items_per_page,
651
			)
652
		);
653
	$files = array();
654
	while ($row = $smcFunc['db_fetch_assoc']($request))
655
		$files[] = $row;
656
	$smcFunc['db_free_result']($request);
657
658
	return $files;
659
}
660
661
/**
662
 * Return the number of files of the specified type recorded in the database.
663
 * (the specified type being attachments or avatars).
664
 *
665
 * @param string $browse_type can be one of 'avatars' or not. (in which case they're attachments)
666
 * @return int The number of files
667
 */
668
function list_getNumFiles($browse_type)
669
{
670
	global $smcFunc;
671
672
	// Depending on the type of file, different queries are used.
673
	if ($browse_type === 'avatars')
674
		$request = $smcFunc['db_query']('', '
675
		SELECT COUNT(*)
676
		FROM {db_prefix}attachments
677
		WHERE id_member != {int:guest_id_member}',
678
		array(
679
			'guest_id_member' => 0,
680
		)
681
	);
682
	else
683
		$request = $smcFunc['db_query']('', '
684
			SELECT COUNT(*) AS num_attach
685
			FROM {db_prefix}attachments AS a
686
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
687
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
688
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
689
			WHERE a.attachment_type = {int:attachment_type}
690
				AND a.id_member = {int:guest_id_member}',
691
			array(
692
				'attachment_type' => $browse_type === 'thumbs' ? '3' : '0',
693
				'guest_id_member' => 0,
694
			)
695
		);
696
697
	list ($num_files) = $smcFunc['db_fetch_row']($request);
698
	$smcFunc['db_free_result']($request);
699
700
	return $num_files;
701
}
702
703
/**
704
 * Show several file maintenance options.
705
 * Called by ?action=admin;area=manageattachments;sa=maintain.
706
 * Calculates file statistics (total file size, number of attachments,
707
 * number of avatars, attachment space available).
708
 *
709
 * @uses the 'maintain' sub template.
710
 */
711
function MaintainFiles()
712
{
713
	global $context, $modSettings, $smcFunc;
714
715
	$context['sub_template'] = 'maintenance';
716
717
	$attach_dirs = smf_json_decode($modSettings['attachmentUploadDir'], true);
718
719
	// Get the number of attachments....
720
	$request = $smcFunc['db_query']('', '
721
		SELECT COUNT(*)
722
		FROM {db_prefix}attachments
723
		WHERE attachment_type = {int:attachment_type}
724
			AND id_member = {int:guest_id_member}',
725
		array(
726
			'attachment_type' => 0,
727
			'guest_id_member' => 0,
728
		)
729
	);
730
	list ($context['num_attachments']) = $smcFunc['db_fetch_row']($request);
731
	$smcFunc['db_free_result']($request);
732
	$context['num_attachments'] = comma_format($context['num_attachments'], 0);
733
734
	// Also get the avatar amount....
735
	$request = $smcFunc['db_query']('', '
736
		SELECT COUNT(*)
737
		FROM {db_prefix}attachments
738
		WHERE id_member != {int:guest_id_member}',
739
		array(
740
			'guest_id_member' => 0,
741
		)
742
	);
743
	list ($context['num_avatars']) = $smcFunc['db_fetch_row']($request);
744
	$smcFunc['db_free_result']($request);
745
	$context['num_avatars'] = comma_format($context['num_avatars'], 0);
746
747
	// Check the size of all the directories.
748
	$request = $smcFunc['db_query']('', '
749
		SELECT SUM(size)
750
		FROM {db_prefix}attachments
751
		WHERE attachment_type != {int:type}',
752
		array(
753
			'type' => 1,
754
		)
755
	);
756
	list ($attachmentDirSize) = $smcFunc['db_fetch_row']($request);
757
	$smcFunc['db_free_result']($request);
758
759
	// Divide it into kilobytes.
760
	$attachmentDirSize /= 1024;
761
	$context['attachment_total_size'] = comma_format($attachmentDirSize, 2);
762
763
	$request = $smcFunc['db_query']('', '
764
		SELECT COUNT(*), SUM(size)
765
		FROM {db_prefix}attachments
766
		WHERE id_folder = {int:folder_id}
767
			AND attachment_type != {int:type}',
768
		array(
769
			'folder_id' => $modSettings['currentAttachmentUploadDir'],
770
			'type' => 1,
771
		)
772
	);
773
	list ($current_dir_files, $current_dir_size) = $smcFunc['db_fetch_row']($request);
774
	$smcFunc['db_free_result']($request);
775
	$current_dir_size /= 1024;
776
777
	// If they specified a limit only....
778
	if (!empty($modSettings['attachmentDirSizeLimit']))
779
		$context['attachment_space'] = comma_format(max($modSettings['attachmentDirSizeLimit'] - $current_dir_size, 0), 2);
780
	$context['attachment_current_size'] = comma_format($current_dir_size, 2);
781
782
	if (!empty($modSettings['attachmentDirFileLimit']))
783
		$context['attachment_files'] = comma_format(max($modSettings['attachmentDirFileLimit'] - $current_dir_files, 0), 0);
784
	$context['attachment_current_files'] = comma_format($current_dir_files, 0);
785
786
	$context['attach_multiple_dirs'] = count($attach_dirs) > 1 ? true : false;
787
	$context['attach_dirs'] = $attach_dirs;
788
	$context['base_dirs'] = !empty($modSettings['attachment_basedirectories']) ? smf_json_decode($modSettings['attachment_basedirectories'], true) : array();
789
	$context['checked'] = isset($_SESSION['checked']) ? $_SESSION['checked'] : true;
790
	if (!empty($_SESSION['results']))
791
	{
792
		$context['results'] = implode('<br>', $_SESSION['results']);
793
		unset($_SESSION['results']);
794
	}
795
}
796
797
/**
798
 * Remove attachments older than a given age.
799
 * Called from the maintenance screen by
800
 *   ?action=admin;area=manageattachments;sa=byAge.
801
 * It optionally adds a certain text to the messages the attachments
802
 *  were removed from.
803
 *  @todo refactor this silly superglobals use...
804
 */
805
function RemoveAttachmentByAge()
806
{
807
	global $smcFunc;
808
809
	checkSession('post', 'admin');
810
811
	// @todo Ignore messages in topics that are stickied?
812
813
	// Deleting an attachment?
814
	if ($_REQUEST['type'] != 'avatars')
815
	{
816
		// Get rid of all the old attachments.
817
		$messages = removeAttachments(array('attachment_type' => 0, 'poster_time' => (time() - 24 * 60 * 60 * $_POST['age'])), 'messages', true);
818
819
		// Update the messages to reflect the change.
820 View Code Duplication
		if (!empty($messages) && !empty($_POST['notice']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
821
			$smcFunc['db_query']('', '
822
				UPDATE {db_prefix}messages
823
				SET body = CONCAT(body, {string:notice})
824
				WHERE id_msg IN ({array_int:messages})',
825
				array(
826
					'messages' => $messages,
827
					'notice' => '<br><br>' . $_POST['notice'],
828
				)
829
			);
830
	}
831
	else
832
	{
833
		// Remove all the old avatars.
834
		removeAttachments(array('not_id_member' => 0, 'last_login' => (time() - 24 * 60 * 60 * $_POST['age'])), 'members');
835
	}
836
	redirectexit('action=admin;area=manageattachments' . (empty($_REQUEST['avatars']) ? ';sa=maintenance' : ';avatars'));
837
}
838
839
/**
840
 * Remove attachments larger than a given size.
841
 * Called from the maintenance screen by
842
 *  ?action=admin;area=manageattachments;sa=bySize.
843
 * Optionally adds a certain text to the messages the attachments were
844
 * 	removed from.
845
 */
846
function RemoveAttachmentBySize()
847
{
848
	global $smcFunc;
849
850
	checkSession('post', 'admin');
851
852
	// Find humungous attachments.
853
	$messages = removeAttachments(array('attachment_type' => 0, 'size' => 1024 * $_POST['size']), 'messages', true);
854
855
	// And make a note on the post.
856 View Code Duplication
	if (!empty($messages) && !empty($_POST['notice']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
857
		$smcFunc['db_query']('', '
858
			UPDATE {db_prefix}messages
859
			SET body = CONCAT(body, {string:notice})
860
			WHERE id_msg IN ({array_int:messages})',
861
			array(
862
				'messages' => $messages,
863
				'notice' => '<br><br>' . $_POST['notice'],
864
			)
865
		);
866
867
	redirectexit('action=admin;area=manageattachments;sa=maintenance');
868
}
869
870
/**
871
 * Remove a selection of attachments or avatars.
872
 * Called from the browse screen as submitted form by
873
 *  ?action=admin;area=manageattachments;sa=remove
874
 */
875
function RemoveAttachment()
876
{
877
	global $txt, $smcFunc, $language, $user_info;
878
879
	checkSession();
880
881
	if (!empty($_POST['remove']))
882
	{
883
		$attachments = array();
884
		// There must be a quicker way to pass this safety test??
885
		foreach ($_POST['remove'] as $removeID => $dummy)
886
			$attachments[] = (int) $removeID;
887
888
		// If the attachments are from a 3rd party, let them remove it. Hooks should remove their ids from the array.
889
		$filesRemoved = false;
890
		call_integration_hook('integrate_attachment_remove', array(&$filesRemoved, $attachments));
891
892
		if ($_REQUEST['type'] == 'avatars' && !empty($attachments))
893
			removeAttachments(array('id_attach' => $attachments));
894
		else if (!empty($attachments))
895
		{
896
			$messages = removeAttachments(array('id_attach' => $attachments), 'messages', true);
897
898
			// And change the message to reflect this.
899
			if (!empty($messages))
900
			{
901
				loadLanguage('index', $language, true);
902
				$smcFunc['db_query']('', '
903
					UPDATE {db_prefix}messages
904
					SET body = CONCAT(body, {string:deleted_message})
905
					WHERE id_msg IN ({array_int:messages_affected})',
906
					array(
907
						'messages_affected' => $messages,
908
						'deleted_message' => '<br><br>' . $txt['attachment_delete_admin'],
909
					)
910
				);
911
				loadLanguage('index', $user_info['language'], true);
912
			}
913
		}
914
	}
915
916
	$_GET['sort'] = isset($_GET['sort']) ? $_GET['sort'] : 'date';
917
	redirectexit('action=admin;area=manageattachments;sa=browse;' . $_REQUEST['type'] . ';sort=' . $_GET['sort'] . (isset($_GET['desc']) ? ';desc' : '') . ';start=' . $_REQUEST['start']);
918
}
919
920
/**
921
 * Removes all attachments in a single click
922
 * Called from the maintenance screen by
923
 *  ?action=admin;area=manageattachments;sa=removeall.
924
 */
925
function RemoveAllAttachments()
926
{
927
	global $txt, $smcFunc;
928
929
	checkSession('get', 'admin');
930
931
	$messages = removeAttachments(array('attachment_type' => 0), '', true);
932
933
	if (!isset($_POST['notice']))
934
		$_POST['notice'] = $txt['attachment_delete_admin'];
935
936
	// Add the notice on the end of the changed messages.
937 View Code Duplication
	if (!empty($messages))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
938
		$smcFunc['db_query']('', '
939
			UPDATE {db_prefix}messages
940
			SET body = CONCAT(body, {string:deleted_message})
941
			WHERE id_msg IN ({array_int:messages})',
942
			array(
943
				'messages' => $messages,
944
				'deleted_message' => '<br><br>' . $_POST['notice'],
945
			)
946
		);
947
948
	redirectexit('action=admin;area=manageattachments;sa=maintenance');
949
}
950
951
/**
952
 * Removes attachments or avatars based on a given query condition.
953
 * Called by several remove avatar/attachment functions in this file.
954
 * It removes attachments based that match the $condition.
955
 * It allows query_types 'messages' and 'members', whichever is need by the
956
 * $condition parameter.
957
 * It does no permissions check.
958
 * @internal
959
 *
960
 * @param array $condition An array of conditions
961
 * @param string $query_type The query type. Can be 'messages' or 'members'
962
 * @param bool $return_affected_messages Whether to return an array with the IDs of affected messages
963
 * @param bool $autoThumbRemoval Whether to automatically remove any thumbnails associated with the removed files
964
 * @return void|int[] Returns an array containing IDs of affected messages if $return_affected_messages is true
965
 */
966
function removeAttachments($condition, $query_type = '', $return_affected_messages = false, $autoThumbRemoval = true)
967
{
968
	global $modSettings, $smcFunc;
969
970
	// @todo This might need more work!
971
	$new_condition = array();
972
	$query_parameter = array(
973
		'thumb_attachment_type' => 3,
974
	);
975
	$do_logging = array();
976
977
	if (is_array($condition))
978
	{
979
		foreach ($condition as $real_type => $restriction)
980
		{
981
			// Doing a NOT?
982
			$is_not = substr($real_type, 0, 4) == 'not_';
983
			$type = $is_not ? substr($real_type, 4) : $real_type;
984
985
			if (in_array($type, array('id_member', 'id_attach', 'id_msg')))
986
				$new_condition[] = 'a.' . $type . ($is_not ? ' NOT' : '') . ' IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
987
			elseif ($type == 'attachment_type')
988
				$new_condition[] = 'a.attachment_type = {int:' . $real_type . '}';
989
			elseif ($type == 'poster_time')
990
				$new_condition[] = 'm.poster_time < {int:' . $real_type . '}';
991
			elseif ($type == 'last_login')
992
				$new_condition[] = 'mem.last_login < {int:' . $real_type . '}';
993
			elseif ($type == 'size')
994
				$new_condition[] = 'a.size > {int:' . $real_type . '}';
995
			elseif ($type == 'id_topic')
996
				$new_condition[] = 'm.id_topic IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
997
998
			// Add the parameter!
999
			$query_parameter[$real_type] = $restriction;
1000
1001
			if ($type == 'do_logging')
1002
				$do_logging = $condition['id_attach'];
1003
		}
1004
		$condition = implode(' AND ', $new_condition);
1005
	}
1006
1007
	// Delete it only if it exists...
1008
	$msgs = array();
1009
	$attach = array();
1010
	$parents = array();
1011
1012
	// Get all the attachment names and id_msg's.
1013
	$request = $smcFunc['db_query']('', '
1014
		SELECT
1015
			a.id_folder, a.filename, a.file_hash, a.attachment_type, a.id_attach, a.id_member' . ($query_type == 'messages' ? ', m.id_msg' : ', a.id_msg') . ',
1016
			thumb.id_folder AS thumb_folder, COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.filename AS thumb_filename, thumb.file_hash AS thumb_file_hash, thumb_parent.id_attach AS id_parent
1017
		FROM {db_prefix}attachments AS a' .($query_type == 'members' ? '
1018
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)' : ($query_type == 'messages' ? '
1019
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)' : '')) . '
1020
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1021
			LEFT JOIN {db_prefix}attachments AS thumb_parent ON (thumb.attachment_type = {int:thumb_attachment_type} AND thumb_parent.id_thumb = a.id_attach)
1022
		WHERE ' . $condition,
1023
		$query_parameter
1024
	);
1025
	while ($row = $smcFunc['db_fetch_assoc']($request))
1026
	{
1027
		// Figure out the "encrypted" filename and unlink it ;).
1028
		if ($row['attachment_type'] == 1)
1029
		{
1030
			// if attachment_type = 1, it's... an avatar in a custom avatar directory.
1031
			// wasn't it obvious? :P
1032
			// @todo look again at this.
1033
			@unlink($modSettings['custom_avatar_dir'] . '/' . $row['filename']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1034
		}
1035
		else
1036
		{
1037
			$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1038
			@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1039
1040
			// If this was a thumb, the parent attachment should know about it.
1041
			if (!empty($row['id_parent']))
1042
				$parents[] = $row['id_parent'];
1043
1044
			// If this attachments has a thumb, remove it as well.
1045
			if (!empty($row['id_thumb']) && $autoThumbRemoval)
1046
			{
1047
				$thumb_filename = getAttachmentFilename($row['thumb_filename'], $row['id_thumb'], $row['thumb_folder'], false, $row['thumb_file_hash']);
1048
				@unlink($thumb_filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1049
				$attach[] = $row['id_thumb'];
1050
			}
1051
		}
1052
1053
		// Make a list.
1054
		if ($return_affected_messages && empty($row['attachment_type']))
1055
			$msgs[] = $row['id_msg'];
1056
1057
		$attach[] = $row['id_attach'];
1058
	}
1059
	$smcFunc['db_free_result']($request);
1060
1061
	// Removed attachments don't have to be updated anymore.
1062
	$parents = array_diff($parents, $attach);
1063
	if (!empty($parents))
1064
		$smcFunc['db_query']('', '
1065
			UPDATE {db_prefix}attachments
1066
			SET id_thumb = {int:no_thumb}
1067
			WHERE id_attach IN ({array_int:parent_attachments})',
1068
			array(
1069
				'parent_attachments' => $parents,
1070
				'no_thumb' => 0,
1071
			)
1072
		);
1073
1074
	if (!empty($do_logging))
1075
	{
1076
		// In order to log the attachments, we really need their message and filename
1077
		$request = $smcFunc['db_query']('', '
1078
			SELECT m.id_msg, a.filename
1079
			FROM {db_prefix}attachments AS a
1080
				INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
1081
			WHERE a.id_attach IN ({array_int:attachments})
1082
				AND a.attachment_type = {int:attachment_type}',
1083
			array(
1084
				'attachments' => $do_logging,
1085
				'attachment_type' => 0,
1086
			)
1087
		);
1088
1089 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1090
			logAction(
1091
				'remove_attach',
1092
				array(
1093
					'message' => $row['id_msg'],
1094
					'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
1095
				)
1096
			);
1097
		$smcFunc['db_free_result']($request);
1098
	}
1099
1100
	if (!empty($attach))
1101
		$smcFunc['db_query']('', '
1102
			DELETE FROM {db_prefix}attachments
1103
			WHERE id_attach IN ({array_int:attachment_list})',
1104
			array(
1105
				'attachment_list' => $attach,
1106
			)
1107
		);
1108
1109
	call_integration_hook('integrate_remove_attachments', array($attach));
1110
1111
	if ($return_affected_messages)
1112
		return array_unique($msgs);
1113
}
1114
1115
/**
1116
 * This function should find attachments in the database that no longer exist and clear them, and fix filesize issues.
1117
 */
1118
function RepairAttachments()
1119
{
1120
	global $modSettings, $context, $txt, $smcFunc;
1121
1122
	checkSession('get');
1123
1124
	// If we choose cancel, redirect right back.
1125
	if (isset($_POST['cancel']))
1126
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
1127
1128
	// Try give us a while to sort this out...
1129
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1130
1131
	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
1132
	$context['starting_substep'] = $_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
1133
1134
	// Don't recall the session just in case.
1135
	if ($_GET['step'] == 0 && $_GET['substep'] == 0)
1136
	{
1137
		unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']);
1138
1139
		// If we're actually fixing stuff - work out what.
1140
		if (isset($_GET['fixErrors']))
1141
		{
1142
			// Nothing?
1143
			if (empty($_POST['to_fix']))
1144
				redirectexit('action=admin;area=manageattachments;sa=maintenance');
1145
1146
			$_SESSION['attachments_to_fix'] = array();
1147
			// @todo No need to do this I think.
1148
			foreach ($_POST['to_fix'] as $value)
1149
				$_SESSION['attachments_to_fix'][] = $value;
1150
		}
1151
	}
1152
1153
	// All the valid problems are here:
1154
	$context['repair_errors'] = array(
1155
		'missing_thumbnail_parent' => 0,
1156
		'parent_missing_thumbnail' => 0,
1157
		'file_missing_on_disk' => 0,
1158
		'file_wrong_size' => 0,
1159
		'file_size_of_zero' => 0,
1160
		'attachment_no_msg' => 0,
1161
		'avatar_no_member' => 0,
1162
		'wrong_folder' => 0,
1163
		'files_without_attachment' => 0,
1164
	);
1165
1166
	$to_fix = !empty($_SESSION['attachments_to_fix']) ? $_SESSION['attachments_to_fix'] : array();
1167
	$context['repair_errors'] = isset($_SESSION['attachments_to_fix2']) ? $_SESSION['attachments_to_fix2'] : $context['repair_errors'];
1168
	$fix_errors = isset($_GET['fixErrors']) ? true : false;
1169
1170
	// Get stranded thumbnails.
1171
	if ($_GET['step'] <= 0)
1172
	{
1173
		$result = $smcFunc['db_query']('', '
1174
			SELECT MAX(id_attach)
1175
			FROM {db_prefix}attachments
1176
			WHERE attachment_type = {int:thumbnail}',
1177
			array(
1178
				'thumbnail' => 3,
1179
			)
1180
		);
1181
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1182
		$smcFunc['db_free_result']($result);
1183
1184
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500)
1185
		{
1186
			$to_remove = array();
1187
1188
			$result = $smcFunc['db_query']('', '
1189
				SELECT thumb.id_attach, thumb.id_folder, thumb.filename, thumb.file_hash
1190
				FROM {db_prefix}attachments AS thumb
1191
					LEFT JOIN {db_prefix}attachments AS tparent ON (tparent.id_thumb = thumb.id_attach)
1192
				WHERE thumb.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1193
					AND thumb.attachment_type = {int:thumbnail}
1194
					AND tparent.id_attach IS NULL',
1195
				array(
1196
					'thumbnail' => 3,
1197
					'substep' => $_GET['substep'],
1198
				)
1199
			);
1200
			while ($row = $smcFunc['db_fetch_assoc']($result))
1201
			{
1202
				// Only do anything once... just in case
1203
				if (!isset($to_remove[$row['id_attach']]))
1204
				{
1205
					$to_remove[$row['id_attach']] = $row['id_attach'];
1206
					$context['repair_errors']['missing_thumbnail_parent']++;
1207
1208
					// If we are repairing remove the file from disk now.
1209 View Code Duplication
					if ($fix_errors && in_array('missing_thumbnail_parent', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1210
					{
1211
						$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1212
						@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1213
					}
1214
				}
1215
			}
1216
			if ($smcFunc['db_num_rows']($result) != 0)
1217
				$to_fix[] = 'missing_thumbnail_parent';
1218
			$smcFunc['db_free_result']($result);
1219
1220
			// Do we need to delete what we have?
1221 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('missing_thumbnail_parent', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1222
				$smcFunc['db_query']('', '
1223
					DELETE FROM {db_prefix}attachments
1224
					WHERE id_attach IN ({array_int:to_remove})
1225
						AND attachment_type = {int:attachment_type}',
1226
					array(
1227
						'to_remove' => $to_remove,
1228
						'attachment_type' => 3,
1229
					)
1230
				);
1231
1232
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1233
		}
1234
1235
		$_GET['step'] = 1;
1236
		$_GET['substep'] = 0;
1237
		pauseAttachmentMaintenance($to_fix);
1238
	}
1239
1240
	// Find parents which think they have thumbnails, but actually, don't.
1241
	if ($_GET['step'] <= 1)
1242
	{
1243
		$result = $smcFunc['db_query']('', '
1244
			SELECT MAX(id_attach)
1245
			FROM {db_prefix}attachments
1246
			WHERE id_thumb != {int:no_thumb}',
1247
			array(
1248
				'no_thumb' => 0,
1249
			)
1250
		);
1251
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1252
		$smcFunc['db_free_result']($result);
1253
1254
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500)
1255
		{
1256
			$to_update = array();
1257
1258
			$result = $smcFunc['db_query']('', '
1259
				SELECT a.id_attach
1260
				FROM {db_prefix}attachments AS a
1261
					LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1262
				WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1263
					AND a.id_thumb != {int:no_thumb}
1264
					AND thumb.id_attach IS NULL',
1265
				array(
1266
					'no_thumb' => 0,
1267
					'substep' => $_GET['substep'],
1268
				)
1269
			);
1270
			while ($row = $smcFunc['db_fetch_assoc']($result))
1271
			{
1272
				$to_update[] = $row['id_attach'];
1273
				$context['repair_errors']['parent_missing_thumbnail']++;
1274
			}
1275
			if ($smcFunc['db_num_rows']($result) != 0)
1276
				$to_fix[] = 'parent_missing_thumbnail';
1277
			$smcFunc['db_free_result']($result);
1278
1279
			// Do we need to delete what we have?
1280
			if ($fix_errors && !empty($to_update) && in_array('parent_missing_thumbnail', $to_fix))
1281
				$smcFunc['db_query']('', '
1282
					UPDATE {db_prefix}attachments
1283
					SET id_thumb = {int:no_thumb}
1284
					WHERE id_attach IN ({array_int:to_update})',
1285
					array(
1286
						'to_update' => $to_update,
1287
						'no_thumb' => 0,
1288
					)
1289
				);
1290
1291
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1292
		}
1293
1294
		$_GET['step'] = 2;
1295
		$_GET['substep'] = 0;
1296
		pauseAttachmentMaintenance($to_fix);
1297
	}
1298
1299
	// This may take forever I'm afraid, but life sucks... recount EVERY attachments!
1300
	if ($_GET['step'] <= 2)
1301
	{
1302
		$result = $smcFunc['db_query']('', '
1303
			SELECT MAX(id_attach)
1304
			FROM {db_prefix}attachments',
1305
			array(
1306
			)
1307
		);
1308
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1309
		$smcFunc['db_free_result']($result);
1310
1311
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 250)
1312
		{
1313
			$to_remove = array();
1314
			$errors_found = array();
1315
1316
			$result = $smcFunc['db_query']('', '
1317
				SELECT id_attach, id_folder, filename, file_hash, size, attachment_type
1318
				FROM {db_prefix}attachments
1319
				WHERE id_attach BETWEEN {int:substep} AND {int:substep} + 249',
1320
				array(
1321
					'substep' => $_GET['substep'],
1322
				)
1323
			);
1324
			while ($row = $smcFunc['db_fetch_assoc']($result))
1325
			{
1326
				// Get the filename.
1327 View Code Duplication
				if ($row['attachment_type'] == 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1328
					$filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename'];
1329
				else
1330
					$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1331
1332
				// File doesn't exist?
1333
				if (!file_exists($filename))
1334
				{
1335
					// If we're lucky it might just be in a different folder.
1336
					if (!empty($modSettings['currentAttachmentUploadDir']))
1337
					{
1338
						// Get the attachment name with out the folder.
1339
						$attachment_name = $row['id_attach'] . '_' . $row['file_hash'] .'.dat';
1340
1341
						// Loop through the other folders.
1342
						foreach ($modSettings['attachmentUploadDir'] as $id => $dir)
1343
							if (file_exists($dir . '/' . $attachment_name))
1344
							{
1345
								$context['repair_errors']['wrong_folder']++;
1346
								$errors_found[] = 'wrong_folder';
1347
1348
								// Are we going to fix this now?
1349 View Code Duplication
								if ($fix_errors && in_array('wrong_folder', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1350
									$smcFunc['db_query']('', '
1351
										UPDATE {db_prefix}attachments
1352
										SET id_folder = {int:new_folder}
1353
										WHERE id_attach = {int:id_attach}',
1354
										array(
1355
											'new_folder' => $id,
1356
											'id_attach' => $row['id_attach'],
1357
										)
1358
									);
1359
1360
								continue 2;
1361
							}
1362
					}
1363
1364
					$to_remove[] = $row['id_attach'];
1365
					$context['repair_errors']['file_missing_on_disk']++;
1366
					$errors_found[] = 'file_missing_on_disk';
1367
				}
1368
				elseif (filesize($filename) == 0)
1369
				{
1370
					$context['repair_errors']['file_size_of_zero']++;
1371
					$errors_found[] = 'file_size_of_zero';
1372
1373
					// Fixing?
1374
					if ($fix_errors && in_array('file_size_of_zero', $to_fix))
1375
					{
1376
						$to_remove[] = $row['id_attach'];
1377
						@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1378
					}
1379
				}
1380
				elseif (filesize($filename) != $row['size'])
1381
				{
1382
					$context['repair_errors']['file_wrong_size']++;
1383
					$errors_found[] = 'file_wrong_size';
1384
1385
					// Fix it here?
1386 View Code Duplication
					if ($fix_errors && in_array('file_wrong_size', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1387
					{
1388
						$smcFunc['db_query']('', '
1389
							UPDATE {db_prefix}attachments
1390
							SET size = {int:filesize}
1391
							WHERE id_attach = {int:id_attach}',
1392
							array(
1393
								'filesize' => filesize($filename),
1394
								'id_attach' => $row['id_attach'],
1395
							)
1396
						);
1397
					}
1398
				}
1399
			}
1400
1401
			if (in_array('file_missing_on_disk', $errors_found))
1402
				$to_fix[] = 'file_missing_on_disk';
1403
			if (in_array('file_size_of_zero', $errors_found))
1404
				$to_fix[] = 'file_size_of_zero';
1405
			if (in_array('file_wrong_size', $errors_found))
1406
				$to_fix[] = 'file_wrong_size';
1407
			if (in_array('wrong_folder', $errors_found))
1408
				$to_fix[] = 'wrong_folder';
1409
			$smcFunc['db_free_result']($result);
1410
1411
			// Do we need to delete what we have?
1412
			if ($fix_errors && !empty($to_remove))
1413
			{
1414
				$smcFunc['db_query']('', '
1415
					DELETE FROM {db_prefix}attachments
1416
					WHERE id_attach IN ({array_int:to_remove})',
1417
					array(
1418
						'to_remove' => $to_remove,
1419
					)
1420
				);
1421
				$smcFunc['db_query']('', '
1422
					UPDATE {db_prefix}attachments
1423
					SET id_thumb = {int:no_thumb}
1424
					WHERE id_thumb IN ({array_int:to_remove})',
1425
					array(
1426
						'to_remove' => $to_remove,
1427
						'no_thumb' => 0,
1428
					)
1429
				);
1430
			}
1431
1432
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1433
		}
1434
1435
		$_GET['step'] = 3;
1436
		$_GET['substep'] = 0;
1437
		pauseAttachmentMaintenance($to_fix);
1438
	}
1439
1440
	// Get avatars with no members associated with them.
1441
	if ($_GET['step'] <= 3)
1442
	{
1443
		$result = $smcFunc['db_query']('', '
1444
			SELECT MAX(id_attach)
1445
			FROM {db_prefix}attachments',
1446
			array(
1447
			)
1448
		);
1449
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1450
		$smcFunc['db_free_result']($result);
1451
1452
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500)
1453
		{
1454
			$to_remove = array();
1455
1456
			$result = $smcFunc['db_query']('', '
1457
				SELECT a.id_attach, a.id_folder, a.filename, a.file_hash, a.attachment_type
1458
				FROM {db_prefix}attachments AS a
1459
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)
1460
				WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1461
					AND a.id_member != {int:no_member}
1462
					AND a.id_msg = {int:no_msg}
1463
					AND mem.id_member IS NULL',
1464
				array(
1465
					'no_member' => 0,
1466
					'no_msg' => 0,
1467
					'substep' => $_GET['substep'],
1468
				)
1469
			);
1470
			while ($row = $smcFunc['db_fetch_assoc']($result))
1471
			{
1472
				$to_remove[] = $row['id_attach'];
1473
				$context['repair_errors']['avatar_no_member']++;
1474
1475
				// If we are repairing remove the file from disk now.
1476
				if ($fix_errors && in_array('avatar_no_member', $to_fix))
1477
				{
1478 View Code Duplication
					if ($row['attachment_type'] == 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1479
						$filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename'];
1480
					else
1481
						$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1482
					@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1483
				}
1484
			}
1485
			if ($smcFunc['db_num_rows']($result) != 0)
1486
				$to_fix[] = 'avatar_no_member';
1487
			$smcFunc['db_free_result']($result);
1488
1489
			// Do we need to delete what we have?
1490 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('avatar_no_member', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1491
				$smcFunc['db_query']('', '
1492
					DELETE FROM {db_prefix}attachments
1493
					WHERE id_attach IN ({array_int:to_remove})
1494
						AND id_member != {int:no_member}
1495
						AND id_msg = {int:no_msg}',
1496
					array(
1497
						'to_remove' => $to_remove,
1498
						'no_member' => 0,
1499
						'no_msg' => 0,
1500
					)
1501
				);
1502
1503
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1504
		}
1505
1506
		$_GET['step'] = 4;
1507
		$_GET['substep'] = 0;
1508
		pauseAttachmentMaintenance($to_fix);
1509
	}
1510
1511
	// What about attachments, who are missing a message :'(
1512
	if ($_GET['step'] <= 4)
1513
	{
1514
		$result = $smcFunc['db_query']('', '
1515
			SELECT MAX(id_attach)
1516
			FROM {db_prefix}attachments',
1517
			array(
1518
			)
1519
		);
1520
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1521
		$smcFunc['db_free_result']($result);
1522
1523
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500)
1524
		{
1525
			$to_remove = array();
1526
			$ignore_ids = array(0);
1527
			call_integration_hook('integrate_repair_attachments_nomsg', array(&$ignore_ids, $_GET['substep'], $_GET['substep'] += 500));
1528
1529
			$result = $smcFunc['db_query']('', '
1530
				SELECT a.id_attach, a.id_folder, a.filename, a.file_hash
1531
				FROM {db_prefix}attachments AS a
1532
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1533
				WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1534
					AND a.id_member = {int:no_member}
1535
					AND a.id_msg != {int:no_msg}
1536
					AND NOT FIND_IN_SET(a.id_msg, {array_int:ignore_ids})
1537
					AND m.id_msg IS NULL',
1538
				array(
1539
					'no_member' => 0,
1540
					'no_msg' => 0,
1541
					'substep' => $_GET['substep'],
1542
					'ignore_ids' => $ignore_ids,
1543
				)
1544
			);
1545
			while ($row = $smcFunc['db_fetch_assoc']($result))
1546
			{
1547
				$to_remove[] = $row['id_attach'];
1548
				$context['repair_errors']['attachment_no_msg']++;
1549
1550
				// If we are repairing remove the file from disk now.
1551 View Code Duplication
				if ($fix_errors && in_array('attachment_no_msg', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1552
				{
1553
					$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1554
					@unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1555
				}
1556
			}
1557
			if ($smcFunc['db_num_rows']($result) != 0)
1558
				$to_fix[] = 'attachment_no_msg';
1559
			$smcFunc['db_free_result']($result);
1560
1561
			// Do we need to delete what we have?
1562 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('attachment_no_msg', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1563
				$smcFunc['db_query']('', '
1564
					DELETE FROM {db_prefix}attachments
1565
					WHERE id_attach IN ({array_int:to_remove})
1566
						AND id_member = {int:no_member}
1567
						AND id_msg != {int:no_msg}',
1568
					array(
1569
						'to_remove' => $to_remove,
1570
						'no_member' => 0,
1571
						'no_msg' => 0,
1572
					)
1573
				);
1574
1575
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1576
		}
1577
1578
		$_GET['step'] = 5;
1579
		$_GET['substep'] = 0;
1580
		pauseAttachmentMaintenance($to_fix);
1581
	}
1582
1583
	// What about files who are not recorded in the database?
1584
	if ($_GET['step'] <= 5)
1585
	{
1586
		$attach_dirs = $modSettings['attachmentUploadDir'];
1587
1588
		$current_check = 0;
1589
		$max_checks = 500;
1590
		$files_checked = empty($_GET['substep']) ? 0 : $_GET['substep'];
1591
		foreach ($attach_dirs as $attach_dir)
1592
		{
1593
			if ($dir = @opendir($attach_dir))
1594
			{
1595
				while ($file = readdir($dir))
1596
				{
1597
					if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
1598
						continue;
1599
1600
					if ($files_checked <= $current_check)
1601
					{
1602
						// Temporary file, get rid of it!
1603
						if (strpos($file, 'post_tmp_') !== false)
1604
						{
1605
							// Temp file is more than 5 hours old!
1606 View Code Duplication
							if (filemtime($attach_dir . '/' . $file) < time() - 18000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1607
								@unlink($attach_dir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1608
						}
1609
						// That should be an attachment, let's check if we have it in the database
1610
						elseif (strpos($file, '_') !== false)
1611
						{
1612
							$attachID = (int) substr($file, 0, strpos($file, '_'));
1613
							if (!empty($attachID))
1614
							{
1615
								$request = $smcFunc['db_query']('', '
1616
									SELECT  id_attach
1617
									FROM {db_prefix}attachments
1618
									WHERE id_attach = {int:attachment_id}
1619
									LIMIT 1',
1620
									array(
1621
										'attachment_id' => $attachID,
1622
									)
1623
								);
1624
								if ($smcFunc['db_num_rows']($request) == 0)
1625
								{
1626 View Code Duplication
									if ($fix_errors && in_array('files_without_attachment', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1627
									{
1628
										@unlink($attach_dir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1629
									}
1630
									else
1631
									{
1632
										$context['repair_errors']['files_without_attachment']++;
1633
										$to_fix[] = 'files_without_attachment';
1634
									}
1635
								}
1636
								$smcFunc['db_free_result']($request);
1637
							}
1638
						}
1639 View Code Duplication
						else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1640
						{
1641
							if ($fix_errors && in_array('files_without_attachment', $to_fix))
1642
							{
1643
								@unlink($attach_dir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1644
							}
1645
							else
1646
							{
1647
								$context['repair_errors']['files_without_attachment']++;
1648
								$to_fix[] = 'files_without_attachment';
1649
							}
1650
						}
1651
					}
1652
					$current_check++;
1653
					$_GET['substep'] = $current_check;
1654
					if ($current_check - $files_checked >= $max_checks)
1655
						pauseAttachmentMaintenance($to_fix);
1656
				}
1657
				closedir($dir);
1658
			}
1659
		}
1660
1661
		$_GET['step'] = 5;
1662
		$_GET['substep'] = 0;
1663
		pauseAttachmentMaintenance($to_fix);
1664
	}
1665
1666
	// Got here we must be doing well - just the template! :D
1667
	$context['page_title'] = $txt['repair_attachments'];
1668
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1669
	$context['sub_template'] = 'attachment_repair';
1670
1671
	// What stage are we at?
1672
	$context['completed'] = $fix_errors ? true : false;
1673
	$context['errors_found'] = !empty($to_fix) ? true : false;
1674
1675
}
1676
1677
/**
1678
 * Function called in-between each round of attachments and avatar repairs.
1679
 * Called by repairAttachments().
1680
 * If repairAttachments() has more steps added, this function needs updated!
1681
 *
1682
 * @param array $to_fix IDs of attachments to fix
1683
 * @param int The maximum substep to reach before pausing
1684
 */
1685
function pauseAttachmentMaintenance($to_fix, $max_substep = 0)
1686
{
1687
	global $context, $txt, $time_start;
1688
1689
	// Try get more time...
1690
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1691
	if (function_exists('apache_reset_timeout'))
1692
		@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1693
1694
	// Have we already used our maximum time?
1695
	if (time() - array_sum(explode(' ', $time_start)) < 3 || $context['starting_substep'] == $_GET['substep'])
1696
		return;
1697
1698
	$context['continue_get_data'] = '?action=admin;area=manageattachments;sa=repair' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1699
	$context['page_title'] = $txt['not_done_title'];
1700
	$context['continue_post_data'] = '';
1701
	$context['continue_countdown'] = '2';
1702
	$context['sub_template'] = 'not_done';
1703
1704
	// Specific stuff to not break this template!
1705
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1706
1707
	// Change these two if more steps are added!
1708
	if (empty($max_substep))
1709
		$context['continue_percent'] = round(($_GET['step'] * 100) / 25);
1710 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1711
		$context['continue_percent'] = round(($_GET['step'] * 100 + ($_GET['substep'] * 100) / $max_substep) / 25);
1712
1713
	// Never more than 100%!
1714
	$context['continue_percent'] = min($context['continue_percent'], 100);
1715
1716
	$_SESSION['attachments_to_fix'] = $to_fix;
1717
	$_SESSION['attachments_to_fix2'] = $context['repair_errors'];
1718
1719
	obExit();
1720
}
1721
1722
/**
1723
 * Called from a mouse click, works out what we want to do with attachments and actions it.
1724
 */
1725
function ApproveAttach()
1726
{
1727
	global $smcFunc;
1728
1729
	// Security is our primary concern...
1730
	checkSession('get');
1731
1732
	// If it approve or delete?
1733
	$is_approve = !isset($_GET['sa']) || $_GET['sa'] != 'reject' ? true : false;
1734
1735
	$attachments = array();
1736
	// If we are approving all ID's in a message , get the ID's.
1737
	if ($_GET['sa'] == 'all' && !empty($_GET['mid']))
1738
	{
1739
		$id_msg = (int) $_GET['mid'];
1740
1741
		$request = $smcFunc['db_query']('', '
1742
			SELECT id_attach
1743
			FROM {db_prefix}attachments
1744
			WHERE id_msg = {int:id_msg}
1745
				AND approved = {int:is_approved}
1746
				AND attachment_type = {int:attachment_type}',
1747
			array(
1748
				'id_msg' => $id_msg,
1749
				'is_approved' => 0,
1750
				'attachment_type' => 0,
1751
			)
1752
		);
1753
		while ($row = $smcFunc['db_fetch_assoc']($request))
1754
			$attachments[] = $row['id_attach'];
1755
		$smcFunc['db_free_result']($request);
1756
	}
1757
	elseif (!empty($_GET['aid']))
1758
		$attachments[] = (int) $_GET['aid'];
1759
1760
	if (empty($attachments))
1761
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1762
1763
	// Now we have some ID's cleaned and ready to approve, but first - let's check we have permission!
1764
	$allowed_boards = boardsAllowedTo('approve_posts');
1765
1766
	// Validate the attachments exist and are the right approval state.
1767
	$request = $smcFunc['db_query']('', '
1768
		SELECT a.id_attach, m.id_board, m.id_msg, m.id_topic
1769
		FROM {db_prefix}attachments AS a
1770
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1771
		WHERE a.id_attach IN ({array_int:attachments})
1772
			AND a.attachment_type = {int:attachment_type}
1773
			AND a.approved = {int:is_approved}',
1774
		array(
1775
			'attachments' => $attachments,
1776
			'attachment_type' => 0,
1777
			'is_approved' => 0,
1778
		)
1779
	);
1780
	$attachments = array();
1781
	while ($row = $smcFunc['db_fetch_assoc']($request))
1782
	{
1783
		// We can only add it if we can approve in this board!
1784
		if ($allowed_boards = array(0) || in_array($row['id_board'], $allowed_boards))
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $allowed_boards = (array...rd'], $allowed_boards)), Probably Intended Meaning: ($allowed_boards = array...ard'], $allowed_boards)
Loading history...
1785
		{
1786
			$attachments[] = $row['id_attach'];
1787
1788
			// Also come up with the redirection URL.
1789
			$redirect = 'topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'];
1790
		}
1791
	}
1792
	$smcFunc['db_free_result']($request);
1793
1794
	if (empty($attachments))
1795
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1796
1797
	// Finally, we are there. Follow through!
1798
	if ($is_approve)
1799
	{
1800
		// Checked and deemed worthy.
1801
		ApproveAttachments($attachments);
1802
	}
1803
	else
1804
		removeAttachments(array('id_attach' => $attachments, 'do_logging' => true));
1805
1806
	// Return to the topic....
1807
	redirectexit($redirect);
0 ignored issues
show
Bug introduced by
The variable $redirect does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1808
}
1809
1810
/**
1811
 * Approve an attachment, or maybe even more - no permission check!
1812
 *
1813
 * @param array $attachments The IDs of the attachments to approve
1814
 * @return void|int Returns 0 if the operation failed, otherwise returns nothing
1815
 */
1816
function ApproveAttachments($attachments)
1817
{
1818
	global $smcFunc;
1819
1820
	if (empty($attachments))
1821
		return 0;
1822
1823
	// For safety, check for thumbnails...
1824
	$request = $smcFunc['db_query']('', '
1825
		SELECT
1826
			a.id_attach, a.id_member, COALESCE(thumb.id_attach, 0) AS id_thumb
1827
		FROM {db_prefix}attachments AS a
1828
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1829
		WHERE a.id_attach IN ({array_int:attachments})
1830
			AND a.attachment_type = {int:attachment_type}',
1831
		array(
1832
			'attachments' => $attachments,
1833
			'attachment_type' => 0,
1834
		)
1835
	);
1836
	$attachments = array();
1837
	while ($row = $smcFunc['db_fetch_assoc']($request))
1838
	{
1839
		// Update the thumbnail too...
1840
		if (!empty($row['id_thumb']))
1841
			$attachments[] = $row['id_thumb'];
1842
1843
		$attachments[] = $row['id_attach'];
1844
	}
1845
	$smcFunc['db_free_result']($request);
1846
1847
	if (empty($attachments))
1848
		return 0;
1849
1850
	// Approving an attachment is not hard - it's easy.
1851
	$smcFunc['db_query']('', '
1852
		UPDATE {db_prefix}attachments
1853
		SET approved = {int:is_approved}
1854
		WHERE id_attach IN ({array_int:attachments})',
1855
		array(
1856
			'attachments' => $attachments,
1857
			'is_approved' => 1,
1858
		)
1859
	);
1860
1861
	// In order to log the attachments, we really need their message and filename
1862
	$request = $smcFunc['db_query']('', '
1863
		SELECT m.id_msg, a.filename
1864
		FROM {db_prefix}attachments AS a
1865
			INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
1866
		WHERE a.id_attach IN ({array_int:attachments})
1867
			AND a.attachment_type = {int:attachment_type}',
1868
		array(
1869
			'attachments' => $attachments,
1870
			'attachment_type' => 0,
1871
		)
1872
	);
1873
1874 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1875
		logAction(
1876
			'approve_attach',
1877
			array(
1878
				'message' => $row['id_msg'],
1879
				'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
1880
			)
1881
		);
1882
	$smcFunc['db_free_result']($request);
1883
1884
	// Remove from the approval queue.
1885
	$smcFunc['db_query']('', '
1886
		DELETE FROM {db_prefix}approval_queue
1887
		WHERE id_attach IN ({array_int:attachments})',
1888
		array(
1889
			'attachments' => $attachments,
1890
		)
1891
	);
1892
1893
	call_integration_hook('integrate_approve_attachments', array($attachments));
1894
}
1895
1896
/**
1897
 * This function lists and allows updating of multiple attachments paths.
1898
 */
1899
function ManageAttachmentPaths()
1900
{
1901
	global $modSettings, $scripturl, $context, $txt, $sourcedir, $boarddir, $smcFunc, $settings;
1902
1903
	// Since this needs to be done eventually.
1904
	if (!isset($modSettings['attachment_basedirectories']))
1905
		$modSettings['attachment_basedirectories'] = array();
1906
1907
	elseif (!is_array($modSettings['attachment_basedirectories']))
1908
		$modSettings['attachment_basedirectories'] = smf_json_decode($modSettings['attachment_basedirectories'], true);
1909
1910
	$errors = array();
1911
1912
	// Saving?
1913
	if (isset($_REQUEST['save']))
1914
	{
1915
		checkSession();
1916
1917
		$_POST['current_dir'] = (int) $_POST['current_dir'];
1918
		$new_dirs = array();
1919
		foreach ($_POST['dirs'] as $id => $path)
1920
		{
1921
			$error = '';
1922
			$id = (int) $id;
1923
			if ($id < 1)
1924
				continue;
1925
1926
			// Sorry, these dirs are NOT valid
1927
			$invalid_dirs = array($boarddir, $settings['default_theme_dir'], $sourcedir);
1928
			if (in_array($path, $invalid_dirs))
1929
			{
1930
				$errors[] = $path . ': ' . $txt['attach_dir_invalid'];
1931
				continue;
1932
			}
1933
1934
			// Hmm, a new path maybe?
1935
			// Don't allow empty paths
1936
			if (!array_key_exists($id, $modSettings['attachmentUploadDir']) && !empty($path))
1937
			{
1938
				// or is it?
1939
				if (in_array($path, $modSettings['attachmentUploadDir']) || in_array($boarddir . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
1940
				{
1941
						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
1942
						continue;
1943
				}
1944
				elseif (empty($path))
1945
				{
1946
					// Ignore this and set $id to one less
1947
					continue;
1948
				}
1949
1950
				// OK, so let's try to create it then.
1951
				require_once($sourcedir . '/Subs-Attachments.php');
1952
				if (automanage_attachments_create_directory($path))
1953
					$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
1954
				else
1955
					$errors[] =  $path . ': ' . $txt[$context['dir_creation_error']];
1956
			}
1957
1958
			// Changing a directory name?
1959
			if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $path != $modSettings['attachmentUploadDir'][$id])
1960
			{
1961
				if ($path != $modSettings['attachmentUploadDir'][$id] && !is_dir($path))
1962
				{
1963
					if (!@rename($modSettings['attachmentUploadDir'][$id], $path))
1964
					{
1965
						$errors[] = $path . ': ' . $txt['attach_dir_no_rename'];
1966
						$path = $modSettings['attachmentUploadDir'][$id];
1967
					}
1968
				}
1969
				else
1970
				{
1971
					$errors[] = $path . ': ' . $txt['attach_dir_exists_msg'];
1972
					$path = $modSettings['attachmentUploadDir'][$id];
1973
				}
1974
1975
				// Update the base directory path
1976
				if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
1977
				{
1978
					$base = $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id] ? $path : $modSettings['basedirectory_for_attachments'];
1979
1980
					$modSettings['attachment_basedirectories'][$id] = $path;
1981
					updateSettings(array(
1982
						'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
1983
						'basedirectory_for_attachments' => $base,
1984
					));
1985
					$modSettings['attachment_basedirectories'] = smf_json_decode($modSettings['attachment_basedirectories'], true);
0 ignored issues
show
Documentation introduced by
$modSettings['attachment_basedirectories'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1986
				}
1987
			}
1988
1989
			if (empty($path))
1990
			{
1991
				$path = $modSettings['attachmentUploadDir'][$id];
1992
1993
				// It's not a good idea to delete the current directory.
1994
				if ($id == (!empty($_POST['current_dir']) ? $_POST['current_dir'] : $modSettings['currentAttachmentUploadDir']))
1995
					$errors[] = $path . ': ' . $txt['attach_dir_is_current'];
1996
				// Or the current base directory
1997
				elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
1998
					$errors[] = $path . ': ' . $txt['attach_dir_is_current_bd'];
1999
				else
2000
				{
2001
					// Let's not try to delete a path with files in it.
2002
					$request = $smcFunc['db_query']('', '
2003
						SELECT COUNT(id_attach) AS num_attach
2004
						FROM {db_prefix}attachments
2005
						WHERE id_folder = {int:id_folder}',
2006
						array(
2007
							'id_folder' => (int) $id,
2008
						)
2009
					);
2010
2011
					list ($num_attach) = $smcFunc['db_fetch_row']($request);
2012
					$smcFunc['db_free_result']($request);
2013
2014
					// A check to see if it's a used base dir.
2015
					if (!empty($modSettings['attachment_basedirectories']))
2016
					{
2017
						// Count any sub-folders.
2018 View Code Duplication
						foreach ($modSettings['attachmentUploadDir'] as $sub)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2019
							if (strpos($sub, $path . DIRECTORY_SEPARATOR) !== false)
2020
								$num_attach++;
2021
					}
2022
2023
					// It's safe to delete. So try to delete the folder also
2024
					if ($num_attach == 0)
2025
					{
2026
						if (is_dir($path))
2027
							$doit = true;
2028
						elseif (is_dir($boarddir . DIRECTORY_SEPARATOR . $path))
2029
						{
2030
							$doit = true;
2031
							$path = $boarddir . DIRECTORY_SEPARATOR . $path;
2032
						}
2033
2034
						if (isset($doit) && realpath($path) != realpath($boarddir))
2035
						{
2036
							unlink($path . '/.htaccess');
2037
							unlink($path . '/index.php');
2038
							if (!@rmdir($path))
2039
								$error = $path . ': ' . $txt['attach_dir_no_delete'];
2040
						}
2041
2042
						// Remove it from the base directory list.
2043
						if (empty($error) && !empty($modSettings['attachment_basedirectories']))
2044
						{
2045
							unset($modSettings['attachment_basedirectories'][$id]);
2046
							updateSettings(array('attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories'])));
2047
							$modSettings['attachment_basedirectories'] = smf_json_decode($modSettings['attachment_basedirectories'], true);
0 ignored issues
show
Documentation introduced by
$modSettings['attachment_basedirectories'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2048
						}
2049
					}
2050
					else
2051
						$error = $path . ': ' . $txt['attach_dir_no_remove'];
2052
2053
					if (empty($error))
2054
						continue;
2055
					else
2056
						$errors[] = $error;
2057
				}
2058
			}
2059
2060
			$new_dirs[$id] = $path;
2061
		}
2062
2063
		// We need to make sure the current directory is right.
2064
		if (empty($_POST['current_dir']) && !empty($modSettings['currentAttachmentUploadDir']))
2065
			$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2066
2067
		// Find the current directory if there's no value carried,
2068
		if (empty($_POST['current_dir']) || empty($new_dirs[$_POST['current_dir']]))
2069
		{
2070
			if (array_key_exists($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
2071
				$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2072
			else
2073
				$_POST['current_dir'] = max(array_keys($modSettings['attachmentUploadDir']));
2074
		}
2075
2076
		// If the user wishes to go back, update the last_dir array
2077
		if ($_POST['current_dir'] !=  $modSettings['currentAttachmentUploadDir']&& !empty($modSettings['last_attachments_directory']) && (isset($modSettings['last_attachments_directory'][$_POST['current_dir']]) || isset($modSettings['last_attachments_directory'][0])))
2078
		{
2079 View Code Duplication
			if (!is_array($modSettings['last_attachments_directory']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2080
				$modSettings['last_attachments_directory'] = smf_json_decode($modSettings['last_attachments_directory'], true);
2081
			$num = substr(strrchr($modSettings['attachmentUploadDir'][$_POST['current_dir']], '_'), 1);
2082
2083
			if (is_numeric($num))
2084
			{
2085
				// Need to find the base folder.
2086
				$bid = -1;
2087
				$use_subdirectories_for_attachments = 0;
2088
				if (!empty($modSettings['attachment_basedirectories']))
2089
					foreach ($modSettings['attachment_basedirectories'] as $bid => $base)
2090 View Code Duplication
						if (strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $base . DIRECTORY_SEPARATOR) !==false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2091
						{
2092
							$use_subdirectories_for_attachments = 1;
2093
							break;
2094
						}
2095
2096 View Code Duplication
				if ($use_subdirectories_for_attachments == 0 && strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $boarddir . DIRECTORY_SEPARATOR) !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2097
					$bid = 0;
2098
2099
				$modSettings['last_attachments_directory'][$bid] = (int) $num;
2100
				$modSettings['basedirectory_for_attachments'] = !empty($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
2101
				$modSettings['use_subdirectories_for_attachments'] = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['use_subdirectories_for_attachments'] : 0;
2102
				updateSettings(array(
2103
					'last_attachments_directory' => json_encode($modSettings['last_attachments_directory']),
2104
					'basedirectory_for_attachments' => $bid == 0 ? $modSettings['basedirectory_for_attachments'] : $modSettings['attachment_basedirectories'][$bid],
2105
					'use_subdirectories_for_attachments' => $use_subdirectories_for_attachments,
2106
				));
2107
			}
2108
		}
2109
2110
		// Going back to just one path?
2111
		if (count($new_dirs) == 1)
2112
		{
2113
			// We might need to reset the paths. This loop will just loop through once.
2114
			foreach ($new_dirs as $id => $dir)
2115
			{
2116
				if ($id != 1)
2117
					$smcFunc['db_query']('', '
2118
						UPDATE {db_prefix}attachments
2119
						SET id_folder = {int:default_folder}
2120
						WHERE id_folder = {int:current_folder}',
2121
						array(
2122
							'default_folder' => 1,
2123
							'current_folder' => $id,
2124
						)
2125
					);
2126
2127
				$update = array(
2128
					'currentAttachmentUploadDir' => 1,
2129
					'attachmentUploadDir' => json_encode(array(1 => $dir)),
2130
				);
2131
			}
2132
		}
2133
		else
2134
		{
2135
			// Save it to the database.
2136
			$update = array(
2137
				'currentAttachmentUploadDir' => $_POST['current_dir'],
2138
				'attachmentUploadDir' => json_encode($new_dirs),
2139
			);
2140
		}
2141
2142
		if (!empty($update))
2143
			updateSettings($update);
2144
2145
		if (!empty($errors))
2146
			$_SESSION['errors']['dir'] = $errors;
2147
2148
		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
2149
	}
2150
2151
	// Saving a base directory?
2152
	if (isset($_REQUEST['save2']))
2153
	{
2154
		checkSession();
2155
2156
		// Changing the current base directory?
2157
		$_POST['current_base_dir'] = isset($_POST['current_base_dir']) ? (int) $_POST['current_base_dir'] : 1;
2158
		if (empty($_POST['new_base_dir']) && !empty($_POST['current_base_dir']))
2159
		{
2160 View Code Duplication
			if ($modSettings['basedirectory_for_attachments'] != $modSettings['attachmentUploadDir'][$_POST['current_base_dir']])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2161
				$update = (array(
2162
					'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2163
				));
2164
		}
2165
2166
		if (isset($_POST['base_dir']))
2167
		{
2168
			foreach ($_POST['base_dir'] as $id => $dir)
2169
			{
2170
				if (!empty($dir) && $dir != $modSettings['attachmentUploadDir'][$id])
2171
				{
2172
					if (@rename($modSettings['attachmentUploadDir'][$id], $dir))
2173
					{
2174
						$modSettings['attachmentUploadDir'][$id] = $dir;
2175
						$modSettings['attachment_basedirectories'][$id] = $dir;
2176
						$update = (array(
2177
							'attachmentUploadDir' => json_encode($modSettings['attachmentUploadDir']),
2178
							'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
2179
							'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2180
						));
2181
					}
2182
				}
2183
2184
				if (empty($dir))
2185
				{
2186
					if ($id == $_POST['current_base_dir'])
2187
					{
2188
						$errors[] = $modSettings['attachmentUploadDir'][$id] . ': ' . $txt['attach_dir_is_current'];
2189
						continue;
2190
					}
2191
2192
					unset($modSettings['attachment_basedirectories'][$id]);
2193
					$update = (array(
2194
						'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
2195
						'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2196
					));
2197
				}
2198
			}
2199
		}
2200
2201
		// Or adding a new one?
2202
		if (!empty($_POST['new_base_dir']))
2203
		{
2204
			require_once($sourcedir . '/Subs-Attachments.php');
2205
			$_POST['new_base_dir'] = $smcFunc['htmlspecialchars']($_POST['new_base_dir'], ENT_QUOTES);
2206
2207
			$current_dir = $modSettings['currentAttachmentUploadDir'];
2208
2209
			if (!in_array($_POST['new_base_dir'], $modSettings['attachmentUploadDir']))
2210
			{
2211
				if (!automanage_attachments_create_directory($_POST['new_base_dir']))
2212
					$errors[] = $_POST['new_base_dir'] . ': ' . $txt['attach_dir_base_no_create'];
2213
			}
2214
2215
			$modSettings['currentAttachmentUploadDir'] = array_search($_POST['new_base_dir'], $modSettings['attachmentUploadDir']);
2216
			if (!in_array($_POST['new_base_dir'], $modSettings['attachment_basedirectories']))
2217
				$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $_POST['new_base_dir'];
2218
			ksort($modSettings['attachment_basedirectories']);
2219
2220
			$update = (array(
2221
				'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
2222
				'basedirectory_for_attachments' => $_POST['new_base_dir'],
2223
				'currentAttachmentUploadDir' => $current_dir,
2224
			));
2225
		}
2226
2227
		if (!empty($errors))
2228
			$_SESSION['errors']['base'] = $errors;
2229
2230
		if (!empty($update))
2231
			updateSettings($update);
2232
2233
		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
2234
	}
2235
2236
	if (isset($_SESSION['errors']))
2237
	{
2238
		if (is_array($_SESSION['errors']))
2239
		{
2240
			$errors = array();
2241 View Code Duplication
			if (!empty($_SESSION['errors']['dir']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2242
				foreach ($_SESSION['errors']['dir'] as $error)
2243
					$errors['dir'][] = $smcFunc['htmlspecialchars']($error, ENT_QUOTES);
2244
2245 View Code Duplication
			if (!empty($_SESSION['errors']['base']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2246
				foreach ($_SESSION['errors']['base'] as $error)
2247
					$errors['base'][] = $smcFunc['htmlspecialchars']($error, ENT_QUOTES);
2248
		}
2249
		unset($_SESSION['errors']);
2250
	}
2251
2252
	$listOptions = array(
2253
		'id' => 'attach_paths',
2254
		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2255
		'title' => $txt['attach_paths'],
2256
		'get_items' => array(
2257
			'function' => 'list_getAttachDirs',
2258
		),
2259
		'columns' => array(
2260
			'current_dir' => array(
2261
				'header' => array(
2262
					'value' => $txt['attach_current'],
2263
					'class' => 'centercol',
2264
				),
2265
				'data' => array(
2266
					'function' => function ($rowData)
2267
					{
2268
						return '<input type="radio" name="current_dir" value="' . $rowData['id'] . '"' . ($rowData['current'] ? ' checked' : '') . (!empty($rowData['disable_current']) ? ' disabled' : '') . ' class="input_radio">';
2269
					},
2270
					'style' => 'width: 10%;',
2271
					'class' => 'centercol',
2272
				),
2273
			),
2274
			'path' => array(
2275
				'header' => array(
2276
					'value' => $txt['attach_path'],
2277
				),
2278
				'data' => array(
2279
					'function' => function ($rowData)
2280
					{
2281
						return '<input type="hidden" name="dirs[' . $rowData['id'] . ']" value="' . $rowData['path'] . '"><input type="text" size="40" name="dirs[' . $rowData['id'] . ']" value="' . $rowData['path'] . '"' . (!empty($rowData['disable_base_dir']) ? ' disabled' : '') . ' class="input_text" style="width: 100%">';
2282
					},
2283
					'style' => 'width: 40%;',
2284
				),
2285
			),
2286
			'current_size' => array(
2287
				'header' => array(
2288
					'value' => $txt['attach_current_size'],
2289
				),
2290
				'data' => array(
2291
					'db' => 'current_size',
2292
					'style' => 'width: 15%;',
2293
				),
2294
			),
2295
			'num_files' => array(
2296
				'header' => array(
2297
					'value' => $txt['attach_num_files'],
2298
				),
2299
				'data' => array(
2300
					'db' => 'num_files',
2301
					'style' => 'width: 15%;',
2302
				),
2303
			),
2304
			'status' => array(
2305
				'header' => array(
2306
					'value' => $txt['attach_dir_status'],
2307
					'class' => 'centercol',
2308
				),
2309
				'data' => array(
2310
					'db' => 'status',
2311
					'style' => 'width: 25%;',
2312
					'class' => 'centercol',
2313
				),
2314
			),
2315
		),
2316
		'form' => array(
2317
			'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2318
		),
2319
		'additional_rows' => array(
2320
			array(
2321
				'position' => 'below_table_data',
2322
				'value' => '
2323
				<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
2324
				<input type="submit" name="save" value="' . $txt['save'] . '" class="button_submit">
2325
				<input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" class="button_submit">',
2326
			),
2327
			empty($errors['dir']) ? array(
2328
				'position' => 'top_of_list',
2329
				'value' => $txt['attach_dir_desc'],
2330
				'style' => 'padding: 5px 10px;',
2331
				'class' => 'windowbg2 smalltext'
2332
			) : array(
2333
				'position' => 'top_of_list',
2334
				'value' => $txt['attach_dir_save_problem'] . '<br>' . implode('<br>', $errors['dir']),
2335
				'style' => 'padding-left: 35px;',
2336
				'class' => 'noticebox',
2337
			),
2338
		),
2339
	);
2340
	require_once($sourcedir . '/Subs-List.php');
2341
	createList($listOptions);
2342
2343
	if (!empty($modSettings['attachment_basedirectories']))
2344
	{
2345
		$listOptions2 = array(
2346
			'id' => 'base_paths',
2347
			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2348
			'title' => $txt['attach_base_paths'],
2349
			'get_items' => array(
2350
				'function' => 'list_getBaseDirs',
2351
			),
2352
			'columns' => array(
2353
				'current_dir' => array(
2354
					'header' => array(
2355
						'value' => $txt['attach_current'],
2356
						'class' => 'centercol',
2357
					),
2358
					'data' => array(
2359
						'function' => function ($rowData)
2360
						{
2361
							return '<input type="radio" name="current_base_dir" value="' . $rowData['id'] . '"' . ($rowData['current'] ? ' checked' : '') . ' class="input_radio">';
2362
						},
2363
						'style' => 'width: 10%;',
2364
						'class' => 'centercol',
2365
					),
2366
				),
2367
				'path' => array(
2368
					'header' => array(
2369
						'value' => $txt['attach_path'],
2370
					),
2371
					'data' => array(
2372
						'db' => 'path',
2373
						'style' => 'width: 45%;',
2374
					),
2375
				),
2376
				'num_dirs' => array(
2377
					'header' => array(
2378
						'value' => $txt['attach_num_dirs'],
2379
					),
2380
					'data' => array(
2381
						'db' => 'num_dirs',
2382
						'style' => 'width: 15%;',
2383
					),
2384
				),
2385
				'status' => array(
2386
					'header' => array(
2387
						'value' => $txt['attach_dir_status'],
2388
					),
2389
					'data' => array(
2390
						'db' => 'status',
2391
						'style' => 'width: 15%;',
2392
						'class' => 'centercol',
2393
					),
2394
				),
2395
			),
2396
			'form' => array(
2397
				'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2398
			),
2399
			'additional_rows' => array(
2400
				array(
2401
					'position' => 'below_table_data',
2402
					'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '"><input type="submit" name="save2" value="' . $txt['save'] . '" class="button_submit">
2403
					<input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" class="button_submit">',
2404
				),
2405
				empty($errors['base']) ? array(
2406
					'position' => 'top_of_list',
2407
					'value' => $txt['attach_dir_base_desc'],
2408
					'style' => 'padding: 5px 10px;',
2409
					'class' => 'windowbg2 smalltext'
2410
				) : array(
2411
					'position' => 'top_of_list',
2412
					'value' => $txt['attach_dir_save_problem'] . '<br>' . implode('<br>', $errors['base']),
2413
					'style' => 'padding-left: 35px',
2414
					'class' => 'noticebox',
2415
				),
2416
			),
2417
		);
2418
		createList($listOptions2);
2419
	}
2420
2421
	// Fix up our template.
2422
	$context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths';
2423
	$context['page_title'] = $txt['attach_path_manage'];
2424
	$context['sub_template'] = 'attachment_paths';
2425
}
2426
2427
/**
2428
 * Prepare the actual attachment directories to be displayed in the list.
2429
 * @return array An array of information about the attachment directories
2430
 */
2431
function list_getAttachDirs()
2432
{
2433
	global $smcFunc, $modSettings, $context, $txt;
2434
2435
	$request = $smcFunc['db_query']('', '
2436
		SELECT id_folder, COUNT(id_attach) AS num_attach, SUM(size) AS size_attach
2437
		FROM {db_prefix}attachments
2438
		WHERE attachment_type != {int:type}
2439
		GROUP BY id_folder',
2440
		array(
2441
			'type' => 1,
2442
		)
2443
	);
2444
2445
	$expected_files = array();
2446
	$expected_size = array();
2447
	while ($row = $smcFunc['db_fetch_assoc']($request))
2448
	{
2449
		$expected_files[$row['id_folder']] = $row['num_attach'];
2450
		$expected_size[$row['id_folder']] = $row['size_attach'];
2451
	}
2452
	$smcFunc['db_free_result']($request);
2453
2454
	$attachdirs = array();
2455
	foreach ($modSettings['attachmentUploadDir'] as $id => $dir)
2456
	{
2457
		// If there aren't any attachments in this directory this won't exist.
2458
		if (!isset($expected_files[$id]))
2459
			$expected_files[$id] = 0;
2460
2461
		// Check if the directory is doing okay.
2462
		list ($status, $error, $files) = attachDirStatus($dir, $expected_files[$id]);
2463
2464
		// If it is one, let's show that it's a base directory.
2465
		$sub_dirs = 0;
2466
		$is_base_dir = false;
2467
		if (!empty($modSettings['attachment_basedirectories']))
2468
		{
2469
			$is_base_dir = in_array($dir, $modSettings['attachment_basedirectories']);
2470
2471
			// Count any sub-folders.
2472
			foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
2473
				if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
2474
				{
2475
					$expected_files[$id]++;
2476
					$sub_dirs++;
2477
				}
2478
		}
2479
2480
		$attachdirs[] = array(
2481
			'id' => $id,
2482
			'current' => $id == $modSettings['currentAttachmentUploadDir'],
2483
			'disable_current' => isset($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] > 1,
2484
			'disable_base_dir' =>  $is_base_dir && $sub_dirs > 0 && !empty($files) && empty($error) && empty($save_errors),
2485
			'path' => $dir,
2486
			'current_size' => !empty($expected_size[$id]) ? comma_format($expected_size[$id] / 1024, 0) : 0,
2487
			'num_files' => comma_format($expected_files[$id] - $sub_dirs, 0) . ($sub_dirs > 0 ? ' (' . $sub_dirs . ')' : ''),
2488
			'status' => ($is_base_dir ? $txt['attach_dir_basedir'] . '<br>' : '') . ($error ? '<div class="error">' : '') . sprintf($txt['attach_dir_' . $status], $context['session_id'], $context['session_var']) . ($error ? '</div>' : ''),
2489
		);
2490
	}
2491
2492
	// Just stick a new directory on at the bottom.
2493
	if (isset($_REQUEST['new_path']))
2494
		$attachdirs[] = array(
2495
			'id' => max(array_merge(array_keys($expected_files), array_keys($modSettings['attachmentUploadDir']))) + 1,
2496
			'current' => false,
2497
			'path' => '',
2498
			'current_size' => '',
2499
			'num_files' => '',
2500
			'status' => '',
2501
		);
2502
2503
	return $attachdirs;
2504
}
2505
2506
/**
2507
 * Prepare the base directories to be displayed in a list.
2508
 * @return void|array Returns nothing if there are no base directories, otherwise returns an array of info about the directories
2509
 */
2510
function list_getBaseDirs()
2511
{
2512
	global $modSettings, $txt;
2513
2514
	if (empty($modSettings['attachment_basedirectories']))
2515
		return;
2516
2517
	$basedirs = array();
2518
	// Get a list of the base directories.
2519
	foreach ($modSettings['attachment_basedirectories'] as $id => $dir)
2520
	{
2521
		// Loop through the attach directory array to count any sub-directories
2522
		$expected_dirs = 0;
2523 View Code Duplication
		foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2524
			if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
2525
				$expected_dirs++;
2526
2527
		if (!is_dir($dir))
2528
			$status = 'does_not_exist';
2529
		elseif (!is_writeable($dir))
2530
			$status = 'not_writable';
2531
		else
2532
			$status = 'ok';
2533
2534
		$basedirs[] = array(
2535
			'id' => $id,
2536
			'current' => $dir == $modSettings['basedirectory_for_attachments'],
2537
			'path' => $expected_dirs > 0 ? $dir : ('<input type="text" name="base_dir[' . $id . ']" value="' . $dir . '" size="40">'),
2538
			'num_dirs' => $expected_dirs,
2539
			'status' => $status == 'ok' ? $txt['attach_dir_ok'] : ('<span class="error">' . $txt['attach_dir_' . $status] . '</span>'),
2540
		);
2541
	}
2542
2543
	if (isset($_REQUEST['new_base_path']))
2544
		$basedirs[] = array(
2545
			'id' => '',
2546
			'current' => false,
2547
			'path' => '<input type="text" name="new_base_dir" value="" size="40">',
2548
			'num_dirs' => '',
2549
			'status' => '',
2550
		);
2551
2552
	return $basedirs;
2553
}
2554
2555
/**
2556
 * Checks the status of an attachment directory and returns an array
2557
 *  of the status key, if that status key signifies an error, and
2558
 *  the file count.
2559
 *
2560
 * @param string $dir The directory to check
2561
 * @param int $expected_files How many files should be in that directory
2562
 * @return array An array containing the status of the directory, whether the number of files was what we expected and how many were in the directory
2563
 */
2564
function attachDirStatus($dir, $expected_files)
2565
{
2566
	if (!is_dir($dir))
2567
		return array('does_not_exist', true, '');
2568
	elseif (!is_writable($dir))
2569
		return array('not_writable', true, '');
2570
2571
	// Everything is okay so far, start to scan through the directory.
2572
	$num_files = 0;
2573
	$dir_handle = dir($dir);
2574
	while ($file = $dir_handle->read())
2575
	{
2576
		// Now do we have a real file here?
2577
		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
2578
			continue;
2579
2580
		$num_files++;
2581
	}
2582
	$dir_handle->close();
2583
2584
	if ($num_files < $expected_files)
2585
		return array('files_missing', true, $num_files);
2586
	// Empty?
2587
	elseif ($expected_files == 0)
2588
		return array('unused', false, $num_files);
2589
	// All good!
2590
	else
2591
		return array('ok', false, $num_files);
2592
}
2593
2594
/**
2595
 * Maintance function to move attachments from one directory to another
2596
 */
2597
function TransferAttachments()
2598
{
2599
	global $modSettings, $smcFunc, $sourcedir, $txt, $boarddir;
2600
2601
	checkSession();
2602
2603
	$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
2604
	if (!empty($modSettings['attachment_basedirectories']))
2605
		$modSettings['attachment_basedirectories'] = smf_json_decode($modSettings['attachment_basedirectories'], true);
0 ignored issues
show
Documentation introduced by
$modSettings['attachment_basedirectories'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2606
	else
2607
		$modSettings['basedirectory_for_attachments'] = array();
2608
2609
	$_POST['from'] = (int) $_POST['from'];
2610
	$_POST['auto'] = !empty($_POST['auto']) ? (int) $_POST['auto'] : 0;
2611
	$_POST['to'] = (int) $_POST['to'];
2612
	$start = !empty($_POST['empty_it']) ? 0 : $modSettings['attachmentDirFileLimit'];
2613
	$_SESSION['checked'] = !empty($_POST['empty_it']) ? true : false;
2614
	$limit = 501;
2615
	$results = array();
2616
	$dir_files = 0;
2617
	$current_progress = 0;
2618
	$total_moved = 0;
2619
	$total_not_moved = 0;
2620
2621
	if (empty($_POST['from']) || (empty($_POST['auto']) && empty($_POST['to'])))
2622
		$results[] = $txt['attachment_transfer_no_dir'];
2623
2624
	if ($_POST['from'] == $_POST['to'])
2625
		$results[] = $txt['attachment_transfer_same_dir'];
2626
2627
	if (empty($results))
2628
	{
2629
		// Get the total file count for the progess bar.
2630
		$request = $smcFunc['db_query']('', '
2631
			SELECT COUNT(*)
2632
			FROM {db_prefix}attachments
2633
			WHERE id_folder = {int:folder_id}
2634
				AND attachment_type != {int:attachment_type}',
2635
			array(
2636
				'folder_id' => $_POST['from'],
2637
				'attachment_type' => 1,
2638
			)
2639
		);
2640
		list ($total_progress) = $smcFunc['db_fetch_row']($request);
2641
		$smcFunc['db_free_result']($request);
2642
		$total_progress -= $start;
2643
2644
		if ($total_progress < 1)
2645
			$results[] = $txt['attachment_transfer_no_find'];
2646
	}
2647
2648
	if (empty($results))
2649
	{
2650
		// Where are they going?
2651
		if (!empty($_POST['auto']))
2652
		{
2653
			require_once($sourcedir . '/Subs-Attachments.php');
2654
2655
			$modSettings['automanage_attachments'] = 1;
2656
			$modSettings['use_subdirectories_for_attachments'] = $_POST['auto'] == -1 ? 0 : 1;
2657
			$modSettings['basedirectory_for_attachments'] = $_POST['auto'] > 0 ? $modSettings['attachmentUploadDir'][$_POST['auto']] : $modSettings['basedirectory_for_attachments'];
2658
2659
			automanage_attachments_check_directory();
2660
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2661
		}
2662
		else
2663
			$new_dir = $_POST['to'];
2664
2665
		$modSettings['currentAttachmentUploadDir'] = $new_dir;
2666
2667
		$break = false;
2668
		while ($break == false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2669
		{
2670
			@set_time_limit(300);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2671
			if (function_exists('apache_reset_timeout'))
2672
				@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2673
2674
			// If limits are set, get the file count and size for the destination folder
2675
			if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])))
2676
			{
2677
				$request = $smcFunc['db_query']('', '
2678
					SELECT COUNT(*), SUM(size)
2679
					FROM {db_prefix}attachments
2680
					WHERE id_folder = {int:folder_id}
2681
						AND attachment_type != {int:attachment_type}',
2682
					array(
2683
						'folder_id' => $new_dir,
2684
						'attachment_type' => 1,
2685
					)
2686
				);
2687
				list ($dir_files, $dir_size) = $smcFunc['db_fetch_row']($request);
2688
				$smcFunc['db_free_result']($request);
2689
			}
2690
2691
			// Find some attachments to move
2692
			$request = $smcFunc['db_query']('', '
2693
				SELECT id_attach, filename, id_folder, file_hash, size
2694
				FROM {db_prefix}attachments
2695
				WHERE id_folder = {int:folder}
2696
					AND attachment_type != {int:attachment_type}
2697
				LIMIT {int:start}, {int:limit}',
2698
				array(
2699
					'folder' => $_POST['from'],
2700
					'attachment_type' => 1,
2701
					'start' => $start,
2702
					'limit' => $limit,
2703
				)
2704
			);
2705
2706
			if ($smcFunc['db_num_rows']($request) === 0)
2707
			{
2708
				if (empty($current_progress))
2709
					$results[] = $txt['attachment_transfer_no_find'];
2710
				break;
2711
			}
2712
2713
			if ($smcFunc['db_num_rows']($request) < $limit)
2714
				$break = true;
2715
2716
			// Move them
2717
			$moved = array();
2718
			while ($row = $smcFunc['db_fetch_assoc']($request))
2719
			{
2720
				$source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
2721
				$dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source);
2722
2723
				// Size and file count check
2724
				if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
2725
				{
2726
					$dir_files++;
2727
					$dir_size += !empty($row['size']) ? $row['size'] : filesize($source);
0 ignored issues
show
Bug introduced by
The variable $dir_size does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2728
2729
					// If we've reached a limit. Do something.
2730
					if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || (!empty($modSettings['attachmentDirFileLimit']) && $dir_files >  $modSettings['attachmentDirFileLimit']))
2731
					{
2732
						if (!empty($_POST['auto']))
2733
						{
2734
							// Since we're in auto mode. Create a new folder and reset the counters.
2735
							automanage_attachments_by_space();
2736
2737
							$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2738
							if (!empty($total_not_moved))
2739
								$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2740
2741
							$dir_files = 0;
2742
							$total_moved = 0;
2743
							$total_not_moved = 0;
2744
2745
							$break = false;
2746
							break;
2747
						}
2748
						else
2749
						{
2750
							// Hmm, not in auto. Time to bail out then...
2751
							$results[] = $txt['attachment_transfer_no_room'];
2752
							$break = true;
2753
							break;
2754
						}
2755
					}
2756
				}
2757
2758
				if (@rename($source, $dest))
2759
				{
2760
					$total_moved++;
2761
					$current_progress++;
2762
					$moved[] = $row['id_attach'];
2763
				}
2764
				else
2765
					$total_not_moved++;
2766
			}
2767
			$smcFunc['db_free_result']($request);
2768
2769
			if (!empty($moved))
2770
			{
2771
				// Update the database
2772
				$smcFunc['db_query']('', '
2773
					UPDATE {db_prefix}attachments
2774
					SET id_folder = {int:new}
2775
					WHERE id_attach IN ({array_int:attachments})',
2776
					array(
2777
						'attachments' => $moved,
2778
						'new' => $new_dir,
2779
					)
2780
				);
2781
			}
2782
2783
			$moved = array();
0 ignored issues
show
Unused Code introduced by
$moved is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2784
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2785
2786
			// Create the progress bar.
2787
			if (!$break)
2788
			{
2789
				$percent_done = min(round($current_progress / $total_progress * 100, 0), 100);
0 ignored issues
show
Bug introduced by
The variable $total_progress does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2790
				$prog_bar = '
2791
					<div class="progress_bar">
2792
						<div class="full_bar">' . $percent_done . '%</div>
2793
						<div class="green_percent" style="width: ' . $percent_done . '%;">&nbsp;</div>
2794
					</div>';
2795
				// Write it to a file so it can be displayed
2796
				$fp = fopen($boarddir . '/progress.php', "w");
2797
				fwrite($fp, $prog_bar);
2798
				fclose($fp);
2799
				usleep(500000);
2800
			}
2801
		}
2802
2803
		$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2804
		if (!empty($total_not_moved))
2805
			$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2806
	}
2807
2808
	$_SESSION['results'] = $results;
2809
	if (file_exists($boarddir . '/progress.php'))
2810
		unlink($boarddir . '/progress.php');
2811
2812
	redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer');
2813
}
2814
2815
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...