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 @@
-
-
\ 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'][] = '
';
- $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 @@
-
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 .= '' . htmlspecialchars($val) . ' | ';
- }
- $this->explain_hold .= '
';
- }
- $this->explain_hold .= '';
- foreach (array_values($row) as $i => $val) {
- $class = !($i % 2) ? 'row1' : 'row2';
- $this->explain_hold .= '' . str_replace(["{$this->db->selected_db}.", ',', ';'], ['', ', ', '; '], htmlspecialchars($val ?? '')) . ' | ';
- }
- $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
>
);