Полный цикл в digital

Свой модуль "очистка папки"

Готовый код можно скачать в моем репозитории на GitFlic.

Cоздадим модуль, который будет удалять из папки /upload/iblock/ файлы отсутствующие в таблице b_file. Логика работы простая, если файл физически существует но его нет в базе, значит файл забыли удалить, например при редактирование страницы.

Модуль имеет ряд настроек, доступных из админки, например можно делать резервную копию удаляемых файлов, которые будут сохранены в папке /upload/iblock_Backup/. Информация об удаленных файлах записывается в таблицу hmarketing_delete, которая автоматически будет создана при установке модуля и текстовый файл который будет создан в директории /upload/iblock_Backup/ при запуске модуля из админки сайта.

Структура модуля

  • Папка hmarketing
  • Папка admin
    • Файл menu.php меню в админке Битрикс
  • Папка install
    • Папка db
      • Файл install.sql запросы, которые выполняются при установке модуля
      • Файл uninstall.sql запросы, которые выполняются при удалении модуля
    • Файл index.php основной файл установки
    • Файл version.php файл с версией и датой выхода модуля
    • Файл instalInfo.php вывод сообщения в админке сайта об ошибке или успехе при установке модуля
    • Файл deInstalInfo.php вывод сообщения в админке сайта об ошибке или успехе при удалении модуля
  • Папка lang
    • Папка ru
      • Папка lib
        • Файл data.php языковой файл базы данных
      • Папка install
        • Файл instalInfo.php языковой файл вывода сообщения в админке сайта об ошибке при установке модуля
        • Файл deInstalInfo.php языковой файл вывода сообщения в админке сайта об ошибке при удалении модуля
        • Файл index.php языковой файл основного файла установки
  • Папка lib
    • Файл Main.php основной класс модуля
    • Файл data.php настройки ORM
  • Файл include.php подключение файлов модуля
  • Файл options.php настройка модуля в админке

Файл /admin/menu.php

Файл вызывается ядром Битрикс автоматически при установке модуля.

Данный файл создаёт пункты меню в административном интерфейсе Битрикс:

В файле небходимо создать массив $aMenu, в котором указываются параметры меню, полный состав массива можно посмотреть на официальном сайте.

local/modules/hmarketing/admin/menu.php<?
defined('B_PROLOG_INCLUDED') and (B_PROLOG_INCLUDED === true) or die();
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
// основной массив $aMenu
$aMenu = array(
   // оснавная ветка меню
   array(
       // пункт меню в разделе Контент
       'parent_menu' => 'global_menu_services',
       // сортировка
       'sort' => 1,
       // название пункта меню
       'text' => "Модули Эйч Маркетинг",
       // идентификатор ветви
       "items_id" => "menu_webforms",
       // иконка
       "icon" => "form_menu_icon",
       // дочерния ветка меню
       'items' => array(
           array(
               // название подпункта меню
               'text' => 'Удаление файлов в /upload/iblock/',
               // ссылка для перехода
               'url' => 'settings.php?lang=ru&mid=hmarketing',
           ),
       )
   ),
);
// возвращаем основной массив $aMenu
return $aMenu;

Файл /install/db/install.sql

Файл вызывается при установки модуля из /install/index.php

В файле указан простой запрос на создание таблицы hmarketing_delete:

local/modules/hmarketing/install/db/install.sqlCREATE TABLE `hmarketing_delete` (
 `ID` INT NOT NULL AUTO_INCREMENT , 
 `DATE` DATETIME NOT NULL , 
 `TIP` VARCHAR(250) NOT NULL , 
 `PATH` VARCHAR(250) NOT NULL , 
 PRIMARY KEY (`ID`)
);

Файл /install/db/uninstall.sql

Файл вызывается при удаление модуля из /install/index.php

В файле указан простой запрос на удаление таблицы hmarketing_delete:

local/modules/hmarketing/install/db/uninstall.sqlDROP TABLE IF EXISTS `hmarketing_delete`;

Файл /install/index.php

Файл вызывается ядром Битрикс автоматически при установке модуля.

Инсталлятор должен находиться в директории /install/index.php. Имя класса должно соответствовать названию папке модуля и являться наследником от CModule. Только вместо точки, если мы делаем партнерский модуль стоит прописать нижние подчеркивание, это нужно для определения модуля в системе. Файл выполняется при установке или удалении модуля через административный интерфейс.

