|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Maho is an open-source ecommerce platform forked from OpenMage, designed for medium-to-small on-premise projects. It's based on the Magento 1 architecture but modernized with PHP 8.3+ support and contemporary development tools. |
| 8 | + |
| 9 | +## Essential Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +vendor/bin/php-cs-fixer fix # Fix code style (lint) |
| 13 | +vendor/bin/phpstan analyze # Run static analysis (level 6) |
| 14 | +vendor/bin/rector -c .rector.php |
| 15 | +./maho cache:flush # Flush all caches |
| 16 | +composer test # Run all tests (Install → Backend → Frontend) |
| 17 | +composer test -- --testsuite=Frontend # Run frontend tests only |
| 18 | +composer test -- --testsuite=Backend # Run backend tests only |
| 19 | +composer test -- --testsuite=Install # Run install tests only |
| 20 | +./maho index:reindex:all # Reindex all indexes |
| 21 | +./maho db:query "QUERY" # Execute a one-shot SQL query |
| 22 | +``` |
| 23 | + |
| 24 | +## Architecture Overview |
| 25 | + |
| 26 | +### Bootstrapping |
| 27 | +```php |
| 28 | +require 'vendor/autoload.php'; |
| 29 | +Mage::app(); |
| 30 | +``` |
| 31 | + |
| 32 | +### MVC Pattern |
| 33 | +- **Models** (`Model/`): Business logic and data access |
| 34 | +- **Views** (`Block/` and templates): Presentation layer |
| 35 | +- **Controllers** (`controllers/`): Request handling |
| 36 | + |
| 37 | +### Module Structure |
| 38 | +``` |
| 39 | +app/code/core/Mage/[ModuleName]/ |
| 40 | +├── Block/ # View blocks |
| 41 | +├── Helper/ # Helper classes |
| 42 | +├── Model/ # Business logic |
| 43 | +├── controllers/ # Controllers |
| 44 | +├── etc/ # Configuration (config.xml, system.xml) |
| 45 | +├── sql/ # Database migrations |
| 46 | +└── data/ # Data install scripts |
| 47 | +``` |
| 48 | + |
| 49 | +### Key Configuration Files |
| 50 | +- `app/etc/local.xml`: Main configuration (DB, cache, etc.) |
| 51 | +- `app/etc/config.xml`: Base configuration |
| 52 | +- `app/etc/modules/*.xml`: Module declarations |
| 53 | + |
| 54 | +### Theme Structure |
| 55 | +``` |
| 56 | +app/design/ |
| 57 | +├── adminhtml/ # Admin panel themes |
| 58 | +├── frontend/ # Frontend themes |
| 59 | +└── install/ # Installer theme |
| 60 | +``` |
| 61 | + |
| 62 | +### Database Access (Doctrine DBAL 4.4) |
| 63 | +Replaces all Zend_Db components. Adapter: `Maho\Db\Adapter\AdapterInterface`. Query builder: `Maho\Db\Select` (wraps Doctrine QueryBuilder). |
| 64 | + |
| 65 | +```php |
| 66 | +$adapter = Mage::getSingleton('core/resource')->getConnection('core_read'); |
| 67 | +$select = $adapter->select() |
| 68 | + ->from(['p' => 'catalog_product'], ['entity_id', 'sku']) |
| 69 | + ->where('status = ?', 1) |
| 70 | + ->order('created_at DESC'); |
| 71 | + |
| 72 | +// Raw SQL expressions |
| 73 | +$select->columns(['total' => new Maho\Db\Expr('COUNT(*)')]); |
| 74 | + |
| 75 | +// Direct queries |
| 76 | +$result = $adapter->fetchAll($select); |
| 77 | +$adapter->insert('table_name', ['column' => 'value']); |
| 78 | +$adapter->update('table_name', ['column' => 'new_value'], 'id = 1'); |
| 79 | +$adapter->delete('table_name', 'id = 1'); |
| 80 | +``` |
| 81 | + |
| 82 | +### Other Key Systems |
| 83 | +- **Events**: `Mage::dispatchEvent('event_name', ['data' => $data])` - Observers in `config.xml` |
| 84 | +- **Layout**: XML-based configuration with block hierarchy and template assignment |
| 85 | +- **Sessions**: `Mage::getSingleton('customer/session')`, `'admin/session'`, `'checkout/session'` |
| 86 | +- **Translations**: CSV files in `app/locale/[locale]/` - Use `$this->__('Text')` in code |
| 87 | +- **Collections**: `Mage::getResourceModel('catalog/product_collection')->addAttributeToSelect('*')->addFieldToFilter('status', 1)` |
| 88 | +- **Errors**: `Mage::throwException()` for user-facing errors, `Mage::log()` for logging |
| 89 | + |
| 90 | +## Development Guidelines |
| 91 | + |
| 92 | +### Critical Rules — Removed Components |
| 93 | +All Zend Framework and Varien components have been completely removed. **NEVER** use any of these in new code: |
| 94 | +- Zend_* classes (Zend_Log, Zend_Date, Zend_Db, Zend_Json, Zend_Validate, Zend_Filter, Zend_Http, Zend_Cache, Zend_Pdf, Zend_Exception) |
| 95 | +- Varien_* prefixed classes — use `Maho\*` namespace instead (see Modernizations) |
| 96 | +- TinyMCE — use TipTap 3.x |
| 97 | +- prototypejs or jquery — use modern vanilla JS |
| 98 | + |
| 99 | +### General Guidelines |
| 100 | +- CSS: use modern features, no IE/legacy browser support |
| 101 | +- JS AJAX: always use `mahoFetch()` instead of native `fetch()` |
| 102 | +- New tools/libraries: always use latest available version |
| 103 | +- Update PHP file headers with current year for the Maho copyright line |
| 104 | +- New PHP files: only Maho copyright with current year: |
| 105 | +```php |
| 106 | +/** |
| 107 | + * Maho |
| 108 | + * |
| 109 | + * @package Mage_Module |
| 110 | + * @copyright Copyright (c) 2026 Maho (https://mahocommerce.com) |
| 111 | + * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
| 112 | + */ |
| 113 | +``` |
| 114 | +- Before committing, ensure translatable strings (`$this->__()` or `Mage::helper()->__()`) are in `app/locale/en_US/` |
| 115 | + |
| 116 | +### Adding New Features |
| 117 | +- New modules: `app/code/core/Maho/` namespace, declared in `app/etc/modules/` |
| 118 | +- Follow existing module patterns; use `declare(strict_types=1)` and PHP 8.3+ features |
| 119 | +- Use `#[\Override]` attribute for overridden methods |
| 120 | +- When overriding admin routes in Maho modules, use `before="Mage_Adminhtml"` pattern |
| 121 | + |
| 122 | +### Modifying Existing Features |
| 123 | +- Do not increment module version in `config.xml` |
| 124 | +- Feel free to modify core files directly |
| 125 | +- Avoid creating a new module unless asked for it |
| 126 | + |
| 127 | +## Modernizations |
| 128 | + |
| 129 | +### Logging (Monolog) |
| 130 | +`Mage::LOG_*` constants follow standard syslog levels (EMERGENCY through DEBUG): |
| 131 | +```php |
| 132 | +Mage::log('Error occurred', Mage::LOG_ERROR); |
| 133 | +Mage::log('Debug info', Mage::LOG_DEBUG, 'custom.log'); |
| 134 | +Mage::logException($e); // Logs to exception.log at ERROR level |
| 135 | +``` |
| 136 | + |
| 137 | +### HTTP Client (Symfony HttpClient) |
| 138 | +```php |
| 139 | +$client = \Symfony\Component\HttpClient\HttpClient::create(['timeout' => 30]); |
| 140 | +$response = $client->request('GET', $url); |
| 141 | +$data = $response->getContent(); |
| 142 | +``` |
| 143 | + |
| 144 | +### JSON Handling |
| 145 | +```php |
| 146 | +Mage::helper('core')->jsonEncode($data); |
| 147 | +Mage::helper('core')->jsonDecode($data); // throws Mage_Core_Exception_Json on error |
| 148 | +``` |
| 149 | + |
| 150 | +### Validation |
| 151 | +```php |
| 152 | +Mage::helper('core')->isValidNotBlank($value); |
| 153 | +Mage::helper('core')->isValidEmail($value); |
| 154 | +Mage::helper('core')->isValidRegex($value, '/pattern/'); |
| 155 | +Mage::helper('core')->isValidLength($value, $min, $max); |
| 156 | +Mage::helper('core')->isValidRange($value, $min, $max); |
| 157 | +Mage::helper('core')->isValidUrl($value); |
| 158 | +Mage::helper('core')->isValidDate($value); |
| 159 | +``` |
| 160 | + |
| 161 | +### Date Handling (Native PHP DateTime) |
| 162 | +- **Database storage**: Always UTC in `'Y-m-d H:i:s'` format |
| 163 | +- **Display**: `storeDate()` converts UTC → HTML5 format |
| 164 | +- **Processing**: `utcDate()` converts HTML5 → UTC for database |
| 165 | + |
| 166 | +```php |
| 167 | +$html = Mage::app()->getLocale()->storeDate(null, $dbDate, false, 'html5'); |
| 168 | +$utc = Mage::app()->getLocale()->utcDate(null, $inputDate, false, 'html5'); |
| 169 | +Mage_Core_Model_Locale::now(); // 'Y-m-d H:i:s' |
| 170 | +Mage_Core_Model_Locale::today(); // 'Y-m-d' |
| 171 | +``` |
| 172 | + |
| 173 | +### Filtering & Locale |
| 174 | +```php |
| 175 | +Mage::app()->getLocale()->normalizeNumber($qty); |
| 176 | +Mage::app()->getLocale()->formatCurrency($amount, $currencyCode); |
| 177 | +Mage::helper('core')->filterEmail($email); |
| 178 | +Mage::helper('core')->filterUrl($url); |
| 179 | +Mage::helper('core')->filterInt($value); |
| 180 | +Mage::helper('core')->filterFloat($value); |
| 181 | +``` |
| 182 | + |
| 183 | +### Varien → Maho Namespace |
| 184 | +`Varien_X_Y` → `Maho\X\Y`. Exceptions: `Varien_Object` → `Maho\DataObject`, `Varien_Filter_Array` → `Maho\Filter\ArrayFilter`, `Varien_Filter_Object` → `Maho\Filter\ObjectFilter`. |
| 185 | + |
| 186 | +### WYSIWYG Editor (TipTap 3.x) |
| 187 | +Files: `public/js/mage/adminhtml/wysiwyg/tiptap/{extensions,setup}.js` and `tiptap.css` |
| 188 | + |
| 189 | +### Other Components |
| 190 | + |
| 191 | +- **Exceptions**: Use `Mage_Core_Exception` (Zend_Exception removed) |
| 192 | +- **PDF Generation**: Use DomPdf with HTML/CSS templates. Extend `Mage_Core_Block_Pdf` (Zend_Pdf removed) |
| 193 | +- **Cache**: Use native Maho cache system (Zend_Cache removed) |
| 194 | + |
| 195 | +## Testing (Pest PHP) |
| 196 | + |
| 197 | +Test contexts: `MahoFrontendTestCase`, `MahoBackendTestCase`, `MahoInstallTestCase` |
| 198 | + |
| 199 | +```php |
| 200 | +uses(Tests\MahoFrontendTestCase::class); |
| 201 | + |
| 202 | +it('can process customer orders', function () { |
| 203 | + // Test code |
| 204 | +}); |
| 205 | +``` |
| 206 | + |
| 207 | +## Security Patterns |
| 208 | + |
| 209 | +- **ALWAYS use `getParam()`** for request parameters in controllers — `getUserParam()` only checks route params and breaks query strings |
| 210 | +- Define `public const ADMIN_RESOURCE` in admin controllers for ACL |
| 211 | +- Use `_setForcedFormKeyActions()` for state-changing actions (delete, save, etc.) |
| 212 | +- Validate/sanitize user input at the model layer |
| 213 | +- Doctrine DBAL parameterized queries are automatic |
| 214 | + |
| 215 | +## Git Commit Rules |
| 216 | +- **NEVER** include "Co-Authored-By: Claude" or any AI attribution in commits |
| 217 | +- **NEVER** mention Claude, AI, or assistant in commit messages |
| 218 | +- Keep commits professional and focused only on code changes |
0 commit comments