Common Mistakes and How to Avoid Them

Developing for a feature-rich platform like UNA CMS can be highly rewarding, but like any complex system, it has common pitfalls that developers, especially those new to the platform, might encounter. This document highlights frequent mistakes made during UNA development, explaining why they are problematic and providing the correct, verified approaches.

Avoiding these mistakes is crucial for building secure, stable, maintainable, and performant modules and customizations. Familiarizing yourself with these points will save significant time and effort in debugging and refactoring later. This document complements the UNA Code Convention and Code Quality Guidelines.

Security Vulnerabilities (Critical Mistakes)

These are the most critical mistakes as they can compromise the entire site and user data.

Not Using Prepared Statements (SQL Injection Risk)

  • Mistake: Directly embedding variables (especially user input) into SQL query strings. Using outdated functions like process_db_input or mysql_real_escape_string.

  • Why it's Wrong: This is the primary cause of SQL Injection vulnerabilities. Attackers can manipulate the input to execute arbitrary SQL commands, potentially stealing data, modifying content, or destroying the database.

  • Correct Approach: Always use Prepared Statements via the BxDolDb::prepare() method after sanitizing input with bx_process_input().

    // --- INCORRECT ---
    $iUserId = $_GET['user_id']; // Unsafe input
    $sSql = "SELECT * FROM `sys_profiles` WHERE `id` = " . $iUserId; // Vulnerable!
    $aProfile = BxDolDb::getInstance()->getRow($sSql);
    
    // --- CORRECT ---
    // 1. Sanitize input
    $iUserId = bx_process_input(bx_get('user_id'), BX_DATA_INT); // Use bx_get and sanitize as integer
    
    // 2. Prepare the query in the appropriate Db class
    // Example within a hypothetical BxMyModuleDb class:
    public function getProfileData($iUserId) {
        $sSql = $this->prepare("SELECT `id`, `name`, `email` FROM `sys_profiles` WHERE `id` = ?", $iUserId);
        return $this->getRow($sSql);
    }
    // Call it: $aProfile = BxDolModule::getInstance('my_module')->_oDb->getProfileData($iUserId);
  • Verification: Review all database queries. Ensure prepare() is used whenever variable data is part of the query condition or values.