local/modules/hmarketing/install/index.php<?
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// пространство имен для управления (регистрации/удалении) модуля в системе/базе
use Bitrix\Main\ModuleManager;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
class Hmarketing extends CModule
{
   // переменные модуля
   public  $MODULE_ID;
   public  $MODULE_VERSION;
   public  $MODULE_VERSION_DATE;
   public  $MODULE_NAME;
   public  $MODULE_DESCRIPTION;
   public  $PARTNER_NAME;
   public  $PARTNER_URI;
   public  $errors;
   // конструктор класса, вызывается автоматически при обращение к классу
   function __construct()
   {
       // создаем пустой массив для файла version.php
       $arModuleVersion = array();
       // подключаем файл version.php
       include_once(__DIR__ . '/version.php');
       // версия модуля
       $this->MODULE_VERSION = $arModuleVersion['VERSION'];
       // дата релиза версии модуля
       $this->MODULE_VERSION_DATE = $arModuleVersion['VERSION_DATE'];
       // id модуля
       $this->MODULE_ID = "hmarketing";
       // название модуля
       $this->MODULE_NAME = "Удаление файлов в /upload/iblock/";
       // описание модуля
       $this->MODULE_DESCRIPTION = "Удаление ненужных файлов в директории /upload/iblock/";
       // имя партнера выпустившего модуль
       $this->PARTNER_NAME = "Эйч Маркетинг";
       // ссылка на рисурс партнера выпустившего модуль
       $this->PARTNER_URI = "https://hmarketing.ru";
   }
   // метод отрабатывает при установке модуля
   function DoInstall()
   {
       // глобальная переменная с обстрактным классом
       global $APPLICATION;
       // создаем таблицы баз данных, необходимые для работы модуля
       $this->InstallDB();
       // регистрируем обработчики событий
       $this->InstallEvents();
       // копируем файлы, необходимые для работы модуля
       $this->InstallFiles();
       // регистрируем модуль в системе
       ModuleManager::RegisterModule("hmarketing");
       // подключаем скрипт с административным прологом и эпилогом
       $APPLICATION->includeAdminFile(
           Loc::getMessage('INSTALL_TITLE'),
           __DIR__ . '/instalInfo.php'
       );
       // для успешного завершения, метод должен вернуть true
       return true;
   }
   // метод отрабатывает при удалении модуля
   function DoUninstall()
   {
       // глобальная переменная с обстрактным классом
       global $APPLICATION;
       // удаляем таблицы баз данных, необходимые для работы модуля
       $this->UnInstallDB();
       // удаляем обработчики событий
       $this->UnInstallEvents();
       // удаляем файлы, необходимые для работы модуля
       $this->UnInstallFiles();
       // удаляем регистрацию модуля в системе
       ModuleManager::UnRegisterModule("hmarketing");
       // подключаем скрипт с административным прологом и эпилогом
       $APPLICATION->includeAdminFile(
           Loc::getMessage('DEINSTALL_TITLE'),
           __DIR__ . '/deInstalInfo.php'
       );
       // для успешного завершения, метод должен вернуть true
       return true;
   }
   // метод для создания таблицы баз данных
   function InstallDB()
   {
       // глобальный объект $DB для работы с базой данных
       global $DB;
       // изначально устанавливаем переменной errors булево значение false
       $this->errors = false;
       // метод выполняет пакет запросов из файла install.sql и возвращает false в случае успеха или массив ошибок
       $this->errors = $DB->RunSQLBatch($_SERVER['DOCUMENT_ROOT'] . "/local/modules/hmarketing/install/db/install.sql");
       // проверяем ответ, если ответ вернул false, значит таблица успешно создана
       if (!$this->errors) {
           // для успешного завершения, метод должен вернуть true
           return true;
       } else
           return $this->errors;
   }
   // метод для удаления таблицы баз данных
   function UnInstallDB()
   {
       // глобальный объект $DB для работы с базой данных
       global $DB;
       // изначально устанавливаем переменной errors булево значение false
       $this->errors = false;
       // метод выполняет пакет запросов из файла uninstall.sql и возвращает false в случае успеха или массив ошибок
       $this->errors = $DB->RunSQLBatch($_SERVER['DOCUMENT_ROOT'] . "/local/modules/hmarketing/install/db/uninstall.sql");
       // проверяем ответ, если ответ вернул false, значит таблица успешно удалена
       if (!$this->errors) {
           // для успешного завершения, метод должен вернуть true
           return true;
       } else
           return $this->errors;
   }
   // метод для создания обработчика событий
   function InstallEvents()
   {
       // для успешного завершения, метод должен вернуть true
       return true;
   }
   // метод для удаления обработчика событий
   function UnInstallEvents()
   {
       // для успешного завершения, метод должен вернуть true
       return true;
   }
   // метод для копирования файлов модуля
   function InstallFiles()
   {
       // для успешного завершения, метод должен вернуть true
       return true;
   }
   // метод для копирования файлов модуля
   function UnInstallFiles()
   {
       // для успешного завершения, метод должен вернуть true
       return true;
   }
}

Файл /install/version.php

Файл вызывается при установки модуля из /install/index.php

Номер версии модуля должен храниться в файле /install/version.php в виде массива. Плюс к этому нужно в классе модуля записывать эти значения. В связи с этим нужно налепить небольшую городушку, которая будет брать значение из этого файла и записывать его в модуль:

local/modules/hmarketing/install/version.php<?
$arModuleVersion = array(
   'VERSION'      => '1.0.0',
   'VERSION_DATE' => '2023-01-01 10:00:00'
);

Файл /install/instalInfo.php

Файл вызывается при установки модуля из /install/index.php

Файл вывод сообщения в админке сайта об ошибке или успехе при установке модуля:

local/modules/hmarketing/install/instalInfo.php<?
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
// метод возвращает объект класса CApplicationException, содержащий последнее исключение
if ($errorException = $APPLICATION->getException()) {
   // вывод сообщения об ошибке при установке модуля
   CAdminMessage::showMessage(
       Loc::getMessage('INSTALL_FAILED') . ': ' . $errorException->GetString()
   );
} else {
   // вывод уведомления при успешной установке модуля
   CAdminMessage::showNote(
       Loc::getMessage('INSTALL_SUCCESS')
   );
}
?>
<!-- Кнопка возврата к списку модулей -->
<form action="<?= $APPLICATION->getCurPage(); ?>">
   <input type="submit" value="<?= Loc::getMessage('RETURN_MODULES'); ?>">
