1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Handles the job of attachment and avatar maintenance / management. |
||
5 | * |
||
6 | * @package ElkArte Forum |
||
7 | * @copyright ElkArte Forum contributors |
||
8 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||
9 | * |
||
10 | * This file contains code covered by: |
||
11 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||
12 | * |
||
13 | * @version 2.0 dev |
||
14 | * |
||
15 | */ |
||
16 | |||
17 | namespace ElkArte\AdminController; |
||
18 | |||
19 | use ElkArte\AbstractController; |
||
20 | use ElkArte\Action; |
||
21 | use ElkArte\Attachments\AttachmentsDirectory; |
||
22 | use ElkArte\Graphics\Image; |
||
23 | use ElkArte\Graphics\Manipulators\Gd2; |
||
24 | use ElkArte\Graphics\Manipulators\ImageMagick; |
||
25 | use ElkArte\Helper\FileFunctions; |
||
26 | use ElkArte\Helper\Util; |
||
27 | use ElkArte\Languages\Loader; |
||
28 | use ElkArte\SettingsForm\SettingsForm; |
||
29 | use Exception; |
||
30 | use FilesystemIterator; |
||
31 | use UnexpectedValueException; |
||
32 | |||
33 | /** |
||
34 | * This is the attachments and avatars controller class. |
||
35 | * It is doing the job of attachments and avatars maintenance and management. |
||
36 | * |
||
37 | */ |
||
38 | class ManageAttachments extends AbstractController |
||
39 | { |
||
40 | /** @var int Loop counter for paused attachment maintenance actions */ |
||
41 | public $step; |
||
42 | |||
43 | /** @var int substep counter for paused attachment maintenance actions */ |
||
44 | public $substep; |
||
45 | |||
46 | /** @var int Substep at the beginning of a maintenance loop */ |
||
47 | public $starting_substep; |
||
48 | |||
49 | /** @var int Current directory key being processed */ |
||
50 | public $current_dir; |
||
51 | |||
52 | /** @var string Used during transfer of files */ |
||
53 | public $from; |
||
54 | |||
55 | /** @var string Type of attachment management in use */ |
||
56 | public $auto; |
||
57 | |||
58 | /** @var string Destination when transferring attachments */ |
||
59 | public $to; |
||
60 | |||
61 | /** @var \ElkArte\Helper\FileFunctions */ |
||
62 | public $file_functions; |
||
63 | |||
64 | /** |
||
65 | * Pre dispatch, load functions needed by all methods |
||
66 | */ |
||
67 | public function pre_dispatch() |
||
68 | { |
||
69 | // These get used often enough that it makes sense to include them for every action |
||
70 | require_once(SUBSDIR . '/Attachments.subs.php'); |
||
71 | require_once(SUBSDIR . '/ManageAttachments.subs.php'); |
||
72 | $this->file_functions = FileFunctions::instance(); |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * The main 'Attachments and Avatars' admin. |
||
77 | * |
||
78 | * What it does: |
||
79 | * |
||
80 | * - This method is the entry point for index.php?action=admin;area=manageattachments |
||
81 | * and it calls a function based on the sub-action. |
||
82 | * - It requires the manage_attachments permission. |
||
83 | * |
||
84 | * @event integrate_sa_manage_attachments |
||
85 | * @uses ManageAttachments template. |
||
86 | * @uses Admin language file. |
||
87 | * @uses template layer 'manage_files' for showing the tab bar. |
||
88 | * |
||
89 | * @see AbstractController::action_index() |
||
90 | 2 | */ |
|
91 | public function action_index() |
||
92 | { |
||
93 | 2 | global $txt, $context; |
|
94 | 2 | ||
95 | 2 | // You have to be able to moderate the forum to do this. |
|
96 | isAllowedTo('manage_attachments'); |
||
97 | |||
98 | // Setup the template stuff we'll probably need. |
||
99 | theme()->getTemplates()->load('ManageAttachments'); |
||
100 | |||
101 | // All the things we can do with attachments |
||
102 | $subActions = array( |
||
103 | 'attachments' => array($this, 'action_attachSettings_display'), |
||
104 | 'avatars' => array( |
||
105 | 'controller' => ManageAvatars::class, |
||
106 | 'function' => 'action_index'), |
||
107 | 'attachpaths' => array($this, 'action_attachpaths'), |
||
108 | 'browse' => array($this, 'action_browse'), |
||
109 | 'byAge' => array($this, 'action_byAge'), |
||
110 | 'bySize' => array($this, 'action_bySize'), |
||
111 | 'maintenance' => array($this, 'action_maintenance'), |
||
112 | 'repair' => array($this, 'action_repair'), |
||
113 | 'remove' => array($this, 'action_remove'), |
||
114 | 'removeall' => array($this, 'action_removeall'), |
||
115 | 'transfer' => array($this, 'action_transfer'), |
||
116 | ); |
||
117 | |||
118 | // Get ready for some action |
||
119 | $action = new Action('manage_attachments'); |
||
120 | |||
121 | // Default page title is good. |
||
122 | $context['page_title'] = $txt['attachments_avatars']; |
||
123 | |||
124 | // Get the subAction, call integrate_sa_manage_attachments |
||
125 | $subAction = $action->initialize($subActions, 'browse'); |
||
126 | $context['sub_action'] = $subAction; |
||
127 | |||
128 | // This uses admin tabs - as it should! |
||
129 | $context[$context['admin_menu_name']]['object']->prepareTabData([ |
||
130 | 'title' => 'attachments_avatars', |
||
131 | 'help' => 'manage_files', |
||
132 | 'description' => 'attachments_desc', |
||
133 | ]); |
||
134 | |||
135 | // Finally, go to where we want to go |
||
136 | $action->dispatch($subAction); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Allows showing / changing attachment settings. |
||
141 | * |
||
142 | * - This is the default sub-action of the 'Attachments and Avatars' center. |
||
143 | * - Called by index.php?action=admin;area=manageattachments;sa=attachments. |
||
144 | * |
||
145 | * @event integrate_save_attachment_settings |
||
146 | * @uses 'attachments' sub template. |
||
147 | */ |
||
148 | public function action_attachSettings_display() |
||
149 | { |
||
150 | global $modSettings, $context; |
||
151 | |||
152 | // initialize the form |
||
153 | $settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER); |
||
154 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||
155 | |||
156 | // Initialize settings |
||
157 | $settingsForm->setConfigVars($this->_settings()); |
||
158 | |||
159 | theme()->addInlineJavascript(' |
||
160 | let storing_type = document.getElementById(\'automanage_attachments\'), |
||
161 | base_dir = document.getElementById(\'use_subdirectories_for_attachments\'); |
||
162 | |||
163 | createEventListener(storing_type) |
||
164 | storing_type.addEventListener("change", toggleSubDir, false); |
||
165 | createEventListener(base_dir) |
||
166 | base_dir.addEventListener("change", toggleSubDir, false); |
||
167 | toggleSubDir();', true); |
||
168 | |||
169 | // Saving settings? |
||
170 | if (isset($this->_req->query->save)) |
||
171 | { |
||
172 | checkSession(); |
||
173 | |||
174 | if (!empty($this->_req->post->attachmentEnable)) |
||
175 | { |
||
176 | enableModules('attachments', array('post', 'display')); |
||
177 | } |
||
178 | else |
||
179 | { |
||
180 | disableModules('attachments', array('post', 'display')); |
||
181 | } |
||
182 | |||
183 | // Default/Manual implies no subdirectories |
||
184 | if (empty($this->_req->post->automanage_attachments)) |
||
185 | { |
||
186 | $this->_req->post->use_subdirectories_for_attachments = 0; |
||
187 | } |
||
188 | |||
189 | // Changing the attachment upload directory |
||
190 | if (isset($this->_req->post->attachmentUploadDir)) |
||
191 | { |
||
192 | if (!empty($this->_req->post->attachmentUploadDir) |
||
193 | && $modSettings['attachmentUploadDir'] !== $this->_req->post->attachmentUploadDir |
||
194 | && $this->file_functions->fileExists($modSettings['attachmentUploadDir'])) |
||
195 | { |
||
196 | rename($modSettings['attachmentUploadDir'], $this->_req->post->attachmentUploadDir); |
||
197 | } |
||
198 | |||
199 | $modSettings['attachmentUploadDir'] = array(1 => $this->_req->post->attachmentUploadDir); |
||
200 | $this->_req->post->attachmentUploadDir = serialize($modSettings['attachmentUploadDir']); |
||
201 | } |
||
202 | |||
203 | // Adding / changing the sub directory's for attachments |
||
204 | if (!empty($this->_req->post->use_subdirectories_for_attachments)) |
||
205 | { |
||
206 | // Make sure we have a base directory defined |
||
207 | if (empty($this->_req->post->basedirectory_for_attachments)) |
||
208 | { |
||
209 | $this->_req->post->basedirectory_for_attachments = (empty($modSettings['basedirectory_for_attachments']) ? (BOARDDIR) : $modSettings['basedirectory_for_attachments']); |
||
210 | } |
||
211 | |||
212 | // The current base directories that we know |
||
213 | if (!empty($modSettings['attachment_basedirectories'])) |
||
214 | { |
||
215 | if (!is_array($modSettings['attachment_basedirectories'])) |
||
216 | { |
||
217 | $modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']); |
||
218 | } |
||
219 | } |
||
220 | else |
||
221 | { |
||
222 | $modSettings['attachment_basedirectories'] = array(); |
||
223 | } |
||
224 | |||
225 | // Trying to use a nonexistent base directory |
||
226 | if (!empty($this->_req->post->basedirectory_for_attachments) && $attachmentsDir->isBaseDir($this->_req->post->basedirectory_for_attachments) === false) |
||
227 | { |
||
228 | $currentAttachmentUploadDir = $attachmentsDir->currentDirectoryId(); |
||
229 | |||
230 | // If this is a new directory being defined, attempt to create it |
||
231 | if ($attachmentsDir->directoryExists($this->_req->post->basedirectory_for_attachments) === false) |
||
232 | { |
||
233 | try |
||
234 | { |
||
235 | $attachmentsDir->createDirectory($this->_req->post->basedirectory_for_attachments); |
||
236 | } |
||
237 | catch (Exception) |
||
238 | { |
||
239 | $this->_req->post->basedirectory_for_attachments = $modSettings['basedirectory_for_attachments']; |
||
240 | } |
||
241 | } |
||
242 | |||
243 | // The base directory should be in our list of available bases |
||
244 | if (!in_array($this->_req->post->basedirectory_for_attachments, $modSettings['attachment_basedirectories'])) |
||
245 | { |
||
246 | $modSettings['attachment_basedirectories'][$modSettings['currentAttachmentUploadDir']] = $this->_req->post->basedirectory_for_attachments; |
||
247 | updateSettings(array('attachment_basedirectories' => serialize($modSettings['attachment_basedirectories']), 'currentAttachmentUploadDir' => $currentAttachmentUploadDir,)); |
||
248 | |||
249 | $this->_req->post->attachmentUploadDir = serialize($modSettings['attachmentUploadDir']); |
||
250 | } |
||
251 | } |
||
252 | } |
||
253 | |||
254 | // Allow or not webp extensions. |
||
255 | if (!empty($this->_req->post->attachment_webp_enable) && strpos($this->_req->post->attachmentExtensions, 'webp') === false) |
||
256 | { |
||
257 | $this->_req->post->attachmentExtensions .= ',webp'; |
||
258 | } |
||
259 | |||
260 | call_integration_hook('integrate_save_attachment_settings'); |
||
261 | |||
262 | $settingsForm->setConfigValues((array) $this->_req->post); |
||
263 | $settingsForm->save(); |
||
264 | redirectexit('action=admin;area=manageattachments;sa=attachments'); |
||
265 | } |
||
266 | |||
267 | $context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachments', 'save']); |
||
268 | $settingsForm->prepare(); |
||
269 | |||
270 | $context['sub_template'] = 'show_settings'; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Retrieve and return the administration settings for attachments. |
||
275 | * |
||
276 | * @event integrate_modify_attachment_settings |
||
277 | */ |
||
278 | private function _settings() |
||
279 | { |
||
280 | global $modSettings, $txt, $context, $settings; |
||
281 | |||
282 | // Get the current attachment directory. |
||
283 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||
284 | $context['attachmentUploadDir'] = $attachmentsDir->getCurrent(); |
||
285 | |||
286 | // First time here? |
||
287 | if (empty($modSettings['attachment_basedirectories']) && $modSettings['currentAttachmentUploadDir'] == 1 && (is_array($modSettings['attachmentUploadDir']) && $attachmentsDir->countDirs() == 1)) |
||
288 | { |
||
289 | 2 | $modSettings['attachmentUploadDir'] = $modSettings['attachmentUploadDir'][1]; |
|
290 | } |
||
291 | 2 | ||
292 | // If not set, show a default path for the base directory |
||
293 | if (!isset($this->_req->query->save) && empty($modSettings['basedirectory_for_attachments'])) |
||
294 | 2 | { |
|
295 | 2 | $modSettings['basedirectory_for_attachments'] = $context['attachmentUploadDir']; |
|
296 | } |
||
297 | |||
298 | 2 | $context['valid_upload_dir'] = $this->file_functions->isDir($context['attachmentUploadDir']) && $this->file_functions->isWritable($context['attachmentUploadDir']); |
|
299 | |||
300 | if ($attachmentsDir->autoManageEnabled()) |
||
301 | { |
||
302 | $context['valid_basedirectory'] = !empty($modSettings['basedirectory_for_attachments']) && $this->file_functions->isWritable($modSettings['basedirectory_for_attachments']); |
||
303 | } |
||
304 | 2 | else |
|
305 | { |
||
306 | $context['valid_basedirectory'] = true; |
||
307 | } |
||
308 | |||
309 | 2 | // A bit of razzle dazzle with the $txt strings. :) |
|
310 | $txt['basedirectory_for_attachments_warning'] = str_replace('{attach_repair_url}', getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths']), $txt['basedirectory_for_attachments_warning']); |
||
311 | 2 | $txt['attach_current_dir_warning'] = str_replace('{attach_repair_url}', getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths']), $txt['attach_current_dir_warning']); |
|
312 | |||
313 | $txt['attachment_path'] = $context['attachmentUploadDir']; |
||
314 | $txt['basedirectory_for_attachments_path'] = $modSettings['basedirectory_for_attachments'] ?? ''; |
||
315 | $txt['use_subdirectories_for_attachments_note'] = empty($modSettings['attachment_basedirectories']) || empty($modSettings['use_subdirectories_for_attachments']) ? $txt['use_subdirectories_for_attachments_note'] : ''; |
||
316 | $txt['attachmentUploadDir_multiple_configure'] = '<a class="linkbutton" href="' . getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths']) . '">' . $txt['attachmentUploadDir_multiple_configure'] . '</a>'; |
||
317 | 2 | $txt['attach_current_dir'] = $attachmentsDir->autoManageEnabled() ? $txt['attach_last_dir'] : $txt['attach_current_dir']; |
|
318 | $txt['attach_current_dir_warning'] = $txt['attach_current_dir'] . $txt['attach_current_dir_warning']; |
||
319 | $txt['basedirectory_for_attachments_warning'] = $txt['basedirectory_for_attachments_current'] . $txt['basedirectory_for_attachments_warning']; |
||
320 | |||
321 | 2 | // Perform several checks to determine imaging capabilities. |
|
322 | 2 | $image = new Image($settings['default_theme_dir'] . '/images/blank.png'); |
|
323 | $testImg = Gd2::canUse() || ImageMagick::canUse(); |
||
324 | 2 | $testImgRotate = ImageMagick::canUse() || (Gd2::canUse() && function_exists('exif_read_data')); |
|
325 | 2 | ||
326 | 2 | // Check on webp support, and correct if wrong |
|
327 | 2 | $testWebP = $image->hasWebpSupport(); |
|
328 | 2 | if (!$testWebP && !empty($modSettings['attachment_webp_enable'])) |
|
329 | 2 | { |
|
330 | 2 | updateSettings(['attachment_webp_enable' => 0]); |
|
331 | } |
||
332 | |||
333 | 2 | // Check if the server settings support these upload size values |
|
334 | $post_max_size = ini_get('post_max_size'); |
||
335 | $upload_max_filesize = ini_get('upload_max_filesize'); |
||
336 | 2 | $testPM = empty($post_max_size) || memoryReturnBytes($post_max_size) >= (isset($modSettings['attachmentPostLimit']) ? $modSettings['attachmentPostLimit'] * 1024 : 0); |
|
337 | 2 | $testUM = empty($upload_max_filesize) || memoryReturnBytes($upload_max_filesize) >= (isset($modSettings['attachmentSizeLimit']) ? $modSettings['attachmentSizeLimit'] * 1024 : 0); |
|
338 | 2 | ||
339 | 2 | // Set some helpful information for the UI |
|
340 | 2 | $post_max_size_text = sprintf($txt['zero_for_system_limit'], $post_max_size === '' || $post_max_size === '0' || $post_max_size === false ? $txt['none'] : $post_max_size, 'post_max_size'); |
|
341 | $upload_max_filesize_text = sprintf($txt['zero_for_system_limit'], $upload_max_filesize === '' || $upload_max_filesize === '0' || $upload_max_filesize === false ? $txt['none'] : $upload_max_filesize, 'upload_max_filesize'); |
||
342 | |||
343 | 2 | $config_vars = array( |
|
344 | array('title', 'attachment_manager_settings'), |
||
345 | 2 | // Are attachments enabled? |
|
346 | 2 | array('select', 'attachmentEnable', array($txt['attachmentEnable_deactivate'], $txt['attachmentEnable_enable_all'], $txt['attachmentEnable_disable_new'])), |
|
347 | '', |
||
348 | 2 | // Directory and size limits. |
|
349 | 2 | 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'])), |
|
350 | 2 | array('check', 'use_subdirectories_for_attachments', 'subtext' => $txt['use_subdirectories_for_attachments_note']), |
|
351 | (empty($modSettings['attachment_basedirectories']) |
||
352 | ? array('text', 'basedirectory_for_attachments', 40,) |
||
353 | : array('var_message', 'basedirectory_for_attachments', 'message' => 'basedirectory_for_attachments_path', 'invalid' => empty($context['valid_basedirectory']), 'text_label' => (empty($context['valid_basedirectory']) |
||
354 | 2 | ? $txt['basedirectory_for_attachments_warning'] |
|
355 | : $txt['basedirectory_for_attachments_current'])) |
||
356 | 2 | ), |
|
357 | Util::is_serialized($modSettings['attachmentUploadDir']) |
||
358 | 2 | ? array('var_message', 'attach_current_directory', 'postinput' => $txt['attachmentUploadDir_multiple_configure'], 'message' => 'attachment_path', 'invalid' => empty($context['valid_upload_dir']), |
|
359 | 'text_label' => (empty($context['valid_upload_dir']) |
||
360 | 2 | ? $txt['attach_current_dir_warning'] |
|
361 | : $txt['attach_current_dir'])) |
||
362 | 2 | : array('text', 'attachmentUploadDir', 'postinput' => $txt['attachmentUploadDir_multiple_configure'], 40, 'invalid' => !$context['valid_upload_dir']), |
|
363 | 2 | array('int', 'attachmentDirFileLimit', 'subtext' => $txt['zero_for_no_limit'], 6), |
|
364 | 2 | array('int', 'attachmentDirSizeLimit', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['kilobyte']), |
|
365 | '', |
||
366 | 2 | // Posting limits |
|
367 | 2 | array('int', 'attachmentPostLimit', 'subtext' => $post_max_size_text, 6, 'postinput' => $testPM === false ? $txt['attachment_postsize_warning'] : $txt['kilobyte'], 'invalid' => $testPM === false), |
|
368 | 2 | array('int', 'attachmentSizeLimit', 'subtext' => $upload_max_filesize_text, 6, 'postinput' => $testUM === false ? $txt['attachment_postsize_warning'] : $txt['kilobyte'], 'invalid' => $testUM === false), |
|
369 | 2 | array('int', 'attachmentNumPerPostLimit', 'subtext' => $txt['zero_for_no_limit'], 6), |
|
370 | 2 | '', |
|
371 | 2 | array('check', 'attachment_webp_enable', 'disabled' => !$testWebP, 'postinput' => $testWebP ? "" : $txt['attachment_webp_enable_na']), |
|
372 | array('check', 'attachment_autorotate', 'disabled' => !$testImgRotate, 'postinput' => $testImgRotate ? '' : $txt['attachment_autorotate_na']), |
||
373 | // Resize limits |
||
374 | array('title', 'attachment_image_resize'), |
||
375 | array('check', 'attachment_image_resize_enabled'), |
||
376 | array('check', 'attachment_image_resize_reformat'), |
||
377 | 2 | array('text', 'attachment_image_resize_width', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['attachment_image_resize_post']), |
|
378 | array('text', 'attachment_image_resize_height', 'subtext' => $txt['zero_for_no_limit'], 6, 'postinput' => $txt['attachment_image_resize_post']), |
||
379 | 2 | // Security Items |
|
380 | array('title', 'attachment_security_settings'), |
||
381 | // Extension checks etc. |
||
382 | array('check', 'attachmentCheckExtensions'), |
||
383 | array('text', 'attachmentExtensions', 40), |
||
384 | '', |
||
385 | // Image checks. |
||
386 | array('warning', $testImg === false ? 'attachment_img_enc_warning' : ''), |
||
387 | array('check', 'attachment_image_reencode'), |
||
388 | 2 | // Thumbnail settings. |
|
389 | 2 | array('title', 'attachment_thumbnail_settings'), |
|
390 | 2 | array('check', 'attachmentShowImages'), |
|
391 | array('check', 'attachmentThumbnails'), |
||
392 | array('text', 'attachmentThumbWidth', 6), |
||
393 | array('text', 'attachmentThumbHeight', 6), |
||
394 | 2 | '', |
|
395 | array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']), |
||
396 | 2 | array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']), |
|
397 | ); |
||
398 | |||
399 | // Add new settings with a nice hook, makes them available for admin settings search as well |
||
400 | call_integration_hook('integrate_modify_attachment_settings', array(&$config_vars)); |
||
401 | |||
402 | 2 | return $config_vars; |
|
403 | } |
||
404 | 2 | ||
405 | /** |
||
406 | * Public method to return the config settings, used for admin search |
||
407 | */ |
||
408 | public function settings_search() |
||
409 | { |
||
410 | return $this->_settings(); |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Show a list of attachment or avatar files. |
||
415 | * |
||
416 | * - Called by |
||
417 | * ?action=admin;area=manageattachments;sa=browse for attachments |
||
418 | * ?action=admin;area=manageattachments;sa=browse;avatars for avatars. |
||
419 | * ?action=admin;area=manageattachments;sa=browse;thumbs for thumbnails. |
||
420 | * - Allows sorting by name, date, size and member. |
||
421 | * - Paginates results. |
||
422 | * |
||
423 | * @uses the 'browse' sub template |
||
424 | */ |
||
425 | public function action_browse() |
||
426 | { |
||
427 | global $context, $txt, $modSettings; |
||
428 | |||
429 | // Attachments or avatars? |
||
430 | $context['browse_type'] = isset($this->_req->query->avatars) ? 'avatars' : (isset($this->_req->query->thumbs) ? 'thumbs' : 'attachments'); |
||
431 | loadJavascriptFile('topic.js'); |
||
432 | |||
433 | // Set the options for the list component. |
||
434 | $listOptions = array( |
||
435 | 'id' => 'attach_browse', |
||
436 | 'title' => $txt['attachment_manager_browse_files'], |
||
437 | 'items_per_page' => $modSettings['defaultMaxMessages'], |
||
438 | 'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'browse'] + ($context['browse_type'] === 'avatars' ? ['avatars'] : ($context['browse_type'] === 'thumbs' ? ['thumbs'] : []))), |
||
439 | 'default_sort_col' => 'name', |
||
440 | 'no_items_label' => $txt['attachment_manager_' . ($context['browse_type'] === 'avatars' ? 'avatars' : ($context['browse_type'] === 'thumbs' ? 'thumbs' : 'attachments')) . '_no_entries'], |
||
441 | 'get_items' => array( |
||
442 | 'function' => 'list_getFiles', |
||
443 | 'params' => array( |
||
444 | $context['browse_type'], |
||
445 | ), |
||
446 | ), |
||
447 | 'get_count' => array( |
||
448 | 'function' => 'list_getNumFiles', |
||
449 | 'params' => array( |
||
450 | $context['browse_type'], |
||
451 | ), |
||
452 | ), |
||
453 | 'columns' => array( |
||
454 | 'name' => array( |
||
455 | 'header' => array( |
||
456 | 'value' => $txt['attachment_name'], |
||
457 | 'class' => 'grid50', |
||
458 | ), |
||
459 | 'data' => array( |
||
460 | 'function' => static function ($rowData) { |
||
461 | global $modSettings, $context; |
||
462 | |||
463 | $link = '<a href="'; |
||
464 | // In case of a custom avatar URL attachments have a fixed directory. |
||
465 | if ((int) $rowData['attachment_type'] === 1) |
||
466 | { |
||
467 | $link .= sprintf('%1$s/%2$s', $modSettings['custom_avatar_url'], $rowData['filename']); |
||
468 | } |
||
469 | // By default, avatars are downloaded almost as attachments. |
||
470 | elseif ($context['browse_type'] === 'avatars') |
||
471 | { |
||
472 | $link .= getUrl('attach', ['action' => 'dlattach', 'type' => 'avatar', 'attach' => (int) $rowData['id_attach'], 'name' => $rowData['filename']]); |
||
473 | } |
||
474 | // Normal attachments are always linked to a topic ID. |
||
475 | else |
||
476 | { |
||
477 | $link .= getUrl('attach', ['action' => 'dlattach', 'topic' => ((int) $rowData['id_topic']) . '.0', 'attach' => (int) $rowData['id_attach'], 'name' => $rowData['filename']]); |
||
478 | } |
||
479 | $link .= '"'; |
||
480 | |||
481 | // Show a popup on click if it's a picture and we know its dimensions (use rand message to prevent navigation) |
||
482 | if (!empty($rowData['width']) && !empty($rowData['height'])) |
||
483 | { |
||
484 | $link .= 'id="link_' . $rowData['id_attach'] . '" data-lightboxmessage="' . random_int(0, 100000) . '" data-lightboximage="' . $rowData['id_attach'] . '"'; |
||
485 | } |
||
486 | |||
487 | $link .= sprintf('>%1$s</a>', preg_replace('~&#(\\\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\\\1;', htmlspecialchars($rowData['filename'], ENT_COMPAT, 'UTF-8'))); |
||
488 | |||
489 | // Show the dimensions. |
||
490 | if (!empty($rowData['width']) && !empty($rowData['height'])) |
||
491 | { |
||
492 | $link .= sprintf(' <span class="smalltext">%1$dx%2$d</span>', $rowData['width'], $rowData['height']); |
||
493 | } |
||
494 | |||
495 | return $link; |
||
496 | }, |
||
497 | ), |
||
498 | 'sort' => array( |
||
499 | 'default' => 'a.filename', |
||
500 | 'reverse' => 'a.filename DESC', |
||
501 | ), |
||
502 | ), |
||
503 | 'filesize' => array( |
||
504 | 'header' => array( |
||
505 | 'value' => $txt['attachment_file_size'], |
||
506 | 'class' => 'nowrap', |
||
507 | ), |
||
508 | 'data' => array( |
||
509 | 'function' => static fn($rowData) => byte_format($rowData['size']), |
||
510 | ), |
||
511 | 'sort' => array( |
||
512 | 'default' => 'a.size', |
||
513 | 'reverse' => 'a.size DESC', |
||
514 | ), |
||
515 | ), |
||
516 | 'member' => array( |
||
517 | 'header' => array( |
||
518 | 'value' => $context['browse_type'] === 'avatars' ? $txt['attachment_manager_member'] : $txt['posted_by'], |
||
519 | 'class' => 'nowrap', |
||
520 | ), |
||
521 | 'data' => array( |
||
522 | 'function' => static function ($rowData) { |
||
523 | // In case of an attachment, return the poster of the attachment. |
||
524 | if (empty($rowData['id_member'])) |
||
525 | { |
||
526 | return htmlspecialchars($rowData['poster_name'], ENT_COMPAT, 'UTF-8'); |
||
527 | } |
||
528 | |||
529 | return '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => (int) $rowData['id_member'], 'name' => $rowData['poster_name']]) . '">' . $rowData['poster_name'] . '</a>'; |
||
530 | }, |
||
531 | ), |
||
532 | 'sort' => array( |
||
533 | 'default' => 'mem.real_name', |
||
534 | 'reverse' => 'mem.real_name DESC', |
||
535 | ), |
||
536 | ), |
||
537 | 'date' => array( |
||
538 | 'header' => array( |
||
539 | 'value' => $context['browse_type'] === 'avatars' ? $txt['attachment_manager_last_active'] : $txt['date'], |
||
540 | 'class' => 'nowrap', |
||
541 | ), |
||
542 | 'data' => array( |
||
543 | 'function' => static function ($rowData) { |
||
544 | global $txt, $context; |
||
545 | |||
546 | // The date the message containing the attachment was posted or the owner of the avatar was active. |
||
547 | $date = empty($rowData['poster_time']) ? $txt['never'] : standardTime($rowData['poster_time']); |
||
548 | // Add a link to the topic in case of an attachment. |
||
549 | if ($context['browse_type'] !== 'avatars') |
||
550 | { |
||
551 | $date .= '<br />' . $txt['in'] . ' <a href="' . getUrl('topic', ['topic' => (int) $rowData['id_topic'], 'start' => 'msg' . (int) $rowData['id_msg'], 'subject' => $rowData['subject']]) . '#msg' . (int) $rowData['id_msg'] . '">' . $rowData['subject'] . '</a>'; |
||
552 | } |
||
553 | |||
554 | return $date; |
||
555 | }, |
||
556 | ), |
||
557 | 'sort' => array( |
||
558 | 'default' => $context['browse_type'] === 'avatars' ? 'mem.last_login' : 'm.id_msg', |
||
559 | 'reverse' => $context['browse_type'] === 'avatars' ? 'mem.last_login DESC' : 'm.id_msg DESC', |
||
560 | ), |
||
561 | ), |
||
562 | 'downloads' => array( |
||
563 | 'header' => array( |
||
564 | 'value' => $txt['downloads'], |
||
565 | 'class' => 'nowrap', |
||
566 | ), |
||
567 | 'data' => array( |
||
568 | 'db' => 'downloads', |
||
569 | 'comma_format' => true, |
||
570 | ), |
||
571 | 'sort' => array( |
||
572 | 'default' => 'a.downloads', |
||
573 | 'reverse' => 'a.downloads DESC', |
||
574 | ), |
||
575 | ), |
||
576 | 'check' => array( |
||
577 | 'header' => array( |
||
578 | 'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />', |
||
579 | 'class' => 'centertext', |
||
580 | ), |
||
581 | 'data' => array( |
||
582 | 'sprintf' => array( |
||
583 | 'format' => '<input type="checkbox" name="remove[%1$d]" class="input_check" />', |
||
584 | 'params' => array( |
||
585 | 'id_attach' => false, |
||
586 | ), |
||
587 | ), |
||
588 | 'class' => 'centertext', |
||
589 | ), |
||
590 | ), |
||
591 | ), |
||
592 | 'form' => array( |
||
593 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'remove', ($context['browse_type'] === 'avatars' ? 'avatars' : ($context['browse_type'] === 'thumbs' ? 'thumbs' : ''))]), |
||
594 | 'include_sort' => true, |
||
595 | 'include_start' => true, |
||
596 | 'hidden_fields' => array( |
||
597 | 'type' => $context['browse_type'], |
||
598 | ), |
||
599 | ), |
||
600 | 'additional_rows' => array( |
||
601 | array( |
||
602 | 'position' => 'below_table_data', |
||
603 | 'value' => '<input type="submit" name="remove_submit" class="right_submit" value="' . $txt['quickmod_delete_selected'] . '" onclick="return confirm(\'' . $txt['confirm_delete_attachments'] . '\');" />', |
||
604 | ), |
||
605 | ), |
||
606 | 'list_menu' => array( |
||
607 | 'show_on' => 'top', |
||
608 | 'class' => 'flow_flex_right', |
||
609 | 'links' => array( |
||
610 | array( |
||
611 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'browse']), |
||
612 | 'is_selected' => $context['browse_type'] === 'attachments', |
||
613 | 'label' => $txt['attachment_manager_attachments'] |
||
614 | ), |
||
615 | array( |
||
616 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'browse', 'avatars']), |
||
617 | 'is_selected' => $context['browse_type'] === 'avatars', |
||
618 | 'label' => $txt['attachment_manager_avatars'] |
||
619 | ), |
||
620 | array( |
||
621 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'browse', 'thumbs']), |
||
622 | 'is_selected' => $context['browse_type'] === 'thumbs', |
||
623 | 'label' => $txt['attachment_manager_thumbs'] |
||
624 | ), |
||
625 | ), |
||
626 | ), |
||
627 | ); |
||
628 | |||
629 | // Create the list. |
||
630 | createList($listOptions); |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Show several file maintenance options. |
||
635 | * |
||
636 | * What it does: |
||
637 | * |
||
638 | * - Called by ?action=admin;area=manageattachments;sa=maintain. |
||
639 | * - Calculates file statistics (total file size, number of attachments, |
||
640 | * number of avatars, attachment space available). |
||
641 | * |
||
642 | * @uses the 'maintenance' sub template. |
||
643 | */ |
||
644 | public function action_maintenance() |
||
645 | { |
||
646 | global $context, $modSettings; |
||
647 | |||
648 | theme()->getTemplates()->load('ManageAttachments'); |
||
649 | $context['sub_template'] = 'maintenance'; |
||
650 | |||
651 | // We need our attachments directories... |
||
652 | $attachmentDirectory = new AttachmentsDirectory($modSettings, database()); |
||
653 | $attach_dirs = $attachmentDirectory->getPaths(); |
||
654 | |||
655 | // Get the number of attachments... |
||
656 | $context['num_attachments'] = comma_format(getAttachmentCountByType('attachments'), 0); |
||
657 | |||
658 | // Also get the avatar amount... |
||
659 | $context['num_avatars'] = comma_format(getAttachmentCountByType('avatars'), 0); |
||
660 | |||
661 | // Total size of attachments |
||
662 | $context['attachment_total_size'] = overallAttachmentsSize(); |
||
663 | |||
664 | // Total size and files from the current attachment dir. |
||
665 | $current_dir = currentAttachDirProperties(); |
||
666 | |||
667 | // If they specified a limit only.... |
||
668 | if ($attachmentDirectory->hasSizeLimit()) |
||
669 | { |
||
670 | $context['attachment_space'] = comma_format($attachmentDirectory->remainingSpace($current_dir['size']), 2); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
671 | } |
||
672 | |||
673 | if ($attachmentDirectory->hasNumFilesLimit()) |
||
674 | { |
||
675 | $context['attachment_files'] = comma_format($attachmentDirectory->remainingFiles($current_dir['files']), 0); |
||
676 | } |
||
677 | |||
678 | $context['attachment_current_size'] = byte_format($current_dir['size']); |
||
679 | $context['attachment_current_files'] = comma_format($current_dir['files'], 0); |
||
680 | $context['attach_multiple_dirs'] = count($attach_dirs) > 1; |
||
681 | $context['attach_dirs'] = $attach_dirs; |
||
682 | $context['base_dirs'] = empty($modSettings['attachment_basedirectories']) ? array() : Util::unserialize($modSettings['attachment_basedirectories']); |
||
683 | $context['checked'] = $this->_req->getSession('checked', true); |
||
684 | |||
685 | if (!empty($_SESSION['results'])) |
||
686 | { |
||
687 | $context['results'] = implode('<br />', $this->_req->session->results); |
||
688 | unset($_SESSION['results']); |
||
689 | } |
||
690 | } |
||
691 | |||
692 | /** |
||
693 | * Remove attachments older than a given age. |
||
694 | * |
||
695 | * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=byAge. |
||
696 | * - It optionally adds a certain text to the messages the attachments were removed from. |
||
697 | * |
||
698 | * @todo refactor this silly superglobals use... |
||
699 | */ |
||
700 | public function action_byAge() |
||
701 | { |
||
702 | checkSession('post', 'admin'); |
||
703 | |||
704 | // @todo Ignore messages in topics that are stickied? |
||
705 | |||
706 | // Deleting an attachment? |
||
707 | if (!$this->_req->compareQuery('type', 'avatars', 'trim|strval')) |
||
708 | { |
||
709 | // Get rid of all the old attachments. |
||
710 | $messages = removeAttachments(array('attachment_type' => 0, 'poster_time' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'messages', true); |
||
711 | |||
712 | // Update the messages to reflect the change. |
||
713 | if (!empty($messages) && !empty($this->_req->post->notice)) |
||
714 | { |
||
715 | setRemovalNotice($messages, $this->_req->post->notice); |
||
716 | } |
||
717 | } |
||
718 | // Remove all the old avatars. |
||
719 | else |
||
720 | { |
||
721 | removeAttachments(array('not_id_member' => 0, 'last_login' => (time() - 24 * 60 * 60 * $this->_req->post->age)), 'members'); |
||
722 | } |
||
723 | |||
724 | redirectexit('action=admin;area=manageattachments' . (empty($this->_req->query->avatars) ? ';sa=maintenance' : ';avatars')); |
||
725 | } |
||
726 | |||
727 | /** |
||
728 | * Remove attachments larger than a given size. |
||
729 | * |
||
730 | * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=bySize. |
||
731 | * - Optionally adds a certain text to the messages the attachments were removed from. |
||
732 | */ |
||
733 | public function action_bySize() |
||
734 | { |
||
735 | checkSession('post', 'admin'); |
||
736 | |||
737 | // Find humongous attachments. |
||
738 | $messages = removeAttachments(array('attachment_type' => 0, 'size' => 1024 * $this->_req->post->size), 'messages', true); |
||
739 | |||
740 | // And make a note on the post. |
||
741 | if (!empty($messages) && !empty($this->_req->post->notice)) |
||
742 | { |
||
743 | setRemovalNotice($messages, $this->_req->post->notice); |
||
744 | } |
||
745 | |||
746 | redirectexit('action=admin;area=manageattachments;sa=maintenance'); |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * Remove a selection of attachments or avatars. |
||
751 | * |
||
752 | * - Called from the browse screen as submitted form by ?action=admin;area=manageattachments;sa=remove |
||
753 | */ |
||
754 | public function action_remove() |
||
755 | { |
||
756 | global $language; |
||
757 | |||
758 | checkSession(); |
||
759 | |||
760 | if (!empty($this->_req->post->remove)) |
||
761 | { |
||
762 | // There must be a quicker way to pass this safety test?? |
||
763 | $attachments = array(); |
||
764 | foreach ($this->_req->post->remove as $removeID => $dummy) |
||
765 | { |
||
766 | $attachments[] = (int) $removeID; |
||
767 | } |
||
768 | |||
769 | if ($this->_req->compareQuery('type', 'avatars', 'trim|strval') && !empty($attachments)) |
||
770 | { |
||
771 | removeAttachments(array('id_attach' => $attachments)); |
||
772 | } |
||
773 | elseif (!empty($attachments)) |
||
774 | { |
||
775 | $messages = removeAttachments(array('id_attach' => $attachments), 'messages', true); |
||
776 | |||
777 | // And change the message to reflect this. |
||
778 | if (!empty($messages)) |
||
779 | { |
||
780 | $mtxt = []; |
||
781 | $lang = new Loader($language, $mtxt, database()); |
||
782 | $lang->load('Admin'); |
||
783 | setRemovalNotice($messages, $mtxt['attachment_delete_admin']); |
||
784 | } |
||
785 | } |
||
786 | } |
||
787 | |||
788 | $sort = $this->_req->getQuery('sort', 'trim|strval', 'date'); |
||
789 | redirectexit('action=admin;area=manageattachments;sa=browse;' . $this->_req->query->type . ';sort=' . $sort . (isset($this->_req->query->desc) ? ';desc' : '') . ';start=' . $this->_req->query->start); |
||
790 | } |
||
791 | |||
792 | /** |
||
793 | * Removes all attachments in a single click |
||
794 | * |
||
795 | * - Called from the maintenance screen by ?action=admin;area=manageattachments;sa=removeall. |
||
796 | */ |
||
797 | public function action_removeall() |
||
798 | { |
||
799 | global $txt; |
||
800 | |||
801 | checkSession('get', 'admin'); |
||
802 | |||
803 | $messages = removeAttachments(array('attachment_type' => 0), '', true); |
||
804 | |||
805 | $notice = $this->_req->getPost('notice', 'trim|strval', $txt['attachment_delete_admin']); |
||
806 | |||
807 | // Add the notice on the end of the changed messages. |
||
808 | if (!empty($messages)) |
||
809 | { |
||
810 | setRemovalNotice($messages, $notice); |
||
811 | } |
||
812 | |||
813 | redirectexit('action=admin;area=manageattachments;sa=maintenance'); |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * This function will perform many attachment checks and provides ways to fix them |
||
818 | * |
||
819 | * What it does: |
||
820 | * |
||
821 | * Checks for the following common issues |
||
822 | * - Orphan Thumbnails |
||
823 | * - Attachments that have no thumbnails |
||
824 | * - Attachments that list thumbnails, but actually, don't have any |
||
825 | * - Attachments list in the wrong_folder |
||
826 | * - Attachments that don't exist on disk any longer |
||
827 | * - Attachments that are zero size |
||
828 | * - Attachments that file size does not match the DB size |
||
829 | * - Attachments that no longer have a message |
||
830 | * - Avatars with no members associated with them. |
||
831 | * - Attachments that are in the attachment folder, but not listed in the DB |
||
832 | */ |
||
833 | public function action_repair() |
||
834 | { |
||
835 | global $modSettings, $context, $txt; |
||
836 | |||
837 | checkSession('get'); |
||
838 | |||
839 | // If we choose cancel, redirect right back. |
||
840 | if (isset($this->_req->post->cancel)) |
||
841 | { |
||
842 | redirectexit('action=admin;area=manageattachments;sa=maintenance'); |
||
843 | } |
||
844 | |||
845 | // Try give us a while to sort this out... |
||
846 | detectServer()->setTimeLimit(600); |
||
847 | |||
848 | $this->step = $this->_req->getQuery('step', 'intval', 0); |
||
849 | $this->substep = $this->_req->getQuery('substep', 'intval', 0); |
||
850 | $this->starting_substep = $this->substep; |
||
851 | |||
852 | // Don't recall the session just in case. |
||
853 | if ($this->step === 0 && $this->substep === 0) |
||
854 | { |
||
855 | unset($_SESSION['attachments_to_fix'], $_SESSION['attachments_to_fix2']); |
||
856 | |||
857 | // If we're actually fixing stuff - work out what. |
||
858 | if (isset($this->_req->query->fixErrors)) |
||
859 | { |
||
860 | // Nothing? |
||
861 | if (empty($this->_req->post->to_fix)) |
||
862 | { |
||
863 | redirectexit('action=admin;area=manageattachments;sa=maintenance'); |
||
864 | } |
||
865 | |||
866 | foreach ($this->_req->post->to_fix as $value) |
||
867 | { |
||
868 | $_SESSION['attachments_to_fix'][] = $value; |
||
869 | } |
||
870 | } |
||
871 | } |
||
872 | |||
873 | // All the valid problems are here: |
||
874 | $context['repair_errors'] = [ |
||
875 | 'missing_thumbnail_parent' => 0, |
||
876 | 'parent_missing_thumbnail' => 0, |
||
877 | 'file_missing_on_disk' => 0, |
||
878 | 'file_wrong_size' => 0, |
||
879 | 'file_size_of_zero' => 0, |
||
880 | 'attachment_no_msg' => 0, |
||
881 | 'avatar_no_member' => 0, |
||
882 | 'wrong_folder' => 0, |
||
883 | 'missing_extension' => 0, |
||
884 | 'files_without_attachment' => 0, |
||
885 | ]; |
||
886 | |||
887 | $to_fix = empty($_SESSION['attachments_to_fix']) ? array() : $_SESSION['attachments_to_fix']; |
||
888 | $context['repair_errors'] = $_SESSION['attachments_to_fix2'] ?? $context['repair_errors']; |
||
889 | $fix_errors = isset($this->_req->query->fixErrors); |
||
890 | |||
891 | // Get stranded thumbnails. |
||
892 | if ($this->step <= 0) |
||
893 | { |
||
894 | $thumbnails = getMaxThumbnail(); |
||
895 | |||
896 | for (; $this->substep < $thumbnails; $this->substep += 500) |
||
897 | { |
||
898 | $removed = findOrphanThumbnails($this->substep, $fix_errors, $to_fix); |
||
899 | $context['repair_errors']['missing_thumbnail_parent'] += count($removed); |
||
900 | |||
901 | pauseAttachmentMaintenance($to_fix, $thumbnails, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
902 | } |
||
903 | |||
904 | // Done here, on to the next |
||
905 | $this->step = 1; |
||
906 | $this->substep = 0; |
||
907 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
908 | } |
||
909 | |||
910 | // Find parents which think they have thumbnails, but actually, don't. |
||
911 | if ($this->step <= 1) |
||
912 | { |
||
913 | $thumbnails = maxNoThumb(); |
||
914 | |||
915 | for (; $this->substep < $thumbnails; $this->substep += 500) |
||
916 | { |
||
917 | $to_update = findParentsOrphanThumbnails($this->substep, $fix_errors, $to_fix); |
||
918 | $context['repair_errors']['parent_missing_thumbnail'] += count($to_update); |
||
919 | |||
920 | pauseAttachmentMaintenance($to_fix, $thumbnails, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
921 | } |
||
922 | |||
923 | // Another step done, but many to go |
||
924 | $this->step = 2; |
||
925 | $this->substep = 0; |
||
926 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
927 | } |
||
928 | |||
929 | // This may take forever I'm afraid, but life sucks... recount EVERY attachments! |
||
930 | if ($this->step <= 2) |
||
931 | { |
||
932 | $thumbnails = maxAttachment(); |
||
933 | |||
934 | for (; $this->substep < $thumbnails; $this->substep += 250) |
||
935 | { |
||
936 | $repair_errors = repairAttachmentData($this->substep, $fix_errors, $to_fix); |
||
937 | |||
938 | foreach ($repair_errors as $key => $value) |
||
939 | { |
||
940 | $context['repair_errors'][$key] += $value; |
||
941 | } |
||
942 | |||
943 | pauseAttachmentMaintenance($to_fix, $thumbnails, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
944 | } |
||
945 | |||
946 | // And onward we go |
||
947 | $this->step = 3; |
||
948 | $this->substep = 0; |
||
949 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
950 | } |
||
951 | |||
952 | // Get avatars with no members associated with them. |
||
953 | if ($this->step <= 3) |
||
954 | { |
||
955 | $thumbnails = maxAttachment(); |
||
956 | |||
957 | for (; $this->substep < $thumbnails; $this->substep += 500) |
||
958 | { |
||
959 | $to_remove = findOrphanAvatars($this->substep, $fix_errors, $to_fix); |
||
960 | $context['repair_errors']['avatar_no_member'] += count($to_remove); |
||
961 | |||
962 | pauseAttachmentMaintenance($to_fix, $thumbnails, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
963 | } |
||
964 | |||
965 | $this->step = 4; |
||
966 | $this->substep = 0; |
||
967 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
968 | } |
||
969 | |||
970 | // What about attachments, who are missing a message :'( |
||
971 | if ($this->step <= 4) |
||
972 | { |
||
973 | $thumbnails = maxAttachment(); |
||
974 | |||
975 | for (; $this->substep < $thumbnails; $this->substep += 500) |
||
976 | { |
||
977 | $to_remove = findOrphanAttachments($this->substep, $fix_errors, $to_fix); |
||
978 | $context['repair_errors']['attachment_no_msg'] += count($to_remove); |
||
979 | |||
980 | pauseAttachmentMaintenance($to_fix, $thumbnails, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
981 | } |
||
982 | |||
983 | $this->step = 5; |
||
984 | $this->substep = 0; |
||
985 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
986 | } |
||
987 | |||
988 | // What about files who are not recorded in the database? |
||
989 | if ($this->step <= 5) |
||
990 | { |
||
991 | // Just use the current path for temp files. |
||
992 | if (!is_array($modSettings['attachmentUploadDir'])) |
||
993 | { |
||
994 | $modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']); |
||
995 | } |
||
996 | |||
997 | $attach_dirs = $modSettings['attachmentUploadDir']; |
||
998 | $current_check = 0; |
||
999 | $max_checks = 500; |
||
1000 | $attachment_count = getAttachmentCountFromDisk(); |
||
1001 | |||
1002 | $files_checked = empty($this->substep) ? 0 : $this->substep; |
||
1003 | foreach ($attach_dirs as $attach_dir) |
||
1004 | { |
||
1005 | try |
||
1006 | { |
||
1007 | $files = new FilesystemIterator($attach_dir, FilesystemIterator::SKIP_DOTS); |
||
1008 | foreach ($files as $file) |
||
1009 | { |
||
1010 | if ($file->getFilename() === '.htaccess') |
||
1011 | { |
||
1012 | continue; |
||
1013 | } |
||
1014 | |||
1015 | if ($files_checked <= $current_check) |
||
1016 | { |
||
1017 | // Temporary file, get rid of it! |
||
1018 | if (strpos($file->getFilename(), 'post_tmp_') !== false) |
||
1019 | { |
||
1020 | // Temp file is more than 5 hours old! |
||
1021 | if ($file->getMTime() < time() - 18000) |
||
1022 | { |
||
1023 | $this->file_functions->delete($file->getPathname()); |
||
1024 | } |
||
1025 | } |
||
1026 | // That should be an attachment, let's check if we have it in the database |
||
1027 | elseif (strpos($file->getFilename(), '_') !== false) |
||
1028 | { |
||
1029 | $attachID = (int) substr($file->getFilename(), 0, strpos($file->getFilename(), '_')); |
||
1030 | if ($attachID !== 0 && !validateAttachID($attachID)) |
||
1031 | { |
||
1032 | if ($fix_errors && in_array('files_without_attachment', $to_fix)) |
||
1033 | { |
||
1034 | $this->file_functions->delete($file->getPathname()); |
||
1035 | } |
||
1036 | else |
||
1037 | { |
||
1038 | $context['repair_errors']['files_without_attachment']++; |
||
1039 | } |
||
1040 | } |
||
1041 | } |
||
1042 | elseif ($file->getFilename() !== 'index.php' && !$file->isDir()) |
||
1043 | { |
||
1044 | if ($fix_errors && in_array('files_without_attachment', $to_fix, true)) |
||
1045 | { |
||
1046 | $this->file_functions->delete($file->getPathname()); |
||
1047 | } |
||
1048 | else |
||
1049 | { |
||
1050 | $context['repair_errors']['files_without_attachment']++; |
||
1051 | } |
||
1052 | } |
||
1053 | } |
||
1054 | |||
1055 | $current_check++; |
||
1056 | $this->substep = $current_check; |
||
1057 | |||
1058 | if ($current_check - $files_checked >= $max_checks) |
||
1059 | { |
||
1060 | pauseAttachmentMaintenance($to_fix, $attachment_count, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
1061 | } |
||
1062 | } |
||
1063 | } |
||
1064 | catch (UnexpectedValueException) |
||
1065 | { |
||
1066 | // @todo for now do nothing... |
||
1067 | } |
||
1068 | } |
||
1069 | |||
1070 | $this->step = 5; |
||
1071 | $this->substep = 0; |
||
1072 | pauseAttachmentMaintenance($to_fix, 0, $this->starting_substep, $this->substep, $this->step, $fix_errors); |
||
1073 | } |
||
1074 | |||
1075 | // Got here we must be doing well - just the template! :D |
||
1076 | $context['page_title'] = $txt['repair_attachments']; |
||
1077 | $context[$context['admin_menu_name']]['current_subsection'] = 'maintenance'; |
||
1078 | $context['sub_template'] = 'attachment_repair'; |
||
1079 | |||
1080 | // What stage are we at? |
||
1081 | $context['completed'] = $fix_errors; |
||
1082 | $context['errors_found'] = false; |
||
1083 | foreach ($context['repair_errors'] as $number) |
||
1084 | { |
||
1085 | if (!empty($number)) |
||
1086 | { |
||
1087 | $context['errors_found'] = true; |
||
1088 | break; |
||
1089 | } |
||
1090 | } |
||
1091 | } |
||
1092 | |||
1093 | /** |
||
1094 | * Function called in-between each round of attachments and avatar repairs. |
||
1095 | * |
||
1096 | * What it does: |
||
1097 | * |
||
1098 | * - Called by repairAttachments(). |
||
1099 | * - If repairAttachments() has more steps added, this function needs to be updated! |
||
1100 | * |
||
1101 | * @param array $to_fix attachments to fix |
||
1102 | * @param int $max_substep = 0 |
||
1103 | * @throws \ElkArte\Exceptions\Exception |
||
1104 | * @todo Move to ManageAttachments.subs.php |
||
1105 | */ |
||
1106 | private function _pauseAttachmentMaintenance($to_fix, $max_substep = 0) |
||
1107 | { |
||
1108 | global $context, $txt, $time_start; |
||
1109 | |||
1110 | // Try get more time... |
||
1111 | detectServer()->setTimeLimit(600); |
||
1112 | |||
1113 | // Have we already used our maximum time? |
||
1114 | if (microtime(true) - $time_start < 3 || $this->starting_substep == $this->substep) |
||
1115 | { |
||
1116 | return; |
||
1117 | } |
||
1118 | |||
1119 | $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']; |
||
1120 | $context['page_title'] = $txt['not_done_title']; |
||
1121 | $context['continue_post_data'] = ''; |
||
1122 | $context['continue_countdown'] = '2'; |
||
1123 | $context['sub_template'] = 'not_done'; |
||
1124 | |||
1125 | // Specific stuff to not break this template! |
||
1126 | $context[$context['admin_menu_name']]['current_subsection'] = 'maintenance'; |
||
1127 | |||
1128 | // Change these two if more steps are added! |
||
1129 | if (empty($max_substep)) |
||
1130 | { |
||
1131 | $context['continue_percent'] = round(($this->step * 100) / 25); |
||
1132 | } |
||
1133 | else |
||
1134 | { |
||
1135 | $context['continue_percent'] = round(($this->step * 100 + ($this->substep * 100) / $max_substep) / 25); |
||
1136 | } |
||
1137 | |||
1138 | // Never more than 100%! |
||
1139 | $context['continue_percent'] = min($context['continue_percent'], 100); |
||
1140 | |||
1141 | // Save the needed information for the next look |
||
1142 | $_SESSION['attachments_to_fix'] = $to_fix; |
||
1143 | $_SESSION['attachments_to_fix2'] = $context['repair_errors']; |
||
1144 | |||
1145 | obExit(); |
||
1146 | } |
||
1147 | |||
1148 | /** |
||
1149 | * This function lists and allows updating of multiple attachments paths. |
||
1150 | */ |
||
1151 | public function action_attachpaths() |
||
1152 | { |
||
1153 | global $modSettings, $context, $txt; |
||
1154 | |||
1155 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||
1156 | $errors = array(); |
||
1157 | |||
1158 | // Saving or changing attachment paths |
||
1159 | if (isset($this->_req->post->save)) |
||
1160 | { |
||
1161 | $this->_savePaths($attachmentsDir); |
||
1162 | } |
||
1163 | |||
1164 | // Saving a base directory? |
||
1165 | if (isset($this->_req->post->save2)) |
||
1166 | { |
||
1167 | $this->_saveBasePaths($attachmentsDir); |
||
1168 | } |
||
1169 | |||
1170 | // Have some errors to show? |
||
1171 | if (isset($_SESSION['errors'])) |
||
1172 | { |
||
1173 | if (is_array($_SESSION['errors'])) |
||
1174 | { |
||
1175 | if (!empty($_SESSION['errors']['dir'])) |
||
1176 | { |
||
1177 | foreach ($_SESSION['errors']['dir'] as $error) |
||
1178 | { |
||
1179 | $errors['dir'][] = Util::htmlspecialchars($error, ENT_QUOTES); |
||
1180 | } |
||
1181 | } |
||
1182 | |||
1183 | if (!empty($_SESSION['errors']['base'])) |
||
1184 | { |
||
1185 | foreach ($_SESSION['errors']['base'] as $error) |
||
1186 | { |
||
1187 | $errors['base'][] = Util::htmlspecialchars($error, ENT_QUOTES); |
||
1188 | } |
||
1189 | } |
||
1190 | } |
||
1191 | |||
1192 | unset($_SESSION['errors']); |
||
1193 | } |
||
1194 | |||
1195 | // Show the list of base and path directories + any errors generated |
||
1196 | $listOptions = array( |
||
1197 | 'id' => 'attach_paths', |
||
1198 | 'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths', '{sesstion_data}']), |
||
1199 | 'title' => $txt['attach_paths'], |
||
1200 | 'get_items' => array( |
||
1201 | 'function' => 'list_getAttachDirs', |
||
1202 | ), |
||
1203 | 'columns' => array( |
||
1204 | 'current_dir' => array( |
||
1205 | 'header' => array( |
||
1206 | 'value' => $txt['attach_current'], |
||
1207 | 'class' => 'centertext', |
||
1208 | ), |
||
1209 | 'data' => array( |
||
1210 | 'function' => static fn($rowData) => '<input type="radio" name="current_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . (empty($rowData['disable_current']) ? '' : ' disabled="disabled"') . ' class="input_radio" />', |
||
1211 | 'class' => 'grid8 centertext', |
||
1212 | ), |
||
1213 | ), |
||
1214 | 'path' => array( |
||
1215 | 'header' => array( |
||
1216 | 'value' => $txt['attach_path'], |
||
1217 | ), |
||
1218 | 'data' => array( |
||
1219 | 'function' => static fn($rowData) => ' |
||
1220 | <input type="hidden" name="dirs[' . $rowData['id'] . ']" value="' . $rowData['path'] . '" /> |
||
1221 | <input type="text" size="40" name="dirs[' . $rowData['id'] . ']" value="' . $rowData['path'] . '"' . (empty($rowData['disable_base_dir']) ? '' : ' disabled="disabled"') . ' class="input_text"/>', |
||
1222 | 'class' => 'grid50', |
||
1223 | ), |
||
1224 | ), |
||
1225 | 'current_size' => array( |
||
1226 | 'header' => array( |
||
1227 | 'value' => $txt['attach_current_size'], |
||
1228 | ), |
||
1229 | 'data' => array( |
||
1230 | 'db' => 'current_size', |
||
1231 | ), |
||
1232 | ), |
||
1233 | 'num_files' => array( |
||
1234 | 'header' => array( |
||
1235 | 'value' => $txt['attach_num_files'], |
||
1236 | ), |
||
1237 | 'data' => array( |
||
1238 | 'db' => 'num_files', |
||
1239 | ), |
||
1240 | ), |
||
1241 | 'status' => array( |
||
1242 | 'header' => array( |
||
1243 | 'value' => $txt['attach_dir_status'], |
||
1244 | ), |
||
1245 | 'data' => array( |
||
1246 | 'db' => 'status', |
||
1247 | 'class' => 'grid20', |
||
1248 | ), |
||
1249 | ), |
||
1250 | ), |
||
1251 | 'form' => array( |
||
1252 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths', '{sesstion_data}']), |
||
1253 | ), |
||
1254 | 'additional_rows' => array( |
||
1255 | array( |
||
1256 | 'class' => 'submitbutton', |
||
1257 | 'position' => 'below_table_data', |
||
1258 | 'value' => ' |
||
1259 | <input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /> |
||
1260 | <input type="submit" name="new_path" value="' . $txt['attach_add_path'] . '" /> |
||
1261 | <input type="submit" name="save" value="' . $txt['save'] . '" />', |
||
1262 | ), |
||
1263 | empty($errors['dir']) ? array( |
||
1264 | 'position' => 'top_of_list', |
||
1265 | 'value' => $txt['attach_dir_desc'], |
||
1266 | 'style' => 'padding: 5px 10px;', |
||
1267 | 'class' => 'description' |
||
1268 | ) : array( |
||
1269 | 'position' => 'top_of_list', |
||
1270 | 'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['dir']), |
||
1271 | 'style' => 'padding-left: 2.75em;', |
||
1272 | 'class' => 'warningbox', |
||
1273 | ), |
||
1274 | ), |
||
1275 | ); |
||
1276 | createList($listOptions); |
||
1277 | |||
1278 | if (!empty($modSettings['attachment_basedirectories'])) |
||
1279 | { |
||
1280 | $listOptions2 = array( |
||
1281 | 'id' => 'base_paths', |
||
1282 | 'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths', '{sesstion_data}']), |
||
1283 | 'title' => $txt['attach_base_paths'], |
||
1284 | 'get_items' => array( |
||
1285 | 'function' => 'list_getBaseDirs', |
||
1286 | ), |
||
1287 | 'columns' => array( |
||
1288 | 'current_dir' => array( |
||
1289 | 'header' => array( |
||
1290 | 'value' => $txt['attach_current'], |
||
1291 | 'class' => 'centertext', |
||
1292 | ), |
||
1293 | 'data' => array( |
||
1294 | 'function' => static fn($rowData) => '<input type="radio" name="current_base_dir" value="' . $rowData['id'] . '" ' . ($rowData['current'] ? ' checked="checked"' : '') . ' class="input_radio" />', |
||
1295 | 'class' => 'grid8 centertext', |
||
1296 | ), |
||
1297 | ), |
||
1298 | 'path' => array( |
||
1299 | 'header' => array( |
||
1300 | 'value' => $txt['attach_path'], |
||
1301 | ), |
||
1302 | 'data' => array( |
||
1303 | 'db' => 'path', |
||
1304 | 'class' => 'grid50', |
||
1305 | ), |
||
1306 | ), |
||
1307 | 'num_dirs' => array( |
||
1308 | 'header' => array( |
||
1309 | 'value' => $txt['attach_num_dirs'], |
||
1310 | ), |
||
1311 | 'data' => array( |
||
1312 | 'db' => 'num_dirs', |
||
1313 | ), |
||
1314 | ), |
||
1315 | 'status' => array( |
||
1316 | 'header' => array( |
||
1317 | 'value' => $txt['attach_dir_status'], |
||
1318 | ), |
||
1319 | 'data' => array( |
||
1320 | 'db' => 'status', |
||
1321 | 'class' => 'grid20', |
||
1322 | ), |
||
1323 | ), |
||
1324 | ), |
||
1325 | 'form' => array( |
||
1326 | 'href' => getUrl('admin', ['action' => 'admin', 'area' => 'manageattachments', 'sa' => 'attachpaths', '{sesstion_data}']), |
||
1327 | ), |
||
1328 | 'additional_rows' => array( |
||
1329 | array( |
||
1330 | 'class' => 'submitbutton', |
||
1331 | 'position' => 'below_table_data', |
||
1332 | 'value' => ' |
||
1333 | <input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /> |
||
1334 | <input type="submit" name="new_base_path" value="' . $txt['attach_add_path'] . '" /> |
||
1335 | <input type="submit" name="save2" value="' . $txt['save'] . '" />', |
||
1336 | ), |
||
1337 | empty($errors['base']) ? array( |
||
1338 | 'position' => 'top_of_list', |
||
1339 | 'value' => $txt['attach_dir_base_desc'], |
||
1340 | 'style' => 'padding: 5px 10px;', |
||
1341 | 'class' => 'description' |
||
1342 | ) : array( |
||
1343 | 'position' => 'top_of_list', |
||
1344 | 'value' => $txt['attach_dir_save_problem'] . '<br />' . implode('<br />', $errors['base']), |
||
1345 | 'style' => 'padding-left: 2.75em', |
||
1346 | 'class' => 'warningbox', |
||
1347 | ), |
||
1348 | ), |
||
1349 | ); |
||
1350 | createList($listOptions2); |
||
1351 | } |
||
1352 | |||
1353 | // Fix up our template. |
||
1354 | $context[$context['admin_menu_name']]['current_subsection'] = 'attachpaths'; |
||
1355 | $context['page_title'] = $txt['attach_path_manage']; |
||
1356 | } |
||
1357 | |||
1358 | /** |
||
1359 | * Saves any changes or additions to the attachment paths |
||
1360 | * |
||
1361 | * @param AttachmentsDirectory $attachmentsDir |
||
1362 | * @return void |
||
1363 | */ |
||
1364 | private function _savePaths($attachmentsDir) |
||
1365 | { |
||
1366 | global $txt, $context; |
||
1367 | |||
1368 | checkSession(); |
||
1369 | |||
1370 | $current_dir = $this->_req->getPost('current_dir', 'intval', 1); |
||
1371 | $dirs = $this->_req->getPost('dirs'); |
||
1372 | $new_dirs = []; |
||
1373 | |||
1374 | // Can't use these directories for attachments |
||
1375 | require_once(SUBSDIR . '/Themes.subs.php'); |
||
1376 | $themes = installedThemes(); |
||
1377 | $reserved_dirs = array(BOARDDIR, SOURCEDIR, SUBSDIR, CONTROLLERDIR, CACHEDIR, EXTDIR, LANGUAGEDIR, ADMINDIR); |
||
1378 | foreach ($themes as $theme) |
||
1379 | { |
||
1380 | $reserved_dirs[] = $theme['theme_dir']; |
||
1381 | } |
||
1382 | |||
1383 | foreach ($dirs as $id => $path) |
||
1384 | { |
||
1385 | $id = (int) $id; |
||
1386 | if ($id < 1) |
||
1387 | { |
||
1388 | continue; |
||
1389 | } |
||
1390 | |||
1391 | // If it doesn't look like a directory, probably is not a directory |
||
1392 | $real_path = rtrim(trim($path), DIRECTORY_SEPARATOR); |
||
1393 | if (preg_match('~[/\\\\]~', $real_path) !== 1) |
||
1394 | { |
||
1395 | $real_path = realpath(BOARDDIR . DIRECTORY_SEPARATOR . ltrim($real_path, DIRECTORY_SEPARATOR)); |
||
1396 | } |
||
1397 | |||
1398 | // Hmm, a new path maybe? |
||
1399 | if ($attachmentsDir->directoryExists($id) === false) |
||
1400 | { |
||
1401 | // or is it? |
||
1402 | if ($attachmentsDir->directoryExists($path)) |
||
1403 | { |
||
1404 | $errors[] = $path . ': ' . $txt['attach_dir_duplicate_msg']; |
||
1405 | continue; |
||
1406 | } |
||
1407 | |||
1408 | // or is it a system dir? |
||
1409 | if (in_array($real_path, $reserved_dirs)) |
||
1410 | { |
||
1411 | $errors[] = $real_path . ': ' . $txt['attach_dir_reserved']; |
||
1412 | continue; |
||
1413 | } |
||
1414 | |||
1415 | // OK, so let's try to create it then. |
||
1416 | try |
||
1417 | { |
||
1418 | $attachmentsDir->createDirectory($path); |
||
1419 | } |
||
1420 | catch (Exception $e) |
||
1421 | { |
||
1422 | $errors[] = $path . ': ' . $txt[$e->getMessage()]; |
||
1423 | continue; |
||
1424 | } |
||
1425 | } |
||
1426 | |||
1427 | // Changing a directory name or trying to remove it entirely? |
||
1428 | try |
||
1429 | { |
||
1430 | if (!empty($path)) |
||
1431 | { |
||
1432 | $attachmentsDir->rename($id, $real_path); |
||
1433 | } |
||
1434 | elseif ($attachmentsDir->delete($id, $real_path) === true) |
||
1435 | { |
||
1436 | // Don't add it back, its now gone! |
||
1437 | continue; |
||
1438 | } |
||
1439 | } |
||
1440 | catch (Exception $e) |
||
1441 | { |
||
1442 | $errors[] = $real_path . ': ' . $txt[$e->getMessage()]; |
||
1443 | } |
||
1444 | |||
1445 | $new_dirs[$id] = $real_path; |
||
1446 | } |
||
1447 | |||
1448 | // We need to make sure the current directory is right. |
||
1449 | if (empty($current_dir) && !empty($attachmentsDir->currentDirectoryId())) |
||
1450 | { |
||
1451 | $current_dir = $attachmentsDir->currentDirectoryId(); |
||
1452 | } |
||
1453 | |||
1454 | // Find the current directory if there's no value carried, |
||
1455 | if (empty($current_dir) || empty($new_dirs[$current_dir])) |
||
1456 | { |
||
1457 | $current_dir = $attachmentsDir->currentDirectoryId(); |
||
1458 | } |
||
1459 | |||
1460 | // If the user wishes to go back, update the last_dir array |
||
1461 | if ($current_dir !== $attachmentsDir->currentDirectoryId()) |
||
1462 | { |
||
1463 | $attachmentsDir->updateLastDirs($current_dir); |
||
1464 | } |
||
1465 | |||
1466 | // Going back to just one path? |
||
1467 | if (count($new_dirs) === 1) |
||
1468 | { |
||
1469 | // We might need to reset the paths. This loop will just loop through once. |
||
1470 | foreach ($new_dirs as $id => $dir) |
||
1471 | { |
||
1472 | if ($id !== 1) |
||
1473 | { |
||
1474 | updateAttachmentIdFolder($id, 1); |
||
1475 | } |
||
1476 | |||
1477 | $update = array('currentAttachmentUploadDir' => 1, 'attachmentUploadDir' => serialize(array(1 => $dir)),); |
||
1478 | } |
||
1479 | } |
||
1480 | else |
||
1481 | { |
||
1482 | // Save it to the database. |
||
1483 | $update = array('currentAttachmentUploadDir' => $current_dir, 'attachmentUploadDir' => serialize($new_dirs),); |
||
1484 | } |
||
1485 | |||
1486 | if (!empty($update)) |
||
1487 | { |
||
1488 | updateSettings($update); |
||
1489 | } |
||
1490 | |||
1491 | if (!empty($errors)) |
||
1492 | { |
||
1493 | $_SESSION['errors']['dir'] = $errors; |
||
1494 | } |
||
1495 | |||
1496 | redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']); |
||
1497 | } |
||
1498 | |||
1499 | /** |
||
1500 | * Saves changes to the attachment base directory section |
||
1501 | * |
||
1502 | * @param AttachmentsDirectory $attachmentsDir |
||
1503 | * @return void |
||
1504 | */ |
||
1505 | private function _saveBasePaths($attachmentsDir) |
||
1506 | { |
||
1507 | global $modSettings, $txt, $context; |
||
1508 | |||
1509 | checkSession(); |
||
1510 | |||
1511 | $attachmentBaseDirectories = $attachmentsDir->getBaseDirs(); |
||
1512 | $attachmentUploadDir = $attachmentsDir->getPaths(); |
||
1513 | $current_base_dir = $this->_req->getPost('current_base_dir', 'intval', 1); |
||
1514 | $new_base_dir = $this->_req->getPost('new_base_dir', 'trim|htmlspecialchars', ''); |
||
1515 | $base_dirs = $this->_req->getPost('base_dir'); |
||
1516 | |||
1517 | // Changing the current base directory? |
||
1518 | if (empty($new_base_dir) && !empty($current_base_dir) && $modSettings['basedirectory_for_attachments'] !== $attachmentUploadDir[$current_base_dir]) |
||
1519 | { |
||
1520 | $update = ['basedirectory_for_attachments' => $attachmentUploadDir[$current_base_dir]]; |
||
1521 | } |
||
1522 | |||
1523 | // Modifying / Removing a basedir entry |
||
1524 | if (isset($base_dirs)) |
||
1525 | { |
||
1526 | // Renaming the base dir, we can try |
||
1527 | foreach ($base_dirs as $id => $dir) |
||
1528 | { |
||
1529 | if (!empty($dir) && $dir !== $attachmentUploadDir[$id] && @rename($attachmentUploadDir[$id], $dir)) |
||
1530 | { |
||
1531 | $attachmentUploadDir[$id] = $dir; |
||
1532 | $attachmentBaseDirectories[$id] = $dir; |
||
1533 | $update = (array('attachmentUploadDir' => serialize($attachmentUploadDir), 'attachment_basedirectories' => serialize($attachmentBaseDirectories), 'basedirectory_for_attachments' => $attachmentUploadDir[$current_base_dir],)); |
||
1534 | } |
||
1535 | |||
1536 | // Or remove it (from selection only) |
||
1537 | if (empty($dir)) |
||
1538 | { |
||
1539 | // Can't remove the currently active one |
||
1540 | if ($id === $current_base_dir) |
||
1541 | { |
||
1542 | $errors[] = $attachmentUploadDir[$id] . ': ' . $txt['attach_dir_is_current']; |
||
1543 | continue; |
||
1544 | } |
||
1545 | |||
1546 | // Removed from selection (not disc) |
||
1547 | unset($attachmentBaseDirectories[$id]); |
||
1548 | $update = ['attachment_basedirectories' => empty($attachmentBaseDirectories) ? '' : serialize($attachmentBaseDirectories), 'basedirectory_for_attachments' => $attachmentUploadDir[$current_base_dir] ?? '',]; |
||
1549 | } |
||
1550 | } |
||
1551 | } |
||
1552 | |||
1553 | // Adding a new one? |
||
1554 | if (!empty($new_base_dir)) |
||
1555 | { |
||
1556 | $current_dir = $attachmentsDir->currentDirectoryId(); |
||
1557 | |||
1558 | // If it does not exist, try to create it. |
||
1559 | if (!in_array($new_base_dir, $attachmentUploadDir)) |
||
1560 | { |
||
1561 | try |
||
1562 | { |
||
1563 | $attachmentsDir->createDirectory($new_base_dir); |
||
1564 | } |
||
1565 | catch (Exception) |
||
1566 | { |
||
1567 | $errors[] = $new_base_dir . ': ' . $txt['attach_dir_base_no_create']; |
||
1568 | } |
||
1569 | } |
||
1570 | |||
1571 | // Find the new key |
||
1572 | $modSettings['currentAttachmentUploadDir'] = array_search($new_base_dir, $attachmentsDir->getPaths(), true); |
||
1573 | if (!in_array($new_base_dir, $attachmentBaseDirectories)) |
||
1574 | { |
||
1575 | $attachmentBaseDirectories[$modSettings['currentAttachmentUploadDir']] = $new_base_dir; |
||
1576 | } |
||
1577 | |||
1578 | ksort($attachmentBaseDirectories); |
||
1579 | $update = ['attachment_basedirectories' => serialize($attachmentBaseDirectories), 'basedirectory_for_attachments' => $new_base_dir, 'currentAttachmentUploadDir' => $current_dir,]; |
||
1580 | } |
||
1581 | |||
1582 | $_SESSION['errors']['base'] = $errors ?? null; |
||
1583 | |||
1584 | if (!empty($update)) |
||
1585 | { |
||
1586 | updateSettings($update); |
||
1587 | } |
||
1588 | |||
1589 | redirectexit('action=admin;area=manageattachments;sa=attachpaths;' . $context['session_var'] . '=' . $context['session_id']); |
||
1590 | } |
||
1591 | |||
1592 | /** |
||
1593 | * Maintenance function to move attachments from one directory to another |
||
1594 | */ |
||
1595 | public function action_transfer() |
||
1596 | { |
||
1597 | global $modSettings, $txt; |
||
1598 | |||
1599 | checkSession(); |
||
1600 | |||
1601 | $attachmentsDir = new AttachmentsDirectory($modSettings, database()); |
||
1602 | |||
1603 | // Clean the inputs |
||
1604 | $this->from = $this->_req->getPost('from', 'intval'); |
||
1605 | $this->auto = $this->_req->getPost('auto', 'intval', 0); |
||
1606 | $this->to = $this->_req->getPost('to', 'intval'); |
||
1607 | $start = empty($this->_req->post->empty_it) ? $modSettings['attachmentDirFileLimit'] : 0; |
||
1608 | $_SESSION['checked'] = !empty($this->_req->post->empty_it); |
||
1609 | |||
1610 | // Prepare for the moving |
||
1611 | $limit = 501; |
||
1612 | $results = array(); |
||
1613 | $dir_files = 0; |
||
1614 | $current_progress = 0; |
||
1615 | $total_moved = 0; |
||
1616 | $total_not_moved = 0; |
||
1617 | $total_progress = 0; |
||
1618 | |||
1619 | // Need to know where we are moving things from |
||
1620 | if (empty($this->from) || (empty($this->auto) && empty($this->to))) |
||
1621 | { |
||
1622 | $results[] = $txt['attachment_transfer_no_dir']; |
||
1623 | } |
||
1624 | |||
1625 | // Same location, that's easy |
||
1626 | if ($this->from === $this->to) |
||
1627 | { |
||
1628 | $results[] = $txt['attachment_transfer_same_dir']; |
||
1629 | } |
||
1630 | |||
1631 | // No errors so determine how many we may have to move |
||
1632 | if (empty($results)) |
||
1633 | { |
||
1634 | // Get the total file count for the progress bar. |
||
1635 | $total_progress = getFolderAttachmentCount($this->from); |
||
1636 | $total_progress -= $start; |
||
1637 | |||
1638 | if ($total_progress < 1) |
||
1639 | { |
||
1640 | $results[] = $txt['attachment_transfer_no_find']; |
||
1641 | } |
||
1642 | } |
||
1643 | |||
1644 | // Nothing to move (no files in source or below the max limit) |
||
1645 | if (empty($results)) |
||
1646 | { |
||
1647 | // Moving them automatically? |
||
1648 | if (!empty($this->auto)) |
||
1649 | { |
||
1650 | $modSettings['automanage_attachments'] = 1; |
||
1651 | $tmpattachmentUploadDir = Util::unserialize($modSettings['attachmentUploadDir']); |
||
1652 | |||
1653 | // Create sub directory's off the root or from an attachment directory? |
||
1654 | $modSettings['use_subdirectories_for_attachments'] = $this->auto == -1 ? 0 : 1; |
||
1655 | $modSettings['basedirectory_for_attachments'] = serialize($this->auto > 0 ? $tmpattachmentUploadDir[$this->auto] : [1 => $modSettings['basedirectory_for_attachments']]); |
||
1656 | |||
1657 | // Finally, where do they need to go |
||
1658 | $attachmentDirectory = new AttachmentsDirectory($modSettings, database()); |
||
1659 | $attachmentDirectory->automanage_attachments_check_directory(true); |
||
1660 | $new_dir = $attachmentDirectory->currentDirectoryId(); |
||
1661 | } |
||
1662 | // Or to a specified directory |
||
1663 | else |
||
1664 | { |
||
1665 | $new_dir = $this->to; |
||
1666 | } |
||
1667 | |||
1668 | $modSettings['currentAttachmentUploadDir'] = $new_dir; |
||
1669 | $break = false; |
||
1670 | while (!$break) |
||
1671 | { |
||
1672 | detectServer()->setTimeLimit(300); |
||
1673 | |||
1674 | // If limits are set, get the file count and size for the destination folder |
||
1675 | if ($dir_files <= 0 && (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))) |
||
1676 | { |
||
1677 | $current_dir = attachDirProperties($new_dir); |
||
1678 | $dir_files = $current_dir['files']; |
||
1679 | $dir_size = $current_dir['size']; |
||
1680 | } |
||
1681 | |||
1682 | // Find some attachments to move |
||
1683 | [$tomove_count, $tomove] = findAttachmentsToMove($this->from, $start, $limit); |
||
1684 | |||
1685 | // Nothing found to move |
||
1686 | if ($tomove_count === 0) |
||
1687 | { |
||
1688 | if ($current_progress === 0) |
||
1689 | { |
||
1690 | $results[] = $txt['attachment_transfer_no_find']; |
||
1691 | } |
||
1692 | |||
1693 | break; |
||
1694 | } |
||
1695 | |||
1696 | // No more to move after this batch then set the finished flag. |
||
1697 | if ($tomove_count < $limit) |
||
1698 | { |
||
1699 | $break = true; |
||
1700 | } |
||
1701 | |||
1702 | // Move them |
||
1703 | $moved = array(); |
||
1704 | $dir_size = empty($dir_size) ? 0 : $dir_size; |
||
1705 | $limiting_by_size_num = $attachmentsDir->hasSizeLimit() || $attachmentsDir->hasNumFilesLimit(); |
||
1706 | |||
1707 | foreach ($tomove as $row) |
||
1708 | { |
||
1709 | $source = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']); |
||
1710 | $dest = $attachmentsDir->getPathById($new_dir) . '/' . basename($source); |
||
1711 | |||
1712 | // Size and file count check |
||
1713 | if ($limiting_by_size_num) |
||
1714 | { |
||
1715 | $dir_files++; |
||
1716 | $dir_size += empty($row['size']) ? filesize($source) : $row['size']; |
||
1717 | |||
1718 | // If we've reached a directory limit. Do something if we are in auto mode, otherwise set an error. |
||
1719 | if ($attachmentsDir->remainingSpace($dir_size) === 0 || $attachmentsDir->remainingFiles($dir_files) === 0) |
||
1720 | { |
||
1721 | // Since we're in auto mode. Create a new folder and reset the counters. |
||
1722 | $create_result = $attachmentsDir->manageBySpace(); |
||
1723 | if ($create_result === true) |
||
1724 | { |
||
1725 | $results[] = sprintf($txt['attachments_transfered'], $total_moved, $attachmentsDir->getPathById($new_dir)); |
||
1726 | if ($total_not_moved !== 0) |
||
1727 | { |
||
1728 | $results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved); |
||
1729 | } |
||
1730 | |||
1731 | $dir_files = 0; |
||
1732 | $total_moved = 0; |
||
1733 | $total_not_moved = 0; |
||
1734 | |||
1735 | $break = false; |
||
1736 | break; |
||
1737 | } |
||
1738 | // Hmm, not in auto. Time to bail out then... |
||
1739 | else |
||
1740 | { |
||
1741 | $results[] = $txt['attachment_transfer_no_room']; |
||
1742 | $break = true; |
||
1743 | break; |
||
1744 | } |
||
1745 | } |
||
1746 | } |
||
1747 | |||
1748 | // Actually move the file |
||
1749 | if (@rename($source, $dest)) |
||
1750 | { |
||
1751 | $total_moved++; |
||
1752 | $current_progress++; |
||
1753 | $moved[] = $row['id_attach']; |
||
1754 | } |
||
1755 | else |
||
1756 | { |
||
1757 | $total_not_moved++; |
||
1758 | } |
||
1759 | } |
||
1760 | |||
1761 | // Update the database to reflect the new file location |
||
1762 | if (!empty($moved)) |
||
1763 | { |
||
1764 | moveAttachments($moved, $new_dir); |
||
1765 | } |
||
1766 | |||
1767 | $new_dir = $modSettings['currentAttachmentUploadDir']; |
||
1768 | |||
1769 | // Create / update the progress bar. |
||
1770 | // @todo why was this done this way? |
||
1771 | if (!$break) |
||
1772 | { |
||
1773 | $percent_done = min(round($current_progress / $total_progress * 100, 0), 100); |
||
1774 | $progressBar = ' |
||
1775 | <div class="progress_bar"> |
||
1776 | <div class="green_percent" style="width: ' . $percent_done . '%;">' . $percent_done . '%</div> |
||
1777 | </div>'; |
||
1778 | |||
1779 | // Write it to a file so it can be displayed |
||
1780 | $fp = fopen(BOARDDIR . '/progress.php', 'wb'); |
||
1781 | fwrite($fp, $progressBar); |
||
1782 | fclose($fp); |
||
1783 | usleep(500000); |
||
1784 | } |
||
1785 | } |
||
1786 | |||
1787 | $results[] = sprintf($txt['attachments_transfered'], $total_moved, $attachmentsDir->getPathById($new_dir)); |
||
1788 | if ($total_not_moved !== 0) |
||
1789 | { |
||
1790 | $results[] = sprintf($txt['attachments_not_transfered'], $total_not_moved); |
||
1791 | } |
||
1792 | } |
||
1793 | |||
1794 | // All done, time to clean up |
||
1795 | $_SESSION['results'] = $results; |
||
1796 | $this->file_functions->delete(BOARDDIR . '/progress.php'); |
||
1797 | |||
1798 | redirectexit('action=admin;area=manageattachments;sa=maintenance#transfer'); |
||
1799 | } |
||
1800 | } |
||
1801 |