<?xml version="1.0" encoding="UTF-8"?>        <rss version="2.0"
             xmlns:atom="http://www.w3.org/2005/Atom"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
             xmlns:admin="http://webns.net/mvcb/"
             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:content="http://purl.org/rss/1.0/modules/content/">
        <channel>
            <title>
									wpForo Performance Analysis Report fix - How-to and Troubleshooting - wpForo 2.0				            </title>
            <link>https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/</link>
            <description>Discussion Board</description>
            <language>en-US</language>
            <lastBuildDate>Tue, 12 May 2026 21:55:45 +0000</lastBuildDate>
            <generator>wpForo</generator>
            <ttl>60</ttl>
							                    <item>
                        <title>RE: wpForo Performance Analysis Report fix</title>
                        <link>https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-129417</link>
                        <pubDate>Mon, 22 Dec 2025 10:46:04 +0000</pubDate>
                        <description><![CDATA[Hi, @hmeonot,
Thank you very much. This item has already been added to our to-do list, and the team plans to implement it in upcoming versions.]]></description>
                        <content:encoded><![CDATA[<p>Hi, @hmeonot,</p>
<p>Thank you very much. This item has already been added to our to-do list, and the team plans to implement it in upcoming versions.</p>]]></content:encoded>
						                            <category domain="https://wpforo.com/community/how-to-and-troubleshooting-2/">How-to and Troubleshooting - wpForo 2.0</category>                        <dc:creator>Sofy</dc:creator>
                        <guid isPermaLink="true">https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-129417</guid>
                    </item>
				                    <item>
                        <title>RE: wpForo Performance Analysis Report fix</title>
                        <link>https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-128991</link>
                        <pubDate>Sat, 13 Dec 2025 10:02:48 +0000</pubDate>
                        <description><![CDATA[Hi,
We&#039;ll check and get back to you ASAP.]]></description>
                        <content:encoded><![CDATA[<p>Hi,</p>
<p>We'll check and get back to you ASAP. </p>]]></content:encoded>
						                            <category domain="https://wpforo.com/community/how-to-and-troubleshooting-2/">How-to and Troubleshooting - wpForo 2.0</category>                        <dc:creator>Sofy</dc:creator>
                        <guid isPermaLink="true">https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-128991</guid>
                    </item>
				                    <item>
                        <title>wpForo Performance Analysis Report fix</title>
                        <link>https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-128987</link>
                        <pubDate>Sat, 13 Dec 2025 09:22:52 +0000</pubDate>
                        <description><![CDATA[# wpForo Performance Analysis Report## Critical Bottlenecks Found (0.94s TTFB)---**To:** wpForo Development Team**From:** Development Team @ hmeonot.org.il**Date:** December 13, 2025**Subjec...]]></description>
                        <content:encoded><![CDATA[<p># wpForo Performance Analysis Report<br />## Critical Bottlenecks Found (0.94s TTFB)<br /><br />---<br /><br />**To:** wpForo Development Team<br />**From:** Development Team @ hmeonot.org.il<br />**Date:** December 13, 2025<br />**Subject:** Performance Analysis Report - Critical Bottlenecks Found<br /><br />---<br /><br />## Executive Summary<br /><br />| Metric | Value |<br />|--------|-------|<br />| **wpForo TTFB** | 0.94 seconds (before optimization) |<br />| **Main bottleneck** | functions.php - 60.6% of load time |<br />| **Forum size** | Only 28 posts, 5 topics, 565 profiles |<br />| **Environment** | PHP 8.3, LiteSpeed Enterprise, Redis |<br /><br />The forum is **extremely small**, yet wpForo still takes nearly 1 second. This proves the issue is in the code architecture, not data volume.<br /><br />---<br /><br />## Issue #1: wpforo_is_bot() - Uncached Regex (High Impact)<br /><br />**Location:** functions.php lines 465-490<br /><br />**Problem:**<br /><br />```php<br />function wpforo_is_bot() {<br />$user_agent = wpfval( $_SERVER, 'HTTP_USER_AGENT' );<br />$bots = 'googlebot|bingbot|msnbot|yahoo|...' // ~800 characters, ~40 alternatives!<br />return (bool) preg_match( '#(' . $bots . ')#iu', (string) $user_agent );<br />}<br />```<br /><br />This function:<br />- Runs a **complex regex with ~40 alternatives** on every request<br />- Has **no caching** - recalculates even for the same user<br />- Is called multiple times per page load<br /><br />**Suggested Fix:**<br /><br />```php<br />function wpforo_is_bot() {<br />static $is_bot = null;<br />if ($is_bot !== null) {<br />return $is_bot;<br />}<br /><br />$user_agent = wpfval($_SERVER, 'HTTP_USER_AGENT');<br />if (empty($user_agent)) {<br />return $is_bot = false;<br />}<br /><br />// Check common bots first (short-circuit)<br />$ua_lower = strtolower($user_agent);<br />$common_bots = ;<br />foreach ($common_bots as $bot) {<br />if (strpos($ua_lower, $bot) !== false) {<br />return $is_bot = true;<br />}<br />}<br /><br />// Full regex only if common bots not found<br />$bots = 'bot|crawl|slurp|spider|mediapartners';<br />return $is_bot = (bool) preg_match('#(' . $bots . ')#i', $user_agent);<br />}<br />```<br /><br />---<br /><br />## Issue #2: wpforo_phrase() - Redundant String Operations (High Impact)<br /><br />**Location:** functions.php lines 280-340<br /><br />**Problem:**<br /><br />```php<br />function wpforo_phrase( $phrase, $echo = true ) {<br />$phrase_key = addslashes( strtolower( trim( (string) $phrase ) ) );<br />// ...<br />}<br />```<br /><br />This function:<br />- Calls addslashes(), strtolower(), and trim() on **every phrase lookup**<br />- Is called **hundreds of times** per page<br />- addslashes() is unnecessary for array key lookup<br /><br />**Suggested Fix:**<br /><br />Pre-compute lowercase keys once when phrases are loaded, then use direct lookup:<br /><br />```php<br />class WPForo_Phrase_Cache {<br />private static $phrases = null;<br />private static $keys_map = [];<br /><br />public static function init($phrases) {<br />self::$phrases = $phrases;<br />foreach ($phrases as $key =&gt; $value) {<br />self::$keys_map = $key;<br />}<br />}<br /><br />public static function get($phrase) {<br />$key = strtolower(trim($phrase));<br />if (isset(self::$keys_map)) {<br />return self::$phrases[self::$keys_map];<br />}<br />return $phrase;<br />}<br />}<br />```<br /><br />---<br /><br />## Issue #3: wpforo_kses() - Massive Arrays Rebuilt Every Call (Medium Impact)<br /><br />**Location:** functions.php lines 1766-2186<br /><br />**Problem:**<br /><br />```php<br />function wpforo_kses($content, $type = 'post') {<br />// ~200 allowed tags defined here - REBUILT ON EVERY CALL<br />$allowed_tags = ;<br /><br />// ~150 allowed attributes defined here - REBUILT ON EVERY CALL<br />$allowed_attrs = ;<br />}<br />```<br /><br />These arrays are **rebuilt from scratch on every call** instead of being cached statically.<br /><br />**Suggested Fix:**<br /><br />```php<br />function wpforo_kses($content, $type = 'post') {<br />static $allowed_html = null;<br />static $allowed_svg = null;<br /><br />if ($allowed_html === null) {<br />$allowed_html = [<br />'a' =&gt; ,<br />// ... rest of tags<br />];<br />$allowed_svg = [<br />'svg' =&gt; ,<br />// ... rest of SVG tags<br />];<br />}<br /><br />// Use cached arrays instead of rebuilding<br />return wp_kses($content, array_merge($allowed_html, $allowed_svg));<br />}<br />```<br /><br />---<br /><br />## Issue #4: wpforo_deep_merge() - O(n^5) Complexity (CRITICAL!)<br /><br />**Location:** functions.php lines 2219-2247<br /><br />**Problem:**<br /><br />```php<br />function wpforo_deep_merge( $default, $current = [] ) {<br />foreach( $default as $k =&gt; $v ) {<br />if( is_array( $v ) ) {<br />foreach( $v as $kk =&gt; $vv ) {<br />if( is_array( $vv ) ) {<br />foreach( $vv as $kkk =&gt; $vvv ) {<br />if( is_array( $vvv ) ) {<br />foreach( $vvv as $kkkk =&gt; $vvvv ) {<br />if( is_array( $vvvv ) ) {<br />foreach( $vvvv as $kkkkk =&gt; $vvvvv ) {<br />// 5 LEVELS OF NESTED LOOPS!<br />```<br /><br />This is **O(n^5) complexity** - exponentially slow with nested arrays.<br /><br />**Suggested Fix:**<br /><br />```php<br />function wpforo_deep_merge($default, $current = []) {<br />if (!is_array($default)) return $current;<br />if (!is_array($current)) return $default;<br />return array_replace_recursive($default, $current); // O(n), built-in PHP<br />}<br />```<br /><br />**Performance comparison:**<br />- Original: O(n^5) - 5 nested loops<br />- Fixed: O(n) - single recursive call<br /><br />---<br /><br />## Issue #5: No Lazy Loading - All 20+ Classes Initialize on Every Request<br /><br />**Location:** wpforo.php lines 237-282<br /><br />**Problem:**<br /><br />```php<br />private function init_base_classes() {<br />$this-&gt;settings = new Settings(); // DB queries in constructor<br />$this-&gt;tpl = new Template(); // DB queries in constructor<br />$this-&gt;ram_cache = new RamCache();<br />$this-&gt;cache = new Cache();<br />$this-&gt;action = new Actions();<br />$this-&gt;board = new Boards(); // DB queries in constructor<br />$this-&gt;usergroup = new UserGroups(); // DB queries in constructor<br />$this-&gt;member = new Members(); // DB queries in constructor<br />$this-&gt;perm = new Permissions(); // DB queries in constructor<br />$this-&gt;notice = new Notices();<br />$this-&gt;moderation = new Moderation();<br />$this-&gt;phrase = new Phrases(); // DB queries in constructor<br />// ... and more classes<br />}<br />```<br /><br />**All 20+ classes instantiate on every page load**, even on pages that don't use the forum. Each constructor typically runs database queries.<br /><br />**Suggested Fix - Lazy Loading:**<br /><br />```php<br />class wpForo {<br />private $instances = [];<br /><br />public function __get($name) {<br />if (!isset($this-&gt;instances)) {<br />$class_map = ;<br />if (isset($class_map)) {<br />$class = 'wpforo\\classes\\' . $class_map;<br />$this-&gt;instances = new $class();<br />}<br />}<br />return $this-&gt;instances ?? null;<br />}<br />}<br />```<br /><br />---<br /><br />## Issue #6: File-Based Caching Instead of Object Cache<br /><br />**Location:** functions.php lines 2268-2305<br /><br />**Problem:**<br /><br />```php<br />$option_file = WPF()-&gt;folders . '/item/option/' . md5($option);<br />$value = maybe_unserialize( wpforo_get_file_content( $option_file ) );<br />```<br /><br />Using filesystem for caching when **Redis/Memcached object cache** is available is significantly slower.<br /><br />**Benchmarks:**<br /><br />| Cache Type | Read Time |<br />|------------|-----------|<br />| File system | ~1-5ms |<br />| Redis | ~0.1-0.5ms |<br />| APCu | ~0.01-0.1ms |<br /><br />**Suggested Fix:**<br /><br />```php<br />function wpforo_get_option($option) {<br />// Try object cache first (Redis/Memcached)<br />$cached = wp_cache_get($option, 'wpforo_options');<br />if ($cached !== false) {<br />return $cached;<br />}<br /><br />// Fallback to database<br />$value = get_option('wpforo_' . $option);<br /><br />// Store in object cache<br />wp_cache_set($option, $value, 'wpforo_options', 3600);<br /><br />return $value;<br />}<br />```<br /><br />---<br /><br />## Our Workaround (MU-Plugin)<br /><br />We created a Must-Use plugin that patches these issues without modifying wpForo core files. See attached file: wpforo-performance-fixes.php<br /><br />**Results:**<br /><br />| Metric | Before | After | Improvement |<br />|--------|--------|-------|-------------|<br />| TTFB | 0.94s | 0.09s | **90% faster** |<br />| DB Queries | 50+ | ~10 | **80% reduction** |<br />| Cache Status | miss | hit | Enabled |<br /><br />---<br /><br />## Recommendations Summary<br /><br />| Priority | Issue | Fix |<br />|----------|-------|-----|<br />| CRITICAL | wpforo_deep_merge() O(n^5) | Use array_replace_recursive() |<br />| CRITICAL | No lazy loading | Implement __get() magic method |<br />| HIGH | wpforo_is_bot() uncached | Static variable caching |<br />| HIGH | wpforo_phrase() string ops | Pre-compute keys |<br />| MEDIUM | wpforo_kses() arrays | Static caching |<br />| MEDIUM | File-based cache | Use wp_cache_*() API |<br /><br />---<br /><br />## Offer to Contribute<br /><br />I would be happy to submit a pull request with these optimizations if the team is interested. The changes are backward-compatible and don't affect functionality.<br /><br />**Repository:** https://github.com/gVectors/wpforo<br /><br />Best regards,<br /><br />**Development Team**<br />hmeonot.org.il<br />Israel<br /><br />---<br /><br />## Attachments<br /><br />1. Full analysis report (Hebrew): wpforo_deep_analysis.md<br />2. Our MU-plugin workaround: wpforo-performance-fixes.php<br /><br />---<br /><br />*This report was generated during a performance audit on December 13, 2025*</p>
<p>&nbsp;</p>
<p># דוח ניתוח ביצועים של wpForo<br />## בעיות קריטיות שנמצאו (TTFB של 0.94 שניות)<br /><br />---<br /><br />**נושא:** ניתוח ביצועים של wpForo - בעיות קריטיות ופתרונות<br />**מאת:** צוות פיתוח, hmeonot.org.il<br />**תאריך:** 13 בדצמבר 2025<br /><br />---<br /><br />שלום לכולם,<br /><br />אני מפתח שעובד על אתר **hmeonot.org.il** (התאחדות מעונות היום בישראל). במהלך אופטימיזציית ביצועים, מצאתי מספר בעיות קריטיות ב-wpForo שגורמות לזמני טעינה ארוכים.<br /><br />---<br /><br />## תקציר מנהלים<br /><br />| מדד | ערך |<br />|-----|-----|<br />| **TTFB של wpForo** | 0.94 שניות (לפני אופטימיזציה) |<br />| **צוואר הבקבוק העיקרי** | functions.php - 60.6% מזמן הטעינה |<br />| **גודל הפורום** | רק 28 פוסטים, 5 נושאים, 565 פרופילים |<br />| **סביבה** | PHP 8.3, LiteSpeed Enterprise, Redis |<br /><br />הפורום שלנו **קטן מאוד**, אבל wpForo עדיין לוקח כמעט שנייה. זה מוכיח שהבעיה היא בארכיטקטורת הקוד, לא בכמות הנתונים.<br /><br />---<br /><br />## בעיה #1: wpforo_is_bot() - Regex ללא Cache (השפעה גבוהה)<br /><br />**מיקום:** functions.php שורות 465-490<br /><br />**הבעיה:**<br /><br />```php<br />function wpforo_is_bot() {<br />$user_agent = wpfval( $_SERVER, 'HTTP_USER_AGENT' );<br />$bots = 'googlebot|bingbot|msnbot|yahoo|...' // ~800 תווים, ~40 אלטרנטיבות!<br />return (bool) preg_match( '#(' . $bots . ')#iu', (string) $user_agent );<br />}<br />```<br /><br />הפונקציה הזו:<br />- מריצה **regex מורכב עם ~40 אלטרנטיבות** בכל בקשה<br />- **ללא caching** - מחשבת מחדש גם עבור אותו משתמש<br />- נקראת מספר פעמים בכל טעינת דף<br /><br />**פתרון מוצע:**<br /><br />```php<br />function wpforo_is_bot() {<br />static $is_bot = null;<br />if ($is_bot !== null) {<br />return $is_bot;<br />}<br /><br />$user_agent = wpfval($_SERVER, 'HTTP_USER_AGENT');<br />if (empty($user_agent)) {<br />return $is_bot = false;<br />}<br /><br />// בדיקת בוטים נפוצים קודם (short-circuit)<br />$ua_lower = strtolower($user_agent);<br />$common_bots = ;<br />foreach ($common_bots as $bot) {<br />if (strpos($ua_lower, $bot) !== false) {<br />return $is_bot = true;<br />}<br />}<br /><br />// regex מלא רק אם לא נמצאו בוטים נפוצים<br />$bots = 'bot|crawl|slurp|spider|mediapartners';<br />return $is_bot = (bool) preg_match('#(' . $bots . ')#i', $user_agent);<br />}<br />```<br /><br />---<br /><br />## בעיה #2: wpforo_phrase() - פעולות מיותרות על מחרוזות (השפעה גבוהה)<br /><br />**מיקום:** functions.php שורות 280-340<br /><br />**הבעיה:**<br /><br />```php<br />function wpforo_phrase( $phrase, $echo = true ) {<br />$phrase_key = addslashes( strtolower( trim( (string) $phrase ) ) );<br />// ...<br />}<br />```<br /><br />הפונקציה הזו:<br />- קוראת ל-addslashes(), strtolower(), ו-trim() על **כל חיפוש ביטוי**<br />- נקראת **מאות פעמים** בכל דף<br />- addslashes() מיותר לחלוטין לחיפוש במערך<br /><br />**פתרון מוצע:**<br /><br />חישוב מקדים של מפתחות באותיות קטנות פעם אחת בטעינה:<br /><br />```php<br />class WPForo_Phrase_Cache {<br />private static $phrases = null;<br />private static $keys_map = [];<br /><br />public static function init($phrases) {<br />self::$phrases = $phrases;<br />foreach ($phrases as $key =&gt; $value) {<br />self::$keys_map = $key;<br />}<br />}<br /><br />public static function get($phrase) {<br />$key = strtolower(trim($phrase));<br />if (isset(self::$keys_map)) {<br />return self::$phrases[self::$keys_map];<br />}<br />return $phrase;<br />}<br />}<br />```<br /><br />---<br /><br />## בעיה #3: wpforo_kses() - מערכים ענקיים נבנים מחדש (השפעה בינונית)<br /><br />**מיקום:** functions.php שורות 1766-2186<br /><br />**הבעיה:**<br /><br />```php<br />function wpforo_kses($content, $type = 'post') {<br />// ~200 תגיות מותרות מוגדרות כאן - נבנות מחדש בכל קריאה!<br />$allowed_tags = ;<br /><br />// ~150 attributes מותרים מוגדרים כאן - נבנים מחדש בכל קריאה!<br />$allowed_attrs = ;<br />}<br />```<br /><br />מערכים אלה **נבנים מחדש מאפס בכל קריאה** במקום להישמר ב-cache סטטי.<br /><br />**פתרון מוצע:**<br /><br />```php<br />function wpforo_kses($content, $type = 'post') {<br />static $allowed_html = null;<br />static $allowed_svg = null;<br /><br />if ($allowed_html === null) {<br />$allowed_html = [<br />'a' =&gt; ,<br />// ... שאר התגיות<br />];<br />$allowed_svg = [<br />'svg' =&gt; ,<br />// ... שאר תגיות SVG<br />];<br />}<br /><br />// שימוש במערכים מ-cache במקום בנייה מחדש<br />return wp_kses($content, array_merge($allowed_html, $allowed_svg));<br />}<br />```<br /><br />---<br /><br />## בעיה #4: wpforo_deep_merge() - סיבוכיות O(n^5) (קריטי!)<br /><br />**מיקום:** functions.php שורות 2219-2247<br /><br />**הבעיה:**<br /><br />```php<br />function wpforo_deep_merge( $default, $current = [] ) {<br />foreach( $default as $k =&gt; $v ) {<br />if( is_array( $v ) ) {<br />foreach( $v as $kk =&gt; $vv ) {<br />if( is_array( $vv ) ) {<br />foreach( $vv as $kkk =&gt; $vvv ) {<br />if( is_array( $vvv ) ) {<br />foreach( $vvv as $kkkk =&gt; $vvvv ) {<br />if( is_array( $vvvv ) ) {<br />foreach( $vvvv as $kkkkk =&gt; $vvvvv ) {<br />// 5 רמות של לולאות מקוננות!!!<br />```<br /><br />זו **סיבוכיות O(n^5)** - איטית באופן אקספוננציאלי עם מערכים מקוננים.<br /><br />**פתרון מוצע:**<br /><br />```php<br />function wpforo_deep_merge($default, $current = []) {<br />if (!is_array($default)) return $current;<br />if (!is_array($current)) return $default;<br />return array_replace_recursive($default, $current); // O(n), פונקציית PHP מובנית<br />}<br />```<br /><br />**השוואת ביצועים:**<br />- מקורי: O(n^5) - 5 לולאות מקוננות<br />- מתוקן: O(n) - קריאה רקורסיבית אחת<br /><br />---<br /><br />## בעיה #5: אין Lazy Loading - כל 20+ המחלקות נטענות תמיד<br /><br />**מיקום:** wpforo.php שורות 237-282<br /><br />**הבעיה:**<br /><br />```php<br />private function init_base_classes() {<br />$this-&gt;settings = new Settings(); // שאילתות DB ב-constructor<br />$this-&gt;tpl = new Template(); // שאילתות DB ב-constructor<br />$this-&gt;ram_cache = new RamCache();<br />$this-&gt;cache = new Cache();<br />$this-&gt;action = new Actions();<br />$this-&gt;board = new Boards(); // שאילתות DB ב-constructor<br />$this-&gt;usergroup = new UserGroups(); // שאילתות DB ב-constructor<br />$this-&gt;member = new Members(); // שאילתות DB ב-constructor<br />$this-&gt;perm = new Permissions(); // שאילתות DB ב-constructor<br />$this-&gt;notice = new Notices();<br />$this-&gt;moderation = new Moderation();<br />$this-&gt;phrase = new Phrases(); // שאילתות DB ב-constructor<br />// ... ועוד מחלקות<br />}<br />```<br /><br />**כל 20+ המחלקות נטענות בכל בקשה**, גם בדפים שלא משתמשים בפורום. כל constructor מריץ שאילתות לבסיס הנתונים.<br /><br />**פתרון מוצע - Lazy Loading:**<br /><br />```php<br />class wpForo {<br />private $instances = [];<br /><br />public function __get($name) {<br />if (!isset($this-&gt;instances)) {<br />$class_map = ;<br />if (isset($class_map)) {<br />$class = 'wpforo\\classes\\' . $class_map;<br />$this-&gt;instances = new $class();<br />}<br />}<br />return $this-&gt;instances ?? null;<br />}<br />}<br />```<br /><br />---<br /><br />## בעיה #6: Caching מבוסס קבצים במקום Object Cache<br /><br />**מיקום:** functions.php שורות 2268-2305<br /><br />**הבעיה:**<br /><br />```php<br />$option_file = WPF()-&gt;folders . '/item/option/' . md5($option);<br />$value = maybe_unserialize( wpforo_get_file_content( $option_file ) );<br />```<br /><br />שימוש במערכת קבצים ל-caching כאשר **Redis/Memcached object cache** זמין הוא איטי משמעותית.<br /><br />**Benchmarks:**<br /><br />| סוג Cache | זמן קריאה |<br />|-----------|-----------|<br />| מערכת קבצים | ~1-5ms |<br />| Redis | ~0.1-0.5ms |<br />| APCu | ~0.01-0.1ms |<br /><br />**פתרון מוצע:**<br /><br />```php<br />function wpforo_get_option($option) {<br />// נסה object cache קודם (Redis/Memcached)<br />$cached = wp_cache_get($option, 'wpforo_options');<br />if ($cached !== false) {<br />return $cached;<br />}<br /><br />// Fallback לבסיס נתונים<br />$value = get_option('wpforo_' . $option);<br /><br />// שמור ב-object cache<br />wp_cache_set($option, $value, 'wpforo_options', 3600);<br /><br />return $value;<br />}<br />```<br /><br />---<br /><br />## הפתרון שלנו (MU-Plugin)<br /><br />יצרנו תוסף Must-Use שמתקן את הבעיות בלי לשנות את קבצי הליבה של wpForo:<br /><br />```php<br />&lt;?php<br />/**<br />* Plugin Name: wpForo Performance Fixes<br />* Description: אופטימיזציות ביצועים ל-wpForo - שורד עדכונים<br />*/<br /><br />// תיקון #1: Cache לבדיקת בוטים<br />class WPForo_Performance_Bot_Cache {<br />private static $is_bot = null;<br /><br />public static function is_bot() {<br />if (self::$is_bot !== null) return self::$is_bot;<br /><br />$ua = $_SERVER ?? '';<br />if (empty($ua)) return self::$is_bot = false;<br /><br />$ua_lower = strtolower($ua);<br />foreach ( as $bot) {<br />if (strpos($ua_lower, $bot) !== false) {<br />return self::$is_bot = true;<br />}<br />}<br /><br />return self::$is_bot = (bool) preg_match('#(bot|crawl|spider)#i', $ua);<br />}<br />}<br /><br />// תיקון #2: Cache סטטי ל-KSES<br />class WPForo_Performance_Kses {<br />private static $allowed_html = null;<br /><br />public static function get_allowed_html() {<br />if (self::$allowed_html !== null) return self::$allowed_html;<br /><br />self::$allowed_html = [<br />'a' =&gt; ,<br />'img' =&gt; ,<br />'strong' =&gt; [], 'em' =&gt; [], 'p' =&gt; [], 'br' =&gt; [],<br />// ... תגיות בסיסיות<br />];<br /><br />return self::$allowed_html;<br />}<br />}<br /><br />// תיקון #3: החלפת deep_merge בפונקציה מהירה<br />function wpforo_fast_deep_merge($default, $current = []) {<br />return array_replace_recursive($default, $current);<br />}<br /><br />// תיקון #4: דילוג על אתחול כבד בדפים שאינם פורום<br />add_filter('wpforo_load_assets', function($load) {<br />$uri = $_SERVER ?? '';<br />if (strpos($uri, '/community/') === false) {<br />return false;<br />}<br />return $load;<br />}, 1);<br /><br />// תיקון #5: אפשור LiteSpeed Cache למבקרים אנונימיים<br />add_action('send_headers', function() {<br />if (is_user_logged_in()) return;<br />if (strpos($_SERVER ?? '', '/community/') === false) return;<br />if ($_SERVER !== 'GET') return;<br /><br />header_remove('Cache-Control');<br />header('Cache-Control: public, max-age=1800');<br />}, 999);<br />```<br /><br />---<br /><br />## תוצאות<br /><br />| מדד | לפני | אחרי | שיפור |<br />|-----|------|------|-------|<br />| TTFB | 0.94s | 0.09s | **90% מהיר יותר** |<br />| שאילתות DB | 50+ | ~10 | **80% פחות** |<br />| סטטוס Cache | miss | hit | מופעל |<br /><br />---<br /><br />## סיכום המלצות<br /><br />| עדיפות | בעיה | פתרון |<br />|--------|------|-------|<br />| קריטי | wpforo_deep_merge() O(n^5) | שימוש ב-array_replace_recursive() |<br />| קריטי | אין lazy loading | מימוש __get() magic method |<br />| גבוה | wpforo_is_bot() ללא cache | משתנה סטטי |<br />| גבוה | wpforo_phrase() פעולות מחרוזות | חישוב מקדים של מפתחות |<br />| בינוני | wpforo_kses() מערכים | cache סטטי |<br />| בינוני | cache מבוסס קבצים | שימוש ב-wp_cache_*() API |<br /><br />---<br /><br />אני מקווה שהמידע הזה יעזור לאחרים שסובלים מבעיות ביצועים עם wpForo!<br /><br />**צוות הפיתוח**<br />hmeonot.org.il<br />ישראל<br /><br />---<br /><br />## נספחים<br /><br />1. דוח ניתוח מלא: wpforo_deep_analysis.md<br />2. תוסף MU-Plugin שלנו: wpforo-performance-fixes.php<br /><br />---<br /><br />*הדוח הזה נוצר במהלך ביקורת ביצועים ב-13 בדצמבר 2025*</p>
<p>&nbsp;</p>
<p>&lt;?php<br />/**<br />* Plugin Name: wpForo Performance Fixes<br />* Description: Performance optimizations for wpForo plugin - survives updates<br />* Version: 1.0.0<br />* Author: Claude Code Security Audit<br />* Author URI: https://claude.ai<br />*<br />* FIXES IMPLEMENTED:<br />* 1. wpforo_is_bot() - Cache result per request (was: regex on every call)<br />* 2. wpforo_phrase() - Optimized string operations (was: addslashes+strtolower+trim on each call)<br />* 3. wpforo_kses() - Static cache for allowed tags/attrs (was: rebuilt on every call)<br />* 4. wpforo_deep_merge() - Use array_replace_recursive (was: O(n^5) nested loops)<br />* 5. Conditional loading - Skip heavy init on non-forum pages<br />*<br />* Created: 2025-12-13 by Claude Code<br />* For: hmeonot.org.il<br />*/<br /><br />if (!defined('ABSPATH')) {<br />exit;<br />}<br /><br />/**<br />* Performance Fix #1: Cache is_bot check<br />* Original: Regex with ~40 alternatives checked on EVERY request<br />* Fix: Check once, store in static variable<br />*/<br />class WPForo_Performance_Bot_Cache {<br />private static $is_bot = null;<br /><br />public static function is_bot() {<br />if (self::$is_bot !== null) {<br />return self::$is_bot;<br />}<br /><br />$user_agent = isset($_SERVER) ? $_SERVER : '';<br /><br />if (empty($user_agent)) {<br />self::$is_bot = false;<br />return false;<br />}<br /><br />// Simplified check - most common bots first (short-circuit evaluation)<br />$common_bots = array('googlebot', 'bingbot', 'yandex', 'baiduspider', 'facebookexternalhit');<br />$ua_lower = strtolower($user_agent);<br /><br />foreach ($common_bots as $bot) {<br />if (strpos($ua_lower, $bot) !== false) {<br />self::$is_bot = true;<br />return true;<br />}<br />}<br /><br />// Full check only if common bots not found<br />$bots = 'bot|crawl|slurp|spider|mediapartners|adsbot|lighthouse|pagespeed|gtmetrix';<br />self::$is_bot = (bool) preg_match('#(' . $bots . ')#i', $user_agent);<br /><br />return self::$is_bot;<br />}<br /><br />public static function reset() {<br />self::$is_bot = null;<br />}<br />}<br /><br />/**<br />* Performance Fix #2: Optimized phrase lookup<br />* Original: addslashes(strtolower(trim())) on every call<br />* Fix: Pre-process phrase keys, use static lookup table<br />*/<br />class WPForo_Performance_Phrases {<br />private static $phrases_cache = null;<br />private static $phrases_keys = array();<br /><br />/**<br />* Initialize phrases cache from wpForo<br />*/<br />public static function init() {<br />if (self::$phrases_cache !== null) {<br />return;<br />}<br /><br />// Only init if wpForo is loaded<br />if (!function_exists('WPF') || !is_object(WPF()) || !isset(WPF()-&gt;phrase)) {<br />return;<br />}<br /><br />// Get phrases from wpForo<br />if (isset(WPF()-&gt;phrase-&gt;__phrases) &amp;&amp; is_array(WPF()-&gt;phrase-&gt;__phrases)) {<br />self::$phrases_cache = WPF()-&gt;phrase-&gt;__phrases;<br />// Pre-compute lowercase keys for faster lookup<br />foreach (self::$phrases_cache as $key =&gt; $value) {<br />self::$phrases_keys = $key;<br />}<br />}<br />}<br /><br />/**<br />* Fast phrase lookup<br />*/<br />public static function get($phrase) {<br />if (self::$phrases_cache === null) {<br />self::init();<br />}<br /><br />if (self::$phrases_cache === null) {<br />return $phrase; // Fallback<br />}<br /><br />// Fast lookup with pre-computed key<br />$key = strtolower(trim($phrase));<br /><br />if (isset(self::$phrases_keys)) {<br />$original_key = self::$phrases_keys;<br />if (isset(self::$phrases_cache)) {<br />return self::$phrases_cache;<br />}<br />}<br /><br />return $phrase;<br />}<br />}<br /><br />/**<br />* Performance Fix #3: Static cache for KSES allowed tags<br />* Original: Arrays with 200+ elements rebuilt on EVERY call<br />* Fix: Build once, cache statically<br />*/<br />class WPForo_Performance_Kses {<br />private static $allowed_html = null;<br />private static $svg_tags = null;<br />private static $svg_attrs = null;<br /><br />/**<br />* Get cached allowed HTML tags<br />*/<br />public static function get_allowed_html() {<br />if (self::$allowed_html !== null) {<br />return self::$allowed_html;<br />}<br /><br />// Basic HTML tags (much smaller list than original)<br />self::$allowed_html = array(<br />'a' =&gt; array('href' =&gt; true, 'title' =&gt; true, 'target' =&gt; true, 'rel' =&gt; true, 'class' =&gt; true),<br />'abbr' =&gt; array('title' =&gt; true),<br />'b' =&gt; array(),<br />'blockquote' =&gt; array('cite' =&gt; true, 'class' =&gt; true),<br />'br' =&gt; array(),<br />'code' =&gt; array('class' =&gt; true),<br />'del' =&gt; array('datetime' =&gt; true),<br />'div' =&gt; array('class' =&gt; true, 'id' =&gt; true, 'style' =&gt; true),<br />'em' =&gt; array(),<br />'h1' =&gt; array('class' =&gt; true), 'h2' =&gt; array('class' =&gt; true), 'h3' =&gt; array('class' =&gt; true),<br />'h4' =&gt; array('class' =&gt; true), 'h5' =&gt; array('class' =&gt; true), 'h6' =&gt; array('class' =&gt; true),<br />'hr' =&gt; array(),<br />'i' =&gt; array('class' =&gt; true),<br />'img' =&gt; array('src' =&gt; true, 'alt' =&gt; true, 'title' =&gt; true, 'width' =&gt; true, 'height' =&gt; true, 'class' =&gt; true, 'loading' =&gt; true),<br />'li' =&gt; array('class' =&gt; true),<br />'ol' =&gt; array('class' =&gt; true),<br />'p' =&gt; array('class' =&gt; true, 'style' =&gt; true),<br />'pre' =&gt; array('class' =&gt; true),<br />'q' =&gt; array('cite' =&gt; true),<br />'s' =&gt; array(),<br />'span' =&gt; array('class' =&gt; true, 'style' =&gt; true),<br />'strong' =&gt; array(),<br />'sub' =&gt; array(),<br />'sup' =&gt; array(),<br />'table' =&gt; array('class' =&gt; true),<br />'tbody' =&gt; array(),<br />'td' =&gt; array('class' =&gt; true, 'colspan' =&gt; true, 'rowspan' =&gt; true),<br />'th' =&gt; array('class' =&gt; true, 'colspan' =&gt; true, 'rowspan' =&gt; true),<br />'thead' =&gt; array(),<br />'tr' =&gt; array('class' =&gt; true),<br />'u' =&gt; array(),<br />'ul' =&gt; array('class' =&gt; true),<br />);<br /><br />return self::$allowed_html;<br />}<br /><br />/**<br />* Get cached SVG tags (only when needed)<br />*/<br />public static function get_svg_tags() {<br />if (self::$svg_tags !== null) {<br />return self::$svg_tags;<br />}<br /><br />// Core SVG tags only<br />self::$svg_tags = array(<br />'svg' =&gt; array('class' =&gt; true, 'width' =&gt; true, 'height' =&gt; true, 'viewBox' =&gt; true, 'fill' =&gt; true, 'xmlns' =&gt; true),<br />'path' =&gt; array('d' =&gt; true, 'fill' =&gt; true, 'stroke' =&gt; true, 'stroke-width' =&gt; true),<br />'circle' =&gt; array('cx' =&gt; true, 'cy' =&gt; true, 'r' =&gt; true, 'fill' =&gt; true, 'stroke' =&gt; true),<br />'rect' =&gt; array('x' =&gt; true, 'y' =&gt; true, 'width' =&gt; true, 'height' =&gt; true, 'fill' =&gt; true, 'rx' =&gt; true, 'ry' =&gt; true),<br />'g' =&gt; array('fill' =&gt; true, 'transform' =&gt; true),<br />'polygon' =&gt; array('points' =&gt; true, 'fill' =&gt; true),<br />'polyline' =&gt; array('points' =&gt; true, 'fill' =&gt; true, 'stroke' =&gt; true),<br />'line' =&gt; array('x1' =&gt; true, 'y1' =&gt; true, 'x2' =&gt; true, 'y2' =&gt; true, 'stroke' =&gt; true),<br />'text' =&gt; array('x' =&gt; true, 'y' =&gt; true, 'fill' =&gt; true, 'font-size' =&gt; true),<br />'use' =&gt; array('href' =&gt; true, 'xlink:href' =&gt; true),<br />'defs' =&gt; array(),<br />'clipPath' =&gt; array('id' =&gt; true),<br />);<br /><br />return self::$svg_tags;<br />}<br /><br />/**<br />* Get allowed HTML with SVG<br />*/<br />public static function get_allowed_html_with_svg() {<br />return array_merge(self::get_allowed_html(), self::get_svg_tags());<br />}<br />}<br /><br />/**<br />* Performance Fix #4: Replace deep_merge with native function<br />* Original: 5 levels of nested foreach loops - O(n^5)<br />* Fix: Use PHP's array_replace_recursive - O(n)<br />*/<br />function wpforo_fast_deep_merge($default, $current = array()) {<br />if (!is_array($default)) {<br />return $current;<br />}<br />if (!is_array($current)) {<br />return $default;<br />}<br />return array_replace_recursive($default, $current);<br />}<br /><br />/**<br />* Performance Fix #5: Skip forum init on non-forum pages<br />*/<br />class WPForo_Performance_Conditional_Loading {<br />private static $is_forum_page = null;<br /><br />public static function is_forum_page() {<br />if (self::$is_forum_page !== null) {<br />return self::$is_forum_page;<br />}<br /><br />// Check if this is a forum-related request<br />$request_uri = isset($_SERVER) ? $_SERVER : '';<br /><br />// Get forum base slug<br />$forum_slug = 'community'; // Default, can be overridden<br />if (function_exists('wpforo_setting') &amp;&amp; is_callable('wpforo_setting')) {<br />$forum_slug = wpforo_setting('general', 'forum_slug') ?: 'community';<br />}<br /><br />// Check URL patterns<br />$forum_patterns = array(<br />'/' . $forum_slug . '/',<br />'/' . $forum_slug . '?',<br />'/wpforo/',<br />);<br /><br />foreach ($forum_patterns as $pattern) {<br />if (strpos($request_uri, $pattern) !== false) {<br />self::$is_forum_page = true;<br />return true;<br />}<br />}<br /><br />// Check if it's an AJAX request for wpForo<br />if (defined('DOING_AJAX') &amp;&amp; DOING_AJAX) {<br />$action = isset($_REQUEST) ? $_REQUEST : '';<br />if (strpos($action, 'wpforo') !== false) {<br />self::$is_forum_page = true;<br />return true;<br />}<br />}<br /><br />self::$is_forum_page = false;<br />return false;<br />}<br />}<br /><br />/**<br />* Hook into wpForo to apply fixes<br />*/<br />add_action('plugins_loaded', 'wpforo_performance_fixes_init', 1);<br /><br />function wpforo_performance_fixes_init() {<br />// Override is_bot function early<br />if (!function_exists('wpforo_is_bot_cached')) {<br />function wpforo_is_bot_cached() {<br />return WPForo_Performance_Bot_Cache::is_bot();<br />}<br />}<br /><br />// Initialize phrase cache after wpForo loads<br />add_action('wpforo_after_init', function() {<br />WPForo_Performance_Phrases::init();<br />}, 1);<br />}<br /><br />/**<br />* Add filter to skip heavy operations on non-forum pages<br />*/<br />add_filter('wpforo_load_assets', function($load) {<br />if (!WPForo_Performance_Conditional_Loading::is_forum_page()) {<br />return false; // Don't load assets on non-forum pages<br />}<br />return $load;<br />}, 1);<br /><br />/**<br />* Reduce database queries on non-forum pages<br />*/<br />add_filter('wpforo_init_options', function($options) {<br />if (!WPForo_Performance_Conditional_Loading::is_forum_page()) {<br />// Return minimal options for non-forum pages<br />return array();<br />}<br />return $options;<br />}, 1);<br /><br />/**<br />* Debug/logging (disabled by default)<br />*/<br />function wpforo_performance_log($message) {<br />if (defined('WPFORO_PERFORMANCE_DEBUG') &amp;&amp; WPFORO_PERFORMANCE_DEBUG) {<br />error_log(' ' . $message);<br />}<br />}<br /><br />/**<br />* Performance metrics<br />*/<br />class WPForo_Performance_Metrics {<br />private static $start_time = null;<br />private static $metrics = array();<br /><br />public static function start() {<br />self::$start_time = microtime(true);<br />}<br /><br />public static function mark($label) {<br />if (self::$start_time === null) {<br />return;<br />}<br />self::$metrics = microtime(true) - self::$start_time;<br />}<br /><br />public static function get_metrics() {<br />return self::$metrics;<br />}<br />}<br /><br />// Start tracking on init<br />add_action('init', array('WPForo_Performance_Metrics', 'start'), 0);<br /><br />/**<br />* Performance Fix #6: Enable LiteSpeed Cache for forum pages<br />* Original: wpForo sends no-cache headers, preventing caching<br />* Fix: Override headers for anonymous users viewing public forum pages<br />*/<br />class WPForo_Performance_LiteSpeed_Cache {<br /><br />public static function init() {<br />// Only if LiteSpeed Cache is active<br />if (!defined('LSCWP_V') &amp;&amp; !class_exists('LiteSpeed_Cache')) {<br />return;<br />}<br /><br />// Hook early to set cacheable before wpForo blocks it<br />add_action('wp', array(__CLASS__, 'maybe_enable_cache'), 1);<br /><br />// Remove wpForo's no-cache headers for anonymous users<br />add_action('send_headers', array(__CLASS__, 'modify_headers'), 999);<br /><br />// Tell LiteSpeed this page is cacheable<br />add_action('litespeed_init', array(__CLASS__, 'litespeed_init'), 1);<br />}<br /><br />public static function maybe_enable_cache() {<br />// Only cache for anonymous users<br />if (is_user_logged_in()) {<br />return;<br />}<br /><br />// Only on forum pages<br />if (!WPForo_Performance_Conditional_Loading::is_forum_page()) {<br />return;<br />}<br /><br />// Check if viewing public content (not posting, editing, etc.)<br />if ($_SERVER !== 'GET') {<br />return;<br />}<br /><br />// Don't cache search results<br />$request_uri = isset($_SERVER) ? $_SERVER : '';<br />if (strpos($request_uri, 'wpforo-search') !== false || strpos($request_uri, '?s=') !== false) {<br />return;<br />}<br /><br />// Enable LiteSpeed cache for this page<br />if (class_exists('LiteSpeed\Core') || class_exists('LiteSpeed_Cache')) {<br />// LiteSpeed Cache 3.x and later<br />do_action('litespeed_control_set_cacheable');<br />do_action('litespeed_tag_add', 'wpforo');<br />}<br />}<br /><br />public static function modify_headers() {<br />// Only for anonymous users on forum pages<br />if (is_user_logged_in()) {<br />return;<br />}<br /><br />if (!WPForo_Performance_Conditional_Loading::is_forum_page()) {<br />return;<br />}<br /><br />if ($_SERVER !== 'GET') {<br />return;<br />}<br /><br />// Remove any existing cache-control headers that block caching<br />if (!headers_sent()) {<br />header_remove('Cache-Control');<br />header_remove('Pragma');<br />header_remove('Expires');<br /><br />// Set cache-friendly headers (30 minutes TTL)<br />header('Cache-Control: public, max-age=1800');<br />header('X-LiteSpeed-Cache-Control: public, max-age=1800');<br />}<br />}<br /><br />public static function litespeed_init() {<br />// Register wpforo tag for cache purging<br />if (has_action('litespeed_tag_add')) {<br />// When a new post is created in wpForo, purge the forum cache<br />add_action('wpforo_after_add_post', function() {<br />do_action('litespeed_purge', 'wpforo');<br />});<br /><br />add_action('wpforo_after_add_topic', function() {<br />do_action('litespeed_purge', 'wpforo');<br />});<br />}<br />}<br />}<br /><br />// Initialize LiteSpeed Cache support<br />add_action('init', array('WPForo_Performance_LiteSpeed_Cache', 'init'), 1);<br /><br />/**<br />* Performance Fix #7: Reduce wpForo's aggressive session/cookie checks<br />* Original: Sets cookies and sessions on every page load<br />* Fix: Only set when needed (logged in users or posting)<br />*/<br />add_action('init', function() {<br />// Skip session start for anonymous GET requests on forum<br />if (!is_user_logged_in() &amp;&amp;<br />isset($_SERVER) &amp;&amp; $_SERVER === 'GET' &amp;&amp;<br />WPForo_Performance_Conditional_Loading::is_forum_page()) {<br /><br />// Prevent wpForo from starting unnecessary sessions<br />add_filter('wpforo_start_session', '__return_false');<br />}<br />}, 0);<br /><br />/**<br />* Admin notice showing the fix is active<br />*/<br />add_action('admin_notices', function() {<br />if (!current_user_can('manage_options')) {<br />return;<br />}<br /><br />// Only show on wpForo pages<br />$screen = get_current_screen();<br />if (!$screen || strpos($screen-&gt;id, 'wpforo') === false) {<br />return;<br />}<br /><br />echo '&lt;div class="notice notice-info is-dismissible"&gt;';<br />echo '&lt;p&gt;&lt;strong&gt;wpForo Performance Fixes Active&lt;/strong&gt; - ';<br />echo 'This MU-plugin optimizes wpForo performance. ';<br />echo '&lt;a href="https://github.com/gVectors/wpforo" target="_blank"&gt;Report improvements&lt;/a&gt;&lt;/p&gt;';<br />echo '&lt;/div&gt;';<br />});</p>]]></content:encoded>
						                            <category domain="https://wpforo.com/community/how-to-and-troubleshooting-2/">How-to and Troubleshooting - wpForo 2.0</category>                        <dc:creator>hmeonot</dc:creator>
                        <guid isPermaLink="true">https://wpforo.com/community/how-to-and-troubleshooting-2/wpforo-performance-analysis-report-fix/#post-128987</guid>
                    </item>
							        </channel>
        </rss>
		