Completed
Pull Request — development (#2331)
by Joshua
10:23
created

ManageAttachments_Controller::pre_dispatch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
609
	{
610
		global $context, $modSettings;
611
612
		loadTemplate('ManageAttachments');
613
		$context['sub_template'] = 'maintenance';
614
615
		// We need our attachments directories...
616
		$attach_dirs = getAttachmentDirs();
617
618
		// Get the number of attachments...
619
		$context['num_attachments'] = comma_format(getAttachmentCount(), 0);
620
621
		// Also get the avatar amount...
622
		$context['num_avatars'] = comma_format(getAvatarCount(), 0);
623
624
		// Total size of attachments
625
		$context['attachment_total_size'] = overallAttachmentsSize();
626
627
		// Total size and files from the current attachments dir.
628
		$current_dir = currentAttachDirProperties();
629
630
		// If they specified a limit only....
631
		if (!empty($modSettings['attachmentDirSizeLimit']))
632
			$context['attachment_space'] = comma_format(max($modSettings['attachmentDirSizeLimit'] - $current_dir['size'], 0), 2);
633
		$context['attachment_current_size'] = comma_format($current_dir['size'], 2);
634
635
		if (!empty($modSettings['attachmentDirFileLimit']))
636
			$context['attachment_files'] = comma_format(max($modSettings['attachmentDirFileLimit'] - $current_dir['files'], 0), 0);
637
		$context['attachment_current_files'] = comma_format($current_dir['files'], 0);
638
639
		$context['attach_multiple_dirs'] = count($attach_dirs) > 1 ? true : false;
640
		$context['attach_dirs'] = $attach_dirs;
641
		$context['base_dirs'] = !empty($modSettings['attachment_basedirectories']) ? unserialize($modSettings['attachment_basedirectories']) : array();
642
		$context['checked'] = $this->_req->getSession('checked', true);
643
		if (!empty($this->_req->session->results))
644
		{
645
			$context['results'] = implode('<br />', $this->_req->session->results);
646
			unset($_SESSION['results']);
647
		}
648
	}
649
650
	/**
651
	 * Move avatars from their current location, to the custom_avatar_dir folder.
652
	 *
653
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=action_moveAvatars.
654
	 */
655
	public function action_moveAvatars()
656
	{
657
		global $modSettings;
658
659
		// First make sure the custom avatar dir is writable.
660
		if (!is_writable($modSettings['custom_avatar_dir']))
661
		{
662
			// Try to fix it.
663
			@chmod($modSettings['custom_avatar_dir'], 0777);
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...
664
665
			// Guess that didn't work?
666
			if (!is_writable($modSettings['custom_avatar_dir']))
667
				Errors::instance()->fatal_lang_error('attachments_no_write', 'critical');
668
		}
669
670
		// Finally move the attachments..
671
		moveAvatars();
672
673
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
674
	}
675
676
	/**
677
	 * Remove attachments older than a given age.
678
	 *
679
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=byAge.
680
	 * - It optionally adds a certain text to the messages the attachments were removed from.
681
	 * @todo refactor this silly superglobals use...
682
	 */
683
	public function action_byAge()
684
	{
685
		checkSession('post', 'admin');
686
687
		// @todo Ignore messages in topics that are stickied?
688
689
		// Deleting an attachment?
690
		if ($this->_req->getQuery('type', 'strval') !== 'avatars')
691
		{
692
			// Get rid of all the old attachments.
693
			$messages = removeAttachments(array('attachment_type' => 0, 'poster_time' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'messages', true);
694
695
			// Update the messages to reflect the change.
696
			if (!empty($messages) && !empty($this->_req->post->notice))
697
				setRemovalNotice($messages, $this->_req->post->notice);
698
		}
699
		else
700
		{
701
			// Remove all the old avatars.
702
			removeAttachments(array('not_id_member' => 0, 'last_login' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'members');
703
		}
704
705
		redirectexit('action=admin;area=manageattachments' . (empty($this->_req->query->avatars) ? ';sa=maintenance' : ';avatars'));
706
	}
707
708
	/**
709
	 * Remove attachments larger than a given size.
710
	 *
711
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=bySize.
712
	 * - Optionally adds a certain text to the messages the attachments were removed from.
713
	 */
714
	public function action_bySize()
715
	{
716
		checkSession('post', 'admin');
717
718
		// Find humungous attachments.
719
		$messages = removeAttachments(array('attachment_type' => 0, 'size' => 1024 * $this->_req->post->size), 'messages', true);
720
721
		// And make a note on the post.
722
		if (!empty($messages) && !empty($this->_req->post->notice))
723
			setRemovalNotice($messages, $this->_req->post->notice);
724
725
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
726
	}
727
728
	/**
729
	 * Remove a selection of attachments or avatars.
730
	 *
731
	 * - Called from the browse screen as submitted form by ?action=admin;area=manageattachments;sa=remove
732
	 */
733
	public function action_remove()
734
	{
735
		global $txt, $language, $user_info;
736
737
		checkSession('post');
738
739
		if (!empty($this->_req->post->remove))
740
		{
741
			// There must be a quicker way to pass this safety test??
742
			$attachments = array();
743
			foreach ($this->_req->post->remove as $removeID => $dummy)
744
				$attachments[] = (int) $removeID;
745
746
			if ($this->_req->query->type == 'avatars' && !empty($attachments))
747
				removeAttachments(array('id_attach' => $attachments));
748
			elseif (!empty($attachments))
749
			{
750
				$messages = removeAttachments(array('id_attach' => $attachments), 'messages', true);
751
752
				// And change the message to reflect this.
753
				if (!empty($messages))
754
				{
755
					loadLanguage('index', $language, true);
756
					setRemovalNotice($messages, $txt['attachment_delete_admin']);
757
					loadLanguage('index', $user_info['language'], true);
758
				}
759
			}
760
		}
761
762
		$sort = $this->_req->getQuery('sort', 'strval', 'date');
763
		redirectexit('action=admin;area=manageattachments;sa=browse;' . $this->_req->query->type . ';sort=' . $sort . (isset($this->_req->query->desc) ? ';desc' : '') . ';start=' . $this->_req->query->start);
764
	}
765
766
	/**
767
	 * Removes all attachments in a single click
768
	 *
769
	 * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=removeall.
770
	 */
771
	public function action_removeall()
772
	{
773
		global $txt;
774
775
		checkSession('get', 'admin');
776
777
		$messages = removeAttachments(array('attachment_type' => 0), '', true);
778
779
		$notice = $this->_req->getPost('notice', 'strval', $txt['attachment_delete_admin']);
780
781
		// Add the notice on the end of the changed messages.
782
		if (!empty($messages))
783
			setRemovalNotice($messages, $notice);
784
785
		redirectexit('action=admin;area=manageattachments;sa=maintenance');
786
	}
787
788
	/**
789
	 * This function will performs many attachment checks and provides ways to fix them
790
	 *
791
	 * What it does:
792
	 * - Checks for the following common issues
793
	 * - Orphan Thumbnails
794
	 * - Attachments that have no thumbnails
795
	 * - Attachments that list thumbnails, but actually, don't have any
796
	 * - Attachments list in the wrong_folder
797
	 * - Attachments that don't exists on disk any longer
798
	 * - Attachments that are zero size
799
	 * - Attachments that file size does not match the DB size
800
	 * - Attachments that no longer have a message
801
	 * - Avatars with no members associated with them.
802
	 * - Attachments that are in the attachment folder, but not listed in the DB
803
	 */
804
	public function action_repair()
0 ignored issues
show
Coding Style introduced by
action_repair uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
805
	{
806
		global $modSettings, $context, $txt;
807
808
		checkSession('get');
809
810
		// If we choose cancel, redirect right back.
811
		if (isset($this->_req->post->cancel))
812
			redirectexit('action=admin;area=manageattachments;sa=maintenance');
813
814
		// Try give us a while to sort this out...
815
		setTimeLimit(600);
816
817
		$this->step = $this->_req->getQuery('step', 'intval', 0);
818
		$this->substep = $this->_req->getQuery('substep', 'intval', 0);
819
		$this->starting_substep = $this->substep;
820
821
		// Don't recall the session just in case.
822
		if ($this->step === 0 && $this->substep === 0)
823
		{
824
			unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']);
825
826
			// If we're actually fixing stuff - work out what.
827
			if (isset($this->_req->query->fixErrors))
828
			{
829
				// Nothing?
830
				if (empty($this->_req->post->to_fix))
831
					redirectexit('action=admin;area=manageattachments;sa=maintenance');
832
833
				foreach ($this->_req->post->to_fix as $key => $value)
834
					$_SESSION['attachments_to_fix'][] = $value;
835
			}
836
		}
837
838
		// All the valid problems are here:
839
		$context['repair_errors'] = array(
840
			'missing_thumbnail_parent' => 0,
841
			'parent_missing_thumbnail' => 0,
842
			'file_missing_on_disk' => 0,
843
			'file_wrong_size' => 0,
844
			'file_size_of_zero' => 0,
845
			'attachment_no_msg' => 0,
846
			'avatar_no_member' => 0,
847
			'wrong_folder' => 0,
848
			'missing_extension' => 0,
849
			'files_without_attachment' => 0,
850
		);
851
852
		$to_fix = !empty($this->_req->session->attachments_to_fix) ? $this->_req->session->attachments_to_fix : array();
853
		$context['repair_errors'] = $this->_req->getSession('attachments_to_fix2', $context['repair_errors']);
854
		$fix_errors = isset($this->_req->query->fixErrors) ? true : false;
855
856
		// Get stranded thumbnails.
857
		if ($this->step <= 0)
858
		{
859
			$thumbnails = getMaxThumbnail();
860
861
			for (; $this->substep < $thumbnails; $this->substep += 500)
862
			{
863
				$removed = findOrphanThumbnails($this->substep, $fix_errors, $to_fix);
864
				$context['repair_errors']['missing_thumbnail_parent'] += count($removed);
865
866
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
867
			}
868
869
			// Done here, on to the next
870
			$this->step = 1;
871
			$this->substep = 0;
872
			$this->_pauseAttachmentMaintenance($to_fix);
873
		}
874
875
		// Find parents which think they have thumbnails, but actually, don't.
876
		if ($this->step <= 1)
877
		{
878
			$thumbnails = maxNoThumb();
879
880
			for (; $this->substep < $thumbnails; $this->substep += 500)
881
			{
882
				$to_update = findParentsOrphanThumbnails($this->substep, $fix_errors, $to_fix);
883
				$context['repair_errors']['parent_missing_thumbnail'] += count($to_update);
884
885
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
886
			}
887
888
			// Another step done, but many to go
889
			$this->step = 2;
890
			$this->substep = 0;
891
			$this->_pauseAttachmentMaintenance($to_fix);
892
		}
893
894
		// This may take forever I'm afraid, but life sucks... recount EVERY attachments!
895
		if ($this->step <= 2)
896
		{
897
			$thumbnails = maxAttachment();
898
899
			for (; $this->substep < $thumbnails; $this->substep += 250)
900
			{
901
				$repair_errors = repairAttachmentData($this->substep, $fix_errors, $to_fix);
902
903
				foreach ($repair_errors as $key => $value)
904
					$context['repair_errors'][$key] += $value;
905
906
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
907
			}
908
909
			// And onward we go
910
			$this->step = 3;
911
			$this->substep = 0;
912
			$this->_pauseAttachmentMaintenance($to_fix);
913
		}
914
915
		// Get avatars with no members associated with them.
916
		if ($this->step <= 3)
917
		{
918
			$thumbnails = maxAttachment();
919
920
			for (; $this->substep < $thumbnails; $this->substep += 500)
921
			{
922
				$to_remove = findOrphanAvatars($this->substep, $fix_errors, $to_fix);
923
				$context['repair_errors']['avatar_no_member'] += count($to_remove);
924
925
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
926
			}
927
928
			$this->step = 4;
929
			$this->substep = 0;
930
			$this->_pauseAttachmentMaintenance($to_fix);
931
		}
932
933
		// What about attachments, who are missing a message :'(
934
		if ($this->step <= 4)
935
		{
936
			$thumbnails = maxAttachment();
937
938
			for (; $this->substep < $thumbnails; $this->substep += 500)
939
			{
940
				$to_remove = findOrphanAttachments($this->substep, $fix_errors, $to_fix);
941
				$context['repair_errors']['attachment_no_msg'] += count($to_remove);
942
943
				$this->_pauseAttachmentMaintenance($to_fix, $thumbnails);
944
			}
945
946
			$this->step = 5;
947
			$this->substep = 0;
948
			$this->_pauseAttachmentMaintenance($to_fix);
949
		}
950
951
		// What about files who are not recorded in the database?
952
		if ($this->step <= 5)
953
		{
954
			// Just use the current path for temp files.
955
			if (!is_array($modSettings['attachmentUploadDir']))
956
				$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
957
958
			$attach_dirs = $modSettings['attachmentUploadDir'];
959
			$current_check = 0;
960
			$max_checks = 500;
961
962
			$files_checked = empty($this->substep) ? 0 : $this->substep;
963
			foreach ($attach_dirs as $attach_dir)
964
			{
965
				try
966
				{
967
					$files = new FilesystemIterator($attach_dir, FilesystemIterator::SKIP_DOTS);
968
					foreach ($files as $file)
969
					{
970
						if ($file->getFilename() === '.htaccess')
971
							continue;
972
973
						if ($files_checked <= $current_check)
974
						{
975
							// Temporary file, get rid of it!
976
							if (strpos($file->getFilename(), 'post_tmp_') !== false)
977
							{
978
								// Temp file is more than 5 hours old!
979
								if ($file->getMTime() < time() - 18000)
980
									@unlink($file->getPathname());
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...
981
							}
982
							// That should be an attachment, let's check if we have it in the database
983
							elseif (strpos($file->getFilename(), '_') !== false)
984
							{
985
								$attachID = (int) substr($file->getFilename(), 0, strpos($file->getFilename(), '_'));
986
								if (!empty($attachID))
987
								{
988
									if (!validateAttachID($attachID))
989
									{
990
										if ($fix_errors && in_array('files_without_attachment', $to_fix))
991
											@unlink($file->getPathname());
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...
992
										else
993
											$context['repair_errors']['files_without_attachment']++;
994
									}
995
								}
996
							}
997
							elseif ($file->getFilename() !== 'index.php' && !$file->isDir())
998
							{
999
								if ($fix_errors && in_array('files_without_attachment', $to_fix))
1000
									@unlink($file->getPathname());
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...
1001
								else
1002
									$context['repair_errors']['files_without_attachment']++;
1003
							}
1004
						}
1005
						$current_check++;
1006
						$this->substep = (int) $current_check;
1007
1008
						if ($current_check - $files_checked >= $max_checks)
1009
							$this->_pauseAttachmentMaintenance($to_fix);
1010
					}
1011
				}
1012
				catch (UnexpectedValueException $e)
1013
				{
1014
					// @todo for now do nothing...
1015
				}
1016
			}
1017
1018
			$this->step = 5;
1019
			$this->substep = 0;
1020
			$this->_pauseAttachmentMaintenance($to_fix);
1021
		}
1022
1023
		// Got here we must be doing well - just the template! :D
1024
		$context['page_title'] = $txt['repair_attachments'];
1025
		$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1026
		$context['sub_template'] = 'attachment_repair';
1027
1028
		// What stage are we at?
1029
		$context['completed'] = $fix_errors ? true : false;
1030
		$context['errors_found'] = false;
1031
		foreach ($context['repair_errors'] as $number)
1032
		{
1033
			if (!empty($number))
1034
			{
1035
				$context['errors_found'] = true;
1036
				break;
1037
			}
1038
		}
1039
	}
1040
1041
	/**
1042
	 * This function lists and allows updating of multiple attachments paths.
1043
	 */
1044
	public function action_attachpaths()
0 ignored issues
show
Coding Style introduced by
action_attachpaths uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1045
	{
1046
		global $modSettings, $scripturl, $context, $txt;
1047
1048
		// Since this needs to be done eventually.
1049
		if (!is_array($modSettings['attachmentUploadDir']))
1050
			$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
1051
1052
		if (!isset($modSettings['attachment_basedirectories']))
1053
			$modSettings['attachment_basedirectories'] = array();
1054
		elseif (!is_array($modSettings['attachment_basedirectories']))
1055
			$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
1056
1057
		$errors = array();
1058
1059
		// Saving?
1060
		if (isset($this->_req->query->save))
1061
		{
1062
			checkSession();
1063
1064
			$this->current_dir = $this->_req->getPost('current_dir', 'intval', 0);
1065
			$new_dirs = array();
1066
1067
			require_once(SUBSDIR . '/Themes.subs.php');
1068
			$themes = installedThemes();
1069
			$reserved_dirs = array(BOARDDIR, SOURCEDIR, SUBSDIR, CONTROLLERDIR, CACHEDIR, EXTDIR, LANGUAGEDIR, ADMINDIR);
1070
			foreach ($themes as $theme)
1071
				$reserved_dirs[] = $theme['theme_dir'];
1072
1073
			foreach ($this->_req->post->dirs as $id => $path)
1074
			{
1075
				$error = '';
1076
				$id = (int) $id;
1077
				if ($id < 1)
1078
					continue;
1079
1080
				$real_path = rtrim(trim($path), DIRECTORY_SEPARATOR);
1081
1082
				// If it doesn't look like a directory, probably is not a directory
1083
				if (preg_match('~[/\\\\]~', $real_path) !== 1)
1084
					$real_path = realpath(BOARDDIR . DIRECTORY_SEPARATOR . ltrim($real_path, DIRECTORY_SEPARATOR));
1085
1086
				// Hmm, a new path maybe?
1087
				if (!array_key_exists($id, $modSettings['attachmentUploadDir']))
1088
				{
1089
					// or is it?
1090
					if (in_array($path, $modSettings['attachmentUploadDir']) || in_array(BOARDDIR . DIRECTORY_SEPARATOR . $path, $modSettings['attachmentUploadDir']))
1091
					{
1092
						$errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg'];
1093
						continue;
1094
					}
1095
1096
					// or is it a system dir?
1097
					if (in_array($real_path, $reserved_dirs))
1098
					{
1099
						$errors[] = $real_path . ': ' . $txt['attach_dir_reserved'];
1100
						continue;
1101
					}
1102
1103
					// OK, so let's try to create it then.
1104
					if (automanage_attachments_create_directory($path))
1105
						$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1106
					else
1107
						$errors[] = $path . ': ' . $txt[$context['dir_creation_error']];
1108
				}
1109
1110
				// Changing a directory name?
1111
				if (!empty($modSettings['attachmentUploadDir'][$id]) && !empty($path) && $real_path != $modSettings['attachmentUploadDir'][$id])
1112
				{
1113
					if ($real_path != $modSettings['attachmentUploadDir'][$id] && !is_dir($real_path))
1114
					{
1115
						if (!@rename($modSettings['attachmentUploadDir'][$id], $real_path))
1116
						{
1117
							$errors[] = $real_path . ': ' . $txt['attach_dir_no_rename'];
1118
							$real_path = $modSettings['attachmentUploadDir'][$id];
1119
						}
1120
					}
1121
					else
1122
					{
1123
						$errors[] = $real_path . ': ' . $txt['attach_dir_exists_msg'];
1124
						$real_path = $modSettings['attachmentUploadDir'][$id];
1125
					}
1126
1127
					// Update the base directory path
1128
					if (!empty($modSettings['attachment_basedirectories']) && array_key_exists($id, $modSettings['attachment_basedirectories']))
1129
					{
1130
						$base = $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id] ? $real_path : $modSettings['basedirectory_for_attachments'];
1131
1132
						$modSettings['attachment_basedirectories'][$id] = $real_path;
1133
						updateSettings(array(
1134
							'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1135
							'basedirectory_for_attachments' => $base,
1136
						));
1137
						$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
1138
					}
1139
				}
1140
1141
				if (empty($path))
1142
				{
1143
					$real_path = $modSettings['attachmentUploadDir'][$id];
1144
1145
					// It's not a good idea to delete the current directory.
1146
					if ($id == (!empty($this->current_dir) ? $this->current_dir : $modSettings['currentAttachmentUploadDir']))
1147
						$errors[] = $real_path . ': ' . $txt['attach_dir_is_current'];
1148
					// Or the current base directory
1149
					elseif (!empty($modSettings['basedirectory_for_attachments']) && $modSettings['basedirectory_for_attachments'] == $modSettings['attachmentUploadDir'][$id])
1150
						$errors[] = $real_path . ': ' . $txt['attach_dir_is_current_bd'];
1151
					else
1152
					{
1153
						// Let's not try to delete a path with files in it.
1154
						$num_attach = countAttachmentsInFolders($id);
1155
1156
						// A check to see if it's a used base dir.
1157
						if (!empty($modSettings['attachment_basedirectories']))
1158
						{
1159
							// Count any sub-folders.
1160
							foreach ($modSettings['attachmentUploadDir'] as $sub)
1161
								if (strpos($sub, $real_path . DIRECTORY_SEPARATOR) !== false)
1162
									$num_attach++;
1163
						}
1164
1165
						// It's safe to delete. So try to delete the folder also
1166
						if ($num_attach == 0)
1167
						{
1168
							if (is_dir($real_path))
1169
								$doit = true;
1170
							elseif (is_dir(BOARDDIR . DIRECTORY_SEPARATOR . $real_path))
1171
							{
1172
								$doit = true;
1173
								$real_path = BOARDDIR . DIRECTORY_SEPARATOR . $real_path;
1174
							}
1175
1176
							if (isset($doit))
1177
							{
1178
								unlink($real_path . '/.htaccess');
1179
								unlink($real_path . '/index.php');
1180
								if (!@rmdir($real_path))
1181
									$error = $real_path . ': ' . $txt['attach_dir_no_delete'];
1182
							}
1183
1184
							// Remove it from the base directory list.
1185
							if (empty($error) && !empty($modSettings['attachment_basedirectories']))
1186
							{
1187
								unset($modSettings['attachment_basedirectories'][$id]);
1188
								updateSettings(array('attachment_basedirectories' => serialize($modSettings['attachment_basedirectories'])));
1189
								$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
1190
							}
1191
						}
1192
						else
1193
							$error = $real_path . ': ' . $txt['attach_dir_no_remove'];
1194
1195
						if (empty($error))
1196
							continue;
1197
						else
1198
							$errors[] = $error;
1199
					}
1200
				}
1201
1202
				$new_dirs[$id] = $real_path;
1203
			}
1204
1205
			// We need to make sure the current directory is right.
1206
			if (empty($this->current_dir) && !empty($modSettings['currentAttachmentUploadDir']))
1207
				$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1208
1209
			// Find the current directory if there's no value carried,
1210
			if (empty($this->current_dir) || empty($new_dirs[$this->current_dir]))
1211
			{
1212
				if (array_key_exists($modSettings['currentAttachmentUploadDir'], $modSettings['attachmentUploadDir']))
1213
					$this->current_dir = $modSettings['currentAttachmentUploadDir'];
1214
				else
1215
					$this->current_dir = max(array_keys($modSettings['attachmentUploadDir']));
1216
			}
1217
1218
			// If the user wishes to go back, update the last_dir array
1219
			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])))
1220
			{
1221
				if (!is_array($modSettings['last_attachments_directory']))
1222
					$modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
1223
1224
				$num = substr(strrchr($modSettings['attachmentUploadDir'][$this->current_dir], '_'), 1);
1225
				if (is_numeric($num))
1226
				{
1227
					// Need to find the base folder.
1228
					$bid = -1;
1229
					$use_subdirectories_for_attachments = 0;
1230
					if (!empty($modSettings['attachment_basedirectories']))
1231
						foreach ($modSettings['attachment_basedirectories'] as $bid => $base)
1232
							if (strpos($modSettings['attachmentUploadDir'][$this->current_dir], $base . DIRECTORY_SEPARATOR) !== false)
1233
							{
1234
								$use_subdirectories_for_attachments = 1;
1235
								break;
1236
							}
1237
1238
					if ($use_subdirectories_for_attachments == 0 && strpos($modSettings['attachmentUploadDir'][$this->current_dir], BOARDDIR . DIRECTORY_SEPARATOR) !== false)
1239
						$bid = 0;
1240
1241
					$modSettings['last_attachments_directory'][$bid] = (int) $num;
1242
					$modSettings['basedirectory_for_attachments'] = !empty($modSettings['basedirectory_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : '';
1243
					$modSettings['use_subdirectories_for_attachments'] = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['use_subdirectories_for_attachments'] : 0;
1244
					updateSettings(array(
1245
						'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
1246
						'basedirectory_for_attachments' => $bid == 0 ? $modSettings['basedirectory_for_attachments'] : $modSettings['attachment_basedirectories'][$bid],
1247
						'use_subdirectories_for_attachments' => $use_subdirectories_for_attachments,
1248
					));
1249
				}
1250
			}
1251
1252
			// Going back to just one path?
1253
			if (count($new_dirs) == 1)
1254
			{
1255
				// We might need to reset the paths. This loop will just loop through once.
1256
				foreach ($new_dirs as $id => $dir)
1257
				{
1258
					if ($id != 1)
1259
						updateAttachmentIdFolder($id, 1);
1260
1261
					$update = array(
1262
						'currentAttachmentUploadDir' => 1,
1263
						'attachmentUploadDir' => serialize(array(1 => $dir)),
1264
					);
1265
				}
1266
			}
1267
			else
1268
			{
1269
				// Save it to the database.
1270
				$update = array(
1271
					'currentAttachmentUploadDir' => $this->current_dir,
1272
					'attachmentUploadDir' => serialize($new_dirs),
1273
				);
1274
			}
1275
1276
			if (!empty($update))
1277
				updateSettings($update);
1278
1279
			if (!empty($errors))
1280
				$_SESSION['errors']['dir'] = $errors;
1281
1282
			redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
1283
		}
1284
1285
		// Saving a base directory?
1286
		if (isset($this->_req->post->save2))
1287
		{
1288
			checkSession();
1289
1290
			// Changing the current base directory?
1291
			$this->current_base_dir = $this->_req->getQuery('current_base_dir', 'intval');
1292
			if (empty($this->_req->post->new_base_dir) && !empty($this->current_base_dir))
1293
			{
1294
				if ($modSettings['basedirectory_for_attachments'] != $modSettings['attachmentUploadDir'][$this->current_base_dir])
1295
					$update = (array(
1296
						'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1297
					));
1298
1299
				//$modSettings['attachmentUploadDir'] = serialize($modSettings['attachmentUploadDir']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1300
			}
1301
1302
			if (isset($this->_req->post->base_dir))
1303
			{
1304
				foreach ($this->_req->post->base_dir as $id => $dir)
1305
				{
1306
					if (!empty($dir) && $dir != $modSettings['attachmentUploadDir'][$id])
1307
					{
1308
						if (@rename($modSettings['attachmentUploadDir'][$id], $dir))
1309
						{
1310
							$modSettings['attachmentUploadDir'][$id] = $dir;
1311
							$modSettings['attachment_basedirectories'][$id] = $dir;
1312
							$update = (array(
1313
								'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
1314
								'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1315
								'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1316
							));
1317
						}
1318
					}
1319
1320
					if (empty($dir))
1321
					{
1322
						if ($id == $this->current_base_dir)
1323
						{
1324
							$errors[] = $modSettings['attachmentUploadDir'][$id] . ': ' . $txt['attach_dir_is_current'];
1325
							continue;
1326
						}
1327
1328
						unset($modSettings['attachment_basedirectories'][$id]);
1329
						$update = (array(
1330
							'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1331
							'basedirectory_for_attachments' => $modSettings['attachmentUploadDir'][$this->current_base_dir],
1332
						));
1333
					}
1334
				}
1335
			}
1336
1337
			// Or adding a new one?
1338
			if (!empty($this->_req->post->new_base_dir))
1339
			{
1340
				$this->_req->post->new_base_dir = htmlspecialchars($this->_req->post->new_base_dir, ENT_QUOTES, 'UTF-8');
1341
1342
				$current_dir = $modSettings['currentAttachmentUploadDir'];
1343
1344
				if (!in_array($this->_req->post->new_base_dir, $modSettings['attachmentUploadDir']))
1345
				{
1346
					if (!automanage_attachments_create_directory($this->_req->post->new_base_dir))
1347
						$errors[] = $this->_req->post->new_base_dir . ': ' . $txt['attach_dir_base_no_create'];
1348
				}
1349
1350
				$modSettings['currentAttachmentUploadDir'] = array_search($this->_req->post->new_base_dir, $modSettings['attachmentUploadDir']);
1351
				if (!in_array($this->_req->post->new_base_dir, $modSettings['attachment_basedirectories']))
1352
					$modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $this->_req->post->new_base_dir;
1353
				ksort($modSettings['attachment_basedirectories']);
1354
1355
				$update = (array(
1356
					'attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']),
1357
					'basedirectory_for_attachments' => $this->_req->post->new_base_dir,
1358
					'currentAttachmentUploadDir' => $current_dir,
1359
				));
1360
			}