Not Escaping Output (Cross-Site Scripting - XSS Risk)

  • Mistake: Directly echoing or printing variables (especially user-generated content) into HTML templates or JavaScript without proper escaping.

  • Why it's Wrong: Allows attackers to inject malicious scripts (JavaScript) into pages viewed by other users. This can lead to session hijacking, phishing, defacement, or redirecting users to malicious sites.

  • Correct Approach: Use UNA's output escaping functions based on the context:

    • bx_process_output($sData, BX_DATA_TEXT): For displaying plain text content within HTML tags.
    • bx_process_output($sData, BX_DATA_HTML): For displaying content that should contain safe HTML (use cautiously, ideally data should be purified first).
    • bx_html_attribute($sValue): For safely outputting data inside HTML attributes (e.g., value, title, alt).
    • bx_js_string($sValue): For safely embedding a string variable within JavaScript code.
      
      // --- INCORRECT ---
      <div title="<?php echo $_GET['user_comment']; ?>">...</div> <!-- XSS Risk -->
      <script> let userName = '<?php echo $sUserName; ?>'; </script> <!-- XSS Risk -->

    // --- CORRECT ---

    ...

    ```
  • Verification: Inspect all template files (.html) and PHP code generating HTML or JS. Ensure any variable output uses an appropriate bx_process_output, bx_html_attribute, or bx_js_string function.

Missing or Incorrect Permission Checks (Authorization Bypass)

  • Mistake: Performing actions (viewing, editing, deleting data) or displaying sensitive information/controls without verifying if the current user has the necessary permissions.

  • Why it's Wrong: Allows unauthorized users to access or modify data they shouldn't, violating privacy and data integrity.

  • Correct Approach: Use the Access Control List (ACL) system (BxDolACL::getInstance()->isAllowed(...)) before performing actions or rendering elements that require specific permissions. Define appropriate actions for your module in its configuration.

    $oACL = BxDolACL::getInstance();
    
    // Check if user can view a specific entry (assuming 'my_module_view' action is defined)
    $aEntry = $this->_oDb->getEntryById($iEntryId);
    $iAuthorId = $aEntry['author_id']; // Example: Check might depend on authorship
    
    if (!$oACL->isAllowed('my_module_view', $iEntryId, $iAuthorId)) {
        // Display an error message or redirect
        return MsgBox(_t('_Access denied'));
    }
    
    // Check before showing an 'Edit' button
    if ($oACL->isAllowed('my_module_edit', $iEntryId, $iAuthorId)) {
        // Display edit button
    }
  • Verification: Review code paths that handle data access or modification. Ensure isAllowed checks are present and use the correct action name, object ID (if applicable), and author ID (if applicable). Test functionality with different user roles.

Missing CSRF Token Validation (Cross-Site Request Forgery Risk)

  • Mistake: Processing form submissions or actions triggered by POST requests (or potentially GET requests that change state) without verifying a CSRF token.

  • Why it's Wrong: Allows attackers to trick logged-in users into unknowingly submitting requests to your site, performing actions on their behalf (e.g., deleting content, changing settings).

  • Correct Approach:

    1. Include the CSRF token key and value in forms using bx_get_csrf_token_key() and bx_get_csrf_token(). BxDolForm usually handles this automatically.
    2. Verify the token on the server-side using bx_verify_csrf_token() before processing the request.
      
      // --- Form (.html or BxDolForm array structure) ---
      <input type="hidden" name="<?= bx_get_csrf_token_key(); ?>" value="<?= bx_get_csrf_token(); ?>" />

    // --- PHP Handler --- if (!bx_verify_csrf_token(bx_get('csrf_token'))) { // bx_get works for POST/GET // Token is invalid or missing - reject the request echo 'Invalid request token.'; exit; } // Proceed with action...

  • Verification: Check all forms and action handlers that modify data or perform sensitive operations. Ensure tokens are included and verified.

Database Interaction Mistakes

N+1 Query Problems

  • Mistake: Fetching a list of items and then looping through that list to perform a separate database query for each item to get related data.

  • Why it's Wrong: Leads to excessive database queries (1 initial query + N queries inside the loop), causing significant performance degradation, especially with larger datasets.

  • Correct Approach: Use SQL JOINs to fetch related data in a single query, or fetch all related data in a second query using an IN clause.

    // --- INCORRECT (N+1 Problem) ---
    $aPosts = $this->_oDb->getLatestPosts(10); // 1 query
    foreach ($aPosts as &$aPost) {
        // N queries inside the loop!
        $aAuthor = BxDolProfileQuery::getInstance()->getInfoById($aPost['author_id']);
        $aPost['author_info'] = $aAuthor;
    }
    
    // --- CORRECT (Using JOIN in Db class) ---
    // Modify the getLatestPosts method in your Db class:
    public function getLatestPostsWithAuthors($iLimit = 10) {
        $sSql = $this->prepare("
            SELECT p.*, pr.`name` AS `author_name`, pr.`email` AS `author_email`
            FROM `{$this->_sPrefix}posts` AS `p`
            INNER JOIN `sys_profiles` AS `pr` ON `p`.`author_id` = `pr`.`id`
            WHERE p.`status` = 'active'
            ORDER BY p.`created` DESC
            LIMIT ?
        ", $iLimit);
        return $this->getAll($sSql);
    }
    // Now call: $aPosts = $this->_oDb->getLatestPostsWithAuthors(10); // 1 query
    
    // --- CORRECT (Using IN clause - alternative) ---
    $aPosts = $this->_oDb->getLatestPosts(10); // 1 query
    $aAuthorIds = array_column($aPosts, 'author_id');
    if ($aAuthorIds) {
        $aAuthors = BxDolProfileQuery::getInstance()->getInfosByIds($aAuthorIds); // 1 query
        // Map authors back to posts... (requires some PHP logic)
    }
  • Verification: Examine loops that contain database queries. Check if the data could be fetched more efficiently using JOINs or IN clauses. Use query logging tools to monitor the number of queries per page load.

Fetching Unnecessary Data (SELECT *)

  • Mistake: Using SELECT * to fetch all columns from a table when only a few are needed.

  • Why it's Wrong: Wastes database resources (CPU, memory, network bandwidth) transferring unused data. Can also lead to unexpected issues if table schemas change.

  • Correct Approach: Explicitly list the required columns in your SELECT statement.

    // --- LESS EFFICIENT ---
    $sSql = $this->prepare("SELECT * FROM `{$this->_sPrefix}entries` WHERE `id` = ?", $iEntryId);
    
    // --- MORE EFFICIENT ---
    $sSql = $this->prepare("SELECT `id`, `title`, `author_id`, `created` FROM `{$this->_sPrefix}entries` WHERE `id` = ?", $iEntryId);
  • Verification: Review SELECT statements. Ensure only necessary columns are being fetched.

Templating and Frontend Mistakes

Hardcoding Language Strings

  • Mistake: Placing user-visible text directly into PHP code or .html template files instead of using language keys.

  • Why it's Wrong: Makes the module or template impossible to translate into other languages using UNA's Polyglot system. Requires code changes for simple text modifications.

  • Correct Approach: Define language keys in the module's language files (/modules/[vendor]/[module]/data/langs/system/en.xml) and use the _t() function (or bx_text() in templates) to display them.

    // --- INCORRECT ---
    echo "<div>Add New Post</div>";
    $sError = "Failed to save data.";
    
    // --- CORRECT ---
    // In en.xml:
    // <string name="_bx_posts_add_new"><![CDATA[Add New Post]]></string>
    // <string name="_bx_posts_err_save_failed"><![CDATA[Failed to save data.]]></string>
    
    // In PHP:
    echo "<div>" . _t('_bx_posts_add_new') . "</div>";
    $sError = _t('_bx_posts_err_save_failed');
    
    // In .html template:
    <div>__bx_posts_add_new__</div> <!-- Or using bx_text -->
    <div>{{ bx_text:_bx_posts_add_new }}</div>
  • Verification: Search code and templates for hardcoded user-facing strings. Ensure they are replaced with _t() calls using appropriate language keys. Check Polyglot in Studio to manage keys.

Incorrect Template Overrides (Path or Cache Issues)

  • Mistake: Placing overridden template files (.html, .css) in the wrong directory structure within the custom template, or forgetting to clear the template cache after adding/modifying overrides.
  • Why it's Wrong: The system won't pick up the overridden file, and the default template will continue to be used.
  • Correct Approach:
    1. Mirror the exact path of the original file relative to the /modules/ directory within your custom template's /templates/[template_name]/modules/ directory. Example: To override /modules/boonex/posts/template/entry.html, place your modified file at /templates/my_template/modules/boonex/posts/template/entry.html.
    2. After adding or modifying template files, clear the site's Template Cache via Studio > Developer > Cache > Template Cache > Clear All.
  • Verification: Double-check the path of the overridden file. Clear the template cache and reload the page. Use browser developer tools to inspect the loaded source and confirm your changes are present.

CSS Conflicts (Specificity or Lack of Prefixing)

  • Mistake: Writing CSS selectors that are too broad or not specific enough, or failing to prefix custom module/template CSS classes, leading to unintended style overrides of core elements or other modules.
  • Why it's Wrong: Breaks the site's layout or the appearance of other components. Makes CSS difficult to debug and maintain.
  • Correct Approach:
    • Use specific selectors. Target elements within your module's unique container.
    • Prefix custom CSS classes with a unique identifier related to your module or template (e.g., .bx-my-module-widget, .my-template-header).
    • Leverage CSS methodologies like BEM (.block__element--modifier) for complex components.
    • Understand CSS Specificity rules. Use browser developer tools to inspect computed styles and identify conflicting rules.
  • Verification: Use browser developer tools to inspect elements and their applied styles. Check for unexpected style overrides. Ensure your CSS rules only affect the intended elements.

JavaScript Global Namespace Pollution

  • Mistake: Defining JavaScript variables and functions directly in the global scope.

  • Why it's Wrong: Can lead to naming collisions with core UNA JavaScript, other modules, or third-party libraries, causing unpredictable behavior and errors.

  • Correct Approach: Encapsulate JavaScript code within objects, classes, or Immediately Invoked Function Expressions (IIFEs). Follow UNA's JS class patterns (e.g., extending BxDolGrid, BxDolForm).

    // --- INCORRECT ---
    // function initMyWidget() { /* ... */ }
    // var widgetData = {};
    // initMyWidget();
    
    // --- CORRECT (Object Pattern) ---
    var BxMyModuleWidget = {
        sSomeVar: '',
        init: function(options) {
            this.sSomeVar = options.someValue;
            // ... initialization logic ...
            console.log('My Widget Initialized');
        },
        doSomething: function() {
            // ... widget logic ...
        }
    };
    // Initialization typically called from PHP template:
    // BxMyModuleWidget.init({ someValue: 'abc' });
    
    // --- CORRECT (Class Pattern - ES6) ---
    class BxMyModuleComponent {
        constructor(options) {
            this.options = options;
            this._init();
        }
    
        _init() {
            console.log('Component Initialized with options:', this.options);
            // Add event listeners, etc.
        }
    
        destroy() {
            // Cleanup
        }
    }
    // new BxMyModuleComponent({ /* options */ });
  • Verification: Check JavaScript files. Ensure variables and functions are scoped within objects, classes, or IIFEs, not directly in the global scope. Use browser console to check for global variable definitions.

Module Development & Structure Mistakes

Modifying Core UNA Files Directly

  • Mistake: Editing files within the /inc/, /studio/, /plugins/, or core module directories (/modules/boonex/... unless contributing back).
  • Why it's Wrong: Changes will be overwritten during UNA updates, making upgrades impossible or extremely difficult. Breaks the modularity principle.
  • Correct Approach:
    • Functionality: Create a new custom module to add features.
    • Behavior: Use UNA's hooks and alerts system (BxDolAlerts) to modify or extend existing behavior without touching core code.
    • Appearance: Override template files (.html, .css, .js) within your site's active template directory (see "Incorrect Template Overrides").
    • Configuration: Change settings via Studio.
  • Verification: Use version control (Git) and compare your codebase against a clean UNA installation to identify core file modifications.

Not Calling Parent Constructor (parent::__construct)

  • Mistake: Extending a UNA base class (like BxDolModule, BxBaseModGeneralDb, BxDolForm) and defining a __construct method without calling parent::__construct(...).

  • Why it's Wrong: The parent class's essential initialization logic (setting up configuration, database connection, default properties) does not run, leading to errors or unexpected behavior in your module.

  • Correct Approach: Always call parent::__construct(...) as the first line in your constructor, passing any required arguments.

    // Example in a Module class
    class BxMyModuleModule extends BxBaseModGeneralModule
    {
        function __construct($aModule)
        {
            parent::__construct($aModule); // <-- Essential call!
            // Your custom initialization logic here...
            $this->_oConfig->init($this->_oDb); // Example: Often needed after parent call
        }
    }
    
    // Example in a Db class
    class BxMyModuleDb extends BxBaseModGeneralDb
    {
        public function __construct(&$oConfig)
        {
            parent::__construct($oConfig); // <-- Essential call!
        }
    }
  • Verification: Review the __construct methods of all classes that extend UNA base classes. Ensure parent::__construct is called correctly.

Forgetting Essential Module Installation Steps

  • Mistake: Creating a module but failing to properly register necessary components during installation (e.g., settings, permissions, alerts, pages, menus, database schema changes) in the Bx[Vendor][Module]Installer class.
  • Why it's Wrong: The module may appear installed but crucial parts will be missing or non-functional. Settings won't appear, permissions won't work, pages won't be accessible.
  • Correct Approach: Carefully implement the install and uninstall methods in your module's Installer class, using the provided helper methods ($this->add.../$this->remove...) to register/unregister all components defined in config.php or required by your module.
  • Verification: Thoroughly test the module after installation and uninstallation. Check Studio for settings, permissions, pages, menus, etc. Verify database tables and data are created/removed correctly.

Not Using Configuration (CNF) Constants

  • Mistake: Hardcoding values like database table names, storage object names, form names, grid names, etc., directly in the module's code instead of using the CNF array defined in the module's Config.php class.

  • Why it's Wrong: Makes the code harder to maintain and adapt. If a table name or object name needs changing, you have to hunt through the entire codebase instead of changing it in one central configuration location.

  • Correct Approach: Define constants in the $CNF array within your module's Config.php class and access them via $this->_oConfig->CNF['CONSTANT_NAME'].

    // In Config.php
    class BxMyModuleConfig extends BxBaseModGeneralConfig {
        function __construct($aModule) {
            parent::__construct($aModule);
            $this->CNF = array(
                // ... other CNF settings
                'TABLE_ENTRIES' => $this->_sPrefix . 'entries', // Use prefix
                'OBJECT_STORAGE' => 'bx_my_module_files',
                'OBJECT_FORM_ENTRY' => 'bx_my_module_entry',
                'OBJECT_GRID' => 'bx_my_module_administration',
                // ...
            );
        }
    }
    
    // In Db.php or Module.php etc.
    $sTableName = $this->_oConfig->CNF['TABLE_ENTRIES'];
    $sSql = "SELECT * FROM `{$sTableName}` WHERE ...";
    
    $oForm = BxDolForm::getObjectInstance($this->_oConfig->CNF['OBJECT_FORM_ENTRY'], ...);
  • Verification: Review module code for hardcoded table names, object names, etc. Replace them with references to $this->_oConfig->CNF.

Development Environment & Workflow Mistakes

Not Clearing Caches During Development

  • Mistake: Making changes to code (especially templates, CSS, JS, language strings, or database structures used by certain caches like forms/menus) and not clearing the relevant caches.
  • Why it's Wrong: You won't see your changes reflected on the site, leading to confusion and wasted time debugging non-existent issues.
  • Correct Approach: Frequently clear caches via Studio > Developer > Cache during development:
    • Template Cache: After changing .html files or template engine logic.
    • CSS/JS Cache (Mixes): After changing .css or .js files included in mixes. Consider disabling CSS/JS caching in Studio > Settings > Advanced Settings during active development.
    • DB Cache: After changing data structures cached by the system (Forms, Menus, Pages, Polyglot keys, ACL actions, Settings, etc.).
    • Purge Cache: Clear specific object cache entries if needed (less common).
  • Verification: If changes aren't appearing, the first step is usually to clear the relevant cache(s). Use browser incognito mode or force-refresh (Ctrl+F5 / Cmd+Shift+R) to bypass browser cache as well.

Conclusion

Becoming proficient in UNA development involves understanding its architecture and avoiding these common errors. By prioritizing security, adhering to conventions, writing efficient code, and utilizing UNA's features correctly, you can build high-quality, robust modules and customizations. Regularly refer back to this list, the Code Convention, and the Code Quality guidelines to reinforce best practices. Happy coding!

On This Page