Файловый менеджер - Редактировать - /var/www/html/administrator/components/com_jdownloads/helpers/scan-worker.php
Ðазад
<?php /** * @package jDownloads * @version 4.1 * @copyright (C) 2007 - 2025 - Arno Betz - www.jdownloads.com * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL * * AJAX Worker for Modern Monitoring System * This file handles the actual scanning in chunks and reports progress via JSON */ // Critical: Suppress ALL output before JSON @ini_set('display_errors', '0'); error_reporting(0); // Start output buffering to catch any errors ob_start(); @ini_set('magic_quotes_runtime', 0); define('_JEXEC', 1); if (!defined('DS')){ define( 'DS', DIRECTORY_SEPARATOR ); } define('JPATH', dirname(__FILE__) ); $parts = explode( DS, JPATH ); $script_root = implode( DS, $parts ) ; // check path $x = array_search ( 'administrator', $parts ); if (!$x) exit; $path = ''; for ($i=0; $i < $x; $i++){ $path = $path.$parts[$i].'/'; } // remove last DS $path = substr($path, 0, -1); if (!defined('JPATH_BASE')){ define('JPATH_BASE', $path ); } setlocale(LC_ALL, 'C.UTF-8', 'C'); // Run the application require_once JPATH_BASE . '/includes/defines.php'; require_once JPATH_BASE . '/includes/framework.php'; // Use statements MUST be at top level use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Application\ApplicationHelper; use Joomla\Filesystem\File; use Joomla\Filesystem\Folder; use Joomla\CMS\Table\Table; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Filter\InputFilter; use Joomla\Database\DatabaseInterface; use JDownloads\Component\JDownloads\Administrator\Helper\JDownloadsHelper; use JDownloads\Component\JDownloads\Administrator\Model\CategoryModel; use JDownloads\Component\JDownloads\Administrator\Model\DownloadModel; // Wrap everything in try-catch for clean error handling try { // Boot the DI container $container = \Joomla\CMS\Factory::getContainer(); // Alias session services $container->alias('session.web', 'session.web.site') ->alias('session', 'session.web.site') ->alias('JSession', 'session.web.site') ->alias(\Joomla\CMS\Session\Session::class, 'session.web.site') ->alias(\Joomla\Session\Session::class, 'session.web.site') ->alias(\Joomla\Session\SessionInterface::class, 'session.web.site'); // Instantiate the application. $app = $container->get(\Joomla\CMS\Application\AdministratorApplication::class); $app->createExtensionNamespaceMap(); \Joomla\CMS\Factory::$application = $app; /* Required Files */ require_once ( $path . '/components/com_jdownloads/src/Helper/CategoriesHelper.php'); require_once ( $path . '/components/com_jdownloads/src/Helper/QueryHelper.php'); require_once ( $path . '/administrator/components/com_jdownloads/src/Helper/JDownloadsHelper.php'); // Get services $database = Factory::getContainer()->get(DatabaseInterface::class); $session = Factory::getContainer()->get(\Joomla\Session\Session::class); // Backend language to prefer for admin scripts $backend_lang = ComponentHelper::getParams('com_languages')->get('administrator', 'en-GB'); // Language loading $lang = Factory::getLanguage(); $lang->load('com_jdownloads', JPATH_ADMINISTRATOR, $backend_lang, true); // Register table path Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_jdownloads/src/Table'); // Get input $jinput = Factory::getApplication()->getInput(); // Security check $config = $app->getConfig(); $params = ComponentHelper::getParams('com_jdownloads'); $secret = $params->get('scan_secret_key'); $key = $jinput->get('key', '', 'string'); // Clear any buffered output FIRST ob_clean(); // Set header for JSON response (only if not already sent) if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } // Security check: Verify secret key if ($key != $secret){ jsonResponse([ 'success' => false, 'error' => 'Invalid security key' ]); } // Security check: Verify user is logged in and is admin $user = Factory::getUser(); if (!$user || $user->id == 0 || !$user->authorise('core.manage', 'com_jdownloads')) { jsonResponse([ 'success' => false, 'error' => 'Unauthorized: You do not have permission to run this scan' ]); } // Get action $action = $jinput->get('action', 'status', 'string'); // Handle different actions switch ($action) { case 'start': handleStart(); break; case 'process': handleProcess(); break; case 'status': handleStatus(); break; case 'reset': handleReset(); break; case 'export_log': handleExportLog(); break; default: jsonResponse(['success' => false, 'error' => 'Invalid action']); } // This point should never be reached because jsonResponse() calls exit() // But just in case, clean up the buffer ob_end_clean(); exit; } catch (Exception $e) { // Catch any fatal errors and return clean JSON ob_clean(); if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } echo json_encode([ 'success' => false, 'error' => $e->getMessage(), 'file' => basename($e->getFile()), 'line' => $e->getLine(), 'type' => 'Exception' ], JSON_UNESCAPED_UNICODE); exit; } catch (Error $e) { // Catch PHP 7+ errors ob_clean(); if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } echo json_encode([ 'success' => false, 'error' => $e->getMessage(), 'file' => basename($e->getFile()), 'line' => $e->getLine(), 'type' => 'Error' ], JSON_UNESCAPED_UNICODE); exit; } catch (Throwable $e) { // Catch everything else ob_clean(); if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } echo json_encode([ 'success' => false, 'error' => $e->getMessage(), 'file' => basename($e->getFile()), 'line' => $e->getLine(), 'type' => 'Throwable' ], JSON_UNESCAPED_UNICODE); exit; } // JSON response helper function handleStart() { global $session, $jinput; $mode = $jinput->get('mode', 0, 'int'); $testrun = $jinput->get('test', 0, 'int'); $log_save = $jinput->get('log', 1, 'int'); // Default: save log (like scan.php) // Create temporary log file $session_id = $session->getId(); // Fallback if session ID is empty: use timestamp + random if (empty($session_id)) { $session_id = time() . '_' . bin2hex(random_bytes(8)); } // Normalize path separators for Windows compatibility $tmp_dir = str_replace(['/', '\\'], DS, JPATH_BASE) . DS . 'tmp'; $log_file = $tmp_dir . DS . 'jd_scan_' . $session_id . '.log'; // Clean up old scan log files (older than 1 hour) $files = glob($tmp_dir . DS . 'jd_scan_*.log'); if ($files) { $one_hour_ago = time() - 3600; foreach ($files as $file) { if (filemtime($file) < $one_hour_ago) { @unlink($file); } } } // Clear current log file if exists if (File::exists($log_file)) { @unlink($log_file); } // Create empty log file to ensure it exists @file_put_contents($log_file, ''); // Initialize scan session (without log array - stored in file) $scanData = [ 'mode' => $mode, 'testrun' => $testrun, 'log_save' => $log_save, 'log_file' => $log_file, 'started' => time(), 'phase' => 'initializing', 'progress' => 0, 'total' => 0, 'current' => 0, 'completed' => false, 'stats' => [ 'new_cats' => 0, 'new_downloads' => 0, 'missing_cats' => 0, 'missing_files' => 0, 'new_testrun_file' => 0 ] ]; $session->set('jd_scan_progress', $scanData); jsonResponse([ 'success' => true, 'message' => 'Scan initialized', 'data' => $scanData ]); } // Main processing function: handles scan phases and updates progress function handleProcess() { global $session, $params, $database; $scanData = $session->get('jd_scan_progress', null); if (!$scanData) { jsonResponse(['success' => false, 'error' => 'No scan in progress']); } try { // Get configuration $mode = $scanData['mode']; $testrun = $scanData['testrun']; // Process based on current phase switch ($scanData['phase']) { case 'initializing': initializeScan($scanData); break; case 'scanning_folders': processFolderChunk($scanData, $testrun); break; case 'scanning_files': processFileChunk($scanData, $testrun); break; case 'checking_missing_cats': checkMissingCategoriesChunk($scanData, $testrun); break; case 'checking_missing_files': checkMissingFilesChunk($scanData, $testrun); break; default: $scanData['completed'] = true; $scanData['phase'] = 'completed'; } // Update progress percentage if ($scanData['total'] > 0) { $scanData['progress'] = min(100, ($scanData['current'] / $scanData['total']) * 100); } // Save monitoring log when scan is completed if ($scanData['completed'] && !isset($scanData['log_saved'])) { saveMonitoringLog($scanData, $testrun); $scanData['log_saved'] = true; // Mark as saved to prevent duplicate saves } $session->set('jd_scan_progress', $scanData); // Add recent log entries for response if (isset($scanData['log_file'])) { $scanData['log'] = getRecentLogs($scanData['log_file'], 100); } else { $scanData['log'] = []; } jsonResponse([ 'success' => true, 'data' => $scanData ]); } catch (Exception $e) { addLog($scanData, 'ERROR: ' . $e->getMessage()); $session->set('jd_scan_progress', $scanData); // Add recent log entries for error response if (isset($scanData['log_file'])) { $scanData['log'] = getRecentLogs($scanData['log_file'], 100); } else { $scanData['log'] = []; } jsonResponse([ 'success' => false, 'error' => $e->getMessage(), 'data' => $scanData ]); } } // Initialize scan: set up exclude/include folders and start first step function initializeScan(&$scanData) { global $params, $session; $mode = $scanData['mode']; $jd_root = $params->get('files_uploaddir') . '/'; // Build exclude folders $temp_dir = $jd_root . $params->get('tempzipfiles_folder_name') . '/'; $preview_dir = $jd_root . $params->get('preview_files_folder_name') . '/'; $exclude_folders = [$temp_dir, $preview_dir]; $include_folders = []; // Handle folder filtering if (!$params->get('all_folders_autodetect')) { $specified_folder_name_types = $params->get('include_or_exclude'); $specified_folder_names = preg_split("/\\r\\n|\\r|\\n/", $params->get('include_or_exclude_folders')); $specified_folder_names = array_filter($specified_folder_names); foreach ($specified_folder_names as &$specified_folder_name) { $specified_folder_name = $jd_root . $specified_folder_name . '/'; } if ($specified_folder_name_types == 1) { $exclude_folders = array_merge($exclude_folders, $specified_folder_names); } else { $include_folders = $specified_folder_names; } } $scanData['exclude_folders'] = $exclude_folders; $scanData['include_folders'] = $include_folders; $scanData['jd_root'] = $jd_root; // Always scan: folders → files → missing_cats → missing_files (Mode 0) $scanData['phase'] = 'scanning_folders'; // Initialize log file with start message //addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO2')); $scanData['initialized'] = true; $session->set('jd_scan_progress', $scanData); } // Search for new directories to create them as new categories function processFolderChunk(&$scanData, $testrun) { global $database, $params, $session; // Log Phase 1 starting message (only on first call) if (!isset($scanData['phase_1_started'])) { addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO3')); $scanData['phase_1_started'] = true; } // Get chunk size from params $chunkSize = (int)$params->get('scan_chunk_size', 20); // Lazy load folder list if (!isset($scanData['folders_to_scan'])) { // Add run hint to log //addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO3')); $folders = JDownloadsHelper::searchdir( $scanData['jd_root'], -1, 'DIRS', 0, $scanData['exclude_folders'], $scanData['include_folders'] ); // Filter and prepare folders $searchdirs = []; foreach ($folders as $folder) { if (!JDownloadsHelper::findStringInArray($scanData['exclude_folders'], $folder) && $scanData['jd_root'] != $folder) { $folder = str_replace($scanData['jd_root'], '', $folder); if ($pos = strrpos($folder, '/')) { $searchdirs[] = substr($folder, 0, $pos); } } } $scanData['folders_to_scan'] = $searchdirs; $scanData['total'] = count($searchdirs); $scanData['folder_index'] = 0; addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_SUM_FOLDERS') . ' ' . count($searchdirs)); } // Get existing categories $database->setQuery("SELECT concat(cat_dir_parent, '/', cat_dir) AS path FROM #__jdownloads_categories WHERE cat_dir != ''"); $existing_categories = $database->loadColumn(); // Process chunk $processed = 0; $folders = $scanData['folders_to_scan']; $startIndex = $scanData['folder_index']; for ($i = $startIndex; $i < count($folders) && $processed < $chunkSize; $i++) { $folder = $folders[$i]; // Check if folder exists as category $dirs = explode('/', $folder); $sum = count($dirs); if ($sum == 1) { $cat_exist = in_array('/' . $folder, $existing_categories); $cat_dir_parent_value = ''; $cat_dir_value = $dirs[0]; } else { $cat_exist = in_array($folder, $existing_categories); $pos = strrpos($folder, '/'); $cat_dir_parent_value = substr($folder, 0, $pos); $cat_dir_value = substr($folder, $pos + 1); } // Create new category if it doesn't exist if (!$cat_exist) { addLog($scanData, Text::sprintf('COM_JDOWNLOADS_AUTO_CHECK_NEW_CAT_FOUND', ' → ' . $folder )); $scanData['stats']['new_cats']++; if (!$testrun) { // Actual implementation: Create category $result = createNewCategory($folder, $cat_dir_value, $cat_dir_parent_value, $sum); if ($result['success']) { $log_msg = Text::sprintf('COM_JDOWNLOADS_AUTO_CAT_CHECK_ADDED', ': ✓ ' . $folder ); addLog($scanData, $log_msg); } else { $log_msg = Text::_('COM_JDOWNLOADS_ERROR_RESULT_MSG' . ': ✗ ' . $folder . ': ' . $result['error']); addLog($scanData, $log_msg); } } } $processed++; $scanData['current']++; $scanData['folder_index'] = $i + 1; } // Check if folder scanning is complete if ($scanData['folder_index'] >= count($folders)) { // Always continue to file scanning (Mode 0) $scanData['phase'] = 'scanning_files'; $scanData['current'] = 0; $scanData['total'] = 0; // Show summary message for Phase 1 if (!$testrun) { // Non-Testrun: zeige new_cats if ($scanData['stats']['new_cats'] > 0) { addLog($scanData, (int)$scanData['stats']['new_cats'] . ' ' . Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NEW_CATS')); } else { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NO_NEW_CATS')); } } } $session->set('jd_scan_progress', $scanData); } // Search for new files to create them as new downloads function processFileChunk(&$scanData, $testrun) { global $database, $params, $session, $secret; // Log Phase 2 starting message (only on first call) if (!isset($scanData['phase_2_started'])) { addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO5')); $scanData['phase_2_started'] = true; } // Get chunk size from params $chunkSize = (int)$params->get('scan_chunk_size', 20); // Lazy-load file list if (!isset($scanData['files_to_scan'])) { $files = []; $file_types = []; if (!$params->get('all_files_autodetect')) { $file_types = explode(',', $params->get('file_types_autodetect')); } $all_dirs = JDownloadsHelper::scan_dir( $scanData['jd_root'], $scanData['exclude_folders'], $scanData['include_folders'], $scanData['jd_root'], $files, $file_types, false, $params->get('all_files_autodetect'), true, true ); // Convert associative array to indexed array for iteration // scan_dir returns: $files[fullpath] = ['path' => ..., 'file' => ..., 'size' => ..., 'date' => ...] $files_indexed = []; foreach ($files as $full_path => $file_info) { $files_indexed[] = [ 'full_path' => $full_path, 'file' => $file_info['file'], 'path' => $file_info['path'], 'size' => $file_info['size'], 'date' => $file_info['date'] ]; } $scanData['files_to_scan'] = $files_indexed; $scanData['total'] = count($files_indexed); $scanData['file_index'] = 0; $sumFilesString = Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_SUM_FILES') . ' ' . count($files_indexed); addLog($scanData, $sumFilesString, 0); } // Get existing downloads $database->setQuery("SELECT id, catid, md5_value, url_download FROM #__jdownloads_files WHERE url_download != ''"); $existing_downloads = $database->loadObjectList(); $compare_also_files_hash = (int) $params->get('compare_also_files_hash', 0); $update_modification_date = (int) $params->get('update_modification_date', 0); $update_update_status = (int) $params->get('update_update_status', 0); $check_sys_settings = JDownloadsHelper::explore($secret); // Process chunk $processed = 0; $files = $scanData['files_to_scan']; $startIndex = $scanData['file_index']; for ($i = $startIndex; $i < count($files) && $processed < $chunkSize; $i++) { $file_info = $files[$i]; $full_path = $file_info['full_path']; $filename = $file_info['file']; // Extract relative path and category info from full path // The file_path from scan_dir comes as: "category/subcategory/filename.zip" $file_path_rel = str_replace($scanData['jd_root'], '', $full_path); $file_path_rel = ltrim($file_path_rel, '/'); // Extract category path from file path $pos = strrpos($file_path_rel, '/'); if ($pos) { $only_dirs = substr($file_path_rel, 0, $pos); $pos2 = strrpos($only_dirs, '/'); if ($pos2 !== false) { // Multiple levels: parent/child category $cat_dir_parent_value = substr($only_dirs, 0, $pos2); $cat_dir_value = substr($only_dirs, $pos2 + 1); } else { // Single level: only one category (no parent) $cat_dir_parent_value = ''; $cat_dir_value = $only_dirs; } } else { $cat_dir_parent_value = ''; $cat_dir_value = ''; } // Check if this file (with same filename AND category) already exists as download $exist_file = false; foreach ($existing_downloads as $download) { if ($download->url_download === $filename) { // Found a download with same filename // Now check if it matches the category $database->setQuery("SELECT COUNT(*) FROM #__jdownloads_categories WHERE id = " . (int)$download->catid . " AND cat_dir = " . $database->quote($cat_dir_value) . " AND cat_dir_parent = " . $database->quote($cat_dir_parent_value)); $row_cat_find = $database->loadResult(); if ($row_cat_find) { $exist_file = true; if ($compare_also_files_hash && $check_sys_settings) { // File already exists in this category, but check if hash changed $hash = md5_file($full_path); if ($hash !== $download->md5_value) { // File hash is different - file has changed if (!$testrun) { $update_date = Factory::getDate()->toSql(); if ($update_modification_date) { if ($update_update_status) { $database->setQuery("UPDATE #__jdownloads_files SET md5_value = " . $database->quote($hash) . ", modified = " . $database->quote($update_date) . ", update_active = 1 WHERE id = " . (int)$download->id); } else { $database->setQuery("UPDATE #__jdownloads_files SET md5_value = " . $database->quote($hash) . ", modified = " . $database->quote($update_date) . " WHERE id = " . (int)$download->id); } } else { $database->setQuery("UPDATE #__jdownloads_files SET md5_value = " . $database->quote($hash) . " WHERE id = " . (int)$download->id); } $database->execute(); if ($update_update_status) { $log_msg = Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_DATE_UPDATED2') . ': ' . $file_path_rel; } else { $log_msg = Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_DATE_UPDATED') . ': ' . $file_path_rel; } addLog($scanData, $log_msg); } else { if ($update_update_status) { $log_msg = Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_DATE_COULD_UPDATED2') . ': ' . $file_path_rel; } else { $log_msg = Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_DATE_COULD_UPDATED') . ': ' . $file_path_rel; } addLog($scanData, $log_msg); } } } break; } } } // Add the file here in a new Download if it doesn't exist if (!$exist_file) { $scanData['stats']['new_testrun_file']++; if (!$testrun) { $result = createNewDownload($full_path, $file_path_rel, $files[$i]); if ($result['success']) { $scanData['stats']['new_downloads']++; $log_msg = Text::sprintf('COM_JDOWNLOADS_AUTO_FILE_CHECK_ADDED', ': ✓ ' . $file_path_rel ); addLog($scanData, $log_msg); } else { $log_msg = Text::_('COM_JDOWNLOADS_ERROR_RESULT_MSG' . ': ✗ ' . $file_path_rel . ': ' . $result['error']); addLog($scanData, $log_msg); } } else { $scanData['stats']['new_downloads']++; $log_msg = Text::sprintf('COM_JDOWNLOADS_AUTO_CHECK_NEW_FILE_FOUND', ' → '.$file_path_rel); addLog($scanData, $log_msg); } } $processed++; $scanData['current']++; $scanData['file_index'] = $i + 1; } // Check if file scanning is complete if ($scanData['file_index'] >= count($files)) { // Continue to check missing categories (Mode 0 flow) $scanData['phase'] = 'checking_missing_cats'; $scanData['current'] = 0; $scanData['total'] = 0; // Show summary message for Phase 2 if (!$testrun) { if ($scanData['stats']['new_downloads'] > 0) { addLog($scanData, $scanData['stats']['new_downloads'] . ' ' . Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NEW_FILES')); } else { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NO_NEW_FILES')); } } else { if (!$scanData['stats']['new_testrun_file'] > 0) { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NO_NEW_FILES')); } } } $session->set('jd_scan_progress', $scanData); } // Check for missing directories and unpublish the corresponding categories function checkMissingCategoriesChunk(&$scanData, $testrun) { global $database, $session, $params; $jd_root = $scanData['jd_root']; $chunkSize = (int)$params->get('scan_chunk_size', 20); // Lazy-load category list if (!isset($scanData['categories_to_check'])) { $database->setQuery("SELECT * FROM #__jdownloads_categories WHERE published = 1 ORDER BY id ASC"); $categories = $database->loadObjectList(); $scanData['categories_to_check'] = $categories; $scanData['category_index'] = 0; $scanData['total'] = count($categories); $scanData['current'] = 0; addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO4')); } $categories = $scanData['categories_to_check']; $processed = 0; // Process chunk for ($i = $scanData['category_index']; $i < count($categories) && $processed < $chunkSize; $i++) { $cat = $categories[$i]; // Build category path if ($cat->cat_dir_parent != '') { $cat_dir = $jd_root . $cat->cat_dir_parent . '/' . $cat->cat_dir; } else { $cat_dir = $jd_root . $cat->cat_dir; } // Check if category directory exists on filesystem if (!is_dir($cat_dir)) { if (!$testrun) { // Unpublish the missing category $database->setQuery("UPDATE #__jdownloads_categories SET published = 0 WHERE id = " . (int)$cat->id); $database->execute(); $scanData['stats']['missing_cats']++; addLog($scanData, '✗ ' . Text::sprintf('COM_JDOWNLOADS_AUTO_CAT_CHECK_DISABLED', $cat->cat_dir)); //addLog($scanData, '✗ ' . Text::sprintf('COM_JDOWNLOADS_AUTO_CHECK_OLD_FOLDER_MISSING', $cat->cat_dir)); } else { $scanData['stats']['missing_cats']++; addLog($scanData, '✗ ' . Text::sprintf('COM_JDOWNLOADS_AUTO_CHECK_OLD_FOLDER_MISSING', $cat->cat_dir)); } } $processed++; $scanData['current']++; $scanData['category_index'] = $i + 1; } // Check if checking is complete if ($scanData['category_index'] >= count($categories)) { // Continue to check missing files $scanData['phase'] = 'checking_missing_files'; $scanData['current'] = 0; $scanData['total'] = 0; $scanData['file_index'] = 0; // Show summary message for Phase 3 if (!$testrun) { if ($scanData['stats']['missing_cats'] > 0) { addLog($scanData, $scanData['stats']['missing_cats'] . ' ' . Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_MISSING_CATS')); } else { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NO_MISSING_CATS')); } } } $session->set('jd_scan_progress', $scanData); } // Check for missing files and unpublish the corresponding downloads function checkMissingFilesChunk(&$scanData, $testrun) { global $database, $session, $params; $jd_root = $scanData['jd_root']; $chunkSize = (int)$params->get('scan_chunk_size', 20); // Lazy-load file list if (!isset($scanData['files_to_check'])) { $database->setQuery("SELECT * FROM #__jdownloads_files WHERE published = 1 ORDER BY id ASC"); $downloads = $database->loadObjectList(); $scanData['files_to_check'] = $downloads; $scanData['file_index'] = 0; $scanData['total'] = count($downloads); $scanData['current'] = 0; addLog($scanData, Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO6')); } $downloads = $scanData['files_to_check']; $processed = 0; // Process chunk for ($i = $scanData['file_index']; $i < count($downloads) && $processed < $chunkSize; $i++) { $file = $downloads[$i]; // We can only check downloads which have a file if ($file->url_download != '') { $database->setQuery("SELECT cat_dir, cat_dir_parent FROM #__jdownloads_categories WHERE id = " . (int)$file->catid); $cat = $database->loadObject(); if ($cat) { // Build category path if ($cat->cat_dir_parent != '') { $cat_dir_path = $cat->cat_dir_parent . '/' . $cat->cat_dir; } else { $cat_dir_path = $cat->cat_dir; } // Build full file path $file_path = $jd_root . $cat_dir_path . '/' . $file->url_download; $display_path = $cat_dir_path . '/' . $file->url_download; // Check if file exists on filesystem if (!file_exists($file_path)) { if (!$testrun) { // Unpublish the missing file $database->setQuery("UPDATE #__jdownloads_files SET published = 0 WHERE id = " . (int)$file->id); $database->execute(); $scanData['stats']['missing_files']++; addLog($scanData, '✗ ' . Text::sprintf('COM_JDOWNLOADS_AUTO_FILE_CHECK_DISABLED', $display_path)); } else { $scanData['stats']['missing_files']++; addLog($scanData, '✗ ' . Text::sprintf('COM_JDOWNLOADS_AUTO_CHECK_OLD_FILE_MISSING', $display_path)); } } } } $processed++; $scanData['current']++; $scanData['file_index'] = $i + 1; } // Check if checking is complete if ($scanData['file_index'] >= count($downloads)) { // Scan is complete $scanData['completed'] = true; $scanData['phase'] = 'completed'; $scanData['progress'] = 100; // Show summary messages if items were found (non-testrun only) if (!$testrun) { if ($scanData['stats']['missing_files'] > 0) { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_MISSING_FILES')); } else { addLog($scanData, Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_NO_MISSING_FILES')); } } addLog($scanData, '✓ ' . Text::_('COM_JDOWNLOADS_MONITORING_LOG_FINISHED')); } $session->set('jd_scan_progress', $scanData); } // Create a new category in the database function createNewCategory($folder, $cat_dir_value, $cat_dir_parent_value, $sum) { global $database, $params, $session; try { // Get parent ID if ($sum == 1) { $parent_id = 1; // Root category } else { $pos = strrpos($cat_dir_parent_value, '/'); if ($pos) { $cat_dir_parent_value2 = substr($cat_dir_parent_value, 0, $pos); $cat_dir_value2 = substr($cat_dir_parent_value, $pos + 1); $query = "SELECT * FROM #__jdownloads_categories WHERE cat_dir = " . $database->quote($database->escape($cat_dir_value2)) . " AND cat_dir_parent = " . $database->quote($database->escape($cat_dir_parent_value2)); } else { $query = "SELECT * FROM #__jdownloads_categories WHERE cat_dir = " . $database->quote($database->escape($cat_dir_parent_value)) . " AND cat_dir_parent = ''"; } $database->setQuery($query); $parent_cat = $database->loadObject(); if (!$parent_cat) { return ['success' => false, 'error' => 'Parent category "' . $cat_dir_parent_value . '" not found']; } $parent_id = $parent_cat->id; } // Clean folder name $checked_cat_dir = JDownloadsHelper::getCleanFolderFileName($cat_dir_value, true); $alias = ApplicationHelper::stringURLSafe($cat_dir_value); $use_default_values = (int) $params->get('autopublish_use_cat_default_values', 0); if ($use_default_values) { $desc = JDownloadsHelper::getOnlyLanguageSubstring($params->get('autopublish_default_cat_description', '')); $desc = InputFilter::getInstance()->clean($desc, 'string'); $access = (int) $params->get('autopublish_cat_access_level', 0); $language = $params->get('autopublish_cat_language', '*'); $tags = $params->get('autopublish_cat_tags', 0); $creator = (int) $params->get('autopublish_cat_created_by', 0); $cat_pic = $params->get('autopublish_cat_pic_default_filename', ''); } else { $desc = ''; $language = '*'; $tags = ''; $creator = 0; $cat_pic = $params->get('cat_pic_default_filename', ''); $access = isset($parent_cat) && $parent_cat ? (int) $parent_cat->access : 1; } // Build category data $data = [ 'id' => 0, 'parent_id' => $parent_id, 'title' => $cat_dir_value, 'alias' => $alias, 'notes' => '', 'description' => $desc, 'cat_dir' => $checked_cat_dir, 'cat_dir_parent' => $cat_dir_parent_value, 'pic' => $cat_pic, 'published' => (int)$params->get('autopublish_founded_files'), 'access' => $access, 'metadesc' => '', 'metakey' => '', 'language' => $language, 'created_user_id' => $creator, 'params' => [], 'rules' => [], ]; // Create category using model $model_category = new \JDownloads\Component\JDownloads\Administrator\Model\CategoryModel(); $result = $model_category->createAutoCategory($data); if ($result) { return ['success' => true]; } else { // Get error from model if available $error = $model_category->getError(); return ['success' => false, 'error' => $error ?: 'Category creation failed']; } } catch (Throwable $e) { return ['success' => false, 'error' => $e->getMessage()]; } } // Create a new download in the database function createNewDownload($file_path, $relative_path, $file) { global $database, $params, $session; $app = Factory::getApplication(); try { // Extract category info from relative path (same logic as processFileChunk) $relative_path_clean = ltrim($relative_path, '/'); // Find the last slash to separate category from filename $pos = strrpos($relative_path_clean, '/'); if ($pos) { $only_dirs = substr($relative_path_clean, 0, $pos); // Determine cat_dir_parent and cat_dir from the directory path $pos2 = strrpos($only_dirs, '/'); if ($pos2 !== false) { // Multiple levels: parent/child category $cat_dir_parent_value = substr($only_dirs, 0, $pos2); $cat_dir_value = substr($only_dirs, $pos2 + 1); } else { // Single level: no parent $cat_dir_parent_value = ''; $cat_dir_value = $only_dirs; } } else { // No category path (file in root) $cat_dir_parent_value = ''; $cat_dir_value = ''; } // Find category ID using exact cat_dir and cat_dir_parent match $cat_id = null; $cat_access = null; if ($cat_dir_value) { // Query for category with exact directory match $database->setQuery("SELECT id, access FROM #__jdownloads_categories WHERE cat_dir = " . $database->quote($cat_dir_value) . " AND cat_dir_parent = " . $database->quote($cat_dir_parent_value)); $cat_row = $database->loadObject(); if ($cat_row) { $cat_id = (int) $cat_row->id; $cat_access = (int) $cat_row->access; } } // Fallback: use root category if not found if (!$cat_id) { $cat_id = 1; // Default to uncategorized/root category $cat_access = 1; } // Extract file info $filename = basename($file_path); // Get file details $only_name = File::stripExt($filename); $file_extension = File::getExt($filename); $file_size = $file['size']; // Build the title $title = InputFilter::getInstance()->clean($only_name, 'STRING'); // Build the alias $alias = ApplicationHelper::stringURLSafe($title); // Calculate date $date = Factory::getDate(); $tz = $app->getConfig()->get( 'offset' ); $date->setTimezone(new DateTimeZone($tz)); // Set creation date $creation_date = Factory::getDate()->toSql(); // Set file mime pic $picpath = strtolower(JPATH_SITE.'/images/jdownloads/fileimages/'.$file_extension.'.png'); if (file_exists($picpath)){ $file_pic = $file_extension.'.png'; } else { $file_pic = $params->get('file_pic_default_filename'); } // Create thumbs form pdf if ($params->get('create_pdf_thumbs') && $params->get('create_pdf_thumbs_by_scan') && $file_extension == 'pdf'){ $thumb_file_type = strtolower($params->get('pdf_thumb_image_type')); // Make sure that we have a unique filename for the new pic $thumb_path = JPATH_SITE.'/images/jdownloads/screenshots/thumbnails/'; $screenshot_path = JPATH_SITE.'/images/jdownloads/screenshots/'; $picfilename = basename($file_path); $only_name = File::stripExt($picfilename); $file_extension = File::getExt($picfilename); $thumbfilename = $thumb_path.$only_name.'.'.$thumb_file_type; $num = 1; while (File::exists($thumbfilename)){ $picfilename = $only_name.$num.'.'.$thumb_file_type; $thumbfilename = $thumb_path.$picfilename; $num++; } // Create now the new pdf thumbnail $only_name = File::stripExt($picfilename); $pdf_thumb_name = jdownloadsHelper::create_new_pdf_thumb($file_path, $only_name, $thumb_path, $screenshot_path); if ($pdf_thumb_name){ $images = $pdf_thumb_name; } } // Create auto thumb when founded file is an image if ($params->get('create_auto_thumbs_from_pics') && $params->get('create_auto_thumbs_from_pics_by_scan')){ if ($file_is_image = JDownloadsHelper::fileIsPicture($filename)){ // Make sure that we have a unique filename for the new pic $thumbpath = JPATH_SITE.'/images/jdownloads/screenshots/thumbnails/'; $picfilename = basename($file_path); $only_name = File::stripExt($picfilename); $file_extension = File::getExt($picfilename); $thumbfilename = $thumbpath.$picfilename; $num = 1; while (File::exists($thumbfilename)){ $picfilename = $only_name.$num.'.'.$file_extension; $thumbfilename = $thumbpath.$picfilename; $num++; } // Create now the new thumbnail $thumb_created = jdownloadsHelper::create_new_thumb($file_path, $picfilename); if ($thumb_created){ $images = $picfilename; // Create new big image for full view $image_created = jdownloadsHelper::create_new_image($file_path, $picfilename); } } } // Use default values from params? $use_default_values = $params->get('autopublish_use_default_values', 0); // Use title rules? $title_rule = $params->get('autopublish_title_format_option', 0); if ($use_default_values){ if ($title_rule > 0){ $title = str_replace('-', ' ', $title); $title = str_replace('_', ' ', $title); } if ($title_rule == 2){ $title = ucwords($title); } // Title must have a value if ($title == '') $title = 'Invalid Name!'; $creator = $params->get('autopublish_created_by', 0); $language = $params->get('autopublish_language', '*'); $desc = JDownloadsHelper::getOnlyLanguageSubstring($params->get('autopublish_default_description', '')); $desc = InputFilter::getInstance()->clean($desc, 'string'); $access = (int) $params->get('autopublish_access_level', 0); $tags = $params->get('autopublish_tags', 0); $price = $params->get('autopublish_price', ''); } else { $creator = 0; $desc = ''; $access = $cat_access !== null ? $cat_access : 1; $language = '*'; $tags = ''; $price = ''; } // Reset images var $images = ''; $sha1_value = sha1_file($file_path); $md5_value = md5_file($file_path); // Build data array $data = array ( 'id' => 0, 'catid' => $cat_id, 'title' => $title, 'alias' => $alias, 'notes' => '', 'url_download' => $filename, 'size' => $file_size, 'price' => $price, 'description' => $desc, 'description_long' => $desc, 'changelog' => $desc, 'file_pic' => $file_pic, 'images' => $images, 'created' => $creation_date, 'file_date' => $creation_date, 'sha1_value' => $sha1_value, 'md5_value' => $md5_value, 'published' => (int)$params->get('autopublish_founded_files'), 'access' => $access, 'metadesc' => '', 'metakey' => '', 'created_by' => $creator, 'language' => $language, 'tags' => $tags, 'rules' => array( 'core.create' => array(), 'core.delete' => array(), 'core.edit' => array(), 'core.edit.state' => array(), 'core.edit.own' => array(), 'download' => array(), ), 'params' => array(), ); // Create download using model $model_download = new \JDownloads\Component\JDownloads\Administrator\Model\DownloadModel(); $result = $model_download->save($data, true); if ($result) { return ['success' => true]; } else { // Get error from model if available $error = $model_download->getError(); return ['success' => false, 'error' => $error ?: 'Download creation failed']; } } catch (Throwable $e) { return ['success' => false, 'error' => $e->getMessage()]; } } /** * Add log entry to temporary file */ function addLog(&$scanData, $message) { // Write to temporary log file if (!isset($scanData['log_file'])) { // Fallback: log_file not set - this should never happen error_log('WARNING: addLog called but log_file not set in scanData'); return; } try { $log_file = $scanData['log_file']; // Ensure directory exists $dir = dirname($log_file); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // Write log entry $result = file_put_contents($log_file, $message . "\n", FILE_APPEND | LOCK_EX); if ($result === false) { error_log('Failed to write to log file: ' . $log_file . ' (Message: ' . $message . ')'); } } catch (Throwable $e) { error_log('Failed to write log entry (Throwable): ' . $e->getMessage()); } } /** * Get recent log entries for backend stats module display */ function getRecentLogs($log_file, $max_lines = 100) { if (!File::exists($log_file)) { return []; } try { $content = file_get_contents($log_file); if (!$content) { return []; } $all_lines = explode("\n", trim($content)); // Return last $max_lines entries return array_slice($all_lines, -$max_lines); } catch (Exception $e) { error_log('Failed to read log file: ' . $e->getMessage()); return []; } } // Helper function for safe JSON output function jsonResponse($data) { // Clean any previous output if (ob_get_level() > 0) { ob_clean(); } // Set JSON header if not already sent if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } // function handleStatus() { global $session; $scanData = $session->get('jd_scan_progress', null); if (!$scanData) { jsonResponse([ 'success' => false, 'error' => 'No scan in progress' ]); } // Add recent log entries for backend stats module display if (isset($scanData['log_file'])) { $scanData['log'] = getRecentLogs($scanData['log_file'], 100); } else { $scanData['log'] = []; } jsonResponse([ 'success' => true, 'data' => $scanData ]); } // Reset scan data and delete temporary log file function handleReset() { global $session; $scanData = $session->get('jd_scan_progress', null); // Delete temporary log file if ($scanData && isset($scanData['log_file']) && File::exists($scanData['log_file'])) { @unlink($scanData['log_file']); } $session->clear('jd_scan_progress'); jsonResponse([ 'success' => true, 'message' => 'Scan data cleared' ]); } /** * Export complete log for download (not limited to 100 entries) */ function handleExportLog() { global $session; $scanData = $session->get('jd_scan_progress', null); $log_file = null; // Try to get log file from session if ($scanData && isset($scanData['log_file'])) { $log_file = $scanData['log_file']; } // Fallback: Find most recent log file if session data not available if (!$log_file || !File::exists($log_file)) { $tmp_dir = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, JPATH_BASE) . DIRECTORY_SEPARATOR . 'tmp'; $pattern = $tmp_dir . DIRECTORY_SEPARATOR . 'jd_scan_*.log'; $files = glob($pattern); if ($files && count($files) > 0) { // Sort by modification time, newest first usort($files, function($a, $b) { return filemtime($b) - filemtime($a); }); $log_file = $files[0]; // Use newest file } } // Still no log file found if (!$log_file || !File::exists($log_file)) { jsonResponse([ 'success' => false, 'error' => 'No log file available', 'debug' => [ 'session_has_data' => ($scanData !== null), 'session_has_log_file' => isset($scanData['log_file']), 'log_file_path' => $log_file ] ]); } // Read ALL log entries from temp file (not limited) $all_logs = []; try { $content = file_get_contents($log_file); if ($content) { $all_logs = explode("\n", trim($content)); // Remove empty lines $all_logs = array_filter($all_logs, function($line) { return !empty(trim($line)); }); // Re-index array $all_logs = array_values($all_logs); } } catch (Exception $e) { jsonResponse([ 'success' => false, 'error' => 'Failed to read log file: ' . $e->getMessage() ]); } jsonResponse([ 'success' => true, 'log' => $all_logs, 'total' => count($all_logs) ]); } /** * Save scan results to monitoring_logs.txt for backend display * This matches the behavior of scan.php */ function saveMonitoringLog($scanData, $testrun) { $params = ComponentHelper::getParams('com_jdownloads'); $log_file = JPATH_BASE . '/administrator/components/com_jdownloads/monitoring_logs.txt'; // Only save if log_save is enabled if (!isset($scanData['log_save']) || !$scanData['log_save']) { return; } // Read all logs from temporary file $all_logs = []; if (isset($scanData['log_file']) && File::exists($scanData['log_file'])) { $content = file_get_contents($scanData['log_file']); if ($content) { $all_logs = explode("\n", trim($content)); } } // Build log message with HTML formatting (same as scan.php) $log_message = ''; if (count($all_logs) > 0) { $date = date(Text::_('DATE_FORMAT_LC2')) . ':<br />'; if ($testrun) { $date .= '<big><b>' . Text::_('COM_JDOWNLOADS_AUTO_CHECK_TEST_RUN_HINT') . '</b></big><br />'; } else { $date .= '<big><b>' . Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_LOG_TITLE') . '</b></big><br />'; } $log_message = $date; // Convert log array entries to HTML foreach ($all_logs as $logEntry) { if (empty($logEntry)) continue; // Add HTML formatting based on log entry content if (strpos($logEntry, '✓') !== false) { $log_message .= '<span style="color:green;">' . htmlspecialchars($logEntry) . '</span><br />'; } elseif (strpos($logEntry, '✗') !== false || strpos($logEntry, 'ERROR') !== false) { $log_message .= '<span style="color:red;"><b>' . htmlspecialchars($logEntry) . '</b></span><br />'; } else { $log_message .= htmlspecialchars($logEntry) . '<br />'; } } // Add statistics summary if (isset($scanData['stats'])) { $log_message .= '<br /><b>' . Text::_('COM_JDOWNLOADS_BACKEND_AUTOCHECK_TITLE') . '</b><br />'; if (isset($scanData['stats']['new_cats'])) { $log_message .= Text::_('COM_JDOWNLOADS_MONITORING_NEW_CATS') . ': <b>' . $scanData['stats']['new_cats'] . '</b><br />'; } if (isset($scanData['stats']['new_downloads'])) { $log_message .= Text::_('COM_JDOWNLOADS_MONITORING_NEW_DOWNLOADS') . ': <b>' . $scanData['stats']['new_downloads'] . '</b><br />'; } if (isset($scanData['stats']['missing_cats'])) { $log_message .= Text::_('COM_JDOWNLOADS_MONITORING_MISSING_CATS') . ': <b>' . $scanData['stats']['missing_cats'] . '</b><br />'; } if (isset($scanData['stats']['missing_files'])) { $log_message .= Text::_('COM_JDOWNLOADS_MONITORING_MISSING_FILES') . ': <b>' . $scanData['stats']['missing_files'] . '</b><br />'; } } $log_message .= '<br /><hr /><br />'; } // Write to file (prepend new log to existing content) if ($log_message != '') { try { if (File::exists($log_file)) { // Check file size limit $size = (int)filesize($log_file); $max_size_kb = $params->get('max_size_log_file', 32); if (($size / 1024) >= $max_size_kb) { // File too large, delete and start fresh @unlink($log_file); file_put_contents($log_file, $log_message, FILE_APPEND | LOCK_EX); } else { // Prepend new log to existing content $content = file_get_contents($log_file); $new_content = $log_message . $content; file_put_contents($log_file, $new_content, LOCK_EX); } } else { // Create new file file_put_contents($log_file, $log_message, FILE_APPEND | LOCK_EX); } } catch (Exception $e) { // Silently fail - logging should not break the scan error_log('Failed to write monitoring log: ' . $e->getMessage()); } } // Note: Temporary log file is NOT deleted here - it remains available // for real-time display in the modal. It will be cleaned up by: // 1. Next scan start (handleStart clears old files) // 2. Periodic cleanup job (optional) } // Helper to get formatted date function getFormattedDate() { // Create date string $date = Factory::getDate(); $tz = Factory::getConfig()->get( 'offset' ); $date->setTimezone(new DateTimeZone($tz)); $date = date(Text::_('DATE_FORMAT_LC2')); return $date; }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка