changes
This commit is contained in:
@@ -27,6 +27,8 @@ RUN apt-get update && apt-get install -y \
|
||||
# LibreOffice для конвертации DOC в DOCX
|
||||
libreoffice-common \
|
||||
libreoffice-writer \
|
||||
# Kerberos для IMAP
|
||||
libkrb5-dev \
|
||||
# Установка Node.js (актуальная LTS-версия 18)
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
@@ -34,9 +36,14 @@ RUN apt-get update && apt-get install -y \
|
||||
# Очистка кэша для уменьшения размера образа
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Установка install-php-extensions
|
||||
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
# Установка расширений PHP
|
||||
RUN docker-php-ext-configure gd --with-webp --with-jpeg \
|
||||
&& docker-php-ext-install -j$(nproc) gd pdo_mysql bcmath zip intl
|
||||
&& docker-php-ext-install -j$(nproc) gd pdo_mysql bcmath zip intl \
|
||||
&& install-php-extensions imap
|
||||
|
||||
# Установка Composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions;
|
||||
|
||||
use App\Containers\Dashboard\Data\EmailAttachmentData;
|
||||
use App\Containers\Dashboard\Exceptions\EmailFetchException;
|
||||
use App\Containers\Dashboard\Tasks\ConnectToImapTask;
|
||||
use App\Containers\Dashboard\Tasks\DownloadAttachmentsTask;
|
||||
use App\Containers\Dashboard\Tasks\FetchUnreadEmailsTask;
|
||||
use App\Containers\Dashboard\Tasks\FilterBySenderTask;
|
||||
use App\Containers\Dashboard\Tasks\MarkEmailAsReadTask;
|
||||
use App\Containers\Dashboard\Actions\ProcessMixedFilesAction;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webklex\PHPIMAP\Client;
|
||||
use Webklex\PHPIMAP\Folder;
|
||||
|
||||
/**
|
||||
* Оркестрация процесса получения новостей из Email
|
||||
*/
|
||||
class FetchEmailNewsAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConnectToImapTask $connectToImapTask,
|
||||
private readonly FetchUnreadEmailsTask $fetchUnreadEmailsTask,
|
||||
private readonly FilterBySenderTask $filterBySenderTask,
|
||||
private readonly DownloadAttachmentsTask $downloadAttachmentsTask,
|
||||
private readonly MarkEmailAsReadTask $markEmailAsReadTask,
|
||||
private readonly ProcessMixedFilesAction $processMixedFilesAction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Выполнить получение и обработку новостей из Email
|
||||
*
|
||||
* @return array Результат обработки
|
||||
* @throws EmailFetchException
|
||||
*/
|
||||
public function run(): array
|
||||
{
|
||||
// Проверяем, включена ли функция
|
||||
if (!config('email-news.enabled', true)) {
|
||||
Log::warning('[FetchEmailNewsAction] Функция отключена в конфиге');
|
||||
throw EmailFetchException::featureDisabled();
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction] Начало получения новостей из Email');
|
||||
|
||||
$result = [
|
||||
'processed_emails' => 0,
|
||||
'skipped_emails' => 0,
|
||||
'created_posts' => 0,
|
||||
'errors' => [],
|
||||
'posts' => [],
|
||||
];
|
||||
|
||||
try {
|
||||
// Подключаемся к IMAP
|
||||
$client = $this->connectToImapTask->run();
|
||||
|
||||
// Получаем папку
|
||||
$folder = $this->connectToImapTask->getFolder(
|
||||
$client,
|
||||
config('email-news.folder', 'INBOX')
|
||||
);
|
||||
|
||||
// Получаем непрочитанные письма
|
||||
$emails = $this->fetchUnreadEmailsTask->run($folder);
|
||||
|
||||
if (empty($emails)) {
|
||||
Log::info('[FetchEmailNewsAction] Нет непрочитанных писем');
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Фильтруем по отправителю
|
||||
$filteredEmails = $this->filterBySenderTask->run($emails);
|
||||
|
||||
$result['skipped_emails'] = count($emails) - count($filteredEmails);
|
||||
|
||||
// Обрабатываем каждое письмо
|
||||
foreach ($filteredEmails as $email) {
|
||||
$emailResult = $this->processEmail($email, $folder);
|
||||
|
||||
if ($emailResult['success']) {
|
||||
$result['created_posts']++;
|
||||
$result['posts'][] = $emailResult['post'];
|
||||
} else {
|
||||
$result['errors'][] = [
|
||||
'email_subject' => $email['subject'],
|
||||
'error' => $emailResult['error'],
|
||||
];
|
||||
}
|
||||
|
||||
$result['processed_emails']++;
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction] Завершено', [
|
||||
'processed' => $result['processed_emails'],
|
||||
'created_posts' => $result['created_posts'],
|
||||
'skipped' => $result['skipped_emails'],
|
||||
'errors_count' => count($result['errors']),
|
||||
]);
|
||||
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[FetchEmailNewsAction] Критическая ошибка', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать одно письмо
|
||||
*
|
||||
* @param array $email Данные письма
|
||||
* @param Folder $folder IMAP папка
|
||||
* @return array Результат обработки
|
||||
*/
|
||||
private function processEmail(array $email, Folder $folder): array
|
||||
{
|
||||
Log::info('[FetchEmailNewsAction:processEmail] Обработка письма', [
|
||||
'subject' => $email['subject'],
|
||||
'from' => $email['from_email'],
|
||||
]);
|
||||
|
||||
try {
|
||||
// Скачиваем вложения
|
||||
$attachments = $this->downloadAttachmentsTask->run($email['message']);
|
||||
|
||||
// Проверяем, есть ли DOC/DOCX файл
|
||||
$hasDocument = collect($attachments)->contains(fn($att) => $att->isDocument());
|
||||
|
||||
if (!$hasDocument) {
|
||||
Log::warning('[FetchEmailNewsAction:processEmail] Нет DOC/DOCX файла во вложениях', [
|
||||
'subject' => $email['subject'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Нет DOC/DOCX файла для извлечения текста',
|
||||
];
|
||||
}
|
||||
|
||||
// Конвертируем вложения в UploadedFile
|
||||
$uploadedFiles = $this->convertToUploadedFiles($attachments);
|
||||
|
||||
// Обрабатываем через существующий ProcessMixedFilesAction
|
||||
$postResult = $this->processMixedFilesAction->run($uploadedFiles);
|
||||
|
||||
// Помечаем письмо как прочитанное
|
||||
$this->markEmail($email['message'], $folder);
|
||||
|
||||
Log::info('[FetchEmailNewsAction:processEmail] Письмо успешно обработано', [
|
||||
'subject' => $email['subject'],
|
||||
'post_id' => $postResult['post']->id,
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'post' => $postResult['post'],
|
||||
'attachments_count' => count($attachments),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[FetchEmailNewsAction:processEmail] Ошибка обработки письма', [
|
||||
'subject' => $email['subject'],
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертировать EmailAttachmentData в UploadedFile
|
||||
*
|
||||
* @param array<EmailAttachmentData> $attachments
|
||||
* @return \Illuminate\Support\Collection<UploadedFile>
|
||||
*/
|
||||
private function convertToUploadedFiles(array $attachments): \Illuminate\Support\Collection
|
||||
{
|
||||
$uploadedFiles = [];
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$fullPath = storage_path('app/' . $attachment->path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
Log::warning('[FetchEmailNewsAction:convertToUploadedFiles] Файл не найден', [
|
||||
'path' => $attachment->path,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Создаём UploadedFile из сохранённого файла
|
||||
$uploadedFile = new UploadedFile(
|
||||
$fullPath,
|
||||
$attachment->filename,
|
||||
$attachment->mimeType,
|
||||
null,
|
||||
true // test = false (файл валиден)
|
||||
);
|
||||
|
||||
$uploadedFiles[] = $uploadedFile;
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction:convertToUploadedFiles] Конвертировано файлов', [
|
||||
'count' => count($uploadedFiles),
|
||||
]);
|
||||
|
||||
return collect($uploadedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить письмо как прочитанное (и возможно переместить)
|
||||
*
|
||||
* @param object $message IMAP сообщение
|
||||
* @param Folder $folder Текущая папка
|
||||
*/
|
||||
private function markEmail(object $message, Folder $folder): void
|
||||
{
|
||||
$moveToFolder = config('email-news.move_to_folder');
|
||||
|
||||
if ($moveToFolder) {
|
||||
$this->markEmailAsReadTask->markAndMove($message, $moveToFolder);
|
||||
} elseif (config('email-news.mark_as_read', true)) {
|
||||
$this->markEmailAsReadTask->run($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Containers\Dashboard\Actions;
|
||||
use App\Containers\Article\Models\Category;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Containers\Dashboard\Tasks\CallAiServiceTask;
|
||||
use App\Containers\Dashboard\Tasks\CompressImageTask;
|
||||
use App\Containers\Dashboard\Tasks\CreatePostFromAiDataTask;
|
||||
use App\Containers\Dashboard\Tasks\ExtractTextFromDocumentTask;
|
||||
use App\Containers\Dashboard\Tasks\FindMainNewsFileTask;
|
||||
@@ -19,6 +20,7 @@ class ProcessMixedFilesAction
|
||||
private readonly ExtractTextFromDocumentTask $extractTextFromDocumentTask,
|
||||
private readonly CallAiServiceTask $callAiServiceTask,
|
||||
private readonly CreatePostFromAiDataTask $createPostFromAiDataTask,
|
||||
private readonly CompressImageTask $compressImageTask,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -104,18 +106,72 @@ class ProcessMixedFilesAction
|
||||
*/
|
||||
private function saveFiles(Collection $files, UploadedFile $mainFile): array
|
||||
{
|
||||
Log::info('[ProcessMixedFilesAction:saveFiles] Начало сохранения файлов', [
|
||||
'total_files' => $files->count(),
|
||||
]);
|
||||
|
||||
// Сохраняем основной документ
|
||||
$documentPath = $mainFile->store('documents', 'local');
|
||||
|
||||
// Сохраняем остальные файлы как медиа
|
||||
$mediaPaths = $files
|
||||
->filter(fn($file) => $file !== $mainFile)
|
||||
->map(fn($file) => $file->store('media', 'public'))
|
||||
->toArray();
|
||||
// Сжимаем и сохраняем остальные файлы как медиа
|
||||
$mediaPaths = [];
|
||||
$compressionStats = ['total' => 0, 'compressed' => 0, 'saved_bytes' => 0];
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Пропускаем основной файл
|
||||
if ($file === $mainFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$compressionStats['total']++;
|
||||
|
||||
// Если это изображение - сжимаем
|
||||
if ($this->compressImageTask->isImage($file)) {
|
||||
Log::info('[ProcessMixedFilesAction:saveFiles] Обработка изображения', [
|
||||
'file' => $file->getClientOriginalName(),
|
||||
]);
|
||||
|
||||
$result = $this->compressImageTask->run($file);
|
||||
|
||||
if ($result['compressed']) {
|
||||
$compressionStats['compressed']++;
|
||||
$compressionStats['saved_bytes'] += $result['original_size'] - $result['size'];
|
||||
|
||||
Log::info('[ProcessMixedFilesAction:saveFiles] Изображение сжато', [
|
||||
'file' => $file->getClientOriginalName(),
|
||||
'original_size' => $this->formatFileSize($result['original_size']),
|
||||
'compressed_size' => $this->formatFileSize($result['size']),
|
||||
'ratio' => $result['compression_ratio'] . '%',
|
||||
]);
|
||||
}
|
||||
|
||||
// Сохраняем сжатый файл
|
||||
$path = $result['file']->store('media', 'public');
|
||||
|
||||
// Очищаем временный файл
|
||||
if (file_exists($result['file']->getRealPath())) {
|
||||
unlink($result['file']->getRealPath());
|
||||
}
|
||||
|
||||
$mediaPaths[] = $path;
|
||||
} else {
|
||||
// Не изображения сохраняем как есть
|
||||
$path = $file->store('media', 'public');
|
||||
$mediaPaths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('[ProcessMixedFilesAction:saveFiles] Статистика сжатия', [
|
||||
'total_images' => $compressionStats['total'],
|
||||
'compressed' => $compressionStats['compressed'],
|
||||
'saved' => $this->formatFileSize($compressionStats['saved_bytes']),
|
||||
'saved_bytes' => $compressionStats['saved_bytes'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'documentPath' => $documentPath,
|
||||
'mediaPaths' => $mediaPaths,
|
||||
'compressionStats' => $compressionStats,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ namespace App\Containers\Dashboard\Actions;
|
||||
use App\Containers\Article\Models\Category;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Containers\Dashboard\Tasks\CallAiServiceTask;
|
||||
use App\Containers\Dashboard\Tasks\CompressImageTask;
|
||||
use App\Containers\Dashboard\Tasks\CreatePostFromAiDataTask;
|
||||
use App\Containers\Dashboard\Tasks\ExtractTextFromDocumentTask;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProcessUploadedFilesAction
|
||||
{
|
||||
@@ -15,6 +17,7 @@ class ProcessUploadedFilesAction
|
||||
private readonly ExtractTextFromDocumentTask $extractTextFromDocumentTask,
|
||||
private readonly CallAiServiceTask $callAiServiceTask,
|
||||
private readonly CreatePostFromAiDataTask $createPostFromAiDataTask,
|
||||
private readonly CompressImageTask $compressImageTask,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -64,13 +67,48 @@ class ProcessUploadedFilesAction
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет медиафайлы
|
||||
* Сохраняет медиафайлы со сжатием
|
||||
*/
|
||||
private function saveMediaFiles(array $mediaFiles): array
|
||||
{
|
||||
return collect($mediaFiles)
|
||||
->map(fn($file) => $file->store('media', 'public'))
|
||||
->toArray();
|
||||
$paths = [];
|
||||
$compressionStats = ['total' => 0, 'compressed' => 0, 'saved_bytes' => 0];
|
||||
|
||||
foreach ($mediaFiles as $file) {
|
||||
$compressionStats['total']++;
|
||||
|
||||
// Если это изображение - сжимаем
|
||||
if ($this->compressImageTask->isImage($file)) {
|
||||
$result = $this->compressImageTask->run($file);
|
||||
|
||||
if ($result['compressed']) {
|
||||
$compressionStats['compressed']++;
|
||||
$compressionStats['saved_bytes'] += $result['original_size'] - $result['size'];
|
||||
}
|
||||
|
||||
// Сохраняем сжатый файл
|
||||
$path = $result['file']->store('media', 'public');
|
||||
|
||||
// Очищаем временный файл
|
||||
if (file_exists($result['file']->getRealPath())) {
|
||||
unlink($result['file']->getRealPath());
|
||||
}
|
||||
|
||||
$paths[] = $path;
|
||||
} else {
|
||||
// Не изображения сохраняем как есть
|
||||
$path = $file->store('media', 'public');
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('[ProcessUploadedFilesAction] Статистика сжатия', [
|
||||
'total_images' => $compressionStats['total'],
|
||||
'compressed' => $compressionStats['compressed'],
|
||||
'saved' => $this->formatFileSize($compressionStats['saved_bytes']),
|
||||
]);
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Commands;
|
||||
|
||||
use App\Containers\Dashboard\Actions\FetchEmailNewsAction;
|
||||
use App\Containers\Dashboard\Exceptions\EmailFetchException;
|
||||
use App\Ship\Abstracts\Commands\ConsoleCommand;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Artisan команда для получения новостей из Email
|
||||
*/
|
||||
class FetchEmailNewsCommand extends ConsoleCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'email:fetch-news
|
||||
{--force : Принудительный запуск, даже если отключено в конфиге}
|
||||
{--log : Выводить подробный лог в консоль}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Получение новостей из Email (IMAP) и создание черновиков';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(FetchEmailNewsAction $fetchEmailNewsAction): int
|
||||
{
|
||||
$this->info('📧 Запуск получения новостей из Email...');
|
||||
|
||||
$force = $this->option('force');
|
||||
$verbose = $this->option('log');
|
||||
|
||||
// Проверяем, включена ли функция
|
||||
if (!config('email-news.enabled', true) && !$force) {
|
||||
$this->error('❌ Функция отключена в конфиге (EMAIL_NEWS_ENABLED=false)');
|
||||
$this->warn('Используйте --force для принудительного запуска');
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $fetchEmailNewsAction->run();
|
||||
|
||||
// Вывод результатов
|
||||
$this->newLine();
|
||||
$this->line('📊 Результаты обработки:');
|
||||
$this->table(
|
||||
['Метрика', 'Значение'],
|
||||
[
|
||||
['Обработано писем', $result['processed_emails']],
|
||||
['Пропущено (не редактор)', $result['skipped_emails']],
|
||||
['Создано новостей', $result['created_posts']],
|
||||
['Ошибок', count($result['errors'])],
|
||||
]
|
||||
);
|
||||
|
||||
// Вывод созданных постов
|
||||
if (!empty($result['posts'])) {
|
||||
$this->newLine();
|
||||
$this->info('✅ Созданные новости:');
|
||||
foreach ($result['posts'] as $post) {
|
||||
$status = $post->status instanceof \BackedEnum ? $post->status->value : $post->status;
|
||||
$this->line(" • {$post->title} (ID: {$post->id}, статус: {$status})");
|
||||
}
|
||||
}
|
||||
|
||||
// Вывод ошибок
|
||||
if (!empty($result['errors'])) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Ошибки:');
|
||||
foreach ($result['errors'] as $error) {
|
||||
$this->warn(" • {$error['email_subject']}: {$error['error']}");
|
||||
}
|
||||
}
|
||||
|
||||
// Подробный лог
|
||||
if ($verbose) {
|
||||
$this->newLine();
|
||||
$this->info('📝 Детальный лог доступен в storage/logs/laravel.log');
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info('✅ Завершено!');
|
||||
|
||||
return 0;
|
||||
} catch (EmailFetchException $e) {
|
||||
$this->error('❌ Ошибка Email: ' . $e->getMessage());
|
||||
Log::error('[FetchEmailNewsCommand] EmailFetchException', [
|
||||
'code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Критическая ошибка: ' . $e->getMessage());
|
||||
$this->warn('Проверьте логи: storage/logs/laravel.log');
|
||||
|
||||
Log::error('[FetchEmailNewsCommand] Critical Error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Email News Fetching Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Настройки для автоматического получения новостей из Email
|
||||
|
|
||||
*/
|
||||
|
||||
// Включить ли функционал
|
||||
'enabled' => env('EMAIL_NEWS_ENABLED', true),
|
||||
|
||||
// Имя аккаунта из config/imap.php
|
||||
'imap_account' => 'email_news',
|
||||
|
||||
// Email редактора (единственный разрешённый отправитель)
|
||||
'editor_email' => env('EMAIL_NEWS_SENDER_EMAIL'),
|
||||
|
||||
// Whitelist email-адресов (если редакторов несколько)
|
||||
'allowed_senders' => [
|
||||
env('EMAIL_NEWS_SENDER_EMAIL'),
|
||||
// Можно добавить дополнительные email
|
||||
// env('EMAIL_NEWS_SENDER_EMAIL_2'),
|
||||
],
|
||||
|
||||
// IMAP папка для проверки
|
||||
'folder' => env('EMAIL_NEWS_FOLDER', 'INBOX'),
|
||||
|
||||
// Что делать с письмами после обработки
|
||||
'mark_as_read' => env('EMAIL_NEWS_MARK_AS_READ', true),
|
||||
|
||||
// Перемещать ли обработанные письма в другую папку (или null)
|
||||
'move_to_folder' => env('EMAIL_NEWS_MOVE_TO', null),
|
||||
|
||||
// Папка для сохранения вложений
|
||||
'attachments_folder' => 'email_attachments',
|
||||
|
||||
// Максимальный размер вложения (в байтах), 0 = без ограничений
|
||||
'max_attachment_size' => env('EMAIL_NEWS_MAX_ATTACHMENT_SIZE', 41943040), // 40MB
|
||||
|
||||
// Логировать ли отклонённые письма (не от редактора)
|
||||
'log_skipped_emails' => env('EMAIL_NEWS_LOG_SKIPPED', true),
|
||||
];
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Data;
|
||||
|
||||
/**
|
||||
* DTO для представления вложения Email
|
||||
*/
|
||||
class EmailAttachmentData
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $filename,
|
||||
public readonly string $mimeType,
|
||||
public readonly int $size,
|
||||
public readonly string $path,
|
||||
public readonly ?string $contentId = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Создать DTO из файла
|
||||
*/
|
||||
public static function fromFile(string $path, string $originalFilename, string $mimeType, int $size, ?string $contentId = null): self
|
||||
{
|
||||
return new self(
|
||||
filename: $originalFilename,
|
||||
mimeType: $mimeType,
|
||||
size: $size,
|
||||
path: $path,
|
||||
contentId: $contentId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли вложение изображением
|
||||
*/
|
||||
public function isImage(): bool
|
||||
{
|
||||
return str_starts_with($this->mimeType, 'image/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли вложение документом (DOC/DOCX)
|
||||
*/
|
||||
public function isDocument(): bool
|
||||
{
|
||||
$documentMimes = [
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
];
|
||||
|
||||
$documentExtensions = ['doc', 'docx'];
|
||||
|
||||
return in_array($this->mimeType, $documentMimes, true)
|
||||
|| in_array(strtolower(pathinfo($this->filename, PATHINFO_EXTENSION)), $documentExtensions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить расширение файла
|
||||
*/
|
||||
public function getExtension(): string
|
||||
{
|
||||
return strtolower(pathinfo($this->filename, PATHINFO_EXTENSION));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Исключение для ошибок при получении Email
|
||||
*/
|
||||
class EmailFetchException extends Exception
|
||||
{
|
||||
/**
|
||||
* Ошибка подключения к IMAP
|
||||
*/
|
||||
public static function connectionFailed(string $message = ''): self
|
||||
{
|
||||
return new self(
|
||||
message: 'Не удалось подключиться к IMAP-серверу. ' . ($message ?: ''),
|
||||
code: 1001,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Папка не найдена
|
||||
*/
|
||||
public static function folderNotFound(string $folder): self
|
||||
{
|
||||
return new self(
|
||||
message: "IMAP-папка не найдена: {$folder}",
|
||||
code: 1002,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Нет писем для обработки
|
||||
*/
|
||||
public static function noEmailsFound(): self
|
||||
{
|
||||
return new self(
|
||||
message: 'Не найдено писем для обработки',
|
||||
code: 1003,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ошибка при загрузке вложения
|
||||
*/
|
||||
public static function attachmentDownloadFailed(string $filename, string $message = ''): self
|
||||
{
|
||||
return new self(
|
||||
message: "Не удалось загрузить вложение: {$filename}. " . ($message ?: ''),
|
||||
code: 1004,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ошибка: письмо не от редактора
|
||||
*/
|
||||
public static function senderNotAllowed(string $senderEmail): self
|
||||
{
|
||||
return new self(
|
||||
message: "Письмо получено от неразрешённого отправителя: {$senderEmail}",
|
||||
code: 1005,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ошибка: нет вложений
|
||||
*/
|
||||
public static function noAttachmentsFound(): self
|
||||
{
|
||||
return new self(
|
||||
message: 'В письме не найдено вложений',
|
||||
code: 1006,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ошибка: функционал отключён
|
||||
*/
|
||||
public static function featureDisabled(): self
|
||||
{
|
||||
return new self(
|
||||
message: 'Функция получения новостей из Email отключена в конфиге',
|
||||
code: 1007,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Модель лога получения Email
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $message_id
|
||||
* @property string $from_email
|
||||
* @property string|null $from_name
|
||||
* @property string $subject
|
||||
* @property \Carbon\Carbon $email_date
|
||||
* @property string $status (success, failed, skipped)
|
||||
* @property int|null $post_id
|
||||
* @property string|null $error_message
|
||||
* @property int $attachments_count
|
||||
* @property int $total_size
|
||||
* @property string|null $processed_from
|
||||
* @property array|null $metadata
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class EmailFetchLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = false;
|
||||
|
||||
protected $casts = [
|
||||
'email_date' => 'datetime',
|
||||
'metadata' => 'array',
|
||||
'total_size' => 'integer',
|
||||
'attachments_count' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Связь с постом
|
||||
*/
|
||||
public function post(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Containers\Article\Models\Post::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: успешные
|
||||
*/
|
||||
public function scopeSuccessful($query)
|
||||
{
|
||||
return $query->where('status', 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: неудачные
|
||||
*/
|
||||
public function scopeFailed($query)
|
||||
{
|
||||
return $query->where('status', 'failed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: пропущенные
|
||||
*/
|
||||
public function scopeSkipped($query)
|
||||
{
|
||||
return $query->where('status', 'skipped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: от конкретного отправителя
|
||||
*/
|
||||
public function scopeFromSender($query, string $email)
|
||||
{
|
||||
return $query->where('from_email', $email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать лог успешной обработки
|
||||
*/
|
||||
public static function logSuccess(
|
||||
string $messageId,
|
||||
string $fromEmail,
|
||||
string $subject,
|
||||
\DateTime $emailDate,
|
||||
int $postId,
|
||||
int $attachmentsCount,
|
||||
int $totalSize,
|
||||
array $metadata = []
|
||||
): self {
|
||||
return static::create([
|
||||
'message_id' => $messageId,
|
||||
'from_email' => $fromEmail,
|
||||
'from_name' => null,
|
||||
'subject' => $subject,
|
||||
'email_date' => $emailDate,
|
||||
'status' => 'success',
|
||||
'post_id' => $postId,
|
||||
'error_message' => null,
|
||||
'attachments_count' => $attachmentsCount,
|
||||
'total_size' => $totalSize,
|
||||
'processed_from' => request()->ip() ?? null,
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать лог ошибки
|
||||
*/
|
||||
public static function logFailed(
|
||||
string $messageId,
|
||||
string $fromEmail,
|
||||
string $subject,
|
||||
\DateTime $emailDate,
|
||||
string $errorMessage,
|
||||
int $attachmentsCount = 0,
|
||||
array $metadata = []
|
||||
): self {
|
||||
return static::create([
|
||||
'message_id' => $messageId,
|
||||
'from_email' => $fromEmail,
|
||||
'from_name' => null,
|
||||
'subject' => $subject,
|
||||
'email_date' => $emailDate,
|
||||
'status' => 'failed',
|
||||
'post_id' => null,
|
||||
'error_message' => $errorMessage,
|
||||
'attachments_count' => $attachmentsCount,
|
||||
'total_size' => 0,
|
||||
'processed_from' => request()->ip() ?? null,
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать лог пропущенного письма
|
||||
*/
|
||||
public static function logSkipped(
|
||||
string $messageId,
|
||||
string $fromEmail,
|
||||
string $subject,
|
||||
\DateTime $emailDate,
|
||||
string $reason = 'Not from allowed sender',
|
||||
array $metadata = []
|
||||
): self {
|
||||
return static::create([
|
||||
'message_id' => $messageId,
|
||||
'from_email' => $fromEmail,
|
||||
'from_name' => null,
|
||||
'subject' => $subject,
|
||||
'email_date' => $emailDate,
|
||||
'status' => 'skipped',
|
||||
'post_id' => null,
|
||||
'error_message' => $reason,
|
||||
'attachments_count' => 0,
|
||||
'total_size' => 0,
|
||||
'processed_from' => request()->ip() ?? null,
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
# 📧 Email News Fetching
|
||||
|
||||
Автоматическое получение новостей из Email и создание черновиков в системе.
|
||||
|
||||
## 📋 Описание
|
||||
|
||||
Функция позволяет автоматически:
|
||||
- Подключаться к IMAP-серверу
|
||||
- Получать непрочитанные письма от редактора
|
||||
- Скачивать вложения (DOC, DOCX, изображения)
|
||||
- Создавать новости через существующий `ProcessMixedFilesAction`
|
||||
- Помечать обработанные письма как прочитанные
|
||||
|
||||
## 🏗 Архитектура (Porto)
|
||||
|
||||
```
|
||||
app/Containers/Dashboard/
|
||||
├── Actions/
|
||||
│ └── FetchEmailNewsAction.php # Оркестрация процесса
|
||||
├── Tasks/
|
||||
│ ├── ConnectToImapTask.php # IMAP подключение
|
||||
│ ├── FetchUnreadEmailsTask.php # Получение писем
|
||||
│ ├── FilterBySenderTask.php # Фильтрация по отправителю
|
||||
│ ├── DownloadAttachmentsTask.php # Загрузка вложений
|
||||
│ └── MarkEmailAsReadTask.php # Пометка как прочитанное
|
||||
├── Data/
|
||||
│ └── EmailAttachmentData.php # DTO для вложений
|
||||
├── Exceptions/
|
||||
│ └── EmailFetchException.php # Исключения
|
||||
├── Commands/
|
||||
│ └── FetchEmailNewsCommand.php # Artisan команда
|
||||
└── Configs/
|
||||
└── email-news.php # Конфигурация
|
||||
```
|
||||
|
||||
## 🔧 Установка
|
||||
|
||||
### 1. Пересобрать PHP-контейнер (требуется расширение IMAP)
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose build --no-cache ntspi-php
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 2. Настроить переменные окружения
|
||||
|
||||
Скопируйте `.env.email-news.example` в `.env` и заполните:
|
||||
|
||||
```env
|
||||
EMAIL_NEWS_ENABLED=true
|
||||
EMAIL_NEWS_IMAP_HOST=imap.yandex.ru
|
||||
EMAIL_NEWS_IMAP_PORT=993
|
||||
EMAIL_NEWS_ENCRYPTION=ssl
|
||||
EMAIL_NEWS_IMAP_USER=news@ntspi.ru
|
||||
EMAIL_NEWS_IMAP_PASS=app_password
|
||||
EMAIL_NEWS_SENDER_EMAIL=editor@example.com
|
||||
EMAIL_NEWS_FOLDER=INBOX
|
||||
```
|
||||
|
||||
### 3. Протестировать подключение
|
||||
|
||||
Запустите команду вручную:
|
||||
|
||||
```bash
|
||||
docker exec -it ntspi-php php artisan email:fetch-news --log
|
||||
```
|
||||
|
||||
## 📝 Использование
|
||||
|
||||
### Ручной запуск
|
||||
|
||||
```bash
|
||||
# Обычный запуск
|
||||
docker exec -it ntspi-php php artisan email:fetch-news
|
||||
|
||||
# С подробным логом
|
||||
docker exec -it ntspi-php php artisan email:fetch-news --log
|
||||
|
||||
# Принудительный запуск (если отключено в конфиге)
|
||||
docker exec -it ntspi-php php artisan email:fetch-news --force
|
||||
```
|
||||
|
||||
### Автоматический запуск (Cron)
|
||||
|
||||
Команда автоматически добавлена в расписание Laravel Scheduler:
|
||||
- **Частота:** каждые 5 минут
|
||||
- **Защита от перекрытий:** `withoutOverlapping()`
|
||||
- **Один сервер:** `onOneServer()`
|
||||
|
||||
Scheduler уже настроен в `app/Ship/Kernels/ConsoleKernel.php`.
|
||||
|
||||
Убедитесь, что Cron запущен в контейнере:
|
||||
|
||||
```bash
|
||||
# Проверьте crontab
|
||||
docker exec -it ntspi-php crontab -l
|
||||
|
||||
# Должно быть:
|
||||
# * * * * * php /var/www/artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
## 🔐 Безопасность
|
||||
|
||||
### Фильтрация отправителей
|
||||
|
||||
Обрабатываются **только письма от редактора**, указанного в `EMAIL_NEWS_SENDER_EMAIL`.
|
||||
|
||||
Письма от других отправителей:
|
||||
- ❌ Не обрабатываются
|
||||
- ❌ Не скачиваются
|
||||
- ✅ Логируются (если `EMAIL_NEWS_LOG_SKIPPED=true`)
|
||||
- ✅ Остаются в папке (не помечаются прочитанными)
|
||||
|
||||
### Whitelist нескольких отправителей
|
||||
|
||||
В конфиге `app/Containers/Dashboard/Configs/email-news.php` можно указать несколько email:
|
||||
|
||||
```php
|
||||
'allowed_senders' => [
|
||||
env('EMAIL_NEWS_SENDER_EMAIL'),
|
||||
env('EMAIL_NEWS_SENDER_EMAIL_2'),
|
||||
env('EMAIL_NEWS_SENDER_EMAIL_3'),
|
||||
],
|
||||
```
|
||||
|
||||
### App-Specific Password
|
||||
|
||||
**Рекомендуется** использовать специальный пароль приложения вместо основного пароля:
|
||||
|
||||
- **Yandex:** https://passport.yandex.ru/profile/passwords
|
||||
- **Google:** https://myaccount.google.com/apppasswords
|
||||
- **Mail.ru:** https://account.mail.ru/security
|
||||
|
||||
## 📊 Логирование
|
||||
|
||||
Логи доступны в `storage/logs/laravel.log`:
|
||||
|
||||
```log
|
||||
[2024-01-15 10:30:00] local.INFO: [FetchEmailNewsAction] Начало получения новостей из Email
|
||||
[2024-01-15 10:30:01] local.INFO: [ConnectToImapTask] Успешное подключение к IMAP
|
||||
[2024-01-15 10:30:02] local.INFO: [FetchUnreadEmailsTask] Получены письма: count=3
|
||||
[2024-01-15 10:30:02] local.INFO: [FilterBySenderTask] Фильтрация писем: total=3, filtered=2, skipped=1
|
||||
[2024-01-15 10:30:05] local.INFO: [FetchEmailNewsAction:processEmail] Письмо успешно обработано: post_id=123
|
||||
```
|
||||
|
||||
## ⚠️ Обработка ошибок
|
||||
|
||||
### Типичные ошибки
|
||||
|
||||
| Ошибка | Причина | Решение |
|
||||
|--------|---------|---------|
|
||||
| `Не удалось подключиться к IMAP-серверу` | Неправильный хост/порт/пароль | Проверьте `.env` настройки |
|
||||
| `IMAP-папка не найдена` | Папка не существует | Проверьте имя папки в `EMAIL_NEWS_FOLDER` |
|
||||
| `Письмо получено от неразрешённого отправителя` | Отправитель не в whitelist | Добавьте email в `allowed_senders` |
|
||||
| `Нет DOC/DOCX файла для извлечения текста` | Во вложениях нет документа | Редактор должен прикрепить DOC/DOCX |
|
||||
|
||||
### Отладка
|
||||
|
||||
```bash
|
||||
# Запуск с подробным выводом
|
||||
docker exec -it ntspi-php php artisan email:fetch-news --log
|
||||
|
||||
# Просмотр последних логов
|
||||
docker exec -it ntspi-php tail -f storage/logs/laravel.log
|
||||
|
||||
# Проверка расширения IMAP
|
||||
docker exec -it ntspi-php php -m | grep imap
|
||||
```
|
||||
|
||||
## 🔄 Поток данных
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Cron (каждые 5 мин) → php artisan email:fetch-news │
|
||||
│ ↓ │
|
||||
│ FetchEmailNewsCommand │
|
||||
│ ↓ │
|
||||
│ FetchEmailNewsAction │
|
||||
│ ├─ ConnectToImapTask (IMAP-соединение) │
|
||||
│ ├─ FetchUnreadEmailsTask (получение писем) │
|
||||
│ ├─ FilterBySenderTask (проверка отправителя) │
|
||||
│ ├─ DownloadAttachmentsTask (скачивание вложений) │
|
||||
│ └─ ProcessMixedFilesAction (создание поста) ←───┐ │
|
||||
│ ↓ │ │
|
||||
│ MarkEmailAsReadTask (пометка как прочитанное) │ │
|
||||
└───────────────────────────────────────────────────┼────┘
|
||||
│
|
||||
┌───────────────────────────────┘
|
||||
↓
|
||||
Существующий процесс создания новости
|
||||
(AI-распознавание, сжатие изображений, и т.д.)
|
||||
```
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### Отправка тестового письма
|
||||
|
||||
1. Отправьте письмо с `EMAIL_NEWS_SENDER_EMAIL` на `EMAIL_NEWS_IMAP_USER`
|
||||
2. Прикрепите DOC/DOCX файл (текст новости)
|
||||
3. Прикрепите изображения (опционально)
|
||||
4. Запустите команду:
|
||||
|
||||
```bash
|
||||
docker exec -it ntspi-php php artisan email:fetch-news --log
|
||||
```
|
||||
|
||||
5. Проверьте создание новости в Dashboard
|
||||
|
||||
### Мок-тестирование (для разработчиков)
|
||||
|
||||
```php
|
||||
// В Unit-тестах можно моковать IMAP
|
||||
$this->mock(ClientManager::class, function ($mock) {
|
||||
$mock->shouldReceive('account')
|
||||
->with('email_news')
|
||||
->andReturn($client);
|
||||
});
|
||||
```
|
||||
|
||||
## 📚 Ссылки
|
||||
|
||||
- [Webklex PHP-IMAP Documentation](https://github.com/Webklex/php-imap)
|
||||
- [Laravel Scheduler](https://laravel.com/docs/10.x/scheduling)
|
||||
- [Porto Architecture](https://github.com/AlxDorosenco/Porto)
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Intervention\Image\Facades\Image;
|
||||
|
||||
class CompressImageTask
|
||||
{
|
||||
/**
|
||||
* Максимальное качество сжатия WebP (0-100)
|
||||
*/
|
||||
private const WEBP_QUALITY = 82;
|
||||
|
||||
/**
|
||||
* Максимальное качество для JPEG (0-100)
|
||||
*/
|
||||
private const JPEG_QUALITY = 85;
|
||||
|
||||
/**
|
||||
* Максимальная ширина изображения (px)
|
||||
*/
|
||||
private const MAX_WIDTH = 1920;
|
||||
|
||||
/**
|
||||
* Максимальная высота изображения (px)
|
||||
*/
|
||||
private const MAX_HEIGHT = 1920;
|
||||
|
||||
/**
|
||||
* Порог размера файла для сжатия (в байтах) - 1MB
|
||||
*/
|
||||
private const SIZE_THRESHOLD = 1048576;
|
||||
|
||||
/**
|
||||
* Сжимает изображение и конвертирует в WebP
|
||||
*
|
||||
* @param UploadedFile $file Исходный файл изображения
|
||||
* @return array ['path' => путь к файлу, 'size' => размер в байтах, 'original_size' => исходный размер]
|
||||
*/
|
||||
public function run(UploadedFile $file): array
|
||||
{
|
||||
$extension = strtolower($file->getClientOriginalExtension());
|
||||
|
||||
Log::info('[CompressImageTask] Начало обработки изображения', [
|
||||
'file' => $file->getClientOriginalName(),
|
||||
'extension' => $extension,
|
||||
'size' => $this->formatFileSize($file->getSize()),
|
||||
'size_bytes' => $file->getSize(),
|
||||
]);
|
||||
|
||||
// Если файл уже меньше порога и это WebP - пропускаем сжатие
|
||||
if ($file->getSize() <= self::SIZE_THRESHOLD && $extension === 'webp') {
|
||||
Log::info('[CompressImageTask] Файл уже оптимизирован, пропускаем', [
|
||||
'file' => $file->getClientOriginalName(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'file' => $file,
|
||||
'size' => $file->getSize(),
|
||||
'original_size' => $file->getSize(),
|
||||
'compressed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
// Создаём изображение через Intervention
|
||||
$img = Image::make($file->getRealPath());
|
||||
|
||||
// Получаем исходные размеры
|
||||
$originalWidth = $img->width();
|
||||
$originalHeight = $img->height();
|
||||
|
||||
Log::info('[CompressImageTask] Исходные размеры', [
|
||||
'width' => $originalWidth,
|
||||
'height' => $originalHeight,
|
||||
]);
|
||||
|
||||
// Ресайзим если больше максимальных размеров
|
||||
if ($originalWidth > self::MAX_WIDTH || $originalHeight > self::MAX_HEIGHT) {
|
||||
$img->resize(self::MAX_WIDTH, self::MAX_HEIGHT, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
|
||||
Log::info('[CompressImageTask] Изображение ресайзнуто', [
|
||||
'new_width' => $img->width(),
|
||||
'new_height' => $img->height(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Конвертируем в WebP и сжимаем
|
||||
$webpData = $img->encode('webp', self::WEBP_QUALITY)->getEncoded();
|
||||
|
||||
// Сохраняем во временный файл
|
||||
$tempFileName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME) . '.webp';
|
||||
$tempPath = sys_get_temp_dir() . '/' . uniqid('img_compress_') . '.webp';
|
||||
file_put_contents($tempPath, $webpData);
|
||||
|
||||
$newSize = filesize($tempPath);
|
||||
|
||||
Log::info('[CompressImageTask] Сжатие завершено', [
|
||||
'original_size' => $this->formatFileSize($file->getSize()),
|
||||
'compressed_size' => $this->formatFileSize($newSize),
|
||||
'compression_ratio' => round((1 - $newSize / $file->getSize()) * 100, 2) . '%',
|
||||
'temp_path' => $tempPath,
|
||||
]);
|
||||
|
||||
// Создаём новый UploadedFile из сжатого
|
||||
$compressedFile = new UploadedFile(
|
||||
$tempPath,
|
||||
$tempFileName,
|
||||
'image/webp',
|
||||
$newSize,
|
||||
true
|
||||
);
|
||||
|
||||
return [
|
||||
'file' => $compressedFile,
|
||||
'size' => $newSize,
|
||||
'original_size' => $file->getSize(),
|
||||
'compressed' => true,
|
||||
'compression_ratio' => round((1 - $newSize / $file->getSize()) * 100, 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, является ли файл изображением
|
||||
*/
|
||||
public function isImage(UploadedFile $file): bool
|
||||
{
|
||||
$extension = strtolower($file->getClientOriginalExtension());
|
||||
$imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'svg'];
|
||||
|
||||
return in_array($extension, $imageExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирует размер файла
|
||||
*/
|
||||
private function formatFileSize(int $bytes): string
|
||||
{
|
||||
$units = ['б', 'КиБ', 'МиБ', 'ГиБ'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
$bytes /= (1 << (10 * $pow));
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use App\Containers\Dashboard\Exceptions\EmailFetchException;
|
||||
use Webklex\PHPIMAP\Client;
|
||||
use Webklex\PHPIMAP\ClientManager;
|
||||
use Webklex\PHPIMAP\Folder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Подключение к IMAP-серверу
|
||||
*/
|
||||
class ConnectToImapTask
|
||||
{
|
||||
/**
|
||||
* Подключиться к IMAP-серверу
|
||||
*
|
||||
* @param string|null $accountName Имя аккаунта из config/imap.php
|
||||
* @return Client IMAP клиент
|
||||
* @throws EmailFetchException
|
||||
*/
|
||||
public function run(?string $accountName = null): Client
|
||||
{
|
||||
$accountName = $accountName ?? config('email-news.imap_account', 'email_news');
|
||||
|
||||
// Получаем конфиг для webklex/php-imap
|
||||
$imapConfig = config('imap');
|
||||
|
||||
Log::info('[ConnectToImapTask] Попытка подключения к IMAP', [
|
||||
'account' => $accountName,
|
||||
'host' => $imapConfig['accounts'][$accountName]['host'] ?? 'unknown',
|
||||
]);
|
||||
|
||||
try {
|
||||
// Создаём ClientManager с явным конфигом
|
||||
$clientManager = new ClientManager($imapConfig);
|
||||
$client = $clientManager->account($accountName);
|
||||
$client->connect();
|
||||
|
||||
Log::info('[ConnectToImapTask] Успешное подключение к IMAP', [
|
||||
'account' => $accountName,
|
||||
]);
|
||||
|
||||
return $client;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[ConnectToImapTask] Ошибка подключения к IMAP', [
|
||||
'account' => $accountName,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
throw EmailFetchException::connectionFailed($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить папку
|
||||
*
|
||||
* @param Client $client IMAP клиент
|
||||
* @param string $folderName Имя папки
|
||||
* @return Folder
|
||||
* @throws EmailFetchException
|
||||
*/
|
||||
public function getFolder(Client $client, string $folderName): Folder
|
||||
{
|
||||
Log::info('[ConnectToImapTask] Получение папки', [
|
||||
'folder' => $folderName,
|
||||
]);
|
||||
|
||||
try {
|
||||
// Пробуем получить папку напрямую
|
||||
$folder = $client->getFolder($folderName);
|
||||
|
||||
// Если не получилось, ищем в списке папок
|
||||
if (!$folder) {
|
||||
$folders = $client->getFolders();
|
||||
foreach ($folders as $f) {
|
||||
if ($f->name === $folderName || $f->path === $folderName) {
|
||||
$folder = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$folder) {
|
||||
throw EmailFetchException::folderNotFound($folderName);
|
||||
}
|
||||
|
||||
Log::info('[ConnectToImapTask] Папка получена успешно', [
|
||||
'folder' => $folderName,
|
||||
'fullName' => $folder->full_name ?? $folder->name ?? $folderName,
|
||||
]);
|
||||
|
||||
return $folder;
|
||||
} catch (EmailFetchException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[ConnectToImapTask] Ошибка получения папки', [
|
||||
'folder' => $folderName,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
throw EmailFetchException::folderNotFound($folderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use App\Containers\Dashboard\Data\EmailAttachmentData;
|
||||
use App\Containers\Dashboard\Exceptions\EmailFetchException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webklex\PHPIMAP\Attachment;
|
||||
|
||||
/**
|
||||
* Загрузка и сохранение вложений из письма
|
||||
*/
|
||||
class DownloadAttachmentsTask
|
||||
{
|
||||
/**
|
||||
* Скачать и сохранить вложения
|
||||
*
|
||||
* @param object $message IMAP сообщение
|
||||
* @param string|null $disk Диск для сохранения
|
||||
* @return array<EmailAttachmentData>
|
||||
* @throws EmailFetchException
|
||||
*/
|
||||
public function run(object $message, ?string $disk = null): array
|
||||
{
|
||||
$disk = $disk ?? config('email-news.attachments_folder', 'email_attachments');
|
||||
|
||||
Log::info('[DownloadAttachmentsTask] Начало загрузки вложений', [
|
||||
'message_id' => $message->getMessageId(),
|
||||
'disk' => $disk,
|
||||
]);
|
||||
|
||||
$attachments = $message->getAttachments();
|
||||
|
||||
if (empty($attachments)) {
|
||||
Log::warning('[DownloadAttachmentsTask] Вложения не найдены');
|
||||
throw EmailFetchException::noAttachmentsFound();
|
||||
}
|
||||
|
||||
Log::info('[DownloadAttachmentsTask] Найдено вложений', [
|
||||
'count' => count($attachments),
|
||||
]);
|
||||
|
||||
$savedAttachments = [];
|
||||
$maxSize = config('email-news.max_attachment_size', 41943040); // 40MB по умолчанию
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
try {
|
||||
// Проверяем размер
|
||||
if ($maxSize > 0 && $attachment->getSize() > $maxSize) {
|
||||
Log::warning('[DownloadAttachmentsTask] Вложение превышает максимальный размер', [
|
||||
'filename' => $attachment->getName(),
|
||||
'size' => $attachment->getSize(),
|
||||
'max_size' => $maxSize,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Сохраняем вложение
|
||||
$savedPath = $this->saveAttachment($attachment, $disk);
|
||||
|
||||
if ($savedPath) {
|
||||
$savedAttachments[] = EmailAttachmentData::fromFile(
|
||||
path: $savedPath,
|
||||
originalFilename: $attachment->getName(),
|
||||
mimeType: $attachment->getContentType(),
|
||||
size: $attachment->getSize(),
|
||||
contentId: $attachment->getContentId(),
|
||||
);
|
||||
|
||||
Log::info('[DownloadAttachmentsTask] Вложение сохранено', [
|
||||
'filename' => $attachment->getName(),
|
||||
'path' => $savedPath,
|
||||
'size' => $attachment->getSize(),
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[DownloadAttachmentsTask] Ошибка сохранения вложения', [
|
||||
'filename' => $attachment->getName(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
// Продолжаем обработку остальных вложений
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($savedAttachments)) {
|
||||
Log::error('[DownloadAttachmentsTask] Не удалось сохранить ни одно вложение');
|
||||
throw EmailFetchException::noAttachmentsFound();
|
||||
}
|
||||
|
||||
Log::info('[DownloadAttachmentsTask] Загрузка вложений завершена', [
|
||||
'saved_count' => count($savedAttachments),
|
||||
]);
|
||||
|
||||
return $savedAttachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить вложение на диск
|
||||
*
|
||||
* @param Attachment $attachment Вложение
|
||||
* @param string $disk Диск для сохранения
|
||||
* @return string|null Путь к сохранённому файлу
|
||||
*/
|
||||
private function saveAttachment(Attachment $attachment, string $disk): ?string
|
||||
{
|
||||
$filename = $this->generateUniqueFilename($attachment->getName());
|
||||
$savePath = storage_path('app/' . $disk);
|
||||
|
||||
// Создаём директорию, если не существует
|
||||
if (!is_dir($savePath)) {
|
||||
mkdir($savePath, 0755, true);
|
||||
}
|
||||
|
||||
// Сохраняем вложение (метод save принимает только путь и имя файла)
|
||||
try {
|
||||
$savedPath = $attachment->save($savePath, $filename);
|
||||
|
||||
if ($savedPath) {
|
||||
// Возвращаем относительный путь для сохранения в БД
|
||||
return $disk . '/' . $filename;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[DownloadAttachmentsTask:saveAttachment] Ошибка сохранения', [
|
||||
'filename' => $attachment->getName(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сгенерировать уникальное имя файла
|
||||
*
|
||||
* @param string $originalName Оригинальное имя файла
|
||||
* @return string
|
||||
*/
|
||||
private function generateUniqueFilename(string $originalName): string
|
||||
{
|
||||
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
$basename = pathinfo($originalName, PATHINFO_FILENAME);
|
||||
|
||||
// Очищаем имя от специальных символов
|
||||
$basename = preg_replace('/[^a-zA-Z0-9_\-\p{L}]/u', '_', $basename);
|
||||
|
||||
// Добавляем timestamp для уникальности
|
||||
return $basename . '_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $extension;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use Webklex\PHPIMAP\Folder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Получение непрочитанных писем из папки
|
||||
*/
|
||||
class FetchUnreadEmailsTask
|
||||
{
|
||||
/**
|
||||
* Получить непрочитанные письма
|
||||
*
|
||||
* @param Folder $folder IMAP папка
|
||||
* @return array Массив писем
|
||||
*/
|
||||
public function run(Folder $folder): array
|
||||
{
|
||||
Log::info('[FetchUnreadEmailsTask] Получение непрочитанных писем', [
|
||||
'folder' => $folder->full_name ?? $folder->name ?? 'unknown',
|
||||
]);
|
||||
|
||||
try {
|
||||
// Получаем все непрочитанные сообщения
|
||||
$messages = $folder->messages()->unseen()->get();
|
||||
|
||||
$emails = [];
|
||||
foreach ($messages as $message) {
|
||||
$from = $message->getFrom()[0] ?? null;
|
||||
$subject = $message->getSubject();
|
||||
$date = $message->getDate();
|
||||
|
||||
$emails[] = [
|
||||
'message' => $message,
|
||||
'message_id' => $message->getMessageId(),
|
||||
'from_email' => $from?->mail ?? null,
|
||||
'from_name' => $from?->name ?? null,
|
||||
'subject' => $subject,
|
||||
'date' => $date,
|
||||
'has_attachments' => $message->hasAttachments(),
|
||||
];
|
||||
}
|
||||
|
||||
Log::info('[FetchUnreadEmailsTask] Получены письма', [
|
||||
'count' => count($emails),
|
||||
'folder' => $folder->full_name ?? $folder->name ?? 'unknown',
|
||||
]);
|
||||
|
||||
return $emails;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[FetchUnreadEmailsTask] Ошибка получения писем', [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Фильтрация писем по отправителю
|
||||
*/
|
||||
class FilterBySenderTask
|
||||
{
|
||||
/**
|
||||
* Отфильтровать письма по разрешённым отправителям
|
||||
*
|
||||
* @param array $emails Массив писем
|
||||
* @param array|null $allowedSenders Whitelist email-адресов
|
||||
* @return array Отфильтрованные письма
|
||||
*/
|
||||
public function run(array $emails, ?array $allowedSenders = null): array
|
||||
{
|
||||
// Если whitelist не передан, берём из конфига
|
||||
$allowedSenders = $allowedSenders ?? config('email-news.allowed_senders', []);
|
||||
|
||||
// Фильтруем пустые значения
|
||||
$allowedSenders = array_filter($allowedSenders, fn($email) => !empty($email));
|
||||
|
||||
// Если список пуст, используем editor_email
|
||||
if (empty($allowedSenders)) {
|
||||
$editorEmail = config('email-news.editor_email');
|
||||
if ($editorEmail) {
|
||||
$allowedSenders = [$editorEmail];
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('[FilterBySenderTask] Фильтрация писем', [
|
||||
'total_emails' => count($emails),
|
||||
'allowed_senders' => $allowedSenders,
|
||||
]);
|
||||
|
||||
if (empty($allowedSenders)) {
|
||||
Log::warning('[FilterBySenderTask] Не указан разрешённый отправитель, пропускаем все письма');
|
||||
return [];
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
$skippedCount = 0;
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$fromEmail = $email['from_email'];
|
||||
|
||||
// Проверяем, есть ли отправитель в whitelist
|
||||
if (!in_array($fromEmail, $allowedSenders, true)) {
|
||||
$skippedCount++;
|
||||
|
||||
// Логируем только если включено логирование
|
||||
if (config('email-news.log_skipped_emails', true)) {
|
||||
Log::debug('[FilterBySenderTask] Пропущено письмо от неразрешённого отправителя', [
|
||||
'from' => $fromEmail,
|
||||
'subject' => $email['subject'],
|
||||
'date' => $email['date'],
|
||||
]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered[] = $email;
|
||||
}
|
||||
|
||||
Log::info('[FilterBySenderTask] Фильтрация завершена', [
|
||||
'total' => count($emails),
|
||||
'filtered' => count($filtered),
|
||||
'skipped' => $skippedCount,
|
||||
]);
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить конкретный email на разрешение
|
||||
*
|
||||
* @param string $email Email для проверки
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowed(string $email): bool
|
||||
{
|
||||
$allowedSenders = config('email-news.allowed_senders', []);
|
||||
$allowedSenders = array_filter($allowedSenders, fn($e) => !empty($e));
|
||||
|
||||
if (empty($allowedSenders)) {
|
||||
$allowedSenders = [config('email-news.editor_email')];
|
||||
}
|
||||
|
||||
return in_array($email, $allowedSenders, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Пометка письма как прочитанного
|
||||
*/
|
||||
class MarkEmailAsReadTask
|
||||
{
|
||||
/**
|
||||
* Пометить письмо как прочитанное
|
||||
*
|
||||
* @param object $message IMAP сообщение
|
||||
* @return bool
|
||||
*/
|
||||
public function run(object $message): bool
|
||||
{
|
||||
Log::info('[MarkEmailAsReadTask] Пометка письма как прочитанного', [
|
||||
'message_id' => $message->getMessageId(),
|
||||
'subject' => $message->getSubject(),
|
||||
]);
|
||||
|
||||
try {
|
||||
$message->setFlag('Seen');
|
||||
|
||||
Log::info('[MarkEmailAsReadTask] Письмо помечено как прочитанное');
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[MarkEmailAsReadTask] Ошибка пометки письма', [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить письмо как прочитанное и переместить в другую папку
|
||||
*
|
||||
* @param object $message IMAP сообщение
|
||||
* @param string $targetFolder Целевая папка
|
||||
* @return bool
|
||||
*/
|
||||
public function markAndMove(object $message, string $targetFolder): bool
|
||||
{
|
||||
Log::info('[MarkEmailAsReadTask] Пометка и перемещение письма', [
|
||||
'message_id' => $message->getMessageId(),
|
||||
'target_folder' => $targetFolder,
|
||||
]);
|
||||
|
||||
try {
|
||||
// Помечаем как прочитанное
|
||||
$message->setFlag('Seen');
|
||||
|
||||
// Перемещаем в другую папку
|
||||
$message->moveToFolder($targetFolder);
|
||||
|
||||
Log::info('[MarkEmailAsReadTask] Письмо обработано и перемещено', [
|
||||
'target_folder' => $targetFolder,
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[MarkEmailAsReadTask] Ошибка обработки письма', [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,15 @@ class ProcessMixedFilesController extends Controller
|
||||
|
||||
$result = $this->processMixedFilesAction->run($files);
|
||||
|
||||
// Формируем сообщение со статистикой сжатия
|
||||
$compressionMessage = '';
|
||||
if (isset($result['compressionStats']) && $result['compressionStats']['compressed'] > 0) {
|
||||
$compressionMessage = ' Изображения сжаты: ' . $result['compressionStats']['compressed'] . '/' . $result['compressionStats']['total'] .
|
||||
' (экономия ' . $this->formatFileSize($result['compressionStats']['saved_bytes']) . ')';
|
||||
}
|
||||
|
||||
return back()->with([
|
||||
'success' => 'Файлы успешно загружены! Новость создана: ' . $result['post']->title,
|
||||
'success' => 'Файлы успешно загружены! Новость создана: ' . $result['post']->title . $compressionMessage,
|
||||
'extracted_text' => $result['newsData'],
|
||||
'created_post' => [
|
||||
...$result['post']->toArray(),
|
||||
@@ -45,6 +52,20 @@ class ProcessMixedFilesController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирует размер файла
|
||||
*/
|
||||
private function formatFileSize(int $bytes): string
|
||||
{
|
||||
$units = ['б', 'КиБ', 'МиБ', 'ГиБ'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
$bytes /= (1 << (10 * $pow));
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Собирает все файлы из request
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Ship\Kernels;
|
||||
|
||||
use AlxDorosenco\PortoForLaravel\Loaders\CommandsLoader;
|
||||
use AlxDorosenco\PortoForLaravel\Loaders\RoutesLoader;
|
||||
use App\Containers\Dashboard\Commands\FetchEmailNewsCommand;
|
||||
use App\Ship\Commands\InitRoles;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as LaravelConsoleKernel;
|
||||
@@ -22,10 +23,17 @@ class ConsoleKernel extends LaravelConsoleKernel
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->command('sitemap:generate')->dailyAt('03:00');
|
||||
|
||||
// Получение новостей из Email каждые 5 минут
|
||||
$schedule->command(FetchEmailNewsCommand::class)
|
||||
->everyFiveMinutes()
|
||||
->withoutOverlapping()
|
||||
->onOneServer();
|
||||
}
|
||||
|
||||
protected $commands = [
|
||||
InitRoles::class,
|
||||
FetchEmailNewsCommand::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
+5
-4
@@ -9,11 +9,11 @@
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*",
|
||||
"alxdorosenco/porto-for-laravel": "^10.0",
|
||||
"awcodes/filament-tiptap-editor": "3.4.16",
|
||||
"bezhansalleh/filament-shield": "3.2.6",
|
||||
"filament/filament": "v3.2.127",
|
||||
"awcodes/filament-tiptap-editor": "^3.4.16",
|
||||
"bezhansalleh/filament-shield": "^3.2.6",
|
||||
"filament/filament": "^3.3.49",
|
||||
"filament/spatie-laravel-settings-plugin": "^3.2",
|
||||
"filament/spatie-laravel-tags-plugin": "v3.2.113",
|
||||
"filament/spatie-laravel-tags-plugin": "^3.2.113",
|
||||
"fruitcake/laravel-cors": "dev-develop",
|
||||
"guava/filament-icon-picker": "2.2.4",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
@@ -36,6 +36,7 @@
|
||||
"tightenco/ziggy": "^1.0",
|
||||
"tomatophp/filament-icons": "v1.1.4",
|
||||
"vkcom/vk-php-sdk": "^5.131",
|
||||
"webklex/php-imap": "^6.2",
|
||||
"xvladqt/faker-lorem-flickr": "^1.0",
|
||||
"yepsua/filament-range-field": "^0.3.4"
|
||||
},
|
||||
|
||||
Generated
+1160
-1060
File diff suppressed because it is too large
Load Diff
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Set any customizations webklex/php-imap you would like to use.
|
||||
*/
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default date format
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The default date format is used to convert any given Carbon::class object
|
||||
| into a valid date string.
|
||||
|
|
||||
*/
|
||||
'date_format' => 'd-M-y',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default account
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The default account identifier. It will be used as default for any missing account parameters.
|
||||
| If however the default account is missing a parameter the package default will be used.
|
||||
|
|
||||
*/
|
||||
'default' => 'email_news',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available accounts
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Please list all IMAP accounts which you are planning to use within the
|
||||
| array below.
|
||||
|
|
||||
*/
|
||||
'accounts' => [
|
||||
'email_news' => [
|
||||
'host' => env('EMAIL_NEWS_IMAP_HOST', 'imap.mail.ru'),
|
||||
'port' => env('EMAIL_NEWS_IMAP_PORT', 993),
|
||||
'protocol' => env('EMAIL_NEWS_PROTOCOL', 'imap'),
|
||||
'encryption' => env('EMAIL_NEWS_ENCRYPTION', 'ssl'),
|
||||
'validate_cert' => env('EMAIL_NEWS_VALIDATE_CERT', true),
|
||||
'username' => env('EMAIL_NEWS_IMAP_USER', 'ilya-vavilov@internet.ru'),
|
||||
'password' => env('EMAIL_NEWS_IMAP_PASS', ''),
|
||||
'authentication' => null,
|
||||
'proxy' => [
|
||||
'socket' => null,
|
||||
'request_fulluri' => false,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
],
|
||||
'timeout' => 30,
|
||||
'extensions' => [],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default account
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This account will be used as default if no account is specified.
|
||||
|
|
||||
*/
|
||||
'default' => [
|
||||
'host' => 'localhost',
|
||||
'port' => 993,
|
||||
'protocol' => 'imap',
|
||||
'encryption' => 'ssl',
|
||||
'validate_cert' => true,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'authentication' => null,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available IMAP options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Available php imap config parameters are listed below
|
||||
|
|
||||
*/
|
||||
'options' => [
|
||||
// Append option to the connection string
|
||||
'append' => null,
|
||||
|
||||
// IMAP open options
|
||||
'open' => [
|
||||
'DISABLE_AUTHENTICATOR' => 'GSSAPI',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available flags
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| List of available flags
|
||||
|
|
||||
*/
|
||||
'flags' => [
|
||||
'recent' => '\Recent',
|
||||
'flagged' => '\Flagged',
|
||||
'answered' => '\Answered',
|
||||
'deleted' => '\Deleted',
|
||||
'seen' => '\Seen',
|
||||
'draft' => '\Draft',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available events
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
*/
|
||||
'events' => [
|
||||
'message' => [
|
||||
'new' => \Webklex\PHPIMAP\Events\MessageNewEvent::class,
|
||||
'moved' => \Webklex\PHPIMAP\Events\MessageMovedEvent::class,
|
||||
],
|
||||
'folder' => [
|
||||
'new' => \Webklex\PHPIMAP\Events\FolderNewEvent::class,
|
||||
'moved' => \Webklex\PHPIMAP\Events\FolderMovedEvent::class,
|
||||
'deleted' => \Webklex\PHPIMAP\Events\FolderDeletedEvent::class,
|
||||
],
|
||||
'flag' => [
|
||||
'new' => \Webklex\PHPIMAP\Events\FlagNewEvent::class,
|
||||
'deleted' => \Webklex\PHPIMAP\Events\FlagDeletedEvent::class,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available decoding options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Available php imap config parameters are listed below
|
||||
|
|
||||
*/
|
||||
'decoding' => [
|
||||
'options' => [
|
||||
'UTF7-IMAP' => env('IMAP_DECODING_UTF7_IMAP', true),
|
||||
'attachments' => env('IMAP_DECODING_ATTACHMENTS', true),
|
||||
],
|
||||
'ignore' => [
|
||||
'spelling' => env('IMAP_DECODING_IGNORE_SPELLING', true),
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Available masking options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By using the masking option you can define which class should be used
|
||||
|
|
||||
*/
|
||||
'masking' => [
|
||||
'message' => \Webklex\PHPIMAP\Support\Masks\MessageMask::class,
|
||||
'attachment' => \Webklex\PHPIMAP\Support\Masks\AttachmentMask::class,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('email_fetch_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Информация о письме
|
||||
$table->string('message_id')->index();
|
||||
$table->string('from_email');
|
||||
$table->string('from_name')->nullable();
|
||||
$table->string('subject');
|
||||
$table->timestamp('email_date');
|
||||
|
||||
// Результат обработки
|
||||
$table->enum('status', ['success', 'failed', 'skipped']);
|
||||
$table->foreignId('post_id')->nullable()->constrained('posts')->onDelete('set null');
|
||||
$table->text('error_message')->nullable();
|
||||
|
||||
// Статистика
|
||||
$table->integer('attachments_count')->default(0);
|
||||
$table->bigInteger('total_size')->default(0); // в байтах
|
||||
|
||||
// Мета
|
||||
$table->ipAddress('processed_from')->nullable(); // IP сервера
|
||||
$table->json('metadata')->nullable(); // Дополнительные данные
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// Индексы для быстрого поиска
|
||||
$table->index('status');
|
||||
$table->index('created_at');
|
||||
$table->index(['from_email', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('email_fetch_logs');
|
||||
}
|
||||
};
|
||||
Generated
+3
-3
@@ -1774,9 +1774,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001717",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz",
|
||||
"integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==",
|
||||
"version": "1.0.30001780",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
|
||||
"integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};
|
||||
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};
|
||||
function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default};
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
body{background:url(../img/background_login.png) no-repeat 50% fixed;background-size:cover;color:#1b1b1b;font-family:Inter,Segoe UI,sans-serif;margin:0;min-height:100vh}.wrapper{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:hsla(0,0%,100%,.9);border-radius:14px;box-shadow:0 10px 35px rgba(0,0,0,.25);margin:2rem auto;max-width:1100px;padding:2.5rem 3rem;transition:all .3s ease}.wrapper:hover{box-shadow:0 12px 40px rgba(0,0,0,.35)}.header-content{align-items:center;display:flex;padding:10px 0;position:relative}.header-logo{height:auto;width:150px}.header-title{color:#1a1f71;font-size:30px;font-weight:700;text-align:center;width:100%}.vikon-logo{background-image:url(../img/vikon_logo.png);background-position:50%;background-repeat:no-repeat;background-size:contain;display:inline-block;height:50px;text-indent:-9999px;width:150px}@media (max-width:992px){.header-logo{display:none}.header-title{font-size:24px;text-align:center}}.btn-success{border-radius:8px;font-weight:500;letter-spacing:.3px;padding:.65rem 1.2rem;transition:all .2s ease}.btn-success:hover{background-color:#248f55;transform:translateY(-1px)}#modules-container{margin-top:1.5rem}#modules-container>ul{list-style:none;margin-bottom:1.5rem;padding-left:0}#modules-container>ul>li:first-child b{border-left:6px solid coral;color:#1a1f71;display:block;font-size:1.2rem;margin-bottom:.6rem;padding-left:12px}.checkbox{align-items:center;background:hsla(0,0%,100%,.65);border-radius:6px;box-shadow:0 1px 2px rgba(0,0,0,.05);display:flex;gap:10px;margin:4px 0;padding:6px 10px;transition:all .2s ease}.checkbox:hover{background:rgba(230,245,233,.9);transform:translateY(-1px)}.checkbox input[type=checkbox]{accent-color:#2e8048;cursor:pointer;transform:scale(1.1)}.checkbox label{color:#333;cursor:pointer;flex-grow:1;font-size:14px;line-height:1.3}.checkbox label .text-danger{font-size:.85em;margin-right:4px}.badge{border-radius:8px;font-size:.95rem;padding:.45em .75em}.footer{color:#555;font-size:13px;padding:1.5rem 0;text-align:center}#progressbar-container{margin-bottom:.75rem;margin-top:1rem}#progressbar-container .progress{border-radius:6px;height:1.3rem;overflow:hidden}#progressbar-container .progress-bar{font-size:.85rem;font-weight:500;transition:width .3s ease-in-out}.process-container{background:hsla(0,0%,100%,.85);border-radius:6px;font-size:.9rem;line-height:1.4;margin-top:.75rem;max-height:400px;overflow-y:auto;padding:.75rem 1rem}.process-container p{margin:.3rem 0}@keyframes rotate{to{transform:rotate(1turn)}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}.static-throbber{animation:rotate 2s linear infinite}.static-throbber .path{stroke-dasharray:1,150;stroke-dashoffset:0;stroke-linecap:round;animation:dash 1.5s ease-in-out infinite}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'no_use_template_for_routes' => array(
|
||||
'/abitur/profile/index.html' => '/abitur/profile/index.html',
|
||||
'/vsoko/survey/index.html' => '/vsoko/survey/index.html',
|
||||
)
|
||||
);
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
|
||||
ini_set('max_execution_time', '60');
|
||||
ini_set('default_socket_timeout', '60');
|
||||
|
||||
$locationOfVikonModules = '/'; //todo new_core_after del
|
||||
$domenName = 'www.ntspi.ru';
|
||||
$apiDomen = 'https://db-nica.ru/';
|
||||
$vikonDomainResolveBypass = 'db-nica.ru:443:62.76.112.192';
|
||||
$fmDomainResolveBypass = 'file.db-nica.ru:443:62.76.112.192';
|
||||
$apiDomainAuth = 'https://auth.db-nica.ru/';
|
||||
$filemanagerApiDomen = 'https://file.db-nica.ru/';
|
||||
$clientId = '542';
|
||||
$clientSecret = '0mmk80mv8zpk7h5uhjvjsmsskvpkn9nmkywgy83vwg7tpzdw0y7rao0k9pvmn8xxwj5mgw8554nhtq6s';
|
||||
$vuzId = '16775';
|
||||
$vuzName = 'Нижнетагильский государственный социально-педагогический институт (филиал) "Российского государственного профессионально-педагогического университета"';
|
||||
$modulesByPathDeploy = array (
|
||||
2 => 'abitur',
|
||||
1 => 'sveden',
|
||||
6 => 'vsoko',
|
||||
);
|
||||
$allowedFoldersInCoreByModule = array (
|
||||
2 =>
|
||||
array (
|
||||
0 => 'abitur',
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
0 => 'assets',
|
||||
1 => 'files_zaglushka',
|
||||
2 => 'common',
|
||||
3 => 'struct',
|
||||
4 => 'document',
|
||||
5 => 'education',
|
||||
6 => 'managers',
|
||||
7 => 'employees',
|
||||
8 => 'objects',
|
||||
9 => 'paid_edu',
|
||||
10 => 'budget',
|
||||
11 => 'vacant',
|
||||
12 => 'grants',
|
||||
13 => 'inter',
|
||||
14 => 'catering',
|
||||
15 => 'eduStandarts',
|
||||
16 => 'corruption',
|
||||
17 => 'antiterrorism',
|
||||
18 => 'files',
|
||||
19 => 'update',
|
||||
20 => 'index.html',
|
||||
21 => '.vikon',
|
||||
22 => '.htaccess',
|
||||
),
|
||||
6 =>
|
||||
array (
|
||||
0 => 'assets',
|
||||
1 => 'general',
|
||||
2 => 'structure',
|
||||
4 => 'faq',
|
||||
5 => 'procedures',
|
||||
6 => 'results-and-reports',
|
||||
7 => 'plans',
|
||||
8 => 'survey',
|
||||
9 => 'files',
|
||||
10 => '.vikon',
|
||||
11 => 'index.html',
|
||||
12 => '.htaccess',
|
||||
),
|
||||
);
|
||||
|
||||
define('DEBUG_MODE', isset($_GET['debug_mode']) && $_GET['debug_mode'] || isset($_POST['debug_mode']) && $_POST['debug_mode']);
|
||||
|
||||
define('IS_DOMAIN_RESOLVE', isset($_COOKIE['is_resolve_domain']) ? (int) $_COOKIE['is_resolve_domain'] : 0);
|
||||
define('FM_DOMAIN_RESOLVE_BYPASS', $fmDomainResolveBypass);
|
||||
define('VIKON_DOMAIN_RESOLVE_BYPASS', $vikonDomainResolveBypass);
|
||||
|
||||
define('SVEDEN', 1);
|
||||
define('ABITUR', 2);
|
||||
define('VSOKO', 6);
|
||||
|
||||
if (DEBUG_MODE === true) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors','on');
|
||||
} else {
|
||||
error_reporting(0);
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once 'path.php';
|
||||
|
||||
class Filesystem
|
||||
{
|
||||
public static function getExtension($path)
|
||||
{
|
||||
if ($path === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||
return strtolower($extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет безопасный scandir, убирает системные точки
|
||||
*
|
||||
* @param $path
|
||||
* @return array|false - возращает entries внутри сканируемой папки или false, в случае отсутсвия прав (или других причин)
|
||||
*/
|
||||
public static function safeScandir($path)
|
||||
{
|
||||
if (!is_dir($path) || !is_readable($path)) {
|
||||
return false;
|
||||
}
|
||||
$entries = scandir($path);
|
||||
if ($entries === false) {
|
||||
return false;
|
||||
}
|
||||
return array_diff($entries, Path::getSystemDots($path));
|
||||
}
|
||||
|
||||
public static function removeZip($path, $withCheckRights)
|
||||
{
|
||||
if (!Path::isActionAllowedThisPath($path)) {
|
||||
return false;
|
||||
}
|
||||
if (empty($path) || !file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($withCheckRights) {
|
||||
$checks = array(
|
||||
'is_file' => is_file($path),
|
||||
'is_zip' => self::getExtension($path) === 'zip',
|
||||
'is_writable' => is_writable($path)
|
||||
);
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if (!$check) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param bool $withRecursive
|
||||
* @param int|null $moduleId - Важный параметр который дает понимание в рамках какой папки мы работаем
|
||||
* Если null - работаем в рамках vikon_core, переданный moduleId - даст путь к модулю
|
||||
* @return bool
|
||||
*/
|
||||
public static function remove($path, $withRecursive, $moduleId = null)
|
||||
{
|
||||
if (!Path::isActionAllowedThisPath($path, $moduleId)) {
|
||||
return false;
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
if (!$withRecursive) {
|
||||
return rmdir($path);
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$entries = self::safeScandir($path);
|
||||
foreach ($entries as $entry) {
|
||||
$fullPath = Path::join($path, $entry);
|
||||
if (is_dir($fullPath) && !is_link($fullPath)) {
|
||||
if (!self::remove($fullPath, $withRecursive, $moduleId)) {
|
||||
$success = false;
|
||||
}
|
||||
} else {
|
||||
if (!unlink($fullPath)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
if (!rmdir($path)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Переносит и переименовывает папку и все содержимое рекурсивно
|
||||
* Перед использованием выполни Filesysyem::remove($targetDir)
|
||||
*
|
||||
* В случае с replaceWithRename перемещать мы можем в рамках vikon_core и в рамках модуля
|
||||
*/
|
||||
public static function replaceWithRename($sourceDir, $targetDir, $moduleId = null)
|
||||
{
|
||||
if ($moduleId === null) {
|
||||
if (!Path::isBasePath(Path::getCoreRootPath(), $sourceDir)) {
|
||||
return false;
|
||||
}
|
||||
if (!Path::isBasePath(Path::getCoreRootPath(), $targetDir)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!Path::isBasePath(Path::getModuleRootPath($moduleId), $sourceDir)
|
||||
&& !Path::isBasePath(Path::getCoreRootPath(), $sourceDir)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!Path::isBasePath(Path::getModuleRootPath($moduleId), $targetDir)
|
||||
&& !Path::isBasePath(Path::getCoreRootPath(), $targetDir)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!file_exists($sourceDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_exists($targetDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parentDir = dirname($targetDir);
|
||||
if (!is_writable($parentDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mkdir($targetDir, 0755, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = self::safeScandir($sourceDir);
|
||||
|
||||
if (empty($entries)) {
|
||||
return rmdir($sourceDir);
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$item = Path::join($sourceDir, $entry);
|
||||
$newPath = Path::join($targetDir, $entry);
|
||||
|
||||
if (is_dir($item)) {
|
||||
if (!self::replaceWithRename($item, $newPath, $moduleId)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!rename($item, $newPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($sourceDir);
|
||||
}
|
||||
|
||||
public static function safeRenameFile($oldPath, $newPath, $moduleId = null)
|
||||
{
|
||||
if ($moduleId === null) {
|
||||
if (!Path::isBasePath(Path::getCoreRootPath(), $oldPath)) {
|
||||
return false;
|
||||
}
|
||||
if (!Path::isBasePath(Path::getCoreRootPath(), $newPath)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!Path::isBasePath(Path::getModuleRootPath($moduleId), $oldPath)
|
||||
&& !Path::isBasePath(Path::getCoreRootPath(), $oldPath)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!Path::isBasePath(Path::getModuleRootPath($moduleId), $newPath)
|
||||
&& !Path::isBasePath(Path::getCoreRootPath(), $newPath)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!file_exists($oldPath)) {
|
||||
return false;
|
||||
}
|
||||
if (file_exists($newPath)) {
|
||||
return false;
|
||||
}
|
||||
$newDir = dirname($newPath);
|
||||
if (!file_exists($newDir)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_writable($newDir)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_writable($oldPath)) {
|
||||
return false;
|
||||
}
|
||||
$result = rename($oldPath, $newPath);
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $restorePath - путь к ядру модуля которое мы будем восстанавливать
|
||||
* @param array $allowedRestoreFolders - разрешенные к восстановлению папки,
|
||||
* т.е папки которые прилетели из архива, будем работать только с ними
|
||||
* @return bool
|
||||
*/
|
||||
public static function restoreUnitCoreAfterFail($restorePath, $allowedRestoreFolders, $moduleId = null)
|
||||
{
|
||||
if (!Path::isActionAllowedThisPath($restorePath, $moduleId)) {
|
||||
return false;
|
||||
}
|
||||
if (!file_exists($restorePath)) {
|
||||
return false;
|
||||
}
|
||||
$restoreEntries = self::safeScandir($restorePath);
|
||||
if ($restoreEntries === false) {
|
||||
return false;
|
||||
}
|
||||
foreach ($restoreEntries as $restoreEntry) {
|
||||
$fullPathRestore = Path::join($restorePath, $restoreEntry);
|
||||
$baseName = substr($restoreEntry, 0, -4);
|
||||
if (
|
||||
file_exists($fullPathRestore)
|
||||
&& substr($restoreEntry, -4) === Path::$n_pstfx
|
||||
&& in_array($baseName, $allowedRestoreFolders)
|
||||
) {
|
||||
self::remove($fullPathRestore, true, $moduleId);
|
||||
}
|
||||
|
||||
if (
|
||||
file_exists($fullPathRestore)
|
||||
&& substr($restoreEntry, -4) === Path::$o_pstfx
|
||||
&& in_array($baseName, $allowedRestoreFolders)
|
||||
) {
|
||||
$newPath = Path::join($restorePath, $baseName);
|
||||
|
||||
if (file_exists($newPath)) {
|
||||
self::remove($newPath, true, $moduleId);
|
||||
}
|
||||
|
||||
self::replaceWithRename($fullPathRestore, $newPath, $moduleId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $pathClean - Путь к ядру модуля или главному ядру, чтобы почистить его
|
||||
* @param $excludedEntries - папки и файлы которые не нужно чистить в ядре
|
||||
* @param int|null $moduleId - модуль id или false - когда проверяем ядро
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function cleanUnitCore($pathClean, $excludedEntries, $moduleId = null)
|
||||
{
|
||||
if (!Path::isActionAllowedThisPath($pathClean, $moduleId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = self::safeScandir($pathClean);
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$item = Path::join($pathClean, $entry);
|
||||
|
||||
if (in_array($entry, $excludedEntries)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($item) && !is_link($item)) {
|
||||
if (!self::remove($item, true, $moduleId)) {
|
||||
return $item;
|
||||
}
|
||||
} else {
|
||||
if (!unlink($item)) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function safeMkdir($path, $mode, $moduleId = null)
|
||||
{
|
||||
if (!Path::isActionAllowedThisPath($path, $moduleId)) {
|
||||
return false;
|
||||
}
|
||||
if (file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
$parentDir = dirname($path);
|
||||
if (!is_dir($parentDir)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_writable($parentDir)) {
|
||||
return false;
|
||||
}
|
||||
return mkdir($path, $mode);
|
||||
}
|
||||
|
||||
public static function safeMkfile($filename, $mode, $content = '')
|
||||
{
|
||||
if (file_exists($filename)) {
|
||||
return true;
|
||||
}
|
||||
$directory = dirname($filename);
|
||||
if (!is_writable($directory)) {
|
||||
return false;
|
||||
}
|
||||
if (file_put_contents($filename, $content) === false) {
|
||||
return false;
|
||||
}
|
||||
if (!chmod($filename, $mode)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function ensureValidDirectoryAndFileName($directory, $filename)
|
||||
{
|
||||
if ($directory !== null && (!is_string($directory) || !preg_match("/^[a-z]{3,4}$/", $directory))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_string($filename) || strpos($filename, '/') !== false || strpos($filename, "\\") !== false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
<?php
|
||||
function loadHeaders()
|
||||
{
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Content-type: application/json');
|
||||
}
|
||||
|
||||
function sendErrorResponse($code, $message)
|
||||
{
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $code,
|
||||
'message' => $message,
|
||||
);
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
die();
|
||||
}
|
||||
|
||||
function sendSuccessResponse($message)
|
||||
{
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'message' => $message,
|
||||
);
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
die();
|
||||
}
|
||||
|
||||
// unpackZip плохо проверяет может ли он распаковать в этой папке
|
||||
// перед использованием сделай Filesystem::remove($pathToUnpack) - чтобы убедиться что проблем не будет
|
||||
function unpackZip($fileName, $pathToUnpack)
|
||||
{
|
||||
$res = array();
|
||||
$res['success'] = false;
|
||||
$res['message'] = '';
|
||||
if($pathToUnpack) {
|
||||
if (extension_loaded('zip')) {
|
||||
$zip = new ZipArchive;
|
||||
if ($isOpenedZip = $zip->open($fileName)) {
|
||||
$zip->extractTo($pathToUnpack);
|
||||
$zip->close();
|
||||
$res['success'] = true;
|
||||
|
||||
} else {
|
||||
$res['message'] = 'Ошибка при распоковке файлов библиотеки ZIP:' . $isOpenedZip;
|
||||
}
|
||||
} else {
|
||||
require_once dirname(__FILE__) . '/../update/zip_helper.php';
|
||||
$archive = new PclZip($fileName);
|
||||
$unZipResult = $archive->extract(PCLZIP_OPT_PATH, $pathToUnpack,
|
||||
PCLZIP_CB_PRE_EXTRACT, 'preExtractCallback', PCLZIP_OPT_REPLACE_NEWER);
|
||||
if ($unZipResult == 0) {
|
||||
$res['message'] = 'Ошибка при распоковке файлов:' . $archive->errorInfo(true);
|
||||
} else {
|
||||
$res['success'] = true;
|
||||
}
|
||||
$archive->privCloseFd();
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
function filterAccessToken($token)
|
||||
{
|
||||
return filter_var($token, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^[a-z0-9_\-\.]+$/i')));
|
||||
}
|
||||
|
||||
function filterFileName($file)
|
||||
{
|
||||
return filter_var($file, FILTER_VALIDATE_REGEXP,
|
||||
array('options' => array('regexp' => '/^[a-z0-9\+\.\,\(\)\;\#\№\-\_\«\»\!\%\=\$\@\'\&\–]+$/i'))
|
||||
);
|
||||
}
|
||||
|
||||
function filterPath($file)
|
||||
{
|
||||
return filter_var($file, FILTER_VALIDATE_REGEXP,
|
||||
array('options' => array('regexp' => '/^[a-z0-9\.\-\_\–\/]+$/i'))
|
||||
);
|
||||
}
|
||||
|
||||
function filterEntry($entry)
|
||||
{
|
||||
return filter_var($entry, FILTER_VALIDATE_REGEXP,
|
||||
array('options' => array('regexp' => '/^\/(?:[a-zA-Zа-яА-Я0-9_\-\.]+(?:\/)?)+$/'))
|
||||
);
|
||||
}
|
||||
|
||||
function filterInt($number)
|
||||
{
|
||||
return filter_var($number, FILTER_VALIDATE_INT);
|
||||
}
|
||||
|
||||
function filterUrl($url)
|
||||
{
|
||||
return filter_var($url, FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
function filterVersion($version)
|
||||
{
|
||||
return filter_var($version, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^[0-9\.]+$/')));
|
||||
}
|
||||
|
||||
function filterPartName($part)
|
||||
{
|
||||
return filter_var($part, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^[a-z_\-]+$/i')));
|
||||
}
|
||||
|
||||
function filterLogin($str)
|
||||
{
|
||||
return filter_var($str, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
function filterPassword($str)
|
||||
{
|
||||
return filter_var($str, FILTER_SANITIZE_STRING);
|
||||
}
|
||||
|
||||
function setResponseCode($code, $reason = null) {
|
||||
$code = intval($code);
|
||||
|
||||
if (version_compare(phpversion(), '5.4', '>') && is_null($reason)) {
|
||||
http_response_code($code);
|
||||
} else {
|
||||
header(trim("HTTP/1.0 $code $reason"));
|
||||
}
|
||||
}
|
||||
|
||||
function preExtractCallback($preEvent, &$preHeader)
|
||||
{
|
||||
if (!is_dir($preHeader['filename'])) {
|
||||
if (strpos($preHeader['filename'], '.htaccess')) {
|
||||
return 0;
|
||||
}
|
||||
if (file_exists($preHeader['filename'])) {
|
||||
unlink($preHeader['filename']);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
class RemoteResult
|
||||
{
|
||||
public $curlHasError = true;
|
||||
public $curlErrorTxt = '';
|
||||
|
||||
public $code = 0;
|
||||
public $responseBody = '';
|
||||
}
|
||||
|
||||
function remoteRequest($url, $responseAsJson = true, $postFields = false, $headers = array())
|
||||
{
|
||||
$result = new RemoteResult();
|
||||
$ch = curl_init();
|
||||
if (IS_DOMAIN_RESOLVE === 1) {
|
||||
curl_setopt($ch, CURLOPT_RESOLVE, array(
|
||||
VIKON_DOMAIN_RESOLVE_BYPASS,
|
||||
FM_DOMAIN_RESOLVE_BYPASS,
|
||||
));
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
|
||||
if (is_array($postFields)) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
$postVars = "";
|
||||
foreach ($postFields as $key => $value) {
|
||||
$postVars .= $key . '=' . $value . '&';
|
||||
}
|
||||
$postVars = rtrim($postVars, '&');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postVars);
|
||||
}
|
||||
|
||||
if ($headers !== array()) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
$output = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch) > 0 || curl_error($ch)) {
|
||||
$result->curlErrorTxt .= ';' . '#:' . curl_errno($ch) . 'Error:' . curl_error($ch);
|
||||
} else{
|
||||
$result->curlHasError = false;
|
||||
|
||||
$curlInfo = curl_getinfo($ch);
|
||||
$result->code = (int) $curlInfo['http_code'];
|
||||
$result->responseBody = ($responseAsJson) ? json_decode($output) : $output;
|
||||
}
|
||||
curl_close($ch);
|
||||
return $result;
|
||||
}
|
||||
|
||||
function tryExtractFmErrorMessage($remoteResultFmWhereBodyIsJson, $prefix = '')
|
||||
{
|
||||
$out = '';
|
||||
if (property_exists($remoteResultFmWhereBodyIsJson->responseBody, 'error')) {
|
||||
$out = $out . $remoteResultFmWhereBodyIsJson->responseBody->error;
|
||||
}
|
||||
|
||||
if (
|
||||
property_exists($remoteResultFmWhereBodyIsJson->responseBody, 'messages')
|
||||
&& is_array($remoteResultFmWhereBodyIsJson->responseBody->messages)
|
||||
) {
|
||||
$message = version_compare(phpversion(), '8.0', '>=')
|
||||
? implode('; ', $remoteResultFmWhereBodyIsJson->responseBody->messages)
|
||||
: implode($remoteResultFmWhereBodyIsJson->responseBody->messages, '; ');
|
||||
$out = $out . $message;
|
||||
}
|
||||
|
||||
if ($out) {
|
||||
$out = $prefix . 'Файловый сервер: ' . $out;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
function moduleDirIsEmptyOrEx($moduleRootPath, $flagFileOfModulePath)
|
||||
{
|
||||
if (is_dir($moduleRootPath)) {
|
||||
if (count(Filesystem::safeScandir($moduleRootPath)) && !file_exists($flagFileOfModulePath)) {
|
||||
throw new Exception(
|
||||
'Ошибка при распаковке ядра модуля. Целевая директория: ' . $moduleRootPath . ' уже содержит данные. ' .
|
||||
'Для запуска обновления целевая директория должна быть пуста. ' .
|
||||
'Удалите или переместите существующие данные и повторите обновление.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
<?php
|
||||
|
||||
class Path
|
||||
{
|
||||
public static $vikonCoreFolder = 'vikon_core';
|
||||
public static $functionalFolder = 'update';
|
||||
public static $srcExecutor = 'src_executor.php';
|
||||
public static $executorFile = 'executor.php';
|
||||
private static $foldersCore = array();
|
||||
|
||||
private static $systemDots = array('.', '..');
|
||||
public static $n_pstfx = '_new';
|
||||
public static $o_pstfx = '_old';
|
||||
|
||||
/**
|
||||
* Инициализация статических путей
|
||||
* @param array $foldersCore
|
||||
*/
|
||||
public static function init($foldersCore)
|
||||
{
|
||||
self::$foldersCore = $foldersCore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path - Путь относительно которого необходимо вернуть систмные точки (., ..)
|
||||
* @return array
|
||||
*/
|
||||
public static function getSystemDots($path)
|
||||
{
|
||||
$systemDots = array();
|
||||
foreach (self::$systemDots as $systemDot) {
|
||||
$systemDots[] = $systemDot;
|
||||
$systemDots[] = $path.'/'.$systemDot;
|
||||
}
|
||||
return $systemDots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к главному ядру
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCoreRootPath()
|
||||
{
|
||||
return self::join(self::normalize(dirname(dirname(dirname(__FILE__)))), self::$vikonCoreFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возращает путь к функциональной папке главного ядра.
|
||||
* Функциональная папка - папка в рамках которой проихсодит синхронизация и обновление частей и ядер
|
||||
* vikon_core/update
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFunctionalPath()
|
||||
{
|
||||
return self::join(self::getCoreRootPath(), self::$functionalFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к папке модуля по его идентификатору
|
||||
*
|
||||
* @param string $moduleId
|
||||
* @return string
|
||||
*/
|
||||
public static function getModuleRootPath($moduleId)
|
||||
{
|
||||
$parentFolderCore = self::normalize(dirname(self::getCoreRootPath()));
|
||||
return self::join($parentFolderCore, self::$foldersCore[$moduleId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает абсолютный путь к папке версий
|
||||
* @return string
|
||||
*/
|
||||
public static function getTmpVersionPath()
|
||||
{
|
||||
return self::join(self::getCoreRootPath(), 'tmp', 'versions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $moduleId Идентификатор модуля (1, 2 или 6).
|
||||
* @return string|null Возвращает путь к директории модуля или null, если модуль не найден.
|
||||
*/
|
||||
public static function getFsPathByModule($moduleId)
|
||||
{
|
||||
return self::join(self::getModuleRootPath($moduleId), 'files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Возращает путь к исходному коду для синхронизации ядра
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSrcForExecutorPath()
|
||||
{
|
||||
return self::join(self::getFunctionalPath(), self::$srcExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает, является ли путь базовым для другого пути.
|
||||
*/
|
||||
public static function isBasePath($basePath, $ofPath)
|
||||
{
|
||||
$basePath = self::canonicalize($basePath);
|
||||
$ofPath = self::canonicalize($ofPath);
|
||||
|
||||
$basePath = rtrim($basePath, '/').'/';
|
||||
$ofPath = $ofPath.'/';
|
||||
|
||||
return 0 === strpos($ofPath, $basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Объединяет несколько частей пути в один канонический путь
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function join()
|
||||
{
|
||||
$paths = func_get_args();
|
||||
|
||||
$finalPath = null;
|
||||
$wasScheme = false;
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if ('' === $path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $finalPath) {
|
||||
$finalPath = $path;
|
||||
$wasScheme = (strpos($path, '://') !== false);
|
||||
continue;
|
||||
}
|
||||
|
||||
$lastChar = substr($finalPath, -1);
|
||||
if ('/' !== $lastChar && '\\' !== $lastChar) {
|
||||
$finalPath .= '/';
|
||||
}
|
||||
|
||||
if ($wasScheme) {
|
||||
$finalPath .= $path;
|
||||
} else {
|
||||
$finalPath .= ltrim($path, '/');
|
||||
}
|
||||
|
||||
$wasScheme = false;
|
||||
}
|
||||
|
||||
if ($finalPath === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::canonicalize($finalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует путь в канонический вид (убирает '.', '..', двойные слеши)
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private static function canonicalize($path)
|
||||
{
|
||||
$path = self::normalize($path);
|
||||
|
||||
$parts = explode('/', $path);
|
||||
$absolutes = array();
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ('' === $part || '.' === $part) {
|
||||
continue;
|
||||
}
|
||||
if ('..' === $part) {
|
||||
array_pop($absolutes);
|
||||
} else {
|
||||
$absolutes[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
$normalized = implode('/', $absolutes);
|
||||
|
||||
if ('/' === substr($path, 0, 1)) {
|
||||
$normalized = '/' . $normalized;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет возможность дальнейшей работы с заданным путем, дальнейшие действия должны проходить в рамках
|
||||
* базовых путей vikon_core, или путей к модулю ($moduleId)
|
||||
*
|
||||
* @param int|null $moduleId - null - vikon_core, или id модуля
|
||||
*/
|
||||
public static function isActionAllowedThisPath($path, $moduleId = null)
|
||||
{
|
||||
if ($moduleId === null && !Path::isBasePath(Path::getCoreRootPath(), $path)) {
|
||||
return false;
|
||||
}
|
||||
if ($moduleId && !Path::isBasePath(Path::getModuleRootPath($moduleId), $path)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормализует слеши в пути
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function normalize($path)
|
||||
{
|
||||
return str_replace('\\', '/', $path);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
$letters = 'ABCDEFGKIJKLMNPQRSTUVWXYZ23456789';
|
||||
$caplen = 4;
|
||||
$width = 120; $height = 20;
|
||||
$font = './../assets/fonts/comic.ttf';
|
||||
$fontsize = 14;
|
||||
|
||||
header('Content-type: image/png');
|
||||
|
||||
$im = imagecreatetruecolor($width, $height);
|
||||
imagesavealpha($im, true);
|
||||
$bg = imagecolorallocatealpha($im, 0, 0, 0, 127);
|
||||
imagefill($im, 0, 0, $bg);
|
||||
|
||||
$captcha = '';
|
||||
|
||||
for ($i = 0; $i < $caplen; $i++) {
|
||||
$captcha .= $letters[ rand(0, strlen($letters)-1) ];
|
||||
$x = ($width - 20) / $caplen * $i + 10;
|
||||
$x = rand($x, $x+4);
|
||||
$y = $height - ( ($height - $fontsize) / 2 );
|
||||
$curcolor = imagecolorallocate( $im, rand(0, 100), rand(0, 100), rand(0, 100) );
|
||||
$angle = rand(-25, 25);
|
||||
imagettftext($im, $fontsize, $angle, $x, $y, $curcolor, $font, $captcha[$i]);
|
||||
}
|
||||
|
||||
session_start();
|
||||
$_SESSION['captcha'] = $captcha;
|
||||
|
||||
imagepng($im);
|
||||
imagedestroy($im);
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$accessToken = filterAccessToken($_POST['access_token']);
|
||||
|
||||
$res = array();
|
||||
try {
|
||||
if (!$accessToken) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $accessToken);
|
||||
$response = remoteRequest($apiDomainAuth . 'api/profile_applicant/check_access_token', true, false, $headers);
|
||||
if ($response->code == 200) {
|
||||
$res = $response->responseBody;
|
||||
} else {
|
||||
$err = 'Не удалось соединиться с удаленным сервером';
|
||||
sendErrorResponse($response->code, $err);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
setResponseCode($e->getCode());
|
||||
$res['error'] = 'Ошибка при проверке токена. ' . $e->getMessage();
|
||||
}
|
||||
loadHeaders();
|
||||
echo json_encode($res);
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$res = array();
|
||||
$res['success'] = true;
|
||||
$res['errorID'] = array();
|
||||
$res['messages'] = array();
|
||||
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$subject = $_POST['subject'];
|
||||
$message = $_POST['message'];
|
||||
$consent = filter_var($_POST['consent'], FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if($consent != true){
|
||||
$res['success'] = false;
|
||||
$res['messages'][] = 'Не получено согласие на обработку персональных данных.';
|
||||
$res['errorID'][] = 'consent';
|
||||
}
|
||||
|
||||
$code = filter_var($_POST['captcha'], FILTER_SANITIZE_STRING);
|
||||
session_start();
|
||||
if (!isset($_SESSION['captcha']) || strtoupper(trim($_SESSION['captcha'])) != strtoupper(trim($code))) {
|
||||
$res['success'] = false;
|
||||
$res['errorID'][] = 'captcha';
|
||||
$res['messages'][] = 'Неверный код с картинки.';
|
||||
}
|
||||
unset($_SESSION['captcha']);
|
||||
|
||||
if ($res['success']) {
|
||||
$res['success'] = false;
|
||||
|
||||
$headers = array('Accept: application/json');
|
||||
$data = remoteRequest($apiDomen . 'oauth2/ClientCredentials', true,
|
||||
array(
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'grant_type' => 'client_credentials',
|
||||
), $headers
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
if (isset($data->responseBody)) {
|
||||
$accessToken = $data->responseBody->access_token;
|
||||
|
||||
try {
|
||||
$data = remoteRequest($apiDomen . 'oauth_via_app/feedbackSendMail?access_token='
|
||||
. $accessToken .
|
||||
'&name=' . urlencode($name) .
|
||||
'&email=' . urlencode($email) .
|
||||
'&subject=' . urlencode($subject) .
|
||||
'&message=' . urlencode($message)
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
$res['success'] = true;
|
||||
} else {
|
||||
sendErrorResponse($data->code, $data->responseBody->message);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$res['message'] = 'Ошибка. '.$e->getMessage();
|
||||
}
|
||||
|
||||
} else {
|
||||
$res['message'] = 'Ошибка ' . $data->responseBody->message;
|
||||
}
|
||||
} else {
|
||||
$res['message'] = $data->responseBody->message;
|
||||
}
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
|
||||
echo json_encode($res);
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$response = array(
|
||||
'client_id' => $clientId,
|
||||
'vuz_id' => $vuzId,
|
||||
'api_domain_auth' => $apiDomainAuth,
|
||||
'api_domain_vikon' => $apiDomen,
|
||||
'api_domain_filemanager' => $filemanagerApiDomen,
|
||||
'vuz_name' => $vuzName,
|
||||
);
|
||||
loadHeaders();
|
||||
echo json_encode($response);
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$login = filterLogin($_POST['login']);
|
||||
$password = filterPassword($_POST['password']);
|
||||
$code = filterInt($_POST['code']);
|
||||
|
||||
$res = array();
|
||||
try {
|
||||
if (!$login && !$password && !$code) {
|
||||
throw new RuntimeException('Переданы некорректные параметры');
|
||||
}
|
||||
$postFields = array(
|
||||
'grant_type' => 'password',
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'username' => $login,
|
||||
'password' => $password,
|
||||
'code' => $code,
|
||||
'scope' => '',
|
||||
);
|
||||
$response = remoteRequest($apiDomainAuth . 'oauth/token', true, $postFields, array(
|
||||
'App-Language: ' . isset($_POST['lang']) ? $_POST['lang'] : 'ru'
|
||||
));
|
||||
if ($response->code == 200) {
|
||||
setResponseCode($response->code);
|
||||
$res = $response->responseBody;
|
||||
} else {
|
||||
$err = 'Не удалось соединиться с удаленным сервером.';
|
||||
sendErrorResponse($response->code, $err);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
setResponseCode($e->getCode());
|
||||
$res['error'] = 'Ошибка при получении токена. ' . $e->getMessage();
|
||||
}
|
||||
loadHeaders();
|
||||
echo json_encode($res);
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$refreshToken = filterAccessToken($_POST['refresh_token']);
|
||||
|
||||
$res = array();
|
||||
try {
|
||||
if (!$refreshToken) {
|
||||
throw new RuntimeException('Некорректный refresh_token');
|
||||
}
|
||||
$postFields = array(
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'scope' => '',
|
||||
);
|
||||
$response = remoteRequest($apiDomainAuth . 'oauth/token', true, $postFields);
|
||||
if ($response->code == 200) {
|
||||
$res = $response->responseBody;
|
||||
} else {
|
||||
$err = 'Не удалось соединиться с удаленным сервером';
|
||||
sendErrorResponse($response->code, $err);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
setResponseCode($e->getCode());
|
||||
$res['error'] = 'Ошибка при обновлении токена. ' . $e->getMessage();
|
||||
}
|
||||
loadHeaders();
|
||||
echo json_encode($res);
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$res = array();
|
||||
$res['success'] = true;
|
||||
$res['errorID'] = array();
|
||||
$res['messages'] = array();
|
||||
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$message = $_POST['message'];
|
||||
$consent = filter_var($_POST['consent'], FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if($consent != true){
|
||||
$res['success'] = false;
|
||||
$res['messages'][] = 'Не получено согласие на обработку персональных данных.';
|
||||
$res['errorID'][] = 'consent';
|
||||
}
|
||||
|
||||
$code = filter_var($_POST['captcha'], FILTER_SANITIZE_STRING);
|
||||
session_start();
|
||||
if (!isset($_SESSION['captcha']) || strtoupper(trim($_SESSION['captcha'])) != strtoupper(trim($code))) {
|
||||
$res['success'] = false;
|
||||
$res['errorID'][] = 'captcha';
|
||||
$res['messages'][] = 'Неверный код с картинки.';
|
||||
}
|
||||
unset($_SESSION['captcha']);
|
||||
|
||||
if ($res['success']) {
|
||||
$res['success'] = false;
|
||||
|
||||
$headers = array('Accept: application/json');
|
||||
$data = remoteRequest($apiDomen . 'oauth2/ClientCredentials', true,
|
||||
array(
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'grant_type' => 'client_credentials',
|
||||
), $headers
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
if (isset($data->responseBody)) {
|
||||
$accessToken = $data->responseBody->access_token;
|
||||
|
||||
try {
|
||||
$data = remoteRequest($apiDomen . 'oauth-via-app/vsoko/feedbackSendMail?access_token='
|
||||
. $accessToken .
|
||||
'&name=' . urlencode($name) .
|
||||
'&email=' . urlencode($email) .
|
||||
'&message=' . urlencode($message)
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
$res['success'] = true;
|
||||
} else {
|
||||
sendErrorResponse($data->code, $data->responseBody->message);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$res['message'] = 'Ошибка. '.$e->getMessage();
|
||||
}
|
||||
|
||||
} else {
|
||||
$res['message'] = 'Ошибка ' . $data->responseBody->message;
|
||||
}
|
||||
} else {
|
||||
$res['message'] = $data->responseBody->message;
|
||||
}
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
|
||||
echo json_encode($res);
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
function isCurlExists()
|
||||
{
|
||||
if (in_array('curl', get_loaded_extensions())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function checkCurl($url, $postFields = false)
|
||||
{
|
||||
$error = '';
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
if (is_array($postFields)) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
$postVars = "";
|
||||
foreach ($postFields as $key => $value) {
|
||||
$postVars .= $key . '=' . $value . '&';
|
||||
}
|
||||
$postVars = rtrim($postVars, '&');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postVars);
|
||||
}
|
||||
curl_exec($ch);
|
||||
if (curl_errno($ch) > 0 || curl_error($ch)) {
|
||||
$error = 'Curl error code: ' . curl_errno($ch) . '. Error: ' . curl_error($ch);
|
||||
}
|
||||
curl_close($ch);
|
||||
return $error;
|
||||
}
|
||||
|
||||
if (!isCurlExists()) {
|
||||
echo '<p class="alert alert-danger">
|
||||
На Вашем сервере отсутствует расширение PHP для работы с cURL.<br>
|
||||
Вам следует обратиться в службу технической поддержки Вашего хостинг-провайдера с просьбой
|
||||
включить PHP расширение curl.
|
||||
</p>';
|
||||
} else {
|
||||
if (($err = checkCurl($apiDomen)) || ($err = checkCurl($apiDomen, array('post' => 42)))) {
|
||||
echo '<p class="alert alert-danger">
|
||||
В процессе тестирования соединения с сервером обновлений, PHP расширение cURL вернуло
|
||||
следующую ошибку:<br> ' . $err .
|
||||
'</p>';
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
if (!isset($_GET['entry']) && filterEntry($_GET['entry'])) {
|
||||
sendErrorResponse(422, 'Некорректный entry');
|
||||
}
|
||||
|
||||
$entry = $_GET['entry'];
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json');
|
||||
$result = remoteRequest($apiDomen . 'oauth2/checkEntryPoint?client_id=' . $clientId . '&entry_point=' . $entry, true, false, $headers);
|
||||
if ($result->curlHasError) {
|
||||
sendErrorResponse(500, 'Не удалось соединиться с сервером.' . $result->curlErrorTxt);
|
||||
} else {
|
||||
setResponseCode($result->code);
|
||||
}
|
||||
loadHeaders();
|
||||
echo json_encode(array('success' => $result->responseBody->success));
|
||||
|
||||
} catch (Exception $e) {
|
||||
sendErrorResponse(500, 'Не удалось проверить настройки безопасности.' . $e->getMessage());
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
$rootDir = dirname($updateDir);
|
||||
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
$errPrefix = 'Для запуска инструмента не удалось подтвердить подлинность вашего токена доступа. ';
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, $errPrefix . 'Токен доступа отсутствует или некорректен.');
|
||||
}
|
||||
$token = $_POST['access_token'];
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$res = remoteRequest($apiDomen . 'pull_updates/checkAccessJson', true, false, $headers);
|
||||
if ($res->curlHasError) {
|
||||
sendErrorResponse(500, 'Ошибка CURL при выполнении запроса авторизации. ' . $errPrefix . $res->curlErrorTxt);
|
||||
}
|
||||
if ($res->code !== 200) {
|
||||
$err = $errPrefix . (!empty($res->responseBody->message) ? $res->responseBody->message : 'Неизвестная ошибка.');
|
||||
sendErrorResponse($res->code, $err);
|
||||
}
|
||||
|
||||
Path::init($modulesByPathDeploy);
|
||||
$payload = array(
|
||||
'is_access_allowed' => true,
|
||||
'err' => null
|
||||
);
|
||||
$message = isWritableRecrusive($rootDir);
|
||||
if ($message) {
|
||||
$payload['is_access_allowed'] = false;
|
||||
$payload['err'] = $message;
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode(
|
||||
array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'payload' => $payload
|
||||
)
|
||||
);
|
||||
die();
|
||||
} catch (Exception $e) {
|
||||
sendErrorResponse(500, $e->getMessage());
|
||||
}
|
||||
|
||||
function isWritableRecrusive($dir)
|
||||
{
|
||||
$message = '';
|
||||
if (is_dir($dir)) {
|
||||
if (is_writable($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
$objectPath = Path::join($dir, $object);
|
||||
$message .= isWritableRecrusive($objectPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message .= 'Отсутствуют права на запись: "' . htmlspecialchars($dir) . '"<br>';
|
||||
}
|
||||
} else {
|
||||
if (!is_writable($dir)) {
|
||||
$message .= 'Отсутствуют права на запись: "' . htmlspecialchars($dir) . '"<br>';
|
||||
}
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
//todo new_core_after del
|
||||
|
||||
require_once dirname(__FILE__) . '/../internal/config.php';
|
||||
require_once dirname(__FILE__) . '/../internal/helper.php';
|
||||
require_once dirname(__FILE__) . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_GET['is_access_remove_old_scripts_update'])) {
|
||||
$isAccessRemoveOldScriptsUpdate = 0;
|
||||
}
|
||||
|
||||
if (!in_array((int) $_GET['is_access_remove_old_scripts_update'], array(0, 1))) {
|
||||
$isAccessRemoveOldScriptsUpdate = 0;
|
||||
}
|
||||
|
||||
$isAccessRemoveOldScriptsUpdate = (int) $_GET['is_access_remove_old_scripts_update'];
|
||||
|
||||
Path::init($modulesByPathDeploy);
|
||||
|
||||
$vikonRootPath = Path::getCoreRootPath();
|
||||
|
||||
//remove executor
|
||||
$executorPath = Path::join($vikonRootPath, Path::$executorFile);
|
||||
if (!Filesystem::remove($executorPath, false)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . $executorPath);
|
||||
}
|
||||
|
||||
//fail
|
||||
//vikon_core-latest
|
||||
$vikonCoreLatestPath = Path::join($vikonRootPath, 'vikon_core-latest');
|
||||
if (!Filesystem::remove($vikonCoreLatestPath, true)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . $vikonCoreLatestPath);
|
||||
}
|
||||
|
||||
//Удаляем старые скрипты в папке sveden/update
|
||||
if ($isAccessRemoveOldScriptsUpdate) {
|
||||
$svedenRoot = Path::getModuleRootPath(SVEDEN);
|
||||
if (!file_exists($svedenRoot)) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$svedenFuncPath = Path::join($svedenRoot, 'update');
|
||||
if (!file_exists($svedenFuncPath)) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$entriesFunc = Filesystem::safeScandir($svedenFuncPath);
|
||||
if (!$entriesFunc) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$excludeEntries = array('index.php');
|
||||
foreach ($entriesFunc as $entry) {
|
||||
if (in_array($entry, $excludeEntries)) {
|
||||
continue;
|
||||
}
|
||||
if (!Filesystem::remove(Path::join($svedenFuncPath, $entry), true, SVEDEN)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . Path::join($svedenFuncPath, $entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
|
||||
$res = array();
|
||||
$res['success'] = false;
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
try {
|
||||
$headers = array(
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $token = $_POST['access_token'],
|
||||
);
|
||||
|
||||
$response = remoteRequest($apiDomen . 'pull_updates/assist/updateEndedSuccessByNewCoreJson', true, array(), $headers);
|
||||
if (!$response->curlHasError) {
|
||||
if (200 === $response->code) {
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
);
|
||||
} else {
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $response->code,
|
||||
'message' => !empty($response->responseBody->message )
|
||||
? $response->responseBody->message
|
||||
: 'Неизвестная ошибка'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException( 'CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1 +0,0 @@
|
||||
5.76.10.1
|
||||
@@ -1,239 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_GET['access_token']) || !filterAccessToken($_GET['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!isset($_GET['module_id']) || !filterInt($_GET['module_id'])) {
|
||||
sendErrorResponse(422, 'Некорректный module_id');
|
||||
}
|
||||
$moduleId = (int)$_GET['module_id'];
|
||||
$token = $_GET['access_token'];
|
||||
Path::init($modulesByPathDeploy);
|
||||
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
sendErrorResponse(422, 'Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
$moduleFolder = $modulesByPathDeploy[$moduleId];
|
||||
$foldersNeedStay = $allowedFoldersInCoreByModule[$moduleId];
|
||||
|
||||
$moduleRootPath = Path::getModuleRootPath($moduleId);
|
||||
$flagFileOfModulePath = Path::join($moduleRootPath, '.vikon');
|
||||
|
||||
try {
|
||||
if ($moduleId === ABITUR) {
|
||||
$headers = array('Accept-Encoding: zip, gzip', 'Authorization: Bearer ' . $token);
|
||||
$zipModuleCore = remoteRequest(
|
||||
$apiDomen . 'pull_updates/generateEmptyModuleCore/' . $moduleId,
|
||||
false,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($zipModuleCore->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $zipModuleCore->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($zipModuleCore->code !== 200) {
|
||||
$zipModuleCore->responseBody = json_decode($zipModuleCore->responseBody);
|
||||
$err = 'Не удается скачать файл с обновлениями. ' . (!empty($zipModuleCore->responseBody->message)
|
||||
? $zipModuleCore->responseBody->message
|
||||
: 'Неизвестная ошибка');
|
||||
sendErrorResponse($zipModuleCore->code, $err);
|
||||
}
|
||||
|
||||
$zipModuleCore->responseBody = json_decode($zipModuleCore->responseBody);
|
||||
if ($zipModuleCore->responseBody->success === true) {
|
||||
moduleDirIsEmptyOrEx($moduleRootPath, $flagFileOfModulePath);
|
||||
|
||||
if (!Filesystem::safeMkdir($moduleRootPath, 0755, $moduleId)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Не удалось создать папку: ' . $moduleFolder;
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
$filesDirectory = Path::join($moduleRootPath, 'files');
|
||||
if (!Filesystem::safeMkdir($filesDirectory, 0755, $moduleId)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Не удалось создать папку: files';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
$msg = 'Ядро модуля "' . $moduleFolder . '" успешно обновлено и синхронизировано.';
|
||||
if (!Filesystem::safeMkfile($flagFileOfModulePath, 0755)) {
|
||||
throw new Exception('Не удалось записать служебную информацию в директорию: ' . $flagFileOfModulePath);
|
||||
}
|
||||
sendSuccessResponse($msg);
|
||||
}
|
||||
sendSuccessResponse('Неизвестная ошибка');
|
||||
}
|
||||
|
||||
$headers = array('Accept-Encoding: zip, gzip', 'Authorization: Bearer ' . $token);
|
||||
$zipModuleCore = remoteRequest(
|
||||
$apiDomen . 'pull_updates/generateEmptyModuleCore/' . $moduleId,
|
||||
false,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($zipModuleCore->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $zipModuleCore->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($zipModuleCore->code != 200) {
|
||||
$zipModuleCore->responseBody = json_decode($zipModuleCore->responseBody);
|
||||
$err = 'Не удается скачать файл с обновлениями. ' . (!empty($zipModuleCore->responseBody->message)
|
||||
? $zipModuleCore->responseBody->message
|
||||
: 'Неизвестная ошибка');
|
||||
sendErrorResponse($zipModuleCore->code, $err);
|
||||
}
|
||||
|
||||
moduleDirIsEmptyOrEx($moduleRootPath, $flagFileOfModulePath);
|
||||
|
||||
$funcPath = Path::getFunctionalPath();
|
||||
$filenameZip = Path::join($funcPath, $moduleFolder . '_core-latest.zip');
|
||||
|
||||
if (!Filesystem::removeZip($filenameZip, true)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Архив уже существует. Недостаточно прав для его удаления';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$dlHandler = fopen($filenameZip, 'w');
|
||||
if (!fwrite($dlHandler, $zipModuleCore->responseBody)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Не удалось записать архив. Проверьте права доступа и свободное место.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$unpackNewModuleCorePath = Path::join($funcPath, $moduleFolder);
|
||||
if (!Filesystem::remove($unpackNewModuleCorePath, true)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Временая директория уже существует и не может быть удалена.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
if (!unpackZip($filenameZip, $funcPath)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Проверьте права доступа и свободное место.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
Filesystem::removeZip($filenameZip, false);
|
||||
|
||||
// перед синхронизацией убедимся что папка модуля вообще существует
|
||||
if (!Filesystem::safeMkdir($moduleRootPath, 0755, $moduleId)) {
|
||||
$err = 'Ошибка обновлении ядра модуля. Не удалось создать папку модуля: ' . $moduleFolder;
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$filesDirectory = Path::join($moduleRootPath, 'files');
|
||||
if (!Filesystem::safeMkdir($filesDirectory, 0755, $moduleId)) {
|
||||
$err = 'Ошибка обновлении ядра модуля. Не удалось создать папку в модуле: files';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$entriesToSync = Filesystem::safeScandir($unpackNewModuleCorePath);
|
||||
$serviceInfo = array('.vikon');
|
||||
$failedEntryName = null;
|
||||
foreach ($entriesToSync as $entryName) {
|
||||
if (in_array($entryName, $serviceInfo)) {
|
||||
continue;
|
||||
}
|
||||
$currentEntryPath = Path::join($moduleRootPath, $entryName);
|
||||
$newEntryPath = Path::join($unpackNewModuleCorePath, $entryName);
|
||||
$isFile = is_file($newEntryPath);
|
||||
|
||||
if (file_exists($currentEntryPath)) {
|
||||
$newEntryPathWithNewPostfix = Path::join($moduleRootPath, $entryName . Path::$n_pstfx);
|
||||
// try del
|
||||
if (!Filesystem::remove($newEntryPathWithNewPostfix, true, $moduleId)) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPath, $newEntryPathWithNewPostfix, $moduleId)
|
||||
: Filesystem::safeRenameFile($newEntryPath, $newEntryPathWithNewPostfix, $moduleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
|
||||
$currentEntryPathWithOldPostfix = Path::join($moduleRootPath, $entryName . Path::$o_pstfx);
|
||||
// try del
|
||||
if (!Filesystem::remove($currentEntryPathWithOldPostfix, true, $moduleId)) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($currentEntryPath, $currentEntryPathWithOldPostfix, $moduleId)
|
||||
: Filesystem::safeRenameFile($currentEntryPath, $currentEntryPathWithOldPostfix, $moduleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPathWithNewPostfix, $currentEntryPath, $moduleId)
|
||||
: Filesystem::safeRenameFile($newEntryPathWithNewPostfix, $currentEntryPath, $moduleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPath, $currentEntryPath, $moduleId)
|
||||
: Filesystem::safeRenameFile($newEntryPath, $currentEntryPath, $moduleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($failedEntryName !== null) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($moduleRootPath, $entriesToSync, $moduleId)) {
|
||||
$err = 'Ошибка при синхронизации ядра модуля. Не удалось восстановить ядро после ошибки.';
|
||||
sendErrorResponse(500, $err);
|
||||
} else {
|
||||
$err = 'Ошибка при синхронизации ядра модуля. Не удалось синхронизировать папку/файл: ' . $failedEntryName;
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
}
|
||||
|
||||
Filesystem::remove($unpackNewModuleCorePath, true);
|
||||
|
||||
$successOrPath = Filesystem::cleanUnitCore($moduleRootPath, $foldersNeedStay, $moduleId);
|
||||
if (is_string($successOrPath)) {
|
||||
$err = 'Ошибка проверки целостности ядра модуля. Не удалось удалить папку: ' . $successOrPath;
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
if (!$successOrPath) {
|
||||
$err = 'Ошибка проверки целостности ядра модуля. Нет доступа к корневой папке ядра.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
//todo new_core_after
|
||||
if ($moduleId === SVEDEN) {
|
||||
$updateDirModuleCore = Path::join($moduleRootPath, 'update');
|
||||
$oldUpdateFile = Path::join($moduleRootPath, 'update', 'index.php');
|
||||
Filesystem::safeMkdir($updateDirModuleCore, 0755, $moduleId);
|
||||
Filesystem::safeMkfile($oldUpdateFile, 0755);
|
||||
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
|
||||
$redirectPath = $locationOfVikonModules . 'vikon_core/update/index.php';
|
||||
$redirectUrl = $protocol . $domenName . $redirectPath;
|
||||
|
||||
$indexContent = "<?php\n"
|
||||
. "header('Location: " . $redirectUrl . "');\n"
|
||||
. "exit;\n";
|
||||
file_put_contents($oldUpdateFile, $indexContent);
|
||||
}
|
||||
|
||||
if (!Filesystem::safeMkfile($flagFileOfModulePath, 0755)) {
|
||||
throw new Exception('Не удалось записать служебную информацию в директорию: ' . $flagFileOfModulePath);
|
||||
}
|
||||
|
||||
} catch (Exception $ex) {
|
||||
sendErrorResponse(500, $ex->getMessage());
|
||||
}
|
||||
sendSuccessResponse('Ядро модуля "' . $moduleFolder . '" успешно обновлено и синхронизировано.');
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
$res = array();
|
||||
$res['success'] = false;
|
||||
|
||||
$codeSuccess = isset($_POST['code']) && filterAccessToken($_POST['code']);
|
||||
$urlSuccess = isset($_POST['url']) && filterUrl($_POST['url']);
|
||||
|
||||
if (!$codeSuccess) {
|
||||
sendErrorResponse(422, 'Некорректный авторизационный код');
|
||||
}
|
||||
|
||||
if (!$urlSuccess) {
|
||||
sendErrorResponse(422, 'Некорректный url выполнения запроса');
|
||||
}
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json');
|
||||
$data = remoteRequest($apiDomen . 'oauth2/authorize/token', true, array(
|
||||
'code' => $_POST['code'],
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'redirect_uri' => $_POST['url'],
|
||||
'grant_type' => 'authorization_code',
|
||||
), $headers);
|
||||
|
||||
if ($data->curlHasError) {
|
||||
$res['message'] = 'Не удалось соединиться с сервером.' . $data->curlErrorTxt;
|
||||
setResponseCode(500);
|
||||
} else {
|
||||
if (isset($data->responseBody->access_token)) {
|
||||
$res['access_token'] = $data->responseBody->access_token;
|
||||
$res['refresh_token'] = $data->responseBody->refresh_token;
|
||||
$res['success'] = true;
|
||||
} else {
|
||||
$message = '';
|
||||
if (isset($data->responseBody->message)) {
|
||||
$message = $data->responseBody->message;
|
||||
} else {
|
||||
$message = 'Ошибка при получении токена.';
|
||||
}
|
||||
$res['message'] = $message;
|
||||
setResponseCode($data->code);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$res['message'] = 'Ошибка при получении ключа. ' . $e->getMessage();
|
||||
setResponseCode(500);
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
echo json_encode($res);
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_GET['module_id']) || !filterInt($_GET['module_id'])) {
|
||||
sendErrorResponse(422, 'Отсутствует идентификатор модуля или он указан некорректно');
|
||||
}
|
||||
$moduleId = (int) $_GET['module_id'];
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
sendErrorResponse(422, 'Не верно передан идентификатор модуля');
|
||||
}
|
||||
$tmpDir = Path::getTmpVersionPath();
|
||||
$result = null;
|
||||
|
||||
$filePath = Path::join($tmpDir, $moduleId . '.json');
|
||||
if (!file_exists($filePath) || !is_readable($filePath)) {
|
||||
sendErrorResponse(422, 'Не получить файл с версией');
|
||||
}
|
||||
$json = @file_get_contents($filePath);
|
||||
if ($json === false || $json === '') {
|
||||
sendErrorResponse(422, 'Не удалось получить версию');
|
||||
}
|
||||
$data = @json_decode($json, true);
|
||||
|
||||
loadHeaders();
|
||||
echo json_encode(array(
|
||||
'success' => true,
|
||||
'message' => '',
|
||||
'forward_code' => 200,
|
||||
'version' => isset($data['version']) ? htmlspecialchars($data['version']) : null
|
||||
));
|
||||
@@ -1,226 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
$currentVersion = file_get_contents($updateDir . '/cur_version.php');
|
||||
|
||||
$showNewCoreInfo = isset($_GET['first_use_new_core']) && $_GET['first_use_new_core'] == '1';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<title>Полуавтоматическое обновление VIKON</title>
|
||||
</head>
|
||||
<body class="vikon-wrapper">
|
||||
<input type="hidden" name="domen" value="<?php echo $domenName; ?>">
|
||||
<input type="hidden" name="api_domen" value="<?php echo $apiDomen; ?>">
|
||||
<input type="hidden" name="client_id" value="<?php echo $clientId; ?>">
|
||||
<input type="hidden" name="current_version" value="<?php echo $currentVersion; ?>">
|
||||
|
||||
<div class="wrapper container">
|
||||
|
||||
<div class="main-wrapper">
|
||||
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="header-logo vikon-logo" title="Vikon Logo">Vikon Logo</div>
|
||||
<div class="header-title">Полуавтоматическое обновление VIKON</div>
|
||||
</div>
|
||||
<hr>
|
||||
</header>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="form-group mb-4 d-flex justify-content-between">
|
||||
<div>
|
||||
<a href="/" class="btn btn-success">На главную</a>
|
||||
<a href="#" class="btn btn-success" id="exit" style="display: none;">Выход</a>
|
||||
</div>
|
||||
<a href="https://db-nica.ru/rukovodstvo/selectFaq/43/132" target="_blank" class="btn btn-success help-button">Помощь</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" id="message-container" style="display: none;"></div>
|
||||
|
||||
<div class="row" id="wait-load">
|
||||
<div class="text-center">
|
||||
<svg class="static-throbber" viewBox="0 0 50 50" style="width: 40px; height: 40px; margin-bottom: 10px;">
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5" stroke="#007bff" />
|
||||
</svg>
|
||||
идет загрузка страницы
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="no-enter" style="display: none;">
|
||||
<div class="col-sm-12 text-center">
|
||||
<h3>Требуется вход</h3>
|
||||
<div>
|
||||
<button type="button" class="btn btn-success mb-3" id="enter-vikon">
|
||||
Войти через VIKON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($showNewCoreInfo) { ?>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
Переход на новую систему обновления успешно завершен! Для корректной работы системы, пожалуйста, <b>выполните полное обновление всех модулей</b>.
|
||||
</p>
|
||||
<p class="text-danger">Внимание! Директории модулей (/sveden, /abitur) являются точками синхронизации данных системы. Все их содержимое будет автоматически синхронизировано (перезаписано) данными из системы. Пожалуйста, убедитесь, что в этих папках нет личной информации или файлов, не относящихся к данным VIKON, чтобы избежать их потери.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="row" id="yes-enter" style="display: none;">
|
||||
<div class="col-sm-6">
|
||||
<h3>Текущая версия системы: <span class="badge bg-secondary"><?php echo $currentVersion; ?></span></h3>
|
||||
<div class="settings-update" id="settings-update">
|
||||
<div id="modules-container">
|
||||
<!-- Модули будут вставлены здесь через JavaScript -->
|
||||
</div>
|
||||
|
||||
<b>Настройки п/а обновления:</b>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" name="is_resolve_domain" id="is_resolve_domain">
|
||||
<label for="is_resolve_domain">
|
||||
Использовать прямое подключение по IP-адресу к серверам VIKON
|
||||
</label>
|
||||
<span class="fas fa-question-circle"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Если в процессе работы у ПО не получается связаться с сервером (например, появляется сообщение об ошибке соединения), вы можете включить данную опцию. В этом режиме система будет подключаться напрямую к VIKON через IP-адрес.">
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="d-none admin-mode-panel" id="admin-mode-panel">
|
||||
<b>Дополнительные опции для администратора:</b>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" name="no_core" id="no_core" value="no_core">
|
||||
<label for="no_core">Не обновлять ядро</label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" name="debug_mode" id="debug_mode" value="debug_mode">
|
||||
<label for="debug_mode">Debug Mode</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<h3>Наличие обновления: <span class="badge bg-warning" id="new-version">получение информации...</span></h3>
|
||||
<div>
|
||||
<button type="button" class="btn btn-success" id="start-update">Начать обновление</button>
|
||||
</div>
|
||||
<p class="alert alert-success" id="update-complete" style="display: none;"></p>
|
||||
<p class="alert alert-danger" id="clear_tmp_error_layout" style="display: none;"></p>
|
||||
<div class="row" id="progressbar-container" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="progress">
|
||||
<div id="progressbar" class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
|
||||
style="width: 0;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info process-container" id="process-container" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php require_once $updateDir . '/check_curl.php'; ?>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<p class="copyright">
|
||||
Национальный фонд поддержки инноваций в сфере образования (НФПИ)
|
||||
<br>
|
||||
Copyright © 2013-<?php echo date('Y') ?>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const CURRENT_VERSION = "<?php echo $currentVersion; ?>";
|
||||
const ASSETS_BASE = "./../assets/";
|
||||
|
||||
let PATHNAME = window.location.pathname;
|
||||
PATHNAME = PATHNAME.replace(/\/+$/, '');
|
||||
PATHNAME = PATHNAME.replace(/\/index.php$/, '');
|
||||
|
||||
const getTargetContainer = (tag) => {
|
||||
return document.getElementsByTagName(tag)[0] || document.documentElement;
|
||||
};
|
||||
|
||||
const putStyle = (filename) => {
|
||||
const head = getTargetContainer('head');
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = `${PATHNAME}/${ASSETS_BASE}css/${filename}?v=${CURRENT_VERSION}`;
|
||||
|
||||
head.appendChild(link);
|
||||
};
|
||||
|
||||
const loadScriptsSequentially = (filenames, index = 0) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (index >= filenames.length) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const filename = filenames[index];
|
||||
const body = getTargetContainer('body');
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = `${PATHNAME}/${ASSETS_BASE}js/${filename}?v=${CURRENT_VERSION}`;
|
||||
|
||||
script.onload = () => {
|
||||
loadScriptsSequentially(filenames, index + 1).then(resolve).catch(reject);
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
loadScriptsSequentially(filenames, index + 1).then(resolve).catch(reject);
|
||||
};
|
||||
|
||||
body.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
const integrate = () => {
|
||||
putStyle('update.css');
|
||||
putStyle('vendor.css');
|
||||
|
||||
loadScriptsSequentially([
|
||||
'vendor.js',
|
||||
'update.js'
|
||||
]).catch((е) => {
|
||||
console.log('Не удалось динамически загрузить js-скрипты для работы полуавтоматического обновления')
|
||||
});
|
||||
};
|
||||
|
||||
integrate();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
$selfDir = dirname(__FILE__);
|
||||
function isWritableR($dir)
|
||||
{
|
||||
$message = '';
|
||||
if (is_dir($dir)) {
|
||||
if (is_writable($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
$message .= isWritableR($dir . DIRECTORY_SEPARATOR . $object);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message .= 'Отсутствуют права на запись: "' . $dir . '"<br>';
|
||||
}
|
||||
} else if (!is_writable($dir)) {
|
||||
$message .= 'Отсутствуют права на запись: "' . $dir . '"<br>';
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
$messageSveden = '';
|
||||
$root = dirname($selfDir);
|
||||
if (file_exists($root)) {
|
||||
$messageSveden = isWritableR($root);
|
||||
}
|
||||
|
||||
if ($messageSveden) {
|
||||
echo '<p class="alert alert-danger">' . $messageSveden . '</p>';
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_GET['access_token']) || !filterAccessToken($_GET['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
$token = $_GET['access_token'];
|
||||
|
||||
$dlHandler = null;
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$hasAccess = remoteRequest($apiDomen . 'pull_updates/checkAccessJson', true, false, $headers);
|
||||
if ($hasAccess->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $hasAccess->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($hasAccess->code !== 200) {
|
||||
$err = 'Не удается скачать файл с обновлениями. ' . (!empty($hasAccess->responseBody->message)
|
||||
? $hasAccess->responseBody->message
|
||||
: 'Неизвестная ошибка');
|
||||
sendErrorResponse($hasAccess->code, $err);
|
||||
}
|
||||
|
||||
if ($hasAccess->responseBody->success) {
|
||||
$vikonRootPath = Path::getCoreRootPath();
|
||||
$executorPath = Path::join($vikonRootPath, Path::$executorFile);
|
||||
|
||||
if (!Filesystem::remove($executorPath, false)) {
|
||||
sendErrorResponse(500, 'Ошибка при генерации ядра. Не удалось удалить устаревший исполняемый скрипт: ' . $executorPath);
|
||||
}
|
||||
|
||||
$srcForExecutorPath = Path::getSrcForExecutorPath();
|
||||
$executorCode = file_get_contents($srcForExecutorPath);
|
||||
if (!$executorCode || !Filesystem::safeMkfile($executorPath, 0755, $executorCode)) {
|
||||
sendErrorResponse(500, 'Ошибка при генерации ядра. Не удалось создать исполняемый скрипт: ' . $executorPath);
|
||||
}
|
||||
sendSuccessResponse('');
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
sendErrorResponse(500, $ex->getMessage());
|
||||
}
|
||||
if ($dlHandler) {
|
||||
fclose($dlHandler);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
$token = $_POST['access_token'];
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$res = remoteRequest($apiDomen . 'pull_updates/checkAccessJson', true, false, $headers);
|
||||
if ($res->curlHasError) {
|
||||
sendErrorResponse(500, 'CURL ' . $res->curlErrorTxt);
|
||||
}
|
||||
if ($res->code !== 200) {
|
||||
$err = 'Ошибка проверки подлинности токена. '
|
||||
. (!empty($zipCore->responseBody->message) ? $zipCore->responseBody->message : 'Неизвестная ошибка');
|
||||
sendErrorResponse($res->code, $err);
|
||||
}
|
||||
|
||||
$isAccessRemoveOldScriptsUpdate = 0;
|
||||
if (isset($_POST['is_access_remove_old_scripts_update']) && in_array((int) $_POST['is_access_remove_old_scripts_update'], array(0, 1))) {
|
||||
$isAccessRemoveOldScriptsUpdate = (int) $_POST['is_access_remove_old_scripts_update'];
|
||||
}
|
||||
|
||||
Path::init($modulesByPathDeploy);
|
||||
$vikonRootPath = Path::getCoreRootPath();
|
||||
|
||||
//remove executor
|
||||
$executorPath = Path::join($vikonRootPath, Path::$executorFile);
|
||||
if (!Filesystem::remove($executorPath, false)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . $executorPath);
|
||||
}
|
||||
|
||||
//fail
|
||||
$vikonCoreLatestPath = Path::join($vikonRootPath, 'vikon_core-latest');
|
||||
if (!Filesystem::remove($vikonCoreLatestPath, true)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . $vikonCoreLatestPath);
|
||||
}
|
||||
|
||||
//todo new_core_after
|
||||
//Удаляем старые скрипты в папке sveden/update
|
||||
if ($isAccessRemoveOldScriptsUpdate) {
|
||||
$svedenRoot = Path::getModuleRootPath(SVEDEN);
|
||||
if (!file_exists($svedenRoot)) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$svedenFuncPath = Path::join($svedenRoot, 'update');
|
||||
if (!file_exists($svedenFuncPath)) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$entriesFunc = Filesystem::safeScandir($svedenFuncPath);
|
||||
if (!$entriesFunc) {
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
}
|
||||
$excludeEntries = array('index.php');
|
||||
foreach ($entriesFunc as $entry) {
|
||||
if (in_array($entry, $excludeEntries)) {
|
||||
continue;
|
||||
}
|
||||
if (!Filesystem::remove(Path::join($svedenFuncPath, $entry), true, SVEDEN)) {
|
||||
sendErrorResponse(500, 'Ошибка при очистке ядра. Не удалось удалить исполняемый файл:' . Path::join($svedenFuncPath, $entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
sendSuccessResponse('Временные файлы успешно удалены.');
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
if (!isset($_POST['refresh_token']) || !filterAccessToken($_POST['refresh_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный code');
|
||||
}
|
||||
|
||||
$res = array();
|
||||
$res['success'] = false;
|
||||
$refreshToken = filterAccessToken($_POST['refresh_token']);
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json');
|
||||
$data = remoteRequest($apiDomen . 'oauth2/RefreshToken', true, array(
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'grant_type' => 'refresh_token',
|
||||
), $headers);
|
||||
|
||||
if ($data->curlHasError) {
|
||||
$res['message'] = 'Не удалось соединиться с сервером.' . $data->curlErrorTxt;
|
||||
setResponseCode(500);
|
||||
} else {
|
||||
if (isset($data->responseBody->access_token)) {
|
||||
$res['access_token'] = $data->responseBody->access_token;
|
||||
$res['refresh_token'] = $data->responseBody->refresh_token;
|
||||
$res['success'] = true;
|
||||
} else {
|
||||
$message = '';
|
||||
if (isset($data->responseBody->message)) {
|
||||
$message = $data->responseBody->message;
|
||||
} else {
|
||||
$message = 'Ошибка при получении токена.';
|
||||
}
|
||||
$res['message'] = $message;
|
||||
setResponseCode($data->code);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$res['message'] = 'Ошибка при обновлении ключа. ' . $e->getMessage();
|
||||
setResponseCode(500);
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
echo json_encode($res);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!isset($_POST['part']) || !filterPartName($_POST['part'])) {
|
||||
sendErrorResponse(422, 'Некорректный part');
|
||||
}
|
||||
|
||||
$token = $_POST['access_token'];
|
||||
$part = $_POST['part'];
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
|
||||
$post = array('part' => $part, 'is_new_core' => true);
|
||||
$response = remoteRequest($apiDomen . 'pull_updates/generatePartByNewCoreJson', true, $post, $headers);
|
||||
if (!$response->curlHasError) {
|
||||
if (200 === $response->code) {
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'operation_identity' => $response->responseBody->operation_identity,
|
||||
'ttl' => $response->responseBody->ttl,
|
||||
'forward_code' => 200,
|
||||
);
|
||||
} else {
|
||||
$err = !empty($response->responseBody->message) ? $response->responseBody->message : 'Неизвестная ошибка';
|
||||
sendErrorResponse($response->code, $err);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,120 +0,0 @@
|
||||
<?php
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/filesystem.php';
|
||||
|
||||
$token = filterAccessToken(isset($_POST['access_token']) ? (string) $_POST['access_token'] : '');
|
||||
$moduleId = isset($_POST['moduleId']) ? filterInt($_POST['moduleId']) : null;
|
||||
$dlHandler = null;
|
||||
|
||||
try {
|
||||
Path::init($modulesByPathDeploy);
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
throw new RuntimeException('Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest($filemanagerApiDomen . 'sync/getNewFileInfoByModule?moduleId=' . $moduleId, true, false, $headers);
|
||||
if ($response->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
|
||||
if (
|
||||
$response->code !== 200
|
||||
|| !property_exists($response->responseBody, 'file_name')
|
||||
|| !property_exists($response->responseBody, 'identity')
|
||||
|| !property_exists($response->responseBody, 'dir_name')
|
||||
) {
|
||||
$msg = 'Не удалось получить информацию о файле, котрый требуется загрузить '
|
||||
. tryExtractFmErrorMessage($response, '. ');
|
||||
sendErrorResponse($response->code, $msg);
|
||||
}
|
||||
|
||||
if ($response->responseBody->file_name === null && $response->responseBody->identity === null) {
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'done' => true,
|
||||
);
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
die();
|
||||
}
|
||||
|
||||
$fileIdentity = $response->responseBody->identity;
|
||||
$filename = $response->responseBody->file_name;
|
||||
$directory = $response->responseBody->dir_name;
|
||||
|
||||
if (!Filesystem::ensureValidDirectoryAndFileName($directory, $filename)) {
|
||||
throw new RuntimeException('Некорректные параметры сохранения: недопустимое имя директории или файла.');
|
||||
}
|
||||
|
||||
$headers = array('Accept-Encoding: zip, gzip', 'Authorization: Bearer ' . $token);
|
||||
$binFile = remoteRequest(
|
||||
$filemanagerApiDomen . 'sync/downloadFileBinary?identity=' . $fileIdentity,
|
||||
false,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($binFile->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $binFile->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($binFile->code !== 200) {
|
||||
$binFile->responseBody = json_decode($binFile->responseBody);
|
||||
$message = 'Не удается скачать файл ' . $filename . tryExtractFmErrorMessage($binFile, '. ');
|
||||
sendErrorResponse($binFile->code, $message);
|
||||
}
|
||||
|
||||
$fsPathDir = Path::getFsPathByModule($moduleId);
|
||||
if ($directory !== null) {
|
||||
$fsPathDir = Path::join($fsPathDir, $directory);
|
||||
}
|
||||
|
||||
if (!Filesystem::safeMkdir($fsPathDir, 0775, $moduleId)) {
|
||||
throw new RuntimeException('Не удалось создать папку "' . $fsPathDir . '" на вашем сервере');
|
||||
}
|
||||
|
||||
$fsFilePath = Path::join($fsPathDir, $filename);
|
||||
$dlHandler = fopen($fsFilePath, 'w');
|
||||
if ($dlHandler == false || !fwrite($dlHandler, $binFile->responseBody)) {
|
||||
throw new RuntimeException('Не удается записать файл ' . $fsFilePath . ' на диск', 500);
|
||||
}
|
||||
|
||||
$headers = array('Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest(
|
||||
$filemanagerApiDomen . 'sync/markNewFileAsLoaded?identity=' . $fileIdentity . '&moduleId=' . $moduleId,
|
||||
true,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($response->code !== 200) {
|
||||
$message = 'Не удалось пометить файл' . $filename . ' как обновленный'
|
||||
. tryExtractFmErrorMessage($response, '. ');
|
||||
sendErrorResponse($response->code, $message);
|
||||
}
|
||||
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'message' => 'Файл ' . $filename . ' загружен',
|
||||
'done' => false,
|
||||
);
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
if ($dlHandler) {
|
||||
fclose($dlHandler);
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,131 +0,0 @@
|
||||
<?php
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/filesystem.php';
|
||||
|
||||
$token = filterAccessToken(isset($_POST['access_token']) ? (string) $_POST['access_token'] : '');
|
||||
|
||||
$fileIdentity = isset($_POST['fileIdentity']) && is_scalar($_POST['fileIdentity']) ? $_POST['fileIdentity'] : null;
|
||||
$moduleId = isset($_POST['moduleId']) ? filterInt($_POST['moduleId']) : null;
|
||||
$dlHandler = null;
|
||||
Path::init($modulesByPathDeploy);
|
||||
|
||||
try {
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
throw new RuntimeException('Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!$fileIdentity) {
|
||||
throw new RuntimeException('Передан невалидный идентификатор файла');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest(
|
||||
$filemanagerApiDomen . 'sync/getFileByIdentityInfo?identity=' . $fileIdentity,
|
||||
true,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($response->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
|
||||
if (
|
||||
$response->code !== 200
|
||||
|| !property_exists($response->responseBody, 'file_name')
|
||||
|| !property_exists($response->responseBody, 'identity')
|
||||
|| !property_exists($response->responseBody, 'dir_name')
|
||||
) {
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $response->code == 200 ? 500 : $response->code,
|
||||
'message' => (int) $response->code === 404
|
||||
? 'Не удалось получить информацию об одном из синхронизируемых файлов. Файл был удален из системы после запуска процесса синхронизации. Перезапустите синхронизацию файлов.'
|
||||
: 'Не удалось получить информацию о файле, котрый требуется загрузить ' . tryExtractFmErrorMessage($response, '. '),
|
||||
'debug_identity' => $fileIdentity,
|
||||
);
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
die();
|
||||
}
|
||||
|
||||
$identity = $response->responseBody->identity;
|
||||
$filename = $response->responseBody->file_name;
|
||||
$directory = $response->responseBody->dir_name;
|
||||
|
||||
if (!Filesystem::ensureValidDirectoryAndFileName($directory, $filename)) {
|
||||
throw new RuntimeException('Некорректные параметры сохранения: недопустимое имя директории или файла.');
|
||||
}
|
||||
|
||||
$headers = array('Accept-Encoding: zip, gzip', 'Authorization: Bearer ' . $token);
|
||||
$bin = remoteRequest(
|
||||
$filemanagerApiDomen . 'sync/downloadFileBinaryForSync?identity=' . $identity . '&moduleId=' . $moduleId,
|
||||
false,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if ($bin->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $bin->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($bin->code !== 200) {
|
||||
$bin->responseBody = json_decode($bin->responseBody);
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $bin->code,
|
||||
'message' => 'Не удается скачать файл ' . $filename . tryExtractFmErrorMessage($bin, '. '),
|
||||
'debug_identity' => $fileIdentity,
|
||||
);
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
die();
|
||||
}
|
||||
|
||||
$fsPathDir = Path::getFsPathByModule($moduleId);
|
||||
if (!$fsPathDir) {
|
||||
throw new RuntimeException('Неизвестная ошибка');
|
||||
}
|
||||
|
||||
if ($directory !== null) {
|
||||
$fsPathDir = Path::join($fsPathDir, $directory);
|
||||
}
|
||||
|
||||
if (!Filesystem::safeMkdir($fsPathDir, 0775, $moduleId)) {
|
||||
throw new RuntimeException('Не удалось создать папку "' . $fsPathDir . '" на вашем сервере');
|
||||
}
|
||||
|
||||
$filePath = Path::join($fsPathDir, $filename);
|
||||
|
||||
$dlHandler = fopen($filePath, 'w');
|
||||
if ($dlHandler === false) {
|
||||
throw new RuntimeException('Не удается открыть файл для записи: ' . $filePath);
|
||||
}
|
||||
|
||||
if (fwrite($dlHandler, $bin->responseBody) === false) {
|
||||
throw new RuntimeException('Не удается записать файл на диск: ' . $filePath);
|
||||
}
|
||||
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'message' => 'Файл ' . $filename . ' загружен',
|
||||
);
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
if ($dlHandler) {
|
||||
fclose($dlHandler);
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!isset($_POST['operation_identity']) || !filter_var($_POST['operation_identity'], FILTER_SANITIZE_STRING)) {
|
||||
sendErrorResponse(422, 'Некорректный operation_identity');
|
||||
}
|
||||
|
||||
if (!isset($_POST['part']) || !filterPartName($_POST['part'])) {
|
||||
sendErrorResponse(422, 'Некорректный part');
|
||||
}
|
||||
|
||||
$token = $_POST['access_token'];
|
||||
$operationIdentity = (string) $_POST['operation_identity'];
|
||||
$part = $_POST['part'];
|
||||
|
||||
try {
|
||||
$headers = array(
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $token,
|
||||
);
|
||||
|
||||
$response = remoteRequest(
|
||||
$apiDomen . 'pull_updates/checkPartGenerationByNewCoreResultJson?operation_identity=' . $operationIdentity . '&part=' . $part,
|
||||
true,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if (!$response->curlHasError) {
|
||||
if ($response->code === 200) {
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
);
|
||||
} else {
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $response->code,
|
||||
'message' => !empty($response->responseBody->message ) ? $response->responseBody->message : 'Неизвестная ошибка'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,229 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
if (!isset($_POST['access_token']) || !filterAccessToken($_POST['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!isset($_POST['operation_identity']) || !filter_var($_POST['operation_identity'], FILTER_SANITIZE_STRING)) {
|
||||
sendErrorResponse(422, 'Некорректный operation_identity');
|
||||
}
|
||||
|
||||
if (!isset($_POST['part']) || !filterPartName($_POST['part'])) {
|
||||
sendErrorResponse(422, 'Некорректный part');
|
||||
}
|
||||
|
||||
$dlHandler = null;
|
||||
$token = $_POST['access_token'];
|
||||
$operationIdentity = (string)$_POST['operation_identity'];
|
||||
$part = $_POST['part'];
|
||||
Path::init($modulesByPathDeploy);
|
||||
try {
|
||||
$headers = array(
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Accept-Encoding: zip, gzip'
|
||||
);
|
||||
|
||||
$response = remoteRequest(
|
||||
$apiDomen . 'pull_updates/downloadPartByNewCoreResult?operation_identity=' . $operationIdentity . '&part=' . $part,
|
||||
false,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
|
||||
if (!$response->curlHasError) {
|
||||
if ($response->code === 200) {
|
||||
$curModuleId = 0;
|
||||
foreach ($allowedFoldersInCoreByModule as $moduleId => $parts) {
|
||||
if (is_array($parts) && in_array($part, $parts)) {
|
||||
$curModuleId = $moduleId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$funcPath = Path::getFunctionalPath();
|
||||
$filenameZip = Path::join($funcPath, $operationIdentity . '.zip');
|
||||
|
||||
if (!Filesystem::removeZip($filenameZip, true)) {
|
||||
$err = 'Ошибка при распаковке раздела: "' . $part . '".'
|
||||
. ' Базовый архив раздела уже существует, недостаточно прав для его удаления.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$dlHandler = fopen($filenameZip, 'w');
|
||||
if (!fwrite($dlHandler, $response->responseBody)) {
|
||||
$err = 'Ошибка при распаковке раздела: "' . $part . '".'
|
||||
. ' Не удалось записать архив. Проверьте права доступа и свободное место.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$unpackPath = Path::join($funcPath, $part.'_part-latest');
|
||||
if (!Filesystem::remove($unpackPath, true)) {
|
||||
$err = 'Ошибка при распаковке раздела: "' . $part . '".'
|
||||
. ' Временная директория уже существует и не может быть удалена.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
if (!unpackZip($filenameZip, $unpackPath)) {
|
||||
$err = 'Ошибка при распаковке раздела: ' . $part . '.'
|
||||
. ' Проверьте права доступа и свободное место.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
Filesystem::removeZip($filenameZip, false);
|
||||
|
||||
$errRepeir = 'Ошибка при синхронизации раздела. Не удалось восстановить часть: ' . $part;
|
||||
$errSync = 'Ошибка при синхронизации раздела. Не удалось синхронизировать часть: ' . $part;
|
||||
|
||||
$rootModuleCore = Path::getModuleRootPath($curModuleId);
|
||||
$pathCurFolderPart = Path::join($rootModuleCore, $part);
|
||||
$excludedEntries = array();
|
||||
if ('abitur' !== $part) {
|
||||
$excludedEntries = $allowedFoldersInCoreByModule[$curModuleId];
|
||||
$pathCurFolderPart = Path::join($rootModuleCore, $part);
|
||||
if (file_exists($pathCurFolderPart)) {
|
||||
$pathPartForSync = Path::join($unpackPath, $part);
|
||||
$pathPartForSyncPostfixNew = Path::join($rootModuleCore, $part.Path::$n_pstfx);
|
||||
|
||||
if (!Filesystem::remove($pathPartForSyncPostfixNew, true, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
}
|
||||
if (!Filesystem::replaceWithRename($pathPartForSync, $pathPartForSyncPostfixNew, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
sendErrorResponse(500, $errSync);
|
||||
}
|
||||
|
||||
$pathCurFolderPartOldPostfix = Path::join($rootModuleCore, $part.Path::$o_pstfx);
|
||||
if (!Filesystem::remove($pathCurFolderPartOldPostfix, true, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
sendErrorResponse(500, $errSync);
|
||||
}
|
||||
if (!Filesystem::replaceWithRename($pathCurFolderPart, $pathCurFolderPartOldPostfix, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
sendErrorResponse(500, $errSync);
|
||||
}
|
||||
|
||||
if (!Filesystem::replaceWithRename($pathPartForSyncPostfixNew, $pathCurFolderPart, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
sendErrorResponse(500, $errSync);
|
||||
}
|
||||
} else {
|
||||
$pathPartForSync = Path::join($unpackPath, $part);
|
||||
if (!Filesystem::replaceWithRename($pathPartForSync, $pathCurFolderPart, $curModuleId)) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($rootModuleCore, array($part), $curModuleId)) {
|
||||
sendErrorResponse(500, $errRepeir);
|
||||
}
|
||||
sendErrorResponse(500, $errSync);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$pathCurFolderPart = $rootModuleCore;
|
||||
$entrySyncFail = null;
|
||||
$isRestoreFail = false;
|
||||
$entriesToSync = Filesystem::safeScandir(Path::join($unpackPath, $part));
|
||||
$excludedEntries = array_merge($entriesToSync, array('files', '.htaccess'));//todo подумать как от этого костыля отказаться
|
||||
foreach ($entriesToSync as $entryName) {
|
||||
$currentEntryPath = Path::join($pathCurFolderPart, $entryName);
|
||||
$newEntryPath = Path::join($unpackPath, $part, $entryName);
|
||||
$isFile = is_file($newEntryPath);
|
||||
|
||||
if (file_exists($currentEntryPath)) {
|
||||
$newEntryPathWithNewPostfix = Path::join($pathCurFolderPart, $entryName.Path::$n_pstfx);
|
||||
// try del
|
||||
if (!Filesystem::remove($newEntryPathWithNewPostfix, true, $curModuleId)) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPath, $newEntryPathWithNewPostfix, $curModuleId)
|
||||
: Filesystem::safeRenameFile($newEntryPath, $newEntryPathWithNewPostfix, $curModuleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
$currentEntryPathWithOldPostfix = Path::join($pathCurFolderPart, $entryName.Path::$o_pstfx);
|
||||
// try del
|
||||
if (!Filesystem::remove($currentEntryPathWithOldPostfix, true, $curModuleId)) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($currentEntryPath, $currentEntryPathWithOldPostfix, $curModuleId)
|
||||
: Filesystem::safeRenameFile($currentEntryPath, $currentEntryPathWithOldPostfix, $curModuleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPathWithNewPostfix, $currentEntryPath, $curModuleId)
|
||||
: Filesystem::safeRenameFile($newEntryPathWithNewPostfix, $currentEntryPath, $curModuleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// try replace
|
||||
$renameSuccess = !$isFile
|
||||
? Filesystem::replaceWithRename($newEntryPath, $currentEntryPath, $curModuleId)
|
||||
: Filesystem::safeRenameFile($newEntryPath, $currentEntryPath, $curModuleId);
|
||||
if (!$renameSuccess) {
|
||||
$failedEntryName = $entryName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filesystem::remove($unpackPath, true);
|
||||
|
||||
$successOrPath = Filesystem::cleanUnitCore($rootModuleCore, $excludedEntries, $curModuleId);
|
||||
if (is_string($successOrPath)) {
|
||||
$err = 'Ошибка проверки целостности части. Не удалось удалить папку: ' . $successOrPath;
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
if (!$successOrPath) {
|
||||
$err = 'Ошибка проверки целостности части. Нет доступа к корневой папке части.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
$resultBody = array('success' => true, 'forward_code' => 200, 'message' => 'Раздел ' . $part . ' успешно обновлен.');
|
||||
} else {
|
||||
$response->responseBody = json_decode($response->responseBody);
|
||||
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $response->code,
|
||||
'message' => !empty($response->responseBody->message ) ? $response->responseBody->message : 'неизвестная ошибка'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
if ($dlHandler != null) {
|
||||
fclose($dlHandler);
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,132 +0,0 @@
|
||||
<?php
|
||||
//скрипт запускается оносительно vikon_core
|
||||
$vikonDir = dirname(__FILE__);
|
||||
require_once $vikonDir . '/internal/config.php';
|
||||
require_once $vikonDir . '/internal/helper.php';
|
||||
require_once $vikonDir . '/internal/filesystem.php';
|
||||
|
||||
if (!isset($_GET['access_token']) || !filterAccessToken($_GET['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
$token = $_GET['access_token'];
|
||||
|
||||
$dlHandler = null;
|
||||
|
||||
try {
|
||||
$headers = array('Accept-Encoding: zip, gzip', 'Authorization: Bearer ' . $token);
|
||||
$zipCore = remoteRequest($apiDomen . 'pull_updates/generateEmptyCore', false, false, $headers);
|
||||
if ($zipCore->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $zipCore->curlErrorTxt);
|
||||
}
|
||||
|
||||
if ($zipCore->code !== 200) {
|
||||
$zipCore->responseBody = json_decode($zipCore->responseBody);
|
||||
|
||||
$err = 'Не удается скачать файл с обновлениями. ' . (!empty($zipCore->responseBody->message)
|
||||
? $zipCore->responseBody->message
|
||||
: 'Неизвестная ошибка');
|
||||
sendErrorResponse($zipCore->code, $err);
|
||||
}
|
||||
|
||||
$vikonFuncPath = Path::getCoreRootPath();
|
||||
$vikonZipCorePath = Path::join($vikonFuncPath, 'vikon_core-latest.zip');
|
||||
|
||||
if (!Filesystem::removeZip($vikonZipCorePath, true)) {
|
||||
$err = 'Ошибка при распаковке ядра. Архив уже существует. Недостаточно прав для его удаления';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$dlHandler = fopen($vikonZipCorePath, 'w');
|
||||
if (!fwrite($dlHandler, $zipCore->responseBody)) {
|
||||
$err = 'Ошибка при распаковке ядра модуля. Не удалось записать архив. Проверьте права доступа и свободное место.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
if ($dlHandler) {
|
||||
fclose($dlHandler);
|
||||
}
|
||||
|
||||
$vikonUnpackNewCorePath = Path::join($vikonFuncPath, 'vikon_core-latest');
|
||||
|
||||
if (!Filesystem::remove($vikonUnpackNewCorePath, true)) {
|
||||
$err = 'Ошибка при распаковке ядра. Временная директория уже существует и не может быть удалена.';
|
||||
sendErrorResponse(500, $err);
|
||||
}
|
||||
|
||||
$unpackResult = unpackZip($vikonZipCorePath, $vikonUnpackNewCorePath);
|
||||
if (!$unpackResult['success']) {
|
||||
sendErrorResponse(500, 'Ошибка при распаковке ядра. Проверьте права доступа и свободное место.');
|
||||
}
|
||||
Filesystem::removeZip($vikonZipCorePath, false);
|
||||
|
||||
$vikonRootPath = Path::getCoreRootPath();
|
||||
|
||||
$pathToFoldersNewCore = Path::join($vikonUnpackNewCorePath, Path::$vikonCoreFolder);
|
||||
$nameFoldersForSync = Filesystem::safeScandir($pathToFoldersNewCore);
|
||||
$isRestoreFail = false;
|
||||
$folderSyncFail = null;
|
||||
foreach ($nameFoldersForSync as $nameFolderSync) {
|
||||
$curFolder = Path::join($vikonRootPath, $nameFolderSync);
|
||||
$pathToNewFolder = Path::join($vikonUnpackNewCorePath, Path::$vikonCoreFolder, $nameFolderSync);
|
||||
|
||||
if (file_exists($curFolder)) {
|
||||
$pathToNewFolderPostfix = Path::join($vikonRootPath, $nameFolderSync . Path::$n_pstfx);
|
||||
// try del vikon_core/assets_new
|
||||
if (!Filesystem::remove($pathToNewFolderPostfix, true)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
// try replace vikon_core/vikon_core-latest/vikon_core/assets -> vikon_core/assets_new
|
||||
if (!Filesystem::replaceWithRename($pathToNewFolder, $pathToNewFolderPostfix)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
|
||||
$pathToOldFolderPostfix = Path::join($vikonRootPath, $nameFolderSync . Path::$o_pstfx);
|
||||
// try del vikon_core/assets_old
|
||||
if (!Filesystem::remove($pathToOldFolderPostfix, true)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
// try replace vikon_core/assets -> vikon_core/assets_old
|
||||
if (!Filesystem::replaceWithRename($curFolder, $pathToOldFolderPostfix)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
|
||||
// vikon_core/assets_new -> vikon_core/assets
|
||||
if (!Filesystem::replaceWithRename($pathToNewFolderPostfix, $curFolder)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// try replace vikon_core/vikon_core-latest/vikon_core/assets -> vikon_core/assets
|
||||
if (!Filesystem::replaceWithRename($pathToNewFolder, $curFolder)) {
|
||||
$folderSyncFail = $nameFolderSync;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//синхронизированные папки + временная папка с версиями
|
||||
$allFoldersNeedCore = array_merge($nameFoldersForSync, array('tmp'));
|
||||
if ($folderSyncFail !== null) {
|
||||
if (!Filesystem::restoreUnitCoreAfterFail($vikonRootPath, $allFoldersNeedCore)) {
|
||||
sendErrorResponse(500, 'Ошибка при синхронизации ядра. Не удалось восстановить ядро после ошибки.');
|
||||
} else {
|
||||
sendErrorResponse(500, 'Ошибка при синхронизации ядра. Не удалось синхронизировать папку: ' . $folderSyncFail);
|
||||
}
|
||||
}
|
||||
|
||||
$successOrPath = Filesystem::cleanUnitCore($vikonRootPath, $allFoldersNeedCore);
|
||||
if (is_string($successOrPath)) {
|
||||
sendErrorResponse(500, 'Ошибка проверки целостности ядра. Не удалось удалить папку: ' . $successOrPath);
|
||||
}
|
||||
if (!$successOrPath) {
|
||||
sendErrorResponse(500, 'Ошибка проверки целостности ядра. Нет доступа к корневой папке ядра.');
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
sendErrorResponse(500, $ex->getMessage());
|
||||
}
|
||||
|
||||
sendSuccessResponse('Архив с базовыми обновлениями успешно загружен.');
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/filesystem.php';
|
||||
|
||||
$token = filterAccessToken(isset($_POST['access_token']) ? (string) $_POST['access_token'] : '');
|
||||
$moduleId = isset($_POST['moduleId']) ? filterInt($_POST['moduleId']) : null;
|
||||
$dlHandler = null;
|
||||
Path::init($modulesByPathDeploy);
|
||||
try {
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
throw new RuntimeException('Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest($filemanagerApiDomen . 'sync/getUsedDirNamesByModule?moduleId=' . $moduleId, true, false, $headers);
|
||||
|
||||
if ($response->code !== 200) {
|
||||
$msg = 'Не удалось инициировать процедуру синхронизации файлов' . tryExtractFmErrorMessage($response, '. ');
|
||||
sendErrorResponse($response->code, $msg);
|
||||
}
|
||||
|
||||
if (!property_exists($response->responseBody, 'directories') || !is_array($response->responseBody->directories)) {
|
||||
throw new RuntimeException('Невалидный формат ответа при запросе используемых директорий');
|
||||
}
|
||||
|
||||
$directories = $response->responseBody->directories;
|
||||
$fsRootPath = Path::getFsPathByModule($moduleId);
|
||||
if (!$fsRootPath) {
|
||||
throw new RuntimeException('Неизвестная ошибка');
|
||||
}
|
||||
|
||||
$dirObjects = Filesystem::safeScandir($fsRootPath);
|
||||
if (false === $dirObjects) {
|
||||
$msg = 'Не удалось просканировать папку: ' . $fsRootPath;
|
||||
sendErrorResponse(500, $msg);
|
||||
}
|
||||
|
||||
$flippedKnownDirectories = array_flip($directories);
|
||||
foreach ($dirObjects as $objectName) {
|
||||
$objectPath = Path::join($fsRootPath, $objectName);
|
||||
if (
|
||||
!array_key_exists($objectName, $flippedKnownDirectories)
|
||||
&& is_dir($objectPath)
|
||||
) {
|
||||
if (is_link($fsRootPath)) {
|
||||
throw new RuntimeException(
|
||||
'В синхронизируемый директории находится ссылка'
|
||||
. $objectPath
|
||||
. ' , которая не может быть безопасно удалена'
|
||||
);
|
||||
}
|
||||
Filesystem::remove($objectPath, true, $moduleId);
|
||||
}
|
||||
}
|
||||
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'forward_code' => 200,
|
||||
'directories' => $directories,
|
||||
);
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
|
||||
if (!isset($_GET['access_token']) || !filterAccessToken($_GET['access_token'])) {
|
||||
sendErrorResponse(422, 'Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!isset($_GET['operation_identity']) || !filter_var($_GET['operation_identity'], FILTER_SANITIZE_STRING)) {
|
||||
sendErrorResponse(422, 'Некорректный operation_identity');
|
||||
}
|
||||
|
||||
$operationIdentity = (string) $_GET['operation_identity'];
|
||||
$token = $_GET['access_token'];
|
||||
|
||||
try {
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest(
|
||||
$apiDomen . 'pull_updates/getStatusPartGenerationByNewCoreJson?operation_identity=' . $operationIdentity,
|
||||
true,
|
||||
false,
|
||||
$headers
|
||||
);
|
||||
if (!$response->curlHasError) {
|
||||
if (200 === $response->code) {
|
||||
$resultBody = array(
|
||||
'success' => true,
|
||||
'status' => $response->responseBody->status,
|
||||
'forward_code' => 200,
|
||||
);
|
||||
} else {
|
||||
$resultBody = array(
|
||||
'success' => false,
|
||||
'forward_code' => $response->code,
|
||||
'message' => !empty($response->responseBody->message ) ? $response->responseBody->message : 'неизвестная ошибка'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
setResponseCode(!empty($response->code) ? $response->code : 500);
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/filesystem.php';
|
||||
|
||||
$token = filterAccessToken(isset($_POST['access_token']) ? (string) $_POST['access_token'] : '');
|
||||
//через jquery нельзя послать пустой массив, если это не json.
|
||||
// json посылать не будем, похоже при некоторых настройках старых серверов есть проблемы с чтением raw-body через php://input($HTTP_RAW_POST_DATA)
|
||||
$isHasSubDirs = isset($_POST['isHasSubDirs']) && is_numeric($_POST['isHasSubDirs'])
|
||||
? (int) $_POST['isHasSubDirs']
|
||||
: null;
|
||||
$knownSubDirs = isset($_POST['knownSubDirs']) && is_array($_POST['knownSubDirs'])
|
||||
? $_POST['knownSubDirs']
|
||||
: array();
|
||||
$moduleId = isset($_POST['moduleId']) ? filterInt($_POST['moduleId']) : null;
|
||||
|
||||
$dlHandler = null;
|
||||
Path::init($modulesByPathDeploy);
|
||||
|
||||
try {
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
throw new RuntimeException('Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
|
||||
if (null === $isHasSubDirs || ($isHasSubDirs && !$knownSubDirs) || (!$isHasSubDirs && $knownSubDirs)) {
|
||||
throw new RuntimeException('Не может быть обработано. Неверные параметры запроса.');
|
||||
}
|
||||
|
||||
$fsDirPath = Path::getFsPathByModule($moduleId);
|
||||
if (!$fsDirPath) {
|
||||
throw new RuntimeException('Не удалось определить модуль');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$response = remoteRequest($filemanagerApiDomen . 'sync/getFileNamesFromRootDirectoryByModule?moduleId=' . $moduleId, true, false, $headers);
|
||||
if ($response->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
|
||||
if (
|
||||
$response->code !== 200
|
||||
|| !property_exists($response->responseBody, 'files')
|
||||
|| !is_array($response->responseBody->files)
|
||||
) {
|
||||
$msg = 'Не удалось получить список файлов с файлового сервера'
|
||||
. tryExtractFmErrorMessage($response, '. ');
|
||||
sendErrorResponse($response->code, $msg);
|
||||
}
|
||||
$filesByNamesFromFm = array();
|
||||
foreach ($response->responseBody->files as $row) {
|
||||
$filesByNamesFromFm[(string) $row->n] = $row->i;
|
||||
}
|
||||
$response = null;
|
||||
|
||||
if (!Filesystem::safeMkdir($fsDirPath, 0775, $moduleId)) {
|
||||
throw new RuntimeException('Не удалось создать папку "' . $fsDirPath . '" на вашем сервере');
|
||||
}
|
||||
$items = Filesystem::safeScandir($fsDirPath);
|
||||
if (false === $items) {
|
||||
$msg = 'Не удалось просканировать существующие файлы';
|
||||
sendErrorResponse(500, $msg);
|
||||
}
|
||||
|
||||
$existingItemsByNames = array();
|
||||
foreach ($items as $fName) {
|
||||
$existingItemsByNames[$fName] = null;
|
||||
}
|
||||
|
||||
$knownSubDirsByNames = array_flip($knownSubDirs);
|
||||
foreach ($existingItemsByNames as $dirItem => $_) {
|
||||
$fsItemPath = Path::join($fsDirPath, $dirItem);
|
||||
if ((array_key_exists($dirItem, $knownSubDirsByNames) && is_dir($fsItemPath))) {
|
||||
unset($existingItemsByNames[$dirItem]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists((string) $dirItem, $filesByNamesFromFm)) {
|
||||
//удаляем только файлы, ненужные папки были удалены в start_sync_files
|
||||
if (is_file($fsItemPath)) {
|
||||
unlink($fsItemPath);
|
||||
}
|
||||
} else {
|
||||
if (!filesize($fsItemPath)) {
|
||||
unset($existingItemsByNames[$dirItem]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filesForSync = array();
|
||||
foreach ($filesByNamesFromFm as $fileName => $identity) {
|
||||
if (!array_key_exists($fileName, $existingItemsByNames)) {
|
||||
$filesForSync[] = $identity;
|
||||
}
|
||||
}
|
||||
|
||||
$resultBody = array('success' => true, 'forward_code' => 200, 'files' => $filesForSync);
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/filesystem.php';
|
||||
|
||||
$dir = isset($_POST['dir']) && is_string($_POST['dir']) ? $_POST['dir'] : '';
|
||||
$token = filterAccessToken(isset($_POST['access_token']) ? (string) $_POST['access_token'] : '');
|
||||
$moduleId = isset($_POST['moduleId']) ? filterInt($_POST['moduleId']) : null;
|
||||
|
||||
$dlHandler = null;
|
||||
Path::init($modulesByPathDeploy);
|
||||
|
||||
try {
|
||||
if (!preg_match("/^[a-z]{3,4}$/", $dir)) {
|
||||
throw new RuntimeException('Невалидное значение для синхронизируемой суб-директории');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
throw new RuntimeException('Некорректный access_token');
|
||||
}
|
||||
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
throw new RuntimeException('Не верно передан параметр модуля');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$url = $filemanagerApiDomen . 'sync/getFileNamesFromSubDirectoryByModule?dir=' . $dir . '&moduleId=' . $moduleId;
|
||||
$response = remoteRequest($url, true, false, $headers);
|
||||
if ($response->curlHasError) {
|
||||
throw new RuntimeException('CURL ' . $response->curlErrorTxt);
|
||||
}
|
||||
if (
|
||||
$response->code !== 200
|
||||
|| !property_exists($response->responseBody, 'files')
|
||||
|| !is_array($response->responseBody->files)
|
||||
) {
|
||||
$message = 'Не удалось получить список файлов с файлового сервера'
|
||||
. tryExtractFmErrorMessage($response, '. ');
|
||||
sendErrorResponse($response->code, $message);
|
||||
}
|
||||
|
||||
$filesByNamesFromFm = array();
|
||||
foreach ($response->responseBody->files as $row) {
|
||||
$filesByNamesFromFm[(string) $row->n] = $row->i;
|
||||
}
|
||||
$response = null;
|
||||
|
||||
$fsDirPath = Path::getFsPathByModule($moduleId);
|
||||
if (!$fsDirPath) {
|
||||
throw new RuntimeException('Не удалось определить модуль');
|
||||
}
|
||||
|
||||
$dirPath = Path::join($fsDirPath, $dir);
|
||||
if (!Filesystem::safeMkdir($dirPath, 0775, $moduleId)) {
|
||||
throw new RuntimeException('Не удалось создать папку "' . $dirPath . '" на вашем сервере');
|
||||
}
|
||||
|
||||
$items = Filesystem::safeScandir($dirPath);
|
||||
if (false === $items) {
|
||||
$msg = 'Не удалось просканировать существующие файлы';
|
||||
sendErrorResponse(500, $msg);
|
||||
}
|
||||
|
||||
$existingItemsByNames = array();
|
||||
foreach ($items as $fName) {
|
||||
$existingItemsByNames[$fName] = null;
|
||||
}
|
||||
|
||||
foreach ($existingItemsByNames as $subDirItem => $_) {
|
||||
$filePath = Path::join($dirPath, $subDirItem);
|
||||
if (!array_key_exists((string) $subDirItem, $filesByNamesFromFm)) {
|
||||
if (is_dir($filePath)) {
|
||||
Filesystem::remove($filePath, true);
|
||||
} else {
|
||||
unlink($filePath);
|
||||
}
|
||||
} else {
|
||||
if (!filesize($filePath)) {
|
||||
unset($existingItemsByNames[$subDirItem]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filesForSync = array();
|
||||
foreach ($filesByNamesFromFm as $fileName => $identity) {
|
||||
if (!array_key_exists($fileName, $existingItemsByNames)) {
|
||||
$filesForSync[] = $identity;
|
||||
}
|
||||
}
|
||||
|
||||
$resultBody = array('success' => true, 'forward_code' => 200, 'files' => $filesForSync);
|
||||
} catch (Exception $ex) {
|
||||
$resultBody = array('success' => false, 'forward_code' => 500, 'message' => $ex->getMessage());
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
setResponseCode(200);
|
||||
echo json_encode($resultBody);
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
$updateDir = dirname(__FILE__);
|
||||
require_once $updateDir . '/../internal/config.php';
|
||||
require_once $updateDir . '/../internal/helper.php';
|
||||
require_once $updateDir . '/../internal/filesystem.php';
|
||||
|
||||
|
||||
$token = null;
|
||||
if (isset($_GET['access_token'])) { //todo new_core_after del GET
|
||||
$token = $_GET['access_token'];
|
||||
} elseif (isset($_POST['access_token'])) {
|
||||
$token = $_POST['access_token'];
|
||||
}
|
||||
|
||||
if ($token === null || !filterAccessToken($token)) {
|
||||
sendErrorResponse(422, 'Токен доступа отсутствует или некорректен');
|
||||
}
|
||||
|
||||
$moduleId = null;
|
||||
if (isset($_GET['module_id'])) { //todo new_core_after del GET
|
||||
$moduleId = $_GET['module_id'];
|
||||
} elseif (isset($_POST['module_id'])) {
|
||||
$moduleId = $_POST['module_id'];
|
||||
}
|
||||
|
||||
if ($moduleId === null || !filterInt($moduleId)) {
|
||||
sendErrorResponse(422, 'Отсутствует идентификатор модуля или он указан некорректно');
|
||||
}
|
||||
if (!in_array($moduleId, array(SVEDEN, ABITUR, VSOKO))) {
|
||||
sendErrorResponse(422, 'Не верно передан идентификатор модуля');
|
||||
}
|
||||
|
||||
$version = null;
|
||||
if (array_key_exists('version', $_GET)) { //todo new_core_after del GET
|
||||
$version = $_GET['version'];
|
||||
} elseif (array_key_exists('version', $_POST)) {
|
||||
$version = $_POST['version'];
|
||||
}
|
||||
|
||||
if ($version !== null && !filterVersion($version)) {
|
||||
sendErrorResponse(422, 'Передан невалидный параметр версии последнего обновления');
|
||||
}
|
||||
|
||||
$headers = array('Accept: application/json', 'Authorization: Bearer ' . $token);
|
||||
$res = remoteRequest($apiDomen . 'pull_updates/checkAccessJson', true, false, $headers);
|
||||
if ($res->curlHasError) {
|
||||
sendErrorResponse(500, 'CURL ' . $res->curlErrorTxt);
|
||||
}
|
||||
if ($res->code !== 200) {
|
||||
$err = 'Ошибка проверки подлинности токена. '
|
||||
. (!empty($res->responseBody->message) ? $res->responseBody->message : 'Неизвестная ошибка');
|
||||
sendErrorResponse($res->code, $err);
|
||||
}
|
||||
|
||||
try {
|
||||
$tmpPath = Path::join(Path::getCoreRootPath(), 'tmp');
|
||||
if (!Filesystem::safeMkdir($tmpPath, 0755)) {
|
||||
throw new RuntimeException('Не удалось создать директорию "' . $tmpPath . '" на вашем сервере');
|
||||
}
|
||||
|
||||
$moduleVersionsPath = Path::join($tmpPath, 'versions');
|
||||
|
||||
if (!Filesystem::safeMkdir($moduleVersionsPath, 0755)) {
|
||||
throw new RuntimeException('Не удалось создать директорию "' . $moduleVersionsPath . '" на вашем сервере');
|
||||
}
|
||||
$moduleVersionFilePath = Path::join($moduleVersionsPath, $moduleId . '.json');
|
||||
|
||||
if (!Filesystem::remove($moduleVersionFilePath, false)) {
|
||||
throw new RuntimeException('Не удалось удалить файл "' . $moduleVersionFilePath . '" на вашем сервере');
|
||||
}
|
||||
|
||||
$json = json_encode(array('version' => $version));
|
||||
if (!Filesystem::safeMkfile($moduleVersionFilePath, 0755, $json)) {
|
||||
throw new Exception('Не удалось записать служебную информацию в директорию: ' . $moduleVersionFilePath);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
sendErrorResponse(500, $ex->getMessage());
|
||||
}
|
||||
|
||||
sendSuccessResponse('');
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
//todo new_core_after del file
|
||||
|
||||
$selfDir = dirname(__FILE__);
|
||||
require_once $selfDir . '/../internal/helper.php';
|
||||
require_once $selfDir . '/../internal/config.php';
|
||||
|
||||
$res = array();
|
||||
$res['success'] = true;
|
||||
$res['errorID'] = array();
|
||||
$res['messages'] = array();
|
||||
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$message = $_POST['message'];
|
||||
$consent = filter_var($_POST['consent'], FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if($consent != true){
|
||||
$res['success'] = false;
|
||||
$res['messages'][] = 'Не получено согласие на обработку персональных данных.';
|
||||
$res['errorID'][] = 'consent';
|
||||
}
|
||||
|
||||
$code = filter_var($_POST['captcha'], FILTER_SANITIZE_STRING);
|
||||
session_start();
|
||||
if (!isset($_SESSION['captcha']) || strtoupper(trim($_SESSION['captcha'])) != strtoupper(trim($code))) {
|
||||
$res['success'] = false;
|
||||
$res['errorID'][] = 'captcha';
|
||||
$res['messages'][] = 'Неверный код с картинки.';
|
||||
}
|
||||
unset($_SESSION['captcha']);
|
||||
|
||||
if ($res['success']) {
|
||||
$res['success'] = false;
|
||||
|
||||
$headers = array('Accept: application/json');
|
||||
$data = remoteRequest($apiDomen . 'oauth2/ClientCredentials', true,
|
||||
array(
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
'grant_type' => 'client_credentials',
|
||||
), $headers
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
if (isset($data->responseBody)) {
|
||||
$accessToken = $data->responseBody->access_token;
|
||||
|
||||
try {
|
||||
$data = remoteRequest($apiDomen . 'oauth-via-app/vsoko/feedbackSendMail?access_token='
|
||||
. $accessToken .
|
||||
'&name=' . urlencode($name) .
|
||||
'&email=' . urlencode($email) .
|
||||
'&message=' . urlencode($message)
|
||||
);
|
||||
|
||||
if ($data->code == 200) {
|
||||
$res['success'] = true;
|
||||
} else {
|
||||
sendErrorResponse($data->code, $data->responseBody->message);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$res['message'] = 'Ошибка. '.$e->getMessage();
|
||||
}
|
||||
|
||||
} else {
|
||||
$res['message'] = 'Ошибка ' . $data->responseBody->message;
|
||||
}
|
||||
} else {
|
||||
$res['message'] = $data->responseBody->message;
|
||||
}
|
||||
}
|
||||
|
||||
loadHeaders();
|
||||
|
||||
echo json_encode($res);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user