1361
1362
			if (!empty($errors))
1363
				$_SESSION['errors']['base'] = $errors;
1364
1365
			if (!empty($update))
1366
				updateSettings($update);
1367
1368
			redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']);
1369
		}
1370
1371
		if (isset($this->_req->session->errors))
1372
		{
1373
			if (is_array($this->_req->session->errors))
1374
			{
1375
				$errors = array();
1376
				if (!empty($this->_req->session->errors['dir']))
1377
					foreach ($this->_req->session->errors['dir'] as $error)
1378
						$errors['dir'][] = Util::htmlspecialchars($error, ENT_QUOTES);
1379
1380
				if (!empty($this->_req->session->errors['base']))
1381
					foreach ($this->_req->session->errors['base'] as $error)
1382
						$errors['base'][] = Util::htmlspecialchars($error, ENT_QUOTES);
1383
			}
1384
			unset($_SESSION['errors'], $this->_req->session->errors);
1385
		}
1386
1387
		$listOptions = array(
1388
			'id' => 'attach_paths',
1389
			'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1390
			'title' => $txt['attach_paths'],
1391
			'get_items' => array(
1392
				'function' => 'list_getAttachDirs',
1393
			),
1394
			'columns' => array(
1395
				'current_dir' => array(
1396
					'header' => array(
1397
						'value' => $txt['attach_current'],
1398
						'class' => 'centertext',
1399
					),
1400
					'data' => array(
1401
						'function' => function ($rowData) {
1402
							return '<input type="radio" name="current_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . (!empty($rowData['disable_current']) ? ' disabled="disabled"' : '') . ' class="input_radio" />';
1403
						},
1404
						'style' => 'width: 10%;',
1405
						'class' => 'centertext',
1406
					),
1407
				),
1408
				'path' => array(
1409
					'header' => array(
1410
						'value' => $txt['attach_path'],
1411
					),
1412
					'data' => array(
1413
						'function' => function ($rowData) {
1414
							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"/>';
1415
						},
1416
						'style' => 'width: 40%;',
1417
					),
1418
				),
1419
				'current_size' => array(
1420
					'header' => array(
1421
						'value' => $txt['attach_current_size'],
1422
					),
1423
					'data' => array(
1424
						'db' => 'current_size',
1425
						'style' => 'width: 15%;',
1426
					),
1427
				),
1428
				'num_files' => array(
1429
					'header' => array(
1430
						'value' => $txt['attach_num_files'],
1431
					),
1432
					'data' => array(
1433
						'db' => 'num_files',
1434
						'style' => 'width: 15%;',
1435
					),
1436
				),
1437
				'status' => array(
1438
					'header' => array(
1439
						'value' => $txt['attach_dir_status'],
1440
					),
1441
					'data' => array(
1442
						'db' => 'status',
1443
						'style' => 'width: 25%;',
1444
					),
1445
				),
1446
			),
1447
			'form' => array(
1448
				'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1449
			),
1450
			'additional_rows' => array(
1451
				array(
1452
					'class' => 'submitbutton',
1453
					'position' => 'below_table_data',
1454
					'value' => '
1455
					<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
1456
					<input type="submit" name="save" value="' . $txt['save'] . '" class="right_submit" />
1457
					<input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" class="right_submit" />',
1458
				),
1459
				empty($errors['dir']) ? array(
1460
					'position' => 'top_of_list',
1461
					'value' => $txt['attach_dir_desc'],
1462
					'style' => 'padding: 5px 10px;',
1463
					'class' => 'smalltext'
1464
				) : array(
1465
					'position' => 'top_of_list',
1466
					'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['dir']),
1467
					'style' => 'padding-left: 35px;',
1468
					'class' => 'warningbox',
1469
				),
1470
			),
1471
		);
