Completed
Push — release-2.1 ( 5876a2...f30e04 )
by Mert
06:50
created

ManageAttachments.php ➔ ManageAttachmentSettings()   F

Complexity

Conditions 47
Paths > 20000

Size

Total Lines 175
Code Lines 106

Duplication

Lines 13
Ratio 7.43 %
Metric Value
cc 47
eloc 106
nc 429496.7295
nop 1
dl 13
loc 175
rs 2

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

Loading history...
207
				rename($modSettings['attachmentUploadDir'], $_POST['attachmentUploadDir']);
208
209
			$modSettings['attachmentUploadDir'] = array(1 => $_POST['attachmentUploadDir']);
210
			$_POST['attachmentUploadDir'] = json_encode($modSettings['attachmentUploadDir']);
211
		}
212
213
		if (!empty($_POST['use_subdirectories_for_attachments']))
214
		{
215
			if (isset($_POST['use_subdirectories_for_attachments']) && empty($_POST['basedirectory_for_attachments']))
216
				$_POST['basedirectory_for_attachments'] = (!empty($modSettings['basedirectory_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
217
218 View Code Duplication
			if (!empty($_POST['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

Loading history...
948
		$smcFunc['db_query']('', '
949
			UPDATE {db_prefix}messages
950
			SET body = CONCAT(body, {string:deleted_message})
951
			WHERE id_msg IN ({array_int:messages})',
952
			array(
953
				'messages' => $messages,
954
				'deleted_message' => '<br><br>' . $_POST['notice'],
955
			)
956
		);
957
958
	redirectexit('action=admin;area=manageattachments;sa=maintenance');
959
}
960
961
/**
962
 * Removes attachments or avatars based on a given query condition.
963
 * Called by several remove avatar/attachment functions in this file.
964
 * It removes attachments based that match the $condition.
965
 * It allows query_types 'messages' and 'members', whichever is need by the
966
 * $condition parameter.
967
 * It does no permissions check.
968
 * @internal
969
 *
970
 * @param array $condition An array of conditions
971
 * @param string $query_type The query type. Can be 'messages' or 'members'
972
 * @param bool $return_affected_messages Whether to return an array with the IDs of affected messages
973
 * @param bool $autoThumbRemoval Whether to automatically remove any thumbnails associated with the removed files
974
 * @return void|int[] Returns an array containing IDs of affected messages if $return_affected_messages is true
975
 */
976
function removeAttachments($condition, $query_type = '', $return_affected_messages = false, $autoThumbRemoval = true)
977
{
978
	global $modSettings, $smcFunc;
979
980
	// @todo This might need more work!
981
	$new_condition = array();
982
	$query_parameter = array(
983
		'thumb_attachment_type' => 3,
984
	);
985
	$do_logging = array();
986
987
	if (is_array($condition))
988
	{
989
		foreach ($condition as $real_type => $restriction)
990
		{
991
			// Doing a NOT?
992
			$is_not = substr($real_type, 0, 4) == 'not_';
993
			$type = $is_not ? substr($real_type, 4) : $real_type;
994
995
			if (in_array($type, array('id_member', 'id_attach', 'id_msg')))
996
				$new_condition[] = 'a.' . $type . ($is_not ? ' NOT' : '') . ' IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
997
			elseif ($type == 'attachment_type')
998
				$new_condition[] = 'a.attachment_type = {int:' . $real_type . '}';
999
			elseif ($type == 'poster_time')
1000
				$new_condition[] = 'm.poster_time < {int:' . $real_type . '}';
1001
			elseif ($type == 'last_login')
1002
				$new_condition[] = 'mem.last_login < {int:' . $real_type . '}';
1003
			elseif ($type == 'size')
1004
				$new_condition[] = 'a.size > {int:' . $real_type . '}';
1005
			elseif ($type == 'id_topic')
1006
				$new_condition[] = 'm.id_topic IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
1007
1008
			// Add the parameter!
1009
			$query_parameter[$real_type] = $restriction;
1010
1011
			if ($type == 'do_logging')
1012
				$do_logging = $condition['id_attach'];
1013
		}
1014
		$condition = implode(' AND ', $new_condition);
1015
	}
1016
1017
	// Delete it only if it exists...
1018
	$msgs = array();
1019
	$attach = array();
1020
	$parents = array();
1021
1022
	// Get all the attachment names and id_msg's.
1023
	$request = $smcFunc['db_query']('', '
1024
		SELECT
1025
			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') . ',
1026
			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
1027
		FROM {db_prefix}attachments AS a' .($query_type == 'members' ? '
1028
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)' : ($query_type == 'messages' ? '
1029
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)' : '')) . '
1030
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1031
			LEFT JOIN {db_prefix}attachments AS thumb_parent ON (thumb.attachment_type = {int:thumb_attachment_type} AND thumb_parent.id_thumb = a.id_attach)
1032
		WHERE ' . $condition,
1033
		$query_parameter
1034
	);
1035
	while ($row = $smcFunc['db_fetch_assoc']($request))
1036
	{
1037
		// Figure out the "encrypted" filename and unlink it ;).
1038
		if ($row['attachment_type'] == 1)
1039
		{
1040
			// if attachment_type = 1, it's... an avatar in a custom avatar directory.
1041
			// wasn't it obvious? :P
1042
			// @todo look again at this.
1043
			@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...
1044
		}
1045
		else
1046
		{
1047
			$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1048
			@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...
1049
1050
			// If this was a thumb, the parent attachment should know about it.
1051
			if (!empty($row['id_parent']))
1052
				$parents[] = $row['id_parent'];
1053
1054
			// If this attachments has a thumb, remove it as well.
1055
			if (!empty($row['id_thumb']) && $autoThumbRemoval)
1056
			{
1057
				$thumb_filename = getAttachmentFilename($row['thumb_filename'], $row['id_thumb'], $row['thumb_folder'], false, $row['thumb_file_hash']);
1058
				@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...
1059
				$attach[] = $row['id_thumb'];
1060
			}
1061
		}
1062
1063
		// Make a list.
1064
		if ($return_affected_messages && empty($row['attachment_type']))
1065
			$msgs[] = $row['id_msg'];
1066
1067
		$attach[] = $row['id_attach'];
1068
	}
1069
	$smcFunc['db_free_result']($request);
1070
1071
	// Removed attachments don't have to be updated anymore.
1072
	$parents = array_diff($parents, $attach);
1073
	if (!empty($parents))
1074
		$smcFunc['db_query']('', '
1075
			UPDATE {db_prefix}attachments
1076
			SET id_thumb = {int:no_thumb}
1077
			WHERE id_attach IN ({array_int:parent_attachments})',
1078
			array(
1079
				'parent_attachments' => $parents,
1080
				'no_thumb' => 0,
1081
			)
1082
		);
1083
1084
	if (!empty($do_logging))
1085
	{
1086
		// In order to log the attachments, we really need their message and filename
1087
		$request = $smcFunc['db_query']('', '
1088
			SELECT m.id_msg, a.filename
1089
			FROM {db_prefix}attachments AS a
1090
				INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
1091
			WHERE a.id_attach IN ({array_int:attachments})
1092
				AND a.attachment_type = {int:attachment_type}',
1093
			array(
1094
				'attachments' => $do_logging,
1095
				'attachment_type' => 0,
1096
			)
1097
		);
1098
1099 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1100
			logAction(
1101
				'remove_attach',
1102
				array(
1103
					'message' => $row['id_msg'],
1104
					'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
1105
				)
1106
			);
1107
		$smcFunc['db_free_result']($request);
1108
	}
1109
1110
	if (!empty($attach))
1111
		$smcFunc['db_query']('', '
1112
			DELETE FROM {db_prefix}attachments
1113
			WHERE id_attach IN ({array_int:attachment_list})',
1114
			array(
1115
				'attachment_list' => $attach,
1116
			)
1117
		);
1118
1119
	call_integration_hook('integrate_remove_attachments', array($attach));
1120
1121
	if ($return_affected_messages)
1122
		return array_unique($msgs);
1123
}
1124
1125
/**
1126
 * This function should find attachments in the database that no longer exist and clear them, and fix filesize issues.
1127
 */
1128
function RepairAttachments()
1129
{
1130
	global $modSettings, $context, $txt, $smcFunc;
1131
1132
	checkSession('get');
1133
1134
	// If we choose cancel, redirect right back.
1135
	if (isset($_POST['cancel']))
1136
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
1137
1138
	// Try give us a while to sort this out...
1139
	@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...
1140
1141
	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
1142
	$context['starting_substep'] = $_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
1143
1144
	// Don't recall the session just in case.
1145
	if ($_GET['step'] == 0 && $_GET['substep'] == 0)
1146
	{
1147
		unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']);
1148
1149
		// If we're actually fixing stuff - work out what.
1150
		if (isset($_GET['fixErrors']))
1151
		{
1152
			// Nothing?
1153
			if (empty($_POST['to_fix']))
1154
				redirectexit('action=admin;area=manageattachments;sa=maintenance');
1155
1156
			$_SESSION['attachments_to_fix'] = array();
1157
			// @todo No need to do this I think.
1158
			foreach ($_POST['to_fix'] as $value)
1159
				$_SESSION['attachments_to_fix'][] = $value;
1160
		}
1161
	}
1162
1163
	// All the valid problems are here:
1164
	$context['repair_errors'] = array(
1165
		'missing_thumbnail_parent' => 0,
1166
		'parent_missing_thumbnail' => 0,
1167
		'file_missing_on_disk' => 0,
1168
		'file_wrong_size' => 0,
1169
		'file_size_of_zero' => 0,
1170
		'attachment_no_msg' => 0,
1171
		'avatar_no_member' => 0,
1172
		'wrong_folder' => 0,
1173
		'files_without_attachment' => 0,
1174
	);
1175
1176
	$to_fix = !empty($_SESSION['attachments_to_fix']) ? $_SESSION['attachments_to_fix'] : array();
1177
	$context['repair_errors'] = isset($_SESSION['attachments_to_fix2']) ? $_SESSION['attachments_to_fix2'] : $context['repair_errors'];
1178
	$fix_errors = isset($_GET['fixErrors']) ? true : false;
1179
1180
	// Get stranded thumbnails.
1181
	if ($_GET['step'] <= 0)
1182
	{
1183
		$result = $smcFunc['db_query']('', '
1184
			SELECT MAX(id_attach)
1185
			FROM {db_prefix}attachments
1186
			WHERE attachment_type = {int:thumbnail}',
1187
			array(
1188
				'thumbnail' => 3,
1189
			)
1190
		);
1191
		list ($thumbnails) = $smcFunc['db_fetch_row']($result);
1192
		$smcFunc['db_free_result']($result);
1193
1194
		for (; $_GET['substep'] < $thumbnails; $_GET['substep'] += 500)
1195
		{
1196
			$to_remove = array();
1197
1198
			$result = $smcFunc['db_query']('', '
1199
				SELECT thumb.id_attach, thumb.id_folder, thumb.filename, thumb.file_hash
1200
				FROM {db_prefix}attachments AS thumb
1201
					LEFT JOIN {db_prefix}attachments AS tparent ON (tparent.id_thumb = thumb.id_attach)
1202
				WHERE thumb.id_attach BETWEEN {int:substep} AND {int:substep} + 499
1203
					AND thumb.attachment_type = {int:thumbnail}
1204
					AND tparent.id_attach IS NULL',
1205
				array(
1206
					'thumbnail' => 3,
1207
					'substep' => $_GET['substep'],
1208
				)
1209
			);
1210
			while ($row = $smcFunc['db_fetch_assoc']($result))
1211
			{
1212
				// Only do anything once... just in case
1213
				if (!isset($to_remove[$row['id_attach']]))
1214
				{
1215
					$to_remove[$row['id_attach']] = $row['id_attach'];
1216
					$context['repair_errors']['missing_thumbnail_parent']++;
1217
1218
					// If we are repairing remove the file from disk now.
1219 View Code Duplication
					if ($fix_errors && in_array('missing_thumbnail_parent', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1220
					{
1221
						$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1222
						@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...
1223
					}
1224
				}
1225
			}
1226
			if ($smcFunc['db_num_rows']($result) != 0)
1227
				$to_fix[] = 'missing_thumbnail_parent';
1228
			$smcFunc['db_free_result']($result);
1229
1230
			// Do we need to delete what we have?
1231 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('missing_thumbnail_parent', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

Loading history...
1363
									$smcFunc['db_query']('', '
1364
										UPDATE {db_prefix}attachments
1365
										SET id_folder = {int:new_folder}
1366
										WHERE id_attach = {int:id_attach}',
1367
										array(
1368
											'new_folder' => $id,
1369
											'id_attach' => $row['id_attach'],
1370
										)
1371
									);
1372
1373
								continue 2;
1374
							}
1375
					}
1376
1377
					$to_remove[] = $row['id_attach'];
1378
					$context['repair_errors']['file_missing_on_disk']++;
1379
					$errors_found[] = 'file_missing_on_disk';
1380
				}
1381
				elseif (filesize($filename) == 0)
1382
				{
1383
					$context['repair_errors']['file_size_of_zero']++;
1384
					$errors_found[] = 'file_size_of_zero';
1385
1386
					// Fixing?
1387
					if ($fix_errors && in_array('file_size_of_zero', $to_fix))
1388
					{
1389
						$to_remove[] = $row['id_attach'];
1390
						@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...
1391
					}
1392
				}
1393
				elseif (filesize($filename) != $row['size'])
1394
				{
1395
					$context['repair_errors']['file_wrong_size']++;
1396
					$errors_found[] = 'file_wrong_size';
1397
1398
					// Fix it here?
1399 View Code Duplication
					if ($fix_errors && in_array('file_wrong_size', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
1492
						$filename = $modSettings['custom_avatar_dir'] . '/' . $row['filename'];
1493
					else
1494
						$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1495
					@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...
1496
				}
1497
			}
1498
			if ($smcFunc['db_num_rows']($result) != 0)
1499
				$to_fix[] = 'avatar_no_member';
1500
			$smcFunc['db_free_result']($result);
1501
1502
			// Do we need to delete what we have?
1503 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('avatar_no_member', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
1565
				{
1566
					$filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1567
					@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...
1568
				}
1569
			}
1570
			if ($smcFunc['db_num_rows']($result) != 0)
1571
				$to_fix[] = 'attachment_no_msg';
1572
			$smcFunc['db_free_result']($result);
1573
1574
			// Do we need to delete what we have?
1575 View Code Duplication
			if ($fix_errors && !empty($to_remove) && in_array('attachment_no_msg', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1576
				$smcFunc['db_query']('', '
1577
					DELETE FROM {db_prefix}attachments
1578
					WHERE id_attach IN ({array_int:to_remove})
1579
						AND id_member = {int:no_member}
1580
						AND id_msg != {int:no_msg}',
1581
					array(
1582
						'to_remove' => $to_remove,
1583
						'no_member' => 0,
1584
						'no_msg' => 0,
1585
					)
1586
				);
1587
1588
			pauseAttachmentMaintenance($to_fix, $thumbnails);
1589
		}
1590
1591
		$_GET['step'] = 5;
1592
		$_GET['substep'] = 0;
1593
		pauseAttachmentMaintenance($to_fix);
1594
	}
1595
1596
	// What about files who are not recorded in the database?
1597
	if ($_GET['step'] <= 5)
1598
	{
1599
		// Just use the current path for temp files.
1600
		if (!is_array($modSettings['attachmentUploadDir']))
1601
			$modSettings['attachmentUploadDir'] = json_decode($modSettings['attachmentUploadDir'], true);
1602
		$attach_dirs = $modSettings['attachmentUploadDir'];
1603
1604
		$current_check = 0;
1605
		$max_checks = 500;
1606
		$files_checked = empty($_GET['substep']) ? 0 : $_GET['substep'];
1607
		foreach ($attach_dirs as $attach_dir)
1608
		{
1609
			if ($dir = @opendir($attach_dir))
1610
			{
1611
				while ($file = readdir($dir))
1612
				{
1613
					if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
1614
						continue;
1615
1616
					if ($files_checked <= $current_check)
1617
					{
1618
						// Temporary file, get rid of it!
1619
						if (strpos($file, 'post_tmp_') !== false)
1620
						{
1621
							// Temp file is more than 5 hours old!
1622 View Code Duplication
							if (filemtime($attach_dir . '/' . $file) < time() - 18000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1623
								@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...
1624
						}
1625
						// That should be an attachment, let's check if we have it in the database
1626
						elseif (strpos($file, '_') !== false)
1627
						{
1628
							$attachID = (int) substr($file, 0, strpos($file, '_'));
1629
							if (!empty($attachID))
1630
							{
1631
								$request = $smcFunc['db_query']('', '
1632
									SELECT  id_attach
1633
									FROM {db_prefix}attachments
1634
									WHERE id_attach = {int:attachment_id}
1635
									LIMIT 1',
1636
									array(
1637
										'attachment_id' => $attachID,
1638
									)
1639
								);
1640
								if ($smcFunc['db_num_rows']($request) == 0)
1641
								{
1642 View Code Duplication
									if ($fix_errors && in_array('files_without_attachment', $to_fix))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1643
									{
1644
										@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...
1645
									}
1646
									else
1647
									{
1648
										$context['repair_errors']['files_without_attachment']++;
1649
										$to_fix[] = 'files_without_attachment';
1650
									}
1651
								}
1652
								$smcFunc['db_free_result']($request);
1653
							}
1654
						}
1655 View Code Duplication
						else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1656
						{
1657
							if ($fix_errors && in_array('files_without_attachment', $to_fix))
1658
							{
1659
								@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...
1660
							}
1661
							else
1662
							{
1663
								$context['repair_errors']['files_without_attachment']++;
1664
								$to_fix[] = 'files_without_attachment';
1665
							}
1666
						}
1667
					}
1668
					$current_check++;
1669
					$_GET['substep'] = $current_check;
1670
					if ($current_check - $files_checked >= $max_checks)
1671
						pauseAttachmentMaintenance($to_fix);
1672
				}
1673
				closedir($dir);
1674
			}
1675
		}
1676
1677
		$_GET['step'] = 5;
1678
		$_GET['substep'] = 0;
1679
		pauseAttachmentMaintenance($to_fix);
1680
	}
1681
1682
	// Got here we must be doing well - just the template! :D
1683
	$context['page_title'] = $txt['repair_attachments'];
1684
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1685
	$context['sub_template'] = 'attachment_repair';
1686
1687
	// What stage are we at?
1688
	$context['completed'] = $fix_errors ? true : false;
1689
	$context['errors_found'] = !empty($to_fix) ? true : false;
1690
1691
}
1692
1693
/**
1694
 * Function called in-between each round of attachments and avatar repairs.
1695
 * Called by repairAttachments().
1696
 * If repairAttachments() has more steps added, this function needs updated!
1697
 *
1698
 * @param array $to_fix IDs of attachments to fix
1699
 * @param int The maximum substep to reach before pausing
1700
 */
1701
function pauseAttachmentMaintenance($to_fix, $max_substep = 0)
1702
{
1703
	global $context, $txt, $time_start;
1704
1705
	// Try get more time...
1706
	@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...
1707
	if (function_exists('apache_reset_timeout'))
1708
		@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...
1709
1710
	// Have we already used our maximum time?
1711
	if (time() - array_sum(explode(' ', $time_start)) < 3 || $context['starting_substep'] == $_GET['substep'])
1712
		return;
1713
1714
	$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'];
1715
	$context['page_title'] = $txt['not_done_title'];
1716
	$context['continue_post_data'] = '';
1717
	$context['continue_countdown'] = '2';
1718
	$context['sub_template'] = 'not_done';
1719
1720
	// Specific stuff to not break this template!
1721
	$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1722
1723
	// Change these two if more steps are added!
1724
	if (empty($max_substep))
1725
		$context['continue_percent'] = round(($_GET['step'] * 100) / 25);
1726 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1778
1779
	// Now we have some ID's cleaned and ready to approve, but first - let's check we have permission!
1780
	$allowed_boards = boardsAllowedTo('approve_posts');
1781
1782
	// Validate the attachments exist and are the right approval state.
1783
	$request = $smcFunc['db_query']('', '
1784
		SELECT a.id_attach, m.id_board, m.id_msg, m.id_topic
1785
		FROM {db_prefix}attachments AS a
1786
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1787
		WHERE a.id_attach IN ({array_int:attachments})
1788
			AND a.attachment_type = {int:attachment_type}
1789
			AND a.approved = {int:is_approved}',
1790
		array(
1791
			'attachments' => $attachments,
1792
			'attachment_type' => 0,
1793
			'is_approved' => 0,
1794
		)
1795
	);
1796
	$attachments = array();
1797
	while ($row = $smcFunc['db_fetch_assoc']($request))
1798
	{
1799
		// We can only add it if we can approve in this board!
1800
		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...
1801
		{
1802
			$attachments[] = $row['id_attach'];
1803
1804
			// Also come up with the redirection URL.
1805
			$redirect = 'topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'];
1806
		}
1807
	}
1808
	$smcFunc['db_free_result']($request);
1809
1810
	if (empty($attachments))
1811
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1812
1813
	// Finally, we are there. Follow through!
1814
	if ($is_approve)
1815
	{
1816
		// Checked and deemed worthy.
1817
		ApproveAttachments($attachments);
1818
	}
1819
	else
1820
		removeAttachments(array('id_attach' => $attachments, 'do_logging' => true));
1821
1822
	// Return to the topic....
1823
	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...
1824
}
1825
1826
/**
1827
 * Approve an attachment, or maybe even more - no permission check!
1828
 *
1829
 * @param array $attachments The IDs of the attachments to approve
1830
 * @return void|int Returns 0 if the operation failed, otherwise returns nothing
1831
 */
1832
function ApproveAttachments($attachments)
1833
{
1834
	global $smcFunc;
1835
1836
	if (empty($attachments))
1837
		return 0;
1838
1839
	// For safety, check for thumbnails...
1840
	$request = $smcFunc['db_query']('', '
1841
		SELECT
1842
			a.id_attach, a.id_member, COALESCE(thumb.id_attach, 0) AS id_thumb
1843
		FROM {db_prefix}attachments AS a
1844
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
1845
		WHERE a.id_attach IN ({array_int:attachments})
1846
			AND a.attachment_type = {int:attachment_type}',
1847
		array(
1848
			'attachments' => $attachments,
1849
			'attachment_type' => 0,
1850
		)
1851
	);
1852
	$attachments = array();
1853
	while ($row = $smcFunc['db_fetch_assoc']($request))
1854
	{
1855
		// Update the thumbnail too...
1856
		if (!empty($row['id_thumb']))
1857
			$attachments[] = $row['id_thumb'];
1858
1859
		$attachments[] = $row['id_attach'];
1860
	}
1861
	$smcFunc['db_free_result']($request);
1862
1863
	if (empty($attachments))
1864
		return 0;
1865
1866
	// Approving an attachment is not hard - it's easy.
1867
	$smcFunc['db_query']('', '
1868
		UPDATE {db_prefix}attachments
1869
		SET approved = {int:is_approved}
1870
		WHERE id_attach IN ({array_int:attachments})',
1871
		array(
1872
			'attachments' => $attachments,
1873
			'is_approved' => 1,
1874
		)
1875
	);
1876
1877
	// In order to log the attachments, we really need their message and filename
1878
	$request = $smcFunc['db_query']('', '
1879
		SELECT m.id_msg, a.filename
1880
		FROM {db_prefix}attachments AS a
1881
			INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
1882
		WHERE a.id_attach IN ({array_int:attachments})
1883
			AND a.attachment_type = {int:attachment_type}',
1884
		array(
1885
			'attachments' => $attachments,
1886
			'attachment_type' => 0,
1887
		)
1888
	);
1889
1890 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1891
		logAction(
1892
			'approve_attach',
1893
			array(
1894
				'message' => $row['id_msg'],
1895
				'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
1896
			)
1897
		);
1898
	$smcFunc['db_free_result']($request);
1899
1900
	// Remove from the approval queue.
1901
	$smcFunc['db_query']('', '
1902
		DELETE FROM {db_prefix}approval_queue
1903
		WHERE id_attach IN ({array_int:attachments})',
1904
		array(
1905
			'attachments' => $attachments,
1906
		)
1907
	);
1908
1909
	call_integration_hook('integrate_approve_attachments', array($attachments));
1910
}
1911
1912
/**
1913
 * This function lists and allows updating of multiple attachments paths.
1914
 */
1915
function ManageAttachmentPaths()
1916
{
1917
	global $modSettings, $scripturl, $context, $txt, $sourcedir, $boarddir, $smcFunc, $settings;
1918
1919
	// Since this needs to be done eventually.
1920
	if (!is_array($modSettings['attachmentUploadDir']))
1921
		$modSettings['attachmentUploadDir'] = json_decode($modSettings['attachmentUploadDir'], true);
1922
	if (!isset($modSettings['attachment_basedirectories']))
1923
		$modSettings['attachment_basedirectories'] = array();
1924
	elseif (!is_array($modSettings['attachment_basedirectories']))
1925
		$modSettings['attachment_basedirectories'] = json_decode($modSettings['attachment_basedirectories'], true);
1926
1927
	$errors = array();
1928
1929
	// Saving?
1930
	if (isset($_REQUEST['save']))
1931
	{
1932
		checkSession();
1933
1934
		$_POST['current_dir'] = (int) $_POST['current_dir'];
1935
		$new_dirs = array();
1936
		foreach ($_POST['dirs'] as $id => $path)
1937
		{
1938
			$error = '';
1939
			$id = (int) $id;
1940
			if ($id < 1)
1941
				continue;
1942
1943
			// Sorry, these dirs are NOT valid
1944
			$invalid_dirs = array($boarddir, $settings['default_theme_dir'], $sourcedir);
1945
			if (in_array($path, $invalid_dirs))
1946
			{
1947
				$errors[] = $path . ': ' . $txt['attach_dir_invalid'];
1948
				continue;
1949
			}
1950
1951
			// Hmm, a new path maybe?
1952
			// Don't allow empty paths
1953
			if (!array_key_exists($id, $modSettings['attachmentUploadDir']) && !empty($path))
1954
			{
1955
				// or is it?
1956
				if (in_array($path, $modSettings['attachmentUploadDir']) || in_array($boarddir . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
1957
				{
1958
						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
1959
						continue;
1960
				}
1961
				elseif (empty($path))
1962
				{
1963
					// Ignore this and set $id to one less
1964
					continue;
1965
				}
1966
1967
				// OK, so let's try to create it then.
1968
				require_once($sourcedir . '/Subs-Attachments.php');
1969
				if (automanage_attachments_create_directory($path))
1970
					$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
1971
				else
1972
					$errors[] =  $path . ': ' . $txt[$context['dir_creation_error']];
1973
			}
1974
1975
			// Changing a directory name?
1976
			if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $path != $modSettings['attachmentUploadDir'][$id])
1977
			{
1978
				if ($path != $modSettings['attachmentUploadDir'][$id] && !is_dir($path))
1979
				{
1980
					if (!@rename($modSettings['attachmentUploadDir'][$id], $path))
1981
					{
1982
						$errors[] = $path . ': ' . $txt['attach_dir_no_rename'];
1983
						$path = $modSettings['attachmentUploadDir'][$id];
1984
					}
1985
				}
1986
				else
1987
				{
1988
					$errors[] = $path . ': ' . $txt['attach_dir_exists_msg'];
1989
					$path = $modSettings['attachmentUploadDir'][$id];
1990
				}
1991
1992
				// Update the base directory path
1993
				if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
1994
				{
1995
					$base = $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id] ? $path : $modSettings['basedirectory_for_attachments'];
1996
1997
					$modSettings['attachment_basedirectories'][$id] = $path;
1998
					updateSettings(array(
1999
						'attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories']),
2000
						'basedirectory_for_attachments' => $base,
2001
					));
2002
					$modSettings['attachment_basedirectories'] = json_decode($modSettings['attachment_basedirectories'], true);
2003
				}
2004
			}
2005
2006
			if (empty($path))
2007
			{
2008
				$path = $modSettings['attachmentUploadDir'][$id];
2009
2010
				// It's not a good idea to delete the current directory.
2011
				if ($id == (!empty($_POST['current_dir']) ? $_POST['current_dir'] : $modSettings['currentAttachmentUploadDir']))
2012
					$errors[] = $path . ': ' . $txt['attach_dir_is_current'];
2013
				// Or the current base directory
2014
				elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
2015
					$errors[] = $path . ': ' . $txt['attach_dir_is_current_bd'];
2016
				else
2017
				{
2018
					// Let's not try to delete a path with files in it.
2019
					$request = $smcFunc['db_query']('', '
2020
						SELECT COUNT(id_attach) AS num_attach
2021
						FROM {db_prefix}attachments
2022
						WHERE id_folder = {int:id_folder}',
2023
						array(
2024
							'id_folder' => (int) $id,
2025
						)
2026
					);
2027
2028
					list ($num_attach) = $smcFunc['db_fetch_row']($request);
2029
					$smcFunc['db_free_result']($request);
2030
2031
					// A check to see if it's a used base dir.
2032
					if (!empty($modSettings['attachment_basedirectories']))
2033
					{
2034
						// Count any sub-folders.
2035 View Code Duplication
						foreach ($modSettings['attachmentUploadDir'] as $sub)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2036
							if (strpos($sub, $path . DIRECTORY_SEPARATOR) !== false)
2037
								$num_attach++;
2038
					}
2039
2040
					// It's safe to delete. So try to delete the folder also
2041
					if ($num_attach == 0)
2042
					{
2043
						if (is_dir($path))
2044
							$doit = true;
2045
						elseif (is_dir($boarddir . DIRECTORY_SEPARATOR . $path))
2046
						{
2047
							$doit = true;
2048
							$path = $boarddir . DIRECTORY_SEPARATOR . $path;
2049
						}
2050
2051
						if (isset($doit) && realpath($path) != realpath($boarddir))
2052
						{
2053
							unlink($path . '/.htaccess');
2054
							unlink($path . '/index.php');
2055
							if (!@rmdir($path))
2056
								$error = $path . ': ' . $txt['attach_dir_no_delete'];
2057
						}
2058
2059
						// Remove it from the base directory list.
2060
						if (empty($error) && !empty($modSettings['attachment_basedirectories']))
2061
						{
2062
							unset($modSettings['attachment_basedirectories'][$id]);
2063
							updateSettings(array('attachment_basedirectories' => json_encode($modSettings['attachment_basedirectories'])));
2064
							$modSettings['attachment_basedirectories'] = json_decode($modSettings['attachment_basedirectories'], true);
2065
						}
2066
					}
2067
					else
2068
						$error = $path . ': ' . $txt['attach_dir_no_remove'];
2069
2070
					if (empty($error))
2071
						continue;
2072
					else
2073
						$errors[] = $error;
2074
				}
2075
			}
2076
2077
			$new_dirs[$id] = $path;
2078
		}
2079
2080
		// We need to make sure the current directory is right.
2081
		if (empty($_POST['current_dir']) && !empty($modSettings['currentAttachmentUploadDir']))
2082
			$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2083
2084
		// Find the current directory if there's no value carried,
2085
		if (empty($_POST['current_dir']) || empty($new_dirs[$_POST['current_dir']]))
2086
		{
2087
			if (array_key_exists($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
2088
				$_POST['current_dir'] = $modSettings['currentAttachmentUploadDir'];
2089
			else
2090
				$_POST['current_dir'] = max(array_keys($modSettings['attachmentUploadDir']));
2091
		}
2092
2093
		// If the user wishes to go back, update the last_dir array
2094
		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])))
2095
		{
2096 View Code Duplication
			if (!is_array($modSettings['last_attachments_directory']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2097
				$modSettings['last_attachments_directory'] = json_decode($modSettings['last_attachments_directory'], true);
2098
			$num = substr(strrchr($modSettings['attachmentUploadDir'][$_POST['current_dir']], '_'), 1);
2099
2100
			if (is_numeric($num))
2101
			{
2102
				// Need to find the base folder.
2103
				$bid = -1;
2104
				$use_subdirectories_for_attachments = 0;
2105
				if (!empty($modSettings['attachment_basedirectories']))
2106
					foreach ($modSettings['attachment_basedirectories'] as $bid => $base)
2107 View Code Duplication
						if (strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $base . DIRECTORY_SEPARATOR) !==false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2108
						{
2109
							$use_subdirectories_for_attachments = 1;
2110
							break;
2111
						}
2112
2113 View Code Duplication
				if ($use_subdirectories_for_attachments == 0 && strpos($modSettings['attachmentUploadDir'][$_POST['current_dir']], $boarddir . DIRECTORY_SEPARATOR) !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2541
			if (strpos($sub, $dir . DIRECTORY_SEPARATOR) !== false)
2542
				$expected_dirs++;
2543
2544
		if (!is_dir($dir))
2545
			$status = 'does_not_exist';
2546
		elseif (!is_writeable($dir))
2547
			$status = 'not_writable';
2548
		else
2549
			$status = 'ok';
2550
2551
		$basedirs[] = array(
2552
			'id' => $id,
2553
			'current' => $dir == $modSettings['basedirectory_for_attachments'],
2554
			'path' => $expected_dirs > 0 ? $dir : ('<input type="text" name="base_dir[' . $id . ']" value="' . $dir . '" size="40">'),
2555
			'num_dirs' => $expected_dirs,
2556
			'status' => $status == 'ok' ? $txt['attach_dir_ok'] : ('<span class="error">' . $txt['attach_dir_' . $status] . '</span>'),
2557
		);
2558
	}
2559
2560
	if (isset($_REQUEST['new_base_path']))
2561
		$basedirs[] = array(
2562
			'id' => '',
2563
			'current' => false,
2564
			'path' => '<input type="text" name="new_base_dir" value="" size="40">',
2565
			'num_dirs' => '',
2566
			'status' => '',
2567
		);
2568
2569
	return $basedirs;
2570
}
2571
2572
/**
2573
 * Checks the status of an attachment directory and returns an array
2574
 *  of the status key, if that status key signifies an error, and
2575
 *  the file count.
2576
 *
2577
 * @param string $dir The directory to check
2578
 * @param int $expected_files How many files should be in that directory
2579
 * @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
2580
 */
2581
function attachDirStatus($dir, $expected_files)
2582
{
2583
	if (!is_dir($dir))
2584
		return array('does_not_exist', true, '');
2585
	elseif (!is_writable($dir))
2586
		return array('not_writable', true, '');
2587
2588
	// Everything is okay so far, start to scan through the directory.
2589
	$num_files = 0;
2590
	$dir_handle = dir($dir);
2591
	while ($file = $dir_handle->read())
2592
	{
2593
		// Now do we have a real file here?
2594
		if (in_array($file, array('.', '..', '.htaccess', 'index.php')))
2595
			continue;
2596
2597
		$num_files++;
2598
	}
2599
	$dir_handle->close();
2600
2601
	if ($num_files < $expected_files)
2602
		return array('files_missing', true, $num_files);
2603
	// Empty?
2604
	elseif ($expected_files == 0)
2605
		return array('unused', false, $num_files);
2606
	// All good!
2607
	else
2608
		return array('ok', false, $num_files);
2609
}
2610
2611
/**
2612
 * Maintance function to move attachments from one directory to another
2613
 */
2614
function TransferAttachments()
2615
{
2616
	global $modSettings, $smcFunc, $sourcedir, $txt, $boarddir;
2617
2618
	checkSession();
2619
2620
	$modSettings['attachmentUploadDir'] = json_decode($modSettings['attachmentUploadDir'], true);
2621
	if (!empty($modSettings['attachment_basedirectories']))
2622
		$modSettings['attachment_basedirectories'] = json_decode($modSettings['attachment_basedirectories'], true);
2623
	else
2624
		$modSettings['basedirectory_for_attachments'] = array();
2625
2626
	$_POST['from'] = (int) $_POST['from'];
2627
	$_POST['auto'] = !empty($_POST['auto']) ? (int) $_POST['auto'] : 0;
2628
	$_POST['to'] = (int) $_POST['to'];
2629
	$start = !empty($_POST['empty_it']) ? 0 : $modSettings['attachmentDirFileLimit'];
2630
	$_SESSION['checked'] = !empty($_POST['empty_it']) ? true : false;
2631
	$limit = 501;
2632
	$results = array();
2633
	$dir_files = 0;
2634
	$current_progress = 0;
2635
	$total_moved = 0;
2636
	$total_not_moved = 0;
2637
2638
	if (empty($_POST['from']) || (empty($_POST['auto']) && empty($_POST['to'])))
2639
		$results[] = $txt['attachment_transfer_no_dir'];
2640
2641
	if ($_POST['from'] == $_POST['to'])
2642
		$results[] = $txt['attachment_transfer_same_dir'];
2643
2644
	if (empty($results))
2645
	{
2646
		// Get the total file count for the progess bar.
2647
		$request = $smcFunc['db_query']('', '
2648
			SELECT COUNT(*)
2649
			FROM {db_prefix}attachments
2650
			WHERE id_folder = {int:folder_id}
2651
				AND attachment_type != {int:attachment_type}',
2652
			array(
2653
				'folder_id' => $_POST['from'],
2654
				'attachment_type' => 1,
2655
			)
2656
		);
2657
		list ($total_progress) = $smcFunc['db_fetch_row']($request);
2658
		$smcFunc['db_free_result']($request);
2659
		$total_progress -= $start;
2660
2661
		if ($total_progress < 1)
2662
			$results[] = $txt['attachment_transfer_no_find'];
2663
	}
2664
2665
	if (empty($results))
2666
	{
2667
		// Where are they going?
2668
		if (!empty($_POST['auto']))
2669
		{
2670
			require_once($sourcedir . '/Subs-Attachments.php');
2671
2672
			$modSettings['automanage_attachments'] = 1;
2673
			$modSettings['use_subdirectories_for_attachments'] = $_POST['auto'] == -1 ? 0 : 1;
2674
			$modSettings['basedirectory_for_attachments'] = $_POST['auto'] > 0 ? $modSettings['attachmentUploadDir'][$_POST['auto']] : $modSettings['basedirectory_for_attachments'];
2675
2676
			automanage_attachments_check_directory();
2677
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2678
		}
2679
		else
2680
			$new_dir = $_POST['to'];
2681
2682
		$modSettings['currentAttachmentUploadDir'] = $new_dir;
2683
2684
		$break = false;
2685
		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...
2686
		{
2687
			@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...
2688
			if (function_exists('apache_reset_timeout'))
2689
				@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...
2690
2691
			// If limits are set, get the file count and size for the destination folder
2692
			if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])))
2693
			{
2694
				$request = $smcFunc['db_query']('', '
2695
					SELECT COUNT(*), SUM(size)
2696
					FROM {db_prefix}attachments
2697
					WHERE id_folder = {int:folder_id}
2698
						AND attachment_type != {int:attachment_type}',
2699
					array(
2700
						'folder_id' => $new_dir,
2701
						'attachment_type' => 1,
2702
					)
2703
				);
2704
				list ($dir_files, $dir_size) = $smcFunc['db_fetch_row']($request);
2705
				$smcFunc['db_free_result']($request);
2706
			}
2707
2708
			// Find some attachments to move
2709
			$request = $smcFunc['db_query']('', '
2710
				SELECT id_attach, filename, id_folder, file_hash, size
2711
				FROM {db_prefix}attachments
2712
				WHERE id_folder = {int:folder}
2713
					AND attachment_type != {int:attachment_type}
2714
				LIMIT {int:start}, {int:limit}',
2715
				array(
2716
					'folder' => $_POST['from'],
2717
					'attachment_type' => 1,
2718
					'start' => $start,
2719
					'limit' => $limit,
2720
				)
2721
			);
2722
2723
			if ($smcFunc['db_num_rows']($request) === 0)
2724
			{
2725
				if (empty($current_progress))
2726
					$results[] = $txt['attachment_transfer_no_find'];
2727
				break;
2728
			}
2729
2730
			if ($smcFunc['db_num_rows']($request) < $limit)
2731
				$break = true;
2732
2733
			// Move them
2734
			$moved = array();
2735
			while ($row = $smcFunc['db_fetch_assoc']($request))
2736
			{
2737
				$source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
2738
				$dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source);
2739
2740
				// Size and file count check
2741
				if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
2742
				{
2743
					$dir_files++;
2744
					$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...
2745
2746
					// If we've reached a limit. Do something.
2747
					if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || (!empty($modSettings['attachmentDirFileLimit']) && $dir_files >  $modSettings['attachmentDirFileLimit']))
2748
					{
2749
						if (!empty($_POST['auto']))
2750
						{
2751
							// Since we're in auto mode. Create a new folder and reset the counters.
2752
							automanage_attachments_by_space();
2753
2754
							$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2755
							if (!empty($total_not_moved))
2756
								$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2757
2758
							$dir_files = 0;
2759
							$total_moved = 0;
2760
							$total_not_moved = 0;
2761
2762
							$break = false;
2763
							break;
2764
						}
2765
						else
2766
						{
2767
							// Hmm, not in auto. Time to bail out then...
2768
							$results[] = $txt['attachment_transfer_no_room'];
2769
							$break = true;
2770
							break;
2771
						}
2772
					}
2773
				}
2774
2775
				if (@rename($source, $dest))
2776
				{
2777
					$total_moved++;
2778
					$current_progress++;
2779
					$moved[] = $row['id_attach'];
2780
				}
2781
				else
2782
					$total_not_moved++;
2783
			}
2784
			$smcFunc['db_free_result']($request);
2785
2786
			if (!empty($moved))
2787
			{
2788
				// Update the database
2789
				$smcFunc['db_query']('', '
2790
					UPDATE {db_prefix}attachments
2791
					SET id_folder = {int:new}
2792
					WHERE id_attach IN ({array_int:attachments})',
2793
					array(
2794
						'attachments' => $moved,
2795
						'new' => $new_dir,
2796
					)
2797
				);
2798
			}
2799
2800
			$moved = array();
0 ignored issues
show
Unused Code introduced by
$moved is not used, you could remove the assignment.

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

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

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

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

Loading history...
2801
			$new_dir = $modSettings['currentAttachmentUploadDir'];
2802
2803
			// Create the progress bar.
2804
			if (!$break)
2805
			{
2806
				$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...
2807
				$prog_bar = '
2808
					<div class="progress_bar">
2809
						<div class="full_bar">' . $percent_done . '%</div>
2810
						<div class="green_percent" style="width: ' . $percent_done . '%;">&nbsp;</div>
2811
					</div>';
2812
				// Write it to a file so it can be displayed
2813
				$fp = fopen($boarddir . '/progress.php', "w");
2814
				fwrite($fp, $prog_bar);
2815
				fclose($fp);
2816
				usleep(500000);
2817
			}
2818
		}
2819
2820
		$results[] = sprintf($txt['attachments_transferred'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
2821
		if (!empty($total_not_moved))
2822
			$results[] = sprintf($txt['attachments_not_transferred'], $total_not_moved);
2823
	}
2824
2825
	$_SESSION['results'] = $results;
2826
	if (file_exists($boarddir . '/progress.php'))
2827
		unlink($boarddir . '/progress.php');
2828
2829
	redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer');
2830
}
2831
2832
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

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

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

Loading history...