Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

ManageAttachments.php ➔ ManageAvatarSettings()   F

Complexity

Conditions 13
Paths 864

Size

Total Lines 120
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 80
nc 864
nop 1
dl 0
loc 120
rs 2.4575
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
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2017 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
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.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|array>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
86
 */
87
88
function ManageAttachmentSettings($return_config = false)
89
{
90
	global $smcFunc, $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]))
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']))
209
			{
210
				if (!is_array($modSettings['attachment_basedirectories']))
211
					$modSettings['attachment_basedirectories'] = $smcFunc['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' => $smcFunc['json_encode']($modSettings['attachment_basedirectories']),
231
						'currentAttachmentUploadDir' => $currentAttachmentUploadDir,
232
					));
233
234
					$_POST['use_subdirectories_for_attachments'] = 1;
235
					$_POST['attachmentUploadDir'] = $smcFunc['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
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string[]|array<*,s...ray<string|array>>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
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 = $smcFunc['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']) ? $smcFunc['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...
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
804
 */
805
function RemoveAttachmentByAge()
806
{
807
	global $smcFunc;
808
809
	checkSession('post', 'admin');
810
811
	// @todo Ignore messages in topics that are stickied?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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']))
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']))
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))
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
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
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!
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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))
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.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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))
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))
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)
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))
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))
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)
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))
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
1528
			// returns an array of ints of id_attach's that should not be deleted
1529
			call_integration_hook('integrate_repair_attachments_nomsg', array(&$ignore_ids, $_GET['substep'], $_GET['substep'] + 500));
1530
1531
			$result = $smcFunc['db_query']('', '
1532
				SELECT a.id_attach, a.id_folder, a.filename, a.file_hash
1533
				FROM {db_prefix}attachments AS a
1534
					LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1535
				WHERE a.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1536
					AND a.id_member = {int:no_member}
1537
					AND (a.id_msg = {int:no_msg} OR m.id_msg IS NULL)
1538
					AND a.id_attach NOT IN ({array_int:ignore_ids})
1539
					AND a.attachment_type IN ({array_int:attach_thumb})',
1540
				array(
1541
					'no_member' => 0,
1542
					'no_msg' => 0,
1543
					'substep' => $_GET['substep'],
1544
					'ignore_ids' => $ignore_ids,
1545
					'attach_thumb' => array(0,3),
1546
				)
1547
			);
1548
1549
			while ($row = $smcFunc['db_fetch_assoc']($result))
1550
			{
1551
				$to_remove[] = $row['id_attach'];
1552
				$context['repair_errors']['attachment_no_msg']++;
1553
1554
				// If we are repairing remove the file from disk now.
1555 View Code Duplication
				if ($fix_errors && in_array('attachment_no_msg', $to_fix))
1556
				{
1557
					$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1558
					@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...
1559
				}
1560
			}
1561
			if ($smcFunc['db_num_rows']($result) != 0)
1562
				$to_fix[] = 'attachment_no_msg';
1563
			$smcFunc['db_free_result']($result);
1564
1565
			// Do we need to delete what we have?
1566 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('attachment_no_msg', $to_fix))
1567
				$smcFunc['db_query']('', '
1568
					DELETE FROM {db_prefix}attachments
1569
					WHERE id_attach IN ({array_int:to_remove})
1570
						AND id_member = {int:no_member}
1571
						AND attachment_type IN ({array_int:attach_thumb})',
1572
					array(
1573
						'to_remove' => $to_remove,
1574
						'no_member' => 0,
1575
						'attach_thumb' => array(0,3),
1576
					)
1577
				);
1578
1579
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1580
		}
1581
1582
		$_GET['step'] = 5;
1583
		$_GET['substep'] = 0;
1584
		pauseAttachmentMaintenance($to_fix);
1585
	}
1586
1587
	// What about files who are not recorded in the database?
1588
	if ($_GET['step'] <= 5)
1589
	{
1590
		$attach_dirs = $modSettings['attachmentUploadDir'];
1591
1592
		$current_check = 0;
1593
		$max_checks = 500;
1594
		$files_checked = empty($_GET['substep']) ? 0 : $_GET['substep'];
1595
		foreach ($attach_dirs as $attach_dir)
1596
		{
1597
			if ($dir = @opendir($attach_dir))
1598
			{
1599
				while ($file = readdir($dir))
1600
				{
1601
					if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
1602
						continue;
1603
1604
					if ($files_checked <= $current_check)
1605
					{
1606
						// Temporary file, get rid of it!
1607
						if (strpos($file, 'post_tmp_') !== false)
1608
						{
1609
							// Temp file is more than 5 hours old!
1610 View Code Duplication
							if (filemtime($attach_dir . '/' . $file) < time() - 18000)
1611
								@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...
1612
						}
1613
						// That should be an attachment, let's check if we have it in the database
1614
						elseif (strpos($file, '_') !== false)
1615
						{
1616
							$attachID = (int) substr($file, 0, strpos($file, '_'));
1617
							if (!empty($attachID))
1618
							{
1619
								$request = $smcFunc['db_query']('', '
1620
									SELECT  id_attach
1621
									FROM {db_prefix}attachments
1622
									WHERE id_attach = {int:attachment_id}
1623
									LIMIT 1',
1624
									array(
1625
										'attachment_id' => $attachID,
1626
									)
1627
								);
1628
								if ($smcFunc['db_num_rows']($request) == 0)
1629
								{
1630 View Code Duplication
									if ($fix_errors && in_array('files_without_attachment', $to_fix))
1631
									{
1632
										@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...
1633
									}
1634
									else
1635
									{
1636
										$context['repair_errors']['files_without_attachment']++;
1637
										$to_fix[] = 'files_without_attachment';
1638
									}
1639
								}
1640
								$smcFunc['db_free_result']($request);
1641
							}
1642
						}
1643 View Code Duplication
						else
1644
						{
1645
							if ($fix_errors && in_array('files_without_attachment', $to_fix))
1646
							{
1647
								@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...
1648
							}
1649
							else
1650
							{
1651
								$context['repair_errors']['files_without_attachment']++;
1652
								$to_fix[] = 'files_without_attachment';
1653
							}
1654
						}
1655
					}
1656
					$current_check++;
1657
					$_GET['substep'] = $current_check;
1658
					if ($current_check - $files_checked >= $max_checks)
1659
						pauseAttachmentMaintenance($to_fix);
1660
				}
1661
				closedir($dir);
1662
			}
1663
		}
1664
1665
		$_GET['step'] = 5;
1666
		$_GET['substep'] = 0;
1667
		pauseAttachmentMaintenance($to_fix);
1668
	}