1472
		createList($listOptions);
1473
1474
		if (!empty($modSettings['attachment_basedirectories']))
1475
		{
1476
			$listOptions2 = array(
1477
				'id' => 'base_paths',
1478
				'base_href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1479
				'title' => $txt['attach_base_paths'],
1480
				'get_items' => array(
1481
					'function' => 'list_getBaseDirs',
1482
				),
1483
				'columns' => array(
1484
					'current_dir' => array(
1485
						'header' => array(
1486
							'value' => $txt['attach_current'],
1487
							'class' => 'centertext',
1488
						),
1489
						'data' => array(
1490
							'function' => function ($rowData) {
1491
								return '<input type="radio" name="current_base_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . ' class="input_radio" />';
1492
							},
1493
							'style' => 'width: 10%;',
1494
							'class' => 'centertext',
1495
						),
1496
					),
1497
					'path' => array(
1498
						'header' => array(
1499
							'value' => $txt['attach_path'],
1500
						),
1501
						'data' => array(
1502
							'db' => 'path',
1503
							'style' => 'width: 45%;',
1504
						),
1505
					),
1506
					'num_dirs' => array(
1507
						'header' => array(
1508
							'value' => $txt['attach_num_dirs'],
1509
						),
1510
						'data' => array(
1511
							'db' => 'num_dirs',
1512
							'style' => 'width: 15%;',
1513
						),
1514
					),
1515
					'status' => array(
1516
						'header' => array(
1517
							'value' => $txt['attach_dir_status'],
1518
						),
1519
						'data' => array(
1520
							'db' => 'status',
1521
							'style' => 'width: 15%;',
1522
						),
1523
					),
1524
				),
1525
				'form' => array(
1526
					'href' => $scripturl . '?action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id'],
1527
				),
1528
				'additional_rows' => array(
1529
					array(
1530
						'class' => 'submitbutton',
1531
						'position' => 'below_table_data',
1532
						'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
1533
						<input type="submit" name="save2" value="' . $txt['save'] . '" class="right_submit" />
1534
						<input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" class="right_submit" />',
1535
					),
1536
					empty($errors['base']) ? array(
1537
						'position' => 'top_of_list',
1538
						'value' => $txt['attach_dir_base_desc'],
1539
						'style' => 'padding: 5px 10px;',
1540
						'class' => 'smalltext'
1541
					) : array(
1542
						'position' => 'top_of_list',
1543
						'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['base']),
1544
						'style' => 'padding-left: 35px',
1545
						'class' => 'warningbox',
1546
					),
1547
				),
1548
			);
