From 7f79ad5217ce04a0f0f792a4257c89f82de34c6d Mon Sep 17 00:00:00 2001 From: Yury Pikhtarev Date: Tue, 1 Jul 2025 23:10:28 +0400 Subject: [PATCH] refactor: remove legacy files and update logo in the project (#2024) - Deleted outdated legacy files including PHP scripts and documentation to streamline the project structure. - Introduced a new logo SVG file while removing the old logo, ensuring a fresh visual identity for the project. - Updated the app logo component to reflect the new branding. These changes aim to modernize the project and enhance its overall presentation. --- docs/static/img/logo.svg | 38 - docs/static/img/logos.svg | 37 + legacy/admin/pagestart.php | 30 - legacy/ajax.php | 45 - legacy/cron.php | 13 - legacy/info.php | 51 - legacy/library/attach_mod/attachment_mod.php | 77 -- .../attach_mod/includes/functions_attach.php | 389 ------ .../includes/functions_includes.php | 156 --- .../attach_mod/includes/functions_thumbs.php | 57 - .../attach_mod/posting_attachments.php | 23 - legacy/library/includes/cron/cron_check.php | 41 - legacy/library/includes/cron/cron_run.php | 124 -- .../cron/jobs/clean_search_results.php | 19 - .../cron/jobs/ds_update_cat_forums.php | 14 - .../includes/cron/jobs/ds_update_stats.php | 14 - .../includes/cron/jobs/sessions_cleanup.php | 54 - .../datastore/build_attach_extensions.php | 21 - .../library/includes/datastore/build_bans.php | 21 - .../includes/datastore/build_censor.php | 21 - .../includes/datastore/build_ranks.php | 21 - .../includes/datastore/build_smilies.php | 25 - legacy/library/includes/functions_cli.php | 161 --- legacy/library/includes/page_footer_dev.php | 96 -- legacy/library/language/en/email/blank.html | 1 - .../library/language/en/html/not_found.html | 3 - .../20250620001449_remove_demo_mode.php | 44 - legacy/profile.php | 62 - legacy/src/Cache/CacheManager.php | 473 -------- legacy/src/Cache/DatastoreManager.php | 470 -------- legacy/src/Cache/UnifiedCacheSystem.php | 454 ------- legacy/src/Captcha/CaptchaInterface.php | 38 - legacy/src/Config.php | 182 --- legacy/src/Database/Database.php | 1072 ----------------- legacy/src/Database/DatabaseDebugger.php | 569 --------- legacy/src/Database/DatabaseFactory.php | 101 -- legacy/src/Database/DebugSelection.php | 295 ----- legacy/src/Database/MigrationStatus.php | 305 ----- legacy/src/Dev.php | 442 ------- legacy/src/Emailer.php | 217 ---- legacy/src/Env.php | 116 -- legacy/src/Helpers/CronHelper.php | 138 --- legacy/src/Helpers/IsHelper.php | 38 - legacy/src/Helpers/StringHelper.php | 51 - legacy/src/Helpers/VersionHelper.php | 48 - .../DependencyInjection/Bootstrap.php | 89 -- .../DependencyInjection/Container.php | 49 - .../DependencyInjection/ContainerFactory.php | 93 -- .../Definitions/ApplicationDefinitions.php | 46 - .../Definitions/DomainDefinitions.php | 32 - .../Definitions/InfrastructureDefinitions.php | 84 -- .../Definitions/PresentationDefinitions.php | 52 - .../DependencyInjection/ServiceProvider.php | 24 - legacy/src/Legacy/Admin/Cron.php | 201 ---- legacy/src/Legacy/Admin/Torrent.php | 141 --- legacy/src/Sessions.php | 144 --- legacy/src/Whoops/DatabaseErrorHandler.php | 393 ------ .../src/Whoops/EnhancedPrettyPageHandler.php | 269 ----- legacy/src/helpers.php | 50 - legacy/styles/js/{libs => }/legacy.js | 0 resources/js/components/app-logo.tsx | 2 +- 61 files changed, 38 insertions(+), 8298 deletions(-) delete mode 100644 docs/static/img/logo.svg create mode 100644 docs/static/img/logos.svg delete mode 100644 legacy/admin/pagestart.php delete mode 100644 legacy/ajax.php delete mode 100644 legacy/cron.php delete mode 100644 legacy/info.php delete mode 100644 legacy/library/attach_mod/attachment_mod.php delete mode 100644 legacy/library/attach_mod/includes/functions_attach.php delete mode 100644 legacy/library/attach_mod/includes/functions_includes.php delete mode 100644 legacy/library/attach_mod/includes/functions_thumbs.php delete mode 100644 legacy/library/attach_mod/posting_attachments.php delete mode 100644 legacy/library/includes/cron/cron_check.php delete mode 100644 legacy/library/includes/cron/cron_run.php delete mode 100644 legacy/library/includes/cron/jobs/clean_search_results.php delete mode 100644 legacy/library/includes/cron/jobs/ds_update_cat_forums.php delete mode 100644 legacy/library/includes/cron/jobs/ds_update_stats.php delete mode 100644 legacy/library/includes/cron/jobs/sessions_cleanup.php delete mode 100644 legacy/library/includes/datastore/build_attach_extensions.php delete mode 100644 legacy/library/includes/datastore/build_bans.php delete mode 100644 legacy/library/includes/datastore/build_censor.php delete mode 100644 legacy/library/includes/datastore/build_ranks.php delete mode 100644 legacy/library/includes/datastore/build_smilies.php delete mode 100644 legacy/library/includes/functions_cli.php delete mode 100644 legacy/library/includes/page_footer_dev.php delete mode 100644 legacy/library/language/en/email/blank.html delete mode 100644 legacy/library/language/en/html/not_found.html delete mode 100644 legacy/migrations/20250620001449_remove_demo_mode.php delete mode 100644 legacy/profile.php delete mode 100644 legacy/src/Cache/CacheManager.php delete mode 100644 legacy/src/Cache/DatastoreManager.php delete mode 100644 legacy/src/Cache/UnifiedCacheSystem.php delete mode 100644 legacy/src/Captcha/CaptchaInterface.php delete mode 100644 legacy/src/Config.php delete mode 100644 legacy/src/Database/Database.php delete mode 100644 legacy/src/Database/DatabaseDebugger.php delete mode 100644 legacy/src/Database/DatabaseFactory.php delete mode 100644 legacy/src/Database/DebugSelection.php delete mode 100644 legacy/src/Database/MigrationStatus.php delete mode 100644 legacy/src/Dev.php delete mode 100644 legacy/src/Emailer.php delete mode 100644 legacy/src/Env.php delete mode 100644 legacy/src/Helpers/CronHelper.php delete mode 100644 legacy/src/Helpers/IsHelper.php delete mode 100644 legacy/src/Helpers/StringHelper.php delete mode 100644 legacy/src/Helpers/VersionHelper.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Bootstrap.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Container.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/ContainerFactory.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Definitions/ApplicationDefinitions.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Definitions/DomainDefinitions.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Definitions/InfrastructureDefinitions.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/Definitions/PresentationDefinitions.php delete mode 100644 legacy/src/Infrastructure/DependencyInjection/ServiceProvider.php delete mode 100644 legacy/src/Legacy/Admin/Cron.php delete mode 100644 legacy/src/Legacy/Admin/Torrent.php delete mode 100644 legacy/src/Sessions.php delete mode 100644 legacy/src/Whoops/DatabaseErrorHandler.php delete mode 100644 legacy/src/Whoops/EnhancedPrettyPageHandler.php delete mode 100644 legacy/src/helpers.php rename legacy/styles/js/{libs => }/legacy.js (100%) diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg deleted file mode 100644 index cb1c04239..000000000 --- a/docs/static/img/logo.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - hexagon/logos/blue/logos-hexagon-blue - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/static/img/logos.svg b/docs/static/img/logos.svg new file mode 100644 index 000000000..ece447692 --- /dev/null +++ b/docs/static/img/logos.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/legacy/admin/pagestart.php b/legacy/admin/pagestart.php deleted file mode 100644 index e9f0378fd..000000000 --- a/legacy/admin/pagestart.php +++ /dev/null @@ -1,30 +0,0 @@ -session_start(); - -if (IS_GUEST) { - redirect(LOGIN_URL . '?redirect=admin/index.php'); -} - -if (!IS_ADMIN) { - bb_die($lang['NOT_ADMIN']); -} - -if (!$userdata['session_admin']) { - $redirect = url_arg($_SERVER['REQUEST_URI'], 'admin', 1); - redirect(LOGIN_URL . "?redirect=$redirect"); -} diff --git a/legacy/ajax.php b/legacy/ajax.php deleted file mode 100644 index abe638c8e..000000000 --- a/legacy/ajax.php +++ /dev/null @@ -1,45 +0,0 @@ -init(); - -// Init userdata -$user->session_start(); - -// Load actions required modules -switch ($ajax->action) { - case 'view_post': - case 'posts': - case 'post_mod_comment': - require INC_DIR . '/bbcode.php'; - break; - - case 'view_torrent': - case 'mod_action': - case 'change_tor_status': - case 'change_torrent': - case 'passkey': - require ATTACH_DIR . '/attachment_mod.php'; - break; -} - -$ajax->exec(); - -/** - * @deprecated ajax_common - * Dirty class removed from here since 2.2.0 - * To add new actions see at src/Ajax.php - */ diff --git a/legacy/cron.php b/legacy/cron.php deleted file mode 100644 index d5c7a5823..000000000 --- a/legacy/cron.php +++ /dev/null @@ -1,13 +0,0 @@ -session_start(); - -$info = []; -$htmlDir = LANG_DIR . 'html/'; -$show = isset($_REQUEST['show']) ? (string)$_REQUEST['show'] : ''; - -switch ($show) { - case 'advert': - $info['title'] = $lang['ADVERT']; - $info['src'] = 'advert.html'; - break; - - case 'copyright_holders': - $info['title'] = $lang['COPYRIGHT_HOLDERS']; - $info['src'] = 'copyright_holders.html'; - break; - - case 'user_agreement': - $info['title'] = $lang['USER_AGREEMENT']; - $info['src'] = 'user_agreement.html'; - break; - - default: - case 'not_found': - $info['title'] = $lang['NOT_FOUND']; - $info['src'] = 'not_found.html'; - break; -} - -$require = is_file($htmlDir . $info['src']) ? ($htmlDir . $info['src']) : false; - -$template->assign_vars([ - 'PAGE_TITLE' => mb_strtoupper($info['title'], DEFAULT_CHARSET), - 'REQUIRE' => $require ? file_get_contents($require) : $lang['NOT_FOUND'], -]); - -print_page('info.tpl', 'simple'); diff --git a/legacy/library/attach_mod/attachment_mod.php b/legacy/library/attach_mod/attachment_mod.php deleted file mode 100644 index 16fa06a8b..000000000 --- a/legacy/library/attach_mod/attachment_mod.php +++ /dev/null @@ -1,77 +0,0 @@ -get('default_lang') . '/' . $language_file . '.php'; - if (file_exists($file)) { - return config()->get('default_lang'); - } - - $file = LANG_ROOT_DIR . '/' . $attach_config['board_lang'] . '/' . $language_file . '.php'; - if (file_exists($file)) { - return $attach_config['board_lang']; - } - - bb_die('Attachment mod language file does not exist: language/' . $attach_config['board_lang'] . '/' . $language_file . '.php'); -} - -/** - * Get attachment mod configuration - */ -function get_config() -{ - $attach_config = []; - - $sql = 'SELECT * FROM ' . BB_ATTACH_CONFIG; - - if (!($result = DB()->sql_query($sql))) { - bb_die('Could not query attachment information'); - } - - while ($row = DB()->sql_fetchrow($result)) { - $attach_config[$row['config_name']] = trim($row['config_value']); - } - - // We assign the original default board language here, because it gets overwritten later with the users default language - $attach_config['board_lang'] = trim(config()->get('default_lang')); - - return $attach_config; -} - -// Get Attachment Config -$attach_config = []; - -if (!$attach_config = CACHE('bb_cache')->get('attach_config')) { - $attach_config = get_config(); - CACHE('bb_cache')->set('attach_config', $attach_config, 86400); -} - -include ATTACH_DIR . '/displaying.php'; -include ATTACH_DIR . '/posting_attachments.php'; - -$upload_dir = $attach_config['upload_dir']; diff --git a/legacy/library/attach_mod/includes/functions_attach.php b/legacy/library/attach_mod/includes/functions_attach.php deleted file mode 100644 index e2d7bc67a..000000000 --- a/legacy/library/attach_mod/includes/functions_attach.php +++ /dev/null @@ -1,389 +0,0 @@ - 4096) { - return; - } - - if ($number < $base) { - return $chars[$number]; - } - - $hexval = ''; - - while ($number > 0) { - $remainder = $number % $base; - - if ($remainder < $base) { - $hexval = $chars[$remainder] . $hexval; - } - - $number = floor($number / $base); - } - - return $hexval; -} - -/** - * base64todec function - */ -function base64_unpack($string) -{ - $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-'; - $base = strlen($chars); - - $length = strlen($string); - $number = 0; - - for ($i = 1; $i <= $length; $i++) { - $pos = $length - $i; - $operand = strpos($chars, (string)$string[$pos]); - $exponent = $base ** ($i - 1); - $decValue = $operand * $exponent; - $number += $decValue; - } - - return $number; -} - -/** - * Per Forum based Extension Group Permissions (Encode Number) -> Theoretically up to 158 Forums saveable. :) - * We are using a base of 64, but splitting it to one-char and two-char numbers. :) - */ -function auth_pack($auth_array) -{ - $one_char_encoding = '#'; - $two_char_encoding = '.'; - $one_char = $two_char = false; - $auth_cache = ''; - - foreach ($auth_array as $i => $iValue) { - $val = base64_pack((int)$auth_array[$i]); - if (strlen($val) == 1 && !$one_char) { - $auth_cache .= $one_char_encoding; - $one_char = true; - } elseif (strlen($val) == 2 && !$two_char) { - $auth_cache .= $two_char_encoding; - $two_char = true; - } - - $auth_cache .= $val; - } - - return $auth_cache; -} - -/** - * Reverse the auth_pack process - */ -function auth_unpack($auth_cache) -{ - $one_char_encoding = '#'; - $two_char_encoding = '.'; - - $auth = []; - $auth_len = 1; - - for ($pos = 0, $posMax = strlen($auth_cache); $pos < $posMax; $pos += $auth_len) { - $forum_auth = $auth_cache[$pos]; - if ($forum_auth == $one_char_encoding) { - $auth_len = 1; - continue; - } - - if ($forum_auth == $two_char_encoding) { - $auth_len = 2; - $pos--; - continue; - } - - $forum_auth = substr($auth_cache, $pos, $auth_len); - $forum_id = base64_unpack($forum_auth); - $auth[] = (int)$forum_id; - } - return $auth; -} - -/** - * Used for determining if Forum ID is authed, please use this Function on all Posting Screens - */ -function is_forum_authed($auth_cache, $check_forum_id) -{ - $one_char_encoding = '#'; - $two_char_encoding = '.'; - - if (trim($auth_cache) == '') { - return true; - } - - $auth = []; - $auth_len = 1; - - for ($pos = 0, $posMax = strlen($auth_cache); $pos < $posMax; $pos += $auth_len) { - $forum_auth = $auth_cache[$pos]; - if ($forum_auth == $one_char_encoding) { - $auth_len = 1; - continue; - } - - if ($forum_auth == $two_char_encoding) { - $auth_len = 2; - $pos--; - continue; - } - - $forum_auth = substr($auth_cache, $pos, $auth_len); - $forum_id = (int)base64_unpack($forum_auth); - if ($forum_id == $check_forum_id) { - return true; - } - } - return false; -} - -/** - * Deletes an Attachment - */ -function unlink_attach($filename, $mode = false) -{ - global $upload_dir, $attach_config; - - $filename = basename($filename); - - if ($mode == MODE_THUMBNAIL) { - $filename = $upload_dir . '/' . THUMB_DIR . '/t_' . $filename; - } else { - $filename = $upload_dir . '/' . $filename; - } - - return @unlink($filename); -} - -/** - * Physical Filename stored already ? - */ -function physical_filename_already_stored($filename) -{ - if ($filename == '') { - return false; - } - - $filename = basename($filename); - - $sql = 'SELECT attach_id - FROM ' . BB_ATTACHMENTS_DESC . " - WHERE physical_filename = '" . DB()->escape($filename) . "' - LIMIT 1"; - - if (!($result = DB()->sql_query($sql))) { - bb_die('Could not get attachment information for filename: ' . htmlspecialchars($filename)); - } - $num_rows = DB()->num_rows($result); - DB()->sql_freeresult($result); - - return $num_rows != 0; -} - -/** - * get all attachments from a post (could be an post array too) - */ -function get_attachments_from_post($post_id_array) -{ - global $attach_config; - - $attachments = []; - - if (!is_array($post_id_array)) { - if (empty($post_id_array)) { - return $attachments; - } - - $post_id = (int)$post_id_array; - - $post_id_array = []; - $post_id_array[] = $post_id; - } - - $post_id_array = implode(', ', array_map('\intval', $post_id_array)); - - if ($post_id_array == '') { - return $attachments; - } - - $display_order = ((int)$attach_config['display_order'] == 0) ? 'DESC' : 'ASC'; - - $sql = 'SELECT a.post_id, d.* - FROM ' . BB_ATTACHMENTS . ' a, ' . BB_ATTACHMENTS_DESC . " d - WHERE a.post_id IN ($post_id_array) - AND a.attach_id = d.attach_id - ORDER BY d.filetime $display_order"; - - if (!($result = DB()->sql_query($sql))) { - bb_die('Could not get attachment informations for post number ' . $post_id_array); - } - - $num_rows = DB()->num_rows($result); - $attachments = DB()->sql_fetchrowset($result); - DB()->sql_freeresult($result); - - if ($num_rows == 0) { - return []; - } - - return $attachments; -} - -/** - * Get allowed Extensions and their respective Values - */ -function get_extension_informations() -{ - return $GLOBALS['datastore']->get('attach_extensions'); -} - -// -// Sync Topic -// -function attachment_sync_topic($topics) -{ - if (is_array($topics)) { - $topics = implode(',', $topics); - } - $posts_without_attach = $topics_without_attach = []; - - // Check orphan post_attachment markers - $sql = "SELECT p.post_id - FROM " . BB_POSTS . " p - LEFT JOIN " . BB_ATTACHMENTS . " a USING(post_id) - WHERE p.topic_id IN($topics) - AND p.post_attachment = 1 - AND a.post_id IS NULL"; - - if ($rowset = DB()->fetch_rowset($sql)) { - foreach ($rowset as $row) { - $posts_without_attach[] = $row['post_id']; - } - if ($posts_sql = implode(',', $posts_without_attach)) { - DB()->query("UPDATE " . BB_POSTS . " SET post_attachment = 0 WHERE post_id IN($posts_sql)"); - } - } - - // Update missing topic_attachment markers - DB()->query(" - UPDATE " . BB_TOPICS . " t, " . BB_POSTS . " p SET - t.topic_attachment = 1 - WHERE p.topic_id IN($topics) - AND p.post_attachment = 1 - AND p.topic_id = t.topic_id - "); - - // Fix orphan topic_attachment markers - $sql = "SELECT t.topic_id - FROM " . BB_POSTS . " p, " . BB_TOPICS . " t - WHERE t.topic_id = p.topic_id - AND t.topic_id IN($topics) - AND t.topic_attachment = 1 - GROUP BY p.topic_id - HAVING SUM(p.post_attachment) = 0"; - - if ($rowset = DB()->fetch_rowset($sql)) { - foreach ($rowset as $row) { - $topics_without_attach[] = $row['topic_id']; - } - if ($topics_sql = implode(',', $topics_without_attach)) { - DB()->query("UPDATE " . BB_TOPICS . " SET topic_attachment = 0 WHERE topic_id IN($topics_sql)"); - } - } -} - -/** - * _set_var - * - * Set variable, used by {@link get_var the get_var function} - * - * @private - */ -function _set_var(&$result, $var, $type, $multibyte = false) -{ - settype($var, $type); - $result = $var; - - if ($type == 'string') { - $result = trim(str_replace(["\r\n", "\r", '\xFF'], ["\n", "\n", ' '], $result)); - // 2.0.x is doing addslashes on all variables - $result = stripslashes($result); - if ($multibyte) { - $result = preg_replace('#&(\#[0-9]+;)#', '&\1', $result); - } - } -} - -/** - * Used to get passed variable - * - * @param $var_name - * @param $default - * @param bool $multibyte - * @return array|string - */ -function get_var($var_name, $default, $multibyte = false) -{ - $type = null; - if (!isset($_REQUEST[$var_name]) || - (is_array($_REQUEST[$var_name]) && !is_array($default)) || - (is_array($default) && !is_array($_REQUEST[$var_name]))) { - return (is_array($default)) ? [] : $default; - } - - $var = $_REQUEST[$var_name]; - - if (!is_array($default)) { - $type = gettype($default); - $key_type = null; - } else { - foreach ($default as $key_type => $type) { - $key_type = gettype($key_type); - $type = gettype($type); - } - } - - if (is_array($var)) { - $_var = $var; - $var = []; - - foreach ($_var as $k => $v) { - if (is_array($v)) { - foreach ($v as $_k => $_v) { - _set_var($k, $k, $key_type); - _set_var($_k, $_k, $key_type); - _set_var($var[$k][$_k], $_v, $type, $multibyte); - } - } else { - _set_var($k, $k, $key_type); - _set_var($var[$k], $v, $type, $multibyte); - } - } - } else { - _set_var($var, $var, $type, $multibyte); - } - - return $var; -} diff --git a/legacy/library/attach_mod/includes/functions_includes.php b/legacy/library/attach_mod/includes/functions_includes.php deleted file mode 100644 index 6528e6c06..000000000 --- a/legacy/library/attach_mod/includes/functions_includes.php +++ /dev/null @@ -1,156 +0,0 @@ -sql_query($sql))) { - bb_die('Unable to get quota settings #1'); - } - - $pm_quota = $upload_quota = 0; - - if ($row = DB()->sql_fetchrow($result)) { - do { - if ($row['quota_type'] == QUOTA_UPLOAD_LIMIT) { - $upload_quota = $row['quota_limit_id']; - } elseif ($row['quota_type'] == QUOTA_PM_LIMIT) { - $pm_quota = $row['quota_limit_id']; - } - } while ($row = DB()->sql_fetchrow($result)); - } else { - // Set Default Quota Limit - $upload_quota = $attach_config['default_upload_quota']; - $pm_quota = $attach_config['default_pm_quota']; - } - DB()->sql_freeresult($result); - - $template->assign_vars([ - 'S_SELECT_UPLOAD_QUOTA' => quota_limit_select('user_upload_quota', $upload_quota), - 'S_SELECT_PM_QUOTA' => quota_limit_select('user_pm_quota', $pm_quota) - ]); - } - - if ($admin_mode == 'user' && $submit && @$_POST['delete_user']) { - process_quota_settings($admin_mode, $user_id, QUOTA_UPLOAD_LIMIT, 0); - process_quota_settings($admin_mode, $user_id, QUOTA_PM_LIMIT, 0); - } elseif ($admin_mode == 'user' && $submit && $mode == 'save') { - // Get the contents - $upload_quota = get_var('user_upload_quota', 0); - $pm_quota = get_var('user_pm_quota', 0); - - process_quota_settings($admin_mode, $user_id, QUOTA_UPLOAD_LIMIT, $upload_quota); - process_quota_settings($admin_mode, $user_id, QUOTA_PM_LIMIT, $pm_quota); - } - - if ($admin_mode == 'group' && $mode == 'newgroup') { - return; - } - - if ($admin_mode == 'group' && !$submit && isset($_POST['edit'])) { - // Get group id again - $group_id = get_var(POST_GROUPS_URL, 0); - - // Show the contents - $sql = 'SELECT quota_limit_id, quota_type FROM ' . BB_QUOTA . ' WHERE group_id = ' . (int)$group_id; - - if (!($result = DB()->sql_query($sql))) { - bb_die('Unable to get quota settings #2'); - } - - $pm_quota = $upload_quota = 0; - - if ($row = DB()->sql_fetchrow($result)) { - do { - if ($row['quota_type'] == QUOTA_UPLOAD_LIMIT) { - $upload_quota = $row['quota_limit_id']; - } elseif ($row['quota_type'] == QUOTA_PM_LIMIT) { - $pm_quota = $row['quota_limit_id']; - } - } while ($row = DB()->sql_fetchrow($result)); - } else { - // Set Default Quota Limit - $upload_quota = $attach_config['default_upload_quota']; - $pm_quota = $attach_config['default_pm_quota']; - } - DB()->sql_freeresult($result); - - $template->assign_vars([ - 'S_SELECT_UPLOAD_QUOTA' => quota_limit_select('group_upload_quota', $upload_quota), - 'S_SELECT_PM_QUOTA' => quota_limit_select('group_pm_quota', $pm_quota) - ]); - } - - if ($admin_mode == 'group' && $submit && isset($_POST['group_delete'])) { - $group_id = get_var(POST_GROUPS_URL, 0); - - process_quota_settings($admin_mode, $group_id, QUOTA_UPLOAD_LIMIT, 0); - process_quota_settings($admin_mode, $group_id, QUOTA_PM_LIMIT, 0); - } elseif ($admin_mode == 'group' && $submit) { - $group_id = get_var(POST_GROUPS_URL, 0); - - // Get the contents - $upload_quota = get_var('group_upload_quota', 0); - $pm_quota = get_var('group_pm_quota', 0); - - process_quota_settings($admin_mode, $group_id, QUOTA_UPLOAD_LIMIT, $upload_quota); - process_quota_settings($admin_mode, $group_id, QUOTA_PM_LIMIT, $pm_quota); - } -} diff --git a/legacy/library/attach_mod/includes/functions_thumbs.php b/legacy/library/attach_mod/includes/functions_thumbs.php deleted file mode 100644 index 65ac22227..000000000 --- a/legacy/library/attach_mod/includes/functions_thumbs.php +++ /dev/null @@ -1,57 +0,0 @@ -fromFile($source) - ->autoOrient() - ->resize(150) - ->toFile($newFile, $mimeType, ['quality' => 85]); - } catch (Exception $e) { - // Handle errors - throw new Exception($e->getMessage()); - } - - // Check the thumbnail existence after creating - if (!is_file($newFile)) { - return false; - } - - return true; -} diff --git a/legacy/library/attach_mod/posting_attachments.php b/legacy/library/attach_mod/posting_attachments.php deleted file mode 100644 index cfc5f7468..000000000 --- a/legacy/library/attach_mod/posting_attachments.php +++ /dev/null @@ -1,23 +0,0 @@ -posting_attachment_mod(); -} diff --git a/legacy/library/includes/cron/cron_check.php b/legacy/library/includes/cron/cron_check.php deleted file mode 100644 index 21392b138..000000000 --- a/legacy/library/includes/cron/cron_check.php +++ /dev/null @@ -1,41 +0,0 @@ -fetch_rowset(" - SELECT * FROM " . BB_CRON . " - WHERE cron_active = 1 - AND next_run <= NOW() - ORDER BY run_order -"); - -// Run cron jobs -if ($cron_jobs) { - bb_log(date('H:i:s - ') . getmypid() . ' --x- RUN jobs' . LOG_LF, CRON_LOG_DIR . '/cron_check'); - - foreach ($cron_jobs as $job) { - if ($job['disable_board']) { - TorrentPier\Helpers\CronHelper::disableBoard(); - break; - } - } - - require(CRON_DIR . 'cron_run.php'); - - // Update cron_last_check - bb_update_config(['cron_last_check' => TIMENOW + 10]); -} else { - bb_log(date('H:i:s - ') . getmypid() . ' --x- no active jobs found ----------------------------------------------' . LOG_LF, CRON_LOG_DIR . '/cron_check'); -} diff --git a/legacy/library/includes/cron/cron_run.php b/legacy/library/includes/cron/cron_run.php deleted file mode 100644 index 4b30dd61c..000000000 --- a/legacy/library/includes/cron/cron_run.php +++ /dev/null @@ -1,124 +0,0 @@ -query(" - SET SESSION - bulk_insert_buffer_size = 8*1024*1024 - , join_buffer_size = 4*1024*1024 - , read_buffer_size = 4*1024*1024 - , read_rnd_buffer_size = 8*1024*1024 - , sort_buffer_size = 4*1024*1024 - , tmp_table_size = 80*1024*1024 - , group_concat_max_len = 1*1024*1024 -"); - -// Restore vars at shutdown -DB()->add_shutdown_query(" - SET SESSION - bulk_insert_buffer_size = DEFAULT - , join_buffer_size = DEFAULT - , read_buffer_size = DEFAULT - , read_rnd_buffer_size = DEFAULT - , sort_buffer_size = DEFAULT - , tmp_table_size = DEFAULT - , group_concat_max_len = DEFAULT -"); - -// $cron_jobs obtained in cron_check.php -foreach ($cron_jobs as $job) { - $job_script = CRON_JOB_DIR . basename($job['cron_script']); - - if (is_file($job_script)) { - $cron_start_time = utime(); - $cron_runtime_log = []; - $cron_write_log = (CRON_LOG_ENABLED && (CRON_FORCE_LOG || $job['log_enabled'] >= 1)); - $cron_sql_log_file = CRON_LOG_DIR . '/SQL-' . basename($job['cron_script']); - - if ($cron_write_log) { - $msg = []; - $msg[] = 'start'; - $msg[] = date('m-d'); - $msg[] = date('H:i:s'); - $msg[] = sprintf('%05d', getmypid()); - $msg[] = $job['cron_title']; - $msg = implode(LOG_SEPR, $msg); - bb_log($msg . LOG_LF, CRON_LOG_DIR . '/' . CRON_LOG_FILE); - } - - if ($job['log_sql_queries']) { - DB()->log_next_query(100000, $cron_sql_log_file); - } - - set_time_limit(600); - require($job_script); - - if ($job['log_sql_queries']) { - DB()->log_next_query(0); - bb_log(LOG_LF, $cron_sql_log_file); - } - - if ($cron_write_log) { - $msg = []; - $msg[] = ' end'; - $msg[] = date('m-d'); - $msg[] = date('H:i:s'); - $msg[] = sprintf('%05d', getmypid()); - $msg[] = round(utime() - $cron_start_time) . '/' . round(utime() - TIMESTART) . ' sec'; - $msg = implode(LOG_SEPR, $msg); - $msg .= LOG_LF . '------=-------=----------=------=-------=----------'; - bb_log($msg . LOG_LF, CRON_LOG_DIR . '/' . CRON_LOG_FILE); - - if (is_array($cron_runtime_log)) { - $runtime_log_file = ($job['log_file']) ?: $job['cron_script']; - $cron_runtime_log[] = ''; - bb_log($cron_runtime_log, CRON_LOG_DIR . '/' . basename($runtime_log_file)); - } - } - - DB()->query(" - UPDATE " . BB_CRON . " SET - last_run = NOW(), - run_counter = run_counter + 1, - next_run = - CASE - WHEN schedule = 'hourly' THEN - DATE_ADD(NOW(), INTERVAL 1 HOUR) - WHEN schedule = 'daily' THEN - DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL TIME_TO_SEC(run_time) SECOND) - WHEN schedule = 'weekly' THEN - DATE_ADD( - DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(NOW()) DAY), INTERVAL 7 DAY), - INTERVAL CONCAT(ROUND(run_day-1), ' ', run_time) DAY_SECOND) - WHEN schedule = 'monthly' THEN - DATE_ADD( - DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(NOW())-1 DAY), INTERVAL 1 MONTH), - INTERVAL CONCAT(ROUND(run_day-1), ' ', run_time) DAY_SECOND) - ELSE - DATE_ADD(NOW(), INTERVAL TIME_TO_SEC(run_interval) SECOND) - END - WHERE cron_id = {$job['cron_id']} - LIMIT 1 - "); - - if (utime() - TIMESTART > 600) { - // so that daily scripts do not block interval scripts for a long time - return; - } - } else { - $cron_err_msg = "Can not run \"{$job['cron_title']}\" : file \"$job_script\" not found" . LOG_LF; - bb_log($cron_err_msg, 'cron_error'); - } -} diff --git a/legacy/library/includes/cron/jobs/clean_search_results.php b/legacy/library/includes/cron/jobs/clean_search_results.php deleted file mode 100644 index be10b12f6..000000000 --- a/legacy/library/includes/cron/jobs/clean_search_results.php +++ /dev/null @@ -1,19 +0,0 @@ -query(" - DELETE FROM " . BB_SEARCH . " - WHERE search_time < $search_results_expire -"); diff --git a/legacy/library/includes/cron/jobs/ds_update_cat_forums.php b/legacy/library/includes/cron/jobs/ds_update_cat_forums.php deleted file mode 100644 index 909db6910..000000000 --- a/legacy/library/includes/cron/jobs/ds_update_cat_forums.php +++ /dev/null @@ -1,14 +0,0 @@ -update('cat_forums'); diff --git a/legacy/library/includes/cron/jobs/ds_update_stats.php b/legacy/library/includes/cron/jobs/ds_update_stats.php deleted file mode 100644 index a9cdb08ba..000000000 --- a/legacy/library/includes/cron/jobs/ds_update_stats.php +++ /dev/null @@ -1,14 +0,0 @@ -update('stats'); diff --git a/legacy/library/includes/cron/jobs/sessions_cleanup.php b/legacy/library/includes/cron/jobs/sessions_cleanup.php deleted file mode 100644 index 1f6adbc5e..000000000 --- a/legacy/library/includes/cron/jobs/sessions_cleanup.php +++ /dev/null @@ -1,54 +0,0 @@ -get('user_session_duration'); -$admin_session_expire_time = TIMENOW - (int)config()->get('admin_session_duration'); - -$user_session_gc_time = $user_session_expire_time - (int)config()->get('user_session_gc_ttl'); -$admin_session_gc_time = $admin_session_expire_time; - -// ############################ Tables LOCKED ################################ -DB()->lock([ - BB_USERS . ' u', - BB_SESSIONS . ' s' -]); - -// Update user's session time -DB()->query(" - UPDATE - " . BB_USERS . " u, - " . BB_SESSIONS . " s - SET - u.user_session_time = IF(u.user_session_time < s.session_time, s.session_time, u.user_session_time) - WHERE - u.user_id = s.session_user_id - AND s.session_user_id != " . GUEST_UID . " - AND ( - (s.session_time < $user_session_expire_time AND s.session_admin = 0) - OR - (s.session_time < $admin_session_expire_time AND s.session_admin != 0) - ) -"); - -DB()->unlock(); -// ############################ Tables UNLOCKED ############################## - -// Delete staled sessions -DB()->query(" - DELETE s - FROM " . BB_SESSIONS . " s - WHERE - (s.session_time < $user_session_gc_time AND s.session_admin = 0) - OR - (s.session_time < $admin_session_gc_time AND s.session_admin != 0) -"); diff --git a/legacy/library/includes/datastore/build_attach_extensions.php b/legacy/library/includes/datastore/build_attach_extensions.php deleted file mode 100644 index e6950bbae..000000000 --- a/legacy/library/includes/datastore/build_attach_extensions.php +++ /dev/null @@ -1,21 +0,0 @@ -fetch_rowset(" - SELECT e.extension, g.cat_id, g.download_mode, g.upload_icon, g.allow_group FROM - " . BB_EXTENSIONS . " e, - " . BB_EXTENSION_GROUPS . " g - WHERE e.group_id = g.group_id -"); - -$this->store('attach_extensions', $extensions); diff --git a/legacy/library/includes/datastore/build_bans.php b/legacy/library/includes/datastore/build_bans.php deleted file mode 100644 index 0544e6328..000000000 --- a/legacy/library/includes/datastore/build_bans.php +++ /dev/null @@ -1,21 +0,0 @@ -fetch_rowset($sql) as $row) { - $bans[$row['ban_userid']] = $row; -} - -$this->store('ban_list', $bans); diff --git a/legacy/library/includes/datastore/build_censor.php b/legacy/library/includes/datastore/build_censor.php deleted file mode 100644 index 589d026b8..000000000 --- a/legacy/library/includes/datastore/build_censor.php +++ /dev/null @@ -1,21 +0,0 @@ -fetch_rowset($sql) as $row) { - $words[$row['word_id']] = $row; -} - -$this->store('censor', $words); diff --git a/legacy/library/includes/datastore/build_ranks.php b/legacy/library/includes/datastore/build_ranks.php deleted file mode 100644 index 733c55483..000000000 --- a/legacy/library/includes/datastore/build_ranks.php +++ /dev/null @@ -1,21 +0,0 @@ -fetch_rowset($sql) as $row) { - $ranks[$row['rank_id']] = $row; -} - -$this->store('ranks', $ranks); diff --git a/legacy/library/includes/datastore/build_smilies.php b/legacy/library/includes/datastore/build_smilies.php deleted file mode 100644 index 204a92e62..000000000 --- a/legacy/library/includes/datastore/build_smilies.php +++ /dev/null @@ -1,25 +0,0 @@ -fetch_rowset("SELECT * FROM " . BB_SMILIES); -sort($rowset); - -foreach ($rowset as $smile) { - $smilies['orig'][] = '#(?<=^|\W)' . preg_quote($smile['code'], '#') . '(?=$|\W)#'; - $smilies['repl'][] = ' ' . $smile['code'] . ''; - $smilies['smile'][] = $smile; -} - -$this->store('smile_replacements', $smilies); diff --git a/legacy/library/includes/functions_cli.php b/legacy/library/includes/functions_cli.php deleted file mode 100644 index 90c415e0f..000000000 --- a/legacy/library/includes/functions_cli.php +++ /dev/null @@ -1,161 +0,0 @@ -isDir()) { - removeDir($file->getPathname(), $withoutOutput); - } else { - removeFile($file->getPathname(), $withoutOutput); - } - } - - if (rmdir($dir)) { - if ($withoutOutput === false) { - echo "- Folder removed: $dir" . PHP_EOL; - } - } else { - if ($withoutOutput === false) { - echo "- Folder cannot be removed: $dir" . PHP_EOL; - } - exit; - } -} - -/** - * Colored console output - * - * @param string $str - * @param string $type - * @return void - */ -function out(string $str, string $type = ''): void -{ - echo match ($type) { - 'error' => "\033[31m$str \033[0m\n", - 'success' => "\033[32m$str \033[0m\n", - 'warning' => "\033[33m$str \033[0m\n", - 'info' => "\033[36m$str \033[0m\n", - 'debug' => "\033[90m$str \033[0m\n", - default => "$str\n", - }; -} - -/** - * Run process with realtime output - * - * @param string $cmd - * @param string|null $input - * @return int - */ -function runProcess(string $cmd, ?string $input = null): int -{ - $descriptorSpec = [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - - $process = proc_open($cmd, $descriptorSpec, $pipes); - - if (!is_resource($process)) { - out('- Could not start subprocess', 'error'); - return -1; - } - - // Write input if provided - if ($input !== null) { - fwrite($pipes[0], $input); - fclose($pipes[0]); - } - - // Read and print output in real-time - while (!feof($pipes[1])) { - echo stream_get_contents($pipes[1], 1); - flush(); // Flush output buffer for immediate display - } - - // Read and print error output - while (!feof($pipes[2])) { - echo stream_get_contents($pipes[2], 1); - flush(); - } - - fclose($pipes[1]); - fclose($pipes[2]); - - return proc_close($process); -} - -/** - * Setting permissions recursively - * - * @param string $dir - * @param int $dirPermissions - * @param int $filePermissions - * @return void - */ -function chmod_r(string $dir, int $dirPermissions, int $filePermissions): void -{ - $dp = opendir($dir); - while ($file = readdir($dp)) { - if (($file == '.') || ($file == '..')) { - continue; - } - - $fullPath = realpath($dir . '/' . $file); - if (is_dir($fullPath)) { - out("- Directory: $fullPath"); - chmod($fullPath, $dirPermissions); - chmod_r($fullPath, $dirPermissions, $filePermissions); - } elseif (is_file($fullPath)) { - // out("- File: $fullPath"); - chmod($fullPath, $filePermissions); - } else { - out("- Cannot find target path: $fullPath", 'error'); - return; - } - } - - closedir($dp); -} diff --git a/legacy/library/includes/page_footer_dev.php b/legacy/library/includes/page_footer_dev.php deleted file mode 100644 index 9528b479a..000000000 --- a/legacy/library/includes/page_footer_dev.php +++ /dev/null @@ -1,96 +0,0 @@ - - - - -do_explain)) { - $db_obj->explain('display'); - } - } catch (\Exception $e) { - // Skip if server not available - } - } -} - -$sql_log = !empty($_COOKIE['sql_log']) ? dev()->getSqlDebugLog() : false; - -if ($sql_log) { - echo '
' . $sql_log . '

'; -} -?> - - diff --git a/legacy/library/language/en/email/blank.html b/legacy/library/language/en/email/blank.html deleted file mode 100644 index eb059e7db..000000000 --- a/legacy/library/language/en/email/blank.html +++ /dev/null @@ -1 +0,0 @@ -{MESSAGE} diff --git a/legacy/library/language/en/html/not_found.html b/legacy/library/language/en/html/not_found.html deleted file mode 100644 index 53bd5aa7a..000000000 --- a/legacy/library/language/en/html/not_found.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

File not found

-
diff --git a/legacy/migrations/20250620001449_remove_demo_mode.php b/legacy/migrations/20250620001449_remove_demo_mode.php deleted file mode 100644 index d32f3e798..000000000 --- a/legacy/migrations/20250620001449_remove_demo_mode.php +++ /dev/null @@ -1,44 +0,0 @@ -table('bb_cron') - ->getAdapter() - ->execute("DELETE FROM bb_cron WHERE cron_script = 'demo_mode.php'"); - } - - /** - * Migrate Down. - */ - public function down(): void - { - // Restore the demo_mode.php cron job to bb_cron table - $this->table('bb_cron')->insert([ - 'cron_active' => 1, - 'cron_title' => 'Demo mode', - 'cron_script' => 'demo_mode.php', - 'schedule' => 'daily', - 'run_day' => null, - 'run_time' => '05:00:00', - 'run_order' => 255, - 'last_run' => '1900-01-01 00:00:00', - 'next_run' => '1900-01-01 00:00:00', - 'run_interval' => null, - 'log_enabled' => 1, - 'log_file' => 'demo_mode_cron', - 'log_sql_queries' => 1, - 'disable_board' => 1, - 'run_counter' => 0 - ])->save(); - } -} diff --git a/legacy/profile.php b/legacy/profile.php deleted file mode 100644 index 9f036bf7e..000000000 --- a/legacy/profile.php +++ /dev/null @@ -1,62 +0,0 @@ -session_start(); - -set_die_append_msg(); -$mode = request_var('mode', 'viewprofile'); - -switch ($mode) { - case 'viewprofile': - require UCP_DIR . '/viewprofile.php'; - break; - - case 'register': - case 'editprofile': - if (IS_GUEST && $mode == 'editprofile') { - login_redirect(); - } - require UCP_DIR . '/register.php'; - break; - - case 'sendpassword': - require UCP_DIR . '/sendpasswd.php'; - break; - - case 'activate': - require UCP_DIR . '/activate.php'; - break; - - case 'email': - require UCP_DIR . '/email.php'; - break; - - case 'bonus': - if (IS_GUEST) { - login_redirect(); - } - require UCP_DIR . '/bonus.php'; - break; - - case 'watch': - if (IS_GUEST) { - login_redirect(); - } - require UCP_DIR . '/topic_watch.php'; - break; - - default: - bb_die('Invalid mode'); -} diff --git a/legacy/src/Cache/CacheManager.php b/legacy/src/Cache/CacheManager.php deleted file mode 100644 index 32f3cbe40..000000000 --- a/legacy/src/Cache/CacheManager.php +++ /dev/null @@ -1,473 +0,0 @@ -storage = $storage; - $this->prefix = $config['prefix'] ?? 'tp_'; - $this->engine = $config['engine'] ?? 'Unknown'; - - // Create Nette Cache instance with namespace - $this->cache = new Cache($this->storage, $namespace); - - // Enable debug if allowed - $this->dbg_enabled = dev()->checkSqlDebugAllowed(); - } - - /** - * Get singleton instance (called by UnifiedCacheSystem) - * - * @param string $namespace - * @param Storage $storage Pre-built storage instance - * @param array $config - * @return self - */ - public static function getInstance(string $namespace, Storage $storage, array $config): self - { - $key = $namespace . '_' . md5(serialize($config)); - - if (!isset(self::$instances[$key])) { - self::$instances[$key] = new self($namespace, $storage, $config); - } - - return self::$instances[$key]; - } - - - /** - * Cache get method (Legacy Cache API) - * - * @param string $name - * @return mixed - */ - public function get(string $name): mixed - { - $key = $this->prefix . $name; - - $this->cur_query = "cache->get('$key')"; - $this->debug('start'); - - $result = $this->cache->load($key); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - // Convert null to false for backward compatibility with legacy cache system - return $result ?? false; - } - - /** - * Cache set method (Legacy Cache API) - * - * @param string $name - * @param mixed $value - * @param int $ttl - * @return bool - */ - public function set(string $name, mixed $value, int $ttl = 604800): bool - { - $key = $this->prefix . $name; - - $this->cur_query = "cache->set('$key')"; - $this->debug('start'); - - $dependencies = []; - if ($ttl > 0) { - $dependencies[Cache::Expire] = $ttl . ' seconds'; - } - - try { - $this->cache->save($key, $value, $dependencies); - $result = true; - } catch (\Exception $e) { - $result = false; - } - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - return $result; - } - - /** - * Cache remove method (Legacy Cache API) - * - * @param string|null $name - * @return bool - */ - public function rm(?string $name = null): bool - { - if ($name === null) { - // Remove all items in this namespace - $this->cur_query = "cache->clean(all)"; - $this->debug('start'); - - $this->cache->clean([Cache::All => true]); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - return true; - } - - $key = $this->prefix . $name; - - $this->cur_query = "cache->remove('$key')"; - $this->debug('start'); - - $this->cache->remove($key); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - return true; - } - - /** - * Advanced Nette Caching methods - */ - - /** - * Load with callback (Nette native method) - * - * @param string $key - * @param callable|null $callback - * @param array $dependencies - * @return mixed - */ - public function load(string $key, ?callable $callback = null, array $dependencies = []): mixed - { - $fullKey = $this->prefix . $key; - - $this->cur_query = "cache->load('$fullKey')"; - $this->debug('start'); - - $result = $this->cache->load($fullKey, $callback, $dependencies); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - // Convert null to false for backward compatibility, but only if no callback was provided - // When callback is provided, null indicates the callback was executed and returned null - return ($result === null && $callback === null) ? false : $result; - } - - /** - * Save with dependencies - * - * @param string $key - * @param mixed $value - * @param array $dependencies - * @return void - */ - public function save(string $key, mixed $value, array $dependencies = []): void - { - $fullKey = $this->prefix . $key; - - $this->cur_query = "cache->save('$fullKey')"; - $this->debug('start'); - - $this->cache->save($fullKey, $value, $dependencies); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - } - - /** - * Clean cache by criteria - * - * @param array $conditions - * @return void - */ - public function clean(array $conditions = []): void - { - $this->cur_query = "cache->clean(" . json_encode($conditions) . ")"; - $this->debug('start'); - - $this->cache->clean($conditions); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - } - - /** - * Bulk load - * - * @param array $keys - * @param callable|null $callback - * @return array - */ - public function bulkLoad(array $keys, ?callable $callback = null): array - { - $prefixedKeys = array_map(fn($key) => $this->prefix . $key, $keys); - - $this->cur_query = "cache->bulkLoad(" . count($keys) . " keys)"; - $this->debug('start'); - - $result = $this->cache->bulkLoad($prefixedKeys, $callback); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - return $result; - } - - /** - * Memoize function call - * - * @param callable $function - * @param mixed ...$args - * @return mixed - */ - public function call(callable $function, ...$args): mixed - { - $this->cur_query = "cache->call(" . (is_string($function) ? $function : 'callable') . ")"; - $this->debug('start'); - - $result = $this->cache->call($function, ...$args); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - - return $result; - } - - /** - * Wrap function for memoization - * - * @param callable $function - * @return callable - */ - public function wrap(callable $function): callable - { - return $this->cache->wrap($function); - } - - /** - * Capture output - * - * @param string $key - * @return \Nette\Caching\OutputHelper|null - */ - public function capture(string $key): ?\Nette\Caching\OutputHelper - { - $fullKey = $this->prefix . $key; - return $this->cache->capture($fullKey); - } - - /** - * Remove specific key - * - * @param string $key - * @return void - */ - public function remove(string $key): void - { - $fullKey = $this->prefix . $key; - - $this->cur_query = "cache->remove('$fullKey')"; - $this->debug('start'); - - $this->cache->remove($fullKey); - - $this->debug('stop'); - $this->cur_query = null; - $this->num_queries++; - } - - /** - * Debug method (backward compatibility) - * - * @param string $mode - * @param string|null $cur_query - * @return void - */ - public function debug(string $mode, ?string $cur_query = null): void - { - if (!$this->dbg_enabled) { - return; - } - - $id =& $this->dbg_id; - $dbg =& $this->dbg[$id]; - - switch ($mode) { - case 'start': - $this->sql_starttime = utime(); - $dbg['sql'] = dev()->formatShortQuery($cur_query ?? $this->cur_query); - $dbg['src'] = $this->debug_find_source(); - $dbg['file'] = $this->debug_find_source('file'); - $dbg['line'] = $this->debug_find_source('line'); - $dbg['time'] = ''; - break; - case 'stop': - $this->cur_query_time = utime() - $this->sql_starttime; - $this->sql_timetotal += $this->cur_query_time; - $dbg['time'] = $this->cur_query_time; - $id++; - break; - default: - bb_simple_die('[Cache] Incorrect debug mode'); - break; - } - } - - /** - * Find caller source (backward compatibility) - * - * @param string $mode - * @return string - */ - public function debug_find_source(string $mode = 'all'): string - { - if (!SQL_PREPEND_SRC) { - return 'src disabled'; - } - foreach (debug_backtrace() as $trace) { - if (!empty($trace['file']) && $trace['file'] !== __FILE__) { - switch ($mode) { - case 'file': - return $trace['file']; - case 'line': - return (string)$trace['line']; - case 'all': - default: - return hide_bb_path($trace['file']) . '(' . $trace['line'] . ')'; - } - } - } - return 'src not found'; - } - - /** - * Get storage instance (for advanced usage) - * - * @return Storage - */ - public function getStorage(): Storage - { - return $this->storage; - } - - /** - * Get Nette Cache instance (for advanced usage) - * - * @return Cache - */ - public function getCache(): Cache - { - return $this->cache; - } - - /** - * Magic property getter for backward compatibility - * - * @param string $name - * @return mixed - */ - public function __get(string $name): mixed - { - // Handle legacy properties that don't exist in unified system - if ($name === 'db') { - // Legacy cache systems sometimes had a 'db' property for database storage - // Our unified system doesn't use separate database connections for cache - // Return an object with empty debug arrays for compatibility - return (object)[ - 'dbg' => [], - 'engine' => $this->engine, - 'sql_timetotal' => 0 - ]; - } - - throw new \InvalidArgumentException("Property '$name' not found in CacheManager"); - } -} diff --git a/legacy/src/Cache/DatastoreManager.php b/legacy/src/Cache/DatastoreManager.php deleted file mode 100644 index 89b1dc103..000000000 --- a/legacy/src/Cache/DatastoreManager.php +++ /dev/null @@ -1,470 +0,0 @@ - data) - */ - public array $data = []; - - /** - * Список элементов, которые будут извлечены из хранилища при первом же запросе get() - * до этого момента они ставятся в очередь $queued_items для дальнейшего извлечения _fetch()'ем - * всех элементов одним запросом - * array('title1', 'title2'...) - */ - public array $queued_items = []; - - /** - * 'title' => 'builder script name' inside "includes/datastore" dir - */ - public array $known_items = [ - 'cat_forums' => 'build_cat_forums.php', - 'censor' => 'build_censor.php', - 'check_updates' => 'build_check_updates.php', - 'jumpbox' => 'build_cat_forums.php', - 'viewtopic_forum_select' => 'build_cat_forums.php', - 'latest_news' => 'build_cat_forums.php', - 'network_news' => 'build_cat_forums.php', - 'ads' => 'build_cat_forums.php', - 'moderators' => 'build_moderators.php', - 'stats' => 'build_stats.php', - 'ranks' => 'build_ranks.php', - 'ban_list' => 'build_bans.php', - 'attach_extensions' => 'build_attach_extensions.php', - 'smile_replacements' => 'build_smilies.php', - ]; - - /** - * Engine type (for backward compatibility) - * @var string - */ - public string $engine; - - /** - * Debug properties (delegated to CacheManager) - */ - public int $num_queries = 0; - public float $sql_starttime = 0; - public float $sql_inittime = 0; - public float $sql_timetotal = 0; - public float $cur_query_time = 0; - public array $dbg = []; - public int $dbg_id = 0; - public bool $dbg_enabled = false; - public ?string $cur_query = null; - - /** - * Constructor - * - * @param Storage $storage Pre-built storage instance from UnifiedCacheSystem - * @param array $config - */ - private function __construct(Storage $storage, array $config) - { - // Create unified cache manager for datastore with pre-built storage - $this->cacheManager = CacheManager::getInstance('datastore', $storage, $config); - $this->engine = $this->cacheManager->engine; - $this->dbg_enabled = dev()->checkSqlDebugAllowed(); - } - - /** - * Get singleton instance - * - * @param Storage $storage Pre-built storage instance - * @param array $config - * @return self - */ - public static function getInstance(Storage $storage, array $config): self - { - if (self::$instance === null) { - self::$instance = new self($storage, $config); - } - - return self::$instance; - } - - /** - * Enqueue items for batch loading - * - * @param array $items - * @return void - */ - public function enqueue(array $items): void - { - foreach ($items as $item) { - if (!in_array($item, $this->queued_items) && !isset($this->data[$item])) { - $this->queued_items[] = $item; - } - } - } - - /** - * Get datastore item - * - * @param string $title - * @return mixed - */ - public function &get(string $title): mixed - { - if (!isset($this->data[$title])) { - $this->enqueue([$title]); - $this->_fetch(); - } - return $this->data[$title]; - } - - /** - * Store data into datastore - * - * @param string $item_name - * @param mixed $item_data - * @return bool - */ - public function store(string $item_name, mixed $item_data): bool - { - $this->data[$item_name] = $item_data; - - // Use cache manager with permanent storage (no TTL) - $dependencies = [ - // No time expiration for datastore items - they persist until manually updated - ]; - - try { - $this->cacheManager->save($item_name, $item_data, $dependencies); - $this->_updateDebugCounters(); - return true; - } catch (\Exception $e) { - $this->_updateDebugCounters(); - return false; - } - } - - /** - * Remove data from memory cache - * - * @param array|string $items - * @return void - */ - public function rm(array|string $items): void - { - foreach ((array)$items as $item) { - unset($this->data[$item]); - } - } - - /** - * Update datastore items - * - * @param array|string $items - * @return void - */ - public function update(array|string $items): void - { - if ($items == 'all') { - $items = array_keys(array_unique($this->known_items)); - } - foreach ((array)$items as $item) { - $this->_build_item($item); - } - } - - /** - * Clean datastore cache (for admin purposes) - * - * @return void - */ - public function clean(): void - { - foreach ($this->known_items as $title => $script_name) { - $this->cacheManager->remove($title); - } - $this->_updateDebugCounters(); - } - - /** - * Fetch items from store - * - * @return void - */ - public function _fetch(): void - { - $this->_fetch_from_store(); - - foreach ($this->queued_items as $title) { - // Only rebuild items that had true cache misses, not cached false/null values - if (!isset($this->data[$title]) || $this->data[$title] === '__CACHE_MISS__') { - $this->_build_item($title); - } - } - - $this->queued_items = []; - } - - /** - * Fetch items from cache store - * - * @return void - * @throws \Exception - */ - public function _fetch_from_store(): void - { - if (!$items = $this->queued_items) { - $src = $this->_debug_find_caller('enqueue'); - throw new \Exception("Datastore: no items queued for fetching [$src]"); - } - - // Use bulk loading for efficiency - $keys = $items; - $results = $this->cacheManager->bulkLoad($keys); - - foreach ($items as $item) { - $fullKey = $this->cacheManager->prefix . $item; - - // Distinguish between cache miss (null) and cached false value - if (array_key_exists($fullKey, $results)) { - // Item exists in cache (even if the value is null/false) - $this->data[$item] = $results[$fullKey]; - } else { - // True cache miss - item not found in cache at all - // Use a special sentinel value to mark as "needs building" - $this->data[$item] = '__CACHE_MISS__'; - } - } - - $this->_updateDebugCounters(); - } - - /** - * Build item using builder script - * - * @param string $title - * @return void - * @throws \Exception - */ - public function _build_item(string $title): void - { - if (!isset($this->known_items[$title])) { - throw new \Exception("Unknown datastore item: $title"); - } - - $file = INC_DIR . '/' . $this->ds_dir . '/' . $this->known_items[$title]; - if (!file_exists($file)) { - throw new \Exception("Datastore builder script not found: $file"); - } - - require $file; - } - - /** - * Find debug caller (backward compatibility) - * - * @param string $function_name - * @return string - */ - public function _debug_find_caller(string $function_name): string - { - foreach (debug_backtrace() as $trace) { - if (isset($trace['function']) && $trace['function'] === $function_name) { - return hide_bb_path($trace['file']) . '(' . $trace['line'] . ')'; - } - } - return 'caller not found'; - } - - /** - * Update debug counters from cache manager - * - * @return void - */ - private function _updateDebugCounters(): void - { - $this->num_queries = $this->cacheManager->num_queries; - $this->sql_timetotal = $this->cacheManager->sql_timetotal; - $this->dbg = $this->cacheManager->dbg; - $this->dbg_id = $this->cacheManager->dbg_id; - } - - /** - * Advanced Nette caching methods (extended functionality) - */ - - /** - * Load with dependencies - * - * @param string $key - * @param callable|null $callback - * @param array $dependencies - * @return mixed - */ - public function load(string $key, ?callable $callback = null, array $dependencies = []): mixed - { - return $this->cacheManager->load($key, $callback, $dependencies); - } - - /** - * Save with dependencies - * - * @param string $key - * @param mixed $value - * @param array $dependencies - * @return void - */ - public function save(string $key, mixed $value, array $dependencies = []): void - { - $this->cacheManager->save($key, $value, $dependencies); - $this->_updateDebugCounters(); - } - - /** - * Clean by criteria - * - * @param array $conditions - * @return void - */ - public function cleanByCriteria(array $conditions = []): void - { - $this->cacheManager->clean($conditions); - $this->_updateDebugCounters(); - } - - /** - * Clean by tags - * - * @param array $tags - * @return void - */ - public function cleanByTags(array $tags): void - { - $this->cacheManager->clean([Cache::Tags => $tags]); - $this->_updateDebugCounters(); - } - - /** - * Get cache manager instance (for advanced usage) - * - * @return CacheManager - */ - public function getCacheManager(): CacheManager - { - return $this->cacheManager; - } - - /** - * Get engine name - * - * @return string - */ - public function getEngine(): string - { - return $this->engine; - } - - /** - * Check if storage supports tags - * - * @return bool - */ - public function supportsTags(): bool - { - return $this->cacheManager->getStorage() instanceof \Nette\Caching\Storages\IJournal; - } - - /** - * Magic method to delegate unknown method calls to cache manager - * - * @param string $method - * @param array $args - * @return mixed - */ - public function __call(string $method, array $args): mixed - { - if (method_exists($this->cacheManager, $method)) { - $result = $this->cacheManager->$method(...$args); - $this->_updateDebugCounters(); - return $result; - } - - throw new \BadMethodCallException("Method '$method' not found in DatastoreManager or CacheManager"); - } - - /** - * Magic property getter to delegate to cache manager - * - * @param string $name - * @return mixed - */ - public function __get(string $name): mixed - { - if (property_exists($this->cacheManager, $name)) { - return $this->cacheManager->$name; - } - - // Handle legacy properties that don't exist in unified system - if ($name === 'db') { - // Legacy cache systems sometimes had a 'db' property for database storage - // Our unified system doesn't use separate database connections for cache - // Return an object with empty debug arrays for compatibility - return (object)[ - 'dbg' => [], - 'engine' => $this->engine, - 'sql_timetotal' => 0 - ]; - } - - throw new \InvalidArgumentException("Property '$name' not found"); - } - - /** - * Magic property setter to delegate to cache manager - * - * @param string $name - * @param mixed $value - * @return void - */ - public function __set(string $name, mixed $value): void - { - if (property_exists($this->cacheManager, $name)) { - $this->cacheManager->$name = $value; - } else { - throw new \InvalidArgumentException("Property '$name' not found"); - } - } -} diff --git a/legacy/src/Cache/UnifiedCacheSystem.php b/legacy/src/Cache/UnifiedCacheSystem.php deleted file mode 100644 index 07a617388..000000000 --- a/legacy/src/Cache/UnifiedCacheSystem.php +++ /dev/null @@ -1,454 +0,0 @@ -cfg = $cfg['cache'] ?? []; - - // Create stub cache manager - $stubStorage = new MemoryStorage(); - $stubConfig = [ - 'engine' => 'Memory', - 'prefix' => $this->cfg['prefix'] ?? 'tp_' - ]; - $this->stub = CacheManager::getInstance('__stub', $stubStorage, $stubConfig); - } - - /** - * Get cache manager instance (backward compatible with CACHE() function) - * - * @param string $cache_name - * @return CacheManager - */ - public function get_cache_obj(string $cache_name): CacheManager - { - if (!isset($this->ref[$cache_name])) { - if (!$engine_cfg = $this->cfg['engines'][$cache_name] ?? null) { - // Return stub for non-configured caches - $this->ref[$cache_name] = $this->stub; - } else { - $cache_type = $engine_cfg[0] ?? 'file'; - - if (!isset($this->managers[$cache_name])) { - // Build storage and config directly - $storage = $this->_buildStorage($cache_type, $cache_name); - $config = [ - 'engine' => $this->_getEngineType($cache_type), - 'prefix' => $this->cfg['prefix'] ?? 'tp_' - ]; - - $this->managers[$cache_name] = CacheManager::getInstance($cache_name, $storage, $config); - } - $this->ref[$cache_name] = $this->managers[$cache_name]; - } - } - - return $this->ref[$cache_name]; - } - - /** - * Get datastore manager instance - * - * @param string $datastore_type - * @return DatastoreManager - */ - public function getDatastore(string $datastore_type = 'file'): DatastoreManager - { - if ($this->datastore === null) { - // Build storage and config for datastore - $storage = $this->_buildDatastoreStorage($datastore_type); - $config = [ - 'engine' => $this->_getEngineType($datastore_type), - 'prefix' => $this->cfg['prefix'] ?? 'tp_' - ]; - - $this->datastore = DatastoreManager::getInstance($storage, $config); - } - - return $this->datastore; - } - - /** - * Build storage instance directly (eliminates redundancy with CacheManager) - * - * @param string $cache_type - * @param string $cache_name - * @return Storage - */ - private function _buildStorage(string $cache_type, string $cache_name): Storage - { - switch ($cache_type) { - case 'file': - case 'filecache': - case 'apcu': - case 'redis': - // Some deprecated cache types will fall back to file storage - $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; - - // Create directory automatically using TorrentPier's bb_mkdir function - if (!is_dir($dir) && !bb_mkdir($dir)) { - throw new \RuntimeException("Failed to create cache directory: $dir"); - } - - return new FileStorage($dir); - - case 'sqlite': - $dbFile = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '.db'; - - // Create parent directory for SQLite file - $dbDir = dirname($dbFile); - if (!is_dir($dbDir) && !bb_mkdir($dbDir)) { - throw new \RuntimeException("Failed to create cache directory for SQLite: $dbDir"); - } - - return new SQLiteStorage($dbFile); - - case 'memory': - return new MemoryStorage(); - - case 'memcached': - $memcachedConfig = $this->cfg['memcached'] ?? ['host' => '127.0.0.1', 'port' => 11211]; - $host = $memcachedConfig['host'] ?? '127.0.0.1'; - $port = $memcachedConfig['port'] ?? 11211; - return new MemcachedStorage("{$host}:{$port}"); - - default: - // Fallback to file storage - $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; - - // Create directory automatically using TorrentPier's bb_mkdir function - if (!is_dir($dir) && !bb_mkdir($dir)) { - throw new \RuntimeException("Failed to create cache directory: $dir"); - } - - return new FileStorage($dir); - } - } - - /** - * Get engine type name for debugging - * - * @param string $cache_type - * @return string - */ - private function _getEngineType(string $cache_type): string - { - return match ($cache_type) { - 'sqlite' => 'SQLite', - 'memory' => 'Memory', - 'memcached' => 'Memcached', - default => 'File', - }; - } - - /** - * Build datastore storage instance - * - * @param string $datastore_type - * @return Storage - */ - private function _buildDatastoreStorage(string $datastore_type): Storage - { - switch ($datastore_type) { - case 'file': - case 'filecache': - case 'apcu': - case 'redis': - // Some deprecated cache types will fall back to file storage - $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; - - // Create directory automatically using TorrentPier's bb_mkdir function - if (!is_dir($dir) && !bb_mkdir($dir)) { - throw new \RuntimeException("Failed to create datastore directory: $dir"); - } - - return new FileStorage($dir); - - case 'sqlite': - $dbFile = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore.db'; - - // Create parent directory for SQLite file - $dbDir = dirname($dbFile); - if (!is_dir($dbDir) && !bb_mkdir($dbDir)) { - throw new \RuntimeException("Failed to create datastore directory for SQLite: $dbDir"); - } - - return new SQLiteStorage($dbFile); - - case 'memory': - return new MemoryStorage(); - - case 'memcached': - $memcachedConfig = $this->cfg['memcached'] ?? ['host' => '127.0.0.1', 'port' => 11211]; - $host = $memcachedConfig['host'] ?? '127.0.0.1'; - $port = $memcachedConfig['port'] ?? 11211; - return new MemcachedStorage("{$host}:{$port}"); - - default: - // Fallback to file storage - $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; - - // Create directory automatically using TorrentPier's bb_mkdir function - if (!is_dir($dir) && !bb_mkdir($dir)) { - throw new \RuntimeException("Failed to create datastore directory: $dir"); - } - - return new FileStorage($dir); - } - } - - /** - * Get all cache managers (for debugging) - * - * @return array - */ - public function getAllCacheManagers(): array - { - return $this->managers; - } - - /** - * Get configuration - * - * @return array - */ - public function getConfig(): array - { - return $this->cfg; - } - - /** - * Clear all caches - * - * @return void - */ - public function clearAll(): void - { - foreach ($this->managers as $manager) { - $manager->rm(); // Clear all items in namespace - } - - if ($this->datastore) { - $this->datastore->clean(); - } - } - - /** - * Get cache statistics - * - * @return array - */ - public function getStatistics(): array - { - $stats = [ - 'total_managers' => count($this->managers), - 'managers' => [] - ]; - - foreach ($this->managers as $name => $manager) { - $stats['managers'][$name] = [ - 'engine' => $manager->engine, - 'num_queries' => $manager->num_queries, - 'total_time' => $manager->sql_timetotal, - 'debug_enabled' => $manager->dbg_enabled - ]; - } - - if ($this->datastore) { - $stats['datastore'] = [ - 'engine' => $this->datastore->engine, - 'num_queries' => $this->datastore->num_queries, - 'total_time' => $this->datastore->sql_timetotal, - 'queued_items' => count($this->datastore->queued_items), - 'loaded_items' => count($this->datastore->data) - ]; - } - - return $stats; - } - - /** - * Magic method for backward compatibility - * Allows access to legacy properties like ->obj - * - * @param string $name - * @return mixed - */ - public function __get(string $name): mixed - { - switch ($name) { - case 'obj': - // Return array of cache objects for backward compatibility - $obj = ['__stub' => $this->stub]; - foreach ($this->managers as $cache_name => $manager) { - $obj[$cache_name] = $manager; - } - return $obj; - - case 'cfg': - return $this->cfg; - - case 'ref': - return $this->ref; - - default: - throw new \InvalidArgumentException("Property '$name' not found"); - } - } - - /** - * Create cache manager with advanced Nette features - * - * @param string $namespace - * @param array $config - * @return CacheManager - */ - public function createAdvancedCache(string $namespace, array $config = []): CacheManager - { - $fullConfig = array_merge($this->cfg, $config); - $fullConfig['prefix'] = $fullConfig['prefix'] ?? 'tp_'; - - // Build storage for the advanced cache - $storageType = $config['storage_type'] ?? 'file'; - $storage = $this->_buildStorage($storageType, $namespace); - $managerConfig = [ - 'engine' => $this->_getEngineType($storageType), - 'prefix' => $fullConfig['prefix'] - ]; - - return CacheManager::getInstance($namespace, $storage, $managerConfig); - } - - /** - * Create cache with file dependencies - * - * @param string $namespace - * @param array $files - * @return CacheManager - */ - public function createFileBasedCache(string $namespace, array $files = []): CacheManager - { - $cache = $this->createAdvancedCache($namespace); - - // Example usage: - // $value = $cache->load('key', function() use ($files) { - // return expensive_computation(); - // }, [Cache::Files => $files]); - - return $cache; - } - - /** - * Create cache with tags support - * - * @param string $namespace - * @return CacheManager - */ - public function createTaggedCache(string $namespace): CacheManager - { - // Use SQLite storage which supports tags via journal - $storage = $this->_buildStorage('sqlite', $namespace); - $config = [ - 'engine' => 'SQLite', - 'prefix' => $this->cfg['prefix'] ?? 'tp_' - ]; - - return CacheManager::getInstance($namespace, $storage, $config); - } - - /** - * Prevent cloning of the singleton instance - */ - private function __clone() - { - } - - /** - * Prevent unserialization of the singleton instance - */ - public function __wakeup() - { - throw new \Exception("Cannot unserialize a singleton."); - } -} diff --git a/legacy/src/Captcha/CaptchaInterface.php b/legacy/src/Captcha/CaptchaInterface.php deleted file mode 100644 index 21a749182..000000000 --- a/legacy/src/Captcha/CaptchaInterface.php +++ /dev/null @@ -1,38 +0,0 @@ -config = $config; - } - - /** - * Get the singleton instance of Config - */ - public static function getInstance(array $config = []): Config - { - if (self::$instance === null) { - self::$instance = new self($config); - } - return self::$instance; - } - - /** - * Initialize the config with the global $bb_cfg array - */ - public static function init(array $bb_cfg): Config - { - self::$instance = new self($bb_cfg); - return self::$instance; - } - - /** - * Get a configuration value by key - * Supports dot notation for nested arrays (e.g., 'db.host') - */ - public function get(string $key, mixed $default = null): mixed - { - if (str_contains($key, '.')) { - return $this->getNestedValue($key, $default); - } - - return $this->config[$key] ?? $default; - } - - /** - * Set a configuration value by key - * Supports dot notation for nested arrays - */ - public function set(string $key, mixed $value): void - { - if (str_contains($key, '.')) { - $this->setNestedValue($key, $value); - } else { - $this->config[$key] = $value; - } - } - - /** - * Check if a configuration key exists - * Supports dot notation for nested arrays - */ - public function has(string $key): bool - { - if (str_contains($key, '.')) { - return $this->getNestedValue($key) !== null; - } - - return array_key_exists($key, $this->config); - } - - /** - * Get all configuration values - */ - public function all(): array - { - return $this->config; - } - - /** - * Get a nested value using dot notation - */ - private function getNestedValue(string $key, mixed $default = null): mixed - { - $keys = explode('.', $key); - $value = $this->config; - - foreach ($keys as $k) { - if (!is_array($value) || !array_key_exists($k, $value)) { - return $default; - } - $value = $value[$k]; - } - - return $value; - } - - /** - * Set a nested value using dot notation - */ - private function setNestedValue(string $key, mixed $value): void - { - $keys = explode('.', $key); - $target = &$this->config; - - foreach ($keys as $k) { - if (!isset($target[$k]) || !is_array($target[$k])) { - $target[$k] = []; - } - $target = &$target[$k]; - } - - $target = $value; - } - - /** - * Merge additional configuration values - */ - public function merge(array $config): void - { - $this->config = array_merge_recursive($this->config, $config); - } - - /** - * Get a section of the configuration - */ - public function getSection(string $section): array - { - return $this->config[$section] ?? []; - } - - /** - * Magic method to allow property access - */ - public function __get(string $key): mixed - { - return $this->get($key); - } - - /** - * Magic method to allow property setting - */ - public function __set(string $key, mixed $value): void - { - $this->set($key, $value); - } - - /** - * Magic method to check if property exists - */ - public function __isset(string $key): bool - { - return $this->has($key); - } - - /** - * Prevent cloning of the singleton instance - */ - private function __clone() {} - - /** - * Prevent unserialization of the singleton instance - */ - public function __wakeup() - { - throw new \Exception("Cannot unserialize a singleton."); - } -} diff --git a/legacy/src/Database/Database.php b/legacy/src/Database/Database.php deleted file mode 100644 index 7157e22da..000000000 --- a/legacy/src/Database/Database.php +++ /dev/null @@ -1,1072 +0,0 @@ -cfg = array_combine($this->cfg_keys, $cfg_values); - $this->db_server = $server_name; - - // Initialize debugger - $this->debugger = new DatabaseDebugger($this); - - // Initialize our own tracking system (replaces the old $DBS global system) - $this->DBS = [ - 'log_file' => 'sql_queries', - 'log_counter' => 0, - 'num_queries' => 0, - 'sql_inittime' => 0, - 'sql_timetotal' => 0 - ]; - } - - /** - * Get singleton instance for default database - */ - public static function getInstance(?array $cfg_values = null, string $server_name = 'db'): self - { - if (self::$instance === null && $cfg_values !== null) { - self::$instance = new self($cfg_values, $server_name); - self::$instances[$server_name] = self::$instance; - } - - return self::$instance; - } - - /** - * Get instance for specific database server - */ - public static function getServerInstance(array $cfg_values, string $server_name): self - { - if (!isset(self::$instances[$server_name])) { - self::$instances[$server_name] = new self($cfg_values, $server_name); - - // If this is the first instance, set as default - if (self::$instance === null) { - self::$instance = self::$instances[$server_name]; - } - } - - return self::$instances[$server_name]; - } - - /** - * Initialize connection - */ - public function init(): void - { - if (!$this->inited) { - $this->connect(); - $this->inited = true; - $this->num_queries = 0; - $this->sql_inittime = $this->sql_timetotal; - - $this->DBS['sql_inittime'] += $this->sql_inittime; - } - } - - /** - * Open connection using Nette Database - */ - public function connect(): void - { - $this->cur_query = $this->debugger->dbg_enabled ? "connect to: {$this->cfg['dbhost']}:{$this->cfg['dbport']}" : 'connect'; - $this->debugger->debug('start'); - - // Build DSN - $dsn = "mysql:host={$this->cfg['dbhost']};port={$this->cfg['dbport']};dbname={$this->cfg['dbname']}"; - if (!empty($this->cfg['charset'])) { - $dsn .= ";charset={$this->cfg['charset']}"; - } - - // Create Nette Database connection - $this->connection = new Connection( - $dsn, - $this->cfg['dbuser'], - $this->cfg['dbpasswd'] - ); - - // Create Nette Database Explorer with all required dependencies - $storage = $this->getExistingCacheStorage(); - $this->explorer = new Explorer( - $this->connection, - new Structure($this->connection, $storage), - new DiscoveredConventions(new Structure($this->connection, $storage)), - $storage - ); - - $this->selected_db = $this->cfg['dbname']; - - register_shutdown_function([$this, 'close']); - - $this->debugger->debug('stop'); - $this->cur_query = null; - } - - /** - * Base query method (compatible with original) - */ - public function sql_query($query): ?ResultSet - { - if (!$this->connection) { - $this->init(); - } - - if (is_array($query)) { - $query = $this->build_sql($query); - } - - $query = '/* ' . $this->debugger->debug_find_source() . ' */ ' . $query; - $this->cur_query = $query; - $this->debugger->debug('start'); - - try { - $this->result = $this->connection->query($query); - - // Update affected rows count for operations that modify data - // For INSERT, UPDATE, DELETE operations, use getRowCount() - if ($this->result instanceof ResultSet) { - $this->last_affected_rows = $this->result->getRowCount(); - } else { - $this->last_affected_rows = 0; - } - } catch (\Exception $e) { - $this->debugger->log_error($e); - $this->result = null; - $this->last_affected_rows = 0; - } - - $this->debugger->debug('stop'); - $this->last_query = $this->cur_query; // Preserve for error reporting - $this->cur_query = null; - - if ($this->inited) { - $this->num_queries++; - $this->DBS['num_queries']++; - } - - return $this->result; - } - - /** - * Execute query WRAPPER (with error handling) - */ - public function query($query): ResultSet - { - if (!$result = $this->sql_query($query)) { - $this->trigger_error(); - } - - return $result; - } - - /** - * Return number of rows - */ - public function num_rows($result = false): int - { - if ($result || ($result = $this->result)) { - if ($result instanceof ResultSet) { - return $result->getRowCount(); - } - } - - return 0; - } - - /** - * Return number of affected rows - */ - public function affected_rows(): int - { - return $this->last_affected_rows; - } - - /** - * Fetch current row (compatible with original) - */ - public function sql_fetchrow($result, string $field_name = ''): mixed - { - if (!$result instanceof ResultSet) { - return false; - } - - try { - $row = $result->fetch(); - if (!$row) { - return false; - } - - // Convert Row to array for backward compatibility - // Nette Database Row extends ArrayHash, so we can cast it to array - $rowArray = (array)$row; - - if ($field_name) { - return $rowArray[$field_name] ?? false; - } - - return $rowArray; - } catch (\Exception $e) { - // Check if this is a duplicate column error - if (str_contains($e->getMessage(), 'Found duplicate columns')) { - // Log this as a problematic query that needs fixing - $this->debugger->logLegacyQuery($this->last_query ?? $this->cur_query ?? 'Unknown query', $e->getMessage()); - - // Automatically retry by re-executing the query with direct PDO - // This bypasses Nette's duplicate column check completely - try { - // Extract the clean SQL query - $cleanQuery = $this->last_query ?? $this->cur_query ?? ''; - // Remove Nette's debug comment - $cleanQuery = preg_replace('#^(\s*)(/\*)(.*)(\*/)(\s*)#', '', $cleanQuery); - - if (!$cleanQuery) { - throw new \RuntimeException('Could not extract clean query for PDO retry'); - } - - // Execute directly with PDO to bypass Nette's column checking - $stmt = $this->connection->getPdo()->prepare($cleanQuery); - $stmt->execute(); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); - - // PDO::FETCH_ASSOC automatically handles duplicate columns by keeping the last occurrence - // which matches MySQL's behavior for SELECT t.*, f.* queries - - if (!$row) { - return false; - } - - if ($field_name) { - return $row[$field_name] ?? false; - } - - return $row; - } catch (\Exception $retryException) { - // If PDO retry also fails, log and re-throw - $this->debugger->log_error($retryException); - throw $retryException; - } - } - - // Log the error including the query that caused it - $this->debugger->log_error($e); - - // Re-throw the exception so it can be handled by Whoops - throw $e; - } - } - - /** - * Alias of sql_fetchrow() - */ - public function fetch_next($result): mixed - { - return $this->sql_fetchrow($result); - } - - /** - * Fetch row WRAPPER (with error handling) - */ - public function fetch_row($query, string $field_name = ''): mixed - { - if (!$result = $this->sql_query($query)) { - $this->trigger_error(); - } - - try { - return $this->sql_fetchrow($result, $field_name); - } catch (\Exception $e) { - // Enhance the exception with query information - $enhancedException = new \RuntimeException( - "Database error during fetch_row: " . $e->getMessage() . - "\nProblematic Query: " . ($this->cur_query ?: $this->last_query ?: 'Unknown'), - $e->getCode(), - $e - ); - - // Log the enhanced error - $this->debugger->log_error($enhancedException); - - throw $enhancedException; - } - } - - /** - * Fetch all rows - */ - public function sql_fetchrowset($result, string $field_name = ''): array - { - if (!$result instanceof ResultSet) { - return []; - } - - $rowset = []; - - try { - while ($row = $result->fetch()) { - // Convert Row to array for backward compatibility - // Nette Database Row extends ArrayHash, so we can cast it to array - $rowArray = (array)$row; - $rowset[] = $field_name ? ($rowArray[$field_name] ?? null) : $rowArray; - } - } catch (\Exception $e) { - // Check if this is a duplicate column error - if (str_contains($e->getMessage(), 'Found duplicate columns')) { - // Log this as a problematic query that needs fixing - $this->debugger->logLegacyQuery($this->last_query ?? $this->cur_query ?? 'Unknown query', $e->getMessage()); - - // Automatically retry by re-executing the query with direct PDO - try { - // Extract the clean SQL query - $cleanQuery = $this->last_query ?? $this->cur_query ?? ''; - // Remove Nette's debug comment - $cleanQuery = preg_replace('#^(\s*)(/\*)(.*)(\*/)(\s*)#', '', $cleanQuery); - - if (!$cleanQuery) { - throw new \RuntimeException('Could not extract clean query for PDO retry'); - } - - // Execute directly with PDO to bypass Nette's column checking - $stmt = $this->connection->getPdo()->prepare($cleanQuery); - $stmt->execute(); - - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $rowset[] = $field_name ? ($row[$field_name] ?? null) : $row; - } - } catch (\Exception $retryException) { - // If PDO retry also fails, log and re-throw - $this->debugger->log_error($retryException); - throw $retryException; - } - } else { - // For other exceptions, just re-throw - $this->debugger->log_error($e); - throw $e; - } - } - - return $rowset; - } - - /** - * Fetch all rows WRAPPER (with error handling) - */ - public function fetch_rowset($query, string $field_name = ''): array - { - if (!$result = $this->sql_query($query)) { - $this->trigger_error(); - } - - return $this->sql_fetchrowset($result, $field_name); - } - - /** - * Get last inserted id after insert statement - */ - public function sql_nextid(): int - { - return $this->connection ? $this->connection->getInsertId() : 0; - } - - /** - * Free sql result - */ - public function sql_freeresult($result = false): void - { - // Nette Database handles resource cleanup automatically - if ($result === false || $result === $this->result) { - $this->result = null; - } - } - - /** - * Get Database Explorer table access with debug logging - */ - public function table(string $table): DebugSelection - { - if (!$this->explorer) { - $this->init(); - } - - $selection = $this->explorer->table($table); - - // Wrap the selection to capture queries for debug logging - return new DebugSelection($selection, $this); - } - - /** - * Get existing cache storage from TorrentPier's unified cache system - * - * @return \Nette\Caching\Storage - */ - private function getExistingCacheStorage(): \Nette\Caching\Storage - { - // Try to use the existing cache system if available - if (function_exists('CACHE')) { - try { - $cacheManager = CACHE('database_structure'); - return $cacheManager->getStorage(); - } catch (\Exception $e) { - // Fall back to DevNullStorage if cache system is not available yet - } - } - - // Fallback to a simple DevNullStorage if cache system is not available - return new \Nette\Caching\Storages\DevNullStorage(); - } - - /** - * Escape data used in sql query (using Nette Database) - */ - public function escape($v, bool $check_type = false, bool $dont_escape = false): string - { - if ($dont_escape) { - return (string)$v; - } - - if (!$check_type) { - return $this->escape_string((string)$v); - } - - switch (true) { - case is_string($v): - return "'" . $this->escape_string($v) . "'"; - case is_int($v): - return (string)$v; - case is_bool($v): - return $v ? '1' : '0'; - case is_float($v): - return "'$v'"; - case $v === null: - return 'NULL'; - default: - $this->trigger_error(__FUNCTION__ . ' - wrong params'); - return ''; - } - } - - /** - * Escape string using Nette Database - */ - public function escape_string(string $str): string - { - if (!$this->connection) { - $this->init(); - } - - // Remove quotes from quoted string - $quoted = $this->connection->quote($str); - return substr($quoted, 1, -1); - } - - /** - * Build SQL statement from array (maintaining compatibility) - */ - public function build_array(string $query_type, array $input_ary, bool $data_already_escaped = false, bool $check_data_type_in_escape = true): string - { - $fields = $values = $ary = []; - $dont_escape = $data_already_escaped; - $check_type = $check_data_type_in_escape; - - if (empty($input_ary)) { - $this->trigger_error(__FUNCTION__ . ' - wrong params: $input_ary is empty'); - } - - if ($query_type == 'INSERT') { - foreach ($input_ary as $field => $val) { - $fields[] = $field; - $values[] = $this->escape($val, $check_type, $dont_escape); - } - $fields = implode(', ', $fields); - $values = implode(', ', $values); - $query = "($fields)\nVALUES\n($values)"; - } elseif ($query_type == 'INSERT_SELECT') { - foreach ($input_ary as $field => $val) { - $fields[] = $field; - $values[] = $this->escape($val, $check_type, $dont_escape); - } - $fields = implode(', ', $fields); - $values = implode(', ', $values); - $query = "($fields)\nSELECT\n$values"; - } elseif ($query_type == 'MULTI_INSERT') { - foreach ($input_ary as $id => $sql_ary) { - foreach ($sql_ary as $field => $val) { - $values[] = $this->escape($val, $check_type, $dont_escape); - } - $ary[] = '(' . implode(', ', $values) . ')'; - $values = []; - } - $fields = implode(', ', array_keys($input_ary[0])); - $values = implode(",\n", $ary); - $query = "($fields)\nVALUES\n$values"; - } elseif ($query_type == 'SELECT' || $query_type == 'UPDATE') { - foreach ($input_ary as $field => $val) { - $ary[] = "$field = " . $this->escape($val, $check_type, $dont_escape); - } - $glue = ($query_type == 'SELECT') ? "\nAND " : ",\n"; - $query = implode($glue, $ary); - } - - if (!isset($query)) { - if (function_exists('bb_die')) { - bb_die('
' . __FUNCTION__ . ": Wrong params for $query_type query type\n\n\$input_ary:\n\n" . htmlspecialchars(print_r($input_ary, true)) . '
'); - } else { - throw new \InvalidArgumentException("Wrong params for $query_type query type"); - } - } - - return "\n" . $query . "\n"; - } - - /** - * Get empty SQL array structure - */ - public function get_empty_sql_array(): array - { - return [ - 'SELECT' => [], - 'select_options' => [], - 'FROM' => [], - 'INNER JOIN' => [], - 'LEFT JOIN' => [], - 'WHERE' => [], - 'GROUP BY' => [], - 'HAVING' => [], - 'ORDER BY' => [], - 'LIMIT' => [], - ]; - } - - /** - * Build SQL from array structure - */ - public function build_sql(array $sql_ary): string - { - $sql = ''; - - // Apply array_unique to nested arrays - foreach ($sql_ary as $clause => $ary) { - if (is_array($ary) && $clause !== 'select_options') { - $sql_ary[$clause] = array_unique($ary); - } - } - - foreach ($sql_ary as $clause => $ary) { - switch ($clause) { - case 'SELECT': - $sql .= ($ary) ? ' SELECT ' . implode(' ', $sql_ary['select_options'] ?? []) . ' ' . implode(', ', $ary) : ''; - break; - case 'FROM': - $sql .= ($ary) ? ' FROM ' . implode(', ', $ary) : ''; - break; - case 'INNER JOIN': - $sql .= ($ary) ? ' INNER JOIN ' . implode(' INNER JOIN ', $ary) : ''; - break; - case 'LEFT JOIN': - $sql .= ($ary) ? ' LEFT JOIN ' . implode(' LEFT JOIN ', $ary) : ''; - break; - case 'WHERE': - $sql .= ($ary) ? ' WHERE ' . implode(' AND ', $ary) : ''; - break; - case 'GROUP BY': - $sql .= ($ary) ? ' GROUP BY ' . implode(', ', $ary) : ''; - break; - case 'HAVING': - $sql .= ($ary) ? ' HAVING ' . implode(' AND ', $ary) : ''; - break; - case 'ORDER BY': - $sql .= ($ary) ? ' ORDER BY ' . implode(', ', $ary) : ''; - break; - case 'LIMIT': - $sql .= ($ary) ? ' LIMIT ' . implode(', ', $ary) : ''; - break; - } - } - - return trim($sql); - } - - /** - * Return sql error array - */ - public function sql_error(): array - { - if ($this->connection) { - try { - $pdo = $this->connection->getPdo(); - $errorCode = $pdo->errorCode(); - $errorInfo = $pdo->errorInfo(); - - // Filter out "no error" states - PDO returns '00000' when there's no error - if (!$errorCode || $errorCode === '00000') { - return ['code' => '', 'message' => '']; - } - - // Build meaningful error message from errorInfo array - // errorInfo format: [SQLSTATE, driver-specific error code, driver-specific error message] - $message = ''; - if (isset($errorInfo[2]) && $errorInfo[2]) { - $message = $errorInfo[2]; // Driver-specific error message is most informative - } elseif (isset($errorInfo[1]) && $errorInfo[1]) { - $message = "Error code: " . $errorInfo[1]; - } else { - $message = "SQLSTATE: " . $errorCode; - } - - return [ - 'code' => $errorCode, - 'message' => $message - ]; - } catch (\Exception $e) { - return ['code' => $e->getCode(), 'message' => $e->getMessage()]; - } - } - - return ['code' => '', 'message' => 'not connected']; - } - - /** - * Close sql connection - */ - public function close(): void - { - if ($this->connection) { - $this->unlock(); - - if (!empty($this->locks)) { - foreach ($this->locks as $name => $void) { - $this->release_lock($name); - } - } - - $this->exec_shutdown_queries(); - - // Nette Database connection will be closed automatically - $this->connection = null; - } - - $this->selected_db = null; - } - - /** - * Add shutdown query - */ - public function add_shutdown_query(string $sql): void - { - $this->shutdown['__sql'][] = $sql; - } - - /** - * Exec shutdown queries - */ - public function exec_shutdown_queries(): void - { - if (empty($this->shutdown)) { - return; - } - - if (!empty($this->shutdown['post_html'])) { - $post_html_sql = $this->build_array('MULTI_INSERT', $this->shutdown['post_html']); - $this->query("REPLACE INTO " . (defined('BB_POSTS_HTML') ? BB_POSTS_HTML : 'bb_posts_html') . " $post_html_sql"); - } - - if (!empty($this->shutdown['__sql'])) { - foreach ($this->shutdown['__sql'] as $sql) { - $this->query($sql); - } - } - } - - /** - * Lock tables - */ - public function lock($tables, string $lock_type = 'WRITE'): ?ResultSet - { - $tables_sql = []; - - foreach ((array)$tables as $table_name) { - $tables_sql[] = "$table_name $lock_type"; - } - - if ($tables_sql = implode(', ', $tables_sql)) { - $this->locked = (bool)$this->sql_query("LOCK TABLES $tables_sql"); - } - - return $this->locked ? $this->result : null; - } - - /** - * Unlock tables - */ - public function unlock(): bool - { - if ($this->locked && $this->sql_query("UNLOCK TABLES")) { - $this->locked = false; - } - - return !$this->locked; - } - - /** - * Obtain user level lock - */ - public function get_lock(string $name, int $timeout = 0): mixed - { - $lock_name = $this->get_lock_name($name); - $timeout = (int)$timeout; - $row = $this->fetch_row("SELECT GET_LOCK('$lock_name', $timeout) AS lock_result"); - - if ($row && $row['lock_result']) { - $this->locks[$name] = true; - } - - return $row ? $row['lock_result'] : null; - } - - /** - * Release user level lock - */ - public function release_lock(string $name): mixed - { - $lock_name = $this->get_lock_name($name); - $row = $this->fetch_row("SELECT RELEASE_LOCK('$lock_name') AS lock_result"); - - if ($row && $row['lock_result']) { - unset($this->locks[$name]); - } - - return $row ? $row['lock_result'] : null; - } - - /** - * Check if lock is free - */ - public function is_free_lock(string $name): mixed - { - $lock_name = $this->get_lock_name($name); - $row = $this->fetch_row("SELECT IS_FREE_LOCK('$lock_name') AS lock_result"); - return $row ? $row['lock_result'] : null; - } - - /** - * Make per db unique lock name - */ - public function get_lock_name(string $name): string - { - if (!$this->selected_db) { - $this->init(); - } - - return "{$this->selected_db}_{$name}"; - } - - /** - * Get info about last query - */ - public function query_info(): string - { - $info = []; - - if ($this->result && ($num = $this->num_rows($this->result))) { - $info[] = "$num rows"; - } - - // Only check affected rows if we have a stored value - if ($this->last_affected_rows > 0) { - $info[] = "{$this->last_affected_rows} rows"; - } - - return implode(', ', $info); - } - - /** - * Get server version - */ - public function server_version(): string - { - if (!$this->connection) { - return ''; - } - - $version = $this->connection->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION); - if (preg_match('#^(\d+\.\d+\.\d+).*#', $version, $m)) { - return $m[1]; - } - return $version; - } - - /** - * Set slow query marker (delegated to debugger) - */ - public function expect_slow_query(int $ignoring_time = 60, int $new_priority = 10): void - { - $this->debugger->expect_slow_query($ignoring_time, $new_priority); - } - - /** - * Store debug info (delegated to debugger) - */ - public function debug(string $mode): void - { - $this->debugger->debug($mode); - } - - /** - * Trigger database error - */ - public function trigger_error(string $msg = 'Database Error'): void - { - $error = $this->sql_error(); - - // Define these variables early so they're available throughout the method - $is_admin = defined('IS_ADMIN') && IS_ADMIN; - $is_dev_mode = (defined('APP_ENV') && APP_ENV === 'local') || (defined('DBG_USER') && DBG_USER); - - // Build a meaningful error message - if (!empty($error['message'])) { - $error_msg = "$msg: " . $error['message']; - if (!empty($error['code'])) { - $error_msg = "$msg ({$error['code']}): " . $error['message']; - } - } else { - // Base error message for all users - $error_msg = "$msg: Database operation failed"; - - // Only add detailed debugging information for administrators or in development mode - if ($is_admin || $is_dev_mode) { - // Gather detailed debugging information - ONLY for admins/developers - $debug_info = []; - - // Connection status - if ($this->connection) { - $debug_info[] = "Connection: Active"; - try { - $pdo = $this->connection->getPdo(); - if ($pdo) { - $debug_info[] = "PDO: Available"; - $errorInfo = $pdo->errorInfo(); - if ($errorInfo && count($errorInfo) >= 3) { - $debug_info[] = "PDO ErrorInfo: " . json_encode($errorInfo); - } - $debug_info[] = "PDO ErrorCode: " . $pdo->errorCode(); - } else { - $debug_info[] = "PDO: Null"; - } - } catch (\Exception $e) { - $debug_info[] = "PDO Check Failed: " . $e->getMessage(); - } - } else { - $debug_info[] = "Connection: None"; - } - - // Query information - if ($this->cur_query) { - $debug_info[] = "Last Query: " . substr($this->cur_query, 0, 200) . (strlen($this->cur_query) > 200 ? '...' : ''); - } else { - $debug_info[] = "Last Query: None"; - } - - // Database information - $debug_info[] = "Database: " . ($this->selected_db ?: 'None'); - $debug_info[] = "Server: " . $this->db_server; - - // Recent queries from debug log (if available) - if (isset($this->debugger->dbg) && !empty($this->debugger->dbg)) { - $recent_queries = array_slice($this->debugger->dbg, -3); // Last 3 queries - $debug_info[] = "Recent Queries Count: " . count($recent_queries); - foreach ($recent_queries as $i => $query_info) { - $debug_info[] = "Query " . ($i + 1) . ": " . substr($query_info['sql'] ?? 'Unknown', 0, 100) . (strlen($query_info['sql'] ?? '') > 100 ? '...' : ''); - } - } - - if ($debug_info) { - $error_msg .= " [DEBUG: " . implode("; ", $debug_info) . "]"; - } - - // Log this for investigation - if (function_exists('bb_log')) { - bb_log("Unknown Database Error Debug:\n" . implode("\n", $debug_info), 'unknown_db_errors'); - } - } else { - // For regular users: generic message only + contact admin hint - $error_msg = "$msg: A database error occurred. Please contact the administrator if this problem persists."; - - // Still log basic information for debugging - if (function_exists('bb_log')) { - bb_log("Database Error (User-facing): $error_msg\nRequest: " . ($_SERVER['REQUEST_URI'] ?? 'CLI'), 'user_db_errors'); - } - } - } - - // Add query context for debugging (but only for admins/developers) - if ($this->cur_query && ($is_admin || $is_dev_mode)) { - $error_msg .= "\nQuery: " . $this->cur_query; - } - - if (function_exists('bb_die')) { - bb_die($error_msg); - } else { - throw new \RuntimeException($error_msg); - } - } - - /** - * Find source of database call (delegated to debugger) - */ - public function debug_find_source(string $mode = 'all'): string - { - return $this->debugger->debug_find_source($mode); - } - - /** - * Prepare for logging (delegated to debugger) - */ - public function log_next_query(int $queries_count = 1, string $log_file = 'sql_queries'): void - { - $this->debugger->log_next_query($queries_count, $log_file); - } - - /** - * Log query (delegated to debugger) - */ - public function log_query(string $log_file = 'sql_queries'): void - { - $this->debugger->log_query($log_file); - } - - /** - * Log slow query (delegated to debugger) - */ - public function log_slow_query(string $log_file = 'sql_slow_bb'): void - { - $this->debugger->log_slow_query($log_file); - } - - /** - * Log error (delegated to debugger) - */ - public function log_error(?\Exception $exception = null): void - { - $this->debugger->log_error($exception); - } - - /** - * Explain queries (delegated to debugger) - */ - public function explain($mode, $html_table = '', array $row = []): mixed - { - return $this->debugger->explain($mode, $html_table, $row); - } - - /** - * Magic method to provide backward compatibility for debug properties - */ - public function __get(string $name): mixed - { - // Delegate debug-related properties to the debugger - switch ($name) { - case 'dbg': - return $this->debugger->dbg ?? []; - case 'dbg_id': - return $this->debugger->dbg_id ?? 0; - case 'dbg_enabled': - return $this->debugger->dbg_enabled ?? false; - case 'do_explain': - return $this->debugger->do_explain ?? false; - case 'explain_hold': - return $this->debugger->explain_hold ?? ''; - case 'explain_out': - return $this->debugger->explain_out ?? ''; - case 'slow_time': - return $this->debugger->slow_time ?? 3.0; - case 'sql_timetotal': - return $this->sql_timetotal; - default: - throw new \InvalidArgumentException("Property '$name' does not exist"); - } - } - - /** - * Magic method to check if debug properties exist - */ - public function __isset(string $name): bool - { - switch ($name) { - case 'dbg': - case 'dbg_id': - case 'dbg_enabled': - case 'do_explain': - case 'explain_hold': - case 'explain_out': - case 'slow_time': - case 'sql_timetotal': - return true; - default: - return false; - } - } - - /** - * Destroy singleton instances (for testing) - */ - public static function destroyInstances(): void - { - self::$instance = null; - self::$instances = []; - } -} diff --git a/legacy/src/Database/DatabaseDebugger.php b/legacy/src/Database/DatabaseDebugger.php deleted file mode 100644 index 8cb764863..000000000 --- a/legacy/src/Database/DatabaseDebugger.php +++ /dev/null @@ -1,569 +0,0 @@ -db = $db; - - // Initialize debug settings more safely - $this->initializeDebugSettings(); - $this->slow_time = defined('SQL_SLOW_QUERY_TIME') ? SQL_SLOW_QUERY_TIME : 3; - } - - /** - * Initialize debug settings exactly like the original Database class - */ - private function initializeDebugSettings(): void - { - // Use the EXACT same logic as the original DB class - $this->dbg_enabled = (dev()->checkSqlDebugAllowed() || !empty($_COOKIE['explain'])); - $this->do_explain = ($this->dbg_enabled && !empty($_COOKIE['explain'])); - } - - /** - * Store debug info - */ - public function debug(string $mode): void - { - $id =& $this->dbg_id; - $dbg =& $this->dbg[$id]; - - if ($mode === 'start') { - // Always update timing if required constants are defined - if (defined('SQL_CALC_QUERY_TIME') && SQL_CALC_QUERY_TIME || defined('SQL_LOG_SLOW_QUERIES') && SQL_LOG_SLOW_QUERIES) { - $this->sql_starttime = microtime(true); - $this->db->sql_starttime = $this->sql_starttime; // Update main Database object too - } - - if ($this->dbg_enabled) { - $currentQuery = $this->db->cur_query ?? ''; - $dbg['sql'] = preg_replace('#^(\s*)(/\*)(.*)(\*/)(\s*)#', '', $currentQuery); - - // Also check SQL syntax to detect Nette Explorer queries - if (!$this->is_nette_explorer_query && $this->detectNetteExplorerBySqlSyntax($dbg['sql'])) { - $this->markAsNetteExplorerQuery(); - } - - $dbg['src'] = $this->debug_find_source(); - $dbg['file'] = $this->debug_find_source('file'); - $dbg['line'] = $this->debug_find_source('line'); - $dbg['time'] = ''; - $dbg['info'] = ''; - $dbg['mem_before'] = function_exists('sys') ? sys('mem') : 0; - } - - if ($this->do_explain) { - $this->explain('start'); - } - } elseif ($mode === 'stop') { - if (defined('SQL_CALC_QUERY_TIME') && SQL_CALC_QUERY_TIME || defined('SQL_LOG_SLOW_QUERIES') && SQL_LOG_SLOW_QUERIES) { - $this->cur_query_time = microtime(true) - $this->sql_starttime; - $this->db->sql_timetotal += $this->cur_query_time; - $this->db->DBS['sql_timetotal'] += $this->cur_query_time; - - if (defined('SQL_LOG_SLOW_QUERIES') && SQL_LOG_SLOW_QUERIES && $this->cur_query_time > $this->slow_time) { - $this->log_slow_query(); - } - } - - if ($this->dbg_enabled) { - $dbg['time'] = $this->cur_query_time > 0 ? $this->cur_query_time : (microtime(true) - $this->sql_starttime); - $dbg['info'] = $this->db->query_info(); - $dbg['mem_after'] = function_exists('sys') ? sys('mem') : 0; - - // Add Nette Explorer marker to debug info for panel display - if ($this->is_nette_explorer_query && !str_contains($dbg['info'], '[Nette Explorer]')) { - // Store both plain text and HTML versions - $dbg['info_plain'] = $dbg['info'] . ' [Nette Explorer]'; - $dbg['info'] .= ' [Nette Explorer]'; - $dbg['is_nette_explorer'] = true; - } else { - $dbg['info_plain'] = $dbg['info']; - $dbg['is_nette_explorer'] = false; - } - - $id++; - } - - if ($this->do_explain) { - $this->explain('stop'); - } - - // Check for logging - if ($this->db->DBS['log_counter'] && $this->db->inited) { - $this->log_query($this->db->DBS['log_file']); - $this->db->DBS['log_counter']--; - } - - // Reset Nette Explorer flag after query completion - $this->resetNetteExplorerFlag(); - } - - // Update timing in main Database object - $this->db->cur_query_time = $this->cur_query_time; - } - - /** - * Find source of database call - */ - public function debug_find_source(string $mode = 'all'): string - { - if (!defined('SQL_PREPEND_SRC') || !SQL_PREPEND_SRC) { - return 'src disabled'; - } - - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - // Check if this is a Nette Explorer query by examining the call stack - $isNetteExplorer = $this->detectNetteExplorerInTrace($trace); - if ($isNetteExplorer) { - $this->markAsNetteExplorerQuery(); - } - - // Find first non-DB call (skip Database.php, DebugSelection.php, and DatabaseDebugger.php) - foreach ($trace as $frame) { - if (isset($frame['file']) && - !str_contains($frame['file'], 'Database/Database.php') && - !str_contains($frame['file'], 'Database/DebugSelection.php') && - !str_contains($frame['file'], 'Database/DatabaseDebugger.php')) { - switch ($mode) { - case 'file': - return $frame['file']; - case 'line': - return (string)($frame['line'] ?? '?'); - case 'all': - default: - $file = function_exists('hide_bb_path') ? hide_bb_path($frame['file']) : basename($frame['file']); - $line = $frame['line'] ?? '?'; - return "$file($line)"; - } - } - } - - return 'src not found'; - } - - /** - * Detect if the current query comes from Nette Explorer by examining the call stack - */ - public function detectNetteExplorerInTrace(array $trace): bool - { - foreach ($trace as $frame) { - if (isset($frame['class'])) { - // Check for Nette Database classes in the call stack - if (str_contains($frame['class'], 'Nette\\Database\\') || - str_contains($frame['class'], 'TorrentPier\\Database\\DebugSelection')) { - return true; - } - } - - if (isset($frame['file'])) { - // Check for Nette Database files or our DebugSelection - if (str_contains($frame['file'], 'vendor/nette/database/') || - str_contains($frame['file'], 'Database/DebugSelection.php')) { - return true; - } - } - } - - return false; - } - - /** - * Detect if SQL query syntax suggests it came from Nette Explorer - */ - public function detectNetteExplorerBySqlSyntax(string $sql): bool - { - // Nette Database typically generates SQL with these characteristics: - // 1. Backticks around column/table names - // 2. Parentheses around WHERE conditions like (column = value) - // 3. Specific patterns like IN (value) instead of IN (value) - - $nettePatterns = [ - '/`[a-zA-Z0-9_]+`/', // Backticks around identifiers - '/WHERE\s*\([^)]+\)/', // Parentheses around WHERE conditions - '/SELECT\s+`[^`]+`.*FROM\s+`[^`]+`/', // SELECT with backticked columns and tables - ]; - - foreach ($nettePatterns as $pattern) { - if (preg_match($pattern, $sql)) { - return true; - } - } - - return false; - } - - /** - * Prepare for logging - */ - public function log_next_query(int $queries_count = 1, string $log_file = 'sql_queries'): void - { - $this->db->DBS['log_file'] = $log_file; - $this->db->DBS['log_counter'] = $queries_count; - } - - /** - * Log query - */ - public function log_query(string $log_file = 'sql_queries'): void - { - if (!function_exists('bb_log') || !function_exists('dev')) { - return; - } - - $q_time = ($this->cur_query_time >= 10) ? round($this->cur_query_time, 0) : sprintf('%.3f', $this->cur_query_time); - $msg = []; - $msg[] = round($this->sql_starttime); - $msg[] = date('m-d H:i:s', (int)$this->sql_starttime); - $msg[] = sprintf('%-6s', $q_time); - $msg[] = sprintf('%05d', getmypid()); - $msg[] = $this->db->db_server; - $msg[] = function_exists('dev') ? dev()->formatShortQuery($this->db->cur_query) : $this->db->cur_query; - $msg = implode(defined('LOG_SEPR') ? LOG_SEPR : ' | ', $msg); - $msg .= ($info = $this->db->query_info()) ? ' # ' . $info : ''; - $msg .= ' # ' . $this->debug_find_source() . ' '; - $msg .= defined('IN_CRON') ? 'cron' : basename($_SERVER['REQUEST_URI'] ?? ''); - bb_log($msg . (defined('LOG_LF') ? LOG_LF : "\n"), $log_file); - } - - /** - * Log slow query - */ - public function log_slow_query(string $log_file = 'sql_slow_bb'): void - { - if (!defined('IN_FIRST_SLOW_QUERY') && function_exists('CACHE')) { - $cache = CACHE('bb_cache'); - if ($cache && $cache->get('dont_log_slow_query')) { - return; - } - } - $this->log_query($log_file); - } - - /** - * Log error - * - * NOTE: This method logs detailed information to FILES only (error_log, bb_log). - * Log files are not accessible to regular users, so detailed information is safe here. - * User-facing error display is handled separately with proper security checks. - */ - public function log_error(?\Exception $exception = null): void - { - $error_details = []; - $error_msg = ''; - - if ($exception) { - // Use the actual exception information which is more reliable - $error_msg = "Database Error: " . $exception->getMessage(); - $error_code = $exception->getCode(); - if ($error_code) { - $error_msg = "Database Error ({$error_code}): " . $exception->getMessage(); - } - - // Collect detailed error information - $error_details[] = "Exception: " . get_class($exception); - $error_details[] = "Message: " . $exception->getMessage(); - $error_details[] = "Code: " . $exception->getCode(); - $error_details[] = "File: " . $exception->getFile() . ":" . $exception->getLine(); - - // Add PDO-specific details if it's a PDO exception - if ($exception instanceof \PDOException) { - $error_details[] = "PDO Error Info: " . json_encode($exception->errorInfo ?? []); - } - } else { - // Fallback to PDO error state (legacy behavior) - $error = $this->db->sql_error(); - - // Only log if there's an actual error (not 00000 which means "no error") - if (!$error['code'] || $error['code'] === '00000' || !$error['message']) { - return; // Don't log empty or "no error" states - } - - $error_msg = "Database Error ({$error['code']}): " . $error['message']; - $error_details[] = "PDO Error Code: " . $error['code']; - $error_details[] = "PDO Error Message: " . $error['message']; - } - - // Add comprehensive context for debugging - $error_details[] = "Query: " . ($this->db->cur_query ?: 'None'); - $error_details[] = "Source: " . $this->debug_find_source(); - $error_details[] = "Database: " . ($this->db->selected_db ?: 'None'); - $error_details[] = "Server: " . $this->db->db_server; - $error_details[] = "Timestamp: " . date('Y-m-d H:i:s'); - $error_details[] = "Request URI: " . ($_SERVER['REQUEST_URI'] ?? 'CLI'); - $error_details[] = "User IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'Unknown'); - - // Check connection status - try { - if ($this->db->connection) { - $error_details[] = "Connection Status: Active"; - $pdo = $this->db->connection->getPdo(); - $error_details[] = "PDO Connection: " . ($pdo ? 'Available' : 'Null'); - if ($pdo) { - $errorInfo = $pdo->errorInfo(); - $error_details[] = "Current PDO Error Info: " . json_encode($errorInfo); - } - } else { - $error_details[] = "Connection Status: No connection"; - } - } catch (\Exception $e) { - $error_details[] = "Connection Check Failed: " . $e->getMessage(); - } - - // Build comprehensive log message - $log_message = $error_msg . "\n" . implode("\n", $error_details); - - // Log to both error_log and TorrentPier's logging system - error_log($error_msg); - - // Use TorrentPier's bb_log for better file management and organization - if (function_exists('bb_log')) { - bb_log($log_message, 'database_errors'); - } - - // Also log to PHP error log for immediate access - error_log("DETAILED: " . $log_message); - } - - /** - * Log legacy query that needed automatic compatibility fix - */ - public function logLegacyQuery(string $query, string $error): void - { - $legacy_entry = [ - 'query' => $query, - 'error' => $error, - 'source' => $this->debug_find_source(), - 'file' => $this->debug_find_source('file'), - 'line' => $this->debug_find_source('line'), - 'time' => microtime(true) - ]; - - $this->legacy_queries[] = $legacy_entry; - - // Mark the CURRENT debug entry as legacy instead of creating a new one - if ($this->dbg_enabled && !empty($this->dbg)) { - // Find the most recent debug entry (the one that just executed and failed) - $current_id = $this->dbg_id - 1; - - if (isset($this->dbg[$current_id])) { - // Mark the existing entry as legacy - $this->dbg[$current_id]['is_legacy_query'] = true; - - // Update the info to show it was automatically fixed - $original_info = $this->dbg[$current_id]['info'] ?? ''; - $original_info_plain = $this->dbg[$current_id]['info_plain'] ?? $original_info; - - $this->dbg[$current_id]['info'] = 'LEGACY COMPATIBILITY FIX APPLIED - ' . $original_info; - $this->dbg[$current_id]['info_plain'] = 'LEGACY COMPATIBILITY FIX APPLIED - ' . $original_info_plain; - } - } - - // Log to file for permanent record - $msg = 'LEGACY QUERY DETECTED - NEEDS FIXING' . LOG_LF; - $msg .= 'Query: ' . $query . LOG_LF; - $msg .= 'Error: ' . $error . LOG_LF; - $msg .= 'Source: ' . $legacy_entry['source'] . LOG_LF; - $msg .= 'Time: ' . date('Y-m-d H:i:s', (int)$legacy_entry['time']) . LOG_LF; - - bb_log($msg, 'legacy_queries', false); - } - - /** - * Set slow query marker - */ - public function expect_slow_query(int $ignoring_time = 60, int $new_priority = 10): void - { - if (function_exists('CACHE')) { - $cache = CACHE('bb_cache'); - if ($old_priority = $cache->get('dont_log_slow_query')) { - if ($old_priority > $new_priority) { - return; - } - } - - if (!defined('IN_FIRST_SLOW_QUERY')) { - define('IN_FIRST_SLOW_QUERY', true); - } - - $cache->set('dont_log_slow_query', $new_priority, $ignoring_time); - } - } - - /** - * Explain queries - maintains compatibility with legacy SqlDb - */ - public function explain($mode, $html_table = '', array $row = []): mixed - { - if (!$this->do_explain) { - return false; - } - - $query = $this->db->cur_query ?? ''; - // Remove comments - $query = preg_replace('#(\s*)(/\*)(.*)(\*/)(\s*)#', '', $query); - - switch ($mode) { - case 'start': - $this->explain_hold = ''; - - if (preg_match('#UPDATE ([a-z0-9_]+).*?WHERE(.*)/#', $query, $m)) { - $query = "SELECT * FROM $m[1] WHERE $m[2]"; - } elseif (preg_match('#DELETE FROM ([a-z0-9_]+).*?WHERE(.*)#s', $query, $m)) { - $query = "SELECT * FROM $m[1] WHERE $m[2]"; - } - - if (str_starts_with($query, "SELECT")) { - $html_table = false; - - try { - $result = $this->db->connection->query("EXPLAIN $query"); - while ($row = $result->fetch()) { - // Convert row to array regardless of type - $rowArray = (array)$row; - $html_table = $this->explain('add_explain_row', $html_table, $rowArray); - } - } catch (\Exception $e) { - // Skip if explain fails - } - - if ($html_table) { - $this->explain_hold .= ''; - } - } - break; - - case 'stop': - if (!$this->explain_hold) { - break; - } - - $id = $this->dbg_id - 1; - $htid = 'expl-' . spl_object_hash($this->db->connection) . '-' . $id; - $dbg = $this->dbg[$id] ?? []; - - // Ensure required keys exist with defaults - $dbg = array_merge([ - 'time' => $this->cur_query_time ?? 0, - 'sql' => $this->db->cur_query ?? '', - 'query' => $this->db->cur_query ?? '', - 'src' => $this->debug_find_source(), - 'trace' => $this->debug_find_source() // Backup for compatibility - ], $dbg); - - $this->explain_out .= ' - - - - - - -
 ' . ($dbg['src'] ?? $dbg['trace']) . '  [' . sprintf('%.3f', $dbg['time']) . ' s]  ' . $this->db->query_info() . '' . "[{$this->db->engine}] {$this->db->db_server}.{$this->db->selected_db}" . ' :: Query #' . ($this->db->num_queries + 1) . ' 
' . $this->explain_hold . '
-
' . (function_exists('dev') ? dev()->formatShortQuery($dbg['sql'] ?? $dbg['query'], true) : htmlspecialchars($dbg['sql'] ?? $dbg['query'])) . '  
-
'; - break; - - case 'add_explain_row': - if (!$html_table && $row) { - $html_table = true; - $this->explain_hold .= ''; - foreach (array_keys($row) as $val) { - $this->explain_hold .= ''; - } - $this->explain_hold .= ''; - } - $this->explain_hold .= ''; - foreach (array_values($row) as $i => $val) { - $class = !($i % 2) ? 'row1' : 'row2'; - $this->explain_hold .= ''; - } - $this->explain_hold .= ''; - - return $html_table; - - case 'display': - echo '
' . $this->explain_out . '
'; - break; - } - - return false; - } - - /** - * Get debug statistics for display - */ - public function getDebugStats(): array - { - return [ - 'num_queries' => count($this->dbg), - 'sql_timetotal' => $this->db->sql_timetotal, - 'queries' => $this->dbg, - 'explain_out' => $this->explain_out - ]; - } - - /** - * Clear debug data - */ - public function clearDebugData(): void - { - $this->dbg = []; - $this->dbg_id = 0; - $this->explain_hold = ''; - $this->explain_out = ''; - } - - /** - * Mark next query as coming from Nette Explorer - */ - public function markAsNetteExplorerQuery(): void - { - $this->is_nette_explorer_query = true; - } - - /** - * Reset Nette Explorer query flag - */ - public function resetNetteExplorerFlag(): void - { - $this->is_nette_explorer_query = false; - } -} diff --git a/legacy/src/Database/DatabaseFactory.php b/legacy/src/Database/DatabaseFactory.php deleted file mode 100644 index 203c95961..000000000 --- a/legacy/src/Database/DatabaseFactory.php +++ /dev/null @@ -1,101 +0,0 @@ -close(); - } - } - self::$instances = []; - Database::destroyInstances(); - } -} \ No newline at end of file diff --git a/legacy/src/Database/DebugSelection.php b/legacy/src/Database/DebugSelection.php deleted file mode 100644 index 7968c9dfb..000000000 --- a/legacy/src/Database/DebugSelection.php +++ /dev/null @@ -1,295 +0,0 @@ -selection = $selection; - $this->db = $db; - } - - /** - * Magic method to delegate calls to the wrapped Selection - */ - public function __call(string $name, array $arguments) - { - $result = call_user_func_array([$this->selection, $name], $arguments); - - // If result is another Selection, wrap it too - if ($result instanceof Selection) { - return new self($result, $this->db); - } - - return $result; - } - - /** - * Magic method to delegate property access - */ - public function __get(string $name) - { - return $this->selection->$name; - } - - /** - * Magic method to delegate property setting - */ - public function __set(string $name, $value): void - { - $this->selection->$name = $value; - } - - /** - * Magic method to check if property is set - */ - public function __isset(string $name): bool - { - return isset($this->selection->$name); - } - - /** - * Log query execution for debug panel - */ - private function logQuery(string $method, array $arguments): void - { - if (!defined('SQL_DEBUG') || !SQL_DEBUG) { - return; - } - - // Use the actual SQL with substituted parameters for both logging and EXPLAIN - $sql = $this->generateSqlForLogging($method, $arguments, false); - - // Mark this query as coming from Nette Explorer - $this->db->debugger->markAsNetteExplorerQuery(); - - // Set the query for debug logging - $this->db->cur_query = $sql; - $this->db->debug('start'); - } - - /** - * Complete query logging after execution - */ - private function completeQueryLogging(): void - { - if (!defined('SQL_DEBUG') || !SQL_DEBUG) { - return; - } - - // Note: explain('stop') is automatically called by debug('stop') when do_explain is true - $this->db->debug('stop'); - } - - /** - * Generate SQL representation for logging and EXPLAIN - */ - private function generateSqlForLogging(string $method, array $arguments, bool $useRawSQL = true): string - { - // For SELECT operations, try to get the SQL from Nette - if (in_array($method, ['fetch', 'fetchAll', 'count'], true)) { - $sql = $useRawSQL ? $this->getSqlFromSelection() : $this->getSqlFromSelection(true); - - // Modify the SQL based on the method - switch ($method) { - case 'fetch': - // If it doesn't already have LIMIT, add it - if (!preg_match('/LIMIT\s+\d+/i', $sql)) { - $sql .= ' LIMIT 1'; - } - return $sql; - case 'count': - // Replace SELECT * with SELECT COUNT(*) - return preg_replace('/^SELECT\s+\*/i', 'SELECT COUNT(*)', $sql); - case 'fetchAll': - default: - return $sql; - } - } - - // For INSERT/UPDATE/DELETE, generate appropriate SQL - $tableName = $this->selection->getName(); - - return match ($method) { - 'insert' => $this->generateInsertSql($tableName, $arguments), - 'update' => $this->generateUpdateSql($tableName, $arguments, $useRawSQL), - 'delete' => $this->generateDeleteSql($tableName, $useRawSQL), - default => "-- Explorer method: {$method} on {$tableName}" - }; - } - - /** - * Generate INSERT SQL statement - */ - private function generateInsertSql(string $tableName, array $arguments): string - { - if (!isset($arguments[0]) || !is_array($arguments[0])) { - return "INSERT INTO {$tableName} (...) VALUES (...)"; - } - - $data = $arguments[0]; - $columns = implode(', ', array_keys($data)); - $values = implode(', ', array_map( - static fn($v) => is_string($v) ? "'$v'" : $v, - array_values($data) - )); - - return "INSERT INTO {$tableName} ({$columns}) VALUES ({$values})"; - } - - /** - * Generate UPDATE SQL statement - */ - private function generateUpdateSql(string $tableName, array $arguments, bool $useRawSQL): string - { - $setPairs = []; - if (isset($arguments[0]) && is_array($arguments[0])) { - foreach ($arguments[0] as $key => $value) { - $setPairs[] = "{$key} = " . (is_string($value) ? "'$value'" : $value); - } - } - - $setClause = !empty($setPairs) ? implode(', ', $setPairs) : '...'; - $sql = $this->getSqlFromSelection(!$useRawSQL); - - // Extract WHERE clause from the SQL - if (preg_match('/WHERE\s+(.+?)(?:\s+ORDER\s+BY|\s+LIMIT|\s+GROUP\s+BY|$)/i', $sql, $matches)) { - return "UPDATE {$tableName} SET {$setClause} WHERE " . trim($matches[1]); - } - - return "UPDATE {$tableName} SET {$setClause}"; - } - - /** - * Generate DELETE SQL statement - */ - private function generateDeleteSql(string $tableName, bool $useRawSQL): string - { - $sql = $this->getSqlFromSelection(!$useRawSQL); - - // Extract WHERE clause from the SQL - if (preg_match('/WHERE\s+(.+?)(?:\s+ORDER\s+BY|\s+LIMIT|\s+GROUP\s+BY|$)/i', $sql, $matches)) { - return "DELETE FROM {$tableName} WHERE " . trim($matches[1]); - } - - return "DELETE FROM {$tableName}"; - } - - /** - * Get SQL from Nette Selection with optional parameter substitution - */ - private function getSqlFromSelection(bool $replaceParameters = false): string - { - try { - $reflectionClass = new ReflectionClass($this->selection); - $sql = ''; - - // Try getSql() method first - if ($reflectionClass->hasMethod('getSql')) { - $getSqlMethod = $reflectionClass->getMethod('getSql'); - $getSqlMethod->setAccessible(true); - $sql = $getSqlMethod->invoke($this->selection); - } else { - // Try __toString() method as fallback - $sql = (string)$this->selection; - } - - // For EXPLAIN to work, we need to replace ? with actual values - if ($replaceParameters) { - $sql = preg_replace('/\?/', '1', $sql); - } - - return $sql; - } catch (Exception $e) { - // Fall back to simple representation - return "SELECT * FROM " . $this->selection->getName() . " WHERE 1=1"; - } - } - - // Delegate common Selection methods with logging - public function where(...$args): self - { - return new self($this->selection->where(...$args), $this->db); - } - - public function order(...$args): self - { - return new self($this->selection->order(...$args), $this->db); - } - - public function select(...$args): self - { - return new self($this->selection->select(...$args), $this->db); - } - - public function limit(...$args): self - { - return new self($this->selection->limit(...$args), $this->db); - } - - public function fetch() - { - $this->logQuery('fetch', []); - $result = $this->selection->fetch(); - $this->completeQueryLogging(); - return $result; - } - - public function fetchAll(): array - { - $this->logQuery('fetchAll', []); - $result = $this->selection->fetchAll(); - $this->completeQueryLogging(); - return $result; - } - - public function insert($data) - { - $this->logQuery('insert', [$data]); - $result = $this->selection->insert($data); - $this->completeQueryLogging(); - return $result; - } - - public function update($data): int - { - $this->logQuery('update', [$data]); - $result = $this->selection->update($data); - $this->completeQueryLogging(); - return $result; - } - - public function delete(): int - { - $this->logQuery('delete', []); - $result = $this->selection->delete(); - $this->completeQueryLogging(); - return $result; - } - - public function count(): int - { - $this->logQuery('count', []); - $result = $this->selection->count(); - $this->completeQueryLogging(); - return $result; - } -} diff --git a/legacy/src/Database/MigrationStatus.php b/legacy/src/Database/MigrationStatus.php deleted file mode 100644 index ed73e038a..000000000 --- a/legacy/src/Database/MigrationStatus.php +++ /dev/null @@ -1,305 +0,0 @@ -migrationTable = BB_MIGRATIONS; - $this->migrationPath = BB_ROOT . 'migrations'; - } - - /** - * Get complete migration status information - * - * @return array Migration status data including applied/pending migrations - */ - public function getMigrationStatus(): array - { - try { - // Check if migration table exists using Nette Database Explorer - $tableExists = $this->checkMigrationTableExists(); - $setupStatus = $this->getSetupStatus(); - - if (!$tableExists) { - return [ - 'table_exists' => false, - 'current_version' => null, - 'applied_migrations' => [], - 'pending_migrations' => $this->getAvailableMigrations(), - 'setup_status' => $setupStatus, - 'requires_setup' => $setupStatus['needs_setup'] - ]; - } - - // Get applied migrations using Nette Database Explorer - $appliedMigrations = DB()->query(" - SELECT version, migration_name, start_time, end_time - FROM {$this->migrationTable} - ORDER BY version ASC - ")->fetchAll(); - - // Convert Nette Result objects to arrays - $appliedMigrationsArray = []; - foreach ($appliedMigrations as $migration) { - $appliedMigrationsArray[] = [ - 'version' => $migration->version, - 'migration_name' => $migration->migration_name, - 'start_time' => $migration->start_time, - 'end_time' => $migration->end_time - ]; - } - - // Get current version (latest applied) - $currentVersion = null; - if (!empty($appliedMigrationsArray)) { - $currentVersion = end($appliedMigrationsArray)['version']; - } - - // Get pending migrations - $availableMigrations = $this->getAvailableMigrations(); - $appliedVersions = array_column($appliedMigrationsArray, 'version'); - $pendingMigrations = array_filter($availableMigrations, function ($migration) use ($appliedVersions) { - return !in_array($migration['version'], $appliedVersions); - }); - - return [ - 'table_exists' => true, - 'current_version' => $currentVersion, - 'applied_migrations' => $appliedMigrationsArray, - 'pending_migrations' => array_values($pendingMigrations), - 'setup_status' => $setupStatus, - 'requires_setup' => $setupStatus['needs_setup'] - ]; - - } catch (\Exception $e) { - bb_die('Error checking migration status: ' . $e->getMessage()); - } - } - - /** - * Determine setup status for existing installations - * - * @return array Setup status information - */ - private function getSetupStatus(): array - { - try { - // Check if core TorrentPier tables exist (indicates existing installation) - $coreTablesExist = $this->checkCoreTablesExist(); - $migrationTableExists = $this->checkMigrationTableExists(); - - if (!$coreTablesExist) { - // Fresh installation - return [ - 'type' => 'fresh', - 'needs_setup' => false, - 'message' => 'Fresh installation - migrations will run normally', - 'action_required' => false - ]; - } - - if (!$migrationTableExists) { - // Existing installation without migration system - return [ - 'type' => 'existing_needs_setup', - 'needs_setup' => true, - 'message' => 'Existing installation detected - migration setup required', - 'action_required' => true, - 'instructions' => 'Mark initial migrations as applied using --fake flag' - ]; - } - - // Check if initial migrations are marked as applied - $initialMigrationsApplied = $this->checkInitialMigrationsApplied(); - - if (!$initialMigrationsApplied) { - return [ - 'type' => 'existing_partial_setup', - 'needs_setup' => true, - 'message' => 'Migration table exists but initial migrations not marked as applied', - 'action_required' => true, - 'instructions' => 'Run: php vendor/bin/phinx migrate --fake --target=20250619000002' - ]; - } - - // Fully set up - return [ - 'type' => 'fully_setup', - 'needs_setup' => false, - 'message' => 'Migration system fully configured', - 'action_required' => false - ]; - - } catch (\Exception $e) { - return [ - 'type' => 'error', - 'needs_setup' => false, - 'message' => 'Error detecting setup status: ' . $e->getMessage(), - 'action_required' => false - ]; - } - } - - /** - * Check if core TorrentPier tables exist - * - * @return bool True if core tables exist - */ - private function checkCoreTablesExist(): bool - { - try { - $coreTable = 'bb_users'; // Key table that should exist in any TorrentPier installation - $escapedTable = DB()->escape($coreTable); - $result = DB()->query(" - SELECT COUNT(*) as table_count - FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = '{$escapedTable}' - ")->fetch(); - - return $result && $result->table_count > 0; - } catch (\Exception $e) { - return false; - } - } - - /** - * Check if initial migrations are marked as applied - * - * @return bool True if initial migrations are applied - */ - private function checkInitialMigrationsApplied(): bool - { - try { - $initialMigrationsCSV = implode(',', $this->initialMigrations); - $result = DB()->query(" - SELECT COUNT(*) as migration_count - FROM {$this->migrationTable} - WHERE version IN ($initialMigrationsCSV) - ")->fetch(); - - return $result && $result->migration_count >= count($this->initialMigrations); - } catch (\Exception $e) { - return false; - } - } - - /** - * Check if migration table exists - * - * @return bool True if table exists, false otherwise - */ - private function checkMigrationTableExists(): bool - { - try { - // Using simple query without parameters to avoid issues - $escapedTable = DB()->escape($this->migrationTable); - $result = DB()->query(" - SELECT COUNT(*) as table_count - FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = '{$escapedTable}' - ")->fetch(); - - return $result && $result->table_count > 0; - } catch (\Exception $e) { - return false; - } - } - - /** - * Get available migrations from filesystem - * - * @return array List of available migration files - */ - private function getAvailableMigrations(): array - { - $migrations = []; - - if (is_dir($this->migrationPath)) { - $files = glob($this->migrationPath . '/*.php'); - foreach ($files as $file) { - $filename = basename($file); - if (preg_match('/^(\d+)_(.+)\.php$/', $filename, $matches)) { - $migrations[] = [ - 'version' => $matches[1], - 'name' => $matches[2], - 'filename' => $filename, - 'file_path' => $file - ]; - } - } - } - - // Sort by version - usort($migrations, function ($a, $b) { - return strcmp($a['version'], $b['version']); - }); - - return $migrations; - } - - /** - * Get database schema information - * - * @return array Database statistics and information - */ - public function getSchemaInfo(): array - { - try { - // Get database name using Nette Database Explorer - $dbInfo = DB()->query("SELECT DATABASE() as db_name")->fetch(); - - // Get table count using Nette Database Explorer - $tableInfo = DB()->query(" - SELECT COUNT(*) as table_count - FROM information_schema.tables - WHERE table_schema = DATABASE() - ")->fetch(); - - // Get database size using Nette Database Explorer - $sizeInfo = DB()->query(" - SELECT - ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) as size_mb - FROM information_schema.tables - WHERE table_schema = DATABASE() - ")->fetch(); - - return [ - 'database_name' => $dbInfo->db_name ?? 'Unknown', - 'table_count' => $tableInfo->table_count ?? 0, - 'size_mb' => $sizeInfo->size_mb ?? 0 - ]; - - } catch (\Exception $e) { - return [ - 'database_name' => 'Unknown', - 'table_count' => 0, - 'size_mb' => 0, - 'error' => $e->getMessage() - ]; - } - } -} diff --git a/legacy/src/Dev.php b/legacy/src/Dev.php deleted file mode 100644 index 8348c52f6..000000000 --- a/legacy/src/Dev.php +++ /dev/null @@ -1,442 +0,0 @@ -whoops = new Run; - - if ($this->isDebugEnabled()) { - $this->getWhoopsOnPage(); - } else { - $this->getWhoopsPlaceholder(); - } - $this->getWhoopsLogger(); - - if (!$this->isDevelopmentEnvironment()) { - $this->getTelegramSender(); - $this->getBugsnag(); - } - - $this->whoops->register(); - } - - /** - * Get the singleton instance of Dev - */ - public static function getInstance(): Dev - { - if (self::$instance === null) { - self::$instance = new self(); - } - return self::$instance; - } - - /** - * Initialize the dev system (for compatibility) - */ - public static function init(): Dev - { - return self::getInstance(); - } - - /** - * [Whoops] Bugsnag handler - * - * @return void - */ - private function getBugsnag(): void - { - if (!config()->get('bugsnag.enabled')) { - return; - } - - $bugsnag = Client::make(config()->get('bugsnag.api_key')); - $this->whoops->pushHandler(function ($e) use ($bugsnag) { - $bugsnag->notifyException($e); - }); - } - - /** - * [Whoops] Telegram handler - * - * @return void - */ - private function getTelegramSender(): void - { - if (!config()->get('telegram_sender.enabled')) { - return; - } - - $telegramSender = new PlainTextHandler(); - $telegramSender->loggerOnly(true); - $telegramSender->setLogger((new Logger( - APP_NAME, - [(new TelegramHandler(config()->get('telegram_sender.token'), (int)config()->get('telegram_sender.chat_id'), timeout: (int)config()->get('telegram_sender.timeout'))) - ->setFormatter(new TelegramFormatter())] - ))); - $this->whoops->pushHandler($telegramSender); - } - - /** - * [Whoops] On page handler (in debug) - * - * @return void - */ - private function getWhoopsOnPage(): void - { - /** - * Show errors on page with enhanced database information - */ - $prettyPageHandler = new \TorrentPier\Whoops\EnhancedPrettyPageHandler(); - foreach (config()->get('whoops.blacklist', []) as $key => $secrets) { - foreach ($secrets as $secret) { - $prettyPageHandler->blacklist($key, $secret); - } - } - $this->whoops->pushHandler($prettyPageHandler); - - /** - * Show log in browser console - */ - $loggingInConsole = new PlainTextHandler(); - $loggingInConsole->loggerOnly(true); - $loggingInConsole->setLogger((new Logger( - APP_NAME, - [(new BrowserConsoleHandler()) - ->setFormatter((new LineFormatter(null, null, true)))] - ))); - $this->whoops->pushHandler($loggingInConsole); - } - - /** - * [Whoops] Logger handler - * - * @return void - */ - private function getWhoopsLogger(): void - { - if ((int)ini_get('log_errors') !== 1) { - return; - } - - $loggingInFile = new PlainTextHandler(); - $loggingInFile->loggerOnly(true); - $loggingInFile->setLogger((new Logger( - APP_NAME, - [(new StreamHandler(WHOOPS_LOG_FILE)) - ->setFormatter((new LineFormatter(null, null, true)))] - ))); - $this->whoops->pushHandler($loggingInFile); - } - - /** - * [Whoops] Placeholder handler (non debug) - * - * @return void - */ - private function getWhoopsPlaceholder(): void - { - $this->whoops->pushHandler(function ($e) { - echo config()->get('whoops.error_message'); - echo "
Error: {$e->getMessage()}."; - }); - } - - /** - * Get SQL debug log (instance method) - * - * @return string - * @throws Exception - */ - public function getSqlLogInstance(): string - { - $log = ''; - $totalLegacyQueries = 0; - - // Check for legacy queries across all database instances - $server_names = \TorrentPier\Database\DatabaseFactory::getServerNames(); - foreach ($server_names as $srv_name) { - try { - $db_obj = \TorrentPier\Database\DatabaseFactory::getInstance($srv_name); - if (!empty($db_obj->debugger->legacy_queries)) { - $totalLegacyQueries += count($db_obj->debugger->legacy_queries); - } - } catch (\Exception $e) { - // Skip if server not available - } - } - - // Add warning banner if legacy queries were detected - if ($totalLegacyQueries > 0) { - $log .= '
' - . '⚠️ Legacy Query Warning: ' - . $totalLegacyQueries . ' quer' . ($totalLegacyQueries > 1 ? 'ies' : 'y') . ' with duplicate columns detected and automatically fixed. ' - . 'These queries should be updated to explicitly select columns. ' - . 'Check the legacy_queries.log file for details.' - . '
'; - } - - // Get debug information from new database system - foreach ($server_names as $srv_name) { - try { - $db_obj = \TorrentPier\Database\DatabaseFactory::getInstance($srv_name); - $log .= !empty($db_obj->dbg) ? $this->getSqlLogHtml($db_obj, "database: $srv_name [{$db_obj->engine}]") : ''; - } catch (\Exception $e) { - // Skip if server not available - } - } - - // Get cache system debug information - $cacheSystem = \TorrentPier\Cache\UnifiedCacheSystem::getInstance(); - $cacheObjects = $cacheSystem->obj; // Uses magic __get method for backward compatibility - - foreach ($cacheObjects as $cache_name => $cache_obj) { - if (!empty($cache_obj->db->dbg)) { - $log .= $this->getSqlLogHtml($cache_obj->db, "cache: $cache_name [{$cache_obj->db->engine}]"); - } elseif (!empty($cache_obj->dbg)) { - $log .= $this->getSqlLogHtml($cache_obj, "cache: $cache_name [{$cache_obj->engine}]"); - } - } - - // Get datastore debug information - $datastore = datastore(); - if (!empty($datastore->db->dbg)) { - $log .= $this->getSqlLogHtml($datastore->db, "cache: datastore [{$datastore->db->engine}]"); - } elseif (!empty($datastore->dbg)) { - $log .= $this->getSqlLogHtml($datastore, "cache: datastore [{$datastore->engine}]"); - } - - return $log; - } - - /** - * Sql debug status (instance method) - * - * @return bool - */ - public function sqlDebugAllowedInstance(): bool - { - return (SQL_DEBUG && DBG_USER && !empty($_COOKIE['sql_log'])); - } - - /** - * Get SQL query html log - * - * @param object $db_obj - * @param string $log_name - * - * @return string - * @throws Exception - */ - private function getSqlLogHtml(object $db_obj, string $log_name): string - { - $log = ''; - - foreach ($db_obj->dbg as $i => $dbg) { - $id = "sql_{$i}_" . random_int(0, mt_getrandmax()); - $sql = $this->shortQueryInstance($dbg['sql'], true); - $time = sprintf('%.3f', $dbg['time']); - $perc = '[' . round($dbg['time'] * 100 / $db_obj->sql_timetotal) . '%]'; - // Use plain text version for title attribute to avoid HTML issues - $info_plain = !empty($dbg['info_plain']) ? $dbg['info_plain'] . ' [' . $dbg['src'] . ']' : $dbg['src']; - $info = !empty($dbg['info']) ? $dbg['info'] . ' [' . $dbg['src'] . ']' : $dbg['src']; - - // Check if this is a legacy query that needed compatibility fix - $isLegacyQuery = !empty($dbg['is_legacy_query']); - $rowClass = $isLegacyQuery ? 'sqlLogRow sqlLegacyRow' : 'sqlLogRow'; - $rowStyle = $isLegacyQuery ? ' style="background-color: #ffe6e6; border-left: 4px solid #dc3545; color: #721c24;"' : ''; - $legacyWarning = $isLegacyQuery ? '[LEGACY]' : ''; - - $log .= '
' - . $legacyWarning - . '' . $time . ' ' - . '' . $perc . ' ' - . '' . $sql . '' - . ' # ' . $info . ' ' - . '
'; - } - - return '
' . $log_name . '
' . $log; - } - - /** - * Short query (instance method) - * - * @param string $sql - * @param bool $esc_html - * @return string - */ - public function shortQueryInstance(string $sql, bool $esc_html = false): string - { - $max_len = 100; - $sql = str_compact($sql); - - if (!empty($_COOKIE['sql_log_full'])) { - if (mb_strlen($sql, DEFAULT_CHARSET) > $max_len) { - $sql = mb_substr($sql, 0, 50) . ' [...cut...] ' . mb_substr($sql, -50); - } - } - - return $esc_html ? htmlCHR($sql, true) : $sql; - } - - // Static methods for backward compatibility (proxy to instance methods) - - /** - * Get SQL debug log (static) - * - * @return string - * @throws Exception - * @deprecated Use dev()->getSqlLog() instead - */ - public static function getSqlLog(): string - { - return self::getInstance()->getSqlLogInstance(); - } - - /** - * Sql debug status (static) - * - * @return bool - * @deprecated Use dev()->sqlDebugAllowed() instead - */ - public static function sqlDebugAllowed(): bool - { - return self::getInstance()->sqlDebugAllowedInstance(); - } - - /** - * Short query (static) - * - * @param string $sql - * @param bool $esc_html - * @return string - * @deprecated Use dev()->shortQuery() instead - */ - public static function shortQuery(string $sql, bool $esc_html = false): string - { - return self::getInstance()->shortQueryInstance($sql, $esc_html); - } - - /** - * Get SQL debug log (for dev() singleton usage) - * - * @return string - * @throws Exception - */ - public function getSqlDebugLog(): string - { - return $this->getSqlLogInstance(); - } - - /** - * Check if SQL debugging is allowed (for dev() singleton usage) - * - * @return bool - */ - public function checkSqlDebugAllowed(): bool - { - return $this->sqlDebugAllowedInstance(); - } - - /** - * Format SQL query for display (for dev() singleton usage) - * - * @param string $sql - * @param bool $esc_html - * @return string - */ - public function formatShortQuery(string $sql, bool $esc_html = false): string - { - return $this->shortQueryInstance($sql, $esc_html); - } - - /** - * Get Whoops instance - * - * @return Run - */ - public function getWhoops(): Run - { - return $this->whoops; - } - - /** - * Check if debug mode is enabled - * - * @return bool - */ - public function isDebugEnabled(): bool - { - return DBG_USER; - } - - /** - * Check if application is in development environment - * - * @return bool - */ - public function isDevelopmentEnvironment(): bool - { - return APP_ENV === 'development'; - } - - /** - * Prevent cloning of the singleton instance - */ - private function __clone() - { - } - - /** - * Prevent unserialization of the singleton instance - */ - public function __wakeup() - { - throw new \Exception("Cannot unserialize a singleton."); - } -} diff --git a/legacy/src/Emailer.php b/legacy/src/Emailer.php deleted file mode 100644 index d31e3716e..000000000 --- a/legacy/src/Emailer.php +++ /dev/null @@ -1,217 +0,0 @@ -subject = $subject; - } - - /** - * Set recipient address - * - * @param string $email recipient address - * @param string $name recipient name - * - * @return void - */ - public function set_to(string $email, string $name): void - { - $this->to = new Address($email, $name); - } - - /** - * Setting an address for the response - * - * @param string $email recipient address - * - * @return void - */ - public function set_reply(string $email): void - { - $this->reply = new Address($email); - } - - /** - * Setting the message template - * - * @param string $template_file - * @param string $template_lang - * - * @return void - */ - public function set_template(string $template_file, string $template_lang = ''): void - { - if (!$template_lang) { - $template_lang = config()->get('default_lang'); - } - - if (empty($this->tpl_msg[$template_lang . $template_file])) { - $tpl_file = LANG_ROOT_DIR . '/' . $template_lang . '/email/' . $template_file . '.html'; - - if (!is_file($tpl_file)) { - $tpl_file = LANG_ROOT_DIR . '/' . config()->get('default_lang') . '/email/' . $template_file . '.html'; - - if (!is_file($tpl_file)) { - throw new Exception('Could not find email template file: ' . $template_file); - } - } - - if (!$fd = fopen($tpl_file, 'rb')) { - throw new Exception('Failed opening email template file: ' . $tpl_file); - } - - $this->tpl_msg[$template_lang . $template_file] = fread($fd, filesize($tpl_file)); - fclose($fd); - } - - $this->message = $this->tpl_msg[$template_lang . $template_file]; - } - - /** - * Sending a message to recipients via Symfony Mailer - * - * @param string $email_format - * - * @return bool - * @throws Exception - */ - public function send(string $email_format = 'text/plain'): bool - { - global $lang; - - if (!config()->get('emailer.enabled')) { - return false; - } - - /** Replace vars and prepare message */ - $this->message = preg_replace('#\{([a-z0-9\-_]*?)}#is', "$\\1", $this->message); - foreach ($this->vars as $key => $val) { - $this->message = preg_replace(sprintf('/\$\{?%s\}?/', $key), $val, $this->message); - } - $this->message = trim($this->message); - - /** Set some variables */ - $this->subject = !empty($this->subject) ? $this->subject : $lang['EMAILER_SUBJECT']['EMPTY']; - - /** Prepare message */ - if (config()->get('emailer.smtp.enabled')) { - if (!empty(config()->get('emailer.smtp.host'))) { - $sslType = config()->get('emailer.smtp.ssl_type'); - if (empty($sslType)) { - $sslType = null; - } - /** @var EsmtpTransport $transport external SMTP with SSL */ - $transport = (new EsmtpTransport( - config()->get('emailer.smtp.host'), - config()->get('emailer.smtp.port'), - $sslType - )) - ->setUsername(config()->get('emailer.smtp.username')) - ->setPassword(config()->get('emailer.smtp.password')); - } else { - $transport = new EsmtpTransport('localhost', 25); - } - } else { - $transport = new SendmailTransport(config()->get('emailer.sendmail_command')); - } - - $mailer = new Mailer($transport); - - /** @var Email $message */ - $message = (new Email()) - ->subject($this->subject) - ->to($this->to) - ->from(new Address(config()->get('board_email'), config()->get('board_email_sitename'))) - ->returnPath(new Address(config()->get('bounce_email'))) - ->replyTo($this->reply ?? new Address(config()->get('board_email'))); - - /** - * This non-standard header tells compliant autoresponders ("email holiday mode") to not - * reply to this message because it's an automated email - */ - $message->getHeaders() - ->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply'); - - switch ($email_format) { - case EMAIL_TYPE_HTML: - $message->html($this->message); - break; - case EMAIL_TYPE_TEXT: - $message->text($this->message); - break; - default: - throw new Exception('Unknown email format: ' . $email_format); - } - - /** Send message */ - try { - $mailer->send($message); - } catch (TransportExceptionInterface $e) { - bb_die('Failed sending email: ' . $e->getMessage()); - } - - return true; - } - - /** - * Set message template variables - * - * @param $vars - * - * @return void - */ - public function assign_vars($vars): void - { - $this->vars = array_merge([ - 'BOARD_EMAIL' => config()->get('board_email'), - 'SITENAME' => config()->get('board_email_sitename'), - 'EMAIL_SIG' => !empty(config()->get('board_email_sig')) ? "-- \n" . config()->get('board_email_sig') : '', - ], $vars); - } -} diff --git a/legacy/src/Env.php b/legacy/src/Env.php deleted file mode 100644 index 52e1e7cac..000000000 --- a/legacy/src/Env.php +++ /dev/null @@ -1,116 +0,0 @@ -addAdapter(PutenvAdapter::class); - } - - static::$repository = $builder->immutable()->make(); - } - - return static::$repository; - } - - /** - * Gets the value of an environment variable. - * - * @param string $key - * @param mixed|null $default - * @return mixed - */ - public static function get(string $key, mixed $default = null): mixed - { - return Option::fromValue(static::getRepository()->get($key)) - ->map(function ($value) { - switch (strtolower($value)) { - case 'true': - case '(true)': - return true; - case 'false': - case '(false)': - return false; - case 'empty': - case '(empty)': - return ''; - case 'null': - case '(null)': - return null; - } - - if (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) { - return $matches[2]; - } - - return $value; - }) - ->getOrCall(fn () => $default instanceof Closure ? $default() : $default); - } -} diff --git a/legacy/src/Helpers/CronHelper.php b/legacy/src/Helpers/CronHelper.php deleted file mode 100644 index d037fede5..000000000 --- a/legacy/src/Helpers/CronHelper.php +++ /dev/null @@ -1,138 +0,0 @@ - 2400) { - self::enableBoard(); - self::releaseLockFile(); - } - } - } - - /** - * Снятие блокировки крона (по файлу) - * - * @return void - */ - public static function releaseLockFile(): void - { - if (is_file(CRON_RUNNING)) { - rename(CRON_RUNNING, CRON_ALLOWED); - } - self::touchLockFile(CRON_ALLOWED); - } - - /** - * Создание файла блокировки - * - * @param string $lock_file - * @return void - */ - public static function touchLockFile(string $lock_file): void - { - file_write('', $lock_file, replace_content: true); - } - - /** - * Включение форума (при разблокировке крона) - * - * @return void - */ - public static function enableBoard(): void - { - if (is_file(BB_DISABLED)) { - rename(BB_DISABLED, BB_ENABLED); - } - } - - /** - * Отключение форума (при блокировке крона) - * - * @return void - */ - public static function disableBoard(): void - { - if (is_file(BB_ENABLED)) { - rename(BB_ENABLED, BB_DISABLED); - } - } - - /** - * Проверка наличия файла блокировки - * - * @return bool - */ - public static function hasFileLock(): bool - { - $lock_obtained = false; - - if (is_file(CRON_ALLOWED)) { - $lock_obtained = rename(CRON_ALLOWED, CRON_RUNNING); - } elseif (is_file(CRON_RUNNING)) { - self::releaseDeadlock(); - } elseif (!is_file(CRON_ALLOWED) && !is_file(CRON_RUNNING)) { - file_write('', CRON_ALLOWED); - $lock_obtained = rename(CRON_ALLOWED, CRON_RUNNING); - } - - return $lock_obtained; - } - - /** - * Отслеживание запуска задач - * - * @param string $mode - * @return void - */ - public static function trackRunning(string $mode): void - { - if (!defined('START_MARK')) { - define('START_MARK', TRIGGERS_DIR . '/cron_started_at_' . date('Y-m-d_H-i-s') . '_by_pid_' . getmypid()); - } - - switch ($mode) { - case 'start': - self::touchLockFile(CRON_RUNNING); - file_write('', START_MARK); - break; - case 'end': - if (is_file(START_MARK)) { - unlink(START_MARK); - } - break; - default: - bb_simple_die("Invalid cron track mode: $mode"); - } - } -} diff --git a/legacy/src/Helpers/IsHelper.php b/legacy/src/Helpers/IsHelper.php deleted file mode 100644 index 4495eaa55..000000000 --- a/legacy/src/Helpers/IsHelper.php +++ /dev/null @@ -1,38 +0,0 @@ -getWrappedContainer()->set(Container::class, self::$container); - self::$container->getWrappedContainer()->set('container', self::$container); - - return self::$container; - } - - public static function getContainer(): ?Container - { - return self::$container; - } - - public static function reset(): void - { - self::$container = null; - } - - private static function loadEnvironment(string $rootPath): void - { - if (file_exists($rootPath . '/.env')) { - $dotenv = Dotenv::createImmutable($rootPath); - $dotenv->load(); - } - } - - private static function loadConfiguration(string $rootPath, array $config): array - { - // Load base configuration - $configPath = $rootPath . '/config'; - - // Container configuration - if (file_exists($configPath . '/container.php')) { - $containerConfig = require $configPath . '/container.php'; - $config = array_merge($config, $containerConfig); - } - - // Services configuration - if (file_exists($configPath . '/services.php')) { - $servicesConfig = require $configPath . '/services.php'; - $config['definitions'] = array_merge( - $config['definitions'] ?? [], - $servicesConfig - ); - } - - // Database configuration - if (file_exists($configPath . '/database.php')) { - $config['database'] = require $configPath . '/database.php'; - } - - // Cache configuration - if (file_exists($configPath . '/cache.php')) { - $config['cache'] = require $configPath . '/cache.php'; - } - - // Environment from .env - $config['environment'] = $_ENV['APP_ENV'] ?? 'development'; - $config['debug'] = filter_var($_ENV['APP_DEBUG'] ?? false, FILTER_VALIDATE_BOOLEAN); - - return $config; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/Container.php b/legacy/src/Infrastructure/DependencyInjection/Container.php deleted file mode 100644 index 25254bd3c..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/Container.php +++ /dev/null @@ -1,49 +0,0 @@ -container = $container; - } - - public function get(string $id): mixed - { - return $this->container->get($id); - } - - public function has(string $id): bool - { - return $this->container->has($id); - } - - public function make(string $name, array $parameters = []): mixed - { - return $this->container->make($name, $parameters); - } - - public function call(callable $callable, array $parameters = []): mixed - { - return $this->container->call($callable, $parameters); - } - - public function injectOn(object $instance): object - { - return $this->container->injectOn($instance); - } - - public function getWrappedContainer(): DIContainer - { - return $this->container; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/ContainerFactory.php b/legacy/src/Infrastructure/DependencyInjection/ContainerFactory.php deleted file mode 100644 index e4424c230..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/ContainerFactory.php +++ /dev/null @@ -1,93 +0,0 @@ -build(); - $container = new Container($diContainer); - - // Register and boot service providers - self::registerProviders($container, $config); - - return $container; - } - - private static function configureContainer(ContainerBuilder $builder, array $config): void - { - // Enable compilation in production for better performance - $isProduction = ($config['environment'] ?? 'development') === 'production'; - - if ($isProduction) { - $builder->enableCompilation($config['compilation_dir'] ?? __DIR__ . '/../../../internal_data/cache/container'); - $builder->writeProxiesToFile(true, $config['proxies_dir'] ?? __DIR__ . '/../../../internal_data/cache/proxies'); - } - - // Enable autowiring by default - $builder->useAutowiring($config['autowiring'] ?? true); - } - - private static function addDefinitions(ContainerBuilder $builder, array $config): void - { - // Add config definitions first - if (isset($config['definitions'])) { - $builder->addDefinitions($config['definitions']); - } - - // Add layer-specific definitions - $builder->addDefinitions(DomainDefinitions::getDefinitions()); - $builder->addDefinitions(ApplicationDefinitions::getDefinitions()); - $builder->addDefinitions(InfrastructureDefinitions::getDefinitions($config)); - $builder->addDefinitions(PresentationDefinitions::getDefinitions()); - - // Add custom definition files if provided - if (isset($config['definition_files'])) { - foreach ($config['definition_files'] as $file) { - if (file_exists($file)) { - $builder->addDefinitions($file); - } - } - } - } - - private static function registerProviders(Container $container, array $config): void - { - $providers = $config['providers'] ?? []; - - // Instantiate providers - $instances = []; - foreach ($providers as $providerClass) { - if (class_exists($providerClass)) { - $provider = new $providerClass(); - if ($provider instanceof ServiceProvider) { - $instances[] = $provider; - $provider->register($container); - } - } - } - - // Boot all providers after registration - foreach ($instances as $provider) { - $provider->boot($container); - } - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/Definitions/ApplicationDefinitions.php b/legacy/src/Infrastructure/DependencyInjection/Definitions/ApplicationDefinitions.php deleted file mode 100644 index d8e6d2842..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/Definitions/ApplicationDefinitions.php +++ /dev/null @@ -1,46 +0,0 @@ - DI\factory(function (ContainerInterface $c) { - // return new CommandBus($c); - // }), - - // Query Bus - // 'QueryBusInterface' => DI\factory(function (ContainerInterface $c) { - // return new QueryBus($c); - // }), - - // Event Dispatcher - // 'EventDispatcherInterface' => DI\factory(function (ContainerInterface $c) { - // return new EventDispatcher(); - // }), - - // Application Services - // These typically orchestrate domain objects and handle use cases - - // Forum Handlers - // 'TorrentPier\Application\Forum\Handler\CreatePostHandler' => DI\autowire(), - // 'TorrentPier\Application\Forum\Handler\GetThreadListHandler' => DI\autowire(), - - // Tracker Handlers - // 'TorrentPier\Application\Tracker\Handler\RegisterTorrentHandler' => DI\autowire(), - // 'TorrentPier\Application\Tracker\Handler\ProcessAnnounceHandler' => DI\autowire(), - - // User Handlers - // 'TorrentPier\Application\User\Handler\RegisterUserHandler' => DI\autowire(), - // 'TorrentPier\Application\User\Handler\AuthenticateUserHandler' => DI\autowire(), - ]; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/Definitions/DomainDefinitions.php b/legacy/src/Infrastructure/DependencyInjection/Definitions/DomainDefinitions.php deleted file mode 100644 index 330d5add0..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/Definitions/DomainDefinitions.php +++ /dev/null @@ -1,32 +0,0 @@ - DI\factory(function (ContainerInterface $c) { - // return $c->get('TorrentPier\Infrastructure\Persistence\Repository\ForumRepository'); - // }), - - // 'TorrentPier\Domain\Tracker\Repository\TorrentRepositoryInterface' => DI\factory(function (ContainerInterface $c) { - // return $c->get('TorrentPier\Infrastructure\Persistence\Repository\TorrentRepository'); - // }), - - // 'TorrentPier\Domain\User\Repository\UserRepositoryInterface' => DI\factory(function (ContainerInterface $c) { - // return $c->get('TorrentPier\Infrastructure\Persistence\Repository\UserRepository'); - // }), - ]; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/Definitions/InfrastructureDefinitions.php b/legacy/src/Infrastructure/DependencyInjection/Definitions/InfrastructureDefinitions.php deleted file mode 100644 index ee64f60a1..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/Definitions/InfrastructureDefinitions.php +++ /dev/null @@ -1,84 +0,0 @@ - DI\factory(function () use ($config) { - // $dbConfig = $config['database'] ?? []; - // $dsn = sprintf( - // 'mysql:host=%s;port=%s;dbname=%s;charset=%s', - // $dbConfig['host'] ?? '127.0.0.1', - // $dbConfig['port'] ?? 3306, - // $dbConfig['database'] ?? 'tp', - // $dbConfig['charset'] ?? 'utf8mb4' - // ); - // - // return new Connection( - // $dsn, - // $dbConfig['username'] ?? 'root', - // $dbConfig['password'] ?? '' - // ); - // }), - - // Example: Cache Storage (implement when cache infrastructure is ready) - // 'cache.storage' => DI\factory(function () use ($config) { - // $cacheConfig = $config['cache'] ?? []; - // $driver = $cacheConfig['driver'] ?? 'file'; - // - // switch ($driver) { - // case 'memcached': - // $memcached = new \Memcached(); - // $memcached->addServer( - // $cacheConfig['memcached']['host'] ?? '127.0.0.1', - // $cacheConfig['memcached']['port'] ?? 11211 - // ); - // return new MemcachedStorage($memcached); - // - // case 'sqlite': - // return new SQLiteStorage($cacheConfig['sqlite']['path'] ?? __DIR__ . '/../../../../internal_data/cache/cache.db'); - // - // case 'file': - // default: - // return new FileStorage($cacheConfig['file']['path'] ?? __DIR__ . '/../../../../internal_data/cache'); - // } - // }), - - // Example: Repository Implementations (implement when repositories are created) - // 'TorrentPier\Infrastructure\Persistence\Repository\ForumRepository' => DI\autowire() - // ->constructorParameter('connection', DI\get('database.connection.default')) - // ->constructorParameter('cache', DI\get('cache.factory')), - - // Example: Email Service (implement when email infrastructure is ready) - // 'EmailServiceInterface' => DI\factory(function (ContainerInterface $c) use ($config) { - // $emailConfig = $config['email'] ?? []; - // return new SmtpEmailService($emailConfig); - // }), - - // Example: File Storage (implement when file storage abstraction is ready) - // 'FileStorageInterface' => DI\factory(function (ContainerInterface $c) use ($config) { - // $storageConfig = $config['storage'] ?? []; - // return match ($storageConfig['driver'] ?? 'local') { - // 's3' => new S3FileStorage($storageConfig['s3']), - // default => new LocalFileStorage($storageConfig['local']['path'] ?? __DIR__ . '/../../../../internal_data/uploads'), - // }; - // }), - ]; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/Definitions/PresentationDefinitions.php b/legacy/src/Infrastructure/DependencyInjection/Definitions/PresentationDefinitions.php deleted file mode 100644 index 78a7e0566..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/Definitions/PresentationDefinitions.php +++ /dev/null @@ -1,52 +0,0 @@ - DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Web\ForumController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Web\TrackerController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Web\UserController' => DI\autowire(), - - // API Controllers - // 'TorrentPier\Presentation\Http\Controllers\Api\UserController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Api\TorrentController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Api\ForumController' => DI\autowire(), - - // Admin Controllers - // 'TorrentPier\Presentation\Http\Controllers\Admin\DashboardController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Admin\UserController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Admin\ForumController' => DI\autowire(), - // 'TorrentPier\Presentation\Http\Controllers\Admin\TrackerController' => DI\autowire(), - - // Middleware - // 'AuthenticationMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\AuthenticationMiddleware'), - // 'AuthorizationMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\AuthorizationMiddleware'), - // 'CorsMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\CorsMiddleware'), - // 'RateLimitMiddleware' => DI\autowire('TorrentPier\Presentation\Http\Middleware\RateLimitMiddleware'), - - // CLI Commands - // 'TorrentPier\Presentation\Cli\Commands\CacheCommand' => DI\autowire(), - // 'TorrentPier\Presentation\Cli\Commands\MigrateCommand' => DI\autowire(), - // 'TorrentPier\Presentation\Cli\Commands\SeedCommand' => DI\autowire(), - // 'TorrentPier\Presentation\Cli\Commands\TrackerCommand' => DI\autowire(), - - // View/Response Transformers - // 'JsonResponseTransformer' => DI\autowire('TorrentPier\Presentation\Http\Responses\JsonResponseTransformer'), - // 'HtmlResponseTransformer' => DI\autowire('TorrentPier\Presentation\Http\Responses\HtmlResponseTransformer'), - ]; - } -} diff --git a/legacy/src/Infrastructure/DependencyInjection/ServiceProvider.php b/legacy/src/Infrastructure/DependencyInjection/ServiceProvider.php deleted file mode 100644 index 6bd480beb..000000000 --- a/legacy/src/Infrastructure/DependencyInjection/ServiceProvider.php +++ /dev/null @@ -1,24 +0,0 @@ -sql_query($sql)) { - bb_die('Could not obtain cron script'); - } - - while ($row = DB()->sql_fetchrow($result)) { - $job = $row['cron_script']; - $job_script = INC_DIR . '/cron/jobs/' . $job; - require($job_script); - } - DB()->query(" - UPDATE " . BB_CRON . " SET - last_run = NOW(), - run_counter = run_counter + 1, - next_run = - CASE - WHEN schedule = 'hourly' THEN - DATE_ADD(NOW(), INTERVAL 1 HOUR) - WHEN schedule = 'daily' THEN - DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL TIME_TO_SEC(run_time) SECOND) - WHEN schedule = 'weekly' THEN - DATE_ADD( - DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(NOW()) DAY), INTERVAL 7 DAY), - INTERVAL CONCAT(ROUND(run_day-1), ' ', run_time) DAY_SECOND) - WHEN schedule = 'monthly' THEN - DATE_ADD( - DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(NOW())-1 DAY), INTERVAL 1 MONTH), - INTERVAL CONCAT(ROUND(run_day-1), ' ', run_time) DAY_SECOND) - ELSE - DATE_ADD(NOW(), INTERVAL TIME_TO_SEC(run_interval) SECOND) - END - WHERE cron_id IN ($jobs) - "); - } - - /** - * Delete cron jobs - * - * @param string $jobs - */ - public static function delete_jobs(string $jobs): void - { - DB()->query("DELETE FROM " . BB_CRON . " WHERE cron_id IN ($jobs)"); - } - - /** - * Toggle activity of cron jobs - * - * @param string $jobs - * @param string $cron_action - */ - public static function toggle_active(string $jobs, string $cron_action): void - { - $active = ($cron_action == 'disable') ? 0 : 1; - DB()->query("UPDATE " . BB_CRON . " SET cron_active = $active WHERE cron_id IN ($jobs)"); - } - - /** - * Validate cron admin post query - * - * @param array $cron_arr - * - * @return int|string - */ - public static function validate_cron_post(array $cron_arr) - { - $errors = 'Errors in: '; - $errnum = 0; - if (!$cron_arr['cron_title']) { - $errors .= 'cron title (empty value), '; - $errnum++; - } - if (!$cron_arr['cron_script']) { - $errors .= 'cron script (empty value), '; - $errnum++; - } - if ($errnum > 0) { - $result = $errors . ' total ' . $errnum . ' errors
Back'; - } else { - $result = 1; - } - return $result; - } - - /** - * Insert cron job to database - * - * @param array $cron_arr - */ - public static function insert_cron_job(array $cron_arr): void - { - $row = DB()->fetch_row("SELECT cron_title, cron_script FROM " . BB_CRON . " WHERE cron_title = '" . $_POST['cron_title'] . "' or cron_script = '" . $_POST['cron_script'] . "' "); - - if ($row) { - global $lang; - - if ($_POST['cron_script'] == $row['cron_script']) { - $langmode = $lang['SCRIPT_DUPLICATE']; - } else { - $langmode = $lang['TITLE_DUPLICATE']; - } - - $message = $langmode . "

" . sprintf($lang['CLICK_RETURN_JOBS_ADDED'], "", "") . "

" . sprintf($lang['CLICK_RETURN_JOBS'], "", "") . "

" . sprintf($lang['CLICK_RETURN_ADMIN_INDEX'], "", ""); - - bb_die($message); - } - - $cron_active = $cron_arr['cron_active']; - $cron_title = $cron_arr['cron_title']; - $cron_script = $cron_arr['cron_script']; - $schedule = $cron_arr['schedule']; - $run_day = $cron_arr['run_day']; - $run_time = $cron_arr['run_time']; - $run_order = $cron_arr['run_order']; - $last_run = $cron_arr['last_run']; - $next_run = $cron_arr['next_run']; - $run_interval = $cron_arr['run_interval']; - $log_enabled = $cron_arr['log_enabled']; - $log_file = $cron_arr['log_file']; - $log_sql_queries = $cron_arr['log_sql_queries']; - $disable_board = $cron_arr['disable_board']; - $run_counter = $cron_arr['run_counter']; - - DB()->query("INSERT INTO " . BB_CRON . " (cron_active, cron_title, cron_script, schedule, run_day, run_time, run_order, last_run, next_run, run_interval, log_enabled, log_file, log_sql_queries, disable_board, run_counter) VALUES ( - $cron_active, '$cron_title', '$cron_script', '$schedule', '$run_day', '$run_time', '$run_order', '$last_run', '$next_run', '$run_interval', $log_enabled, '$log_file', $log_sql_queries, $disable_board, '$run_counter')"); - } - - /** - * Update cron job in database - * - * @param array $cron_arr - */ - public static function update_cron_job(array $cron_arr): void - { - $cron_id = $cron_arr['cron_id']; - $cron_active = $cron_arr['cron_active']; - $cron_title = DB()->escape($cron_arr['cron_title']); - $cron_script = DB()->escape($cron_arr['cron_script']); - $schedule = $cron_arr['schedule']; - $run_day = $cron_arr['run_day']; - $run_time = $cron_arr['run_time']; - $run_order = $cron_arr['run_order']; - $last_run = $cron_arr['last_run']; - $next_run = $cron_arr['next_run']; - $run_interval = $cron_arr['run_interval']; - $log_enabled = $cron_arr['log_enabled']; - $log_file = DB()->escape($cron_arr['log_file']); - $log_sql_queries = $cron_arr['log_sql_queries']; - $disable_board = $cron_arr['disable_board']; - $run_counter = $cron_arr['run_counter']; - - DB()->query("UPDATE " . BB_CRON . " SET - cron_active = '$cron_active', - cron_title = '$cron_title', - cron_script = '$cron_script', - schedule = '$schedule', - run_day = '$run_day', - run_time = '$run_time', - run_order = '$run_order', - last_run = '$last_run', - next_run = '$next_run', - run_interval = '$run_interval', - log_enabled = '$log_enabled', - log_file = '$log_file', - log_sql_queries = '$log_sql_queries', - disable_board = '$disable_board', - run_counter = '$run_counter' - WHERE cron_id = $cron_id - "); - } -} diff --git a/legacy/src/Legacy/Admin/Torrent.php b/legacy/src/Legacy/Admin/Torrent.php deleted file mode 100644 index d0e1b80ad..000000000 --- a/legacy/src/Legacy/Admin/Torrent.php +++ /dev/null @@ -1,141 +0,0 @@ -sql_query($sql)) { - bb_die('Could not update ' . $table_name); - } - - if (isset($_POST[$field_name])) { - // Get new status - $in_sql = []; - - foreach ($_POST[$field_name] as $i => $val) { - $in_sql[] = (int)$val; - } - - // Update status - if ($in_sql = implode(',', $in_sql)) { - $sql = "UPDATE $table_name - SET $field_name = 1 - WHERE $key IN($in_sql)"; - - if (!$result = DB()->sql_query($sql)) { - bb_die('Could not update ' . $table_name); - } - } - } - } - - /** - * Assign config variables to template - * - * @param array $default_cfg - * @param array $cfg - */ - public static function set_tpl_vars($default_cfg, $cfg) - { - global $template; - - foreach ($default_cfg as $config_name => $config_value) { - $template->assign_vars([strtoupper($config_name) => htmlspecialchars($cfg[$config_name])]); - } - } - - /** - * Assign boolean config variables to template - * - * @param array $default_cfg - * @param array $cfg - */ - public static function set_tpl_vars_bool($default_cfg, $cfg) - { - global $template, $lang; - - foreach ($default_cfg as $config_name => $config_value) { - // YES/NO 'checked' - $template->assign_vars([ - strtoupper($config_name) . '_YES' => ($cfg[$config_name]) ? HTML_CHECKED : '', - strtoupper($config_name) . '_NO' => (!$cfg[$config_name]) ? HTML_CHECKED : '', - ]); - // YES/NO lang vars - $template->assign_vars([ - 'L_' . strtoupper($config_name) . '_YES' => ($cfg[$config_name]) ? "$lang[YES]" : $lang['YES'], - 'L_' . strtoupper($config_name) . '_NO' => (!$cfg[$config_name]) ? "$lang[NO]" : $lang['NO'], - ]); - } - } - - /** - * Assign language config variables to template - * - * @param array $default_cfg - */ - public static function set_tpl_vars_lang($default_cfg) - { - global $template, $lang; - - foreach ($default_cfg as $config_name => $config_value) { - $template->assign_vars([ - 'L_' . strtoupper($config_name) => $lang[$config_name] ?? '', - 'L_' . strtoupper($config_name) . '_EXPL' => $lang[$config_name . '_expl'] ?? '', - 'L_' . strtoupper($config_name) . '_HEAD' => $lang[$config_name . '_head'] ?? '', - ]); - } - } - - /** - * Update config table - * - * @param string $table_name - * @param array $default_cfg - * @param array $cfg - * @param string $type - */ - public static function update_config_table($table_name, $default_cfg, $cfg, $type) - { - foreach ($default_cfg as $config_name => $config_value) { - if (isset($_POST[$config_name]) && $_POST[$config_name] != $cfg[$config_name]) { - if ($type == 'str') { - $config_value = $_POST[$config_name]; - } elseif ($type == 'bool') { - $config_value = ($_POST[$config_name]) ? 1 : 0; - } elseif ($type == 'num') { - $config_value = abs((int)$_POST[$config_name]); - } else { - return; - } - - bb_update_config([$config_name => $config_value], $table_name); - } - } - } -} diff --git a/legacy/src/Sessions.php b/legacy/src/Sessions.php deleted file mode 100644 index 97e0e773a..000000000 --- a/legacy/src/Sessions.php +++ /dev/null @@ -1,144 +0,0 @@ -get($id); - } - - /** - * Set userdata to cache - * - * @param array|null $userdata - * @param bool $force - * - * @return bool - */ - public static function cache_set_userdata(?array $userdata, bool $force = false): bool - { - if (!$userdata || (self::ignore_cached_userdata() && !$force)) { - return false; - } - - $id = ($userdata['user_id'] == GUEST_UID) ? $userdata['session_ip'] : $userdata['session_id']; - return CACHE('session_cache')->set($id, $userdata, config()->get('session_update_intrv')); - } - - /** - * Delete userdata from cache - * - * @param array $userdata - * - * @return bool - */ - public static function cache_rm_userdata(array $userdata): bool - { - if (!$userdata) { - return false; - } - - $id = ($userdata['user_id'] == GUEST_UID) ? $userdata['session_ip'] : $userdata['session_id']; - return CACHE('session_cache')->rm($id); - } - - /** - * Delete user sessions from cache - * - * @param int|string $user_id - */ - public static function cache_rm_user_sessions(int|string $user_id): void - { - $user_id = get_id_csv(explode(',', (string)$user_id)); - - $rowset = DB()->fetch_rowset("SELECT session_id FROM " . BB_SESSIONS . " WHERE session_user_id IN($user_id)"); - - foreach ($rowset as $row) { - CACHE('session_cache')->rm($row['session_id']); - } - } - - /** - * Update userdata in cache - * - * @param array|null $userdata - * - * @return bool - */ - public static function cache_update_userdata(?array $userdata): bool - { - return self::cache_set_userdata($userdata, true); - } - - /** - * Update userdata in database - * - * @param array $userdata - * @param array $sql_ary - * @param bool $data_already_escaped - * - * @return bool - */ - public static function db_update_userdata(array $userdata, array $sql_ary, bool $data_already_escaped = true): bool - { - if (!$userdata) { - return false; - } - - $sql_args = DB()->build_array('UPDATE', $sql_ary, $data_already_escaped); - DB()->query("UPDATE " . BB_USERS . " SET $sql_args WHERE user_id = {$userdata['user_id']}"); - - if (DB()->affected_rows()) { - self::cache_rm_userdata($userdata); - } - - return true; - } - - /** - * Delete user sessions from cache and database - * - * @param int|string $user_id - */ - public static function delete_user_sessions(int|string $user_id): void - { - self::cache_rm_user_sessions($user_id); - - $user_id = get_id_csv(explode(',', (string)$user_id)); - DB()->query("DELETE FROM " . BB_SESSIONS . " WHERE session_user_id IN($user_id)"); - } -} diff --git a/legacy/src/Whoops/DatabaseErrorHandler.php b/legacy/src/Whoops/DatabaseErrorHandler.php deleted file mode 100644 index 7668c28c1..000000000 --- a/legacy/src/Whoops/DatabaseErrorHandler.php +++ /dev/null @@ -1,393 +0,0 @@ -addToOutput) { - return Handler::DONE; - } - - $inspector = $this->getInspector(); - - if (!$inspector) { - return Handler::DONE; - } - - $exception = $inspector->getException(); - - // Add database information to the exception frames - $this->addDatabaseContextToFrames($inspector); - - // Add global database state information - $this->addGlobalDatabaseInfo($exception); - - return Handler::DONE; - } - - /** - * Set whether to add database info to output - */ - public function setAddToOutput(bool $add): self - { - $this->addToOutput = $add; - return $this; - } - - /** - * Set whether to include query history - */ - public function setIncludeQueryHistory(bool $include): self - { - $this->includeQueryHistory = $include; - return $this; - } - - /** - * Set maximum number of queries to show in history - */ - public function setMaxQueryHistory(int $max): self - { - $this->maxQueryHistory = max(1, $max); - return $this; - } - - /** - * Add database context information to exception frames - */ - private function addDatabaseContextToFrames($inspector): void - { - if (!$inspector) { - return; - } - - $frames = $inspector->getFrames(); - - if (!$frames || empty($frames)) { - return; - } - - foreach ($frames as $frame) { - $frameData = []; - - // Check if this frame involves database operations - $fileName = $frame->getFile(); - $className = $frame->getClass(); - $functionName = $frame->getFunction(); - - // Detect database-related frames - $isDatabaseFrame = $this->isDatabaseRelatedFrame($fileName, $className, $functionName); - - if ($isDatabaseFrame) { - $frameData['database_context'] = $this->getCurrentDatabaseContext(); - - // Add frame-specific database info - $frame->addComment('Database Context', 'This frame involves database operations'); - - foreach ($frameData['database_context'] as $key => $value) { - if (is_string($value) || is_numeric($value)) { - $frame->addComment("DB: $key", $value); - } elseif (is_array($value) && !empty($value)) { - $frame->addComment("DB: $key", json_encode($value, JSON_PRETTY_PRINT)); - } - } - } - } - } - - /** - * Add global database information to the exception - */ - private function addGlobalDatabaseInfo($exception): void - { - try { - $databaseInfo = $this->collectDatabaseInformation(); - - // Use Whoops' built-in method if available - this is the proper way - if (method_exists($exception, 'setAdditionalInfo')) { - $exception->setAdditionalInfo('Database Information', $databaseInfo); - } - // For PHP 8.2+ compatibility: Avoid dynamic properties completely - // Instead, we'll add the info as frame comments on the first database-related frame - else { - $this->addDatabaseInfoAsFrameComments($databaseInfo); - } - } catch (\Exception $e) { - // Don't let database info collection break error handling - if (method_exists($exception, 'setAdditionalInfo')) { - $exception->setAdditionalInfo('Database Info Error', $e->getMessage()); - } - } - } - - /** - * Add database info as frame comments when setAdditionalInfo is not available - */ - private function addDatabaseInfoAsFrameComments(array $databaseInfo): void - { - $inspector = $this->getInspector(); - - if (!$inspector) { - return; - } - - $frames = $inspector->getFrames(); - - // Find the first frame and add database info as comments - if (!empty($frames) && is_array($frames) && isset($frames[0])) { - $firstFrame = $frames[0]; - $firstFrame->addComment('=== Database Information ===', ''); - - foreach ($databaseInfo as $key => $value) { - if (is_string($value) || is_numeric($value)) { - $firstFrame->addComment("DB Info - $key", $value); - } elseif (is_array($value) && !empty($value)) { - $firstFrame->addComment("DB Info - $key", json_encode($value, JSON_PRETTY_PRINT)); - } - } - } - } - - /** - * Check if a frame is related to database operations - */ - private function isDatabaseRelatedFrame(?string $fileName, ?string $className, ?string $functionName): bool - { - if (!$fileName) { - return false; - } - - // Check file paths - $databaseFiles = [ - '/Database/', - '/database/', - 'Database.php', - 'DatabaseDebugger.php', - 'DebugSelection.php', - ]; - - foreach ($databaseFiles as $dbFile) { - if (str_contains($fileName, $dbFile)) { - return true; - } - } - - // Check class names - $databaseClasses = [ - 'Database', - 'DatabaseDebugger', - 'DebugSelection', - 'DB', - 'Nette\Database', - ]; - - if ($className) { - foreach ($databaseClasses as $dbClass) { - if (str_contains($className, $dbClass)) { - return true; - } - } - } - - // Check function names - $databaseFunctions = [ - 'sql_query', - 'fetch_row', - 'fetch_rowset', - 'sql_fetchrow', - 'query', - 'execute', - ]; - - if ($functionName) { - foreach ($databaseFunctions as $dbFunc) { - if (str_contains($functionName, $dbFunc)) { - return true; - } - } - } - - return false; - } - - /** - * Get current database context - */ - private function getCurrentDatabaseContext(): array - { - $context = []; - - try { - // Get main database instance - if (function_exists('DB')) { - $db = DB(); - - $context['current_query'] = $db->cur_query ?? 'None'; - $context['database_server'] = $db->db_server ?? 'Unknown'; - $context['selected_database'] = $db->selected_db ?? 'Unknown'; - - // Connection status - $context['connection_status'] = $db->connection ? 'Active' : 'No connection'; - - // Query stats - $context['total_queries'] = $db->num_queries ?? 0; - $context['total_time'] = isset($db->sql_timetotal) ? sprintf('%.3f sec', $db->sql_timetotal) : 'Unknown'; - - // Recent error information - $sqlError = $db->sql_error(); - if (!empty($sqlError['message'])) { - $context['last_error'] = $sqlError; - } - } - } catch (\Exception $e) { - $context['error'] = 'Could not retrieve database context: ' . $e->getMessage(); - } - - return $context; - } - - /** - * Collect comprehensive database information - */ - private function collectDatabaseInformation(): array - { - $info = [ - 'timestamp' => date('Y-m-d H:i:s'), - 'request_uri' => $_SERVER['REQUEST_URI'] ?? 'CLI', - 'user_ip' => $_SERVER['REMOTE_ADDR'] ?? 'Unknown', - ]; - - try { - // Get information from all database servers - if (class_exists('\TorrentPier\Database\DatabaseFactory')) { - $serverNames = \TorrentPier\Database\DatabaseFactory::getServerNames(); - - foreach ($serverNames as $serverName) { - try { - $db = \TorrentPier\Database\DatabaseFactory::getInstance($serverName); - - $serverInfo = [ - 'server_name' => $serverName, - 'engine' => $db->engine ?? 'Unknown', - 'host' => $db->db_server ?? 'Unknown', - 'database' => $db->selected_db ?? 'Unknown', - 'connection_status' => $db->connection ? 'Connected' : 'Disconnected', - 'total_queries' => $db->num_queries ?? 0, - 'total_time' => isset($db->sql_timetotal) ? sprintf('%.3f sec', $db->sql_timetotal) : 'Unknown', - ]; - - // Current query - if (!empty($db->cur_query)) { - $serverInfo['current_query'] = $this->formatQueryForDisplay($db->cur_query); - } - - // Last error - $sqlError = $db->sql_error(); - if (!empty($sqlError['message'])) { - $serverInfo['last_error'] = $sqlError; - } - - // Recent query history (if available and enabled) - if ($this->includeQueryHistory && !empty($db->dbg)) { - $recentQueries = array_slice($db->dbg, -$this->maxQueryHistory); - $serverInfo['recent_queries'] = []; - - foreach ($recentQueries as $query) { - $serverInfo['recent_queries'][] = [ - 'sql' => $this->formatQueryForDisplay($query['sql'] ?? 'Unknown'), - 'time' => isset($query['time']) ? sprintf('%.3f sec', $query['time']) : 'Unknown', - 'source' => $query['src'] ?? 'Unknown', - ]; - } - } - - $info['databases'][$serverName] = $serverInfo; - - } catch (\Exception $e) { - $info['databases'][$serverName] = [ - 'error' => 'Could not retrieve info: ' . $e->getMessage() - ]; - } - } - } - - // Legacy single database support - if (function_exists('DB') && empty($info['databases'])) { - $db = DB(); - - $info['legacy_database'] = [ - 'engine' => $db->engine ?? 'Unknown', - 'host' => $db->db_server ?? 'Unknown', - 'database' => $db->selected_db ?? 'Unknown', - 'connection_status' => $db->connection ? 'Connected' : 'Disconnected', - 'total_queries' => $db->num_queries ?? 0, - 'total_time' => isset($db->sql_timetotal) ? sprintf('%.3f sec', $db->sql_timetotal) : 'Unknown', - ]; - - if (!empty($db->cur_query)) { - $info['legacy_database']['current_query'] = $this->formatQueryForDisplay($db->cur_query); - } - - $sqlError = $db->sql_error(); - if (!empty($sqlError['message'])) { - $info['legacy_database']['last_error'] = $sqlError; - } - } - - } catch (\Exception $e) { - $info['collection_error'] = $e->getMessage(); - } - - return $info; - } - - /** - * Format SQL query for readable display - */ - private function formatQueryForDisplay(string $query, int $maxLength = 500): string - { - // Remove comments at the start (debug info) - $query = preg_replace('#^/\*.*?\*/#', '', $query); - $query = trim($query); - - // Truncate if too long - if (strlen($query) > $maxLength) { - $query = substr($query, 0, $maxLength) . '... [truncated]'; - } - - return $query; - } - - /** - * Get priority - run after the main PrettyPageHandler - */ - public function contentType(): ?string - { - return 'text/html'; - } -} diff --git a/legacy/src/Whoops/EnhancedPrettyPageHandler.php b/legacy/src/Whoops/EnhancedPrettyPageHandler.php deleted file mode 100644 index 818b86559..000000000 --- a/legacy/src/Whoops/EnhancedPrettyPageHandler.php +++ /dev/null @@ -1,269 +0,0 @@ -connection ? 'Connected' : 'Disconnected'; - $info['Database Server'] = $db->db_server ?? 'Unknown'; - $info['Selected Database'] = $db->selected_db ?? 'Unknown'; - $info['Database Engine'] = $db->engine ?? 'Unknown'; - $info['Total Queries'] = $db->num_queries ?? 0; - - if (isset($db->sql_timetotal)) { - $info['Total Query Time'] = sprintf('%.3f seconds', $db->sql_timetotal); - } - - // Current/Last executed query - if (!empty($db->cur_query)) { - $info['Current Query'] = $this->formatSqlQuery($db->cur_query); - } - - // Database error information - $sqlError = $db->sql_error(); - if (!empty($sqlError['message'])) { - $info['Last Database Error'] = [ - 'Code' => $sqlError['code'] ?? 'Unknown', - 'Message' => $sqlError['message'], - ]; - } - - // Connection details if available - if ($db->connection) { - try { - $pdo = $db->connection->getPdo(); - if ($pdo) { - $info['PDO Driver'] = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) ?? 'Unknown'; - $info['Server Version'] = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION) ?? 'Unknown'; - - // Current PDO error state - $errorCode = $pdo->errorCode(); - if ($errorCode && $errorCode !== '00000') { - $errorInfo = $pdo->errorInfo(); - $info['PDO Error State'] = [ - 'Code' => $errorCode, - 'Info' => $errorInfo[2] ?? 'Unknown' - ]; - } - } - } catch (\Exception $e) { - $info['PDO Error'] = $e->getMessage(); - } - } - } - - // Get information from all database servers (new system) - if (class_exists('\TorrentPier\Database\DatabaseFactory')) { - try { - $serverNames = \TorrentPier\Database\DatabaseFactory::getServerNames(); - - if (count($serverNames) > 1) { - foreach ($serverNames as $serverName) { - try { - $db = \TorrentPier\Database\DatabaseFactory::getInstance($serverName); - $info["Server: $serverName"] = [ - 'Host' => $db->db_server ?? 'Unknown', - 'Database' => $db->selected_db ?? 'Unknown', - 'Queries' => $db->num_queries ?? 0, - 'Connected' => $db->connection ? 'Yes' : 'No', - ]; - } catch (\Exception $e) { - $info["Server: $serverName"] = ['Error' => $e->getMessage()]; - } - } - } - } catch (\Exception $e) { - $info['Multi-Server Error'] = $e->getMessage(); - } - } - - } catch (\Exception $e) { - $info['Collection Error'] = $e->getMessage(); - } - - return $info; - } - - /** - * Get recent SQL queries from debug log - */ - private function getRecentSqlQueries(): array - { - $queries = []; - - try { - if (function_exists('DB')) { - $db = DB(); - - // Check if debug information is available - if (!empty($db->dbg) && is_array($db->dbg)) { - // Get last 5 queries - $recentQueries = array_slice($db->dbg, -5); - - foreach ($recentQueries as $index => $queryInfo) { - $queryNum = $index + 1; - $queries["Query #$queryNum"] = [ - 'SQL' => $this->formatSqlQuery($queryInfo['sql'] ?? 'Unknown'), - 'Time' => isset($queryInfo['time']) ? sprintf('%.3f sec', $queryInfo['time']) : 'Unknown', - 'Source' => $queryInfo['src'] ?? 'Unknown', - 'Info' => $queryInfo['info'] ?? '', - ]; - - // Add memory info if available - if (isset($queryInfo['mem_before'], $queryInfo['mem_after'])) { - $memUsed = $queryInfo['mem_after'] - $queryInfo['mem_before']; - $queries["Query #$queryNum"]['Memory'] = sprintf('%+d bytes', $memUsed); - } - } - } - - if (empty($queries)) { - $queries['Info'] = 'No query debug information available. Enable debug mode to see recent queries.'; - } - } - } catch (\Exception $e) { - $queries['Error'] = $e->getMessage(); - } - - return $queries; - } - - /** - * Get TorrentPier environment information - */ - private function getTorrentPierEnvironment(): array - { - $env = []; - - try { - // Basic environment - $env['Application Environment'] = defined('APP_ENV') ? APP_ENV : 'Unknown'; - $env['Debug Mode'] = defined('DBG_USER') && DBG_USER ? 'Enabled' : 'Disabled'; - $env['SQL Debug'] = defined('SQL_DEBUG') && SQL_DEBUG ? 'Enabled' : 'Disabled'; - - // Configuration status - if (function_exists('config')) { - $config = config(); - $env['Config Loaded'] = 'Yes'; - $env['TorrentPier Version'] = $config->get('tp_version', 'Unknown'); - $env['Board Title'] = $config->get('sitename', 'Unknown'); - } else { - $env['Config Loaded'] = 'No'; - } - - // Cache system - if (function_exists('CACHE')) { - $env['Cache System'] = 'Available'; - } - - // Language system - if (function_exists('lang')) { - $env['Language System'] = 'Available'; - if (isset(lang()->getCurrentLanguage)) { - $env['Current Language'] = lang()->getCurrentLanguage; - } - } - - // Memory and timing - if (defined('TIMESTART')) { - $env['Execution Time'] = sprintf('%.3f sec', microtime(true) - TIMESTART); - } - - if (function_exists('sys')) { - // Use plain text formatting for memory values (no HTML entities) - $env['Peak Memory'] = str_replace(' ', ' ', humn_size(sys('mem_peak'))); - $env['Current Memory'] = str_replace(' ', ' ', humn_size(sys('mem'))); - } - - // Request information - $env['Request Method'] = $_SERVER['REQUEST_METHOD'] ?? 'Unknown'; - $env['Request URI'] = $_SERVER['REQUEST_URI'] ?? 'CLI'; - $env['User Agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'; - $env['Remote IP'] = $_SERVER['REMOTE_ADDR'] ?? 'Unknown'; - - } catch (\Exception $e) { - $env['Error'] = $e->getMessage(); - } - - return $env; - } - - /** - * Format SQL query for display - */ - private function formatSqlQuery(string $query): string - { - // Remove debug comments - $query = preg_replace('#^/\*.*?\*/#', '', $query); - $query = trim($query); - - // Truncate very long queries but keep them readable - if (strlen($query) > 1000) { - return substr($query, 0, 1000) . "\n... [Query truncated - " . (strlen($query) - 1000) . " more characters]"; - } - - return $query; - } - - /** - * Override parent method to add database info and custom styling - */ - public function handle() - { - // Add TorrentPier-specific database information dynamically - try { - $this->addDataTable('Database Information', $this->getDatabaseInformation()); - } catch (\Exception $e) { - $this->addDataTable('Database Information', ['Error' => $e->getMessage()]); - } - - try { - $this->addDataTable('Recent SQL Queries', $this->getRecentSqlQueries()); - } catch (\Exception $e) { - $this->addDataTable('Recent SQL Queries', ['Error' => $e->getMessage()]); - } - - try { - $this->addDataTable('TorrentPier Environment', $this->getTorrentPierEnvironment()); - } catch (\Exception $e) { - $this->addDataTable('TorrentPier Environment', ['Error' => $e->getMessage()]); - } - - return parent::handle(); - } -} diff --git a/legacy/src/helpers.php b/legacy/src/helpers.php deleted file mode 100644 index b01f1bea5..000000000 --- a/legacy/src/helpers.php +++ /dev/null @@ -1,50 +0,0 @@ -get($id); - } catch (NotFoundExceptionInterface $e) { - throw new RuntimeException("Service '$id' not found in container: " . $e->getMessage(), 0, $e); - } catch (ContainerExceptionInterface $e) { - throw new RuntimeException("Container error while resolving '$id': " . $e->getMessage(), 0, $e); - } - } -} diff --git a/legacy/styles/js/libs/legacy.js b/legacy/styles/js/legacy.js similarity index 100% rename from legacy/styles/js/libs/legacy.js rename to legacy/styles/js/legacy.js diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx index 69bdcb84d..0df3d5226 100644 --- a/resources/js/components/app-logo.tsx +++ b/resources/js/components/app-logo.tsx @@ -7,7 +7,7 @@ export default function AppLogo() {
- Laravel Starter Kit + TorrentPier
);
' . htmlspecialchars($val) . '
' . str_replace(["{$this->db->selected_db}.", ',', ';'], ['', ', ', ';
'], htmlspecialchars($val ?? '')) . '