</form>

Файл /install/deInstalInfo.php

Файл вызывается при удалении модуля из /install/index.php

Файл вывод сообщения в админке сайта об ошибке или успехе при удалении модуля:

local/modules/hmarketing/install/deInstalInfo.php<?
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
// метод возвращает объект класса CApplicationException, содержащий последнее исключение
if ($errorException = $APPLICATION->getException()) {
   // вывод сообщения об ошибке при удалении модуля
   CAdminMessage::showMessage(
       Loc::getMessage('DEINSTALL_FAILED') . ': ' . $errorException->GetString()
   );
} else {
   // вывод уведомления при успешном удалении модуля
   CAdminMessage::showNote(
       Loc::getMessage('DEINSTALL_SUCCESS')
   );
}
?>
<!-- Кнопка возврата к списку модулей -->
<form action="<?= $APPLICATION->getCurPage(); ?>">
   <input type="submit" value="<?= Loc::getMessage('RETURN_MODULES'); ?>">
</form>

Файлы в дириктории /lang/ru/

Файл вызывается ядром Битрикс автоматически при установке модуля.

Файлы служат для реализации переводов, в них находится простой массив $MESS в котором хранятся пары ключ=>значение перевода, часть ru в пути к файлу является указанимем на язык перевода:

local/modules/hmarketing/lang/ru/lib/data.php<?
// class DeleteTable
$MESS["DELETE_ENTITY_ID_FIELD"] = "ID";
$MESS["DELETE_ENTITY_DATE_FIELD"] = "Дата";
$MESS["DELETE_ENTITY_TIP_FIELD"] = "Тип действий";
$MESS["DELETE_ENTITY_PATH_FIELD"] = "Путь";
// class OptionTable
$MESS["OPTION_ENTITY_MODULE_ID_FIELD"] = "ID модуля";
$MESS["OPTION_ENTITY_NAME_FIELD"] = "Имя";
$MESS["OPTION_ENTITY_VALUE_FIELD"] = "Значение";
$MESS["OPTION_ENTITY_DESCRIPTION_FIELD"] = "Описаний";
$MESS["OPTION_ENTITY_SITE_ID_FIELD"] = "ID сайта";
// class FileTable
$MESS["FILE_ENTITY_ID_FIELD"] = "ID";
$MESS["FILE_ENTITY_TIMESTAMP_X_FIELD"] = "Дата и время";
$MESS["FILE_ENTITY_MODULE_ID_FIELD"] = "ID модуля";
$MESS["FILE_ENTITY_HEIGHT_FIELD"] = "Высота файла";
$MESS["FILE_ENTITY_WIDTH_FIELD"] = "Ширина файла";
$MESS["FILE_ENTITY_FILE_SIZE_FIELD"] = "Размер файла";
$MESS["FILE_ENTITY_CONTENT_TYPE_FIELD"] = "Тип файла";
$MESS["FILE_ENTITY_SUBDIR_FIELD"] = "Папка файла";
$MESS["FILE_ENTITY_FILE_NAME_FIELD"] = "Имя файла которое дал Битрикс";
$MESS["FILE_ENTITY_ORIGINAL_NAME_FIELD"] = "Оригинальное имя файла";
$MESS["FILE_ENTITY_DESCRIPTION_FIELD"] = "Описание файла";
$MESS["FILE_ENTITY_HANDLER_ID_FIELD"] = "Обработчик файла";
$MESS["FILE_ENTITY_EXTERNAL_ID_FIELD"] = "Внешний ID";
local/modules/hmarketing/lang/ru/install/instalInfo.php<?
$MESS['INSTALL_SUCCESS'] = 'Модуль успешно установлен';
$MESS['INSTALL_FAILED'] = 'Ошибка при установке модуля';
$MESS['RETURN_MODULES'] = 'Вернуться в список модулей';
local/modules/hmarketing/lang/ru/install/deinstalInfo.php<?
$MESS['DEINSTALL_FAILED'] = 'Ошибка при удалении модуля';
$MESS['DEINSTALL_SUCCESS'] = 'Модуль успешно удален';
$MESS['RETURN_MODULES'] = 'Вернуться в список модулей';
local/modules/hmarketing/lang/ru/install/index.php<?
$MESS['INSTALL_TITLE'] = 'Установка модуля';
$MESS['DEINSTALL_TITLE'] = 'Удаление модуля';

Файл /lib/Main.php

Файл подключается из файла /include.php.

В данном основном файле будет храниться логика работы модуля, основной класс со всеми методами.

local/modules/hmarketing/lib/Main.php<?
// пространство имен для класса Main
namespace Hmarketing\Main;
// битриксовое пространство имен для установки даты в базе
use Bitrix\Main\Type\DateTime;
// пространство имен для ORM
use Hmarketing\Data;
class Main
{
 // удалять ли найденые файлы yes/no, если выбран yes можно выбрать создовать ли копию. Если варан no, бекап автоматически создастся но файлы из /upload/ не будут удалятся
 public $deleteFiles;
 // создавать ли бэкап файла yes/no
 public $saveBackup;
 // папка для бэкапа
 public $patchBackup;
 // целевая папка для поиска файлов
 public $rootDirPath;
 // файл для записи данных
 public $file;
 // массив для записи файлов из таблицы b_file
 public $arFilesCache = array();
 function __construct()
 {
     // папка для бэкапа
     $this->patchBackup = $_SERVER['DOCUMENT_ROOT'] . "/upload/iblock_Backup/";
     // целевая папка для поиска файлов
     $this->rootDirPath = $_SERVER['DOCUMENT_ROOT'] . "/upload/iblock";
     // создаем пустой файл
     file_put_contents($this->file = $this->patchBackup . date('H.i.s_d.m.Y') . '.txt', '');
     // вызываем метод получения данных модуля из базы
     $this->Option();
     // вызываем метод создания папки для бекапа
     $this->DirPatchBackup();
     // вызываем метод создания массива с файлами из базы
     $this->ArFiles();
     // вызываем метод создания пути
     $this->Path();
 }
 // получение из базы опций настройки модуля
 function Option()
 {
     // делаем запрос на выборку данных из таблицы настройки модуля
     $res = Data\OptionTable::getList(array(
         //'filter' => array('=MODULE_ID' => 'hmarketing'),
         'filter' => array(
             'LOGIC' => 'OR',
             array(
                 // 'LOGIC' => 'AND', // по умолчанию элементы склеиваются через AND
                 '=MODULE_ID' => 'hmarketing',
                 '=NAME' => 'savebackup'
             ),
             array(
                 // 'LOGIC' => 'AND', // по умолчанию элементы склеиваются через AND
                 '=MODULE_ID' => 'hmarketing',
                 '=NAME' => 'deletefiles'
             ),
         ),
         'select' => array('NAME', 'VALUE')
     ))->fetchAll();
     // если вернулся не пустой массив
     if (count($res) > 0) {
         // перебираем ответ от базы
         foreach ($res as $key => $value) {
             // deletefiles
             if ($value['NAME'] == 'deletefiles') {
                 switch ($value['VALUE']) {
                     case 'Y':
                         $this->deleteFiles = 'yes';
                         break;
                     default:
                         $this->deleteFiles = 'no';
                         break;
                 }
             }
             // savebackup
             if ($value['NAME'] == 'savebackup') {
                 switch ($value['VALUE']) {
                     case 'Y':
                         $this->saveBackup = 'yes';
                         break;
                     default:
                         $this->saveBackup = 'no';
                         break;
                 }
             }
         }
     }
 }
 // cоздание папки для бэкапа
 function DirPatchBackup()
 {
     if (!file_exists($this->patchBackup)) {
         CheckDirPath($this->patchBackup);
     }
 }
 // запись файлов в массив из базы
 function ArFiles()
 {
     // получаем записи из таблицы b_file
     $result = Data\FileTable::getList(array(
         'select' => array('FILE_NAME', 'SUBDIR'),
         'filter' => array('=MODULE_ID' => 'iblock'),
     ));
     // перебираем записи из таблицы b_file
     while ($row = $result->Fetch()) {
         $this->arFilesCache[$row['FILE_NAME']] = $row['SUBDIR'];
     }
 }
 // получение пути
 function Path()
 {
     // открываем целевую папку с файлами /upload/iblock
     $rootDir = opendir($this->rootDirPath);
     // запускаем цикл и получает элемент подкатигории по его дескриптору из папки /upload/iblock
     while (false !== ($subDirName = readdir($rootDir))) {
         // проверяем на точку и прирываем итерацию, в каталоге самая первая запись всегда точка, вторая две точки, после этого идут подпапки и файлы
         if ($subDirName == '.' || $subDirName == '..') {
             continue;
         }
         // путь до подкатегории /upload/iblock/..
         $subDirPath = "$this->rootDirPath/$subDirName";
         // открываем папку подкатигории /upload/iblock/..
         $subDir = opendir($subDirPath);
         // запускаем цикл и получает элемент подкатигории по его дескриптору из папки /upload/iblock/..
         while (false !== ($subFileName = readdir($subDir))) {
             // проверяем на точку и прирываем итерацию, в каталоге самая первая запись всегда точка, вторая две точки, после этого идут подпапки и файлы
             if ($subFileName == '.' || $subFileName == '..') {
                 continue;
             }
             // путь до подкатегории или файла /upload/iblock/../..
             $subFilePath = "$this->rootDirPath/$subDirName/$subFileName";
             // проверяем на директорию/файл и вызываем соответствующий метод
             if (is_dir($subFilePath)) {
                 $this->TwoDir($subFilePath, $subDirName, $subFileName, $subDirPath);
             } else {
                 $this->OneDir($subDirPath, $subFileName, $subDirName);
             }
         }
     }
     // закрываем целевую папку с файлами /upload/iblock
     closedir($rootDir);
 }
 // одна папка
 function OneDir($subDirPath, $fileName, $subDirName)
 {
     // пометка для файла
     $filesCount = 0;
     // если файл с диска есть в списке файлов базы, значит пропуск
     if (array_key_exists($fileName, $this->arFilesCache)) {
         // увеличиваем счетчик нужных файлов
         $filesCount++;
     }
     // полный путь до файла
     $fullPath = "$subDirPath/$fileName";
     // переменная сигнализирующая о наличии поддириктории
     $backTrue = false;
     // если задано удаление найденных файлов и найденный файл не нужный
     if ($this->deleteFiles === 'yes' && $filesCount == 0) {
         // если задано делать бекап, делаем
         if ($this->saveBackup === 'yes') {
             // проверяем наличие поддиректории в папке для бекапа /upload/iblock_Backup/
             if (!file_exists($this->patchBackup . $subDirName)) {
                 // если в папке для бекапов /upload/iblock_Backup/ нет поддириктории, создаем ее 
                 if (CheckDirPath($this->patchBackup . $subDirName)) {
                     // меняем переменную сигнализирующая о наличии поддириктории
                     $backTrue = true;
                 }
             } else {
                 // меняем переменную сигнализирующая о наличии поддириктории
                 $backTrue = true;
             }
             // если поддириктория есть
             if ($backTrue) {
                 // создаем копию в бэкап
                 CopyDirFiles($fullPath, $this->patchBackup . $subDirName . '/' . $fileName);
             }
         }
         // метод записи в информационный файл
         $this->Save('Файл удален: ', $fullPath);
         // удаление файла
         unlink($fullPath);
         // удаление поддириктории
         rmdir($subDirPath);
     }
     // если задано не удаление найденных файлов и найденный файл не нужный
     if ($this->deleteFiles === 'no' && $filesCount == 0) {
         // проверяем наличие поддиректории в папке для бекапа /upload/iblock_Backup/
         if (!file_exists($this->patchBackup . $subDirName)) {
             // если в папке для бекапов /upload/iblock_Backup/ нет поддириктории, создаем ее 
             if (CheckDirPath($this->patchBackup . $subDirName)) {
                 // меняем переменную сигнализирующая о наличии поддириктории
                 $backTrue = true;
             }
         } else {
             // меняем переменную сигнализирующая о наличии поддириктории
             $backTrue = true;
         }
         // если поддириктория есть
         if ($backTrue) {
             // создаем копию в бэкап
             CopyDirFiles($fullPath, $this->patchBackup . $subDirName . '/' . $fileName);
         }
         // метод записи в информационный файл
         $this->Save('Файл не удален и скопирован: ', $fullPath);
     }
 }
 // две папки
 function TwoDir($subSubDirPath, $subDirName, $subFileName, $subDirPath)
 {
     // пометка для файла
     $filesCount = 0;
     // открываем папку с файлом
     $hSubDir = opendir($subSubDirPath);
     // запускаем цикл и получает уже элемент по его дескриптору из подкатигории /upload/iblock/../..
     while (false !== ($fileName = readdir($hSubDir))) {
         // проверяем на точку и прирываем итерацию, в каталоге самая первая запись всегда точка, вторая две точки, после этого идут подпапки и файлы
         if ($fileName == '.' || $fileName == '..') {
             continue;
         }
         // если файл с диска есть в списке файлов базы, значит пропуск
         if (array_key_exists($fileName, $this->arFilesCache)) {
             // увеличиваем счетчик нужных файлов
             $filesCount++;
             continue;
         }
         // полный путь до файла
         $fullPath = "$subSubDirPath/$fileName";
         // переменная сигнализирующая о наличии поддириктории
         $backTrue = false;
         // если задано удаление найденных файлов
         if ($this->deleteFiles === 'yes') {
             // проверяем наличие поддиректории в папке для бекапа /upload/iblock_Backup/
             if (!file_exists($this->patchBackup . $subDirName . '/' . $subFileName . '/')) {
                 // если в папке для бекапов /upload/iblock_Backup/ нет поддириктории, создаем ее 
                 if (CheckDirPath($this->patchBackup . $subDirName . '/' . $subFileName . '/')) {
                     // меняем переменную сигнализирующая о наличии поддириктории
                     $backTrue = true;
                 }
             } else {
                 // меняем переменную сигнализирующая о наличии поддириктории
                 $backTrue = true;
             }
             // если поддириктория есть и нужно создавать бекап файла
             if ($backTrue && $this->saveBackup === 'yes') {
                 // создаем копию в бэкап
                 CopyDirFiles($fullPath, $this->patchBackup . $subDirName . '/' . $subFileName . '/' . $fileName);
             }
             // метод записи в информационный файл
             $this->Save('Файл удален: ', $fullPath);
             // удаление файла
             unlink($fullPath);
             // удаление поддиректории, если каталог пуст
             if ($this->deleteFiles === 'yes' && $filesCount == 0) {
                 rmdir($subSubDirPath);
                 rmdir($subDirPath);
             }
         }
         // если задано не удаление найденных файлов
         if ($this->deleteFiles === 'no') {
             // проверяем наличие поддиректории в папке для бекапа /upload/iblock_Backup/
             if (!file_exists($this->patchBackup . $subDirName . '/' . $subFileName . '/')) {
                 // если в папке для бекапов /upload/iblock_Backup/ нет поддириктории, создаем ее 
                 if (CheckDirPath($this->patchBackup . $subDirName . '/' . $subFileName . '/')) {
                     // меняем переменную сигнализирующая о наличии поддириктории
                     $backTrue = true;
                 }
             } else {
                 // меняем переменную сигнализирующая о наличии поддириктории
                 $backTrue = true;
             }
             // если поддириктория есть
             if ($backTrue) {
                 // создаем копию в бэкап
                 CopyDirFiles($fullPath, $this->patchBackup . $subDirName . '/' . $subFileName . '/' . $fileName);
             }
             // метод записи в информационный файл
             $this->Save('Файл не удален и скопирован: ', $fullPath);
         }
         // удаляем переменные
         unset($fileName, $backTrue);
     }
     // закрываем папку подкатигории
     closedir($hSubDir);
 }
 // сохранения информации о файлах
 function Save($name, $path)
 {
     // записываем в базу
     Data\DeleteTable::add(array(
         'DATE' => new DateTime(date('Y-m-d H:i:s'), "Y-m-d H:i:s"),
         'TIP' => $name,
         'PATH' => $path,
     ));
     // записываем в файл
     $fd = fopen($this->file, 'a');
     // записываем в информационный файл
     fputs($fd, $name . $path . "\n");
     // закрываем информационный файл
     fclose($fd);
 }
}