1549
			createList($listOptions2);
1550
		}
1551
1552
		// Fix up our template.
1553
		$context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths';
1554
		$context['page_title'] = $txt['attach_path_manage'];
1555
	}
1556
1557
	/**
1558
	 * Maintenance function to move attachments from one directory to another
1559
	 */
1560
	public function action_transfer()
0 ignored issues
show
Coding Style introduced by
action_transfer uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1561
	{
1562
		global $modSettings, $txt;
1563
1564
		checkSession();
1565
1566
		// The list(s) of directory's that are available.
1567
		$modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
1568
		if (!empty($modSettings['attachment_basedirectories']))
1569
			$modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
1570
		else
1571
			$modSettings['basedirectory_for_attachments'] = array();
1572
1573
		// Clean the inputs
1574
		$this->from = $this->_req->getPost('from', 'intval');
1575
		$this->auto = $this->_req->getPost('auto', 'intval', 0);
1576
		$this->to = $this->_req->getPost('to', 'intval');
1577
		$start = !empty($this->_req->post->empty_it) ? 0 : $modSettings['attachmentDirFileLimit'];
1578
		$_SESSION['checked'] = !empty($this->_req->post->empty_it) ? true : false;
1579
1580
		// Prepare for the moving
1581
		$limit = 501;
1582
		$results = array();
1583
		$dir_files = 0;
1584
		$current_progress = 0;
1585
		$total_moved = 0;
1586
		$total_not_moved = 0;
1587
		$total_progress = 0;
1588
1589
		// Need to know where we are moving things from
1590
		if (empty($this->from) || (empty($this->auto) && empty($this->to)))
1591
			$results[] = $txt['attachment_transfer_no_dir'];
1592
1593
		// Same location, that's easy
1594
		if ($this->from == $this->to)
1595
			$results[] = $txt['attachment_transfer_same_dir'];
1596
1597
		// No errors so determine how many we may have to move
1598
		if (empty($results))
1599
		{
1600
			// Get the total file count for the progress bar.
1601
			$total_progress = getFolderAttachmentCount($this->from);
1602
			$total_progress -= $start;
1603
1604
			if ($total_progress < 1)
1605
				$results[] = $txt['attachment_transfer_no_find'];
1606
		}
1607
1608
		// Nothing to move (no files in source or below the max limit)
1609
		if (empty($results))
1610
		{
1611
			// Moving them automatically?
1612
			if (!empty($this->auto))
1613
			{
1614
				$modSettings['automanage_attachments'] = 1;
1615
1616
				// Create sub directory's off the root or from an attachment directory?
1617
				$modSettings['use_subdirectories_for_attachments'] = $this->auto == -1 ? 0 : 1;
1618
				$modSettings['basedirectory_for_attachments'] = $this->auto > 0 ? $modSettings['attachmentUploadDir'][$this->auto] : $modSettings['basedirectory_for_attachments'];
1619
1620
				// Finally, where do they need to go
1621
				automanage_attachments_check_directory();
1622
				$new_dir = $modSettings['currentAttachmentUploadDir'];
1623
			}
1624
			// Or to a specified directory
1625
			else
1626
				$new_dir = $this->to;
1627
1628
			$modSettings['currentAttachmentUploadDir'] = $new_dir;
1629
			$break = false;
1630
			while ($break === false)
1631
			{
1632
				setTimeLimit(300);
1633
1634
				// If limits are set, get the file count and size for the destination folder
1635
				if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit'])))
1636
				{
1637
					$current_dir = attachDirProperties($new_dir);
1638
					$dir_files = $current_dir['files'];
1639
					$dir_size = $current_dir['size'];
1640
				}
1641
1642
				// Find some attachments to move
1643
				list ($tomove_count, $tomove) = findAttachmentsToMove($this->from, $start, $limit);
1644
1645
				// Nothing found to move
1646
				if ($tomove_count === 0)
1647
				{
1648
					if (empty($current_progress))
1649
						$results[] = $txt['attachment_transfer_no_find'];
1650
					break;
1651
				}
1652
1653
				// No more to move after this batch then set the finished flag.
1654
				if ($tomove_count < $limit)
1655
					$break = true;
1656
1657
				// Move them
1658
				$moved = array();
1659
				$dir_size = empty($dir_size) ? 0 : $dir_size;
1660
				foreach ($tomove as $row)
1661
				{
1662
					$source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1663
					$dest = $modSettings['attachmentUploadDir'][$new_dir] . '/' . basename($source);
1664
1665
					// Size and file count check
1666
					if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
1667
					{
1668
						$dir_files++;
1669
						$dir_size += !empty($row['size']) ? $row['size'] : filesize($source);
1670
1671
						// If we've reached a directory limit. Do something if we are in auto mode, otherwise set an error.
1672
						if (!empty($modSettings['attachmentDirSizeLimit']) && $dir_size > $modSettings['attachmentDirSizeLimit'] * 1024 || (!empty($modSettings['attachmentDirFileLimit']) && $dir_files > $modSettings['attachmentDirFileLimit']))
1673
						{
1674
							// Since we're in auto mode. Create a new folder and reset the counters.
1675
							if (!empty($this->auto))
1676
							{
1677
								automanage_attachments_by_space();
1678
1679
								$results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
1680
								if (!empty($total_not_moved))
1681
									$results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved);
1682
1683
								$dir_files = 0;
1684
								$total_moved = 0;
1685
								$total_not_moved = 0;
1686
1687
								$break = false;
1688
								break;
1689
							}
1690
							// Hmm, not in auto. Time to bail out then...
1691
							else
1692
							{
1693
								$results[] = $txt['attachment_transfer_no_room'];
1694
								$break = true;
1695
								break;
1696
							}
1697
						}
1698
					}
1699
1700
					// Actually move the file
1701
					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.

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...
1702
					{
1703
						$total_moved++;
1704
						$current_progress++;
1705
						$moved[] = $row['id_attach'];
1706
					}
1707
					else
1708
						$total_not_moved++;
1709
				}
1710
1711
				// Update the database to reflect the new file location
1712
				if (!empty($moved))
1713
					moveAttachments($moved, $new_dir);
1714
1715
				$new_dir = $modSettings['currentAttachmentUploadDir'];
1716
1717
				// Create / update the progress bar.
1718
				// @todo why was this done this way?
1719
				if (!$break)
1720
				{
1721
					$percent_done = min(round($current_progress / $total_progress * 100, 0), 100);
1722
					$prog_bar = '
1723
						<div class="progress_bar">
1724
							<div class="full_bar">' . $percent_done . '%</div>
1725
							<div class="green_percent" style="width: ' . $percent_done . '%;">&nbsp;</div>
1726
						</div>';
1727
1728
					// Write it to a file so it can be displayed
1729
					$fp = fopen(BOARDDIR . '/progress.php', 'w');
1730
					fwrite($fp, $prog_bar);
1731
					fclose($fp);
1732
					usleep(500000);
1733
				}
1734
			}
1735
1736
			$results[] = sprintf($txt['attachments_transfered'], $total_moved, $modSettings['attachmentUploadDir'][$new_dir]);
1737
			if (!empty($total_not_moved))
1738
				$results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved);
1739
		}
1740
1741
		// All done, time to clean up
1742
		$_SESSION['results'] = $results;
1743
		if (file_exists(BOARDDIR . '/progress.php'))
1744
			unlink(BOARDDIR . '/progress.php');
1745
1746
		redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer');
1747
	}
1748
1749
	/**
1750
	 * Function called in-between each round of attachments and avatar repairs.
1751
	 *
1752
	 * What it does:
1753
	 * - Called by repairAttachments().
1754
	 * - If repairAttachments() has more steps added, this function needs updated!
1755
	 *
1756
	 * @package Attachments
1757
	 * @param mixed[] $to_fix attachments to fix
1758
	 * @param int $max_substep = 0
1759
	 * @todo Move to ManageAttachments.subs.php
1760
	 */
1761
	private function _pauseAttachmentMaintenance($to_fix, $max_substep = 0)
0 ignored issues
show
Coding Style introduced by
_pauseAttachmentMaintenance uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1762
	{
1763
		global $context, $txt, $time_start;
1764
1765
		// Try get more time...
1766
		setTimeLimit(600);
1767
1768
		// Have we already used our maximum time?
1769
		if (microtime(true) - $time_start < 3 || $this->starting_substep == $this->substep)
1770
			return;
1771
1772
		$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'];
1773
		$context['page_title'] = $txt['not_done_title'];
1774
		$context['continue_post_data'] = '';
1775
		$context['continue_countdown'] = '2';
1776
		$context['sub_template'] = 'not_done';
1777
1778
		// Specific stuff to not break this template!
1779
		$context[$context['admin_menu_name']]['current_subsection'] = 'maintenance';
1780
1781
		// Change these two if more steps are added!
1782
		if (empty($max_substep))
1783
			$context['continue_percent'] = round(($this->step * 100) / 25);
1784
		else
1785
			$context['continue_percent'] = round(($this->step * 100 + ($this->substep * 100) / $max_substep) / 25);
1786
1787
		// Never more than 100%!
1788
		$context['continue_percent'] = min($context['continue_percent'], 100);
1789
1790
		// Save the needed information for the next look
1791
		$_SESSION['attachments_to_fix'] = $to_fix;
1792
		$_SESSION['attachments_to_fix2'] = $context['repair_errors'];
1793
1794
		obExit();
1795
	}
1796
}