1С-Битрикс: Очистка папки upload
Если сайт или интернет-магазин на 1С-Битрикс у вас стоит уже давно, то скорее-всего, что папка upload забита изображениями удалённых товаров. Конечно, это касается тех интернет-магазинов, в которых ассортимент периодически меняется.
Разработчики 1С-Битрикс не предусмотрели встроенную в CMS систему очистки таких файлов, поэтому приходится пользоваться либо сторонними модулями, либо сторонними скриптами.
Я приведу несколько вариантов решения данного вопроса.
- Решение из маркетплейса 1С-Битрикс – Мастер очистки сайта. Это решение имеет более широкий функционал, чем просто очистка неиспользуемых изображений, оно также позволяет очистить базу и от других записей. Подробности смотрите по ссылке выше. Недостатком данного решения является то, что оно платное.
- Скрипт для командной строки сервера – bitrix-clear-upload. Решение работает через CLI SAPI PHP, что позволяет автоматизировать процесс с помощью cron. Недостатком является то, что скрипт последний раз изменялся 4 года назад, нужно анализировать исходники и, возможно, оптимизировать под текущие реалии.
- Скрипт, который я нашёл на форуме разработчиков 1С-Битрикс. К сожалению, не смог найти страницу-источник, как найду, обязательно добавлю автора. Этот скрипт имеет веб-интерфейс и работает из браузера. Я его использовал осенью 22 года на нескольких сайтах, ошибок выполнения не было обнаружено. Код скрипта прилагаю ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
<?php $nbDDDrawErrAndMess=true;//отладсообщения $pwUrlCurr=parse_url($_SERVER["REQUEST_URI"],PHP_URL_HOST).parse_url($_SERVER["REQUEST_URI"],PHP_URL_PATH).__FILE__;// $pwUrlLoopBody="http://".$_SERVER["HTTP_HOST"].dirname(parse_url($_SERVER["REQUEST_URI"],PHP_URL_PATH))."/clearpictures.php"; header("Content-Type:text/html;charset=windows-1251"); session_start();//?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1251" /><meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" /> <title>Очиститель Битрикса 1.0</title><link rel="stylesheet" href="allInOne.css" type="text/css"/><link href="/gfx/mzrf.ico" rel="shortcut icon" /></head><body><? require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/header.php"); global $USER;if(!$USER->IsAdmin()){echo "авторизуйся с правами администратора...";return;} define("NO_KEEP_STATISTIC", true);define("NOT_CHECK_PERMISSIONS", true);//бесполезно ведь $patchBackup=$_SERVER['DOCUMENT_ROOT'] . "/upload/kastilio_backup/";if(!file_exists($patchBackup)){CheckDirPath($patchBackup);}//создать папку бэкапа $rootDirPath=$_SERVER['DOCUMENT_ROOT'] . "/upload/iblock";//Целевая папка для поиска файлов $arFilesCache = array();$time_start=microtime(true);//записи из базы b_file if(!$_REQUEST['nbOnlyScan'])$_REQUEST['nbOnlyScan'];////Удалять ли найденые файлы. по умолчанию не ставить if(!$_REQUEST['nbCopyToBackup'])$_REQUEST['nbCopyToBackup']="aeChecked";//Создаст бэкапы файлов if(!$_REQUEST['unNumbBegnFolder'])$_REQUEST['unNumbBegnFolder']=0; if(!$_REQUEST['unNumbEndsFolders'])$_REQUEST['unNumbEndsFolders']=5; if(!$_REQUEST['unNumbBeginFile'])$_REQUEST['unNumbBeginFile']=0; if(!$_REQUEST['unNumbEndsFiles'])$_REQUEST['unNumbEndsFiles']=999999; if(!$_REQUEST['unNumbDeletedFiles'])$_REQUEST['unNumbDeletedFiles']=0; if(!$_REQUEST['unTickBetweenPacks'])$_REQUEST['unTickBetweenPacks']=10000;?> <form id="htmlFormEdinstveniy" method="get" action=""><input type="hidden" id="unNumbDeletedFiles" name="unNumbDeletedFiles" value="<?=$_REQUEST['unNumbDeletedFiles'];?>"> <br><input type="checkbox" id="nbOnlyScan" <?if(isset($_REQUEST['nbOnlyScan'])&&($_REQUEST['nbOnlyScan']=="aeChecked")){echo"checked='checked' ";}?> value="aeChecked" style="top: 0px; box-shadow: none; background: none 0% 0% / auto repeat scroll padding-box border-box #373737;">Только сканировать, без изменений <?=$$rootDirPath;?> <br><input type="checkbox" id="nbCopyToBackup" <?if(isset($_REQUEST['nbCopyToBackup'])&&($_REQUEST['nbCopyToBackup']=="aeChecked")){echo"checked='checked' ";}?> value="aeChecked" style="top: 0px; box-shadow: none; background: none 0% 0% / auto repeat scroll padding-box border-box #373737;">Скопировать не нужные файлы в <?=$patchBackup;?> <br><input type="number" min="0" id="unNumbBegnFolder" name="unNumbBegnFolder" value="<?=$_REQUEST['unNumbBegnFolder'];?>"> сколько папок пропустить <br><input type="number" min="1" id="unNumbEndsFolders" name="unNumbEndsFolders" value="<?=$_REQUEST['unNumbEndsFolders'];?>"> сколько папок обработать <br><input type="number" min="0" id="unNumbBeginFile" name="unNumbBeginFile" value="<?=$_REQUEST['unNumbBeginFile'];?>"> сколько файлов пропустить. При большом количестве файлов в подкаталоге <br><input type="number" min="1" id="unNumbEndsFiles" name="unNumbEndsFiles" value="<?=$_REQUEST['unNumbEndsFiles'];?>"> сколько файлов обработать <br><button type="submit" id="nbButtCopyFromEtimRs24" name="nbButtCopyFromEtimRs24" disabled="disabled" value="nbButtCopyFromEtimRs24">Идёт выполнение</button> <input type="checkbox" id="htmlNextLoopInListSectionWhereTake" name="htmlNextLoopInListSectionWhereTake" <?if(isset($_REQUEST['htmlNextLoopInListSectionWhereTake'])&&($_REQUEST['htmlNextLoopInListSectionWhereTake']=="aeChecked")){echo"checked='checked' ";}?> value="aeChecked" style="top:0px;box-shadow: none; background: none 0% 0% / auto repeat scroll padding-box border-box #373737;">Перезаряжать на следующую пачку через <input type="number" min="0" id="unTickBetweenPacks" name="unTickBetweenPacks" value="<?=$_REQUEST['unTickBetweenPacks'];?>"> миллисекунд<? $hRootDir=opendir($rootDirPath);$unTotalCastFileProcessed=0;$removeFile=0;$contDir = 0;//Счётчик пройденых папок while(false !== ($subDirName = readdir($hRootDir))){$contDir++; if($subDirName=='.'||$subDirName=='..'){continue;}//пропускать фигню $filesCount = 0;//Счётчик пройденых файлов $hSubDir=opendir("$rootDirPath/$subDirName");//Путь до подкатегорий с файлами if($contDir<$_REQUEST['unNumbBegnFolder']){continue;}//пропускать перебраные в предвызове текфайла подпапки if($contDir>=($_REQUEST['unNumbBegnFolder']+$_REQUEST['unNumbEndsFolders'])){break;}//ограничение в переборе папок за такт сработало или цикл отработал сам $result = $DB->Query('SELECT FILE_NAME FROM b_file WHERE MODULE_ID = "iblock" AND SUBDIR="iblock/'.$subDirName.'"'); unset($arFilesCache);$arFilesCache=[];//предидущий список файлов while($row = $result->Fetch()){$arFilesCache[$row['FILE_NAME']]="";} while (false !== ($fileName = readdir($hSubDir))) { if($fileName == '.' || $fileName == '..') { continue; }//пропускать фигню $unTotalCastFileProcessed++;$filesCount++; if($unTotalCastFileProcessed>($_REQUEST['unNumbBeginFile']+$_REQUEST['unNumbEndsFiles'])){echo"<br>Пачка ".($_REQUEST['unNumbEndsFiles'])." файлов отработала. ";break;} if($unTotalCastFileProcessed<$_REQUEST['unNumbBeginFile']){$unTotalCastFileProcessed++;continue;} echo"<br>Перебран $rootDirPath/$subDirName/$fileName"; if(array_key_exists($fileName, $arFilesCache)){continue;}//Файл с диска есть в списке файлов базы - пропуск $result = $DB->Query('SELECT FILE_NAME FROM b_file WHERE MODULE_ID = "iblock" AND FILE_NAME="'.$fileName.'" LIMIT 1'); if($result->Fetch()){echo"Предупреждение 534 допнагрузска на SQL. Файл дополнительно проверяеться. ";$filesCount++;continue;}//Файл с диска есть в списке файлов базы - пропуск $removeFile++;$fullPath="$rootDirPath/$subDirName/$fileName";//полный путь до файла if($_REQUEST['nbCopyToBackup']=="aeChecked"){ if(!file_exists($patchBackup.$subDirName)){if(!CheckDirPath($patchBackup.$subDirName . '/')){echo"НЕ УДАЛОСЬ СОЗДАТЬ ПОДДЕРИКТОРИЮ ".$patchBackup.$subDirName;return;}}//создал поддиректорию CopyDirFiles($fullPath, $patchBackup . $subDirName . '/' . $fileName);}//копия в бэкап if($_REQUEST['nbOnlyScan']=="aeChecked"){echo 'Кандидат к удалению'. $fullPath . '<br>';} else{if(unlink($fullPath)){echo"Удалён ".$fullPath. '<br>';}else{echo"Ошибка удаления файла ".$fullPath;}}//Удаление файла unset($fileName);} if($unTotalCastFileProcessed>($_REQUEST['unNumbBeginFile']+$_REQUEST['unNumbEndsFiles'])){echo" с ".($_REQUEST['unNumbBeginFile'])." файла<br>";break;} closedir($hSubDir); if(($_REQUEST['nbOnlyScan']!="aeChecked")&&(!$filesCount)){rmdir("$rootDirPath/$subDirName");}}//Удалить поддиректорию, если удаление активно и счётчик файлов пустой - т.е каталог пуст closedir($hRootDir); $_REQUEST['unNumbDeletedFiles']=$_REQUEST['unNumbDeletedFiles']+$removeFile; if($removeFile<1){echo '<br>В текущей пачке нет файлов на удаление/перемещение<br>';} if($_REQUEST['nbCopyToBackup']=="aeChecked"){echo 'Всего файлов <strong>';echo($removeFile+$_REQUEST['unNumbDeletedFiles']).'</strong> перемещенно в: <strong>' . $patchBackup . '</strong>';} elseif($_REQUEST['nbOnlyScan']=="aeChecked"){echo 'Всего файлов к удалению увидено <strong>';echo($removeFile+$_REQUEST['unNumbDeletedFiles']).'</strong>';} else{echo 'Всего файлов удалено <strong>';echo($removeFile+$_REQUEST['unNumbDeletedFiles']).'</strong>';} echo '<br>Всего отработано <strong>'.$unTotalCastFileProcessed.'</strong> файлов в ' . $rootDirPath ; echo '<br>Всего отработано <strong>'.$contDir.'</strong> подкаталогов в ' . $rootDirPath; echo '<br>Количество файлов в текущей пачке: <strong>'. count($arFilesCache) . '</strong>'; $time = microtime(true) - $time_start; echo "<br>Время выполнения пачки $time секунд\n";?> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script language="javascript" type="text/javascript"> //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- setTimeout(start,<?=$_REQUEST['unTickBetweenPacks'];?>); function start(){ <?if($subDirName===false){?>//предцикл завершился сам а не из-за ограничителя document.getElementById('nbButtCopyFromEtimRs24').firstChild.data="Очистка завершена"; document.getElementById('htmlNextLoopInListSectionWhereTake').checked=false; return;<?}?> document.getElementById('unNumbBegnFolder').value='<?=$contDir;?>'; document.getElementById('unNumbDeletedFiles').value=Number(document.getElementById('unNumbDeletedFiles').value+<?=$_REQUEST['unNumbDeletedFiles'];?>); document.getElementById('nbButtCopyFromEtimRs24').firstChild.data="Пуск"; document.getElementById('nbButtCopyFromEtimRs24').disabled = false; if(document.getElementById('htmlNextLoopInListSectionWhereTake').checked){document.getElementById('htmlFormEdinstveniy').submit();}} //-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- </script></form></body></html> |
Напоминаю, что первое тестирование скрипта лучше проводить на тестовой площадке и только, после тщательной проверки, запускать его на продакшене. Какой-либо ответственности за действия скрипта я не несу, будьте внимательны!