diff --git a/UPGRADE_GUIDE.md b/UPGRADE_GUIDE.md index 98bf8ca1c..b5a704637 100644 --- a/UPGRADE_GUIDE.md +++ b/UPGRADE_GUIDE.md @@ -5,6 +5,8 @@ This guide helps you upgrade your TorrentPier installation to the latest version ## 📖 Table of Contents - [Configuration System Migration](#configuration-system-migration) +- [Censor System Migration](#censor-system-migration) +- [Development System Migration](#development-system-migration) - [Breaking Changes](#breaking-changes) - [Best Practices](#best-practices) @@ -85,6 +87,171 @@ if (isset(config()->bt_announce_url)) { } ``` +## 🛡️ Censor System Migration + +The word censoring system has been refactored to use a singleton pattern, similar to the Configuration system, providing better performance and consistency. + +### Quick Migration Overview + +```php +// ❌ Old way (still works, but not recommended) +global $wordCensor; +$censored = $wordCensor->censorString($text); + +// ✅ New way (recommended) +$censored = censor()->censorString($text); +``` + +### Key Censor Changes + +#### Basic Usage +```php +// Censor a string +$text = "This contains badword content"; +$censored = censor()->censorString($text); + +// Check if censoring is enabled +if (censor()->isEnabled()) { + $censored = censor()->censorString($text); +} else { + $censored = $text; +} + +// Get count of loaded censored words +$wordCount = censor()->getWordsCount(); +``` + +#### Advanced Usage +```php +// Add runtime censored words (temporary, not saved to database) +censor()->addWord('badword', '***'); +censor()->addWord('anotherbad*', 'replaced'); // Wildcards supported + +// Reload censored words from database (useful after admin updates) +censor()->reload(); + +// Check if censoring is enabled +$isEnabled = censor()->isEnabled(); +``` + +### Backward Compatibility + +The global `$wordCensor` variable is still available and works exactly as before: + +```php +// This still works - backward compatibility maintained +global $wordCensor; +$censored = $wordCensor->censorString($text); + +// But this is now preferred +$censored = censor()->censorString($text); +``` + +### Performance Benefits + +- **Single Instance**: Only one censor instance loads words from database +- **Automatic Reloading**: Words are automatically reloaded when updated in admin panel +- **Memory Efficient**: Shared instance across entire application +- **Lazy Loading**: Words only loaded when censoring is enabled + +### Admin Panel Updates + +When you update censored words in the admin panel, the system now automatically: +1. Updates the datastore cache +2. Reloads the singleton instance with fresh words +3. Applies changes immediately without requiring page refresh + +## 🛠️ Development System Migration + +The development and debugging system has been refactored to use a singleton pattern, providing better resource management and consistency across the application. + +### Quick Migration Overview + +```php +// ❌ Old way (still works, but not recommended) +$sqlLog = \TorrentPier\Dev::getSqlLog(); +$isDebugAllowed = \TorrentPier\Dev::sqlDebugAllowed(); +$shortQuery = \TorrentPier\Dev::shortQuery($sql); + +// ✅ New way (recommended) +$sqlLog = dev()->getSqlDebugLog(); +$isDebugAllowed = dev()->checkSqlDebugAllowed(); +$shortQuery = dev()->formatShortQuery($sql); +``` + +### Key Development System Changes + +#### Basic Usage +```php +// Get SQL debug log +$sqlLog = dev()->getSqlDebugLog(); + +// Check if SQL debugging is allowed +if (dev()->checkSqlDebugAllowed()) { + $debugInfo = dev()->getSqlDebugLog(); +} + +// Format SQL queries for display +$formattedQuery = dev()->formatShortQuery($sql, true); // HTML escaped +$plainQuery = dev()->formatShortQuery($sql, false); // Plain text +``` + +#### New Instance Methods +```php +// Access Whoops instance directly +$whoops = dev()->getWhoops(); + +// Check debug mode status +if (dev()->isDebugEnabled()) { + // Debug mode is active +} + +// Check environment +if (dev()->isLocalEnvironment()) { + // Running in local development +} +``` + +### Backward Compatibility + +All existing static method calls continue to work exactly as before: + +```php +// This still works - backward compatibility maintained +$sqlLog = \TorrentPier\Dev::getSqlLog(); +$isDebugAllowed = \TorrentPier\Dev::sqlDebugAllowed(); +$shortQuery = \TorrentPier\Dev::shortQuery($sql); + +// But this is now preferred +$sqlLog = dev()->getSqlDebugLog(); +$isDebugAllowed = dev()->checkSqlDebugAllowed(); +$shortQuery = dev()->formatShortQuery($sql); +``` + +### Performance Benefits + +- **Single Instance**: Only one debugging instance across the entire application +- **Resource Efficiency**: Whoops handlers initialized once and reused +- **Memory Optimization**: Shared debugging state and configuration +- **Lazy Loading**: Debug features only activated when needed + +### Advanced Usage + +```php +// Access the singleton directly +$devInstance = \TorrentPier\Dev::getInstance(); + +// Initialize the system (called automatically in common.php) +\TorrentPier\Dev::init(); + +// Get detailed environment information +$environment = [ + 'debug_enabled' => dev()->isDebugEnabled(), + 'local_environment' => dev()->isLocalEnvironment(), + 'sql_debug_allowed' => dev()->sqlDebugAllowed(), +]; +``` + ## ⚠️ Breaking Changes ### Deprecated Functions @@ -92,6 +259,12 @@ if (isset(config()->bt_announce_url)) { - `set_config()` → Use `config()->set()` - Direct `$bb_cfg` access → Use `config()` methods +### Deprecated Patterns +- `new TorrentPier\Censor()` → Use `censor()` global function +- Direct `$wordCensor` access → Use `censor()` methods +- `new TorrentPier\Dev()` → Use `dev()` global function +- Static `Dev::` methods → Use `dev()` instance methods + ### File Structure Changes - New `/src/` directory for modern PHP classes - Reorganized template structure @@ -123,6 +296,50 @@ class TrackerService { } ``` +### Censor Management +```php +// ✅ Check if censoring is enabled before processing +function processUserInput(string $text): string { + if (censor()->isEnabled()) { + return censor()->censorString($text); + } + return $text; +} + +// ✅ Use the singleton consistently +$censoredText = censor()->censorString($input); +``` + +### Development and Debugging +```php +// ✅ Use instance methods for debugging +if (dev()->checkSqlDebugAllowed()) { + $debugLog = dev()->getSqlDebugLog(); +} + +// ✅ Access debugging utilities consistently +function formatSqlForDisplay(string $sql): string { + return dev()->formatShortQuery($sql, true); +} + +// ✅ Check environment properly +if (dev()->isLocalEnvironment()) { + // Development-specific code +} +class ForumPost { + public function getDisplayText(): string { + return censor()->censorString($this->text); + } +} + +// ✅ Add runtime words when needed +function setupCustomCensoring(): void { + if (isCustomModeEnabled()) { + censor()->addWord('custombad*', '[censored]'); + } +} +``` + ### Error Handling ```php // ✅ Graceful error handling diff --git a/admin/admin_words.php b/admin/admin_words.php index ba0746ca1..94f11caba 100644 --- a/admin/admin_words.php +++ b/admin/admin_words.php @@ -81,6 +81,7 @@ if ($mode != '') { } $datastore->update('censor'); + censor()->reload(); // Reload the singleton instance with updated words $message .= '

' . sprintf($lang['CLICK_RETURN_WORDADMIN'], '', '') . '

' . sprintf($lang['CLICK_RETURN_ADMIN_INDEX'], '', ''); bb_die($message); @@ -95,6 +96,7 @@ if ($mode != '') { } $datastore->update('censor'); + censor()->reload(); // Reload the singleton instance with updated words bb_die($lang['WORD_REMOVED'] . '

' . sprintf($lang['CLICK_RETURN_WORDADMIN'], '', '') . '

' . sprintf($lang['CLICK_RETURN_ADMIN_INDEX'], '', '')); } else { diff --git a/common.php b/common.php index b896f4194..5da013a31 100644 --- a/common.php +++ b/common.php @@ -86,7 +86,8 @@ if (is_file(BB_PATH . '/library/config.local.php')) { require_once BB_PATH . '/library/config.local.php'; } -// Initialize Config singleton +/** @noinspection PhpUndefinedVariableInspection */ +// Initialize Config singleton, bb_cfg from global file config $config = \TorrentPier\Config::init($bb_cfg); /** @@ -99,6 +100,26 @@ function config(): \TorrentPier\Config return \TorrentPier\Config::getInstance(); } +/** + * Get the Censor instance + * + * @return \TorrentPier\Censor + */ +function censor(): \TorrentPier\Censor +{ + return \TorrentPier\Censor::getInstance(); +} + +/** + * Get the Dev instance + * + * @return \TorrentPier\Dev + */ +function dev(): \TorrentPier\Dev +{ + return \TorrentPier\Dev::getInstance(); +} + /** * Initialize debug */ @@ -108,7 +129,7 @@ if (APP_ENV === 'local') { } else { define('DBG_USER', isset($_COOKIE[COOKIE_DBG])); } -(new \TorrentPier\Dev()); +(\TorrentPier\Dev::init()); /** * Server variables initialize diff --git a/index.php b/index.php index c946c758f..55d64e381 100644 --- a/index.php +++ b/index.php @@ -339,7 +339,7 @@ if (config()->get('show_latest_news')) { $template->assign_block_vars('news', [ 'NEWS_TOPIC_ID' => $news['topic_id'], - 'NEWS_TITLE' => str_short($wordCensor->censorString($news['topic_title']), config()->get('max_news_title')), + 'NEWS_TITLE' => str_short(censor()->censorString($news['topic_title']), config()->get('max_news_title')), 'NEWS_TIME' => bb_date($news['topic_time'], 'd-M', false), 'NEWS_IS_NEW' => is_unread($news['topic_time'], $news['topic_id'], $news['forum_id']), ]); @@ -362,7 +362,7 @@ if (config()->get('show_network_news')) { $template->assign_block_vars('net', [ 'NEWS_TOPIC_ID' => $net['topic_id'], - 'NEWS_TITLE' => str_short($wordCensor->censorString($net['topic_title']), config()->get('max_net_title')), + 'NEWS_TITLE' => str_short(censor()->censorString($net['topic_title']), config()->get('max_net_title')), 'NEWS_TIME' => bb_date($net['topic_time'], 'd-M', false), 'NEWS_IS_NEW' => is_unread($net['topic_time'], $net['topic_id'], $net['forum_id']), ]); diff --git a/library/ajax/posts.php b/library/ajax/posts.php index 2c5405df3..2cff05d00 100644 --- a/library/ajax/posts.php +++ b/library/ajax/posts.php @@ -11,7 +11,7 @@ if (!defined('IN_AJAX')) { die(basename(__FILE__)); } -global $lang, $userdata, $wordCensor; +global $lang, $userdata; if (!isset($this->request['type'])) { $this->ajax_die('empty type'); @@ -80,7 +80,7 @@ switch ($this->request['type']) { // hide sid $message = preg_replace('#(?<=[\?&;]sid=)[a-zA-Z0-9]#', 'sid', $message); - $message = $wordCensor->censorString($message); + $message = censor()->censorString($message); if ($post['post_id'] == $post['topic_first_post_id']) { $message = "[quote]" . $post['topic_title'] . "[/quote]\r"; diff --git a/library/includes/bbcode.php b/library/includes/bbcode.php index 7e5a40916..391814489 100644 --- a/library/includes/bbcode.php +++ b/library/includes/bbcode.php @@ -401,12 +401,12 @@ function add_search_words($post_id, $post_message, $topic_title = '', $only_retu function bbcode2html($text) { - global $bbcode, $wordCensor; + global $bbcode; if (!isset($bbcode)) { $bbcode = new TorrentPier\Legacy\BBCode(); } - $text = $wordCensor->censorString($text); + $text = censor()->censorString($text); return $bbcode->bbcode2html($text); } diff --git a/library/includes/init_bb.php b/library/includes/init_bb.php index 622e696cf..d1fd494f4 100644 --- a/library/includes/init_bb.php +++ b/library/includes/init_bb.php @@ -379,8 +379,10 @@ require_once INC_DIR . '/functions.php'; config()->merge(bb_get_config(BB_CONFIG)); $bb_cfg = config()->all(); +// wordCensor deprecated, but kept for compatibility with non-adapted code +$wordCensor = censor(); + $log_action = new TorrentPier\Legacy\LogAction(); -$wordCensor = new TorrentPier\Censor(); $html = new TorrentPier\Legacy\Common\Html(); $user = new TorrentPier\Legacy\Common\User(); diff --git a/library/includes/page_footer_dev.php b/library/includes/page_footer_dev.php index d7ddd49eb..5f972d735 100644 --- a/library/includes/page_footer_dev.php +++ b/library/includes/page_footer_dev.php @@ -71,7 +71,7 @@ if (!empty($_COOKIE['explain'])) { } } -$sql_log = !empty($_COOKIE['sql_log']) ? \TorrentPier\Dev::getSqlLog() : false; +$sql_log = !empty($_COOKIE['sql_log']) ? dev()->getSqlDebugLog() : false; if ($sql_log) { echo '
' . $sql_log . '

