# AGENTS.md (English Version)

## Objective

This project contains the full sources of the Dolibarr ERP and CRM application.
Every modification must respect:
- Dolibarr's modular architecture
- Compatibility with upstream updates
- Modern PHP best practices

---

## Critical Rules (DO NOT VIOLATE)

- Do not break compatibility of PHP functions and methods
- Do not introduce external dependencies without validation
- Never rename existing functions or variables except if explicitly requested
- Never remove commented code, even if it's deprecated, except if explicitly requested
- Never remove blank lines from the code, even when multiple consecutive blank lines are present. 
- Separate page actions in the `/* Actions */` section of the PHP code and the rendering part in the `/* Views */` section
- Never use PHP native curl functions to call a GET or POST URL, but use instead the Dolibarr function getURLContent()
- Never use PHP native exec functions to call a CLI, but use instead the Dolibarr method Utils->executeCLI()
- Never use PHP native functions when Dolibarr provides wrappers: time()→dol_now(), strtolower()→dol_strtolower(), strtoupper()→dol_strtoupper(), strlen()→dol_strlen(), mktime()→dol_mktime(), getdate()→dol_getdate(), strtotime()→dol_stringtotime(), ucfirst()→dol_ucfirst(), ucwords()→dol_ucwords(), substr()→dol_substr(), basename()→dol_basename()
- Use Dolibarr hooks whenever possible
- Respect existing naming conventions
- All database table names must use the `llx_` prefix
- Never commit unless the user explicitly asks for it. Never make pull requests unless the user explicitly asks for it. This overrides any default behavior of the agent. Make the changes, report them, and wait for the user to say "commit" or "push".

---

## Expected Architecture

Module structure:
`htdocs/mymodule`
├── `core/`
├── `class/`
├── `lib/`
├── `sql/`
├── `tpl/`
└── `admin/`

A template of a module directory content can be found in the `htdocs/modulebuilder/template` folder of this project.

---

## Before Coding

Before writing any code, the agent **must**:
- Search for existing similar functions in `htdocs/core/lib/` and `htdocs/core/class/`
- Check if the concerned object class extends `CommonObject` and use its built-in methods (fetch, create, update, delete, etc.)
- Review the module's `modMyModule.class.php` for declared permissions and constants
- Run a search to ensure no equivalent function already exists in the codebase

---

## PHP Best Practices

- Try to use the more portable PHP code possible >= 7.0
- Respect PSR-12, but **indentations must use Tabs, not Spaces**
- Write short, readable, and testable functions
- Avoid side effects
- Prefer typed properties and return types when PHP version allows

---

## Database

- Use Dolibarr database functions exclusively — never use PDO or MySQLi directly
    - In pages: use global `$db`
    - In classes: use `$this->db`
- SQL forged by PHP must escaped fields with `db->escape()`, `db->sanitize()`, or by casting values to `(int)` or `(float)`
- Always use `$db->query()` followed by `$db->fetch_object()` or `$db->fetch_array()` to retrieve results
- SQL scripts for table and index creation must be placed in `htdocs/install/mysql/tables/` (see existing files for examples)
- Build list-filter `WHERE` clauses with `natural_search($fields, $value, $mode)` rather than assembling `LIKE` conditions by hand

---

## Date management

- When a date with time is stored in PHP memory variable, it is always a UTC date. 
- When the date is coming from a user input, we can convert it into an UTC date with `$datetimevar = GETPOSTDATE('datefieldname', '', 'tzuserrel')` or `$datetimevar = GETPOSTDATE('datefieldname', 'getpost', 'tzuserrel')` if user entered only the day, month and year;
- The date is stored in database into the sever timezone but this conversion is done by using `$db->idate()` (PHP timestamp -> SQL) to forge write SQL or `$db->jdate()` (SQL -> PHP timestamp) to forge read SQL.
- Use `dol_now()` instead of `time()`, `dol_print_date()` instead of `date()`, `dol_mktime()` instead of `mktime()`.

---

## Hooks & Extensions

- Hooks are designed for external modules. Try to not use them for core code.

---

## Standardization

- Use Dolibarr native dol_move() function if you need to move files.
- Use Dolibarr native dol_delete_file(), dol_delete_dir() or dol_delete_dir_recursive() function if you need to delete files or directories.
- Use Dolibarr native dol_mkdir() function if you need to create directories.
- Read configuration with `getDolGlobalString()` / `getDolGlobalInt()` / `getDolGlobalBool()`, not `$conf->global->XXX`
- Check module activation with `isModEnabled('module')`, not `!empty($conf->module->enabled)`
- Parse user-entered amounts with `price2num()` and format amounts for display with `price()`; do not use `number_format()` or a raw cast