Файл /lib/data.php

Файл подключается из файла /include.php.

Данный файл служит для организации доступа к таблице с помощью ORM, в нём создаётся класс, который наследуется от класса Entity\DataManager, который в свою очередь реализует парадигму ORM.

<?
namespace Hmarketing\Data;
use Bitrix\Main\Localization\Loc,
 Bitrix\Main\ORM\Data\DataManager,
 Bitrix\Main\ORM\Fields\DateField,
 Bitrix\Main\ORM\Fields\DatetimeField,
 Bitrix\Main\ORM\Fields\IntegerField,
 Bitrix\Main\ORM\Fields\TextField,
 Bitrix\Main\ORM\Fields\StringField,
 Bitrix\Main\ORM\Fields\Validators\LengthValidator,
 Bitrix\Main\Type\DateTime;
Loc::loadMessages(__FILE__);
// таблица для записи удаляемых файлов
class DeleteTable extends DataManager
{
 /**
   * Returns DB table name for entity.
   *
   * @return string
   */
 public static function getTableName()
 {
   return 'hmarketing_delete';
 }
 /**
   * Returns entity map definition.
   *
   * @return array
   */
 public static function getMap()
 {
   return [
     new IntegerField(
       'ID',
       [
         'primary' => true,
         'autocomplete' => true,
         'title' => Loc::getMessage('DELETE_ENTITY_ID_FIELD')
       ]
     ),
     new DateField(
       'DATE',
       [
         'required' => true,
         'title' => Loc::getMessage('DELETE_ENTITY_DATE_FIELD')
       ]
     ),
     new StringField(
       'TIP',
       [
         'required' => true,
         'validation' => [__CLASS__, 'validateTip'],
         'title' => Loc::getMessage('DELETE_ENTITY_TIP_FIELD')
       ]
     ),
     new StringField(
       'PATH',
       [
         'required' => true,
         'validation' => [__CLASS__, 'validatePath'],
         'title' => Loc::getMessage('DELETE_ENTITY_PATH_FIELD')
       ]
     ),
   ];
 }
 /**
   * Returns validators for TIP field.
   *
   * @return array
   */
 public static function validateTip()
 {
   return [
     new LengthValidator(null, 250),
   ];
 }
 /**
   * Returns validators for PATH field.
   *
   * @return array
   */
 public static function validatePath()
 {
   return [
     new LengthValidator(null, 250),
   ];
 }
}
// таблица с опциями модуля установленных через админку
class OptionTable extends DataManager
{
 /**
   * Returns DB table name for entity.
   *
   * @return string
   */
 public static function getTableName()
 {
   return 'b_option';
 }
 /**
   * Returns entity map definition.
   *
   * @return array
   */
 public static function getMap()
 {
   return [
     new StringField(
       'MODULE_ID',
       [
         'primary' => true,
         'validation' => [__CLASS__, 'validateModuleId'],
         'title' => Loc::getMessage('OPTION_ENTITY_MODULE_ID_FIELD')
       ]
     ),
     new StringField(
       'NAME',
       [
         'primary' => true,
         'validation' => [__CLASS__, 'validateName'],
         'title' => Loc::getMessage('OPTION_ENTITY_NAME_FIELD')
       ]
     ),
     new TextField(
       'VALUE',
       [
         'title' => Loc::getMessage('OPTION_ENTITY_VALUE_FIELD')
       ]
     ),
     new StringField(
       'DESCRIPTION',
       [
         'validation' => [__CLASS__, 'validateDescription'],
         'title' => Loc::getMessage('OPTION_ENTITY_DESCRIPTION_FIELD')
       ]
     ),
     new StringField(
       'SITE_ID',
       [
         'validation' => [__CLASS__, 'validateSiteId'],
         'title' => Loc::getMessage('OPTION_ENTITY_SITE_ID_FIELD')
       ]
     ),
   ];
 }
 /**
   * Returns validators for MODULE_ID field.
   *
   * @return array
   */
 public static function validateModuleId()
 {
   return [
     new LengthValidator(null, 50),
   ];
 }
 /**
   * Returns validators for NAME field.
   *
   * @return array
   */
 public static function validateName()
 {
   return [
     new LengthValidator(null, 100),
   ];
 }
 /**
   * Returns validators for DESCRIPTION field.
   *
   * @return array
   */
 public static function validateDescription()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for SITE_ID field.
   *
   * @return array
   */
 public static function validateSiteId()
 {
   return [
     new LengthValidator(null, 2),
   ];
 }
}
// таблица в которую заносятся данные загружаемых файлов
class FileTable extends DataManager
{
 /**
   * Returns DB table name for entity.
   *
   * @return string
   */
 public static function getTableName()
 {
   return 'b_file';
 }
 /**
   * Returns entity map definition.
   *
   * @return array
   */
 public static function getMap()
 {
   return [
     new IntegerField(
       'ID',
       [
         'primary' => true,
         'autocomplete' => true,
         'title' => Loc::getMessage('FILE_ENTITY_ID_FIELD')
       ]
     ),
     new DatetimeField(
       'TIMESTAMP_X',
       [
         'default' => function () {
           return new DateTime();
         },
         'title' => Loc::getMessage('FILE_ENTITY_TIMESTAMP_X_FIELD')
       ]
     ),
     new StringField(
       'MODULE_ID',
       [
         'validation' => [__CLASS__, 'validateModuleId'],
         'title' => Loc::getMessage('FILE_ENTITY_MODULE_ID_FIELD')
       ]
     ),
     new IntegerField(
       'HEIGHT',
       [
         'title' => Loc::getMessage('FILE_ENTITY_HEIGHT_FIELD')
       ]
     ),
     new IntegerField(
       'WIDTH',
       [
         'title' => Loc::getMessage('FILE_ENTITY_WIDTH_FIELD')
       ]
     ),
     new IntegerField(
       'FILE_SIZE',
       [
         'title' => Loc::getMessage('FILE_ENTITY_FILE_SIZE_FIELD')
       ]
     ),
     new StringField(
       'CONTENT_TYPE',
       [
         'default' => 'IMAGE',
         'validation' => [__CLASS__, 'validateContentType'],
         'title' => Loc::getMessage('FILE_ENTITY_CONTENT_TYPE_FIELD')
       ]
     ),
     new StringField(
       'SUBDIR',
       [
         'validation' => [__CLASS__, 'validateSubdir'],
         'title' => Loc::getMessage('FILE_ENTITY_SUBDIR_FIELD')
       ]
     ),
     new StringField(
       'FILE_NAME',
       [
         'required' => true,
         'validation' => [__CLASS__, 'validateFileName'],
         'title' => Loc::getMessage('FILE_ENTITY_FILE_NAME_FIELD')
       ]
     ),
     new StringField(
       'ORIGINAL_NAME',
       [
         'validation' => [__CLASS__, 'validateOriginalName'],
         'title' => Loc::getMessage('FILE_ENTITY_ORIGINAL_NAME_FIELD')
       ]
     ),
     new StringField(
       'DESCRIPTION',
       [
         'validation' => [__CLASS__, 'validateDescription'],
         'title' => Loc::getMessage('FILE_ENTITY_DESCRIPTION_FIELD')
       ]
     ),
     new StringField(
       'HANDLER_ID',
       [
         'validation' => [__CLASS__, 'validateHandlerId'],
         'title' => Loc::getMessage('FILE_ENTITY_HANDLER_ID_FIELD')
       ]
     ),
     new StringField(
       'EXTERNAL_ID',
       [
         'validation' => [__CLASS__, 'validateExternalId'],
         'title' => Loc::getMessage('FILE_ENTITY_EXTERNAL_ID_FIELD')
       ]
     ),
   ];
 }
 /**
   * Returns validators for MODULE_ID field.
   *
   * @return array
   */
 public static function validateModuleId()
 {
   return [
     new LengthValidator(null, 50),
   ];
 }
 /**
   * Returns validators for CONTENT_TYPE field.
   *
   * @return array
   */
 public static function validateContentType()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for SUBDIR field.
   *
   * @return array
   */
 public static function validateSubdir()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for FILE_NAME field.
   *
   * @return array
   */
 public static function validateFileName()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for ORIGINAL_NAME field.
   *
   * @return array
   */
 public static function validateOriginalName()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for DESCRIPTION field.
   *
   * @return array
   */
 public static function validateDescription()
 {
   return [
     new LengthValidator(null, 255),
   ];
 }
 /**
   * Returns validators for HANDLER_ID field.
   *
   * @return array
   */
 public static function validateHandlerId()
 {
   return [
     new LengthValidator(null, 50),
   ];
 }
 /**
   * Returns validators for EXTERNAL_ID field.
   *
   * @return array
   */
 public static function validateExternalId()
 {
   return [
     new LengthValidator(null, 50),
   ];
 }
}

файл /include.php

Файл вызывается ядром Битрикс автоматически при установке модуля.

Файл необходим для подключения классов или дополнительных файлов модуля. Если соблюдать соответствие namespace и имени модуля, то поддерживается автозагрузка класса. Автозагрузка работает медленнее, чем поключение файла в include.php.

local/modules/hmarketing/include.php<?
Bitrix\Main\Loader::registerAutoloadClasses(
 // имя модуля
 "hmarketing",
 array(
   // ключ - имя класса с простанством имен, значение - путь относительно корня сайта к файлу
   "hmarketing\\Main\\Main" => "lib/Main.php",
   "hmarketing\\Data\\DeleteTable" => "lib/data.php",
   "hmarketing\\Data\\OptionTable" => "lib/data.php",
   "hmarketing\\Data\\FileTable" => "lib/data.php",
 )
);

файл /options.php

Файл вызывается ядром Битрикс автоматически при установке модуля.

Иногда полезно выносить какие-то настройки решения на отдельную страницу, отвлечённую от компонентов, к тому же компонентов может не быть вовсе, а всё равно надо дать возможность администратору сайта как-то влиять на работу модуля. В Битрикс довольно легко создавать страницы с параметрами для своих модулей.

В панели Битрикс сама страница с настройками находится по адресу Настройки -> Настройки модулей -> Имя_модуля:

local/modules/hmarketing/options.php<?
// пространство имен для подключений ланговых файлов
use Bitrix\Main\Localization\Loc;
// пространство имен для получения ID модуля
use Bitrix\Main\HttpApplication;
// пространство имен для загрузки необходимых файлов, классов, модулей
use Bitrix\Main\Loader;
// пространство имен для работы с параметрами модулей хранимых в базе данных
use Bitrix\Main\Config\Option;
// подключение ланговых файлов
Loc::loadMessages(__FILE__);
// получаем id модуля
$request = HttpApplication::getInstance()->getContext()->getRequest();
$module_id = htmlspecialcharsbx($request["mid"] != "" ? $request["mid"] : $request["id"]);
// подключение модуля
Loader::includeModule($module_id);
// настройки модуля для админки в том числе значения по умолчанию
$aTabs = array(
   array(
       // значение будет вставленно во все элементы вкладки для идентификации
       "DIV" => "hmarketing",
       // название вкладки в табах 
       "TAB" => "Удаление ненужных файлов из папки /upload/iblock",
       // массив с опциями секции
       "OPTIONS" => array(
           "Удалять найденые файлы?",
           array(
               // имя элемента формы
               "deletefiles",
               // поясняющий текст
               "Если флажок не активен, бекап автоматически создастся но файлы из /upload/iblock/ не будут удалены. Если флажок активен, можно выбрать ниже пораметр 'Сохранять в бекап найденые файлы'.",
               // значение checkbox по умолчанию "Нет"
               "N",
               // тип элемента формы "checkbox"
               array("checkbox"),
           ),
           "Сохранять в бекап найденые файлы?",
           array(
               // имя элемента формы
               "savebackup",
               // поясняющий текст
               "Если флажок активен, бекап создастся, файлы будут сохранены в /upload/iblock_Backup",
               // значение checkbox по умолчанию "Нет"
               "N",
               // тип элемента формы "checkbox"
               array("checkbox"),
           ),
       )
   )
);
// проверяем текущий POST запрос и сохраняем выбранные пользователем настройки
if ($request->isPost() && check_bitrix_sessid()) {
   // проверяем POST запрос, если инициатором выступила кнопка с name="apply" сохраняем введенные настройки в базу данных
   if ($request["apply"]) {
       // цикл по заполненым пользователем вкладкам
       foreach ($aTabs as $aTab) {
           foreach ($aTab["OPTIONS"] as $arOption) {
               // если это название секции, переходим к следующий итерации цикла
               if (!is_array($arOption)) {
                   continue;
               }
               // получаем в переменную $optionValue введенные пользователем данные
               $optionValue = $request->getPost($arOption[0]);
               // устанавливаем выбранные значения параметров и сохраняем в базу данных, перед сохранением проверяем если массив то соединяем данные, если не массив сохраняем как есть
               Option::set($module_id, $arOption[0], $optionValue);
           }
       }
   }
   // проверяем POST запрос, если инициатором выступила кнопка с name="dalete", выполняем действия с файлами 
   if ($request["dalete"]) {
       new Hmarketing\Main\Main();
   }
}
// отрисовываем форму, для этого создаем новый экземпляр класса CAdminTabControl, куда и передаём массив с настройками
$tabControl = new CAdminTabControl(
   "tabControl",
   $aTabs
);
// обозначаем начало отрисовки формы
$tabControl->Begin();
?>
<form action="<?= ($APPLICATION->GetCurPage()) . '?mid=' . ($module_id) . '&lang=' . (LANG) ?>" method="post">
   <? foreach ($aTabs as $aTab) {
       if ($aTab["OPTIONS"]) {
           // завершает предыдущую закладку, если она есть, начинает следующую
           $tabControl->BeginNextTab();
           // отрисовываем форму из массива
           __AdmSettingsDrawList($module_id, $aTab["OPTIONS"]);
       }
   }
   // выводим скрытый input с идентификатором сессии
   echo (bitrix_sessid_post());
   // выводит стандартные кнопки отправки формы
   $tabControl->Buttons();
   ?>
   <input class="adm-btn-save" type="submit" name="apply" value="Сохранить настройки" />
   <input type="submit" name="dalete" value="Очистить папку" />
</form>
<?
// обозначаем конец отрисовки формы
$tabControl->End();
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг