Completed
Pull Request — patch_1-1-4 (#3188)
by Spuds
09:56
created

ManageAttachments_Controller::_settings()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 108
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 75
CRAP Score 31.0168

Importance

Changes 0
Metric Value
cc 31
eloc 76
nc 47185920
nop 0
dl 0
loc 108
ccs 75
cts 77
cp 0.974
crap 31.0168
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Handles the job of attachment and avatar maintenance /management.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.4
15
 *
16
 */
17
18
/**
19
 * This is the attachments and avatars controller class.
20
 * It is doing the job of attachments and avatars maintenance and management.
21
 *
22
 * @package Attachments
23
 */
24
class ManageAttachments_Controller extends Action_Controller
25
{
26
	/**
27
	 * Loop counter for paused attachment maintenance actions
28
	 * @var int
29
	 */
30
	public $step;
31
32
	/**
33
	 * substep counter for paused attachment maintenance actions
34
	 * @var int
35
	 */
36
	public $substep;
37
38
	/**
39
	 * Substep at the beginning of a maintenance loop
40
	 * @var int
41
	 */
42
	public $starting_substep;
43
44
	/**
45
	 * Current directory key being processed
46
	 * @var int
47
	 */
48
	public $current_dir;
49
50
	/**
51
	 * Current base directory key being processed
52
	 * @var int
53
	 */
54
	public $current_base_dir;
55
56
	/**
57
	 * Used during transfer of files
58
	 * @var string
59
	 */
60
	public $from;
61
62
	/**
63
	 * Type of attachment management in use
64
	 * @var string
65
	 */
66
	public $auto;
67
68
	/**
69
	 * Destination when transferring attachments
70
	 * @var string
71
	 */
72
	public $to;
73
74 1
	public function pre_dispatch()
75
	{
76
		// These get used often enough that it makes sense to include them for every action
77 1
		require_once(SUBSDIR . '/Attachments.subs.php');
78 1
		require_once(SUBSDIR . '/ManageAttachments.subs.php');
79 1
	}
80
81
	/**
82
	 * The main 'Attachments and Avatars' admin.
83
	 *
84
	 * What it does:
85
	 *
86
	 * - This method is the entry point for index.php?action=admin;area=manageattachments
87
	 * and it calls a function based on the sub-action.
88
	 * - It requires the manage_attachments permission.
89
	 *
90
	 * @event integrate_sa_manage_attachments
91
	 * @uses ManageAttachments template.
92
	 * @uses Admin language file.
93
	 * @uses template layer 'manage_files' for showing the tab bar.
94
	 *
95
	 * @see Action_Controller::action_index()
96
	 */
97
	public function action_index()
98
	{
99
		global $txt, $context;
100
101
		// You have to be able to moderate the forum to do this.
102
		isAllowedTo('manage_attachments');
103
104
		// Setup the template stuff we'll probably need.
105
		loadTemplate('ManageAttachments');
106
107
		// If they want to delete attachment(s), delete them. (otherwise fall through..)
108
		$subActions = array(
109
			'attachments' => array($this, 'action_attachSettings_display'),
110
			'avatars' => array(
111
				'controller' => 'ManageAvatars_Controller',
112
				'function' => 'action_index'),
113
			'attachpaths' => array($this, 'action_attachpaths'),
114
			'browse' => array($this, 'action_browse'),
115
			'byAge' => array($this, 'action_byAge'),
116
			'bySize' => array($this, 'action_bySize'),
117
			'maintenance' => array($this, 'action_maintenance'),
118
			'moveAvatars' => array($this, 'action_moveAvatars'),
119
			'repair' => array($this, 'action_repair'),
120
			'remove' => array($this, 'action_remove'),
121
			'removeall' => array($this, 'action_removeall'),
122
			'transfer' => array($this, 'action_transfer'),
123
		);
124
125
		// Get ready for some action
126
		$action = new Action('manage_attachments');
127
128
		// Default page title is good.
129
		$context['page_title'] = $txt['attachments_avatars'];
130
131
		// This uses admin tabs - as it should!
132
		$context[$context['admin_menu_name']]['tab_data'] = array(
133
			'title' => $txt['attachments_avatars'],
134
			'help' => 'manage_files',
135
			'description' => $txt['attachments_desc'],
136
		);
137
138
		// Get the subAction, call integrate_sa_manage_attachments
139
		$subAction = $action->initialize($subActions, 'browse');
140
		$context['sub_action'] = $subAction;
141
142
		// Finally go to where we want to go
143
		$action->dispatch($subAction);
144
	}
145
146
	/**
147
	 * Allows to show/change attachment settings.
148
	 *
149
	 * - This is the default sub-action of the 'Attachments and Avatars' center.
150
	 * - Called by index.php?action=admin;area=manageattachments;sa=attachments.
151
	 *
152
	 * @event integrate_save_attachment_settings
153
	 * @uses 'attachments' sub template.
154
	 */
155
	public function action_attachSettings_display()
156
	{
157
		global $modSettings, $scripturl, $context;
158
159
		// initialize the form
160
		$settingsForm = new Settings_Form(Settings_Form::DB_ADAPTER);
161
162
		// Initialize settings
163
		$settingsForm->setConfigVars($this->_settings());
164
165
		addInlineJavascript('
166
	var storing_type = document.getElementById(\'automanage_attachments\'),
167
		base_dir = document.getElementById(\'use_subdirectories_for_attachments\');
168
169
	createEventListener(storing_type)
170
	storing_type.addEventListener("change", toggleSubDir, false);
171
	createEventListener(base_dir)
172
	base_dir.addEventListener("change", toggleSubDir, false);
173
	toggleSubDir();', true);
174
175
		// Saving settings?
176
		if (isset($this->_req->query->save))
177
		{
178
			checkSession();
179
180
			if (!empty($this->_req->post->attachmentEnable))
181
			{
182
				enableModules('attachments', array('post'));
183
			}
184
			else
185
			{
186
				disableModules('attachments', array('post'));
187
			}
188
189
			// Changing the attachment upload directory
190
			if (isset($this->_req->post->attachmentUploadDir))
191
			{
192
				if (!empty($this->_req->post->attachmentUploadDir) && file_exists($modSettings['attachmentUploadDir']) && $modSettings['attachmentUploadDir'] != $this->_req->post->attachmentUploadDir)
193
					rename($modSettings['attachmentUploadDir'], $this->_req->post->attachmentUploadDir);
194
195
				$modSettings['attachmentUploadDir'] = array(1 => $this->_req->post->attachmentUploadDir);
196
				$this->_req->post->attachmentUploadDir = serialize($modSettings['attachmentUploadDir']);
197
			}
198
199
			// Adding / changing the sub directory's for attachments
200
			if (!empty($this->_req->post->use_subdirectories_for_attachments))
201
			{
202
				// Make sure we have a base directory defined
203
				if (isset($this->_req->post->use_subdirectories_for_attachments) && empty($this->_req->post->basedirectory_for_attachments))
204
					$this->_req->post->basedirectory_for_attachments = (!empty($modSettings['basedirectory_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : BOARDDIR);
205
206
				if (!empty($modSettings['attachment_basedirectories']))
207
				{
208
					if (!is_array($modSettings['attachment_basedirectories']))
209
						$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
210
				}
211
				else
212
					$modSettings['attachment_basedirectories'] = array();
213
214
				if (!empty($this->_req->post->basedirectory_for_attachments) && !in_array($this->_req->post->basedirectory_for_attachments, $modSettings['attachment_basedirectories']))
215
				{
216
					$currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir'];
217
218 View Code Duplication
					if (!in_array($this->_req->post->basedirectory_for_attachments, $modSettings['attachmentUploadDir']))
219
					{
220
						if (!automanage_attachments_create_directory($this->_req->post->basedirectory_for_attachments))
221
							$this->_req->post->basedirectory_for_attachments = $modSettings['basedirectory_for_attachments'];
222
					}
223
224
					if (!in_array($this->_req->post->basedirectory_for_attachments, $modSettings['attachment_basedirectories']))
225
					{
226
						$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $this->_req->post->basedirectory_for_attachments;
227
						updateSettings(array(
228
							'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
229
							'currentAttachmentUploadDir' => $currentAttachmentUploadDir,
230
						));
231
232
						$this->_req->post->use_subdirectories_for_attachments = 1;
233
						$this->_req->post->attachmentUploadDir = serialize($modSettings['attachmentUploadDir']);
234
					}
235
				}
236
			}
237
238
			call_integration_hook('integrate_save_attachment_settings');
239
240
			$settingsForm->setConfigValues((array) $this->_req->post);
241
			$settingsForm->save();
242
			redirectexit('action=admin;area=manageattachments;sa=attachments');
243
		}
244
245
		$context['post_url'] = $scripturl . '?action=admin;area=manageattachments;save;sa=attachments';
246
		$settingsForm->prepare();
247
248
		$context['sub_template'] = 'show_settings';
249
	}
250
251
	/**
252
	 * Retrieve and return the administration settings for attachments.
253
	 *
254
	 * @event integrate_modify_attachment_settings
255
	 */
256 1
	private function _settings()
257
	{
258 1
		global $modSettings, $txt, $scripturl, $context;
259
260
		// Get the current attachment directory.
261 1
		$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
262 1
		$context['attachmentUploadDir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
263
264
		// First time here?
265 1
		if (empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && count($modSettings['attachmentUploadDir']) == 1)
266 1
			$modSettings['attachmentUploadDir'] = $modSettings['attachmentUploadDir'][1];
267
268
		// If not set, show a default path for the base directory
269 1
		if (!isset($this->_req->query->save) && empty($modSettings['basedirectory_for_attachments']))
270 1
			$modSettings['basedirectory_for_attachments'] = $context['attachmentUploadDir'];
271
272 1
		$context['valid_upload_dir'] = is_dir($context['attachmentUploadDir']) && is_writable($context['attachmentUploadDir']);
273
274 1
		if (!empty($modSettings['automanage_attachments']))
275 1
			$context['valid_basedirectory'] = !empty($modSettings['basedirectory_for_attachments']) && is_writable($modSettings['basedirectory_for_attachments']);
276
		else
277 1
			$context['valid_basedirectory'] = true;
278
279
		// A bit of razzle dazzle with the $txt strings. :)
280 1
		$txt['basedirectory_for_attachments_warning'] = str_replace('{attach_repair_url}', $scripturl . '?action=admin;area=manageattachments;sa=attachpaths', $txt['basedirectory_for_attachments_warning']);
281 1
		$txt['attach_current_dir_warning'] = str_replace('{attach_repair_url}', $scripturl . '?action=admin;area=manageattachments;sa=attachpaths', $txt['attach_current_dir_warning']);
282
283 1
		$txt['attachment_path'] = $context['attachmentUploadDir'];
284 1
		$txt['basedirectory_for_attachments_path'] = isset($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
285 1
		$txt['use_subdirectories_for_attachments_note'] = empty($modSettings['attachment_basedirectories']) || empty($modSettings['use_subdirectories_for_attachments']) ? $txt['use_subdirectories_for_attachments_note'] : '';
286 1
		$txt['attachmentUploadDir_multiple_configure'] = '<a class="linkbutton" href="' . $scripturl . '?action=admin;area=manageattachments;sa=attachpaths">' . $txt['attachmentUploadDir_multiple_configure'] . '</a>';
287 1
		$txt['attach_current_dir'] = empty($modSettings['automanage_attachments']) ? $txt['attach_current_dir'] : $txt['attach_last_dir'];
288 1
		$txt['attach_current_dir_warning'] = $txt['attach_current_dir'] . $txt['attach_current_dir_warning'];
289 1
		$txt['basedirectory_for_attachments_warning'] = $txt['basedirectory_for_attachments_current'] . $txt['basedirectory_for_attachments_warning'];
290
291
		// Perform a test to see if the GD module or ImageMagick are installed.
292 1
		$testImg = get_extension_funcs('gd') || class_exists('Imagick');
293
294
		// See if we can find if the server is set up to support the attachment limits
295 1
		$post_max_size = ini_get('post_max_size');
296 1
		$upload_max_filesize = ini_get('upload_max_filesize');
297 1
		$testPM = !empty($post_max_size) ? (memoryReturnBytes($post_max_size) >= (isset($modSettings['attachmentPostLimit']) ? $modSettings['attachmentPostLimit'] * 1024 : 0)) : true;
298 1
		$testUM = !empty($upload_max_filesize) ? (memoryReturnBytes($upload_max_filesize) >= (isset($modSettings['attachmentSizeLimit']) ? $modSettings['attachmentSizeLimit'] * 1024 : 0)) : true;
299 1
		$testImgRotate = class_exists('Imagick') || (get_extension_funcs('gd') && function_exists('exif_read_data'));
300
301
		$config_vars = array(
302 1
			array('title', 'attachment_manager_settings'),
303
				// Are attachments enabled?
304 1
				array('select', 'attachmentEnable', array($txt['attachmentEnable_deactivate'], $txt['attachmentEnable_enable_all'], $txt['attachmentEnable_disable_new'])),
305 1
			'',
306
				// Extension checks etc.
307 1
				array('check', 'attachmentRecodeLineEndings'),
308 1
			'',
309
				// Directory and size limits.
310 1
				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'])),
311 1
				array('check', 'use_subdirectories_for_attachments', 'subtext' => $txt['use_subdirectories_for_attachments_note']),
312 1
				(empty($modSettings['attachment_basedirectories'])
313 1
					? array('text', 'basedirectory_for_attachments', 40,)
314 1
					: array('var_message', 'basedirectory_for_attachments', 'message' => 'basedirectory_for_attachments_path', 'invalid' => empty($context['valid_basedirectory']), 'text_label' => (!empty($context['valid_basedirectory'])
315
						? $txt['basedirectory_for_attachments_current']
316
						: $txt['basedirectory_for_attachments_warning']))
317 1
				),
318 1
				empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && (is_array($modSettings['attachmentUploadDir']) && count($modSettings['attachmentUploadDir']) == 1)
319 1
					? array('text', 'attachmentUploadDir', 'postinput' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir'])
320 1
					: array('var_message', 'attach_current_directory', 'postinput' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), 'text_label' => (!empty($context['valid_upload_dir'])
321 1
						? $txt['attach_current_dir']
322 1
						: $txt['attach_current_dir_warning'])
323 1
				),
324 1
				array('int', 'attachmentDirFileLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
325 1
				array('int', 'attachmentDirSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
326 1
			'',
327
				// Posting limits
328 1
				array('warning', empty($testPM) ? 'attachment_postsize_warning' : ''),
329 1
				array('int', 'attachmentPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
330 1
				array('warning', empty($testUM) ? 'attachment_filesize_warning' : ''),
331 1
				array('int', 'attachmentSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']),
332 1
				array('int', 'attachmentNumPerPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6),
333 1
				array('check', 'attachment_autorotate', 'postinput' => empty($testImgRotate) ? $txt['attachment_autorotate_na'] : ''),
334
			// Security Items
335 1
			array('title', 'attachment_security_settings'),
336
				// Extension checks etc.
337 1
				array('check', 'attachmentCheckExtensions'),
338 1
				array('text', 'attachmentExtensions', 40),
339 1
			'',
340
				// Image checks.
341 1
				array('warning', empty($testImg) ? 'attachment_img_enc_warning' : ''),
342 1
				array('check', 'attachment_image_reencode'),
343 1
			'',
344 1
				array('warning', 'attachment_image_paranoid_warning'),
345 1
				array('check', 'attachment_image_paranoid'),
346
			// Thumbnail settings.
347 1
			array('title', 'attachment_thumbnail_settings'),
348 1
				array('check', 'attachmentShowImages'),
349 1
				array('check', 'attachmentThumbnails'),
350 1
				array('check', 'attachment_thumb_png'),
351 1
				array('check', 'attachment_thumb_memory', 'subtext' => $txt['attachment_thumb_memory_note1'], 'postinput' => $txt['attachment_thumb_memory_note2']),
352 1
				array('text', 'attachmentThumbWidth', 6),
353 1
				array('text', 'attachmentThumbHeight', 6),
354 1
			'',
355 1
				array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']),
356 1
				array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']),
357 1
		);
358
359
		// Add new settings with a nice hook, makes them available for admin settings search as well
360 1
		call_integration_hook('integrate_modify_attachment_settings', array(&$config_vars));
361
362 1
		return $config_vars;
363
	}
364
365
	/**
366
	 * Public method to return the config settings, used for admin search
367
	 */
368 1
	public function settings_search()
369
	{
370 1
		return $this->_settings();
371
	}
372
373
	/**
374
	 * Show a list of attachment or avatar files.
375
	 *
376
	 * - Called by ?action=admin;area=manageattachments;sa=browse for attachments
377
	 * and ?action=admin;area=manageattachments;sa=browse;avatars for avatars.
378
	 * - Allows sorting by name, date, size and member.
379
	 * - Paginates results.
380
	 *
381
	 *  @uses the 'browse' sub template
382
	 */
383
	public function action_browse()
384
	{
385
		global $context, $txt, $scripturl, $modSettings;
386
387
		// Attachments or avatars?
388
		$context['browse_type'] = isset($this->_req->query->avatars) ? 'avatars' : (isset($this->_req->query->thumbs) ? 'thumbs' : 'attachments');
389
390
		// Set the options for the list component.
391
		$listOptions = array(
392
			'id' => 'attach_browse',
393
			'title' => $txt['attachment_manager_browse_files'],
394
			'items_per_page' => $modSettings['defaultMaxMessages'],
395
			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=browse' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
396
			'default_sort_col' => 'name',
397
			'no_items_label' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ($context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments')) . '_no_entries'],
398
			'get_items' => array(
399
				'function' => 'list_getFiles',
400
				'params' => array(
401
					$context['browse_type'],
402
				),
403
			),
404
			'get_count' => array(
405
				'function' => 'list_getNumFiles',
406
				'params' => array(
407
					$context['browse_type'],
408
				),
409
			),
410
			'columns' => array(
411
				'name' => array(
412
					'header' => array(
413
						'value' => $txt['attachment_name'],
414
						'class' => 'grid50',
415
					),
416
					'data' => array(
417
						'function' => function ($rowData) {
418
							global $modSettings, $context, $scripturl;
419
420
							$link = '<a href="';
421
422
							// In case of a custom avatar URL attachments have a fixed directory.
423
							if ($rowData['attachment_type'] == 1)
424
								$link .= sprintf('%1$s/%2$s', $modSettings['custom_avatar_url'], $rowData['filename']);
425
426
							// By default avatars are downloaded almost as attachments.
427
							elseif ($context['browse_type'] == 'avatars')
428
								$link .= sprintf('%1$s?action=dlattach;type=avatar;attach=%2$d', $scripturl, $rowData['id_attach']);
429
430
							// Normal attachments are always linked to a topic ID.
431
							else
432
								$link .= sprintf('%1$s?action=dlattach;topic=%2$d.0;attach=%3$d', $scripturl, $rowData['id_topic'], $rowData['id_attach']);
433
434
							$link .= '"';
435
436
							// Show a popup on click if it's a picture and we know its dimensions.
437
							if (!empty($rowData['width']) && !empty($rowData['height']))
438
								$link .= sprintf(' onclick="return reqWin(this.href' . ($rowData['attachment_type'] == 1 ? '' : ' + \';image\'') . ', %1$d, %2$d, true);"', $rowData['width'] + 20, $rowData['height'] + 20);
439
440
							$link .= sprintf('>%1$s</a>', preg_replace('~&amp;#(\\\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\\\1;', htmlspecialchars($rowData['filename'], ENT_COMPAT, 'UTF-8')));
441
442
							// Show the dimensions.
443
							if (!empty($rowData['width']) && !empty($rowData['height']))
444
								$link .= sprintf(' <span class="smalltext">%1$dx%2$d</span>', $rowData['width'], $rowData['height']);
445
446
							return $link;
447
						},
448
					),
449
					'sort' => array(
450
						'default' => 'a.filename',
451
						'reverse' => 'a.filename DESC',
452
					),
453
				),
454
				'filesize' => array(
455
					'header' => array(
456
						'value' => $txt['attachment_file_size'],
457
						'class' => 'nowrap',
458
					),
459
					'data' => array(
460
						'function' => function ($rowData) {
461
							return byte_format($rowData['size']);
462
						},
463
					),
464
					'sort' => array(
465
						'default' => 'a.size',
466
						'reverse' => 'a.size DESC',
467
					),
468
				),
469
				'member' => array(
470
					'header' => array(
471
						'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_member'] : $txt['posted_by'],
472
						'class' => 'nowrap',
473
					),
474
					'data' => array(
475
						'function' => function ($rowData) {
476
							global $scripturl;
477
478
							// In case of an attachment, return the poster of the attachment.
479
							if (empty($rowData['id_member']))
480
								return htmlspecialchars($rowData['poster_name'], ENT_COMPAT, 'UTF-8');
481
482
							// Otherwise it must be an avatar, return the link to the owner of it.
483
							else
484
								return sprintf('<a href="%1$s?action=profile;u=%2$d">%3$s</a>', $scripturl, $rowData['id_member'], $rowData['poster_name']);
485
						},
486
					),
487
					'sort' => array(
488
						'default' => 'mem.real_name',
489
						'reverse' => 'mem.real_name DESC',
490
					),
491
				),
492
				'date' => array(
493
					'header' => array(
494
						'value' => $context['browse_type'] == 'avatars' ? $txt['attachment_manager_last_active'] : $txt['date'],
495
						'class' => 'nowrap',
496
					),
497
					'data' => array(
498
						'function' => function ($rowData) {
499
							global $txt, $context, $scripturl;
500
501
							// The date the message containing the attachment was posted or the owner of the avatar was active.
502
							$date = empty($rowData['poster_time']) ? $txt['never'] : standardTime($rowData['poster_time']);
503
504
							// Add a link to the topic in case of an attachment.
505
							if ($context['browse_type'] !== 'avatars')
506
								$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']);
507
508
							return $date;
509
							},
510
					),
511
					'sort' => array(
512
						'default' => $context['browse_type'] === 'avatars' ? 'mem.last_login' : 'm.id_msg',
513
						'reverse' => $context['browse_type'] === 'avatars' ? 'mem.last_login DESC' : 'm.id_msg DESC',
514
					),
515
				),
516
				'downloads' => array(
517
					'header' => array(
518
						'value' => $txt['downloads'],
519
						'class' => 'nowrap',
520
					),
521
					'data' => array(
522
						'db' => 'downloads',
523
						'comma_format' => true,
524
					),
525
					'sort' => array(
526
						'default' => 'a.downloads',
527
						'reverse' => 'a.downloads DESC',
528
					),
529
				),
530
				'check' => array(
531
					'header' => array(
532
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
533
						'class' => 'centertext',
534
					),
535
					'data' => array(
536
						'sprintf' => array(
537
							'format' => '<input type="checkbox" name="remove[%1$d]" class="input_check" />',
538
							'params' => array(
539
								'id_attach' => false,
540
							),
541
						),
542
						'class' => 'centertext',
543
					),
544
				),
545
			),
546
			'form' => array(
547
				'href' => $scripturl . '?action=admin;area=manageattachments;sa=remove' . ($context['browse_type'] === 'avatars' ? ';avatars' : ($context['browse_type'] === 'thumbs' ? ';thumbs' : '')),
548
				'include_sort' => true,
549
				'include_start' => true,
550
				'hidden_fields' => array(
551
					'type' => $context['browse_type'],
552
				),
553
			),
554
			'additional_rows' => array(
555
				array(
556
					'position' => 'below_table_data',
557
					'value' => '<input type="submit" name="remove_submit" class="right_submit" value="' . $txt['quickmod_delete_selected'] . '" onclick="return confirm(\'' . $txt['confirm_delete_attachments'] . '\');" />',
558
				),
559
			),
560
			'list_menu' => array(
561
				'show_on' => 'top',
562
				'links' => array(
563
					array(
564
						'href' => $scripturl . '?action=admin;area=manageattachments;sa=browse',
565
						'is_selected' => $context['browse_type'] === 'attachments',
566
						'label' => $txt['attachment_manager_attachments']
567
					),
568
					array(
569
						'href' => $scripturl . '?action=admin;area=manageattachments;sa=browse;avatars',
570
						'is_selected' => $context['browse_type'] === 'avatars',
571
						'label' => $txt['attachment_manager_avatars']
572
					),
573
					array(
574
						'href' => $scripturl . '?action=admin;area=manageattachments;sa=browse;thumbs',
575
						'is_selected' => $context['browse_type'] === 'thumbs',
576
						'label' => $txt['attachment_manager_thumbs']
577
					),
578
				),
579
			),
580
		);
581
582
		// Create the list.
583
		createList($listOptions);
584
	}
585
586
	/**
587
	 * Show several file maintenance options.
588
	 *
589
	 * What it does:
590
	 *
591
	 * - Called by ?action=admin;area=manageattachments;sa=maintain.
592
	 * - Calculates file statistics (total file size, number of attachments,
593
	 * number of avatars, attachment space available).
594
	 *
595
	 * @uses the 'maintenance' sub template.
596
	 */
597
	public function action_maintenance()
598
	{
599
		global $context, $modSettings;
600
601
		loadTemplate('ManageAttachments');
602
		$context['sub_template'] = 'maintenance';
603
604
		// We need our attachments directories...
605
		$attach_dirs = getAttachmentDirs();
606
607
		// Get the number of attachments...
608
		$context['num_attachments'] = comma_format(getAttachmentCount(), 0);
609
610
		// Also get the avatar amount...
611
		$context['num_avatars'] = comma_format(getAvatarCount(), 0);
612
613
		// Total size of attachments
614
		$context['attachment_total_size'] = overallAttachmentsSize();
615
616
		// Total size and files from the current attachments dir.
617
		$current_dir = currentAttachDirProperties();
618
619
		// If they specified a limit only....
620
		if (!empty($modSettings['attachmentDirSizeLimit']))
621
			$context['attachment_space'] = comma_format(max($modSettings['attachmentDirSizeLimit'] - $current_dir['size'], 0), 2);
622
		$context['attachment_current_size'] = byte_format($current_dir['size']);
623
624
		if (!empty($modSettings['attachmentDirFileLimit']))
625
			$context['attachment_files'] = comma_format(max($modSettings['attachmentDirFileLimit'] - $current_dir['files'], 0), 0);
626
		$context['attachment_current_files'] = comma_format($current_dir['files'], 0);
627
628
		$context['attach_multiple_dirs'] = count($attach_dirs) > 1 ? true : false;
629
		$context['attach_dirs'] = $attach_dirs;
630
		$context['base_dirs'] = !empty($modSettings['attachment_basedirectories']) ? Util::unserialize($modSettings['attachment_basedirectories']) : array();
631
		$context['checked'] = $this->_req->getSession('checked', true);
632
		if (!empty($this->_req->session->results))
633
		{
634
			$context['results'] = implode('<br />', $this->_req->session->results);
635
			unset($_SESSION['results']);
636
		}
637
	}
638
639
	/**
640
	 * Move avatars from their current location, to the custom_avatar_dir folder.
641
	 *
642
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=action_moveAvatars.
643
	 */
644
	public function action_moveAvatars()
645
	{
646
		global $modSettings;
647
648
		// First make sure the custom avatar dir is writable.
649
		if (!is_writable($modSettings['custom_avatar_dir']))
650
		{
651
			// Try to fix it.
652
			@chmod($modSettings['custom_avatar_dir'], 0777);
653
654
			// Guess that didn't work?
655
			if (!is_writable($modSettings['custom_avatar_dir']))
656
				throw new Elk_Exception('attachments_no_write', 'critical');
657
		}
658
659
		// Finally move the attachments..
660
		moveAvatars();
661
662
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
663
	}
664
665
	/**
666
	 * Remove attachments older than a given age.
667
	 *
668
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=byAge.
669
	 * - It optionally adds a certain text to the messages the attachments were removed from.
670
	 * @todo refactor this silly superglobals use...
671
	 */
672
	public function action_byAge()
673
	{
674
		checkSession('post', 'admin');
675
676
		// @todo Ignore messages in topics that are stickied?
677
678
		// Deleting an attachment?
679
		if ($this->_req->getQuery('type', 'strval') !== 'avatars')
680
		{
681
			// Get rid of all the old attachments.
682
			$messages = removeAttachments(array('attachment_type' => 0, 'poster_time' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'messages', true);
683
684
			// Update the messages to reflect the change.
685 View Code Duplication
			if (!empty($messages) && !empty($this->_req->post->notice))
686
				setRemovalNotice($messages, $this->_req->post->notice);
0 ignored issues
show
Documentation introduced by
$messages is of type array|boolean, but the function expects a array<integer,integer>.

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...
687
		}
688
		else
689
		{
690
			// Remove all the old avatars.
691
			removeAttachments(array('not_id_member' => 0, 'last_login' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'members');
692
		}
693
694
		redirectexit('action=admin;area=manageattachments' . (empty($this->_req->query->avatars) ? ';sa=maintenance' : ';avatars'));
695
	}
696
697
	/**
698
	 * Remove attachments larger than a given size.
699
	 *
700
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=bySize.
701
	 * - Optionally adds a certain text to the messages the attachments were removed from.
702
	 */
703
	public function action_bySize()
704
	{
705
		checkSession('post', 'admin');
706
707
		// Find humongous attachments.
708
		$messages = removeAttachments(array('attachment_type' => 0, 'size' => 1024 * $this->_req->post->size), 'messages', true);
709
710
		// And make a note on the post.
711 View Code Duplication
		if (!empty($messages) && !empty($this->_req->post->notice))
712
			setRemovalNotice($messages, $this->_req->post->notice);
0 ignored issues
show
Documentation introduced by
$messages is of type array|boolean, but the function expects a array<integer,integer>.

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...
713
714
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
715
	}
716
717
	/**
718
	 * Remove a selection of attachments or avatars.
719
	 *
720
	 * - Called from the browse screen as submitted form by ?action=admin;area=manageattachments;sa=remove
721
	 */
722
	public function action_remove()
723
	{
724
		global $txt, $language, $user_info;
725
726
		checkSession('post');
727
728
		if (!empty($this->_req->post->remove))
729
		{
730
			// There must be a quicker way to pass this safety test??
731
			$attachments = array();
732
			foreach ($this->_req->post->remove as $removeID => $dummy)
733
				$attachments[] = (int) $removeID;
734
735
			if ($this->_req->query->type == 'avatars' && !empty($attachments))
736
				removeAttachments(array('id_attach' => $attachments));
737
			elseif (!empty($attachments))
738
			{
739
				$messages = removeAttachments(array('id_attach' => $attachments), 'messages', true);
740
741
				// And change the message to reflect this.
742
				if (!empty($messages))
743
				{
744
					loadLanguage('index', $language, true);
745
					setRemovalNotice($messages, $txt['attachment_delete_admin']);
0 ignored issues
show
Documentation introduced by
$messages is of type array|boolean, but the function expects a array<integer,integer>.

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...
746
					loadLanguage('index', $user_info['language'], true);
747
				}
748
			}
749
		}
750
751
		$sort = $this->_req->getQuery('sort', 'strval', 'date');
752
		redirectexit('action=admin;area=manageattachments;sa=browse;' . $this->_req->query->type . ';sort=' . $sort . (isset($this->_req->query->desc) ? ';desc' : '') . ';start=' . $this->_req->query->start);
753
	}
754
755
	/**
756
	 * Removes all attachments in a single click
757
	 *
758
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=removeall.
759
	 */
760
	public function action_removeall()
761
	{
762
		global $txt;
763
764
		checkSession('get', 'admin');
765
766
		$messages = removeAttachments(array('attachment_type' => 0), '', true);
767
768
		$notice = $this->_req->getPost('notice', 'strval', $txt['attachment_delete_admin']);
769
770
		// Add the notice on the end of the changed messages.
771
		if (!empty($messages))
772
			setRemovalNotice($messages, $notice);
0 ignored issues
show
Documentation introduced by
$messages is of type array|boolean, but the function expects a array<integer,integer>.

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...
773
774
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
775
	}
776
777
	/**
778
	 * This function will performs many attachment checks and provides ways to fix them
779
	 *
780
	 * What it does:
781
	 *
782
	 * - Checks for the following common issues
783
	 * - Orphan Thumbnails
784
	 * - Attachments that have no thumbnails
785
	 * - Attachments that list thumbnails, but actually, don't have any
786
	 * - Attachments list in the wrong_folder
787
	 * - Attachments that don't exists on disk any longer
788
	 * - Attachments that are zero size
789
	 * - Attachments that file size does not match the DB size
790
	 * - Attachments that no longer have a message
791
	 * - Avatars with no members associated with them.
792
	 * - Attachments that are in the attachment folder, but not listed in the DB
793
	 */
794
	public function action_repair()
795
	{
796
		global $modSettings, $context, $txt;
797
798
		checkSession('get');
799
800
		// If we choose cancel, redirect right back.
801
		if (isset($this->_req->post->cancel))
802
			redirectexit('action=admin;area=manageattachments;sa=maintenance');
803
804
		// Try give us a while to sort this out...
805
		detectServer()->setTimeLimit(600);
806
807
		$this->step = $this->_req->getQuery('step', 'intval', 0);
808
		$this->substep = $this->_req->getQuery('substep', 'intval', 0);
809
		$this->starting_substep = $this->substep;
810
811
		// Don't recall the session just in case.
812
		if ($this->step === 0 && $this->substep === 0)
813
		{
814
			unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']);
815
816
			// If we're actually fixing stuff - work out what.
817
			if (isset($this->_req->query->fixErrors))
818
			{
819
				// Nothing?
820
				if (empty($this->_req->post->to_fix))
821
					redirectexit('action=admin;area=manageattachments;sa=maintenance');
822
823
				foreach ($this->_req->post->to_fix as $key => $value)
824
					$_SESSION['attachments_to_fix'][] = $value;
825
			}
826
		}
827
828
		// All the valid problems are here:
829
		$context['repair_errors'] = array(
830
			'missing_thumbnail_parent' => 0,
831
			'parent_missing_thumbnail' => 0,
832
			'file_missing_on_disk' => 0,
833
			'file_wrong_size' => 0,
834
			'file_size_of_zero' => 0,
835
			'attachment_no_msg' => 0,
836
			'avatar_no_member' => 0,
837
			'wrong_folder' => 0,
838
			'missing_extension' => 0,
839
			'files_without_attachment' => 0,
840
		);
841
842
		$to_fix = !empty($this->_req->session->attachments_to_fix) ? $this->_req->session->attachments_to_fix : array();
843
		$context['repair_errors'] = $this->_req->getSession('attachments_to_fix2', $context['repair_errors']);
844
		$fix_errors = isset($this->_req->query->fixErrors) ? true : false;
845
846
		// Get stranded thumbnails.
847 View Code Duplication
		if ($this->step <= 0)
848
		{
849
			$thumbnails = getMaxThumbnail();
850
851
			for (; $this->substep < $thumbnails; $this->substep += 500)
852
			{
853
				$removed = findOrphanThumbnails($this->substep, $fix_errors, $to_fix);
854
				$context['repair_errors']['missing_thumbnail_parent'] += count($removed);
855
856
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
857
			}
858
859
			// Done here, on to the next
860
			$this->step = 1;
861
			$this->substep = 0;
862
			$this->_pauseAttachmentMaintenance($to_fix);
863
		}
864
865
		// Find parents which think they have thumbnails, but actually, don't.
866 View Code Duplication
		if ($this->step <= 1)
867
		{
868
			$thumbnails = maxNoThumb();
869
870
			for (; $this->substep < $thumbnails; $this->substep += 500)
871
			{
872
				$to_update = findParentsOrphanThumbnails($this->substep, $fix_errors, $to_fix);
873
				$context['repair_errors']['parent_missing_thumbnail'] += count($to_update);
874
875
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
876
			}
877
878
			// Another step done, but many to go
879
			$this->step = 2;
880
			$this->substep = 0;
881
			$this->_pauseAttachmentMaintenance($to_fix);
882
		}
883
884
		// This may take forever I'm afraid, but life sucks... recount EVERY attachments!
885
		if ($this->step <= 2)
886
		{
887
			$thumbnails = maxAttachment();
888
889
			for (; $this->substep < $thumbnails; $this->substep += 250)
890
			{
891
				$repair_errors = repairAttachmentData($this->substep, $fix_errors, $to_fix);
892
893
				foreach ($repair_errors as $key => $value)
894
					$context['repair_errors'][$key] += $value;
895
896
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
897
			}
898
899
			// And onward we go
900
			$this->step = 3;
901
			$this->substep = 0;
902
			$this->_pauseAttachmentMaintenance($to_fix);
903
		}
904
905
		// Get avatars with no members associated with them.
906 View Code Duplication
		if ($this->step <= 3)
907
		{
908
			$thumbnails = maxAttachment();
909
910
			for (; $this->substep < $thumbnails; $this->substep += 500)
911
			{
912
				$to_remove = findOrphanAvatars($this->substep, $fix_errors, $to_fix);
913
				$context['repair_errors']['avatar_no_member'] += count($to_remove);
914
915
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
916
			}
917
918
			$this->step = 4;
919
			$this->substep = 0;
920
			$this->_pauseAttachmentMaintenance($to_fix);
921
		}
922
923
		// What about attachments, who are missing a message :'(
924 View Code Duplication
		if ($this->step <= 4)
925
		{
926
			$thumbnails = maxAttachment();
927
928
			for (; $this->substep < $thumbnails; $this->substep += 500)
929
			{
930
				$to_remove = findOrphanAttachments($this->substep, $fix_errors, $to_fix);
931
				$context['repair_errors']['attachment_no_msg'] += count($to_remove);
932
933
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
934
			}
935
936
			$this->step = 5;
937
			$this->substep = 0;
938
			$this->_pauseAttachmentMaintenance($to_fix);
939
		}
940
941
		// What about files who are not recorded in the database?
942
		if ($this->step <= 5)
943
		{
944
			// Just use the current path for temp files.
945
			if (!is_array($modSettings['attachmentUploadDir']))
946
				$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
947
948
			$attach_dirs = $modSettings['attachmentUploadDir'];
949
			$current_check = 0;
950
			$max_checks = 500;
951
952
			$files_checked = empty($this->substep) ? 0 : $this->substep;
953
			foreach ($attach_dirs as $attach_dir)
954
			{
955
				try
956
				{
957
					$files = new FilesystemIterator($attach_dir, FilesystemIterator::SKIP_DOTS);
958
					foreach ($files as $file)
959
					{
960
						if ($file->getFilename() === '.htaccess')
961
							continue;
962
963
						if ($files_checked <= $current_check)
964
						{
965
							// Temporary file, get rid of it!
966
							if (strpos($file->getFilename(), 'post_tmp_') !== false)
967
							{
968
								// Temp file is more than 5 hours old!
969
								if ($file->getMTime() < time() - 18000)
970
									@unlink($file->getPathname());
971
							}
972
							// That should be an attachment, let's check if we have it in the database
973
							elseif (strpos($file->getFilename(), '_') !== false)
974
							{
975
								$attachID = (int) substr($file->getFilename(), 0, strpos($file->getFilename(), '_'));
976
								if (!empty($attachID))
977
								{
978 View Code Duplication
									if (!validateAttachID($attachID))
979
									{
980
										if ($fix_errors && in_array('files_without_attachment', $to_fix))
981
											@unlink($file->getPathname());
982
										else
983
											$context['repair_errors']['files_without_attachment']++;
984
									}
985
								}
986
							}
987
							elseif ($file->getFilename() !== 'index.php' && !$file->isDir())
988
							{
989 View Code Duplication
								if ($fix_errors && in_array('files_without_attachment', $to_fix))
990
									@unlink($file->getPathname());
991
								else
992
									$context['repair_errors']['files_without_attachment']++;
993
							}
994
						}
995
						$current_check++;
996
						$this->substep = (int) $current_check;
997
998
						if ($current_check - $files_checked >= $max_checks)
999
							$this->_pauseAttachmentMaintenance($to_fix);
1000
					}
1001
				}
1002
				catch (UnexpectedValueException $e)
1003
				{
1004
					// @todo for now do nothing...
1005
				}
1006
			}
1007
1008
			$this->step = 5;
1009
			$this->substep = 0;
1010
			$this->_pauseAttachmentMaintenance($to_fix);
1011
		}
1012
1013
		// Got here we must be doing well - just the template! :D
1014
		$context['page_title'] = $txt['repair_attachments'];
1015
		$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1016
		$context['sub_template'] = 'attachment_repair';
1017
1018
		// What stage are we at?
1019
		$context['completed'] = $fix_errors ? true : false;
1020
		$context['errors_found'] = false;
1021
		foreach ($context['repair_errors'] as $number)
1022
		{
1023
			if (!empty($number))
1024
			{
1025
				$context['errors_found'] = true;
1026
				break;
1027
			}
1028
		}
1029
	}
1030
1031
	/**
1032
	 * This function lists and allows updating of multiple attachments paths.
1033
	 */
1034
	public function action_attachpaths()
1035
	{
1036
		global $modSettings, $scripturl, $context, $txt;
1037
1038
		// Since this needs to be done eventually.
1039
		if (!is_array($modSettings['attachmentUploadDir']))
1040
			$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
1041
1042
		if (!isset($modSettings['attachment_basedirectories']))
1043
			$modSettings['attachment_basedirectories'] = array();
1044
		elseif (!is_array($modSettings['attachment_basedirectories']))
1045
			$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
1046
1047
		$errors = array();
1048
1049
		// Saving?
1050
		if (isset($this->_req->post->save))
1051
		{
1052
			checkSession();
1053
1054
			$this->current_dir = $this->_req->getPost('current_dir', 'intval', 0);
1055
			$new_dirs = array();
1056
1057
			require_once(SUBSDIR . '/Themes.subs.php');
1058
			$themes = installedThemes();
1059
			$reserved_dirs = array(BOARDDIR, SOURCEDIR, SUBSDIR, CONTROLLERDIR, CACHEDIR, EXTDIR, LANGUAGEDIR, ADMINDIR);
1060
			foreach ($themes as $theme)
1061
				$reserved_dirs[] = $theme['theme_dir'];
1062
1063
			foreach ($this->_req->post->dirs as $id => $path)
1064
			{
1065
				$error = '';
1066
				$id = (int) $id;
1067
				if ($id < 1)
1068
					continue;
1069
1070
				$real_path = rtrim(trim($path), DIRECTORY_SEPARATOR);
1071
1072
				// If it doesn't look like a directory, probably is not a directory
1073
				if (preg_match('~[/\\\\]~', $real_path) !== 1)
1074
					$real_path = realpath(BOARDDIR . DIRECTORY_SEPARATOR . ltrim($real_path, DIRECTORY_SEPARATOR));
1075
1076
				// Hmm, a new path maybe?
1077
				if (!array_key_exists($id, $modSettings['attachmentUploadDir']))
1078
				{
1079
					// or is it?
1080
					if (in_array($path, $modSettings['attachmentUploadDir']) || in_array(BOARDDIR . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
1081
					{
1082
						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
1083
						continue;
1084
					}
1085
1086
					// or is it a system dir?
1087
					if (in_array($real_path, $reserved_dirs))
1088
					{
1089
						$errors[] = $real_path . ': ' . $txt['attach_dir_reserved'];
1090
						continue;
1091
					}
1092
1093
					// OK, so let's try to create it then.
1094
					if (automanage_attachments_create_directory($path))
1095
						$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1096
					else
1097
						$errors[] = $path . ': ' . $txt[$context['dir_creation_error']];
1098
				}
1099
1100
				// Changing a directory name?
1101
				if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $real_path != $modSettings['attachmentUploadDir'][$id])
1102
				{
1103
					if ($real_path != $modSettings['attachmentUploadDir'][$id] && !is_dir($real_path))
1104
					{
1105
						if (!@rename($modSettings['attachmentUploadDir'][$id], $real_path))
1106
						{
1107
							$errors[] = $real_path . ': ' . $txt['attach_dir_no_rename'];
1108
							$real_path = $modSettings['attachmentUploadDir'][$id];
1109
						}
1110
					}
1111
					else
1112
					{
1113
						$errors[] = $real_path . ': ' . $txt['attach_dir_exists_msg'];
1114
						$real_path = $modSettings['attachmentUploadDir'][$id];
1115
					}
1116
1117
					// Update the base directory path
1118
					if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
1119
					{
1120
						$base = $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id] ? $real_path : $modSettings['basedirectory_for_attachments'];
1121
1122
						$modSettings['attachment_basedirectories'][$id] = $real_path;
1123
						updateSettings(array(
1124
							'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1125
							'basedirectory_for_attachments' => $base,
1126
						));
1127
						$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
1128
					}
1129
				}
1130
1131
				if (empty($path))
1132
				{
1133
					$real_path = $modSettings['attachmentUploadDir'][$id];
1134
1135
					// It's not a good idea to delete the current directory.
1136
					if ($id == (!empty($this->current_dir) ? $this->current_dir : $modSettings['currentAttachmentUploadDir']))
1137
						$errors[] = $real_path . ': ' . $txt['attach_dir_is_current'];
1138
					// Or the current base directory
1139
					elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
1140
						$errors[] = $real_path . ': ' . $txt['attach_dir_is_current_bd'];
1141
					else
1142
					{
1143
						// Let's not try to delete a path with files in it.
1144
						$num_attach = countAttachmentsInFolders($id);
1145
1146
						// A check to see if it's a used base dir.
1147
						if (!empty($modSettings['attachment_basedirectories']))
1148
						{
1149
							// Count any sub-folders.
1150
							foreach ($modSettings['attachmentUploadDir'] as $sub)
1151
								if (strpos($sub, $real_path . DIRECTORY_SEPARATOR) !== false)
1152
									$num_attach++;
1153
						}
1154
1155
						// It's safe to delete. So try to delete the folder also
1156
						if ($num_attach == 0)
1157
						{
1158
							if (is_dir($real_path))
1159
								$doit = true;
1160
							elseif (is_dir(BOARDDIR . DIRECTORY_SEPARATOR . $real_path))
1161
							{
1162
								$doit = true;
1163
								$real_path = BOARDDIR . DIRECTORY_SEPARATOR . $real_path;
1164
							}
1165
1166
							if (isset($doit))
1167
							{
1168
								unlink($real_path . '/.htaccess');
1169
								unlink($real_path . '/index.php');
1170
								if (!@rmdir($real_path))
1171
									$error = $real_path . ': ' . $txt['attach_dir_no_delete'];
1172
							}
1173
1174
							// Remove it from the base directory list.
1175
							if (empty($error) && !empty($modSettings['attachment_basedirectories']))
1176
							{
1177
								unset($modSettings['attachment_basedirectories'][$id]);
1178
								updateSettings(array('attachment_basedirectories' => serialize($modSettings['attachment_basedirectories'])));
1179
								$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
1180
							}
1181
						}
1182
						else
1183
							$error = $real_path . ': ' . $txt['attach_dir_no_remove'];
1184
1185
						if (empty($error))
1186
							continue;
1187
						else
1188
							$errors[] = $error;
1189
					}
1190
				}
1191
1192
				$new_dirs[$id] = $real_path;
1193
			}
1194
1195
			// We need to make sure the current directory is right.
1196
			if (empty($this->current_dir) && !empty($modSettings['currentAttachmentUploadDir']))
1197
				$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1198
1199
			// Find the current directory if there's no value carried,
1200
			if (empty($this->current_dir) || empty($new_dirs[$this->current_dir]))
1201
			{
1202
				if (array_key_exists($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
1203
					$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1204
				else
1205
					$this->current_dir = max(array_keys($modSettings['attachmentUploadDir']));
1206
			}
1207
1208
			// If the user wishes to go back, update the last_dir array
1209
			if ($this->current_dir != $modSettings['currentAttachmentUploadDir'] && !empty($modSettings['last_attachments_directory']) && (isset($modSettings['last_attachments_directory'][$this->current_dir]) || isset($modSettings['last_attachments_directory'][0])))
1210
			{
1211 View Code Duplication
				if (!is_array($modSettings['last_attachments_directory']))
1212
					$modSettings['last_attachments_directory'] = Util::unserialize($modSettings['last_attachments_directory']);
1213
1214
				$num = substr(strrchr($modSettings['attachmentUploadDir'][$this->current_dir], '_'), 1);
1215
				if (is_numeric($num))
1216
				{
1217
					// Need to find the base folder.
1218
					$bid = -1;
1219
					$use_subdirectories_for_attachments = 0;
1220
					if (!empty($modSettings['attachment_basedirectories']))
1221
						foreach ($modSettings['attachment_basedirectories'] as $bid => $base)
1222
							if (strpos($modSettings['attachmentUploadDir'][$this->current_dir], $base . DIRECTORY_SEPARATOR) !== false)
1223
							{
1224
								$use_subdirectories_for_attachments = 1;
1225
								break;
1226
							}
1227
1228
					if ($use_subdirectories_for_attachments == 0 && strpos($modSettings['attachmentUploadDir'][$this->current_dir], BOARDDIR . DIRECTORY_SEPARATOR) !== false)
1229
						$bid = 0;
1230
1231
					$modSettings['last_attachments_directory'][$bid] = (int) $num;
1232
					$modSettings['basedirectory_for_attachments'] = !empty($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
1233
					$modSettings['use_subdirectories_for_attachments'] = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['use_subdirectories_for_attachments'] : 0;
1234
					updateSettings(array(
1235
						'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
1236
						'basedirectory_for_attachments' => $bid == 0 ? $modSettings['basedirectory_for_attachments'] : $modSettings['attachment_basedirectories'][$bid],
1237
						'use_subdirectories_for_attachments' => $use_subdirectories_for_attachments,
1238
					));
1239
				}
1240
			}
1241
1242
			// Going back to just one path?
1243
			if (count($new_dirs) == 1)
1244
			{
1245
				// We might need to reset the paths. This loop will just loop through once.
1246
				foreach ($new_dirs as $id => $dir)
1247
				{
1248
					if ($id != 1)
1249
						updateAttachmentIdFolder($id, 1);
1250
1251
					$update = array(
1252
						'currentAttachmentUploadDir' => 1,
1253
						'attachmentUploadDir' => serialize(array(1 => $dir)),
1254
					);
1255
				}
1256
			}
1257
			else
1258
			{
1259
				// Save it to the database.
1260
				$update = array(
1261
					'currentAttachmentUploadDir' => $this->current_dir,
1262
					'attachmentUploadDir' => serialize($new_dirs),
1263
				);
1264
			}
1265
1266
			if (!empty($update))
1267
				updateSettings($update);
1268
1269
			if (!empty($errors))
1270
				$_SESSION['errors']['dir'] = $errors;
1271
1272
			redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
1273
		}
1274
1275
		// Saving a base directory?
1276
		if (isset($this->_req->post->save2))
1277
		{
1278
			checkSession();
1279
1280
			// Changing the current base directory?
1281
			$this->current_base_dir = $this->_req->getQuery('current_base_dir', 'intval');
1282
			if (empty($this->_req->post->new_base_dir) && !empty($this->current_base_dir))
1283
			{
1284
				if ($modSettings['basedirectory_for_attachments'] != $modSettings['attachmentUploadDir'][$this->current_base_dir])
1285
					$update = (array(
1286
						'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1287
					));
1288
			}
1289
1290
			if (isset($this->_req->post->base_dir))
1291
			{
1292
				foreach ($this->_req->post->base_dir as $id => $dir)
1293
				{
1294
					if (!empty($dir) && $dir != $modSettings['attachmentUploadDir'][$id])
1295
					{
1296
						if (@rename($modSettings['attachmentUploadDir'][$id], $dir))
1297
						{
1298
							$modSettings['attachmentUploadDir'][$id] = $dir;
1299
							$modSettings['attachment_basedirectories'][$id] = $dir;
1300
							$update = (array(
1301
								'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
1302
								'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1303
								'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1304
							));
1305
						}
1306
					}
1307
1308
					if (empty($dir))
1309
					{
1310
						if ($id == $this->current_base_dir)
1311
						{
1312
							$errors[] = $modSettings['attachmentUploadDir'][$id] . ': ' . $txt['attach_dir_is_current'];
1313
							continue;
1314
						}
1315
1316
						unset($modSettings['attachment_basedirectories'][$id]);
1317
						$update = (array(
1318
							'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1319
							'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1320
						));
1321
					}
1322
				}
1323
			}
1324
1325
			// Or adding a new one?
1326
			if (!empty($this->_req->post->new_base_dir))
1327
			{
1328
				$this->_req->post->new_base_dir = htmlspecialchars($this->_req->post->new_base_dir, ENT_QUOTES, 'UTF-8');
1329
1330
				$current_dir = $modSettings['currentAttachmentUploadDir'];
1331
1332 View Code Duplication
				if (!in_array($this->_req->post->new_base_dir, $modSettings['attachmentUploadDir']))
1333
				{
1334
					if (!automanage_attachments_create_directory($this->_req->post->new_base_dir))
1335
						$errors[] = $this->_req->post->new_base_dir . ': ' . $txt['attach_dir_base_no_create'];
1336
				}
1337
1338
				$modSettings['currentAttachmentUploadDir'] = array_search($this->_req->post->new_base_dir, $modSettings['attachmentUploadDir']);
1339
				if (!in_array($this->_req->post->new_base_dir, $modSettings['attachment_basedirectories']))
1340
					$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $this->_req->post->new_base_dir;
1341
				ksort($modSettings['attachment_basedirectories']);
1342
1343
				$update = (array(
1344
					'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1345
					'basedirectory_for_attachments' => $this->_req->post->new_base_dir,
1346
					'currentAttachmentUploadDir' => $current_dir,
1347
				));
1348
			}
1349
1350
			if (!empty($errors))
1351
				$_SESSION['errors']['base'] = $errors;
1352
1353
			if (!empty($update))
1354
				updateSettings($update);
1355
1356
			redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
1357
		}
1358
1359
		if (isset($this->_req->session->errors))
1360
		{
1361
			if (is_array($this->_req->session->errors))
1362
			{
1363
				$errors = array();
1364 View Code Duplication
				if (!empty($this->_req->session->errors['dir']))
1365
					foreach ($this->_req->session->errors['dir'] as $error)
1366
						$errors['dir'][] = Util::htmlspecialchars($error, ENT_QUOTES);
1367
1368 View Code Duplication
				if (!empty($this->_req->session->errors['base']))
1369
					foreach ($this->_req->session->errors['base'] as $error)
1370
						$errors['base'][] = Util::htmlspecialchars($error, ENT_QUOTES);
1371
			}
1372
			unset($_SESSION['errors'], $this->_req->session->errors);
1373
		}
1374
1375
		$listOptions = array(
1376
			'id' => 'attach_paths',
1377
			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1378
			'title' => $txt['attach_paths'],
1379
			'get_items' => array(
1380
				'function' => 'list_getAttachDirs',
1381
			),
1382
			'columns' => array(
1383
				'current_dir' => array(
1384
					'header' => array(
1385
						'value' => $txt['attach_current'],
1386
						'class' => 'centertext',
1387
					),
1388
					'data' => array(
1389
						'function' => function ($rowData) {
1390
							return '<input type="radio" name="current_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . (!empty($rowData['disable_current']) ? ' disabled="disabled"' : '') . ' class="input_radio" />';
1391
						},
1392
						'style' => 'width: 10%;',
1393
						'class' => 'centertext',
1394
					),
1395
				),
1396
				'path' => array(
1397
					'header' => array(
1398
						'value' => $txt['attach_path'],
1399
					),
1400
					'data' => array(
1401
						'function' => function ($rowData) {
1402
							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="disabled"' : '') . ' class="input_text"/>';
1403
						},
1404
						'style' => 'width: 40%;',
1405
					),
1406
				),
1407
				'current_size' => array(
1408
					'header' => array(
1409
						'value' => $txt['attach_current_size'],
1410
					),
1411
					'data' => array(
1412
						'db' => 'current_size',
1413
						'style' => 'width: 15%;',
1414
					),
1415
				),
1416
				'num_files' => array(
1417
					'header' => array(
1418
						'value' => $txt['attach_num_files'],
1419
					),
1420
					'data' => array(
1421
						'db' => 'num_files',
1422
						'style' => 'width: 15%;',
1423
					),
1424
				),
1425
				'status' => array(
1426
					'header' => array(
1427
						'value' => $txt['attach_dir_status'],
1428
					),
1429
					'data' => array(
1430
						'db' => 'status',
1431
						'style' => 'width: 25%;',
1432
					),
1433
				),
1434
			),
1435
			'form' => array(
1436
				'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1437
			),
1438
			'additional_rows' => array(
1439
				array(
1440
					'class' => 'submitbutton',
1441
					'position' => 'below_table_data',
1442
					'value' => '
1443
					<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
1444
					<input type="submit" name="save" value="' . $txt['save'] . '" class="right_submit" />
1445
					<input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" class="right_submit" />',
1446
				),
1447
				empty($errors['dir']) ? array(
1448
					'position' => 'top_of_list',
1449
					'value' => $txt['attach_dir_desc'],
1450
					'style' => 'padding: 5px 10px;',
1451
					'class' => 'smalltext'
1452
				) : array(
1453
					'position' => 'top_of_list',
1454
					'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['dir']),
1455
					'style' => 'padding-left: 35px;',
1456
					'class' => 'warningbox',
1457
				),
1458
			),
1459
		);
1460
		createList($listOptions);
1461
1462
		if (!empty($modSettings['attachment_basedirectories']))
1463
		{
1464
			$listOptions2 = array(
1465
				'id' => 'base_paths',
1466
				'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1467
				'title' => $txt['attach_base_paths'],
1468
				'get_items' => array(
1469
					'function' => 'list_getBaseDirs',
1470
				),
1471
				'columns' => array(
1472
					'current_dir' => array(
1473
						'header' => array(
1474
							'value' => $txt['attach_current'],
1475
							'class' => 'centertext',
1476
						),
1477
						'data' => array(
1478
							'function' => function ($rowData) {
1479
								return '<input type="radio" name="current_base_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . ' class="input_radio" />';
1480
							},
1481
							'style' => 'width: 10%;',
1482
							'class' => 'centertext',
1483
						),
1484
					),
1485
					'path' => array(
1486
						'header' => array(
1487
							'value' => $txt['attach_path'],
1488
						),
1489
						'data' => array(
1490
							'db' => 'path',
1491
							'style' => 'width: 45%;',
1492
						),
1493
					),
1494
					'num_dirs' => array(
1495
						'header' => array(
1496
							'value' => $txt['attach_num_dirs'],
1497
						),
1498
						'data' => array(
1499
							'db' => 'num_dirs',
1500
							'style' => 'width: 15%;',
1501
						),
1502
					),
1503
					'status' => array(
1504
						'header' => array(
1505
							'value' => $txt['attach_dir_status'],
1506
						),
1507
						'data' => array(
1508
							'db' => 'status',
1509
							'style' => 'width: 15%;',
1510
						),
1511
					),
1512
				),
1513
				'form' => array(
1514
					'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1515
				),
1516
				'additional_rows' => array(
1517
					array(
1518
						'class' => 'submitbutton',
1519
						'position' => 'below_table_data',
1520
						'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
1521
						<input type="submit" name="save2" value="' . $txt['save'] . '" class="right_submit" />
1522
						<input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" class="right_submit" />',
1523
					),
1524
					empty($errors['base']) ? array(
1525
						'position' => 'top_of_list',
1526
						'value' => $txt['attach_dir_base_desc'],
1527
						'style' => 'padding: 5px 10px;',
1528
						'class' => 'smalltext'
1529
					) : array(
1530
						'position' => 'top_of_list',
1531
						'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['base']),
1532
						'style' => 'padding-left: 35px',
1533
						'class' => 'warningbox',
1534
					),
1535
				),
1536
			);
1537
			createList($listOptions2);
1538
		}
1539
1540
		// Fix up our template.
1541
		$context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths';
1542
		$context['page_title'] = $txt['attach_path_manage'];
1543
	}
1544
1545
	/**
1546
	 * Maintenance function to move attachments from one directory to another
1547
	 */
1548
	public function action_transfer()
1549
	{
1550
		global $modSettings, $txt;
1551
1552
		checkSession();
1553
1554
		// The list(s) of directory's that are available.
1555
		$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
1556
		if (!empty($modSettings['attachment_basedirectories']))
1557
			$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
1558
		else
1559
			$modSettings['basedirectory_for_attachments'] = array();
1560
1561
		// Clean the inputs
1562
		$this->from = $this->_req->getPost('from', 'intval');
1563
		$this->auto = $this->_req->getPost('auto', 'intval', 0);
1564
		$this->to = $this->_req->getPost('to', 'intval');
1565
		$start = !empty($this->_req->post->empty_it) ? 0 : $modSettings['attachmentDirFileLimit'];
1566
		$_SESSION['checked'] = !empty($this->_req->post->empty_it) ? true : false;
1567
1568
		// Prepare for the moving
1569
		$limit = 501;
1570
		$results = array();
1571
		$dir_files = 0;
1572
		$current_progress = 0;
1573
		$total_moved = 0;
1574
		$total_not_moved = 0;
1575
		$total_progress = 0;
1576
1577
		// Need to know where we are moving things from
1578
		if (empty($this->from) || (empty($this->auto) && empty($this->to)))
1579
			$results[] = $txt['attachment_transfer_no_dir'];
1580
1581
		// Same location, that's easy
1582
		if ($this->from == $this->to)
1583
			$results[] = $txt['attachment_transfer_same_dir'];
1584
1585
		// No errors so determine how many we may have to move
1586
		if (empty($results))
1587
		{
1588
			// Get the total file count for the progress bar.
1589
			$total_progress = getFolderAttachmentCount($this->from);
1590
			$total_progress -= $start;
1591
1592
			if ($total_progress < 1)
1593
				$results[] = $txt['attachment_transfer_no_find'];
1594
		}
1595
1596
		// Nothing to move (no files in source or below the max limit)
1597
		if (empty($results))
1598
		{
1599
			// Moving them automatically?
1600
			if (!empty($this->auto))
1601
			{
1602
				$modSettings['automanage_attachments'] = 1;
1603
1604
				// Create sub directory's off the root or from an attachment directory?
1605
				$modSettings['use_subdirectories_for_attachments'] = $this->auto == -1 ? 0 : 1;
1606
				$modSettings['basedirectory_for_attachments'] = $this->auto > 0 ? $modSettings['attachmentUploadDir'][$this->auto] : $modSettings['basedirectory_for_attachments'];
1607
1608
				// Finally, where do they need to go
1609
				automanage_attachments_check_directory();
1610
				$new_dir = $modSettings['currentAttachmentUploadDir'];
1611
			}
1612
			// Or to a specified directory
1613
			else
1614
				$new_dir = $this->to;
1615
1616
			$modSettings['currentAttachmentUploadDir'] = $new_dir;
1617
			$break = false;
1618
			while ($break === false)
1619
			{
1620
				detectServer()->setTimeLimit(300);
1621
1622
				// If limits are set, get the file count and size for the destination folder
1623
				if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])))
1624
				{
1625
					$current_dir = attachDirProperties($new_dir);
1626
					$dir_files = $current_dir['files'];
1627
					$dir_size = $current_dir['size'];
1628
				}
1629
1630
				// Find some attachments to move
1631
				list ($tomove_count, $tomove) = findAttachmentsToMove($this->from, $start, $limit);
1632
1633
				// Nothing found to move
1634
				if ($tomove_count === 0)
1635
				{
1636
					if (empty($current_progress))
1637
						$results[] = $txt['attachment_transfer_no_find'];
1638
					break;
1639
				}
1640
1641
				// No more to move after this batch then set the finished flag.
1642
				if ($tomove_count < $limit)
1643
					$break = true;
1644
1645
				// Move them
1646
				$moved = array();
1647
				$dir_size = empty($dir_size) ? 0 : $dir_size;
1648
				foreach ($tomove as $row)
1649
				{
1650
					$source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1651
					$dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source);
1652
1653
					// Size and file count check
1654
					if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
1655
					{
1656
						$dir_files++;
1657
						$dir_size += !empty($row['size']) ? $row['size'] : filesize($source);
1658
1659
						// If we've reached a directory limit. Do something if we are in auto mode, otherwise set an error.
1660
						if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || (!empty($modSettings['attachmentDirFileLimit']) && $dir_files > $modSettings['attachmentDirFileLimit']))
1661
						{
1662
							// Since we're in auto mode. Create a new folder and reset the counters.
1663
							if (!empty($this->auto))
1664
							{
1665
								automanage_attachments_by_space();
1666
1667
								$results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
1668
								if (!empty($total_not_moved))
1669
									$results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved);
1670
1671
								$dir_files = 0;
1672
								$total_moved = 0;
1673
								$total_not_moved = 0;
1674
1675
								$break = false;
1676
								break;
1677
							}
1678
							// Hmm, not in auto. Time to bail out then...
1679
							else
1680
							{
1681
								$results[] = $txt['attachment_transfer_no_room'];
1682
								$break = true;
1683
								break;
1684
							}
1685
						}
1686
					}
1687
1688
					// Actually move the file
1689
					if (@rename($source, $dest))
0 ignored issues
show
Security File Manipulation introduced by
$dest can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and $_POST is passed to Data_Validator::is_valid()
    in sources/controllers/Post.controller.php on line 883
  2. $data is passed to Data_Validator::validate()
    in sources/subs/DataValidator.class.php on line 147
  3. Data_Validator::$_data is assigned
    in sources/subs/DataValidator.class.php on line 249
  4. Tainted property Data_Validator::$_data is read
    in sources/subs/DataValidator.class.php on line 281
  5. Data_Validator::validation_data() returns tainted data
    in sources/subs/HttpReq.class.php on line 359
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/subs/HttpReq.class.php on line 219
  7. Tainted property HttpReq::$_param is read
    in sources/subs/HttpReq.class.php on line 302
  8. HttpReq::getPost() returns tainted data, and ManageAttachments_Controller::$to is assigned
    in sources/admin/ManageAttachments.controller.php on line 1564
  9. Tainted property ManageAttachments_Controller::$to is read, and $new_dir is assigned
    in sources/admin/ManageAttachments.controller.php on line 1614
  10. $modSettings is assigned
    in sources/admin/ManageAttachments.controller.php on line 1616
  11. $dest is assigned
    in sources/admin/ManageAttachments.controller.php on line 1651

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1690
					{
1691
						$total_moved++;
1692
						$current_progress++;
1693
						$moved[] = $row['id_attach'];
1694
					}
1695
					else
1696
						$total_not_moved++;
1697
				}
1698
1699
				// Update the database to reflect the new file location
1700
				if (!empty($moved))
1701
					moveAttachments($moved, $new_dir);
1702
1703
				$new_dir = $modSettings['currentAttachmentUploadDir'];
1704
1705
				// Create / update the progress bar.
1706
				// @todo why was this done this way?
1707
				if (!$break)
1708
				{
1709
					$percent_done = min(round($current_progress / $total_progress * 100, 0), 100);
1710
					$prog_bar = '
1711
						<div class="progress_bar">
1712
							<div class="full_bar">' . $percent_done . '%</div>
1713
							<div class="green_percent" style="width: ' . $percent_done . '%;">&nbsp;</div>
1714
						</div>';
1715
1716
					// Write it to a file so it can be displayed
1717
					$fp = fopen(BOARDDIR . '/progress.php', 'w');
1718
					fwrite($fp, $prog_bar);
1719
					fclose($fp);
1720
					usleep(500000);
1721
				}
1722
			}
1723
1724
			$results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
1725
			if (!empty($total_not_moved))
1726
				$results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved);
1727
		}
1728
1729
		// All done, time to clean up
1730
		$_SESSION['results'] = $results;
1731
		if (file_exists(BOARDDIR . '/progress.php'))
1732
			unlink(BOARDDIR . '/progress.php');
1733
1734
		redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer');
1735
	}
1736
1737
	/**
1738
	 * Function called in-between each round of attachments and avatar repairs.
1739
	 *
1740
	 * What it does:
1741
	 *
1742
	 * - Called by repairAttachments().
1743
	 * - If repairAttachments() has more steps added, this function needs updated!
1744
	 *
1745
	 * @package Attachments
1746
	 * @param mixed[] $to_fix attachments to fix
1747
	 * @param int $max_substep = 0
1748
	 * @todo Move to ManageAttachments.subs.php
1749
	 * @throws Elk_Exception
1750
	 */
1751
	private function _pauseAttachmentMaintenance($to_fix, $max_substep = 0)
1752
	{
1753
		global $context, $txt, $time_start;
1754
1755
		// Try get more time...
1756
		detectServer()->setTimeLimit(600);
1757
1758
		// Have we already used our maximum time?
1759
		if (microtime(true) - $time_start < 3 || $this->starting_substep == $this->substep)
1760
			return;
1761
1762
		$context['continue_get_data'] = '?action=admin;area=manageattachments;sa=repair' . (isset($this->_req->query->fixErrors) ? ';fixErrors' : '') . ';step=' . $this->step . ';substep=' . $this->substep . ';' . $context['session_var'] . '=' . $context['session_id'];
1763
		$context['page_title'] = $txt['not_done_title'];
1764
		$context['continue_post_data'] = '';
1765
		$context['continue_countdown'] = '2';
1766
		$context['sub_template'] = 'not_done';
1767
1768
		// Specific stuff to not break this template!
1769
		$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1770
1771
		// Change these two if more steps are added!
1772
		if (empty($max_substep))
1773
			$context['continue_percent'] = round(($this->step * 100) / 25);
1774
		else
1775
			$context['continue_percent'] = round(($this->step * 100 + ($this->substep * 100) / $max_substep) / 25);
1776
1777
		// Never more than 100%!
1778
		$context['continue_percent'] = min($context['continue_percent'], 100);
1779
1780
		// Save the needed information for the next look
1781
		$_SESSION['attachments_to_fix'] = $to_fix;
1782
		$_SESSION['attachments_to_fix2'] = $context['repair_errors'];
1783
1784
		obExit();
1785
	}
1786
}
1787