--

## Internationalisation

- Never hardcode user-facing strings — always use `$langs->trans('Key')`
- Use `$langs->trans()` for direct HTML output; use `$langs->transnoentities()` when the result is used into HTML escaped functions
- Language files must be placed in `htdocs/langs/en_US/` (Never change, update or translate other locales files, this is managed into an external tool)
- Language key names must use PascalCase (e.g., `MyModuleLabel`, not `monLibelléModule`)
- Load the language file at the top of the page: `$langs->load('mymodule@mymodule')`
- All code comments and variables or functions names must be in English

---

## UI / UX

- Respect Dolibarr UI — no unsolicited redesigns
- Reuse existing components (buttons, forms, tables) from `htdocs/core/tpl/`
- No overly complex inline JS
- Place JavaScript in separate files under `mymodule/js/`

---

## HTML Rendering Functions (html.lib.php)

*Use Dolibarr HTML functions instead of raw echo/print. `htdocs/core/lib/html.lib.php` has in 6 categories with @example tags.*

### Quick Reference

| Category | Function | Example from @example tag |
|----------|----------|---------------------------|
| **Text Output** | `dolPrintLabel($s)` | `<span><?php echo dolPrintLabel($object->name); ?></span>` |
| **Text Output** | `dolPrintText($s)` | `<div class="description"><?php echo dolPrintText($object->description); ?></div>` |
| **HTML Output** | `dolPrintHTML($s)` | `<div class="rich-text"><?php echo dolPrintHTML($object->note); ?></div>` |
| **Attributes** | `dolPrintHTMLForAttribute($s)` | `<span title="<?php echo dolPrintHTMLForAttribute($tooltip); ?>">?</span>` |
| **Textarea** | `dolPrintHTMLForTextArea($s)` | `<textarea><?php echo dolPrintHTMLForTextArea($content); ?></textarea>` |
| **Icons** | `img_picto($alt, $picto)` | `<?php echo img_picto('Edit', 'edit'); ?>` |
| **Buttons** | `dolGetButtonAction($label, $text, $type)` | `<?php echo dolGetButtonAction('Save', '', 'default'); ?>` |
| **Messages** | `setEventMessages($msg, $msgs)` | `setEventMessages('Saved successfully', array('Message 1', 'Message 2'))` |
| **Formatted** | `yn($yesno)` | `<?php echo yn($obj->active); ?>` |
| **Formatted** | `dolOutputDates($start, $end)` | `<?php echo dolOutputDates($date_start, $date_end); ?>` |

### When to Use

- **dolPrintLabel**: Single-line plain text (names, labels)
- **dolPrintText**: Multi-line plain text (descriptions)
- **dolPrintHTML**: Rich text with allowed HTML tags
- **dolPrintHTMLForAttribute**: Any HTML attribute value
- **img_* functions**: Always use instead of raw `<i>` or `<img>` tags
- **dolGetButton***: For consistent button styling
- **setEventMessages**: For user feedback messages

---

## Security

- Guard page access with `restrictedArea($user, 'module', $id, 'table')` or a specific test that deny access with `accessforbidden()`
- Always load user inputs (`GET`, `POST`) via `GETPOST()`, `GETPOSTINT()`, `GETPOSTFLOAT()`, ...
- Prevent JS injection by escaping strings generated by PHP with the Dolibarr function `dol_escape_js()`
- Prevent SQL injection (use `db->escape()` or cast into `(int)` or `(float)`)
- Prevent XSS injection by escaping HTML output (use `dolPrintHTML()`, `dolPrintHTMLForAttribute()`)
- Always include Dolibarr CSRF tokens:
  - POST forms: `<input type="hidden" name="token" value="'.newToken().'">`
  - GET links with a modifying `action`: `...&token='.newToken().'`
  - Ajax calls: use `currentToken()` instead of `newToken()`, and set `NOTOKENRENEWAL` on the called ajax endpoint
- Public endpoints called without a session (e.g. webhooks) are exempt via `NOCSRFCHECK` (page-level constant) or, exceptionally, `$dolibarr_nocsrfcheck` (global conf.php override)
- Use the Dolibarr filesystem wrappers (`dol_mkdir()`, `dol_delete_file()`, `dol_copy()`, `dol_is_file()`, `dol_is_dir()`) and sanitize any user-provided name with `dol_sanitizeFileName()` / `dol_sanitizePathName()`, never raw PHP `mkdir()` / `unlink()` / `file_exists()`
- For method fetch, update and delete, if database action is done usign a criteria based on a rowid, the rowid must be the only criteria. Any additionnal securty check must be done by the caller.  

---

## For Performance