'; diff --git a/library/includes/ucp/topic_watch.php b/library/includes/ucp/topic_watch.php index c684cbabc..c75538a11 100644 --- a/library/includes/ucp/topic_watch.php +++ b/library/includes/ucp/topic_watch.php @@ -83,7 +83,7 @@ if ($watch_count > 0) { 'ROW_CLASS' => (!($i % 2)) ? 'row1' : 'row2', 'POST_ID' => $watch[$i]['topic_first_post_id'], 'TOPIC_ID' => $watch[$i]['topic_id'], - 'TOPIC_TITLE' => str_short($wordCensor->censorString($watch[$i]['topic_title']), 70), + 'TOPIC_TITLE' => str_short(censor()->censorString($watch[$i]['topic_title']), 70), 'FULL_TOPIC_TITLE' => $watch[$i]['topic_title'], 'U_TOPIC' => TOPIC_URL . $watch[$i]['topic_id'], 'FORUM_TITLE' => $watch[$i]['forum_name'], diff --git a/posting.php b/posting.php index b41db5f6e..5fb5146cb 100644 --- a/posting.php +++ b/posting.php @@ -472,8 +472,8 @@ if ($refresh || $error_msg || ($submit && $topic_has_new_posts)) { // hide sid $message = preg_replace('#(?<=[\?&;]sid=)[a-zA-Z0-9]#', 'sid', $message); - $subject = $wordCensor->censorString($subject); - $message = $wordCensor->censorString($message); + $subject = censor()->censorString($subject); + $message = censor()->censorString($message); if (!preg_match('/^Re:/', $subject) && !empty($subject)) { $subject = 'Re: ' . $subject; diff --git a/privmsg.php b/privmsg.php index 42b47d1c1..409d0aacb 100644 --- a/privmsg.php +++ b/privmsg.php @@ -376,8 +376,8 @@ if ($mode == 'read') { // $post_subject = htmlCHR($privmsg['privmsgs_subject']); $private_message = $privmsg['privmsgs_text']; - $post_subject = $wordCensor->censorString($post_subject); - $private_message = $wordCensor->censorString($private_message); + $post_subject = censor()->censorString($post_subject); + $private_message = censor()->censorString($private_message); $private_message = bbcode2html($private_message); // @@ -1044,8 +1044,8 @@ if ($mode == 'read') { if ($preview && !$error) { $preview_message = bbcode2html($privmsg_message); - $preview_subject = $wordCensor->censorString($privmsg_subject); - $preview_message = $wordCensor->censorString($preview_message); + $preview_subject = censor()->censorString($privmsg_subject); + $preview_message = censor()->censorString($preview_message); $s_hidden_fields = ''; $s_hidden_fields .= ''; @@ -1381,7 +1381,7 @@ if ($mode == 'read') { $msg_userid = $row['user_id']; $msg_user = profile_url($row); - $msg_subject = $wordCensor->censorString($row['privmsgs_subject']); + $msg_subject = censor()->censorString($row['privmsgs_subject']); $u_subject = PM_URL . "?folder=$folder&mode=read&" . POST_POST_URL . "=$privmsg_id"; diff --git a/search.php b/search.php index b0d9bb977..01e977a65 100644 --- a/search.php +++ b/search.php @@ -571,7 +571,7 @@ if ($post_mode) { 'FORUM_ID' => $forum_id, 'FORUM_NAME' => $forum_name_html[$forum_id], 'TOPIC_ID' => $topic_id, - 'TOPIC_TITLE' => $wordCensor->censorString($first_post['topic_title']), + 'TOPIC_TITLE' => censor()->censorString($first_post['topic_title']), 'TOPIC_ICON' => get_topic_icon($first_post, $is_unread_t), )); @@ -586,7 +586,7 @@ if ($post_mode) { } $message = get_parsed_post($post); - $message = $wordCensor->censorString($message); + $message = censor()->censorString($message); $template->assign_block_vars('t.p', array( 'ROW_NUM' => $row_num, @@ -787,7 +787,7 @@ else { 'FORUM_NAME' => $forum_name_html[$forum_id], 'TOPIC_ID' => $topic_id, 'HREF_TOPIC_ID' => $moved ? $topic['topic_moved_id'] : $topic['topic_id'], - 'TOPIC_TITLE' => $wordCensor->censorString($topic['topic_title']), + 'TOPIC_TITLE' => censor()->censorString($topic['topic_title']), 'IS_UNREAD' => $is_unread, 'TOPIC_ICON' => get_topic_icon($topic, $is_unread), 'PAGINATION' => $moved ? '' : build_topic_pagination(TOPIC_URL . $topic_id, $topic['topic_replies'], config()->get('posts_per_page')), diff --git a/src/Ajax.php b/src/Ajax.php index a4005ee90..374ee6664 100644 --- a/src/Ajax.php +++ b/src/Ajax.php @@ -68,7 +68,9 @@ class Ajax */ public function exec() { - global $lang; + /** @noinspection PhpUnusedLocalVariableInspection */ + // bb_cfg deprecated, but kept for compatibility with non-adapted ajax files + global $bb_cfg, $lang; // Exit if we already have errors if (!empty($this->response['error_code'])) { @@ -194,8 +196,8 @@ class Ajax ]; } - if (Dev::sqlDebugAllowed()) { - $this->response['sql_log'] = Dev::getSqlLog(); + if (dev()->checkSqlDebugAllowed()) { + $this->response['sql_log'] = dev()->getSqlDebugLog(); } // sending output will be handled by $this->ob_handler() diff --git a/src/Censor.php b/src/Censor.php index 5d564240e..dc5c760ce 100644 --- a/src/Censor.php +++ b/src/Censor.php @@ -10,11 +10,15 @@ namespace TorrentPier; /** - * Class Censor - * @package TorrentPier + * Word Censoring System + * + * Singleton class that provides word censoring functionality + * with automatic loading of censored words from the datastore. */ class Censor { + private static ?Censor $instance = null; + /** * Word replacements * @@ -32,11 +36,38 @@ class Censor /** * Initialize word censor */ - public function __construct() + private function __construct() + { + $this->loadCensoredWords(); + } + + /** + * Get the singleton instance of Censor + */ + public static function getInstance(): Censor + { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Initialize the censor system (for compatibility) + */ + public static function init(): Censor + { + return self::getInstance(); + } + + /** + * Load censored words from datastore + */ + private function loadCensoredWords(): void { global $datastore; - if (!config()->get('use_word_censor')) { + if (!$this->isEnabled()) { return; } @@ -57,6 +88,62 @@ class Censor */ public function censorString(string $word): string { + if (!$this->isEnabled()) { + return $word; + } + return preg_replace($this->words, $this->replacements, $word); } + + /** + * Reload censored words from datastore + * Useful when words are updated in admin panel + */ + public function reload(): void + { + $this->words = []; + $this->replacements = []; + $this->loadCensoredWords(); + } + + /** + * Check if censoring is enabled + */ + public function isEnabled(): bool + { + return config()->get('use_word_censor', false); + } + + /** + * Add a censored word (runtime only) + * + * @param string $word + * @param string $replacement + */ + public function addWord(string $word, string $replacement): void + { + $this->words[] = '#(?replacements[] = $replacement; + } + + /** + * Get all censored words count + */ + public function getWordsCount(): int + { + return count($this->words); + } + + /** + * 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/src/Dev.php b/src/Dev.php index 0ad193880..e3661a11d 100644 --- a/src/Dev.php +++ b/src/Dev.php @@ -26,11 +26,15 @@ use jacklul\MonologTelegramHandler\TelegramFormatter; use Exception; /** - * Class Dev - * @package TorrentPier + * Development and Debugging System + * + * Singleton class that provides development and debugging functionality + * including error handling, SQL logging, and debugging utilities. */ class Dev { + private static ?Dev $instance = null; + /** * Whoops instance * @@ -39,9 +43,9 @@ class Dev private Run $whoops; /** - * Constructor + * Initialize debugging system */ - public function __construct() + private function __construct() { $this->whoops = new Run; @@ -60,6 +64,25 @@ class Dev $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 * @@ -164,44 +187,44 @@ class Dev } /** - * Get SQL debug log + * Get SQL debug log (instance method) * * @return string * @throws Exception */ - public static function getSqlLog(): string + public function getSqlLogInstance(): string { global $DBS, $CACHES, $datastore; $log = ''; foreach ($DBS->srv as $srv_name => $db_obj) { - $log .= !empty($db_obj->dbg) ? self::getSqlLogHtml($db_obj, "database: $srv_name [{$db_obj->engine}]") : ''; + $log .= !empty($db_obj->dbg) ? $this->getSqlLogHtml($db_obj, "database: $srv_name [{$db_obj->engine}]") : ''; } foreach ($CACHES->obj as $cache_name => $cache_obj) { if (!empty($cache_obj->db->dbg)) { - $log .= self::getSqlLogHtml($cache_obj->db, "cache: $cache_name [{$cache_obj->db->engine}]"); + $log .= $this->getSqlLogHtml($cache_obj->db, "cache: $cache_name [{$cache_obj->db->engine}]"); } elseif (!empty($cache_obj->dbg)) { - $log .= self::getSqlLogHtml($cache_obj, "cache: $cache_name [{$cache_obj->engine}]"); + $log .= $this->getSqlLogHtml($cache_obj, "cache: $cache_name [{$cache_obj->engine}]"); } } if (!empty($datastore->db->dbg)) { - $log .= self::getSqlLogHtml($datastore->db, "cache: datastore [{$datastore->db->engine}]"); + $log .= $this->getSqlLogHtml($datastore->db, "cache: datastore [{$datastore->db->engine}]"); } elseif (!empty($datastore->dbg)) { - $log .= self::getSqlLogHtml($datastore, "cache: datastore [{$datastore->engine}]"); + $log .= $this->getSqlLogHtml($datastore, "cache: datastore [{$datastore->engine}]"); } return $log; } /** - * Sql debug status + * Sql debug status (instance method) * * @return bool */ - public static function sqlDebugAllowed(): bool + public function sqlDebugAllowedInstance(): bool { return (SQL_DEBUG && DBG_USER && !empty($_COOKIE['sql_log'])); } @@ -215,13 +238,13 @@ class Dev * @return string * @throws Exception */ - private static function getSqlLogHtml(object $db_obj, string $log_name): string + 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 = self::shortQuery($dbg['sql'], true); + $sql = $this->shortQueryInstance($dbg['sql'], true); $time = sprintf('%.3f', $dbg['time']); $perc = '[' . round($dbg['time'] * 100 / $db_obj->sql_timetotal) . '%]'; $info = !empty($dbg['info']) ? $dbg['info'] . ' [' . $dbg['src'] . ']' : $dbg['src']; @@ -238,13 +261,13 @@ class Dev } /** - * Short query + * Short query (instance method) * * @param string $sql * @param bool $esc_html * @return string */ - public static function shortQuery(string $sql, bool $esc_html = false): string + public function shortQueryInstance(string $sql, bool $esc_html = false): string { $max_len = 100; $sql = str_compact($sql); @@ -257,4 +280,120 @@ class Dev 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 local environment + * + * @return bool + */ + public function isLocalEnvironment(): bool + { + return APP_ENV === 'local'; + } + + /** + * 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/src/Legacy/Atom.php b/src/Legacy/Atom.php index 090f23aee..5c50a7655 100644 --- a/src/Legacy/Atom.php +++ b/src/Legacy/Atom.php @@ -179,7 +179,7 @@ class Atom */ private static function create_atom($file_path, $mode, $id, $title, $topics) { - global $lang, $wordCensor; + global $lang; $date = null; $time = null; $dir = \dirname($file_path); @@ -213,7 +213,7 @@ class Atom if (isset($topic['tor_status'])) { $tor_status = " ({$lang['TOR_STATUS_NAME'][$topic['tor_status']]})"; } - $topic_title = $wordCensor->censorString($topic['topic_title']); + $topic_title = censor()->censorString($topic['topic_title']); $author_name = $topic['first_username'] ?: $lang['GUEST']; $last_time = $topic['topic_last_post_time']; if ($topic['topic_last_post_edit_time']) { diff --git a/src/Legacy/Cache/APCu.php b/src/Legacy/Cache/APCu.php index d7e4eb8ed..732e6b5e7 100644 --- a/src/Legacy/Cache/APCu.php +++ b/src/Legacy/Cache/APCu.php @@ -61,7 +61,7 @@ class APCu extends Common } $this->apcu = new Apc(); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Cache/Common.php b/src/Legacy/Cache/Common.php index 4fb32eec9..b6fdbe85a 100644 --- a/src/Legacy/Cache/Common.php +++ b/src/Legacy/Cache/Common.php @@ -82,7 +82,7 @@ class Common switch ($mode) { case 'start': $this->sql_starttime = utime(); - $dbg['sql'] = Dev::shortQuery($cur_query ?? $this->cur_query); + $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'); diff --git a/src/Legacy/Cache/File.php b/src/Legacy/Cache/File.php index fea4d0ea5..fc64fb068 100644 --- a/src/Legacy/Cache/File.php +++ b/src/Legacy/Cache/File.php @@ -61,7 +61,7 @@ class File extends Common $filesystem = new Filesystem($adapter); $this->file = new Flysystem($filesystem); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Cache/Memcached.php b/src/Legacy/Cache/Memcached.php index 4c5c5225b..27aec1942 100644 --- a/src/Legacy/Cache/Memcached.php +++ b/src/Legacy/Cache/Memcached.php @@ -85,7 +85,7 @@ class Memcached extends Common $this->client = new MemcachedClient(); $this->cfg = $cfg; $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Cache/Redis.php b/src/Legacy/Cache/Redis.php index 08a6a16ab..df7528420 100644 --- a/src/Legacy/Cache/Redis.php +++ b/src/Legacy/Cache/Redis.php @@ -85,7 +85,7 @@ class Redis extends Common $this->client = new RedisClient(); $this->cfg = $cfg; $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Cache/Sqlite.php b/src/Legacy/Cache/Sqlite.php index ae38a7699..c8079b5e5 100644 --- a/src/Legacy/Cache/Sqlite.php +++ b/src/Legacy/Cache/Sqlite.php @@ -71,7 +71,7 @@ class Sqlite extends Common $client = new PDO('sqlite:' . $dir . $this->dbExtension); $this->sqlite = new SQLiteCache($client); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Datastore/APCu.php b/src/Legacy/Datastore/APCu.php index c99e1c333..76ca886c7 100644 --- a/src/Legacy/Datastore/APCu.php +++ b/src/Legacy/Datastore/APCu.php @@ -54,7 +54,7 @@ class APCu extends Common } $this->apcu = new Apc(); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Datastore/Common.php b/src/Legacy/Datastore/Common.php index 53f148d6e..89490737a 100644 --- a/src/Legacy/Datastore/Common.php +++ b/src/Legacy/Datastore/Common.php @@ -157,7 +157,7 @@ class Common switch ($mode) { case 'start': $this->sql_starttime = utime(); - $dbg['sql'] = Dev::shortQuery($cur_query ?? $this->cur_query); + $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'); diff --git a/src/Legacy/Datastore/File.php b/src/Legacy/Datastore/File.php index a802724ba..d6aef20f8 100644 --- a/src/Legacy/Datastore/File.php +++ b/src/Legacy/Datastore/File.php @@ -54,7 +54,7 @@ class File extends Common $filesystem = new Filesystem($adapter); $this->file = new Flysystem($filesystem); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Datastore/Memcached.php b/src/Legacy/Datastore/Memcached.php index 65fb92c94..87c1c97f0 100644 --- a/src/Legacy/Datastore/Memcached.php +++ b/src/Legacy/Datastore/Memcached.php @@ -78,7 +78,7 @@ class Memcached extends Common $this->client = new MemcachedClient(); $this->cfg = $cfg; $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Datastore/Redis.php b/src/Legacy/Datastore/Redis.php index 20a843589..71603b229 100644 --- a/src/Legacy/Datastore/Redis.php +++ b/src/Legacy/Datastore/Redis.php @@ -78,7 +78,7 @@ class Redis extends Common $this->client = new RedisClient(); $this->cfg = $cfg; $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Datastore/Sqlite.php b/src/Legacy/Datastore/Sqlite.php index 9d032a4f5..b15ce1625 100644 --- a/src/Legacy/Datastore/Sqlite.php +++ b/src/Legacy/Datastore/Sqlite.php @@ -64,7 +64,7 @@ class Sqlite extends Common $client = new PDO('sqlite:' . $dir . $this->dbExtension); $this->sqlite = new SQLiteCache($client); $this->prefix = $prefix; - $this->dbg_enabled = Dev::sqlDebugAllowed(); + $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } /** diff --git a/src/Legacy/Post.php b/src/Legacy/Post.php index feb0a71f1..a3d933f20 100644 --- a/src/Legacy/Post.php +++ b/src/Legacy/Post.php @@ -341,7 +341,7 @@ class Post */ public static function user_notification($mode, &$post_data, &$topic_title, &$forum_id, &$topic_id, &$notify_user) { - global $lang, $userdata, $wordCensor; + global $lang, $userdata; if (!config()->get('topic_notify_enabled')) { return; @@ -363,7 +363,7 @@ class Post "); if ($watch_list) { - $topic_title = $wordCensor->censorString($topic_title); + $topic_title = censor()->censorString($topic_title); $u_topic = make_url(TOPIC_URL . $topic_id . '&view=newest#newest'); $unwatch_topic = make_url(TOPIC_URL . "$topic_id&unwatch=topic"); diff --git a/src/Legacy/SqlDb.php b/src/Legacy/SqlDb.php index 366108923..6394e8725 100644 --- a/src/Legacy/SqlDb.php +++ b/src/Legacy/SqlDb.php @@ -60,7 +60,7 @@ class SqlDb global $DBS; $this->cfg = array_combine($this->cfg_keys, $cfg_values); - $this->dbg_enabled = (Dev::sqlDebugAllowed() || !empty($_COOKIE['explain'])); + $this->dbg_enabled = (dev()->checkSqlDebugAllowed() || !empty($_COOKIE['explain'])); $this->do_explain = ($this->dbg_enabled && !empty($_COOKIE['explain'])); $this->slow_time = SQL_SLOW_QUERY_TIME; @@ -839,7 +839,7 @@ class SqlDb $msg[] = sprintf('%-6s', $q_time); $msg[] = sprintf('%05d', getmypid()); $msg[] = $this->db_server; - $msg[] = Dev::shortQuery($this->cur_query); + $msg[] = dev()->formatShortQuery($this->cur_query); $msg = implode(LOG_SEPR, $msg); $msg .= ($info = $this->query_info()) ? ' # ' . $info : ''; $msg .= ' # ' . $this->debug_find_source() . ' '; @@ -948,7 +948,7 @@ class SqlDb ' . $this->explain_hold . ' -
' . Dev::shortQuery($dbg['sql'], true) . '  
+
' . dev()->formatShortQuery($dbg['sql'], true) . '  

'; break; diff --git a/viewforum.php b/viewforum.php index d1cfb460f..98519bce7 100644 --- a/viewforum.php +++ b/viewforum.php @@ -445,7 +445,7 @@ foreach ($topic_rowset as $topic) { 'FORUM_ID' => $forum_id, 'TOPIC_ID' => $topic_id, 'HREF_TOPIC_ID' => $moved ? $topic['topic_moved_id'] : $topic['topic_id'], - 'TOPIC_TITLE' => $wordCensor->censorString($topic['topic_title']), + 'TOPIC_TITLE' => censor()->censorString($topic['topic_title']), 'TOPICS_SEPARATOR' => $separator, 'IS_UNREAD' => $is_unread, 'TOPIC_ICON' => get_topic_icon($topic, $is_unread), diff --git a/viewtopic.php b/viewtopic.php index b1117690d..efb89bc59 100644 --- a/viewtopic.php +++ b/viewtopic.php @@ -364,7 +364,7 @@ if (!$ranks = $datastore->get('ranks')) { } // Censor topic title -$topic_title = $wordCensor->censorString($topic_title); +$topic_title = censor()->censorString($topic_title); // Post, reply and other URL generation for templating vars $new_topic_url = POSTING_URL . "?mode=newtopic&" . POST_FORUM_URL . "=$forum_id"; @@ -624,8 +624,8 @@ for ($i = 0; $i < $total_posts; $i++) { $user_sig = str_replace( '\"', '"', substr( - preg_replace_callback('#(\>(((?>([^><]+|(?R)))*)\<))#s', function ($matches) use ($wordCensor) { - return $wordCensor->censorString(reset($matches)); + preg_replace_callback('#(\>(((?>([^><]+|(?R)))*)\<))#s', function ($matches) { + return censor()->censorString(reset($matches)); }, '>' . $user_sig . '<'), 1, -1 ) ); @@ -634,8 +634,8 @@ for ($i = 0; $i < $total_posts; $i++) { $message = str_replace( '\"', '"', substr( - preg_replace_callback('#(\>(((?>([^><]+|(?R)))*)\<))#s', function ($matches) use ($wordCensor) { - return $wordCensor->censorString(reset($matches)); + preg_replace_callback('#(\>(((?>([^><]+|(?R)))*)\<))#s', function ($matches) { + return censor()->censorString(reset($matches)); }, '>' . $message . '<'), 1, -1 ) );