1669
1670
	// Got here we must be doing well - just the template! :D
1671
	$context['page_title'] = $txt['repair_attachments'];
1672
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1673
	$context['sub_template'] = 'attachment_repair';
1674
1675
	// What stage are we at?
1676
	$context['completed'] = $fix_errors ? true : false;
1677
	$context['errors_found'] = !empty($to_fix) ? true : false;
1678
1679
}
1680
1681
/**
1682
 * Function called in-between each round of attachments and avatar repairs.
1683
 * Called by repairAttachments().
1684
 * If repairAttachments() has more steps added, this function needs updated!
1685
 *
1686
 * @param array $to_fix IDs of attachments to fix
1687
 * @param int $max_substep The maximum substep to reach before pausing
1688
 */
1689
function pauseAttachmentMaintenance($to_fix, $max_substep = 0)
1690
{
1691
	global $context, $txt, $time_start;
1692
1693
	// Try get more time...
1694
	@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...
1695
	if (function_exists('apache_reset_timeout'))
1696
		@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...
1697
1698
	// Have we already used our maximum time?
1699
	if (time() - array_sum(explode(' ', $time_start)) < 3 || $context['starting_substep'] == $_GET['substep'])
1700
		return;
1701
1702
	$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'];
1703
	$context['page_title'] = $txt['not_done_title'];
1704
	$context['continue_post_data'] = '';
1705
	$context['continue_countdown'] = '2';
1706
	$context['sub_template'] = 'not_done';
1707
1708
	// Specific stuff to not break this template!
1709
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1710
1711
	// Change these two if more steps are added!
1712
	if (empty($max_substep))
1713
		$context['continue_percent'] = round(($_GET['step'] * 100) / 25);
1714 View Code Duplication
	else
1715
		$context['continue_percent'] = round(($_GET['step'] * 100 + ($_GET['substep'] * 100) / $max_substep) / 25);
1716
1717
	// Never more than 100%!
1718
	$context['continue_percent'] = min($context['continue_percent'], 100);
1719
1720
	$_SESSION['attachments_to_fix'] = $to_fix;
1721
	$_SESSION['attachments_to_fix2'] = $context['repair_errors'];
1722
1723
	obExit();
1724
}
1725
1726
/**
1727
 * Called from a mouse click, works out what we want to do with attachments and actions it.
1728
 */
1729
function ApproveAttach()
1730
{
1731
	global $smcFunc;
1732
1733
	// Security is our primary concern...
1734
	checkSession('get');
1735
1736
	// If it approve or delete?
1737
	$is_approve = !isset($_GET['sa']) || $_GET['sa'] != 'reject' ? true : false;
1738
1739
	$attachments = array();
1740
	// If we are approving all ID's in a message , get the ID's.
1741
	if ($_GET['sa'] == 'all' && !empty($_GET['mid']))
1742
	{
1743
		$id_msg = (int) $_GET['mid'];
1744
1745
		$request = $smcFunc['db_query']('', '
1746
			SELECT id_attach
1747
			FROM {db_prefix}attachments
1748
			WHERE id_msg = {int:id_msg}
1749
				AND approved = {int:is_approved}
1750
				AND attachment_type = {int:attachment_type}',
1751
			array(
1752
				'id_msg' => $id_msg,
1753
				'is_approved' => 0,
1754
				'attachment_type' => 0,
1755
			)
1756
		);
1757
		while ($row = $smcFunc['db_fetch_assoc']($request))
1758
			$attachments[] = $row['id_attach'];
1759
		$smcFunc['db_free_result']($request);
1760
	}
1761
	elseif (!empty($_GET['aid']))
1762
		$attachments[] = (int) $_GET['aid'];
1763
1764
	if (empty($attachments))
1765
		fatal_lang_error('no_access', false);
1766
1767
	// Now we have some ID's cleaned and ready to approve, but first - let's check we have permission!
1768
	$allowed_boards = boardsAllowedTo('approve_posts');
1769
1770
	// Validate the attachments exist and are the right approval state.
1771
	$request = $smcFunc['db_query']('', '
1772
		SELECT a.id_attach, m.id_board, m.id_msg, m.id_topic
1773
		FROM {db_prefix}attachments AS a
1774
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1775
		WHERE a.id_attach IN ({array_int:attachments})
1776
			AND a.attachment_type = {int:attachment_type}
1777
			AND a.approved = {int:is_approved}',
1778
		array(
1779
			'attachments' => $attachments,
1780
			'attachment_type' => 0,
1781
			'is_approved' => 0,
1782
		)
1783
	);
1784
	$attachments = array();
1785
	while ($row = $smcFunc['db_fetch_assoc']($request))
1786
	{
1787
		// We can only add it if we can approve in this board!
1788
		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...
1789
		{
1790
			$attachments[] = $row['id_attach'];
1791
1792
			// Also come up with the redirection URL.
1793
			$redirect = 'topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'];
1794
		}
1795
	}
1796
	$smcFunc['db_free_result']($request);
1797
1798
	if (empty($attachments))
1799
		fatal_lang_error('no_access', false);
1800
1801
	// Finally, we are there. Follow through!
1802
	if ($is_approve)
1803
	{
1804
		// Checked and deemed worthy.
1805
		ApproveAttachments($attachments);
1806
	}
1807
	else
1808
		removeAttachments(array('id_attach' => $attachments, 'do_logging' => true));
1809
1810
	// Return to the topic....
1811
	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...
1812
}
1813
1814
/**
1815
 * Approve an attachment, or maybe even more - no permission check!
1816
 *
1817
 * @param array $attachments The IDs of the attachments to approve
1818
 * @return void|int Returns 0 if the operation failed, otherwise returns nothing
1819
 */
1820
function ApproveAttachments($attachments)
1821
{
1822
	global $smcFunc;
1823
1824
	if (empty($attachments))
1825
		return 0;
1826
1827
	// For safety, check for thumbnails...
1828
	$request = $smcFunc['db_query']('', '
1829
		SELECT
1830
			a.id_attach, a.id_member, COALESCE(thumb.id_attach, 0) AS id_thumb
1831
		FROM {db_prefix}attachments AS a
1832
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1833
		WHERE a.id_attach IN ({array_int:attachments})
1834
			AND a.attachment_type = {int:attachment_type}',
1835
		array(
1836
			'attachments' => $attachments,
1837
			'attachment_type' => 0,
1838
		)
1839
	);
1840
	$attachments = array();
1841
	while ($row = $smcFunc['db_fetch_assoc']($request))
1842
	{
1843
		// Update the thumbnail too...
1844
		if (!empty($row['id_thumb']))
1845
			$attachments[] = $row['id_thumb'];
1846
1847
		$attachments[] = $row['id_attach'];
1848
	}
1849
	$smcFunc['db_free_result']($request);
1850
1851
	if (empty($attachments))
1852
		return 0;
1853
1854
	// Approving an attachment is not hard - it's easy.
1855
	$smcFunc['db_query']('', '
1856
		UPDATE {db_prefix}attachments
1857
		SET approved = {int:is_approved}
1858
		WHERE id_attach IN ({array_int:attachments})',
1859
		array(
1860
			'attachments' => $attachments,
1861
			'is_approved' => 1,
1862
		)
1863
	);
1864
1865
	// In order to log the attachments, we really need their message and filename
1866
	$request = $smcFunc['db_query']('', '
1867
		SELECT m.id_msg, a.filename
1868
		FROM {db_prefix}attachments AS a
1869
			INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
1870
		WHERE a.id_attach IN ({array_int:attachments})
1871
			AND a.attachment_type = {int:attachment_type}',
1872
		array(
1873
			'attachments' => $attachments,
1874
			'attachment_type' => 0,
1875
		)
1876
	);
1877
1878 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
1879
		logAction(
1880
			'approve_attach',
1881
			array(
1882
				'message' => $row['id_msg'],
1883
				'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
1884
			)
1885
		);
1886
	$smcFunc['db_free_result']($request);
1887
1888
	// Remove from the approval queue.
1889
	$smcFunc['db_query']('', '
1890
		DELETE FROM {db_prefix}approval_queue
1891
		WHERE id_attach IN ({array_int:attachments})',
1892
		array(
1893
			'attachments' => $attachments,
1894
		)
1895
	);
1896
1897
	call_integration_hook('integrate_approve_attachments', array($attachments));
1898
}
1899
1900
/**
1901
 * This function lists and allows updating of multiple attachments paths.
1902
 */
1903
function ManageAttachmentPaths()
1904
{
1905
	global $modSettings, $scripturl, $context, $txt, $sourcedir, $boarddir, $smcFunc, $settings;
1906
1907
	// Since this needs to be done eventually.
1908
	if (!isset($modSettings['attachment_basedirectories']))
1909
		$modSettings['attachment_basedirectories'] = array();
1910
1911
	elseif (!is_array($modSettings['attachment_basedirectories']))
1912
		$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
1913
1914
	$errors = array();
1915
1916
	// Saving?
1917
	if (isset($_REQUEST['save']))
1918
	{
1919
		checkSession();
1920
1921
		$_POST['current_dir'] = (int) $_POST['current_dir'];
1922
		$new_dirs = array();
1923
		foreach ($_POST['dirs'] as $id => $path)
1924
		{
1925
			$error = '';
1926
			$id = (int) $id;
1927
			if ($id < 1)
1928
				continue;
1929
1930
			// Sorry, these dirs are NOT valid
1931
			$invalid_dirs = array($boarddir, $settings['default_theme_dir'], $sourcedir);
1932
			if (in_array($path, $invalid_dirs))
1933
			{
1934
				$errors[] = $path . ': ' . $txt['attach_dir_invalid'];
1935
				continue;
1936
			}
1937
1938
			// Hmm, a new path maybe?
1939
			// Don't allow empty paths
1940
			if (!array_key_exists($id, $modSettings['attachmentUploadDir']) && !empty($path))
1941
			{
1942
				// or is it?
1943
				if (in_array($path, $modSettings['attachmentUploadDir']) || in_array($boarddir . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
1944
				{
1945
						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
1946
						continue;
1947
				}
1948
				elseif (empty($path))
1949
				{
1950
					// Ignore this and set $id to one less
1951
					continue;
1952
				}
1953
1954
				// OK, so let's try to create it then.
1955
				require_once($sourcedir . '/Subs-Attachments.php');
1956
				if (automanage_attachments_create_directory($path))
1957
					$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
1958
				else
1959
					$errors[] = $path . ': ' . $txt[$context['dir_creation_error']];
1960
			}
1961
1962
			// Changing a directory name?
1963
			if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $path != $modSettings['attachmentUploadDir'][$id])
1964
			{
1965
				if ($path != $modSettings['attachmentUploadDir'][$id] && !is_dir($path))
1966
				{
1967
					if (!@rename($modSettings['attachmentUploadDir'][$id], $path))
1968
					{
1969
						$errors[] = $path . ': ' . $txt['attach_dir_no_rename'];
1970
						$path = $modSettings['attachmentUploadDir'][$id];
1971
					}
1972
				}
1973
				else
1974
				{
1975
					$errors[] = $path . ': ' . $txt['attach_dir_exists_msg'];
1976
					$path = $modSettings['attachmentUploadDir'][$id];
1977
				}
1978
1979
				// Update the base directory path
1980
				if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
1981
				{
1982
					$base = $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id] ? $path : $modSettings['basedirectory_for_attachments'];
1983
1984
					$modSettings['attachment_basedirectories'][$id] = $path;
1985
					updateSettings(array(
1986
						'attachment_basedirectories' => $smcFunc['json_encode']($modSettings['attachment_basedirectories']),
1987
						'basedirectory_for_attachments' => $base,
1988
					));
1989
					$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
1990
				}
1991
			}
1992
1993
			if (empty($path))
1994
			{
1995
				$path = $modSettings['attachmentUploadDir'][$id];
1996
1997
				// It's not a good idea to delete the current directory.
1998
				if ($id == (!empty($_POST['current_dir']) ? $_POST['current_dir'] : $modSettings['currentAttachmentUploadDir']))
1999
					$errors[] = $path . ': ' . $txt['attach_dir_is_current'];
2000
				// Or the current base directory
2001
				elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
2002
					$errors[] = $path . ': ' . $txt['attach_dir_is_current_bd'];
2003
				else
2004
				{
2005
					// Let's not try to delete a path with files in it.
2006
					$request = $smcFunc['db_query']('', '
2007
						SELECT COUNT(id_attach) AS num_attach
2008
						FROM {db_prefix}attachments
2009
						WHERE id_folder = {int:id_folder}',
2010
						array(
2011
							'id_folder' => (int) $id,
2012
						)
2013
					);
2014
2015
					list ($num_attach) = $smcFunc['db_fetch_row']($request);
2016
					$smcFunc['db_free_result']($request);
2017
2018
					// A check to see if it's a used base dir.
2019
					if (!empty($modSettings['attachment_basedirectories']))
2020
					{
2021
						// Count any sub-folders.
2022 View Code Duplication
						foreach ($modSettings['attachmentUploadDir'] as $sub)
2023
							if (strpos($sub, $path . DIRECTORY_SEPARATOR) !== false)
2024
								$num_attach++;
2025
					}
2026
2027
					// It's safe to delete. So try to delete the folder also
2028
					if ($num_attach == 0)
2029
					{
2030
						if (is_dir($path))
2031
							$doit = true;
2032
						elseif (is_dir($boarddir . DIRECTORY_SEPARATOR . $path))
2033
						{
2034
							$doit = true;
2035
							$path = $boarddir . DIRECTORY_SEPARATOR . $path;
2036
						}
2037
2038
						if (isset($doit) && realpath($path) != realpath($boarddir))
2039
						{
2040
							unlink($path . '/.htaccess');
2041
							unlink($path . '/index.php');
2042
							if (!@rmdir($path))
2043
								$error = $path . ': ' . $txt['attach_dir_no_delete'];
2044
						}
2045
2046
						// Remove it from the base directory list.
2047
						if (empty($error) && !empty($modSettings['attachment_basedirectories']))
2048
						{
2049
							unset($modSettings['attachment_basedirectories'][$id]);
2050
							updateSettings(array('attachment_basedirectories' => $smcFunc['json_encode']($modSettings['attachment_basedirectories'])));
2051
							$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
2052
						}
2053
					}
2054
					else
2055
						$error = $path . ': ' . $txt['attach_dir_no_remove'];
2056
2057
					if (empty($error))
2058
						continue;
2059
					else
2060
						$errors[] = $error;
2061
				}
2062
			}
2063
2064
			$new_dirs[$id] = $path;
2065
		}
2066
2067
		// We need to make sure the current directory is right.
2068
		if (empty($_POST['current_dir']) && !empty($modSettings['currentAttachmentUploadDir']))
2069
			$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2070
2071
		// Find the current directory if there's no value carried,
2072
		if (empty($_POST['current_dir']) || empty($new_dirs[$_POST['current_dir']]))
2073
		{
2074
			if (array_key_exists($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
2075
				$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2076
			else
2077
				$_POST['current_dir'] = max(array_keys($modSettings['attachmentUploadDir']));
2078
		}
2079
2080
		// If the user wishes to go back, update the last_dir array
2081
		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])))
2082
		{
2083
			if (!is_array($modSettings['last_attachments_directory']))
2084
				$modSettings['last_attachments_directory'] = $smcFunc['json_decode']($modSettings['last_attachments_directory'], true);
2085
			$num = substr(strrchr($modSettings['attachmentUploadDir'][$_POST['current_dir']], '_'), 1);
2086
2087
			if (is_numeric($num))
2088
			{
2089
				// Need to find the base folder.
2090
				$bid = -1;
2091
				$use_subdirectories_for_attachments = 0;
2092
				if (!empty($modSettings['attachment_basedirectories']))
2093
					foreach ($modSettings['attachment_basedirectories'] as $bid => $base)
2094 View Code Duplication
						if (strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $base . DIRECTORY_SEPARATOR) !== false)
2095
						{
2096
							$use_subdirectories_for_attachments = 1;
2097
							break;
2098
						}
2099
2100 View Code Duplication
				if ($use_subdirectories_for_attachments == 0 && strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $boarddir . DIRECTORY_SEPARATOR) !== false)
2101
					$bid = 0;
2102
2103
				$modSettings['last_attachments_directory'][$bid] = (int) $num;
2104
				$modSettings['basedirectory_for_attachments'] = !empty($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
2105
				$modSettings['use_subdirectories_for_attachments'] = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['use_subdirectories_for_attachments'] : 0;
2106
				updateSettings(array(
2107
					'last_attachments_directory' => $smcFunc['json_encode']($modSettings['last_attachments_directory']),
2108
					'basedirectory_for_attachments' => $bid == 0 ? $modSettings['basedirectory_for_attachments'] : $modSettings['attachment_basedirectories'][$bid],
2109
					'use_subdirectories_for_attachments' => $use_subdirectories_for_attachments,
2110
				));
2111
			}
2112
		}
2113
2114
		// Going back to just one path?
2115
		if (count($new_dirs) == 1)
2116
		{
2117
			// We might need to reset the paths. This loop will just loop through once.
2118
			foreach ($new_dirs as $id => $dir)
2119
			{
2120
				if ($id != 1)
2121
					$smcFunc['db_query']('', '
2122
						UPDATE {db_prefix}attachments
2123
						SET id_folder = {int:default_folder}
2124
						WHERE id_folder = {int:current_folder}',
2125
						array(
2126
							'default_folder' => 1,
2127
							'current_folder' => $id,
2128
						)
2129
					);
2130
2131
				$update = array(
2132
					'currentAttachmentUploadDir' => 1,
2133
					'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $dir)),
2134
				);
2135
			}
2136
		}
2137
		else
2138
		{
2139
			// Save it to the database.
2140
			$update = array(
2141
				'currentAttachmentUploadDir' => $_POST['current_dir'],
2142
				'attachmentUploadDir' => $smcFunc['json_encode']($new_dirs),
2143
			);
2144
		}
2145
2146
		if (!empty($update))
2147
			updateSettings($update);
2148
2149
		if (!empty($errors))
2150
			$_SESSION['errors']['dir'] = $errors;
2151
2152
		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
2153
	}
2154
2155
	// Saving a base directory?
2156
	if (isset($_REQUEST['save2']))
2157
	{
2158
		checkSession();
2159
2160
		// Changing the current base directory?
2161
		$_POST['current_base_dir'] = isset($_POST['current_base_dir']) ? (int) $_POST['current_base_dir'] : 1;
2162
		if (empty($_POST['new_base_dir']) && !empty($_POST['current_base_dir']))
2163
		{
2164 View Code Duplication
			if ($modSettings['basedirectory_for_attachments'] != $modSettings['attachmentUploadDir'][$_POST['current_base_dir']])
2165
				$update = (array(
2166
					'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2167
				));
2168
		}
2169
2170
		if (isset($_POST['base_dir']))
2171
		{
2172
			foreach ($_POST['base_dir'] as $id => $dir)
2173
			{
2174
				if (!empty($dir) && $dir != $modSettings['attachmentUploadDir'][$id])
2175
				{
2176
					if (@rename($modSettings['attachmentUploadDir'][$id], $dir))
2177
					{
2178
						$modSettings['attachmentUploadDir'][$id] = $dir;
2179
						$modSettings['attachment_basedirectories'][$id] = $dir;
2180
						$update = (array(
2181
							'attachmentUploadDir' => $smcFunc['json_encode']($modSettings['attachmentUploadDir']),
2182
							'attachment_basedirectories' => $smcFunc['json_encode']($modSettings['attachment_basedirectories']),
2183
							'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2184
						));
2185
					}
2186
				}
2187
2188
				if (empty($dir))
2189
				{
2190
					if ($id == $_POST['current_base_dir'])
2191
					{
2192
						$errors[] = $modSettings['attachmentUploadDir'][$id] . ': ' . $txt['attach_dir_is_current'];
2193
						continue;
2194
					}
2195
2196
					unset($modSettings['attachment_basedirectories'][$id]);
2197
					$update = (array(
2198
						'attachment_basedirectories' => $smcFunc['json_encode']($modSettings['attachment_basedirectories']),
2199
						'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$_POST['current_base_dir']],
2200
					));
2201
				}
2202
			}
2203
		}
2204
2205
		// Or adding a new one?
2206
		if (!empty($_POST['new_base_dir']))
2207
		{
2208
			require_once($sourcedir . '/Subs-Attachments.php');
2209
			$_POST['new_base_dir'] = $smcFunc['htmlspecialchars']($_POST['new_base_dir'], ENT_QUOTES);
2210
2211
			$current_dir = $modSettings['currentAttachmentUploadDir'];
2212
2213
			if (!in_array($_POST['new_base_dir'], $modSettings['attachmentUploadDir']))
2214
			{
2215
				if (!automanage_attachments_create_directory($_POST['new_base_dir']))
2216
					$errors[] = $_POST['new_base_dir'] . ': ' . $txt['attach_dir_base_no_create'];
2217
			}
2218
2219
			$modSettings['currentAttachmentUploadDir'] = array_search($_POST['new_base_dir'], $modSettings['attachmentUploadDir']);
2220
			if (!in_array($_POST['new_base_dir'], $modSettings['attachment_basedirectories']))
2221
				$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $_POST['new_base_dir'];
2222
			ksort($modSettings['attachment_basedirectories']);
2223
2224
			$update = (array(
2225
				'attachment_basedirectories' => $smcFunc['json_encode']($modSettings['attachment_basedirectories']),
2226
				'basedirectory_for_attachments' => $_POST['new_base_dir'],
2227
				'currentAttachmentUploadDir' => $current_dir,
2228
			));
2229
		}
2230
2231
		if (!empty($errors))
2232
			$_SESSION['errors']['base'] = $errors;
2233
2234
		if (!empty($update))
2235
			updateSettings($update);
2236
2237
		redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
2238
	}
2239
2240
	if (isset($_SESSION['errors']))
2241
	{
2242
		if (is_array($_SESSION['errors']))
2243
		{
2244
			$errors = array();
2245 View Code Duplication
			if (!empty($_SESSION['errors']['dir']))
2246
				foreach ($_SESSION['errors']['dir'] as $error)
2247
					$errors['dir'][] = $smcFunc['htmlspecialchars']($error, ENT_QUOTES);
2248
2249 View Code Duplication
			if (!empty($_SESSION['errors']['base']))
2250
				foreach ($_SESSION['errors']['base'] as $error)
2251
					$errors['base'][] = $smcFunc['htmlspecialchars']($error, ENT_QUOTES);
2252
		}
2253
		unset($_SESSION['errors']);
2254
	}
2255
2256
	$listOptions = array(
2257
		'id' => 'attach_paths',
2258
		'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2259
		'title' => $txt['attach_paths'],
2260
		'get_items' => array(
2261
			'function' => 'list_getAttachDirs',
2262
		),
2263
		'columns' => array(
2264
			'current_dir' => array(
2265
				'header' => array(
2266
					'value' => $txt['attach_current'],
2267
					'class' => 'centercol',
2268
				),
2269
				'data' => array(
2270
					'function' => function($rowData)
2271
					{
2272
						return '<input type="radio" name="current_dir" value="' . $rowData['id'] . '"' . ($rowData['current'] ? ' checked' : '') . (!empty($rowData['disable_current']) ? ' disabled' : '') . ' class="input_radio">';
2273
					},
2274
					'style' => 'width: 10%;',
2275
					'class' => 'centercol',
2276
				),
2277
			),
2278
			'path' => array(
2279
				'header' => array(
2280
					'value' => $txt['attach_path'],
2281
				),
2282
				'data' => array(
2283
					'function' => function($rowData)
2284
					{
2285
						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%">';
2286
					},
2287
					'style' => 'width: 40%;',
2288
				),
2289
			),
2290
			'current_size' => array(
2291
				'header' => array(
2292
					'value' => $txt['attach_current_size'],
2293
				),
2294
				'data' => array(
2295
					'db' => 'current_size',
2296
					'style' => 'width: 15%;',
2297
				),
2298
			),
2299
			'num_files' => array(
2300
				'header' => array(
2301
					'value' => $txt['attach_num_files'],
2302
				),
2303
				'data' => array(
2304
					'db' => 'num_files',
2305
					'style' => 'width: 15%;',
2306
				),
2307
			),
2308
			'status' => array(
2309
				'header' => array(
2310
					'value' => $txt['attach_dir_status'],
2311
					'class' => 'centercol',
2312
				),
2313
				'data' => array(
2314
					'db' => 'status',
2315
					'style' => 'width: 25%;',
2316
					'class' => 'centercol',
2317
				),
2318
			),
2319
		),
2320
		'form' => array(
2321
			'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2322
		),
2323
		'additional_rows' => array(
2324
			array(
2325
				'position' => 'below_table_data',
2326
				'value' => '
2327
				<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '">
2328
				<input type="submit" name="save" value="' . $txt['save'] . '" class="button_submit">
2329
				<input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" class="button_submit">',
2330
			),
2331
			empty($errors['dir']) ? array(
2332
				'position' => 'top_of_list',
2333
				'value' => $txt['attach_dir_desc'],
2334
				'class' => 'information'
2335
			) : array(
2336
				'position' => 'top_of_list',
2337
				'value' => $txt['attach_dir_save_problem'] . '<br>' . implode('<br>', $errors['dir']),
2338
				'style' => 'padding-left: 35px;',
2339
				'class' => 'noticebox',
2340
			),
2341
		),
2342
	);
2343
	require_once($sourcedir . '/Subs-List.php');
2344
	createList($listOptions);
2345
2346
	if (!empty($modSettings['attachment_basedirectories']))
2347
	{
2348
		$listOptions2 = array(
2349
			'id' => 'base_paths',
2350
			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2351
			'title' => $txt['attach_base_paths'],
2352
			'get_items' => array(
2353
				'function' => 'list_getBaseDirs',
2354
			),
2355
			'columns' => array(
2356
				'current_dir' => array(
2357
					'header' => array(
2358
						'value' => $txt['attach_current'],
2359
						'class' => 'centercol',
2360
					),
2361
					'data' => array(
2362
						'function' => function($rowData)
2363
						{
2364
							return '<input type="radio" name="current_base_dir" value="' . $rowData['id'] . '"' . ($rowData['current'] ? ' checked' : '') . ' class="input_radio">';
2365
						},
2366
						'style' => 'width: 10%;',
2367
						'class' => 'centercol',
2368
					),
2369
				),
2370
				'path' => array(
2371
					'header' => array(
2372
						'value' => $txt['attach_path'],
2373
					),
2374
					'data' => array(
2375
						'db' => 'path',
2376
						'style' => 'width: 45%;',
2377
					),
2378
				),
2379
				'num_dirs' => array(
2380
					'header' => array(
2381
						'value' => $txt['attach_num_dirs'],
2382
					),
2383
					'data' => array(
2384
						'db' => 'num_dirs',
2385
						'style' => 'width: 15%;',
2386
					),
2387
				),
2388
				'status' => array(
2389
					'header' => array(
2390
						'value' => $txt['attach_dir_status'],
2391
					),
2392
					'data' => array(
2393
						'db' => 'status',
2394
						'style' => 'width: 15%;',
2395
						'class' => 'centercol',
2396
					),
2397
				),
2398
			),
2399
			'form' => array(
2400
				'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
2401
			),
2402
			'additional_rows' => array(
2403
				array(
2404
					'position' => 'below_table_data',
2405
					'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '"><input type="submit" name="save2" value="' . $txt['save'] . '" class="button_submit">
2406
					<input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" class="button_submit">',
2407
				),
2408
				empty($errors['base']) ? array(
2409
					'position' => 'top_of_list',
2410
					'value' => $txt['attach_dir_base_desc'],
2411
					'style' => 'padding: 5px 10px;',
2412
					'class' => 'windowbg2 smalltext'
2413
				) : array(
2414
					'position' => 'top_of_list',
2415
					'value' => $txt['attach_dir_save_problem'] . '<br>' . implode('<br>', $errors['base']),
2416
					'style' => 'padding-left: 35px',
2417
					'class' => 'noticebox',
2418
				),
2419
			),
2420
		);
2421
		createList($listOptions2);
2422
	}
2423
2424
	// Fix up our template.
2425
	$context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths';
2426
	$context['page_title'] = $txt['attach_path_manage'];
2427
	$context['sub_template'] = 'attachment_paths';
2428
}
2429
2430
/**
2431
 * Prepare the actual attachment directories to be displayed in the list.
2432
 * @return array An array of information about the attachment directories
2433
 */
2434
function list_getAttachDirs()
2435
{
2436
	global $smcFunc, $modSettings, $context, $txt;
2437
2438
	$request = $smcFunc['db_query']('', '
2439
		SELECT id_folder, COUNT(id_attach) AS num_attach, SUM(size) AS size_attach
2440
		FROM {db_prefix}attachments
2441
		WHERE attachment_type != {int:type}
2442
		GROUP BY id_folder',
2443
		array(
2444
			'type' => 1,
2445
		)
2446
	);
2447
2448
	$expected_files = array();
2449
	$expected_size = array();
2450
	while ($row = $smcFunc['db_fetch_assoc']($request))
2451
	{
2452
		$expected_files[$row['id_folder']] = $row['num_attach'];
2453
		$expected_size[$row['id_folder']] = $row['size_attach'];
2454
	}
2455
	$smcFunc['db_free_result']($request);
2456
2457
	$attachdirs = array();
2458
	foreach ($modSettings['attachmentUploadDir'] as $id => $dir)
2459
	{
2460
		// If there aren't any attachments in this directory this won't exist.
2461
		if (!isset($expected_files[$id]))
2462
			$expected_files[$id] = 0;
2463
2464
		// Check if the directory is doing okay.
2465
		list ($status, $error, $files) = attachDirStatus($dir, $expected_files[$id]);
2466
2467
		// If it is one, let's show that it's a base directory.
2468
		$sub_dirs = 0;
2469
		$is_base_dir = false;
2470
		if (!empty($modSettings['attachment_basedirectories']))
2471
		{
2472
			$is_base_dir = in_array($dir, $modSettings['attachment_basedirectories']);
2473
2474
			// Count any sub-folders.
2475
			foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
2476
				if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
2477
				{
2478
					$expected_files[$id]++;
2479
					$sub_dirs++;
2480
				}
2481
		}
2482
2483
		$attachdirs[] = array(
2484
			'id' => $id,
2485
			'current' => $id == $modSettings['currentAttachmentUploadDir'],
2486
			'disable_current' => isset($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] > 1,
2487
			'disable_base_dir' =>  $is_base_dir && $sub_dirs > 0 && !empty($files) && empty($error) && empty($save_errors),
2488
			'path' => $dir,
2489
			'current_size' => !empty($expected_size[$id]) ? comma_format($expected_size[$id] / 1024, 0) : 0,
2490
			'num_files' => comma_format($expected_files[$id] - $sub_dirs, 0) . ($sub_dirs > 0 ? ' (' . $sub_dirs . ')' : ''),
2491
			'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>' : ''),
2492
		);
2493
	}
2494
2495
	// Just stick a new directory on at the bottom.
2496
	if (isset($_REQUEST['new_path']))
2497
		$attachdirs[] = array(
2498
			'id' => max(array_merge(array_keys($expected_files), array_keys($modSettings['attachmentUploadDir']))) + 1,
2499
			'current' => false,
2500
			'path' => '',
2501
			'current_size' => '',
2502
			'num_files' => '',
2503
			'status' => '',
2504
		);
2505
2506
	return $attachdirs;
2507
}
2508
2509
/**
2510
 * Prepare the base directories to be displayed in a list.
2511
 * @return void|array Returns nothing if there are no base directories, otherwise returns an array of info about the directories
2512
 */
2513
function list_getBaseDirs()
2514
{
2515
	global $modSettings, $txt;
2516
2517
	if (empty($modSettings['attachment_basedirectories']))
2518
		return;
2519
2520
	$basedirs = array();
2521
	// Get a list of the base directories.
2522
	foreach ($modSettings['attachment_basedirectories'] as $id => $dir)
2523
	{
2524
		// Loop through the attach directory array to count any sub-directories
2525
		$expected_dirs = 0;
2526 View Code Duplication
		foreach ($modSettings['attachmentUploadDir'] as $sid => $sub)
2527
			if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
2528
				$expected_dirs++;
2529
2530
		if (!is_dir($dir))
2531
			$status = 'does_not_exist';
2532
		elseif (!is_writeable($dir))
2533
			$status = 'not_writable';
2534
		else
2535
			$status = 'ok';
2536
2537
		$basedirs[] = array(
2538
			'id' => $id,
2539
			'current' => $dir == $modSettings['basedirectory_for_attachments'],
2540
			'path' => $expected_dirs > 0 ? $dir : ('<input type="text" name="base_dir[' . $id . ']" value="' . $dir . '" size="40">'),
2541
			'num_dirs' => $expected_dirs,
2542
			'status' => $status == 'ok' ? $txt['attach_dir_ok'] : ('<span class="error">' . $txt['attach_dir_' . $status] . '</span>'),
2543
		);
2544
	}
2545
2546
	if (isset($_REQUEST['new_base_path']))
2547
		$basedirs[] = array(
2548
			'id' => '',
2549
			'current' => false,
2550
			'path' => '<input type="text" name="new_base_dir" value="" size="40">',
2551
			'num_dirs' => '',
2552
			'status' => '',
2553
		);
2554
2555
	return $basedirs;
2556
}
2557
2558
/**
2559
 * Checks the status of an attachment directory and returns an array
2560
 *  of the status key, if that status key signifies an error, and
2561
 *  the file count.
2562
 *
2563
 * @param string $dir The directory to check
2564
 * @param int $expected_files How many files should be in that directory
2565
 * @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
2566
 */
2567
function attachDirStatus($dir, $expected_files)
2568
{
2569
	if (!is_dir($dir))
2570
		return array('does_not_exist', true, '');
2571
	elseif (!is_writable($dir))
2572
		return array('not_writable', true, '');
2573
2574
	// Everything is okay so far, start to scan through the directory.
2575
	$num_files = 0;
2576
	$dir_handle = dir($dir);
2577
	while ($file = $dir_handle->read())
2578
	{
2579
		// Now do we have a real file here?
2580
		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
2581
			continue;
2582
2583
		$num_files++;
2584
	}
2585
	$dir_handle->close();
2586
2587
	if ($num_files < $expected_files)
2588
		return array('files_missing', true, $num_files);
2589
	// Empty?
2590
	elseif ($expected_files == 0)
2591
		return array('unused', false, $num_files);
2592
	// All good!
2593
	else
2594
		return array('ok', false, $num_files);
2595
}
2596
2597
/**
2598
 * Maintance function to move attachments from one directory to another
2599
 */
2600
function TransferAttachments()
2601
{
2602
	global $modSettings, $smcFunc, $sourcedir, $txt, $boarddir;
2603
2604
	checkSession();
2605
2606
	$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
2607
	if (!empty($modSettings['attachment_basedirectories']))
2608
		$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
2609
	else
2610
		$modSettings['basedirectory_for_attachments'] = array();
2611
2612
	$_POST['from'] = (int) $_POST['from'];
2613
	$_POST['auto'] = !empty($_POST['auto']) ? (int) $_POST['auto'] : 0;
2614
	$_POST['to'] = (int) $_POST['to'];
2615
	$start = !empty($_POST['empty_it']) ? 0 : $modSettings['attachmentDirFileLimit'];
2616
	$_SESSION['checked'] = !empty($_POST['empty_it']) ? true : false;
2617
	$limit = 501;
2618
	$results = array();
2619
	$dir_files = 0;
2620
	$current_progress = 0;
2621
	$total_moved = 0;
2622
	$total_not_moved = 0;
2623
2624
	if (empty($_POST['from']) || (empty($_POST['auto']) && empty($_POST['to'])))
2625
		$results[] = $txt['attachment_transfer_no_dir'];
2626
2627
	if ($_POST['from'] == $_POST['to'])
2628
		$results[] = $txt['attachment_transfer_same_dir'];
2629
2630
	if (empty($results))
2631
	{
2632
		// Get the total file count for the progess bar.
2633
		$request = $smcFunc['db_query']('', '
2634
			SELECT COUNT(*)
2635
			FROM {db_prefix}attachments
2636
			WHERE id_folder = {int:folder_id}
2637
				AND attachment_type != {int:attachment_type}',
2638
			array(
2639
				'folder_id' => $_POST['from'],
2640
				'attachment_type' => 1,
2641
			)
2642
		);
2643
		list ($total_progress) = $smcFunc['db_fetch_row']($request);
2644
		$smcFunc['db_free_result']($request);
2645
		$total_progress -= $start;
2646
2647
		if ($total_progress < 1)
2648
			$results[] = $txt['attachment_transfer_no_find'];
2649
	}
2650
2651
	if (empty($results))
2652
	{
2653
		// Where are they going?
2654
		if (!empty($_POST['auto']))
2655
		{
2656
			require_once($sourcedir . '/Subs-Attachments.php');
2657
2658
			$modSettings['automanage_attachments'] = 1;
2659
			$modSettings['use_subdirectories_for_attachments'] = $_POST['auto'] == -1 ? 0 : 1;
2660
			$modSettings['basedirectory_for_attachments'] = $_POST['auto'] > 0 ? $modSettings['attachmentUploadDir'][$_POST['auto']] : $modSettings['basedirectory_for_attachments'];
2661
2662
			automanage_attachments_check_directory();
2663
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2664
		}
2665
		else
2666
			$new_dir = $_POST['to'];
2667
2668
		$modSettings['currentAttachmentUploadDir'] = $new_dir;
2669
2670
		$break = false;
2671
		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...
2672
		{
2673
			@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...
2674
			if (function_exists('apache_reset_timeout'))
2675
				@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...
2676
2677
			// If limits are set, get the file count and size for the destination folder
2678
			if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])))
2679
			{
2680
				$request = $smcFunc['db_query']('', '
2681
					SELECT COUNT(*), SUM(size)
2682
					FROM {db_prefix}attachments
2683
					WHERE id_folder = {int:folder_id}
2684
						AND attachment_type != {int:attachment_type}',
2685
					array(
2686
						'folder_id' => $new_dir,
2687
						'attachment_type' => 1,
2688
					)
2689
				);
2690
				list ($dir_files, $dir_size) = $smcFunc['db_fetch_row']($request);
2691
				$smcFunc['db_free_result']($request);
2692
			}
2693
2694
			// Find some attachments to move
2695
			$request = $smcFunc['db_query']('', '
2696
				SELECT id_attach, filename, id_folder, file_hash, size
2697
				FROM {db_prefix}attachments
2698
				WHERE id_folder = {int:folder}
2699
					AND attachment_type != {int:attachment_type}
2700
				LIMIT {int:start}, {int:limit}',
2701
				array(
2702
					'folder' => $_POST['from'],
2703
					'attachment_type' => 1,
2704
					'start' => $start,
2705
					'limit' => $limit,
2706
				)
2707
			);
2708
2709
			if ($smcFunc['db_num_rows']($request) === 0)
2710
			{
2711
				if (empty($current_progress))
2712
					$results[] = $txt['attachment_transfer_no_find'];
2713
				break;
2714
			}
2715
2716
			if ($smcFunc['db_num_rows']($request) < $limit)
2717
				$break = true;
2718
2719
			// Move them
2720
			$moved = array();
2721
			while ($row = $smcFunc['db_fetch_assoc']($request))
2722
			{
2723
				$source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
2724
				$dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source);
2725
2726
				// Size and file count check
2727
				if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
2728
				{
2729
					$dir_files++;
2730
					$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...
2731
2732
					// If we've reached a limit. Do something.
2733
					if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || (!empty($modSettings['attachmentDirFileLimit']) && $dir_files > $modSettings['attachmentDirFileLimit']))
2734
					{
2735
						if (!empty($_POST['auto']))
2736
						{
2737
							// Since we're in auto mode. Create a new folder and reset the counters.
2738
							automanage_attachments_by_space();
2739
2740
							$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2741
							if (!empty($total_not_moved))
2742
								$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2743
2744
							$dir_files = 0;
2745
							$total_moved = 0;
2746
							$total_not_moved = 0;
2747
2748
							$break = false;
2749
							break;
2750
						}
2751
						else
2752
						{
2753
							// Hmm, not in auto. Time to bail out then...
2754
							$results[] = $txt['attachment_transfer_no_room'];
2755
							$break = true;
2756
							break;
2757
						}
2758
					}
2759
				}
2760
2761
				if (@rename($source, $dest))
2762
				{
2763
					$total_moved++;
2764
					$current_progress++;
2765
					$moved[] = $row['id_attach'];
2766
				}
2767
				else
2768
					$total_not_moved++;
2769
			}
2770
			$smcFunc['db_free_result']($request);
2771
2772
			if (!empty($moved))
2773
			{
2774
				// Update the database
2775
				$smcFunc['db_query']('', '
2776
					UPDATE {db_prefix}attachments
2777
					SET id_folder = {int:new}
2778
					WHERE id_attach IN ({array_int:attachments})',
2779
					array(
2780
						'attachments' => $moved,
2781
						'new' => $new_dir,
2782
					)
2783
				);
2784
			}
2785
2786
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2787
2788
			// Create the progress bar.
2789
			if (!$break)
2790
			{
2791
				$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...
2792
				$prog_bar = '
2793
					<div class="progress_bar">
2794
						<div class="full_bar">' . $percent_done . '%</div>
2795
						<div class="green_percent" style="width: ' . $percent_done . '%;">&nbsp;</div>
2796
					</div>';
2797
				// Write it to a file so it can be displayed
2798
				$fp = fopen($boarddir . '/progress.php', "w");
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Coding Style Comprehensibility introduced by
The string literal w does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
2799
				fwrite($fp, $prog_bar);
2800
				fclose($fp);
2801
				usleep(500000);
2802
			}
2803
		}
2804
2805
		$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2806
		if (!empty($total_not_moved))
2807
			$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2808
	}
2809
2810
	$_SESSION['results'] = $results;
2811
	if (file_exists($boarddir . '/progress.php'))
2812
		unlink($boarddir . '/progress.php');
2813
2814
	redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer');
2815
}
2816
2817
?>