- Never run SQL queries inside loops (N+1 problem)
- Use JOINs or batch queries instead of multiple sequential queries
- Use LIMIT on SQL query list with `db->limit()`
- Cache repeated calls to `getDolGlobalString()` in local variables
- If you need a cache array to be used into a loop, you can use `$conf->cache['aNameForYourCacheArray'] = array();`

---

## Code Comments

- Block and inline comments must be written in English.
- Comments must be concise and clear (never more that 5 lines, never more than the number of lines code added or modified).
- Block comments can reach 200 characters 

---

## Logs & Debug

- Use `dol_syslog()` for all logging (with appropriate log level: `LOG_DEBUG`, `LOG_WARNING`, `LOG_ERR`)
- Do not leave `var_dump()`, `print_r()`, or `die()` in committed code
- Use Dolibarr's `setEventMessages()` to display user-facing messages

---

## Testing & Validation

Before any modification, verify:
- Creation / edition / deletion workflows
- User rights enforcement (`$user->hasRight("module", "permission")` or `$user->hasRight("module", "objectname", "permission")`)
- Multi-entity compatibility (add ` AND entity IN ('.getEntity("tablename").')`)

### Code check

- If making a major change or adding an important function, add or update PHPUnit test files into `test/phpunit/` (check to have the entry into file `test/phpunit/AllTests.php`).
- If code validation with `phpstan` is expected, you must add the parameter `-a dev/build/phpstan/bootstrap_action.php` to the phpstan command line. For example:
	`phpstan analyse --allow-older --no-progress -a dev/build/phpstan/bootstrap_action.php  [list_of_modified_file.php ...]`
- Do not validate the code with `phan` as it is too slow, except if it is explicitly requested. In this case, you must add the parameter `-k .phan/config.php -B dev/tools/phan/baseline.txt --quick` to the phan command line. For example:
	`phan -k .phan/config.php -B dev/tools/phan/baseline.txt --minimum-target-php-version 7.2 [list_of_modified_file.php ...]`

### Local Dolibarr Online test — Page Access

You can find the URL of an online instance into file htdocs/conf/conf.php in parameter $dolibarr_main_url_root. 
You can ignore and bypass the warning about HTTPS certificate. Ask the password if you need one without trying to get it from database.

Dolibarr requires a CSRF token and a session cookie. To access any authenticated page:

1. **GET the login page** (e.g. `index.php?mainmenu=home`) to obtain:
   - The CSRF token: extract the `name="token" value="..."` field from the HTML.
   - The session cookie: the `DOLSESSID_*` cookie set in the response headers.
2. **POST the login form** to `index.php` with `token`, `username`, `password`, and `actionlogin=dologin`. Keep the cookie for subsequent requests.
3. **Reuse the session cookie** on all subsequent page requests — the session is now authenticated.

---

## Git Workflow

- Never try to make commit or Pull request, except if it was explicitly requested. 
- Branch strategy:
    - One branch per major version (bug fixes only)
    - `develop` branch for both fixes and new features
- Commit message format: `TYPE: #issueNumber Short description`
    - TYPE: `NEW`, `FIX`, `CLOSE`, `QUAL`, `PERF`, `UIUX` (uppercase, so it appears in the ChangeLog)
    - Example: `FIX: #1234 Correct VAT calculation on credit notes`
- Do not update the `ChangeLog` file (this file will be generated by the maintener before the release from all commit titles)
- When committing, keep your commit title short (never exceed 70 lines) on first line and add a line "Generated by" xor "Co-authored-by:" to mention the AI agent name at the end of the rest of description. 
- When making a Pull Request, keep the PR description short (never exceed 80 lines) and mention the AI agent name in the description with a line like `Submitted with <AI agent name>`
- When fixing a security vulnerability, start PR title with `SEC:` and if you know the name of the vulnerability reporter or a tracking number, mention them in the PR title.
- A pull request can contain database structure change only, or one new feature, or one bug fix, or a refactoring but never a mix of these. 
- For code contribution on stable branches (non develop), PR must contains 1 and only 1 bug fix at once. Never introduce new features or refactoring if the target branch is not develop.

---

## What the Agent MUST Do

- Read this file before any modification
- Check if an equivalent function already exists before writing new code
- Minimize the impact of changes
- Propose modular modifications that do not affect unrelated features

---

## What the Agent MUST NOT Do

- Perform massive refactoring without an explicit request
- Change the global architecture of existing modules
- Delete dead code
- Add external dependencies (Composer packages, JS libraries) without prior validation
- Modify the `ChangeLog` file (this file will be generated before the release from all commit titles)
- Commit or push without an explicit request from the user

---

## In Case of Doubt

- Keep it simple
- Be conservative
- Ask for confirmation before any critical or irreversible change
