# Developers

# Coding style

A list of coding style rules to be used when developing in vtenext.

<table id="bkmrk-file-encoding%3A-utf-8" style="border-collapse:collapse;width:100%;height:1513.17px;"><colgroup><col style="width:16.9235%;"></col><col style="width:83.0765%;"></col></colgroup><tbody><tr style="height:29.7969px;"><td class="align-right" style="height:29.7969px;">**File encoding:**</td><td style="height:29.7969px;">UTF-8. All files should be encoded in UTF-8</td></tr><tr style="height:29.7969px;"><td class="align-right" style="height:29.7969px;">**Newlines:**</td><td style="height:29.7969px;">Unix style, so **\\n**, not \\r\\n or \\r</td></tr><tr style="height:288.391px;"><td class="align-right" style="height:288.391px;">**Indentation size:**</td><td style="height:288.391px;">With TABS, set to 4 character. Don't mix spaces and tabs:

[![Jwzimage.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-02/jwzimage.png)](https://usermanual.vtenext.com/uploads/images/gallery/2025-10/Jwzimage.png)

</td></tr><tr style="height:657.906px;"><td style="height:657.906px;">**Indentation style:**</td><td style="height:657.906px;">C Style, so:

- Opening brace ( `{` ) on the same line as its control clause (an exception can be made if the line is very long, so in this case the brace can go to a new line, for better readability)
- Closing brace ( `}` ) aligned with the opening statement
- Single space before the opening brace
- Single space before the parenthesis of a control structure (`if`, `for`, `foreach`, ...)

<table style="border-collapse:collapse;width:100.145%;"><colgroup><col style="width:50.0725%;"></col><col style="width:50.0725%;"></col></colgroup><thead><tr><td class="align-center" colspan="2">**Examples**  
</td></tr></thead><tbody><tr><td><p class="callout success">Good</p>

```php
if ($var === '234') {
    $out = 'ok';
} elseif ($var === '567') {
    $out = 'also ok';
} else {
    $out = 'maybe ok?';
}
```

</td><td><p class="callout danger">**Bad**: no space between `foreach` and `(`</p>

```
foreach($array as $var) {
    // do something
}
```

</td></tr><tr><td><p class="callout danger">**Bad**: opening brace on new line</p>

```
if ($var == 8)
{
    // do something
}
```

</td><td>  
</td></tr></tbody></table>

Please note that these style rules are not strictly observed throughout the existing code, so you may find exceptions. Try to follow them, to improve the code homogeneity, but don't be too rigid in following them when evaluating existing code.

</td></tr><tr><td class="align-right">**Line length:**</td><td>We don't have a strict maximum line length. Usually up to 200 characters is ok, more than that can be hard to read on smaller screens, so better to split the line, but this is left to the developer's choice.

Also, splitting too much, for example a 300 chars line divided in 30 lines of 10 chars is no more readable than the original line. Balance readability and conciseness of code.

</td></tr><tr style="height:447.688px;"><td class="align-right" style="height:447.688px;">**PHP tags:**</td><td style="height:447.688px;">Use standard opening tag: **`<?php`** , not the short one: `<?`

Avoid the closing tag ( `?>` ) if there's no output.

<table style="border-collapse:collapse;width:100.143%;"><colgroup><col style="width:33.381%;"></col><col style="width:33.381%;"></col><col style="width:33.381%;"></col></colgroup><thead><tr><td colspan="3">**Examples**

</td></tr></thead><tbody><tr><td><p class="callout success">**Good**: standard opening tag, no closing tag</p>

```php
<?php

$var = 'value';
// php code
 
// no closing tag here
```

</td><td><p class="callout danger">**Bad**: short opening tag, closing tag</p>

```php
<? // short tag
 
$var = 'value';
// php code
 
// unnecessary
// closing tag
?>
```

</td><td><p class="callout success">**Good**: standard opening tag, closing tag with output</p>

```php
<?php

$var = 'value';
// php code

// output after php code
?>
<html>
<!-- html code -->
</html>
```

</td></tr></tbody></table>

</td></tr><tr style="height:29.7969px;"><td class="align-right" style="height:29.7969px;">**Auto formatting:**</td><td style="height:29.7969px;">- **Core files**: Do not enable any "auto formatting on save" functionality in your IDE/editor, to avoid too many changes in existing files, which will be hard to diff.
- **Custom files**: you can reformat existing files to make them compliant with our standards.

</td></tr><tr style="height:29.7969px;"><td class="align-right" style="height:29.7969px;">**License:**</td><td style="height:29.7969px;">When creating a new file (php, js, tpl, html, scss) to be included in the standard vtenext, use the following license at the top of the file:

```php
<?php
/*************************************
* SPDX-FileCopyrightText: 2009-present Vtenext S.r.l. Società Benefit
* SPDX-License-Identifier: LicenseRef-vtenext-business-license
************************************/
```

</td></tr></tbody></table>

</body></html>

# Weak comparisons

#### **Backward Incompatible Changes**

View [https://www.php.net/manual/en/migration80.incompatible.php](https://www.php.net/manual/en/migration80.incompatible.php)

#### **Key principles**

Follow these fundamental rules when writing compatible code:

<p class="callout success">**Analyze variable types:** Always understand what types and values a variable can hold.</p>

<p class="callout success">**Use strict comparisons:** Prefer `===` and `!==` over `==` and `!=` to avoid implicit type coercion.</p>

<p class="callout success">**Declare types explicitly:** Always use type declarations for function/method parameters and return types.</p>

<p class="callout success">**Validate with strict checks:** Use strict type checks in conditional statements to prevent unexpected behavior (e.g., `in_array` with `true` as third parameter).  
Example: [https://www.php.net/manual/en/function.in-array.php#example-2](https://www.php.net/manual/en/function.in-array.php#example-2)</p>

<p class="callout success">**Use PHP constants:** Always use predefined PHP constants instead of numbers (e.g., `UPLOAD_ERR_OK` instead of `0`).</p>

<p class="callout success">**Enable strict types:** Add `declare(strict_types=1);` at the top of your PHP files ***when possible***.</p>

#### **Strings**

Use `empty()` instead of comparing with empty string.

<table id="bkmrk-%E2%9D%8C-don%27t-%E2%9C%85-do-if-%28%24a-"><tbody><tr><th>❌ DON'T</th><th>✅ DO</th></tr><tr><td>```php
if ($a == '') {
    // ...
}
```

</td><td>```php
if (empty($a)) {
    // ...
}
```

</td></tr><tr><td>```php
if ($a != '') {
    // ...
}
```

</td><td>```php
if (!empty($a)) {
    // ...
}
```

</td></tr></tbody></table>

#### **Checkbox values**

In PHP 8, comparing numeric strings with numbers produces different results.

<table id="bkmrk-%E2%9D%8C-don%27t-%E2%9C%85-do-if-%28%24a--1"><tbody><tr><th>❌ DON'T</th><th>✅ DO</th></tr><tr><td>```php
if ($a == 0) {
    // ...
} elseif ($a == 1) {
    // ...
} else {
    // ...
}
```

</td><td>```php
if (strval($a) === '0') {
    // ...
} elseif (strval($a) === '1') {
    // ...
} else {
    // ...
}
```

</td></tr></tbody></table>

# RequestHandler

#### **What is RequestHandler?**

`RH` (alias for `RequestHandler`) provides secure, centralized access to HTTP request data. It automatically sanitizes input using `vtlib_purify()` and applies type-safe filters from `F::` enum. This replaces direct access to superglobals like `$_REQUEST`, `$_GET`, `$_POST`, etc.  
Our goal with this system is to prevent vulnerabilities ex. SQL Injection, XSS, File Inclusion, ecc.

##### **How filtering works**

RequestHandler applies a two-level filtering process:

- **Whitelisted keys** (defined in `config/request.config.php`): These keys are automatically filtered using the filter specified in the configuration during initialization. The configured filter is applied first, and if you specify an additional filter when accessing the value, that filter is applied to the already-sanitized value.
- **Non-whitelisted keys**: All other keys pass through `vtlib_purify()` for HTML sanitization by default. If you specify an explicit filter (e.g., `F::int`, `F::email`), it is applied to the purified value.

```php
// ex. index.php?module=Accounts&action=CustomAction&custom=123&another=TEST

// In config: 'module' => F::mod
$module = RH::r('module');               // Uses F::mod filter from config
$customParam = RH::r('custom', F::int);  // Not in config: vtlib_purify() + F::int
$anotherParam = RH::r('another');        // Not in config: only vtlib_purify()
```

**This approach ensures:**

- Unknown parameters are still sanitized to prevent XSS attacks
- Explicit filters always provide additional type safety
- Explicitly mapping more parameters enhances input security in the application.

---

#### ✅ **DO**

```php
// ✅ Always access superglobals through RH
$module = RH::r('module', F::mod);
$id = RH::r('id', F::int);

// ✅ Always specify appropriate filters
$email = RH::r('email', F::email);
$age = RH::r('age', F::int);
$status = RH::r('status', F::enum(['active', 'inactive']));

// ✅ Check for null values when data is required
$userId = RH::r('user_id', F::int);
if ($userId === null) {
    throw new InvalidArgumentException('User ID is required');
}

// ✅ Use POST for operations that are sensitive or that modify the CRM state (e.g.: sending an email, saving a record, ...)
RH::ensureMethod('POST');
```

#### ❌ **DON'Ts**

<p class="callout danger">**Avoid these practices:** They bypass security, are redundant and can introduce vulnerabilities.</p>

- **Never access superglobals directly:** `$_REQUEST`, `$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`
- **Don't use manual sanitization:** Avoid `vtlib_purify()` calls, RH handles this centrally
- **Don't skip filters:** Calling `RH::r('age')` without a filter may return unvalidated strings
- **Don't use raw methods without reason:** Prefer `RH::r(..., F::...)` over `RH::r_raw(...)`
- **Don't ignore security:** Always validate HTTP methods for sensitive operations or action that modify the CRM state
- **Don't assume data types:** Always use appropriate filters and null checks

```php
// ❌ Direct superglobal access
$userId = $_REQUEST['user_id'];
$email = $_POST['email'];
$page = intval($_GET['page']);

// ❌ Manual sanitization
$name = vtlib_purify($_REQUEST['name']);
$description = vtlib_purify($_POST['description']);

// ❌ No filter applied
$age = RH::r('age');  // Returns string "25" instead of int 25
if ($age > 18) {      // String comparison may not work as expected
    // ...
}

// ❌ Using raw without justification
$data = RH::r_raw('data');  // Bypasses sanitization
echo $data;                 // Potential XSS vulnerability

// ❌ No null check for required data
$recordId = RH::r('record', F::int);
$record = getRecord($recordId);  // May fail if $recordId is null

// ❌ Accepting any HTTP method for sensitive operation or action that modify the CRM state
// No method check here - vulnerable to GET-based attacks
$settings = RH::r('settings', F::json);
saveSettings($settings);

// ❌ Type assumption without validation
$ids = RH::r('ids');     // Assuming it's an array
foreach ($ids as $id) {  // Fatal error if $ids is not an array
    // ...
}
```

---

#### **Main methods**

<table id="bkmrk-metodo-descrizione-r"><thead><tr><th>Method</th><th>Description</th></tr></thead><tbody><tr><td>`RH::r($key, $filter)`</td><td>Access $\_REQUEST with filter (optional)</td></tr><tr><td>`RH::g($key, $filter)`</td><td>Access $\_GET with filter (optional)</td></tr><tr><td>`RH::p($key, $filter)`</td><td>Access $\_POST with filter (optional)</td></tr><tr><td>`RH::c($key, $filter)`</td><td>Access $\_COOKIE with filter (optional)</td></tr><tr><td>`RH::s($key, $filter)`</td><td>Access $\_SERVER with filter (optional)</td></tr><tr><td>`RH::f($key)`</td><td>Access uploaded files (PSR-7)</td></tr><tr><td>`RH::r_mod()`</td><td>Module name from $\_REQUEST</td></tr><tr><td>`RH::r_action()`</td><td>Action name from $\_REQUEST</td></tr><tr><td>`RH::r_record()`</td><td>Record ID from $\_REQUEST</td></tr><tr><td>`RH::r_has($key)`</td><td>Verify key existence (sanitized)</td></tr><tr><td>`RH::r_has_raw($key)`</td><td>Verify key existence (raw)</td></tr><tr><td>`RH::r_all()`</td><td>All parameters sanitized</td></tr><tr><td>`RH::r_all_raw()`</td><td>All parameters raw (unsanitized)</td></tr><tr><td>`RH::ensureMethod($method)`</td><td>Force HTTP method (405 if different)</td></tr><tr><td>`RH::isFromMobile()`</td><td>Check if request is from mobile app</td></tr><tr><td>`RH::isFromPortal()`</td><td>Check if request is from portal</td></tr></tbody></table>

#### **Basic filters**

- **Null input always returns null** - All filters preserve null values
- **Filters for primitive types (int, float, bool)** - Convert invalid input to default values (0, 0.0, false)
- **Validation filters (email, url, ip, enum, regex, datetime)** - Return null for invalid input
- **Sanitization filters (str, html, alpha, alphanum, mod, action, etc.)** - Remove invalid characters and return cleaned string
- **Always check for null** - Use null coalescing operator `??` for required values

<table id="bkmrk-filter-use-case-exam" style="width:100%;"><thead><tr><th style="width:16%;">Filter</th><th style="width:20%;">Use case</th><th style="width:32%;">Valid input → Output</th><th style="width:32%;">Invalid input → Output</th></tr></thead><tbody><tr><td>`F::int`</td><td>IDs, numbers</td><td>`"42"` → `42`</td><td>`"abc"` → `0`  
`null` → `null`</td></tr><tr><td>`F::float`</td><td>Decimal numbers</td><td>`"3.14"` → `3.14`</td><td>`"abc"` → `0.0`  
`null` → `null`</td></tr><tr><td>`F::str`</td><td>General strings (**no HTML; default max length: 1000 characters**)</td><td>`"Hello"` → `"Hello"`</td><td>`"<b>abc</b>"` → `"abc"` (tags stripped)

`"Very long text..."` → Truncated to 1000 chars by default  
`null` → `null`

</td></tr><tr><td>`F::str(max: 50)`</td><td>Limited strings (**no HTML**)</td><td>`"Short text"` → `"Short text"`</td><td>`"Very long text..."` → Truncated to 50 chars  
`null` → `null`</td></tr><tr><td>`F::substr(max: 50)`</td><td>Substring (**keeps HTML**, may result in invalid HTML)</td><td>`"<b>text</b>"` → `"<b>text</b>"`</td><td>Long string → Truncated (HTML preserved)  
`null` → `null`</td></tr><tr><td>`F::html`</td><td>HTML strings (purified)</td><td>`"<b>bold</b>"` → `"<b>bold</b>"`</td><td>`"<script>alert(0)</script>"` → `""` (XSS removed)  
`null` → `null`</td></tr><tr><td>`F::htmlurl`</td><td>HTML strings (purified, allow URLs) \[only from vtenext &gt;= 26.07\]</td><td>`"<img src='...'>"` → `"<img src='...'>"`</td><td>  
</td></tr><tr><td>`F::bool`</td><td>Boolean flags</td><td>`"1"`, `"true"`, `"yes"` → `true`</td><td>`"0"`, `"false"`, `"no"` → `false`  
`null` → `null`</td></tr><tr><td>`F::email`</td><td>Email addresses</td><td>`"user@example.com"` → `"user@example.com"`</td><td>`"invalid-email"` → `null`  
`"user@"` → `null`  
`null` → `null`</td></tr><tr><td>`F::url`</td><td>URLs</td><td>`"https://example.com"` → `"https://example.com"`</td><td>`"invalid-url"` → `null`  
`"htp://exa"` → `null`  
`null` → `null`</td></tr><tr><td>`F::ip`</td><td>IP addresses</td><td>`"192.168.1.1"` → `"192.168.1.1"`</td><td>`"999.999.999.999"` → `null`  
`"invalid-ip"` → `null`  
`null` → `null`</td></tr><tr><td>`F::json`</td><td>JSON data</td><td>`'{"key":"value"}'` → `["key" => "value"]`</td><td>`'invalid json'` → `null`  
`'{broken'` → `null`  
`null` → `null`</td></tr></tbody></table>

#### **Specific filters**

<table id="bkmrk-filter-use-case-exam-1" style="width:100%;"><thead><tr><th style="width:16%;">Filter</th><th style="width:20%;">Use case</th><th style="width:32%;">Valid input → Output</th><th style="width:32%;">Invalid input → Output</th></tr></thead><tbody><tr><td>`F::mod`</td><td>Module names</td><td>`"Accounts"` → unchanged</td><td>`"Acc<script>"` → `"Accscript"` (special chars removed)  
`null` → `null`</td></tr><tr><td>`F::action`</td><td>Action names</td><td>`"Save"`, `"Detail.View"` → unchanged</td><td>`"Act!on@"` → `"Acton"` (special chars removed)  
`null` → `null`</td></tr><tr><td>`F::field`</td><td>Field names</td><td>`"firstname"` → unchanged</td><td>`"field-name!"` → `"fieldname"` (special chars removed)  
`null` → `null`</td></tr><tr><td>`F::recfields`</td><td>Record/field IDs</td><td>`"123@-456"`, `"1x2,3|4"` → unchanged</td><td>`"123abc!@#"` → `"123@"` (invalid chars removed)  
`null` → `null`</td></tr><tr><td>`F::path`</td><td>File paths (**<span style="color:rgb(224,62,45);">dots allowed</span>; use checkFileAccess to ensure secure file access**)</td><td>`"modules/Accounts"` → unchanged</td><td>`"path@with#special$chars"` → `"pathwithspecialchars"` (keeps valid chars)  
`null` → `null`</td></tr><tr><td>`F::subpath`</td><td>Sub paths (**<span style="color:rgb(224,62,45);">no dots</span>; use checkFileAccess to ensure secure file access**)</td><td>`"modules/Accounts"` → unchanged</td><td>`"subpath.with.dots"` → `"subpathwithdots"` (dots removed)  
`null` → `null`</td></tr><tr><td>`F::alpha`</td><td>Alphabetic only</td><td>`"abcABC_"` → unchanged</td><td>`"abc123!@#"` → `"abc"` (numbers/symbols removed)  
`null` → `null`</td></tr><tr><td>`F::alphanum`</td><td>Alphanumeric only</td><td>`"abcABC123_"` → unchanged</td><td>`"abc123!@#"` → `"abc123"` (symbols removed)  
`null` → `null`</td></tr><tr><td>`F::enum([...])`</td><td>Whitelist values</td><td>`"active"` (if in list) → unchanged</td><td>`"invalid"` (not in list) → `null`  
`null` → `null`</td></tr><tr><td>`F::regex('/pattern/')`</td><td>Pattern matching</td><td>`"ABC123"` (matches) → unchanged</td><td>`"invalid"` (no match) → `null`  
`null` → `null`</td></tr><tr><td>`F::datetime('Y-m-d')`</td><td>Date/time parsing</td><td>`"2026-01-08"` → unchanged</td><td>`"invalid-date"` → `null`  
`"2026-99-99"` → `null`  
`null` → `null`</td></tr><tr><td>`F::sep(',', 'intval')`</td><td>Split and transform</td><td>`"1,2,3"` → `[1, 2, 3]`</td><td>Non-string → `null`  
`null` → `null`</td></tr><tr><td>`F::jsfunc`</td><td>Safe JS functions</td><td>`"closePopup"` (if whitelisted) → unchanged</td><td>`"evilFunction"` (not whitelisted) → `null`  
`null` → `null`</td></tr></tbody></table>

#### **Advanced filters**

```php
// Enum filter - restrict to specific values
$status = RH::r('status', F::enum(['active', 'inactive', 'pending']));

// Regex filter - custom pattern validation
$code = RH::r('code', F::regex('/^[A-Z]{3}\d{3}$/'));

// DateTime filter - parse date strings
$date = RH::r('date', F::datetime('Y-m-d'));

// Separator filter - split strings into arrays
$ids = RH::r('ids', F::sep(',', 'intval')); // "1,2,3" -> [1,2,3]

// Array filter - validate nested arrays
$user = RH::r('user', F::array([
    'name' => F::str(max: 100),
    'email' => F::email,
    'age' => F::int,
]));

// Custom callable filter
$customCode = RH::r('code', function($value) {
    if (!is_string($value)) return null;
    $value = strtoupper(trim($value));
    return preg_match('/^[A-Z0-9]{6}$/', $value) ? $value : null;
});
```

---

#### **Examples**

##### **Simple form data**

```php
$name = RH::p('name', F::str(max: 100));
$email = RH::p('email', F::email);
$age = RH::p('age', F::int);

if ($name === null || $email === null) {
    throw new InvalidArgumentException('Name and email are required');
}
```

##### **List page with pagination**

```php
$page = RH::g('page', F::int) ?? 1;
$limit = RH::g('limit', F::int) ?? 20;
$sort = RH::g('sort', F::enum(['name', 'date', 'id'])) ?? 'name';
$direction = RH::g('dir', F::enum(['asc', 'desc'])) ?? 'asc';
$query = RH::g('q', F::str(max: 255));
```

##### **Complex form with nested arrays**

```php
$userData = RH::p('user', F::array([
    'personal' => F::array([
        'firstname' => F::str(max: 50),
        'lastname' => F::str(max: 50),
        'email' => F::email,
        'phone' => F::str(max: 20),
    ]),
    'preferences' => F::array([
        'language' => F::enum(['en', 'it', 'es']),
        'timezone' => F::str(max: 50),
    ]),
    'addresses' => F::array([
        'shipping' => F::array([
            'street' => F::str(max: 255),
            'city' => F::str(max: 100),
            'country' => F::str(max: 2),
        ]),
    ]),
]));
```

##### **File upload**

```php
$uploadedFile = RH::f('document');

if ($uploadedFile && $uploadedFile->getError() === UPLOAD_ERR_OK) {
    $filename = $uploadedFile->getClientFilename();
    $mimeType = $uploadedFile->getClientMediaType();
    $size = $uploadedFile->getSize();
    
    // ...
} else {
    // Handle upload error
    $error = $uploadedFile ? $uploadedFile->getError() : 'No file uploaded';
}
```

---

#### **Advanced Usage**

##### **Temporary request context**

<p class="callout danger">**Push/pop operations should be used only for testing or very specific scenarios.**</p>

```php
// Save current state and temporarily override
RH::push_r(['module' => 'TestModule', 'action' => 'TestAction']);

// Do something with temporary data
$module = RH::r_mod(); // Returns 'TestModule'

// Restore original state
RH::pop_r();
```

---

#### **Configuration**

The RequestHandler uses a configuration file to control request sanitization behavior and define safe parameters. Configuration is defined in `config/request.config.php` and can be extended via `config/request.config.override.php`.

##### `<strong>preserve_original_request</strong>`

```php
'preserve_original_request' => false,  // default
```

Controls whether all original request parameters are preserved in the `$_REQUEST` superglobal:

- **`false` (default, recommended):** Only keys listed in `safe_keys` are preserved in `$_REQUEST`. This provides maximum security by removing any unexpected parameters that could be malicious.
- **`true`:** All original parameters remain in `$_REQUEST`. Use only if you need backward compatibility with legacy code that accesses parameters not in the whitelist.

<p class="callout warning">**Security note:** When `preserve_original_request` is `false`, the `$_REQUEST` array is cleaned to contain only whitelisted keys. This prevents injection of unexpected parameters but does NOT bypass RH's access control - you should still use RH methods for all access.</p>

##### `<strong>safe_keys</strong>`

```php
'safe_keys' => [
    'module' => F::mod,
    'action' => F::action,
    'record' => F::int,
    'id' => F::int,
    'page' => F::int,
    'limit' => F::int,
    // ... add commonly used keys here
],
```

Defines a whitelist of parameter keys that are:

- **Preserved during request cleanup:** When `preserve_original_request` is `false`, only these keys remain in `$_REQUEST`
- **Pre-sanitized automatically:** Each key is associated with a filter (e.g., `F::mod`, `F::int`) that is applied during initialization

##### `<strong>safe_js_functions</strong>`

```php
'safe_js_functions' => [
    'closePopup',
    'LPOP.create',
    'parent.ActionTaskScript.addStaticRelatedRecord',
    // ... other trusted JavaScript function names
],
```

Lists JavaScript function names that are allowed to be passed as request parameters (e.g., for callbacks). This prevents XSS attacks by restricting which functions can be executed client-side.

- Used by the `F::jsfunc` filter
- Only listed functions are considered safe
- Reject any function not in the whitelist

##### **Creating custom configuration**

To add your own safe keys without modifying the core configuration, create `config/request.config.override.php`:

```php
// config/request.config.override.php
return [
    'safe_keys' => [
        // Your custom keys
        'custom_param' => F::str(max: 100),
        'my_record_id' => F::int,
    ],
    'safe_js_functions' => [
        // Your custom callback functions
        'MyApp.customCallback',
    ],
];
```

The override configuration is automatically merged with the default configuration.

<p class="callout warning">**Migration to version 26.04**  
During the upgrade to version 26.04, the system automatically scans for direct `$_REQUEST` superglobal usage. Any detected parameters are automatically added to `config/request.config.override.php` with the `F::html` filter to maintain backward compatibility and prevent breaking existing customizations.   
  
**All core code has been refactored to use RH methods exclusively** instead of direct superglobal access.</p>

# Routing system

Previously, `index.php` directly included the module file to process the action, based on the `action` and `file` request parameters.

With the new architecture, the resolution logic has been moved to the `<strong>IndexRouter</strong>` class, which handles action path resolution and processing.

#### **Action resolution**

##### **Searched paths (in order)**

When an action is requested, `IndexRouter` searches for the file in the following paths:

1. `modules/{MODULE}/{ACTION}.php`
2. `modules/{MODULE}/{ACTION}/{ACTION}.php`
3. `modules/{MODULE}/controllers/{ACTION}.php` <span style="color:rgb(45,194,107);">**(NEW)**</span>
4. `modules/VteCore/{ACTION}.php`
5. `modules/VteCore/controllers/{ACTION}.php` <span style="color:rgb(45,194,107);">**(NEW)**</span>

##### **Removal of `{MODULE}Ajax.php`**

It is no longer necessary to create a `{MODULE}Ajax.php` file in the module folder. AJAX requests can be handled directly by BaseAction classes, which automatically detect the request type and desired output format.

##### **Execution process**

After resolving and including the action file:

1. `IndexRouter` checks if the file contains a class that extends `BaseAction`
2. If found, it calls the `fromRequest()` method to create an instance of the class
3. During `fromRequest()`, class properties are automatically populated from request parameters
4. `processFromIndex()` is called, which performs validation and processing
5. The result is formatted according to the requested content-type and sent to the client

If the file included in `index.php` does not contain a `BaseAction` class, then the included code is executed and entirely managed by the file (previous behavior).

#### **Creating a new BaseAction**

##### **Basic structure**

To create a new action, create a file in:

- `modules/{MODULE}/controllers/{ACTION}.php`
- or `modules/VteCore/controllers/{ACTION}.php` (for global actions)

The class must:

- Extend `BaseAction`
- Implement the `process()` method
- Declare properties with appropriate attributes

```php
<?php

class MyAction extends BaseAction {
    
    protected function process() {
        // Your logic here
        return ['success' => true, 'data' => []];
    }
}

```

#### **Declaring properties**

Class properties can be automatically populated from the request using PHP 8 attributes:

<table id="bkmrk-attribute-source-des"><thead><tr><th>Attribute</th><th>Source</th><th>Description</th></tr></thead><tbody><tr><td>`RequestParam`</td><td>$\_REQUEST</td><td>Parameter from request (GET or POST)</td></tr><tr><td>`RequestRawParam`</td><td>$\_REQUEST</td><td>Raw parameter from request</td></tr><tr><td>`GetParam`</td><td>$\_GET</td><td>Parameter from GET</td></tr><tr><td>`GetRawParam`</td><td>$\_GET</td><td>Raw parameter from GET</td></tr><tr><td>`PostParam`</td><td>$\_POST</td><td>Parameter from POST</td></tr><tr><td>`PostRawParam`</td><td>$\_POST</td><td>Raw parameter from POST</td></tr><tr><td>`CookieParam`</td><td>$\_COOKIE</td><td>Parameter from cookie</td></tr></tbody></table>

#### **Attribute parameters**

- **name**: Parameter name in the request (default: property name)
- **filter**: Filter to apply (e.g. `F::int`, `F::str`, `F::bool`). For more details, see [Basic filters](https://usermanual.vtenext.com/books/developers/page/requesthandler#bkmrk-basic-filters).
- **required**: Whether the parameter is required (default: based on nullable type)
- **default**: Default value if the parameter is not present
- **validation**: Regex or callable to validate the value
- **description**: Parameter description (for documentation)

```php
<?php
class MyAction extends BaseAction {
    
    // Required parameter (not nullable)
    #[RequestParam()]
    protected int $record;
    
    // Optional parameter (nullable)
    #[RequestParam()]
    protected ?string $searchText;
    
    // Parameter with different name
    #[RequestParam(name: 'return_module', filter: F::mod)]
    protected ?string $returnModule;
    
    // Parameter with regex validation
    #[RequestParam(validation: '/^(true|false)$/')]
    protected ?string $isDuplicate;
    
    // Parameter with callable validation
    #[RequestParam(validation: 'is_numeric')]
    protected ?string $amount;
    
    // Parameter with default value
    #[RequestParam(default: 10)]
    protected int $limit;
    
    // Parameter from POST
    #[PostParam()]
    protected ?string $comment;
    
    // Parameter from GET
    #[GetParam()]
    protected ?int $page;
    
    protected function process() {
        // Properties are already populated here
        return [
            'record' => $this->record,
            'search' => $this->searchText,
        ];
    }
}

```

#### **Main methods**

##### **process()**

Main method that contains the action logic. Must return data that will be formatted and sent to the client.

```php
protected abstract function process();
```

##### **validate(?string &amp;$error): bool**

Called before `process()` to validate the request. If it returns `false`, execution stops and an error is sent.

```php
protected function validate(?string &$error): bool {
    if ($this->record <= 0) {
        $error = "Invalid record ID";
        return false;
    }
    return true;
}
```

##### **beforeProcess()**

Called before `process()`, after validation.

```php
protected function beforeProcess(): void {
    // Initialization, logging, etc.
}
```

##### **afterProcess(&amp;$result)**

Called after `process()`, allows modifying the result before sending.

```php
protected function afterProcess(&$result): void {
    // Modify result, logging, etc.
    $result['timestamp'] = time();
}
```

#### **Output**

The output format is automatically determined based on:

1. The class's `$outputFormat` property (if set)
2. The `format` or `_format` parameter in the request
3. The `Accept` header of the request
4. Whether the request is AJAX (default: `json`) or not (default: `html`)

##### **Supported formats**

- **json** - JSON response (automatic for AJAX)
- **html** - HTML response (automatic for standard requests)

You can force a specific format by setting the `$outputFormat` property:

```php
class MyAction extends BaseAction {
    
    protected ?string $outputFormat = 'json';
    
    protected function process() {
        return ['data' => 'Always JSON'];
    }
}
```

Or by using the `setOutputFormat()` method:

```php
class MyAction extends BaseAction {
    
    protected function beforeProcess(): void {
        $this->setOutputFormat('html');
    }
    
    protected function process() {
        return '<h1>HTML Output</h1>';
    }
}
```

##### **JSON response format**

On success:

```json
{
    "success": true,
    "result": { /* data returned by process() */ }
}
```

On error:

```json
{
    "success": false,
    "error": {
        "message": "Error message",
        "code": 400
    }
}
```

##### **Requesting JSON from client**

To request a JSON response, the client can:

- Add the parameter `?format=json` or `?_format=json` to the URL
- Set the header `Accept: application/json`
- Make an AJAX request (automatically detected)

#### **Example**

```php
<?php

use RequestParam;
use PostParam;

class SaveData extends BaseAction {
    
    // We don't specify outputFormat, it will be determined automatically
    
    #[RequestParam()]
    protected ?int $record;
    
    #[PostParam(required: true)]
    protected string $name;
    
    #[PostParam(filter: F::int, default: 1)]
    protected int $status;
    
    #[PostParam()]
    protected ?string $description;
    
    protected function validate(?string &$error): bool {
        if (strlen($this->name) < 3) {
            $error = "Name must be at least 3 characters long";
            return false;
        }
        return true;
    }
    
    protected function beforeProcess(): void {
        // Log the operation
        global $log;
        $log->info("Saving record: " . $this->name);
    }
    
    protected function process() {
        global $adb;
        
        // Save logic
        if ($this->record) {
            // Update logic
        } else {
            // Insert logic
        }
        
        // Return data
        // If the request is AJAX, it will be automatically formatted as JSON
        // Otherwise as HTML
        return [
            'record_id' => $id,
            'message' => 'Save completed'
        ];
    }
    
    protected function afterProcess(&$result): void {
        // Add timestamp to result
        $result['timestamp'] = time();
    }
}

```

#### **Differences from the previous version**

<table id="bkmrk-aspect-before-now-ac"><thead><tr><th>Aspect</th><th>Before</th><th>Now</th></tr></thead><tbody><tr><td>Action resolution</td><td>In index.php</td><td>In IndexRouter.php</td></tr><tr><td>Ajax files</td><td>`{MODULE}Ajax.php` required</td><td>No longer necessary</td></tr><tr><td>Controllers paths</td><td>Did not exist</td><td>`modules/{MODULE}/controllers/`  
`modules/VteCore/controllers/`</td></tr><tr><td>Parameter population</td><td>Manual with $\_REQUEST</td><td>Automatic with PHP attributes</td></tr><tr><td>Validation</td><td>Manual</td><td>validate() method + attribute validation</td></tr><tr><td>Output format</td><td>Manually handled</td><td>Automatic</td></tr><tr><td>Error handling</td><td>Manual</td><td>Automatic with try/catch in processFromIndex()</td></tr></tbody></table>

#### **Best Practices**

- Create new actions as BaseAction classes in `modules/{MODULE}/controllers/`
- Use attributes to declare parameters explicitly
- Always specify the appropriate filter (`F::int`, `F::str`, etc.)
- Implement the `validate()` method for business logic validations
- Use attribute validation for simple validations (regex, callable)
- Let the system automatically determine the output format (JSON/HTML)
- Force `$outputFormat` only when necessary
- Use `beforeProcess()` for initialization and `afterProcess()` for post-processing

# Database best practices

#### **Best practices (all versions)**

<p class="callout warning">**Avoid methods that do automatic html conversion**, like `query_result` and `fetchByAssoc(...)`</p>

<p class="callout warning">**Never concatenate variables (especially coming from request) into the sql string**</p>

<p class="callout success">**Always use the no\_html** versions (`query_result_no_html` or `fetchByAssoc(..., -1, false)`) or the new shortcut methods (see below)</p>

<p class="callout success">**Always use prepared statement** (`pquery`), to avoid SQL injections</p>

<p class="callout success">In `SELECT` statements, **select only the necessary columns**, avoid `*` if you don't need all the columns</p>

<p class="callout success">In `INSERT` statements, **always specify the column names** (to avoid breaking the query in case of new columns added later)</p>

#### **Shortcut methods (version ≥ 26.01)**

New utility methods have been added to easy query execution and retrieval of rows. None of these functions will do any html conversion of the result.

- `rows()`: Iterate over rows:  
      
    ```php
    $res = $adb->pquery("SELECT * FROM table WHERE column = ?", [$param]);
    foreach ($res->rows() as $row) {
      // ... use $row
    }
    ```
    
    This is equivalent of:  
    ```php
    $res = $adb->pquery("SELECT * FROM table WHERE column = ?", [$param]);
    while ($row = $adb->fetchByAssoc($res, -1, false)) {
      // ... use $row
    }
    ```
- `row($index = 0)`: Get a single row:  
      
    ```php
    $res = $adb->pquery("SELECT * FROM table WHERE column = ?", [$param]);
    $row1 = $res->row(1); // get the second row (index starts at 0)
    ```
- `allRows()`: Read all rows (note: may use a lot of memory if there are many rows):  
      
    ```php
    $res = $adb->pquery("SELECT * FROM table WHERE column = ?", [$param]);
    $allrows = $res->allRows();
    ```
- `col($column)`: Return a specific column (by index or by name) from the result set (note: may use a lot of memory if there are many rows):
    
    ```php
    $res = $adb->pquery("SELECT * FROM table WHERE column = ?", [$param]);
    
    $col = $res->col();     // first column
    $col = $res->col(2);    // 3rd column (0-based index)
    $col = $res->col('id'); // "id" column
    ```
- `allCols()`: Return all the columns from the result set (note: may use a lot of memory if there are many rows). The return value is an array, where each key is the name of the column and the value, an array of the values for that column:
    
    ```php
    $res = $adb->pquery("SELECT id, name FROM table WHERE column = ?", [$param]);
    $all = $res->allCols();
    
    /* 
    $all looks like:
    Array (
        [id] => Array
            (
                [0] => 1
                [1] => 6
            )
        [name] => Array
            (
                [0] => first value
                [1] => something else
            )
    )
    */
    ```
- `queryGetAll($sql, $params = [], $offset = 0, $limit = -1)`: Combines `pquery` and `allRows`:
    
    ```php
    // get all rows from query (no parameters)
    $rows = $adb->queryGetAll("SELECT column1, column2 FROM table");
    
    // get all rows from query (single parameter)
    $rows = $adb->queryGetAll("SELECT * FROM table WHERE column = ?", $param);
    
    // with limit, select first 5 rows
    $rows = $adb->queryGetAll("SELECT * FROM table WHERE column = ? AND id < ?", [$param, 6], 0, 5);
    ```
- `queryGetFirst($sql, $params = [])`: Get the first row returned by the query:  
      
    ```php
    // no need to specify LIMIT
    $row = $adb->queryGetFirst("SELECT column1, column2 FROM table");
    ```
- `queryGetFirstValue($sql, $params = [], $column = 0)`: Get a single value from the first row returned:  
      
    ```php
    // first column, if nothing specified
    $column1 = $adb->queryGetFirstValue("SELECT column1, column2 FROM table");
    
    // by name
    $column2 = $adb->queryGetFirstValue("SELECT column1, column2 FROM table", [], 'column2');
    ```
- `queryHasRows($sql, $params = [])`: Return true if the query returns at least one row:  
      
    ```php
    $hasRows = $adb->queryHasRows("SELECT column1 FROM table WHERE id > ?", [10]);
    ```
- `queryGetAllColumns($sql, $params = [], $offset = 0, $limit = -1)`: Combines `pquery` and `allCols`:
    
    ```php
    // get all columns from query (no parameters)
    $cols = $adb->queryGetAllColumns("SELECT column1, column2 FROM table");
    
    // get all columns from query (single parameter)
    $cols = $adb->queryGetAllColumns("SELECT * FROM table WHERE column = ?", $param);
    
    // with limit, select first 5 rows and return them as columns
    $cols = $adb->queryGetAllColumns("SELECT * FROM table WHERE column = ? AND id < ?", [$param, 6], 0, 5);
    ```
- `queryGetFirstColumn($sql, $params = [])`: Get the first column returned by the query:  
      
    ```php
    // no need to specify LIMIT
    $col1 = $adb->queryGetFirstColumn("SELECT column1, column2 FROM table");
    ```

<p class="callout info">In version 26.01, there is still a main source of queries transforming data to html: **reading records**. So all queries reading records to be displayed in ListView, DetailView, EditView, Reports... are still converting to html.   
This behaviour was kept to avoid too many breaks with existing custom code (uitypes, views, presave, ...)</p>

#### **Aliased methods (vtenext ≥ 26.01)**

Some methods in PearDatabase have now new aliases, to better remember that a html conversion can take place:

- `query_result_html` -&gt; alias of `query_result`
- `fetch_array_html` -&gt; alias of `fetch_array`
- `fetchByAssocHtml` -&gt; alias of `fetchByAssoc`, with 3rd parameter set to true
- `fetchByAssocNoHtml` -&gt; alias of `fetchByAssoc`, with 3rd parameter set to false

It is recommended not to use the html methods, but if really necessary, try to use the "html" aliases.

#### **New methods (version ≥ 26.04)**

- `pqueryTimeout(string $sql, ?array $params, int $timeout, bool $dieOnError=false)`: Executes a SELECT statement with a timeout in ms (only for MySql connections). Returns the string `TIMEOUT` if a timeout occured.  
    ```php
    $res = $adb->pqueryTimeout("SELECT * FROM table ORDER BY date_field", null, 3000);
    if ($res === 'TIMEOUT') {
      // took longer than 3s
      // alternative code to execute the query in background
      return;
    else {
      // use $res
    }
    ```

# Escaping

<span>How to properly escape html/js code in Smarty templates and PHP files</span>

# Introduction

Starting with vtenext version 26.01, all variables outputted by Smarty templates (for example `{$VARIABLE}` ) by default have all applicable characters are converted to html `&...;` notation. For instance, the string `"nice & 'smooth' à > è"` is converted to `"nice &amp; &039;smooth&039; &agrave; &gt; &egrave;"`

The conversion is done with the `htmlentities` function, without double encoding existing entities.

The reason of this change is to reduce as much as possible exposure to XSS attacks, since it was extremely difficult to track every possible variable in templates and ensure it was properly escaped.

There are of course some exceptions on the escaping and some edge cases that should be understood. The next chapter presents the rules to follow to write simple and secure code.

# Escaping rules

##### 1. Avoid html code in php

Avoid generating html code directly in php, or outputting it directly. If possible use Smarty templates. Exceptions can be made for very short snippets, see paragraph 3 for how to handle them.

<table border="1" id="bkmrk-" style="border-collapse: collapse; width: 108.929%; height: 419px;"><colgroup><col style="width: 44.4577%;"></col><col style="width: 55.5423%;"></col></colgroup><thead><tr><td class="align-center" colspan="2">**Examples**  
</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><p class="callout success">**Good**</p>

```php
$name = "John & friends";
$smarty->assign("NAME", $name);
```

And in smarty:

```smarty
{* & will be converted to &amp; *}
<div>{$NAME}</div>
```

Will output:

```html
<div>John &amp; friends</div>
```

</td><td style="height: 29.7969px;"><p class="callout danger">**Bad**: html generated in php</p>

```php
$name = "John & friends";
// the "div" will be escaped
$string = "<div>{$name}</div>";
$smarty->assign("TEXT", $string);
```

 And in smarty:

```smarty
{$TEXT}
```

Will output:

```html
&lt;div&gt;John &amp; friends&lt;/div&gt;
```

<p class="callout danger">**Bad**: output via echo</p>

```php
$name = "John & friends";
$string = "<div>{$name}</div>";

// by using echo on the raw string,
// we introduce a XSS if $name comes from user input

echo $string;
```

</td></tr></tbody></table>

##### 2. Reading from database

Avoid using `query_result` and `fetchByAssoc` without the `-1, false` 2nd and 3rd parameters.

This is because the those functions already do html conversion, which is the wrong place to do it, since the result may have to be processed more before being displayed by the browser (or not displayed at all in a browser if the result is for a REST API).

<table border="1" id="bkmrk-examples-good-%C2%A0-%C2%A0-%24r" style="border-collapse: collapse; width: 108.929%; height: 419px;"><colgroup><col style="width: 50.0596%;"></col><col style="width: 50.0596%;"></col></colgroup><thead><tr><td class="align-center" colspan="2">**Examples**  
</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><p class="callout success">**Good**</p>

```
$res = $adb->query("SELECT helpinfo FROM vte_field");

$first = $adb->query_result_no_html($res, 0, 'helpinfo');
```

</td><td style="height: 29.7969px;"><p class="callout danger">**Bad**: helpinfo is converted!</p>

```
$res = $adb->query("SELECT helpinfo FROM vte_field");

$first = $adb->query_result($res, 0, 'helpinfo');
```

</td></tr><tr style="height: 27.7969px;"><td style="height: 27.7969px;"><p class="callout success">**Good**</p>

```
$res = $adb->query("SELECT helpinfo FROM vte_field");

while ($row = $adb->fetchByAssoc($res, -1, false)) {
  // ...
}
```

</td><td style="height: 27.7969px;"><p class="callout danger">**Bad**: helpinfo is converted!</p>

```
$res = $adb->query("SELECT helpinfo FROM vte_field");
while ($row = $adb->fetchByAssoc($res)) {
  // ...
}
```

</td></tr></tbody></table>

##### 3. Generating html in php

Sometimes it's really inevitable to generate html code in php (legacy code, or existing developments...), so in this case, to avoid a double conversion when Smarty processes the template, we should use the `\Vtenext\Types\HtmlString` class to wrap the html string. This class is a simple wrapper around a string, that doesn't get automatically converted by Smarty. Remember, that in this case, all the html conversion **must be done** in php using the `VStr::toHtml` or `VStr::toHtmlAttr` methods.

Example:

```php
// for brevity:
use \Vtenext\Types\HtmlString;

$name = "John Connor";
$url = "index.php?module=aaa&action=".urlencode($_REQUEST['param']); // danger here: user input!

// building the link with manual conversion
$link = "<a href='".VStr::toHtmlAttr($url)."'>".VStr::toHtml($name)."</a>";

// wrapping it in HtmlString
$link = new HtmlString($link);

$smarty->assign("LINK", $link); // this will NOT be escaped!! Be sure all parameters are properly escaped!!
```

There are also 2 utility methods to build html string safely, `HtmlString::build` and `HtmlString::buildSmarty`. Let's see some examples:

```php
// with build(), every instance of ### is replaced with one of the parameters,
// and escaped for html. The resulting string is safe for raw inclusion in templates
$icon = HtmlString::build('<i class="vteicon md-sm" title="###" style="color: white;">settings</i>', [trans('LBL_AREAS_SETTINGS')]);

# even when reading from $_REQUEST, we don't have XSS here
$errStr = HtmlString::build("<font class='error'>Error: ###</font>", RH::r('error_string'));

// buildSmarty takes a smarty template in string form and does the standard conversion:
$tpl = '
  <input class="crmbutton" onclick="myFunction(\'{$module}\', {$recordid}, \'{$entityName}\')" type="button"
  value="{"LBL_LINK_ACTION"|trans}">';

$params = [
	'module' => $currentModule,
	'recordid' => intval($recordid),
	'entityName' => VStr::toJs($entityName, ''), // js stuff must be explicitely escaped
];
$html =  HtmlString::buildSmarty($tpl, $params);

```

##### 4. Assigning to Smarty

Assign the variables normally, without using decode\_html, htmlspecialchars, htmlentities or other conversion functions.

<table border="1" id="bkmrk-examples-good-%C2%A0-%C2%A0-%24s" style="border-collapse: collapse; width: 108.929%; height: 419px;"><colgroup><col style="width: 50.0596%;"></col><col style="width: 50.0596%;"></col></colgroup><thead><tr><td class="align-center" colspan="2">**Examples**  
</td></tr></thead><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><p class="callout success">**Good**</p>

```sma
$string = "my nice text with < and >";
$smarty->assign("VAR", $string);

```

And in smarty:

```smarty
{* < and > will be converted *}
<div>{$VAR}</div>
```

</td><td style="height: 29.7969px;"><p class="callout danger">**Bad**: no need to convert 2 times</p>

```sma
$string = "my nice text with < and >";
$smarty->assign("VAR", htmlentities($string));

```

 And in smarty:

```smarty
{* still work the same, 
since double_encoding is false
but not a good idea anyway *}
<div>{$VAR}</div>
```

 </td></tr></tbody></table>

##### 5. Inside Smarty templates (html)

Output variables as they are, no additional escaping needed in html. For variables inside javascript code (ex: in onclick attributes) see the next paragraph.

##### 6. Inside Smarty templates (javascript)

Javacript code, inside Smarty templates, delimited by &lt;script&gt; tags, has a special handling. Inside these blocks, the default html conversion is not done, instead, the following happens:

1. If the variable to replace begins with `[` or `{`, no escaping is done
2. Otherwise, the variable is escaped with `VStr::toJs` (which does a addslashes)

For examples, if in PHP we have:

```php
$arr = [1,2,3];
$obj = ['name' => 'test'];

// no need of js escaping here
$smarty->assign("STR", "test <tag> 'quote' end");

// this is the preferred way to pass complex types, as they are!
$smarty->assign("ARR", $arr);
$smarty->assign("OBJ", $obj);

// for legacy code it's acceptable to have this
$smarty->assign("JSARR", json_encode($arr));
$smarty->assign("JSOBJ", json_encode($obj));

// AVOID to build js structures with string manipulation in PHP!!
```

then in the template we can use:

```html
<script>
  // both double quotes and single quotes can be used, they are both escaped!
  var str1 = '{$STR}'; // will become test <tag> \'quote\' end
  var str2 = "{$STR}";

  // the preferred way is to use json_encode for complex types
  var arr = {$ARR|json_encode};
  var obj = {$OBJ|json_encode};

  // but in case something is already in json form:
  var arr = {$JSARR}; // this WILL NOT be escaped!
  var obj = {$JSOBJ}; // also this one
  
</script>
```

Outside of `<script>` tags, the standard html conversion is done, so in case of javascript code in attributes (ex: onclick handlers), manual escaping is still necessary, for example:

```smarty
{* using escape in "javascript" mode *}
<span onclick="myFunction('{$PARAM|escape:"javascript"}')">Link</span>

{* using out VStr::toJs method *}
<span onclick="myFunction2('{VStr::toJs($PARAM)}')">Link2 </span>

{* for urls inside js, use "url" mode before the js encoding *}
<span onclick="location.href='index.php?mode={$MODPARAM|escape:"url"|escape:"javascript"}';">Link 3</span>
```

##### 7. Skipping the conversion

Sometimes it's necessary to override the default conversion and output the raw variable as is. This can be achieved in two ways:

1. By using `nofilter`:  
      
    ```smarty
    <span>{$RAW_VAR nofilter}</span>
    ```
2. By using the modifier `rawhtml` (will convert the string to HtmlString, thus avoiding the conversion)  
      
    ```smarty
    <span>{$RAW_VAR|rawhtml}</span>
    ```

##### 8. Special cases: labels

When using labels in templates (ex: `"LBL_SOMETHING"|trans` or `$APP.LBL_SOMETHING`), html chars are converted. Some labels, though, contains html code, and in this case they should be used with the `rawhtml` or `nofilter` modifiers.

##### 9. Special cases: {capture}

When using `{capture}` in templates, the content is saved in a variable. If that variable is then outputted, the content is escaped, which is usually unwanted since the content is html. In this case, the `rawhtml` or `nofilter` modifier must be used.

For example, if the capture block is:

```smarty
{capture assign="content"}
<div> Hello {$FRIEND}</div> {* $FRIEND will be converted to html entities *}
{/capture}
```

It should be used with:

```smarty
<h2>{$content|rawhtml}</h2>
```

or

```smarty
<h2>{$content nofilter}</h2>
```

or

```smarty
{include file="NoLoginMsg.tpl" BODY=$content|rawhtml}
```

# Cheat sheet

Quick reference if you just need to know how to escape stuff!

**Do's and Don'ts**

- Use only `query_result_no_html` and `fetchByAssoc(..., -1, false)` or `fetchByAssocNoHtml`, we don't want that pesky `to_html` function to be called
- Do not generate html strings in php, use templates or `HtmlString::build`
- Do not echo html code in PHP, use Smarty
- Do not use `VStr::toJsAttr` method
- Do not try to build js code from PHP, use .js files or `<script>` tags in templates
- Do not use `html_entity_decode`, `htmlentities`, `htmlspecialchars`, `addslashes`, it's probably not needed (unless you are working on legacy code)
- Do not use `to_html`, `from_html`, `decode_html` , these were always a bad idea

**How to's:**

<table border="1" id="bkmrk-how-do-i-handle...-." style="border-collapse: collapse; width: 100%; height: 681.453px;"><colgroup><col style="width: 9.77354%;"></col><col style="width: 18.9511%;"></col><col style="width: 71.2753%;"></col></colgroup><thead><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**In**</td><td style="height: 29.7969px;">**How do I handle... ?**</td><td style="height: 29.7969px;">**... like this:**</td></tr></thead><tbody><tr style="height: 46.5938px;"><td rowspan="4" style="height: 316.312px;">Smarty, html code

</td><td style="height: 46.5938px;">standard variable</td><td style="height: 46.5938px;">`{$VARIABLE}`</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">variable, but it's a HtmlString</td><td style="height: 46.5938px;">`{$VARIABLE}`</td></tr><tr style="height: 63.3906px;"><td style="height: 63.3906px;">variable, but it's a string and already html</td><td style="height: 63.3906px;">`{$VARIABLE nofilter}`

or

`{$VARIABLE|rawhtml}`

</td></tr><tr style="height: 159.734px;"><td style="height: 159.734px;">`{capture}` blocks</td><td style="height: 159.734px;">```smarty
{capture assign="capname"}
  <div>.... html code {$VARIABLE} </div>
{capture}

{$capname nofilter}
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"></td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 63.375px;"><td rowspan="3" style="height: 122.969px;">Smarty,

inside `<script>`

  
</td><td style="height: 63.375px;">string variable</td><td style="height: 63.375px;">`var myvar = '{$VARIABLE}';`</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">object or array variable</td><td style="height: 29.7969px;">`var mylist = {$VARIABLE|json_encode};`</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">string inside url</td><td style="height: 29.7969px;">`var url = "index.php?module={$VARIABLE|escape:"url"}";`

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 63.3906px;"><td rowspan="2" style="height: 63.3906px;">Smarty,

js in attributes

</td><td style="height: 63.3906px;">string variable</td><td style="height: 63.3906px;">```smarty
{* using escape in "javascript" mode *}
<span onclick="myFunction('{$PARAM|escape:"javascript"}')">Link</span>

{* using out VStr::toJs method *}
<span onclick="myFunction2('{VStr::toJs($PARAM)}')">Link2 </span>
```

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">string in url</td><td style="height: 29.7969px;">```smarty
<span onclick="location.href='index.php?mode={$MODPARAM|escape:"url"|escape:"javascript"};>Link 3</span>
```

</td></tr></tbody></table>

# SDK 2

This manual describes the SDK methods that allow customization of vtenext.

# Include custom php/js/css files

In order to include your custom code (that will be included in every page) you need to register the new file with this call:

```PHP
SDK::setUtil($src);
```

*$src : the php file path and the file name to be included*

To remove the customization:

```PHP
SDK::unsetUtil($src);
```

*$src : the php file path and the file name to be removed (the file will NOT be removed from disk)*

In case of css/js file you can call:

```PHP
Vtiger_Link::addLink($id, $type, 'SDKScript', $file);
```

*$id : module id that register the customization (in this case, SDK, 31)*  
*$type : can be “HEADERCSS” or “HEADERSCRIPT”*  
*$file : the php file path and the file name to be included*

<p class="callout info">**Hooks:**  
include/Webservices/Utils.php  
include/squirrelmail/src/redirect.php  
install/PopulateSeedData.php  
index.php</p>

# Javascript overrides and extensions

Some commonly used Javascript functions can be replaced or extended to change their behavior. To do this, simply create a function that has the same name as the function to be modified with the addition of "\_override" or "\_extension" and the same parameters. The behavior of the two extensions is as follows:

<table border="1" id="bkmrk-funzione_override%28%29-" style="border-collapse: collapse; width: 100%;"><tbody><tr><td style="width: 50%;">FUNCTION\_override()</td><td style="width: 50%;">If present, this function is called instead of the original one. The return value of this function is the return value.</td></tr><tr><td style="width: 50%;">FUNCTION\_extension()</td><td style="width: 50%;">If present, this function is called and if it returns **false** or a value equivalent to false, the original function ends by returning false, while if it returns **true** or an equivalent value, the execution continues in the original function.</td></tr></tbody></table>

The difference is that, in the first case the original function is completely ignored, while in the second, it is possible to decide whether to continue the standard execution or not. This is very convenient in the case of validation functions, usually very long, in which you simply want to add a control, without copying the entire function for small changes.  
The functions that support these extensions are as follows:

<table border="1" id="bkmrk-file-funzioni-includ" style="border-collapse: collapse; width: 100%;"><tbody><tr><td style="width: 50%;">**File**</td><td style="width: 50%;">**Functions**</td></tr><tr><td style="width: 50%;">include/js/general.js</td><td style="width: 50%;">doformValidation  
startCall  
getFormValidate</td></tr><tr><td style="width: 50%;">include/js/Inventory.js</td><td style="width: 50%;"><span style="font-family: DejaVu Sans, Arial, sans-serif;">settotalnoofrows</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">deleteRow</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcTotal</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcProductTotal</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcGrandTotal</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">validateInventory</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">FindDuplicate</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">validateNewTaxType</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">validateTaxes</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">setDiscount</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">callTaxCalc  
</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcCurrentTax</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcGroupTax</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">calcSHTax</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">validateProductDiscounts</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">updatePrices</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">updatePriceValues</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">resetSHandAdjValues</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">moveUpDown</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">InventorySelectAll</span>

<span style="font-family: DejaVu Sans, Arial, sans-serif;">fnAddProductOrServiceRowNew</span>

</td></tr></tbody></table>

# Standard PHP replacement

You can replace the standard php files of the modules, such as DetailView.php, EditView.php and so on through the method:

```PHP
SDK::setFile($module, $file, $newfile);
```

*$module : the name of the module*  
*$file : the value of the "action" parameter to be compared*  
*$newfile: the new php source, without extension and without path*

The *$newfile* must be in the same folder of the module and must be specified without an extension and without a path.

<p class="callout warning">If you want to replace the ListView, you must call setFile twice, once with $file = "ListView" and once with $file = "index".</p>

To remove the customization:

```PHP
SDK::unsetFile($module, $file);
```

*$module : the name of the module*  
*$file : the value of the "action"*

<p class="callout info">**<span style="font-family: DejaVu Sans, Arial, sans-serif;">Hooks:  
</span>**<span style="font-family: DejaVu Sans, Arial, sans-serif;">include/Ajax/CommonAjax.php</span><span style="font-family: DejaVu Sans, Arial, sans-serif;">  
index.php</span></p>

# Inclusion of other files

To associate files or folders to a module, so that they are imported automatically, the following methods are available:

```PHP
SDK::setExtraSrc($module, $src);
```

*$module : the name of the module*  
*$src : the path of the file or folder to be associated*

To delete the association (but not the files themselves) use:

```PHP
SDK::unsetExtraSrc($module, $src);
```

*$module : the name of the module*  
*$src : the path of the file or folder associated*

# Custom Uitypes

You can add new types to the existing ones and manage them completely without changing other code. The procedure for creating a new one is:

1. Create a new custom field with the new type (nnn)
2. Create the files:  
    a. nnn.php in modules/SDK/examples  
    b. nnn.js in modules/SDK/examples  
    c. nnn.tpl in Smarty/templates/modules/SDK/examples  
    These files manage the behavior of the new field depending on the context (list, detail, and so on)
3. Register the new type with the class method SDK::setUitype.

In modules/SDK/examples/VTE-SDK-2.php there are various examples of field creation and uitype registration.  
In modules/SDK/doc/VTE-SDK-2.pdf under **Uitypes List** you can find the list of the main standard uitypes.

```PHP
SDK::setUitype($uitype, $src_php, $src_tpl, $src_js, $type='', $params='');
```

*$uitype : the number of the new type; it must be nnn (the name of the files)*  
*$src\_php: the path of nnn.php*  
*$src\_tpl: the path of nnn.tpl (without Smarty/templates/ at the beginning)*  
*$src\_js : the path of nnn.js*  
*$type : the type in webservice format (‘text’, ‘boolean’, and so on)*  
*$params : not used yet*

To remove the uitype:

```
SDK::unsetUitype($uitype);
```

*$uitype : it is the number of the uitype to remove (the files associated with it will not be deleted)*

<p class="callout warning">We recommend using uitype with a value greater than 2000, to avoid conflicts with the future releases of **vtenext**.</p>

The php script has several variables available, the first one is:

*$sdk\_mode : the views that can be customized for the new uitype (“insert”, “detail”, “edit”, “relatedlist”, “list”, “pdfmaker”, “report”, and so on)*

Depending on the type of $sdk\_mode, various variables can be read and modified.

**detail**  
to manage the display of the field in DetailView  
*INPUT*  
*$module : the current module name*  
*$fieldlabel: the label of the field*  
*$fieldname : the name of the field*  
*$col\_fields: (array) the values of the fields*  
*OUTPUT*  
*$label\_fld\[\] : the label translated*  
*$label\_fld\[\] : the value to be displayed*

**edit**  
to manage the display of the field in EditView  
*INPUT*  
*$module\_name : the current module name*  
*$fieldlabel : the label of the field*  
*$value : the value of the field*  
*OUTPUT*  
*$editview\_label\[\] : the label translated*  
*$fieldvalue\[\] : the value to be displayed*

**relatedlist, list, pdfmaker**  
to manage the display of the field in ListView, RelatedList and PDFMaker  
*INPUT*  
*$sdk\_value : the value of the field*  
*OUTPUT*  
*$value : the value to be displayed*

**report**  
to manage the display of the field in Report  
*INPUT*  
*$sdk\_value : the value of the field*  
*OUTPUT*  
*$fieldvalue : the value to be displayed*

<p class="callout warning">If the value to be displayed from the interface **is formatted differently** than the value saved in the database (e.g. number 1.000,25 which must be saved as 1000.25) then the following methods must also be managed to save the value in the correct format and search for it.</p>

**insert**  
to convert the value to the format to be saved in the database  
*INPUT*  
*$this-&gt;column\_fields : (array) the values of the fields*  
*$fieldname : the name of the field*  
*OUTPUT*  
*$fldvalue : the value to save in the database*

**formatvalue**  
to convert the value coming from the $\_REQUEST into the format saved in the database (used in the new management of Conditional Fields)  
*INPUT*  
*$value : the value of the field*  
*OUTPUT*  
*$value : the value converted to database format*

**querygeneratorsearch**  
to convert the value searched in the lists and filters by the user  
*INPUT AND OUTPUT*  
*$fieldname : the name of the field*  
*$operator : the comparison operator*  
*$value : the value searched*

**customviewsearch**  
to manage the conversion of the value in the popup filters for the reference fields  
*INPUT AND OUTPUT*  
*$tablename : the table of the field*  
*$fieldname : the name of the field*  
*$comparator : the comparison operator*  
*$value : the value searched*

**popupbasicsearch**  
to manage the conversion of the value in the popup search for the reference fields  
*INPUT*  
*$table\_name : the table name*  
*$column\_name : the column name*  
*$search\_string : the value searched*  
*OUTPUT*  
*$where : the condition of the query*  
*e.g. $where = "$table\_name.$column\_name = '".convertToDBFunction($search\_string)."'";*

**popupadvancedsearch**  
to manage the conversion of the value in the advanced popup search for the reference fields  
*INPUT AND OUTPUT*  
*$tab\_col : the table and the column of the field*  
*$srch\_cond : the comparison operator*  
*$srch\_val : the value searched*

**reportsearch**  
to convert the value searched in the reports by the user  
*INPUT AND OUTPUT*  
*$table : the table of the field*  
*$column : the column of the field*  
*$fieldname : the field name*  
*$comparator : the comparison operator*  
*$value : the value searched*

<p class="callout info">**Hooks**  
data/CRMEntity.php  
include/ListView/ListViewController.php  
include/utils/crmv\_utils.php  
include/utils/EditViewUtils.php  
include/utils/DetailViewUtils.php  
include/utils/ListViewUtils.php  
include/utils/SearchUtils.php  
include/QueryGenerator/QueryGenerator.php  
modules/PDFMaker/InventoryPDF.php  
modules/Reports/ReportRun.php  
modules/Users/Users.php  
modules/CustomView/Save.php  
modules/CustomView/CustomView.php  
Smarty/templates/DisplayFieldsReadonly.tpl  
Smarty/templates/DisplayFieldsHidden.tpl  
Smarty/templates/DetailViewFields.tpl  
Smarty/templates/EditViewUI.tpl  
Smarty/templates/DetailViewUI.tpl</p>

# Smarty Custom Templates

You can create your own templates, which replace the standard ones (such as EditView.tpl and so on). The new template is used if $\_REQUEST values of the page meet the requirements. The registration of a new template is done through the method:

```PHP
SDK::setSmartyTemplate($params, $src);
```

*$params : associative array with requirements (see below)*  
*$src : path of the new template*

In the $params variable you can specify a special value ("$NOTNULL$") to indicate that this parameter must exist, with any value. The unspecified parameters are ignored. If the rule to be inserted already exists or is not compatible with the existing ones (it could cause ambiguity for some $\_REQUEST), the insertion fails (and an explanatory message is saved in the log).

To remove the customization:

```PHP
SDK::unsetSmartyTemplate($params, $src = NULL);
```

*$params : associative array with requirements*  
*$src : path of the template (if NULL, it includes all file)*

To completely replace all types of views of a module you need at least 7 rules:

<table border="1" id="bkmrk-%24params-note-array%28-" style="border-collapse: collapse; width: 100%;"><tbody><tr><td style="width: 50%;">**$params**</td><td style="width: 50%;">**Notes**</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’ListView’)</td><td style="width: 50%;">ListView</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’index’)</td><td style="width: 50%;">ListView</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’DetailView’ , ‘record’=&gt;’$NOTNULL$’)</td><td style="width: 50%;">DetailView</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’EditView’ , ‘record’=&gt;’$NOTNULL$’)</td><td style="width: 50%;">EditView (with record in $\_REQUEST)</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’EditView’)</td><td style="width: 50%;">New record</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’EditView’, ‘record’=&gt;’$NOTNULL$’, "isDuplicate"=&gt;"true")</td><td style="width: 50%;">Duplicate record</td></tr><tr><td style="width: 50%;">array(‘module’=&gt;’Leads’, ‘action’=&gt;’LeadsAjax’, ‘record’=&gt;’$NOTNULL$’, 'ajxaction'=&gt;'LOADRELATEDLIST', 'header'=&gt;'Products')</td><td style="width: 50%;">The related list of leads showing the products</td></tr></tbody></table>

<p class="callout warning">If **multiple rules match**, the most specific will be used. For example, if there are 2 rules, one with "$NOTNULL$" and one with the value "Leads" and the request is "Leads", the second rule will be used.</p>

<p class="callout info">**Hooks**  
Smarty\_setup.php</p>

# Popup

Two actions are available for managing popup. You can insert a php script before the query is made to load the data. In addition, it is possible to insert another php script before the data is shown, so you can edit this data or the result when the popup is closed. In the first case, the are two methods available:

```PHP
SDK::setPopupQuery($type, $module, $param, $src, $hidden_rel_fields = '');
```

*$type : “field” or “related” to indicate a standard field or popup opened from a related list*  
*$module: the module in which the popup opens*  
*$param : the name of the field that opens the popup (must be uitype 10) in the case type = "field", otherwise the name of the connected module*  
*$src : the path of php file*  
*$hidden\_rel\_fields : associative array of fields to pass in the popup request like array($urlvalue =&gt; $jscode)*

To remove the customization:

```PHP
SDK::unsetPopupQuery($type, $module, $param, $src);
```

*Same parameters as before*

The following variables are available within the php script:  
*$query : the query that takes the values to show*  
*$sdk\_show\_all\_button : if true it shows the button to cancel the SDK restrictions and show all records*

In the second case there are the following methods:

```PHP
SDK::setPopupReturnFunction($module, $fieldname, $src);
```

*$module : the module containing the field that opens the popup*  
*$fieldname : the name of the field that opens the popup (only uitype 10)*  
*$src : the php file*

To remove the customization:

```PHP
SDK::unsetPopupReturnFunction($module, $fieldname = NULL, $src = NULL);
```

For now the only fields supported are those with uitype 10.

<p class="callout info">**Hooks**  
Popup.php  
include/utils/ListViewUtils.php  
include/ListView/SimpleListView.php</p>

**Esempio**

```PHP
<?php
SDK::setPopupQuery('field','Contacts','account_name','modules/SDK/examples/PopupQuery1.php');
SDK::setPopupQuery('related', 'Contacts', 'Products', 'modules/SDK/examples/contacts/PopupRelQuery.php');

SDK::setPopupReturnFunction('Contacts','vendor_id','modules/SDK/examples/ReturnVendorToContact.php');

// you can set a PopupQquery and a PopupReturnFunction to a field in a table field (VTENEXT 24.08)
SDK::setPopupQuery('field', 'Accounts', 'ml1_f4', 'modules/SDK/examples/Contacts/AccountQuery.php');
// here we have a table field (vcf_3) with an account field (vcf_4) and a contact field (vcf_5), I can view only accounts of rating Market Failed, Project Cancelled and Shutdown and only contacts of these accounts. 
$rel_fields = array('processmaker'=>'jQuery("#processmaker").val()','running_process'=>'jQuery("#running_process").val()');
SDK::setPopupQuery('field', 'Processes', 'vcf_3_vcf_4', 'modules/SDK/examples/Contacts/AccountQuery.php', $rel_fields);
$rel_fields['accountid'] = 'jQuery("#vcf_3_vcf_4_"+VTE.EditView.getTableFieldCurrentRow(this)).val()';
SDK::setPopupQuery('field', 'Processes', 'vcf_3_vcf_5', 'modules/SDK/examples/Contacts/ContactsQuery.php', $rel_fields);

// you can set a PopupReturnFunction to the product or other custom fields in the inventory product block (VTENEXT 26.01)
// view examples in modules/SDK/examples/VTE-SDK-2.php
?>
```

# Presave

You can enter your own script when you press the "Save" button in EditView mode. To register a script use the method:

```PHP
SDK::setPreSave($module, $src);
```

*$module : the name of the module*  
*$src : the path of php file*

To remove the customization:

```PHP
SDK::unsetPreSave($module, $src = NULL);
```

*$module : the name of the module*  
*$src : the path of php file (if NULL, includes all scripts registered for that module)*

The following variables are available within the script:

*$type : the form name (“MassEditSave”, “DetailView”, “EditView”, “createTODO”, “QcEditView”, “ConvertLead”, “createQuickTODO”, “Kanban”)*  
*$values : (array) new values*  
  
For MassEditSave, you can know record that are going to be managed calling getListViewCheck($currentModule);

The following variables can be set:

*$status : (bool) whether or not the submit should be saved*  
*$message: (string) if not empty a popup with the message is shown*  
*$confirm: (bool) if true, a Javascript popup is shown asking for confirmation to continue, showing $message. In this case, $status must not be set.*  
*$focus : (string) in case of error, the element that takes the focus (only if $status = false)*  
*$changes: (array) values to assign to the fields (only if $status = false)*

The $focus and $changes variables are only available when $status is false and $type is one of 'EditView', 'createTodo', 'QcEditView', 'ConvertLead'.

<p class="callout info">**Hooks**  
include/js/general.js  
include/js/KanbanView.js  
modules/Calendar/script.js  
modules/Calendar/wdCalendar/sample.php  
modules/Leads/Leads.js  
modules/Users/Forms.php  
modules/VteCore/KanbanAjax.php  
Smarty/templates/Header.tpl  
Smarty/templates/ComposeEmail.tpl  
Smarty/templates/Popup.tpl</p>

# Advanced query

You can modify the query executed to load the data in ListView, RelatedList and Popup mode in order to limit or extend the visibility of the data. This does not affect Administrator users, who have access to all data. In addition, the module must be set as Private.

Editing the query is done through a custom php function (see below). Only one function of this type can be used in each module. To register the function use:

```PHP
SDK::setAdvancedQuery($module, $func, $src);
```

*$module : the module in which to apply the function (if a function is already registered for the module, the new one is not inserted)*  
*$func : the name of the php function*  
*$src : the php file that contains the function*

To remove the customization:

```PHP
SDK::unsetAdvancedQuery($module);
```

*$module : the name of the module*

The $func function must be defined as follows:

```PHP
<?php

function myFunction($module) {
	// Your code ... 
}
```

*$module : the module that calls the function*

It returns a string:

*“” : (empty string) the query is unchanged*  
*? : (not empty string) this string is added to the query*

<p class="callout info">**Hooks**  
data/CRMEntity.php</p>

# Page Header

You can customize the user icon, the settings icon or the blue bars at the top of the pages of VTE to incorporate new features. To do this, simply extend the method setCustomVars of class VTEPageHeader as follows:

```PHP
SDK::setClass('VTEPageHeader', 'NewPageHeader', 'modules/SDK/src/NewPageHeader.php');
```

The file NewPageHeader.php will have this content:

```PHP
<?php
require_once('include/utils/PageHeader.php');

class NewPageHeader extends VTEPageHeader {
	
	protected function setCustomVars(&$smarty, $options = array()) {
		$overrides = array(

			// HTML code to be put right after the menu bar
			'post_menu_bar' => null,

			// HTML code right after the second bar
			'post_primary_bar' => null,

			// HTML code after the third bar
			'post_secondary_bar' => null,

			// HTML code that replace the standard user icon
			'user_icon' => null,

			// HTML code that replace the standard settings icon
			'settings_icon' => null,
		);
		// assign these values to a smarty variable
		$smarty->assign("HEADER_OVERRIDE", $overrides);
	}
}
```

# Translations

Translations can be customized for each language and module installed. To modify or insert a new translation use the method:

```PHP
SDK::setLanguageEntry($module, $langid, $label, $newlabel);
```

*$module : the module name that contains the string*  
*$langid : the code of the language (e.g. “en\_us”, “it\_it”)*  
*$label : the label (e.g. LBL\_TASK\_TITLE)*  
*$newlabel : the translation of the label*

If the label already exists for the chosen module and language, it will be replaced. As a module you can specify "APP\_STRINGS" to insert a global translation or "ALERT\_ARR" to make the translation available in JavaScript files. To load a string simultaneously in multiple languages, the method is:

```PHP
SDK::setLanguageEntries($module, $label, $strings);
```

*$module : the name of the module*  
*$label : the label*  
*$strings : associative array with the translations (e.g. array(“it\_it”=&gt;str1, …))*

To remove a translation:

```PHP
SDK::deleteLanguageEntry($module, $langid, $label = NULL);
```

*$module : the name of the module*  
*$langid : the code of the language*  
*$label : the label (if NULL, all the strings that match)*

# Fields Visibility

You can change the visibility of the various fields (value of $readonly) and other variables in the different modes (ListView, EditView, and so on) via SDK. To register a new "view" use the method:

```PHP
SDK::addView($module, $src, $mode, $success);
```

*$module : the name of the module to apply the view*  
*$src : the path of php file*  
*$mode : how the rule is applied (see below)*  
*$success: what to do after applying the rule (see below)*

The views defined for each module are applied in the order in which they are registered. When they register, they are added to the queue of views for that module. The $mode variable only makes sense if you change the *$readonly variable and it admits the following values:*

*“constrain” : forces the value of $readonly to take the new value given in the script*  
*“restrict” : changes the value of $readonly only for a more restrictive value (from 1 to 99 or 100, from 99 to 100, not vice versa)*

The $success variable instead can be:

*“continue” : after applying the view, continue with the next*  
*“stop” : if the view returns $success = true, no other rules are executed*

The following variables are available within the scripts:

*$sdk\_mode : one of “” (create), “edit”, “detail”, “popup\_query”, “list\_related\_query”, “popup”, “related”, “list”, “mass\_edit”*  
*$readonly : the readonly value for the current field (1, 99, 100)*  
*$col\_fields: values of fields, only for $sdk\_mode = “edit”, “detail” e “”*  
*$fieldname or $fieldName: the name of the current field*  
*$current\_user: the current user*

And you can set the $success variable with true or false.  
Depending on the mode (ListView, EditView, and so on) there are different ways to edit the queries and different variables available.

<table border="1" id="bkmrk-modalit%C3%A0-valore-di-" style="border-collapse: collapse; width: 100%; height: 276px;"><tbody><tr style="height: 30px;"><td style="width: 25%; height: 30px;">**Mode**</td><td style="width: 25%; height: 30px;">**Value of $sdk\_mode**</td><td style="width: 25%; height: 30px;">**Available variables**</td><td style="width: 25%; height: 30px;">**Notes**</td></tr><tr style="height: 31px;"><td style="width: 25%; height: 31px;">CreateView</td><td style="width: 25%; height: 31px;">*""*</td><td rowspan="4" style="width: 25%; height: 101px;">*$col\_fields*  
*$current\_user*  
*$fieldname*  
*$readonly*  
*$success*</td><td style="width: 25%; height: 31px;">1</td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">EditView</td><td style="width: 25%; height: 30px;">*"edit"*</td><td style="width: 25%; height: 30px;"> </td></tr><tr style="height: 10px;"><td style="width: 25%; height: 10px;">DetailView</td><td style="width: 25%; height: 10px;">*"detail"*</td><td style="width: 25%; height: 10px;"> </td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">MassEdit</td><td style="width: 25%; height: 30px;">*"mass\_edit"*</td><td style="width: 25%; height: 30px;"> </td></tr><tr style="height: 25px;"><td style="width: 25%; height: 25px;">PopupQuery</td><td style="width: 25%; height: 25px;">*"popup\_query"*</td><td style="width: 25%; height: 25px;">*$sdk\_columns*  
*$success*</td><td style="width: 25%; height: 25px;">2</td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">List/RelatedQuery</td><td style="width: 25%; height: 30px;">*"list\_related\_query"*</td><td style="width: 25%; height: 30px;">*$sdk\_columns*  
*$success*</td><td style="width: 25%; height: 30px;"> </td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">Popup</td><td style="width: 25%; height: 30px;">*"popup"*</td><td rowspan="3" style="width: 25%; height: 90px;">*$current\_user*  
*$fieldname*  
*$sdk\_columnnames*  
*$sdk\_columnvalues*  
*$readonly*  
*$success*</td><td style="width: 25%; height: 30px;">3</td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">Related</td><td style="width: 25%; height: 30px;">*"related"*</td><td style="width: 25%; height: 30px;">4</td></tr><tr style="height: 30px;"><td style="width: 25%; height: 30px;">List</td><td style="width: 25%; height: 30px;">*"list"*</td><td style="width: 25%; height: 30px;"> </td></tr></tbody></table>

Notes:

1. In these modes the values of the fields are in $col\_fields\[nameOfTheField\]
2. These modes are used to modify the query so that additional fields can be picked up. In $sdk\_columns variable there are the database columns to add to the query. Include then the php file “modules/SDK/AddColumnsToQueryView.php”
3. To get values from other fields, specified in the PopupQuery and ListRelatedQuery modes, write them in the $sdk\_columnnames variable, include the php file “modules/SDK/GetFieldsFromQueryView.php” and then take them from $sdk\_columnvalues
4. In the case of the related "Activity history", only the variables $recordId and $readonly are available and apply to the entire row, not to the single field.

To remove the view:

```PHP
SDK::deleteView($module, $src);
```

*$module : the name of the module*  
*$src : the path of php file*

<p class="callout info">**Hooks**  
include/ListView/ListViewController.php  
include/utils/DetailViewUtils.php  
include/utils/EditViewUtils.php  
include/utils/ListViewUtils.php  
include/QueryGenerator/QueryGenerator.php  
Popup.php</p>

# Home Blocks

New blocks can be added to the home of VTE via SDK. The blocks cannot be deleted from the interface. The method for creating a new block is:

```PHP
SDK::setHomeIframe($size, $url, $title, $userid = null, $useframe = true);
```

*$size : the horizontal size of the block (from 1 to 4)*  
*$url : the address to be shown within the block. It can also have a protocol at the beginning (e.g. http://www.mysite.com/file)*  
*$title : the label of the block (it can be translated via API)*  
*$userid : array containing the ids of the users who can see the block. If you leave null, the block is visible to all users*  
*$useframe: if true the content will be inside an &lt;iframe&gt; otherwise the file is included directly*

Users created later will see all previously registered blocks.  
Block cancellation is possible via 2 methods:

```PHP
SDK::unsetHomeIframe($stuffid);
```

*$stuffid : the id of the block*

```PHP
SDK::unsetHomeIframeByUrl($url);
```

*$url : the url of the block*

Blocks are removed for all users.

<p class="callout info">**Hooks**  
modules/Home/HomestuffAjax.php  
modules/Home/HomeWidgetBlockList.php  
modules/Home/HomeBlock.php  
modules/Home/Homestuff.js  
modules/Users/Save.php  
Smarty/templates/Home/MainHomeBlock.tpl</p>

# Custom Buttons

Buttons can be added under the main menu. To insert a new button use the following method:

```PHP
SDK::setMenuButton($type, $title, $onclick, $image='', $module='', $action='', $condition = '');
```

*$type : the type of button, it can be 'fixed' or 'contextual'; in the first case the button appears on the left and is always visible, in the second case the button is inserted on the right and will be visible only in the chosen module and for the chosen action.*  
*$title : the label of the button*  
*$onclick : the javascript code to execute. It is NOT possible to use double quotes! (“)*  
*$image : the button image. It must be specified without path and reside in themes/softed/images folder even in the smallest version (e.g. img.png e img\_min.png)*  
*$module : if type = ‘contestual’, the module in which the button is visible*  
*$action : if type = ‘contestual’, the action (request action) in which the button is visible*  
*$condition : string like FunctionName:PathPhp representing a function (in the PathPhp file) to be called before showing the button. If it returns false, the button is not shown. The function has only one parameter of type reference to an array with the information of the button.*

To remove the button use:

```PHP
SDK::unsetMenuButton($type, $id);
```

*$type : the type of the button*  
*$id : the id of the button*

<p class="callout info">**Hooks**  
Smarty/templates/Buttons\_List.tpl</p>

# Transitions Manager

You can change the selection options for the picklists managed by the transitions manager, as well as add messages to the "State manager" block to the right of the record detail. To register this functionality use the method:

```PHP
SDK::setTransition($module, $fieldname, $file, $function);
```

*$module : the name of the module to handle*  
*$fieldname : the name of the field managed by transition*  
*$file : the path of the php file that contains the function to call*  
*$function : the function to call*

To remove the customization:

```PHP
SDK::unsetTransition($module, $fieldname);
```

The function called by the transitions manager has the following format:

```PHP
<?php

function myFunction($module, $fieldname, $record, $status, $values) {
	// Your code ...
}
```

*$module : the current module*  
*$fieldname : the name of the field managed by transition*  
*$record : the current record*  
*$status : the value of the field managed by transition of the current record*  
*$values : array of admissible values for the status*

It must return null if you do not want to change the transitions manager behavior or an array with the following format:

*array(*  
*'values' =&gt; array(..) // array of admissible values for the status*  
*'message' =&gt; '' // html code to be included under the transitions manager block*  
*);*

<p class="callout info">**Hooks**  
modules/Transitions/Transitions.php  
modules/Transitions/Statusblock.php  
Smarty/templates/modules/Transitions/StatusBlock.tpl</p>

# PdfMaker Custom Functions

Custom functions can be added in the PDFMaker module. To insert one use:

```PHP
SDK::setPDFCustomFunction($label, $name, $params);
```

*$label: the label of the function (it is translated into the PDFMaker module)*  
*$name: the name of the function*  
*$params: array with the names of the function parameters*

<p class="callout warning">Registered functions must be saved in php files in modules/PDFMaker/functions/ to be used by the PDFMaker module</p>

To remove the function:

```PHP
SDK::unsetPDFCustomFunction($name);
```

*$name: the name of the function*

# Custom Folders and Reports

Custom folders can be created using the following API.

```PHP
SDK::setReportFolder($name, $description);
```

*$name : the name of the folder*  
*$description : the description of the folder*

The created folder will be on the reports page. It will be visible to all users and cannot be changed. The name and description can be translated with the translation API.

Folders created via API can be deleted with:

```PHP
SDK::unsetReportFolder($name, $delreports = true);
```

*$name : the name of the folder to be deleted*  
*$delreports : if true it also deletes all reports (created via API) in that folder (files are not deleted)*

Inside the folders you can insert customized reports with the following statement:

```PHP
SDK::setReport($name, $description, $foldername, $reportrun, $class, $jsfunction = '');
```

*$name : the name of the report (it can be translated via API)*  
*$description : the description of the report (it can be translated via API)*  
*$foldername : the name of the folder created via API where to insert the report*  
*$reportrun : the path of the php file that contains the class that generates the report*  
*$class : the name of the class that handles the report*  
*$jsfunction : the name of the javascript function to run when the "Generate Report" button is pressed*

The report created via API can be deleted with:

```PHP
SDK::unsetReport($name);
```

*$name : the name of the report to delete (files are not deleted)*

The class specified in $class must follow the following structure:

```PHP
<?php
require_once('modules/Reports/ReportRun.php');

class ReportRunAccounts extends ReportRun {
	
	var $enableExportPdf = true;
	var $enableExportXls = true;
	var $enablePrint = true;
	var $hideParamsBlock = true;
	
	function __construct($reportid) {
		$this->reports = Reports::getInstance($reportid); // crmv@172034
		$this->reportid = $reportid;
		$this->primarymodule = 'Accounts';
		$this->reporttype = '';
		$this->reportname = 'Account con sito';
		$this->reportlabel = getTranslatedString($this->reportname, 'Reports');
	}
	
	function getSDKBlock() {
		global $mod_strings;
		$sdkblock = '<p><h2>Questo report mostra le aziende con sito</h2></p>';
		// here I can also add custom inputs to filter the report or any html I need
		return $sdkblock;
	}
	
	// overridden, always hide the summary tab
	function hasSummary() {
		return false;
	}
	
	// overridden, always show the total tab
	function hasTotals() {
		return true;
	}
	
	// generate the report
	function GenerateReport($outputformat = "", $filterlist = null, $directOutput=false) {
		global $adb;
		
		// compatibility, please use set them with the proper methods
		if (!empty($outputformat)) {
			$format = "HTML";
			$tab = "MAIN";
		
			if (strpos($outputformat, 'HTML') !== false) $format = "HTML";
			if (strpos($outputformat, 'PRINT') !== false) $format = "PRINT";
			if (strpos($outputformat, 'PDF') !== false) $format = "PDF";
			if (strpos($outputformat, 'XLS') !== false) $format = "XLS";
			if (strpos($outputformat, 'JSON') !== false) $format = "JSON";
			if (strpos($outputformat, 'CV') !== false) $format = "NULL";
			
			if (strpos($outputformat, 'COUNT') !== false) $tab = "COUNT";
			if (strpos($outputformat, 'TOTAL') !== false) $tab = "TOTAL";
			if (strpos($outputformat, 'CV') !== false) $tab = "CV";
			
			$this->setOutputFormat($format, $directOutput);
			$this->setReportTab($tab);
		} else {
			$format = $this->outputFormat;
			$tab = $this->reportTab;
		}
		
		$format = $this->outputFormat;
		$direct = $this->directOutput;
		$tab = $this->reportTab;

		// prepare the output class
		$output = $this->getOutputClass();
		$output->clearAll();
		
		$return_data = array();
	
		
		if ($tab == 'COUNT' && $this->hasSummary()) {

			// no summary for this custom report
			
		} elseif ($tab == 'CV') {
		
			// no customview for this report
		
		} elseif ($tab == 'MAIN') {

			$sSQL = $this->getReportQuery($outputformat, $filterlist);

			$result = $adb->query($sSQL);
			$this->total_count = $adb->num_rows($result);
			
			$error_msg = $adb->database->ErrorMsg();
			if(!$result && $error_msg!=''){
				// Performance Optimization: If direct output is requried
				if($direct) {
					echo getTranslatedString('LBL_REPORT_GENERATION_FAILED', 'Reports') . "<br>" . $error_msg;
					$error_msg = false;
				}
				// END
				return $error_msg;
			}
			
			if($result) {
			
				$this->generateHeader($result, $output);
			
				while ($row = $adb->fetchByAssoc($result)) {
					$colcount = count($row);
					foreach ($row as $column => $value) {
						$cell = array(
							'value' => $value,
							'column' => $column,
							'class' => 'rptData',
						);
						$output->addCell($cell);
					}
					$output->endCurrentRow();
					
				}
				
				$output->countTotal = $this->total_count;
				$output->countFiltered = $this->total_count;
				
				if ($format == 'XLS') {
					$head = $output->getSimpleHeaderArray();
					$data = $output->getSimpleDataArray();
					foreach ($data as $row) {
						$return_data[] = array_combine($head, $row);
					}
				} else {
					$return_data[] = $output->output(!$direct);
					$return_data[] = $this->total_count;
					$return_data[] = $sSQL;
					$return_data[] = $colcount;
				}
				
			}

		} elseif ($tab == "TOTAL" && $this->hasTotals()) {
			
			$output->addHeader(array('column' => 'fieldname', 'label' => getTranslatedString('Totals')));
			$output->addHeader(array('column' => 'sum', 'label' => getTranslatedString('SUM')));
			$output->addHeader(array('column' => 'avg', 'label' => getTranslatedString('AVG')));
			$output->addHeader(array('column' => 'min', 'label' => getTranslatedString('MIN')));
			$output->addHeader(array('column' => 'max', 'label' => getTranslatedString('MAX')));
			
			// fixed totals
			$rows = array(
				array(
					array('column'=> 'fieldname', 'value' => 'Fatturato totale', 'class' => 'rptData'),
					array('column'=> 'sum', 'value' => 2000, 'class' => 'rptTotal'),
					array('column'=> 'avg', 'value' => null, 'class' => 'rptTotal'),	// not used
					array('column'=> 'min', 'value' => 850, 'class' => 'rptTotal'),
					array('column'=> 'max', 'value' => null, 'class' => 'rptTotal'),	// not used
				),
			);
			
			// add them to the output class
			foreach ($rows as $row) {
				foreach ($row as $cell) {
					$output->addCell($cell);
				}
				$output->endCurrentRow();
			}
			
			// format for xls or html
			if ($format == "XLS") {
				
				// change the output array to match the expected format for XLS export
				$return_data = array();
				$data = $output->getSimpleDataArray();
				$fieldName = '';
				foreach ($data as $row) {
					$nrow = array();
					foreach ($row as $key => $value) {
						if ($key == 'fieldname') {
							$fieldName = $value;
							continue;
						}
						$klabel = $fieldName.'_'.strtoupper($key);
						$nrow[$klabel] = $value;
					}
					$return_data[] = $nrow;
				}
			
			} else {
				$return_data = $output->output(!$direct);
			}

		}
		
		return $return_data;
	}
	
	// generate a fixed header for the report
	function generateHeader($result, $output, $options = array()) {
		global $adb, $table_prefix;
		
		$module = 'Accounts';
		$tabid = getTabid($module);
		$count = $adb->num_fields($result);

		for ($x=0; $x<$count; ++$x) {
			$fld = $adb->field_name($result, $x);
			
			// get the field label from the column (if possible)
			$res = $adb->pquery("SELECT fieldlabel FROM {$table_prefix}_field WHERE columnname = ? and tabid = ?", array($fld->name, $tabid));
			if ($res && $adb->num_rows($res) > 0) {
				$fieldlabel = $adb->query_result_no_html($res, 0, 'fieldlabel');
				$headerLabel =  getTranslatedString($fieldlabel, $module);
			} else {
				$headerLabel = $fld->name;
			}
			$hcell = array(
				'column' => $fld->name,
				'label' => $headerLabel,
				'orderable' => false,
				'searchable' => false,
			);
			$output->addHeader($hcell);
		}
		
	}
	
	// generate the report query
	function getReportQuery($outputformat, $filterlist) {
		global $table_prefix;
		$query = 'SELECT accountid, accountname, website, phone FROM '.$table_prefix.'_account WHERE website <> "" ';
		return $query;
	}
}

```

The javascript function specified in $jsfunction must already be declared and return a string to be added to the request.

```JavaScript
function preRunReport(id) {
	var params = "";
	var select = getObj('picklist1’);

	if (select) {
		params += "&picklist1="+select.options[select.selectedIndex].value;
	}

	var selectuser = getObj('picklist2’);
	if (selectuser) {
		params += "&picklist2="+selectuser.options[selectuser.selectedIndex].value;
	}

	return params;
}
```

<p class="callout warning">When updating a VTE to version 16.09 (or later), some features of the customized reports must be verified.</p>

<p class="callout warning">One of the changed parts is the management of **time filters**, which previously could be modified by extending the getPrimaryStdFilterHTML and getSecondaryStdFilterHTML methods. To obtain the same result, you need to extend the getStdFilterFields function which returns an array of available fields, for example:</p>

```PHP
<?php
function getStdFilterFields() {
	// See the method Reports::getStdFilterFields for the standard implementation
	
	// this example just loads standard fields for the Potential module
	$list = $this->reports->getStdFiltersFieldsListForChain(0, array('Potentials'));
	
	return $list;
}
```

When the report is generated the variable $this-&gt;stdfilters contains the time filter to be used. If the report query is completely customized, the generation of the time filter must also be managed manually, for example using a method like this:

```PHP
<?php
function addStdFilters() {
	global $current_user;
	$sql = '';
	
	if (is_array($this->stdfilters)) {
		foreach ($this->stdfilters as $flt) {
			if ($flt['fieldid'] > 0) {
				// get field informations
				$finfo = $this->getFieldInfoById($flt['fieldid']);
				$table = $finfo['tablename'];
				$qgen = QueryGenerator::getInstance($finfo['module'], $current_user);
				$operator = 'BETWEEN';
				if ($flt['value'] == 'custom') {
					$value = array($flt['startdate'], $flt['enddate']);
				} else {
					$cv = CRMEntity::getInstance('CustomView');
					$value = $cv->getDateforStdFilterBytype($flt['value']);
				}
				// adjust for timezone
				$value[0] = $this->fixDateTimeValue(
					$qgen, $finfo['fieldname'], $value[0]
				);
				$value[1] = $this→fixDateTimeValue(
					$qgen, $finfo['fieldname'], $value[1], false
				);
				// add the condition
				$sql .= ' AND '.$table.'.'.$finfo['columnname'].' '.
					$operator.' '.$value[0].' AND '.$value[1];
			}
		}
	}
	
	return $sql;
}
```

<p class="callout info">**Hooks**  
modules/Reports/Listview.php  
modules/SaveAndRun.php  
modules/Reports/Reports.php  
modules/Reports/CreatePDF.php  
modules/Reports/CreateXL.php  
modules/Reports/PrintReport.php  
Smarty/templates/ReportContents.tpl  
Smarty/templates/ReportRunContents.tpl  
Smarty/templates/ReportRun.tpl</p>

# Turbolift Counter

When changing the standard extraction criteria of a related list, for example using another method or redefining it by extending the class that contains it, the counter of the number of the linked records visible in the Turbolift (the right column with the list of modules in record detail) may no longer be valid. In this case, therefore, a method that returns the correct count must be defined through the following API.

```PHP
SDK::setTurboliftCount($relation_id, $method);
```

$relation\_id : relation id (table vte\_relatedlists)  
$method : the method name that returns the correct count

<p class="callout warning">The method must be implemented in the class that contains the relation (e.g. in the Contact relationship connected to a Company we mean the Accounts class or any extensions to it)</p>

To remove the customization:

```PHP
SDK::unsetTurboliftCount($relation_id);
```

<p class="callout info">The defined method can be the related list method (column **name** in vte\_relatedlists) or a custom method that returns an integer.</p>

# SDK Processes

# Process log

The way of viewing the logs has changed in the various **vtenext** releases and is summarized as follows.

- **vtenext 16.09**: Logs can be consulted by accessing the filesystem in the folder logs/ProcessEngine
- **vtenext 18.X**: In Settings → Business Process Manager → ProcessManager the button 'LOG' will be available once the following script has been executed:

```PHP
<?php
require_once('include/utils/VTEProperties.php');
$VP = VTEProperties::getInstance();
$VP->set('settings.process_manager.show_logs_button', 1);
```

<p class="callout info">The file 01.log is initially created. When the file exceeds the 5MB, the file 02.log will be created and so on.</p>

- **vtenext 19.10**: Logs are available in Settings → Other Settings → System Logs

# Import Processes with a script

From version 18.05 (rev. 1696) it is possible to import processes previously exported in the format vtebpmn (diagram + configuration) or bpmn (diagram only) with php scripts using the importFile method of the ProcessMakerUtils class.  
The importFile method takes in the first parameter the path of the file to be installed (.vtebpmn / .bpmn) and in the second one a Boolean value (true/false) depending on whether you want to automatically activate the process or not.  
This method is useful for installing processes at the end of the installation of a new module: just include the process file in the installation zip and execute the code in the case 'postinstall' of the method vtlib\_handler of the module class.

Example

```php
require_once('modules/Settings/ProcessMaker/ProcessMakerUtils.php');
$PMUtils = ProcessMakerUtils::getInstance();
$PMUtils->importFile('PATH_FILE/Process1.vtebpmn',true);
$PMUtils->importFile('PATH_FILE/DiagramProcess2.bpmn',false);
```

# SDK

You can add custom functions to processes, click [here](https://usermanual.vtenext.com/books/old-business-process-manager-manual/chapter/sdk) for more details.

# Webservice REST

These Web services allow you to do HTTP requests to the specified endpoints (VTE\_URL/restapi/v1/vtews/METHOD\_NAME).

The requests need POST method, basic authentication (you must set ‘Authentication basic’ into the header which is calculated through a function based on username\* and password\*) and their relative parameters.  
Each one method will give a JSON response with a status and the data or in case of error an error code and its relative message.

**Notes:**  
• The “id” parameter is always specified as ‘moduleid\* x recordid’ (ex: 2x313)   
 \*: You can get the the moduleid from webservice ‘describe’ method (idPrefix)

**Headers:**   
• Authorization: Basic base64\_encode("username\*:password\*")  
• Content-Type : application/json  
  
username\* (VTE) password\* (User preferences’ Webservice Access Key)

Authorization Example:  
‘Basic ZmVkZXJpY28ucGVybGluOjB5cm5MRjNhS2RhMDZ2c3E=‘

  
**Requirements for Apache2:** • Activate apache rewrite module  
• Set AllowOverride All into the virtualhost file to the vte path folder.   
Example:

```
<Directory /var/www/html/VTE_FOLDER>
Options -Indexes +FollowSymLinks +MultiViews
AllowOverride All
Require all granted
</Directory>
```

**Requirements for Nginx + PHP FPM** As Nginx does not handle htaccess file, some special rewrites are needed in order to handle WS requests.  
Below an example of nginx conf file:

```
server {
	listen 80;

	root /var/www/vtenext/;
	
	autoindex off;
	
	index index.php index.html index.htm;

	# Make site accessible from http://localhost/
	
	server_name localhost;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to index.html
		try_files $uri $uri/ index.html;
	}

	# deny access to .htaccess files, if Apache's document root
	# concurs with nginx's one
	location ~ /\.ht {
		deny all;
	}
	
	# special folder handling, since .htaccess files are not supported
	
	# REST API
	
	location /restapi/v1 {
		rewrite ^(.+)/vtews/(.*)$ /restapi/v1/index.php/vtews/$2;
	}
    
    # PORTAL

	location ~ /portal/v2/(?!public) {
		rewrite ^/portal/v2/(.*)$ /portal/v2/public/$1;
	}
	
	# STORAGE
	
	location /storage {
		deny all;
		
		location /storage/uploads_emails_ {
			allow all;
		}
		
		location /storage/images_uploaded/ {
			allow all;
		}
		
		location ~* /storage/logo/.+\.(jpg|jpeg|png|gif)$ {
			allow all;
		}
		
		rewrite ^/storage/(.*)$ /getStorage.php?file=$1;
	}
	
	# PROTECTED FOLDERS
	
	location /logs {
		deny all;
	}
	
	location /plugins/dataimporter {
		deny all;
	}
	
	location /cache/sys {
		deny all;
	}
	
	location /cache/pdfmaker {
		deny all;
	}
	
	location /cache/import {
		deny all;
	}
	
	location /cache/session {
		deny all;
	}
	
	location /cache/pdf {
		deny all;
	}
	
	location /dataimport {
		deny all;
	}
	
	location /modules/VteSync/VteSyncLib/storage {
		deny all;
	}
	
	location /modules/Messages/src/attachment_tnef/plugins/attachment_tnef/class/ {
		deny all;
	}
	
	# SMARTOPTIMIZER
	
	# disabled by default, enable it if needed
	#location ~* \.(gif|jpe?g|png|swf|css|js|html?|xml|txt|ico)$ {
	#	rewrite ^(.*)$ /smartoptimizer/?$1;
	#}
    
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
	location ~ [^/]\.php(/|$) {
		include snippets/fastcgi-php.conf;
		
		# With php cgi alone:
		#fastcgi_pass 127.0.0.1:9000;
		
		# With php fpm:
		fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}
}

```

**Web Service Methods:**

<div id="bkmrk-name-description-par"><table style="width:100%;"><colgroup> <col style="width:138px;"></col> <col style="width:162px;"></col> <col style="width:143px;"></col> <col style="width:201px;"></col> </colgroup><tbody><tr><td style="width:24.1975%;">**Name**</td><td style="width:22.4691%;">**Description**</td><td style="width:22.0988%;">**Parameters**</td><td style="width:31.1111%;">**Response**</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**create**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Create a record of the specified module</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">elementType (String)element (Encoded)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return all fields and values of the created record</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/create

**Parameters**:   
{"elementType":"Accounts", "element":"{\\"accountname\\":\\"account1\\", \\"assigned\_user\_id\\":\\"19x1\\"}"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;">**update**</td><td style="width:22.4691%;">Update the specified record</td><td style="width:22.0988%;">id (String)columns (Encoded)</td><td style="width:31.1111%;">Return all fields and values of the updated record</td></tr><tr><td colspan="3" style="width:75.679%;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/update

**Parameters**:   
{"id":"3x310", "columns":"{\\"accountname\\":\\"test fede postman\\"}"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**revise**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Update the specified record. It’s different only for parameters, the result is the same</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">element (Encoded)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return all fields and updated values</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/revise

**Parameters**:

{"element":"{\\"id\\":\\"3x27\\",\\"employees\\":\\"5\\",\\"industry\\":\\"Banking\\"}"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**retrieve**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">Illustrate the fields of the specified record and their relative values</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;">id (String)</td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return all fields and values of the specified record</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#ffffff;background:#ffffff;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/retrieve

**Parameters**:   
{"id":"3x310"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**retrieveinventory**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Illustrate the fields of the specified inventory record, their relative values and the product block’s information</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">id (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return all fields and values of the specified record and product’s block information</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex:

**Url**: VTE\_URL/restapi/v1/vtews/retrieveinventory

**Parameters**:

{"id":"16x104"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**delete**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">Delete the specified record</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;">id (String)</td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return the request status (successful or not)</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#ffffff;background:#ffffff;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/delete

**Parameters**:   
{"id":"3x306"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**query**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Execute a query and return the result’s rows</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">query (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return all rows of the executed query</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/query

**Parameters**:

{"query":"SELECT \* FROM Accounts WHERE accountname like '%vte%';"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**listtypes**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">Describe each one module which contains uitype of the specified format</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;">fieldTypeList (Encoded)</td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return module information of the specified fieldtypes</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#ffffff;background:#ffffff;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/listtypes

**Parameters**:

{"fieldTypeList":"\[\\"integer\\",\\"file\\"\]"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**describe**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Describe the specified module and their relative fields</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">elementType (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return all module information and its fields properties(no hidden fields)</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/describe

**Parameters**:

{"elementType":"Accounts"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**describeall**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">The describeall method is different from describe one because it shows hidden fields too</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;">ElementType (Encoded)</td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return all module information and its fields properties(hidden fields too)</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#ffffff;background:#ffffff;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/describeall

**Parameters**:

{"elementType":"Accounts"}

</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**getlabels**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Return all labels and translated ones of the specified module,language</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">username (String)language (String)module (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return labels and translated ones of the specified module, language</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex:**Url**: VTE\_URL/restapi/v1/vtews/getlabels

**Parameters**:

{"username":"admin", "language":"it\_it", "module":"Accounts"}

</td></tr><tr><td style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**getlangs**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">Return all languages installed into the CRM</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;"> </td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return all CRM languages</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**loginpwd**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Return user webservice access key if username and password are valid</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">username (String)password (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return user webservice access key</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex:**Url**: VTE\_URL/restapi/v1/vtews/loginpwd

**Parameters**:

{"username":"admin","password":"123456789"}

</td></tr><tr><td style="width:24.1975%;background-color:#ffffff;background:#ffffff;">**getmenulist**</td><td style="width:22.4691%;background-color:#ffffff;background:#ffffff;">Return all modules information (visibility, tabid, name, sequence, ...)</td><td style="width:22.0988%;background-color:#ffffff;background:#ffffff;"> </td><td style="width:31.1111%;background-color:#ffffff;background:#ffffff;">Return modules information and properties</td></tr><tr><td rowspan="2" style="width:24.1975%;background-color:#e0efd4;background:#e0efd4;">**oldoquery**</td><td style="width:22.4691%;background-color:#e0efd4;background:#e0efd4;">Return records that contain the searched value into specified module’s fields</td><td style="width:22.0988%;background-color:#e0efd4;background:#e0efd4;">module (String)search\_fields (Encoded)search\_value (String)</td><td style="width:31.1111%;background-color:#e0efd4;background:#e0efd4;">Return some record basic information where searched value is contained into the specified module’s fields</td></tr><tr><td colspan="3" style="width:75.679%;background-color:#e0efd4;background:#e0efd4;">Ex: **Url**: VTE\_URL/restapi/v1/vtews/oldoquery

**Parameters**:

{"module":"Accounts", "search\_fields":"\[\\"accountname\\", \\"website\\"\]", "search\_value":"vtenext"}

</td></tr></tbody></table>

</div>Technical documentation for SDK is available [here](https://usermanual.vtenext.com/books/developers/page/rest-webservice-methods).

# Portal v2

#### Theme documentation

---

[https://adminlte.io/docs/3.2/](https://adminlte.io/docs/3.2/)

[https://github.com/ColorlibHQ/AdminLTE](https://github.com/ColorlibHQ/AdminLTE)

[https://adminlte.io/themes/v3/](https://adminlte.io/themes/v3/)

#### Webservice REST

---

**Handler**: include/Webservices/CustomerPortal.php

**Headers:**  
• Authorization: Basic base64\_encode("username:accesskey")  
• Portal-Session-Id: the parameter is optional and is returned by the "portal.login". Use in subsequent calls to optimize portal performance.  
• Content-Type: application/json

<table id="bkmrk-nome-handler-metodo-" style="width:100%;"><tbody><tr><th class="align-left" style="width:9.38277%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Name</span></span>**</th><th class="align-left" style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Parameters</span></span>**</th><th class="align-left" style="width:15.2469%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</th></tr><tr><td style="width:9.38277%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">portal.info</span></span></td><td style="width:8.08637%;"> </td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain information on the license and logos used by the connected vtenext installation.</span></span></td></tr><tr><td style="width:9.38277%;">portal.login</td><td style="width:8.08637%;"><div><div>**Body**</div><div>. username: string (*)</div><div>. password: string (*)</div><div>. language: string</div></div></td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to log in to the customer portal by providing the email (username) and password of the contact. The service also returns the accesskey and session id values to be used in subsequent calls as headers (Authorization and Portal-Session-Id).</span></span></td></tr><tr><td style="width:9.38277%;">portal.logout</td><td style="width:8.08637%;">**Headers**

. Authorization

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to logout the contact from the portal.</span></span></td></tr><tr><td style="width:9.38277%;">portal.send\_mail\_for\_password</td><td style="width:8.08637%;"><div><div>**Body**</div><div>. email: string (*)</div><div>. language: string</div></div></td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to recover the password of a contact.</span></span></td></tr><tr><td style="width:9.38277%;">portal.modules\_list</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

<div><div>**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**</div><div>. language: string (*)</div></div></td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain a list of modules enabled for the contact's profile.</span></span></td></tr><tr><td style="width:9.38277%;">portal.get\_list</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

. module: string (\*)

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. language: string</span></span>

. folderid: int  
. search: array

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain the list of records of a module filtered by the contact profile.   
  
</span><span style="vertical-align:inherit;">The "folderid" parameter is used by the Documents module to get the documents of a specific folder.  
  
</span></span>The "search" parameter allows the paging of the list on the server-side.

Ex.

\[  
 'length' =&gt; 50, // Number of records to return  
 'start' =&gt; 0, // Paging offset  
 'search' =&gt; '', // Global search  
 'search\_columns' =&gt; \[ // Search by columns  
 \[  
 'index' =&gt; 0, // Index of the field  
 'column' =&gt; '', // Name of the field

 'search' =&gt; '', // Value of the field  
 \]

 \],  
 'ordering' =&gt; \[ // Ordering  
 \[

 'index' =&gt; 0, // Index of the field  
 'column' =&gt; '', // Name of the field

 'dir' =&gt; '', // asc/desc  
 \]

 \],  
\]

Note:  
1\. All searches are performed ONLY on the columns of the list.  
2\. Column search is performed in "LIKE" mode.  
3\. Sorting can only be done on one column.

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">  
</span></span></td></tr><tr><td style="width:9.38277%;">portal.get\_blocks</td><td style="width:8.08637%;">**Headers**

. Authorization

**Body**

. module: string (\*)

. language: string

. mode: string (edit, create, detail, list) (\*)  
. app\_data: array

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain the list of the blocks and the related fields of a module filtered by the contact profile.  
  
The "app\_data" parameter represents the record being edited (array with field name and value) and is used by the SDK views to establish the visibility of the fields.  
This parameter will also be used in the future for managing conditional fields.  
</span></span></td></tr><tr><td style="width:9.38277%;">portal.get\_record</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. module: string (\*)</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">If the contact has visibility permissions for the indicated record, it allows you to obtain its data.</span></span>

</td></tr><tr><td style="width:9.38277%;">portal.save\_record</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. module: string (\*)</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

. values: encoded (\*)

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">If the contact has write permissions for the indicated record, it allows its saving.</span></span></td></tr><tr><td style="width:9.38277%;">portal.delete\_record</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. module: string (\*)</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">If the contact has delete permissions for the indicated record, it allows its deletion.</span></span></td></tr><tr><td style="width:9.38277%;">portal.write\_ticket\_comment</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

. comment: string (\*)

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to write a comment within the indicated ticket.</span></span></td></tr><tr><td style="width:9.38277%;">portal.get\_attachments</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain the list of documents of the specified record filtered by the visibility of the contact profile.</span></span></td></tr><tr><td style="width:9.38277%;">portal.download\_attachment</td><td style="width:8.08637%;">**Headers**

. Authorization

**Body**

. relid: int (\*)

. docid: int

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">If the contact has permission to view the attachment (relid), it allows its download.</span></span></td></tr><tr><td style="width:9.38277%;">portal.upload\_attachment</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. relid: int (\*)</span></span>

. title: string (\*)  
**File**

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows the uploading of an attachment related to the specified record (relid). </span><span style="vertical-align:inherit;">You can indicate the name (title) of the document that will be generated.</span></span></td></tr><tr><td style="width:9.38277%;">portal.provide\_confidential\_info</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. </span><span style="vertical-align:inherit;">id: int (\*)</span></span>

. comments: string

. data: string (\*)

. request\_commentid: int (\*)

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to respond to a request for confidential information. </span><span style="vertical-align:inherit;">The "id" parameter indicates the ticket id, the "comments" parameter indicates the unencrypted comment, the "data" parameter indicates the confidential response, and the "request\_commentid" parameter indicates the id of the comment to which the confidential response should be provided .</span></span></td></tr><tr><td style="width:9.38277%;">portal.get\_home\_widgets</td><td style="width:8.08637%;">**Headers**

. Authorization

**Body**

. language: string

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain the widgets configured in the profile associated with the contact.</span></span></td></tr><tr><td style="width:9.38277%;">portal.save\_authenticate\_cookie</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

. contactid: int (\*)

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Provides an hash to be stored in a cookie to remember the contact's login.</span></span></td></tr><tr><td style="width:9.38277%;">portal.check\_authenticate\_cookie</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. contactid: int (\*)</span></span>

. hash: string (\*)

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to verify the hash used to remember the contact's login.</span></span></td></tr><tr><td style="width:9.38277%;">portal.change\_password</td><td style="width:8.08637%;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Headers</span></span>**

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">. Authorization</span></span>

**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Body</span></span>**

. username: string (\*)

. old\_password: string (\*)

. password: string (\*)

. language: string

</td><td style="width:15.2469%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to change the contact's password.</span></span></td></tr></tbody></table>

##### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Register a new REST webservice</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Create a new file and execute it (e.g. plugins/script/script.php).</span></span>

```PHP
<?php
require('../../config.inc.php');
chdir($root_directory);
require_once('include/utils/utils.php');
require_once('vtlib/Vtecrm/Module.php');
$Vtiger_Utils_Log = true;
global $adb, $table_prefix;
VteSession::start();

SDK::setClass('CustomerPortalRestApi', 'CustomerPortalRestApi2', 'modules/SDK/src/CustomerPortalRestApi2.php');

$parameters = ['param1' => 'string', 'param2' => 'encoded', 'param3' => 'encoded'];
$perm = 'read'; // read, write, readwrite
SDK::setRestOperation('portal.foo', 'modules/SDK/src/CustomerPortalRestApi2.php', 'CustomerPortalRestApi2.foo', $parameters, $perm);

```

Create a new file that contains the CustomerPortalRestApi2 class <span style="vertical-align:inherit;">(e.g. modules/SDK/src/CustomerPortalRestApi2.php).</span>

```PHP
<?php

require_once('include/Webservices/CustomerPortal.php');

class CustomerPortalRestApi2 extends CustomerPortalRestApi {

	public function foo($param1, $param2, $param3) {
		$data = [1, 2, 3, 4, 5];
        // ...
		return $data;
	}

}
```

##### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Extend an existing REST webservice</span></span>

<span style="vertical-align:inherit;">Create a new file and execute it (e.g. plugins/script/script.php).</span>

```PHP
<?php
require('../../config.inc.php');
chdir($root_directory);
require_once('include/utils/utils.php');
require_once('vtlib/Vtecrm/Module.php');
$Vtiger_Utils_Log = true;
global $adb, $table_prefix;
VteSession::start();

SDK::setClass('CustomerPortalRestApi', 'CustomerPortalRestApi2', 'modules/SDK/src/CustomerPortalRestApi2.php');
```

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Create a new file that contains the CustomerPortalRestApi2 class​ (e.g. modules/SDK/src/CustomerPortalRestApi2.php).</span></span>

```PHP
<?php

require_once('include/Webservices/CustomerPortal.php');

class CustomerPortalRestApi2 extends CustomerPortalRestApi {

	public function get_list($module, $language, $folderid = 0, $search = []) {
		$ret = parent::get_list($module, $language, $folderid, $search);
		// your code here ...
		return $ret;
	}

}
```

#### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Structure of main folders/files</span></span>

---

<p class="callout info"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The portal folder is located at: </span></span>**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">VTE\_ROOT/portal/v2</span></span>**</p>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The main folders/files of the new portal are:</span></span>

<table id="bkmrk-nome-cartella-descri" style="border-collapse:collapse;width:100%;height:469px;"><tbody><tr style="height:29px;"><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Folder/file name</span></span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">app</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the files and the logic of the portal.</span></span></td></tr><tr style="height:46px;"><td style="width:50%;height:46px;">app/controllers</td><td style="width:50%;height:46px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the files that manage the portal's default actions (e.g. Login, Logout, Edit, Detail, etc.).</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">app/fields</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the files that manage the portal module fields.</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">app/modules</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains custom logic of some vtenext modules (e.g. Documents, Processes, HelpDesk, etc.).</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">app/PortalModule.php</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Class that manages the actions of the portal modules (e.g. Create, Detail, Edit, etc.). </span><span style="vertical-align:inherit;">The class can be extended to change the default behaviors of a module.</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">config</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the portal configuration files.</span></span></td></tr><tr style="height:46px;"><td style="width:50%;height:46px;">public</td><td style="width:50%;height:46px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the portal's public files (e.g. index.php, css, javascript, images, etc.).</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">resources</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the resources used by the portal such as translation files (lang) and templates (templates).</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">sdk</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The folder is used to insert new customizations for the customer.</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">storage</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains temporary files (e.g. cache, logs, etc.).</span></span></td></tr><tr style="height:29px;"><td style="width:50%;height:29px;">vendor</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Contains the external libraries used by the portal.</span></span></td></tr></tbody></table>

#### Request lifecycle

---

<div drawio-diagram="4959"><img src="https://usermanual.vtenext.com/uploads/images/drawio/2023-09/drawing-6-1694591374.png" alt="drawing-6-1694591374.png"/></div>


#### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Portal configuration</span></span>

---

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The portal configuration file is located in "config/portal.config.php".</span></span>

<p class="callout danger"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">To </span></span>**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">overwrite the parameters</span></span>**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;"> the "config/sdk.config.php" file must be used otherwise, in case of updating the version of vtenext, the changes could be lost.</span></span></p>

<p class="callout danger"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">**How to move the Business Portal to another host/folder**  
Update the "portal\_url", "vte\_url" and "csrf\_secret" parameters in the "config/sdk.config.php" file.  
Also change the "default\_timezone" parameter if the host has a different timezone from the one in which the vte is located. Change the "portal.url" prop to the link pointing to the v2 folder. e.g. https://ticket.vtenext.com/v2​​</span></span></p>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Here is the list of parameters supported by the new portal:</span></span>

<table id="bkmrk-parametro-tipo-defau" style="border-collapse:collapse;width:100%;height:1403px;"><tbody><tr style="height:29px;"><td style="width:22.6544%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Parameter</span></span>**</td><td style="width:10.9258%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Type</span></span>**</td><td style="width:18.7039%;height:29px;">**Default**</td><td style="width:47.716%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:80px;"><td style="width:22.6544%;height:80px;">portal\_url</td><td style="width:10.9258%;height:80px;">String</td><td style="width:18.7039%;height:80px;">$PORTAL\_URL  
(config.inc.php)</td><td style="width:47.716%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the URL of the customer portal. </span><span style="vertical-align:inherit;">If the portal folder is inside the vtenext root directory, the variable will be set to the value of the $PORTAL\_URL variable set in the config.inc.php file.</span></span></td></tr><tr style="height:96px;"><td style="width:22.6544%;height:96px;">vte\_url</td><td style="width:10.9258%;height:96px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:96px;">$site\_URL (config.inc.php)</td><td style="width:47.716%;height:96px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the URL of vtenext and is used to obtain its data via the rest API. </span><span style="vertical-align:inherit;">If the portal folder is inside the vtenext root directory, the variable will be set to the value of the $site\_URL variable set in the config.inc.php file.</span></span></td></tr><tr style="height:63px;"><td style="width:22.6544%;height:63px;"><div><div>default_language</div></div></td><td style="width:10.9258%;height:63px;">String</td><td style="width:18.7039%;height:63px;"><div><div>it_it</div></div></td><td style="width:47.716%;height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the default language used in the customer portal. </span><span style="vertical-align:inherit;">The value can be replaced with a supported language (see "languages" parameter).</span></span></td></tr><tr style="height:63px;"><td style="width:22.6544%;height:63px;"><div><div>languages</div></div></td><td style="width:10.9258%;height:63px;">Array</td><td style="width:18.7039%;height:63px;"><div><div>['en_us' =&gt; 'US English', 'it_it' =&gt; 'IT Italiano']</div></div></td><td style="width:47.716%;height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the languages ​​supported in the customer portal. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">To add a new language you need to create a new file in the resources/lang folder.</span></span></td></tr><tr style="height:113px;"><td style="width:22.6544%;height:113px;"><div><div>production</div></div></td><td style="width:10.9258%;height:113px;">Bool</td><td style="width:18.7039%;height:113px;">false</td><td style="width:47.716%;height:113px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">This configuration indicates whether errors should be displayed or not. </span><span style="vertical-align:inherit;">If the environment is production, the value will be set to true to disable error display. </span><span style="vertical-align:inherit;">If the environment is development, the value will be set to false to allow errors to appear.</span></span></td></tr><tr style="height:80px;"><td style="width:22.6544%;height:80px;"><div><div>default_module</div></div></td><td style="width:10.9258%;height:80px;">String</td><td style="width:18.7039%;height:80px;"> </td><td style="width:47.716%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the default module to load after logging into the portal. </span><span style="vertical-align:inherit;">The value can be replaced with the name of the desired module (must be enabled from profile). </span><span style="vertical-align:inherit;">If the parameter is empty, the portal home will be loaded.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>favicon</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"><div><div>assets/img/VTENEXT_favicon.ico</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the path of the favicon. </span><span style="vertical-align:inherit;">The value is relative to the public folder.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>login_logo</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"><div><div>assets/img/VTENEXT_login.png</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the path of the logo to load on the login page. </span><span style="vertical-align:inherit;">The value is relative to the public folder.</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[UPDATE\] The logo must be loaded in vtenext settings &gt; Logos.</span></span>

</td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>login_background</div></div></td><td style="width:10.9258%;height:29px;">String</td><td style="width:18.7039%;height:29px;"> </td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the path of the background to load on the login page. </span><span style="vertical-align:inherit;">The value is relative to the public folder.</span></span></td></tr><tr style="height:96px;"><td style="width:22.6544%;height:96px;"><div><div>header_logo_sm</div></div></td><td style="width:10.9258%;height:96px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:96px;"><div><div>assets/img/VTENEXT_toggle.png</div></div></td><td style="width:47.716%;height:96px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the path of the icon to load in the minimized sidebar. </span><span style="vertical-align:inherit;">The value is relative to the public folder. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[UPDATE\] The icon must be loaded in vtenext settings &gt; Logos.</span></span></td></tr><tr style="height:80px;"><td style="width:22.6544%;height:80px;"><div><div>header_logo_lg</div></div></td><td style="width:10.9258%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:80px;"><div><div>assets/img/VTENEXT_header.png</div></div></td><td style="width:47.716%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the path of the icon to load in the expanded sidebar. </span><span style="vertical-align:inherit;">The value is relative to the public folder. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[UPDATE\] The icon must be loaded in vtenext settings &gt; Logos.</span></span></td></tr><tr style="height:63px;"><td style="width:22.6544%;height:63px;"><div><div>helpdesk_logo</div></div></td><td style="width:10.9258%;height:63px;">String</td><td style="width:18.7039%;height:63px;"><div><div>assets/img/helpdesk.png</div></div></td><td style="width:47.716%;height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the logo used to display the response given by customer support. </span><span style="vertical-align:inherit;">The value is relative to the public folder.</span></span></td></tr><tr style="height:80px;"><td style="width:22.6544%;height:80px;"><div><div>sidebar_theme</div></div></td><td style="width:10.9258%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:80px;"><div><div>sidebar-dark-primary</div></div></td><td style="width:47.716%;height:80px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the class of the main sidebar. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">It can have a "dark" or "light" brightness. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">It can also have a color variant, such as "primary", "success", "warning", "info", "danger".</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>enable_sidebar_search</div></div></td><td style="width:10.9258%;height:29px;">Bool</td><td style="width:18.7039%;height:29px;"><div><div>false</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Enable/disable the search bar in the main sidebar.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>csrf_secret</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"><div><div>$csrf_secret (config.inc.php)</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the secret key used to generate a csrf token. </span><span style="vertical-align:inherit;">If the portal folder is inside the vtenext root directory, the variable will be set to the value of the $csrf\_secret variable set in the config.inc.php file.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">upload\_dir</span></span></div></div></td><td style="width:10.9258%;height:29px;">String</td><td style="width:18.7039%;height:29px;"> </td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the name of the folder used for uploading files.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>browser_title_prefix</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"> </td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the prefix label to use for the browser title. </span><span style="vertical-align:inherit;">The value can be replaced with the desired label.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>browser_title_suffix</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"><div><div>customer_portal</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the suffix label to use for the browser title. </span><span style="vertical-align:inherit;">The value can be replaced with the desired label.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>remember_cookie_name</div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">String</span></span></td><td style="width:18.7039%;height:29px;"><div><div>portal_login_hash</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the name of the cookie used to remember user authentication.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>login_expire_time</div></div></td><td style="width:10.9258%;height:29px;">Int</td><td style="width:18.7039%;height:29px;"><div><div>2592000 (one month)</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the expiration of the cookie used to remember user authentication. </span><span style="vertical-align:inherit;">The value can be replaced with the desired number of seconds.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div>default_timezone</div></div></td><td style="width:10.9258%;height:29px;">String</td><td style="width:18.7039%;height:29px;"><div><div>Europe/Rome</div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the default time zone used in the customer portal. </span><span style="vertical-align:inherit;">This configuration must be the same as the $default\_timezone variable set in the vtenext config.inc.php file.</span></span></td></tr><tr style="height:96px;"><td style="width:22.6544%;height:96px;"><div><div><div><div>module_icons</div></div></div></div></td><td style="width:10.9258%;height:96px;">Array</td><td style="width:18.7039%;height:96px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:96px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can override the default icons used for modules enabled in the customer portal. </span><span style="vertical-align:inherit;">The default form icons are found in app/layouts/PortalLayout.php. </span><span style="vertical-align:inherit;">The names of the icons can be found here https://fonts.google.com/icons.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><div><div>sdk_languages</div></div></div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add new labels or modify existing ones. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the sdk folder.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><div><div>sdk_global_php</div></div></div></div></td><td style="width:10.9258%;height:29px;">Array</td><td style="width:18.7039%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add php files to load on each page (they should contain classes/functions). </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the sdk folder.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><div><div>sdk_global_js</div></div></div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add js to load globally. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the public/assets/sdk folder.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><div><div>sdk_module_js</div></div></div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add js to load for a specific module. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the public/assets/sdk folder.</span></span></td></tr><tr style="height:29px;"><td style="width:22.6544%;height:29px;"><div><div><div><div>sdk_global_css</div></div></div></div></td><td style="width:10.9258%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add css to load globally. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the public/assets/sdk folder.</span></span></td></tr><tr><td style="width:22.6544%;"><div><div><div><div><div><div>sdk_controllers</div></div></div></div></div></div></td><td style="width:10.9258%;">Array</td><td style="width:18.7039%;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add custom actions ("action" field in the URL). </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The array represents an association between the action name and the file that contains the controller class to handle the request. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the sdk folder.</span></span></td></tr><tr><td style="width:22.6544%;"><div><div><div><div><div><div>sdk_module</div></div></div></div></div></div></td><td style="width:10.9258%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add customizations on a specific module. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">The array represents an association between the module name and the file that contains the module's extended class. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the sdk folder.</span></span></td></tr><tr><td style="width:22.6544%;"><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">sdk\_menu</span></span></div></div></div></div></div></div></td><td style="width:10.9258%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Array</span></span></td><td style="width:18.7039%;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">\[\]</span></span></div></div></td><td style="width:47.716%;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">With this configuration you can add custom menu items in the sidebar. </span></span>  
<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">You need to create a new file in the sdk folder.</span></span></td></tr></tbody></table>

#### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">API Reference - Main classes, methods, functions and variables</span></span>

---

##### \\app\\Request

<table id="bkmrk-metodo-descrizione" style="border-collapse:collapse;width:100%;height:290px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Arguments</span></span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>get()</div></div></td><td style="width:25%;height:29px;"><div><div>$keys = null, $purify = false</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get one or more parameters from the global variable $\_GET. </span><span style="vertical-align:inherit;">If the second argument is set to "true", the parameters are purified through the HTML Purifier library. </span><span style="vertical-align:inherit;">Example:</span></span>

```PHP
$request->get('foo');
$request->get(['foo', 'bar']);
$request->get('foo', true);
```

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div>post()</div></div></div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$keys = null, $purify = false</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get one or more parameters from the global variable $\_POST. </span><span style="vertical-align:inherit;">If the second argument is set to "true", the parameters are purified through the HTML Purifier library. </span><span style="vertical-align:inherit;">Example:</span></span>

```PHP
$request->post('foo');
$request->post(['foo', 'bar']);
$request->post('foo', true);
```

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div>files()</div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div>$key</div></div></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get files uploaded via the HTTP POST method and organized via the global variable $\_FILES.</span></span>

```PHP
$request->files('attachments');
```

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div>cookie()</div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div>$keys = null, $purify = false</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to obtain one or more parameters from the global variable $\_COOKIE. </span><span style="vertical-align:inherit;">If the second argument is set to "true", the parameters are purified through the HTML Purifier library. </span><span style="vertical-align:inherit;">Example:</span></span>

```PHP
$request->cookie('foo');
```

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div>server()</div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$keys = null, $purify = false</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get one or more parameters from the global variable $\_SERVER. </span><span style="vertical-align:inherit;">If the second argument is set to "true", the parameters are purified through the HTML Purifier library. </span><span style="vertical-align:inherit;">Example:</span></span>

```PHP
$request->server('');
```

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>isGet()</div></div></td><td style="width:25%;height:29px;"> </td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns true if the request method is GET.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">isPost()</span></span></div></div></td><td style="width:25%;height:29px;"> </td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns true if the request method is POST.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>isAjax()</div></div></td><td style="width:25%;height:29px;"> </td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns true if the request is AJAX.​</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>purify()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div>$input</div></div></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Purify the $input variable through the HTML Purifier library.</span></span></td></tr></tbody></table>

##### \\app\\Response

<table id="bkmrk-metodo-argomenti-des" style="border-collapse:collapse;width:100%;height:290px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>__construct()</div></div></td><td style="width:25%;height:29px;"><div><div>$content = '', $statusCode = 200, $headers = []</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Initializes a new \\app\\Response() object with the response content ($content), return code ($statusCode), and default headers ($headers).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>setContent()</div></div></td><td style="width:25%;height:29px;"><div><div>$content</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the content of the response.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">setStatusCode()</span></span></div></div></td><td style="width:25%;height:29px;"><div><div>$statusCode</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the response return code.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>setHeader()</div></div></td><td style="width:25%;height:29px;"><div><div>$header, $replace = true</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set a new header in the response. </span><span style="vertical-align:inherit;">With the second argument it is possible to indicate whether or not the header must replace a previous header already set.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>setMimeType()</div></div></td><td style="width:25%;height:29px;"><div><div>$mimeType = 'text/html'</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Sets the response content mime.</span></span>

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>json()</div></div></td><td style="width:25%;height:29px;"><div><div>$data</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set response content with $data converted to JSON format and 'application/json' mime.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>redirect()</div></div></td><td style="width:25%;height:29px;"><div><div>$page</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Performs a redirect to $page.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>downloadFile()</div></div></td><td style="width:25%;height:29px;"><div><div>$fullpath</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows downloading of a file located in $fullpath.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>output()</div></div></td><td style="width:25%;height:29px;"> </td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Outputs the content, response code, and headers you set.</span></span></td></tr></tbody></table>

##### \\app\\Session

<table id="bkmrk-metodo-argomenti-des-0" style="border-collapse:collapse;width:100%;height:261px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>set()</div></div></td><td style="width:25%;height:29px;"><div><div>$key, $value = ''</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the value $value with key $key in $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>get()</div></div></td><td style="width:25%;height:29px;"><div><div>$key</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get the value with key $key from $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>flash()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key</span></span></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get the value with key $key from $\_SESSION. </span><span style="vertical-align:inherit;">Next, the $key will be deleted from $\_SESSION.​</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>remove()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key</span></span></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Delete the $key from $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>hasKey()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key</span></span></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns true if $key exists in $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">setMulti()</span></span></div></div></td><td style="width:25%;height:29px;">$keys</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to write multiple values ​​to $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>removeMulti()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$keys</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Delete multiple values ​​in $\_SESSION.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>append()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key, $value = ''</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the $key as an array in $\_SESSION and the value $value is added to it.</span></span></td></tr></tbody></table>

##### \\app\\Config

<table id="bkmrk-metodo-argomenti-des-1" style="border-collapse:collapse;width:100%;height:261px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>has()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns true if $key exists in the global configuration.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">get()</span></span></div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$key</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get the value with key $key from the global configuration.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>set()</div></div></td><td style="width:25%;height:29px;"><div><div>$key, $value</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the value $value with key $key in the global configuration.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>getAll()</div></div></td><td style="width:25%;height:29px;"> </td><td><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">It allows you to obtain a key-value list with all the global configuration.</span></span></td></tr><tr><td style="width:25%;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">setMulti()</span></span></div></div></td><td style="width:25%;"><div><div>$values</div></div></td><td><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to write multiple values ​​into the global configuration.</span></span></td></tr><tr><td style="width:25%;"><div><div><div><div>clear()</div></div></div></div></td><td style="width:25%;"><div><div><div><div>$key</div></div></div></div></td><td><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Delete the $key from the global configuration.</span></span></td></tr><tr><td style="width:25%;"><div><div><div><div><div><div>clearAll()</div></div></div></div></div></div></td><td style="width:25%;"> </td><td><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Delete all values ​​from the global configuration.</span></span></td></tr></tbody></table>

##### \\app\\PortalModule

<table id="bkmrk-variabile-default-de" style="border-collapse:collapse;width:100%;height:307px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Variable</span></span>**</td><td style="width:25%;height:29px;">**Default**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;">$hasComments</td><td style="width:25%;height:29px;"><div><div>false</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates whether the module supports comments.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>$hasAttachments</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">false</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates whether the module supports attachments.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>$enableEdit</div></div></td><td style="width:25%;height:29px;"><div><div>true</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates whether the module can be modified (edit mode).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>$formColumns</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">3</span></span></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the number of columns to use for displaying fields in Create, Edit and Detail.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>$listTemplate</div></div></td><td style="width:25%;height:29px;"><div><div>List.tpl</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the template used for displaying a list (action List).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;">$referenceListTemplate</td><td style="width:25%;height:29px;"><div><div>sections/ReferenceList.tpl</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the template used for displaying a related list (uitype 10).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>$detailTemplate</div></div></td><td style="width:25%;height:29px;"><div><div>Detail.tpl</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the template used to display the detail of a record (action Detail).</span></span></td></tr><tr style="height:46px;"><td style="width:25%;height:46px;"><div><div><div><div>$editTemplate</div></div></div></div></td><td style="width:25%;height:46px;"><div><div><div><div>Edit.tpl</div></div></div></div></td><td style="height:46px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the template used to display the edit of a record (action Edit).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div>$notAuthorizedTemplate</div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div>PageNotAuthorized.tpl</div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Indicates the template used to display a permission error (e.g. record not found or permission errors).</span></span></td></tr></tbody></table>

<table id="bkmrk-metodo-argomenti-des-2" style="width:100%;border-collapse:collapse;height:353px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>__construct()</div></div></td><td style="width:25%;height:29px;"><div><div>$module</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$module indicates the name of the module to obtain an instance of the class. </span><span style="vertical-align:inherit;">If the module has been extended via SDK then an instance of the extended class will be returned.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>prepareList()</div></div></td><td style="width:25%;height:29px;"><div><div>$viewer, $request</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method used for displaying a list (action List).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>postProcessList()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$viewer, $request</span></span></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">This method can be used by extended classes to insert/modify data set in prepareList().</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>prepareEdit()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$viewer, $request</span></span></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method used to display the edit of a record (action Edit).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>postProcessEdit()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$viewer, $request</span></span></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">This method can be used by extended classes to insert/modify the data set in prepareEdit().</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>prepareDetail()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$viewer, $request</span></span></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method used to display the detail of a record (action Detail).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>postProcessDetail()</div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$viewer, $request</span></span></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">This method can be used by extended classes to insert/modify data set in prepareDetail().</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>saveRecord()</div></div></td><td style="width:25%;height:29px;"><div><div>$request</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method used to save a record (action Save).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>postProcessSaveValues()</div></div></td><td style="width:25%;height:29px;"><div><div>$request, &amp;$values</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">This method can be used by extended classes to insert/modify data set in saveRecord().</span></span></td></tr><tr style="height:63px;"><td style="width:25%;height:63px;"><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">isPermitted()</span></span></div></div></div></div></td><td style="width:25%;height:63px;"><div><div><div><div>$module, $action = self::ACTION_LIST, $recordValues = []</div></div></div></div></td><td style="height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns if the contact has permission to perform a certain action.</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">List of supported actions:</span></span>

<div><div>. ACTION_LIST</div><div>. ACTION_CREATE</div><div>. ACTION_EDIT</div><div>. ACTION_DETAIL</div><div>. ACTION_DELETE</div><div>. ACTION_SAVE</div><div>. ACTION_ADD_COMMENTS</div><div>. ACTION_UPLOAD_ATTACHMENTS</div><div>. ACTION_CHANGE_PWD</div><div>. ACTION_SOLVE_TICKET</div></div></td></tr></tbody></table>

##### \\app\\clients\\PortalRestClient

<table id="bkmrk-metodo-argomenti-des-3" style="border-collapse:collapse;width:100%;height:266px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Method</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>get()</div></div></td><td style="width:25%;height:29px;">$restName, $queryParameters = \[\], $headers = \[\]</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to perform a GET request to vtenext.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>post()</div></div></td><td style="width:25%;height:29px;">$restName, $queryParameters = \[\], $bodyParameters = \[\], $headers = \[\]</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to perform a POST request to vtenext.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">postMultipart()</span></span></div></div></td><td style="width:25%;height:29px;">$restName, $queryParameters = \[\], $bodyParameters = \[\], $fileParameters = \[\], $headers = \[\]</td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to perform a multipart POST request to vtenext.</span></span>

</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>patch()</div></div></td><td style="width:25%;height:29px;"><div><div>$restName, $queryParameters = [], $bodyParameters = [], $headers = []</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to execute a PATCH request to vtenext.</span></span></td></tr><tr style="height:63px;"><td style="width:25%;height:63px;"><div><div>delete()</div></div></td><td style="width:25%;height:63px;"><div><div>$restName, $queryParameters = [], $headers = []</div></div></td><td style="height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to execute a DELETE request to vtenext.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>postDownload()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$restName, $queryParameters = \[\], $bodyParameters = \[\], $headers = \[\]</span></span></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to perform a POST request to download a vtenext file in stream mode.</span></span></td></tr></tbody></table>

##### Helpers

<table id="bkmrk-funzione-argomenti-d" style="border-collapse:collapse;width:100%;height:1310px;"><tbody><tr style="height:29px;"><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Function</span></span>**</td><td style="width:25%;height:29px;">**<span style="vertical-align:inherit;">Arguments</span>**</td><td style="width:50%;height:29px;">**<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Description</span></span>**</td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>preprint()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$var</span></span></div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">print\_r formatted with &lt;pre&gt; tag</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">predump()</span></span></div></div></td><td style="width:25%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$var</span></span></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">var\_dump formatted with &lt;pre&gt; tags</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>encodeForHtml()</div></div></td><td style="width:25%;height:29px;"><div><div>$value, $charset = 'UTF-8'</div></div></td><td style="width:50%;height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Encode the $value value to be inserted into an HTML page.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>encodeForHtmlAttr()</div></div></td><td style="width:25%;height:29px;"><div><div>$value, $enclosing = '"'</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Encode the $value value to be placed inside an HTML tag attribute.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>encodeForJs()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$value, $enclosing = '"'</span></span></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Encode the $value value to be placed inside a javascript &lt;script&gt;.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>htmlAttr()</div></div></td><td style="width:25%;height:29px;"><div><div>$attributes</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Encode a list of attributes to be placed inside an HTML tag.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>config()</div></div></td><td style="width:25%;height:29px;"><div><div>$key</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to get the value with key $key from the global configuration.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div>trans()</div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div>$key, $args = []</div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Translate the label $key.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div>listUrl()</div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div>$module, $extraParams = []</div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generate a link to open a list.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div>createUrl()</div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$module, $extraParams = \[\]</span></span></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to open the creation of a record.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div>createDocUrl()</div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div>$module, $folderId, $extraParams = []</div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to open the creation of a document.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div>detailUrl()</div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div>$module, $record, $extraParams = []</div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to open the detail of a record.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">editUrl()</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$module, $record, $extraParams = \[\]</span></span></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to open the editing of a record.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>downloadUrl()</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$record, $documentId, $extraParams = []</div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to download a document.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>docFolderUrl()</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$module, $folderId, $extraParams = \[\]</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generates a link to open a specific folder in the documents module.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>returnUrl()</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$request</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Generate a return link (e.g. "Cancel" action).</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>portalLanguage()</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the language used in the portal.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>setPortalLanguage()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$language</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set the $language in the portal.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div>getBrowserTitle()</div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$title</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the title to be set in an HTML page of the portal.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">getModuleLabel()</span></span></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$module</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the translation of the module $module.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">getSingleModuleLabel()</span></span></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$module</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the singular translation of the module $module.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div>getMaxUploadSize()</div></div></div></div></div></div></div></div></div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the maximum upload size in the portal.</span></span></td></tr><tr style="height:63px;"><td style="width:25%;height:63px;"><div><div>setPortalCookie()</div></div></td><td style="width:25%;height:63px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$name, $value = "", $expires_or_options, $httponly = false</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:63px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Set a cookie in the portal.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">unsetPortalCookie()</span></span></div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$name, $httponly = false</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Removes a cookie from the portal.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div>csrfToken()</div></div></div></div></div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the csrf token.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>csrfInputName()</div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the name of the input for sending the csrf token.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>flashPortalError()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$error</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to display a timed error message.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div>flashPortalMessage()</div></div></div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$message</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows you to display a timed message.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">customerId()</span></span></div></div></div></div></div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the ID of the authenticated contact.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>customerEmail()</div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the email of the authenticated contact.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>customerUsername()</div></div></td><td style="width:25%;height:29px;"> </td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the name and surname of the authenticated contact.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>resourcever()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$filename</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Allows the versioning of css and javascript files.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>basePath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$path = ''</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal base folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the base folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>appPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "app" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "app" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>configPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "config" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "config" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>resourcesPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$path = ''</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "resources" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "resources" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>langPath()</div></div></td><td style="width:25%;height:29px;"><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">only $</span></span></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the indicated language file $lang.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>storagePath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "storage" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "storage" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>publicPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "public" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "public" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>vtePath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the vtenext folder. </span><span style="vertical-align:inherit;">If $path is indicated, a path is created and returned starting from the vtenext folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>sdkPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>$path = ''</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "sdk" folder. </span><span style="vertical-align:inherit;">If $path is indicated, a path is created and returned starting from the "sdk" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>sdkAssetsPath()</div></div></td><td style="width:25%;height:29px;"><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">$path = ''</span></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns the path to the portal's "public/assets/sdk" folder. </span><span style="vertical-align:inherit;">If $path is specified, a path is created and returned starting from the "public/assets/sdk" folder.</span></span></td></tr><tr style="height:29px;"><td style="width:25%;height:29px;"><div><div>publicRelPath()</div></div></td><td style="width:25%;height:29px;"><div><div>$assetPath</div></div></td><td style="height:29px;"><span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Returns a relative path starting from the "public" folder.</span></span></td></tr></tbody></table>

#### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Examples</span></span>

---

##### SDK view

Here is an example of how to change the visibility of portal fields via view SDK.

```PHP
<?php
require('../../config.inc.php');
chdir($root_directory);
require_once('include/utils/utils.php');
require_once('vtlib/Vtecrm/Module.php');
$Vtiger_Utils_Log = true;
global $adb, $table_prefix;
VteSession::start();

$module = '';
$src = '';
$mode = 'constrain';
$success = 'continue';
SDK::addView($module, $src, $mode, $success);
```

```php
<?php

global $sdk_mode, $table_prefix;

switch ($sdk_mode) {
	case 'portal.create':
		$readonly = 100;
		$success = true;
		break;
	case 'portal.edit':
	case 'portal.detail':
		if ($col_fields['field3'] !== 'Open') {
			$readonly = 99;
			$success = true;
		}
		if (in_array($fieldname, ['field1', 'field2'])) {
			$readonly = 100;
			$success = true;
		}
		break;
}

```


##### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Creating a new controller</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Here is an example of how to create a new controller to manage a custom action.</span></span>

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Edit "config/sdk.config.php" to insert a new controller</span></span>

```PHP
return [
	'sdk_controllers' => [
		'SampleVte' => 'controllers/SampleVteController.php',
	]
];
```

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Implement the SampleVteController class in "sdk/controllers/SampleVteController.php"</span></span>

```PHP
<?php

class SampleVteController extends \app\controllers\BaseController {
	
	public function index($request) {
		return $this->displaySomething($request);
	}

	protected function displaySomething($request) {
		$parameter1 = $request->get('parameter1', true);
		$parameter2 = $request->get('parameter2', true);

		$this->viewer->assign('PARAMETER1', $parameter1);
		$this->viewer->assign('PARAMETER2', $parameter2);

		$layout = \app\LayoutFactory::getPortalLayout($this->viewer, $this->client, $request);
		$output = $this->fetchWithLayout('sdk/SampleVte.tpl', $layout);

		return new \app\Response($output);
	}
	
}

```

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Create a new template in "resources/templates/sdk/SampleVte.tpl"</span></span>

```PHP
{extends file='layouts/PortalLayout.tpl'}

{block name=content}
	<h1>Sample Vte</h1>
{/block}
```

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Modify "config/sdk.config.php" indicating the sdk file for inserting the new entries in the side menu</span></span>

```PHP
return [
	'sdk_controllers' => [
		'SampleVte' => 'controllers/SampleVteController.php',
	],
  	'sdk_menu' => [
		'samplevte-menu.php',
	]
];
```

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Edit the "sdk/samplevte-menu.php" file</span></span>

```PHP
<?php

return [
	[
		'text' => trans('SampleVte'),
		'active' => false,
		'prefix' => [
			'type' => 'icon',
			'icon_style' => 'material',
			'icon_name' => 'pie_chart',
		],
		'action' => [
			'type' => 'link',
			'link_href' => "index.php?action=SampleVte",
		],
	],
];

```

[![image-1694533951130.48.16.png](https://usermanual.vtenext.com/uploads/images/gallery/2023-09/scaled-1680-/image-1694533951130-48-16.png)](https://usermanual.vtenext.com/uploads/images/gallery/2023-09/image-1694533951130-48-16.png)

##### <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Extending a module</span></span>

<span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Here is an example of how to extend the functionality of a module.</span></span>

- <span style="vertical-align:inherit;">Edit</span> "config/sdk.config.php"

```PHP
return [
	'sdk_module' => [
		'Contacts' => 'modules/ContactsModule.php',
	]
];
```

- <span style="vertical-align:inherit;"><span style="vertical-align:inherit;">Implement the ContactsModule class in "sdk/modules/ContactsModule.php"</span></span>

```PHP
<?php

class ContactsModule extends \app\PortalModule {
	
	public $hasComments = true;

	public $formColumns = 2;

	public function canAddComments() {
		return true;
	}
	
}

```

[![image-1694596916198.20.03.png](https://usermanual.vtenext.com/uploads/images/gallery/2023-09/scaled-1680-/image-1694596916198-20-03.png)](https://usermanual.vtenext.com/uploads/images/gallery/2023-09/image-1694596916198-20-03.png)

# Migration to PHP ≥ 8.3

# What's new in PHP 8.3+

#### **Minimum PHP version updated**

The application now requires **PHP 8.3** as the minimum version.




#### **Database updates**

Starting from version 26.01 of vtenext, some important database changes took place, such as:

**Charset and Collation** Database is now using by default the charset **utf8mb4** and collation **utf8mb4\_general\_ci**, which allows to store and display all possible UTF-8 characters, including emojis and special symbols.

**BIGINT columns**  
All columns containing crmids are now BIGINT (for new installations). When creating columns that store crmid, always use BIGINT (or **I8** when using adodb datadict). This is necessary to support a large number of records (more than ~2 billion).

**TINYINT columns**  
All columns containing only boolean integers (e.g., 0/1 for checkbox fields uitype 56, deleted=0/1, or various status columns with only small integers) are now TINYINT (or **I1** when using adodb datadict). This change will slightly reduce the database size and query execution time in certain cases.

During the update (step 3019 - 3020) a script to automatically convert the charset and the integer columns is executed (modules/Update/changes/bigint\_utf8mb4\_alter.php). Since this change can take a very long time, bigger tables (modules with more than 100k records, or tables bigger than 5GB) are excluded and should be converted manually at the end of the update (or at an appropriate time to minimize downtime, like at night). The script will output if this is necessary or if all tables have been converted. In case the manual conversion is necessary, it can be done in 2 ways:

- By running the queries in file: modules/Update/changes/bigint\_alter\_big.sql
- For extremely large tables, by executing the script: modules/Update/changes/percona\_alter.sh, to use the pt-online-schema-change tool (installation to be done separately) to avoid locking the tables. It is advised to review this script and manually add the necessary command line option to the Percona command.

**Licence tag**

A new SPDX license tag is now **mandatory in all vtenext core files** (php, js and tpl) that are not external libraries

```php
/*************************************
 * SPDX-FileCopyrightText: 2009-present Vtenext S.r.l. Società Benefit 
 * SPDX-License-Identifier: LicenseRef-vtenext-business-license 
 ************************************/
```



#### **New configuration files**

All new custom configuration files (e.g., custom api token) must be placed in the `/config` folder. If some configuration is different from dev/prod environments, use the corresponding config file in `/config/`.

- `config.$envType.php`: Overrides of PHP configuration and global variables for a specific environment type
- `phpstan/*`: Configuration to use when running phpstan
- `smarty/*`: Configuration directory for Smarty library
- `logging.php`: Configuration for the legacy logs (old log4php)
- `request.config.php`: See [Configuration - RequestHandler](https://usermanual.vtenext.com/books/developers/page/requesthandler#bkmrk-configuration)




#### **New config variables**

The following global config variables have been added to `config.inc.php`:

##### **`$enableLegacyLogs`**

Activate the legacy logs (the old log4php), which logs a lot of stuff in `logs/vtenext.log`. The usefulness of this log is not certain. Configuration of this log is in `/config/logging.php`.

##### **`$envType`**

Specify the environment type. Possible values:

- `"prod"` (default)
- `"preprod"`
- `"dev"`
- `"debug"`

Each level activates more error messages. The default configuration is in `config.inc.php`, variable `$php_config`, overridden by the specific `/config/config.$envType.php` file.

##### **`$php_config`**

Used to specify the default PHP configuration used by `$envType`.

##### **`$smarty_warn_superglobals`**

<div id="bkmrk-a-global-configurati"><div>A global configuration flag that enables warnings when PHP superglobals are used in Smarty templates. When enabled, the system registers a Smarty POST filter that scans compiled template code for superglobal usage ($_GET, $_POST, $_REQUEST, $_COOKIE, $_SERVER, $_ENV). <div><div>By default it is enabled only in development environments.</div></div></div></div>


#### **New functions and methods**

##### **RequestHandler (RH)**

`RequestHandler` provides secure, centralized access to HTTP request data. It automatically sanitizes input using `vtlib_purify()` and applies type-safe filters from `F::` enum. This replaces direct access to superglobals like `$_REQUEST`, `$_GET`, `$_POST`, etc.

For details, see: [RequestHandler](https://usermanual.vtenext.com/books/developers/page/requesthandler)

##### **BaseAction**

We have moved the route resolution logic to the new `IndexRouter` class and introduced the `BaseAction` class to handle actions.

For details, see: [Routing system](https://usermanual.vtenext.com/books/developers/page/routing-system).

##### **CRMEntity methods update**

The old `retrieve_entity_info` has been renamed to `retrieve_html` and `retrieve_entity_info_no_html` to `retrieve`. This is to make more explicit that there is a HTML conversion going on. The old functions are deprecated, but still working.

- `CRMEntity::retrieve_html()`: Retrieves entity with HTML conversion
- `CRMEntity::retrieve()`: Retrieves entity without HTML conversion

##### **Translation aliases**

New convenient aliases for translation functions:

- `trans()`: Alias for `getTranslatedString()`
- `trans_app()`: Alias for `getTranslatedString($str, "APP_STRINGS")`
- `trans_js()`: Alias for `getTranslatedString($str, "ALERT_ARR")`




#### **New database query methods**

New database query methods are available. For details, see: [Database Best Practices](https://usermanual.vtenext.com/books/developers/page/database-best-practices)




#### **<span data-teams="true">Autoescaping of all Smarty variable</span>**

<span data-teams="true">All variables outputted by Smarty templates (for example `{$VARIABLE}`) by default have all applicable characters are converted to html `&...;` notation.</span>

<span data-teams="true">The reason of this change is to reduce as much as possible exposure to XSS attacks, since it was extremely difficult to track every possible variable in templates and ensure it was properly escaped.</span>

For details, see: [Escape](https://usermanual.vtenext.com/books/developers/page/introduction).




#### **New tools**

##### **`tools/change-env`**

Allows to easily change the `$envType` variable in `config.inc.php` and automatically execute scripts afterwards. Scripts to execute are located in `plugins/envs/make.$env/*`.

##### **`tools/activate`**

<div id="bkmrk-prefix-the-absolute-"><div>Prefix the absolute path of `tools/` to `PATH`.</div></div>##### **`tools/analyze`**

<div id="bkmrk-install-and-run-phps"><div>Install and run phpstan on the vte root directory, or a custom one.</div></div>##### **`tools/tests`**

<div id="bkmrk-install-and-run-phpu"><div id="bkmrk-install-and-run-phpu-1"><div id="bkmrk-install-and-run-phpu-2"><div><div>Install and run phpunit with vte unit and functional tests.</div></div></div></div></div>##### **`tools/change-color`**

<div id="bkmrk-simple-script-to-cha"><div><div>Simple script to change color of vtenext using some standard values. The purpose is to quickly change color of an environment to differentiate it from the production.</div></div></div>##### **`tools/compile-themes`**

<div id="bkmrk-compile-scss-sources"><div id="bkmrk-compile-scss-sources-1"><div><div>Compile scss sources of all (or some) known vte themes. Can monitor for file changes and trigger a compilation automatically (`inotifywait` must be installed).</div><div>  
</div></div></div></div>

#### **Important files and folders**

##### **`cache_local/`**

This folder should contain cache files to be stored on the same host as the webserver. It is useful in case of cluster deployment, where the `cache/` folder is normally shared, but `cache_local/` is only local.

##### **`plugins/envs/`**

Contains the scripts to be executed when changing the environment type (e.g., after cloning the prod in the dev host).




#### **GDPR portal update**

The GDPR portal has been significantly updated with the following improvements:

- Migration of libraries to Composer for better dependency management
- API calls switched to vtenext RestClient
- Smarty templating engine upgraded to the latest version (^5.4)




#### **Libraries**

All third party libraries now must be imported via composer (living in the `/vendor/` folder), or placed in the `/vendor-extra/` folder. Libraries in vendor-extra are automatically included in the class path.

<p class="callout info">**Important note** Whenever a new library is added, either via composer or manually, the following command must be executed to refresh the classmap: `composer optimize` → alias of `composer dump-autoload -o`</p>

##### **Libraries updated**

<table id="bkmrk-library-previous-ver" style="width: 84.7619%; height: 510.344px;"><thead><tr style="height: 46.5938px;"><th style="width: 50.4923%; height: 46.5938px;">**Library**</th><th style="width: 27.8481%; height: 46.5938px;">**Previous version**</th><th style="width: 21.6596%; height: 46.5938px;">**New version**</th></tr></thead><tbody><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`phpmailer/phpmailer`</td><td style="width: 27.8481%; height: 29.7969px;">6.6.5</td><td style="width: 21.6596%; height: 29.7969px;">6.9.3</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`ezyang/htmlpurifier`</td><td style="width: 27.8481%; height: 29.7969px;">4.13.0</td><td style="width: 21.6596%; height: 29.7969px;">4.18.0</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`mpdf/mpdf`</td><td style="width: 27.8481%; height: 29.7969px;">8.0.12</td><td style="width: 21.6596%; height: 29.7969px;">8.2.5</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`league/iso3166`</td><td style="width: 27.8481%; height: 29.7969px;">2.1.5</td><td style="width: 21.6596%; height: 29.7969px;">4.3.2</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`league/oauth2-client`</td><td style="width: 27.8481%; height: 29.7969px;">2.7.0</td><td style="width: 21.6596%; height: 29.7969px;">2.8.1</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`league/oauth2-google`</td><td style="width: 27.8481%; height: 29.7969px;">3.0.4</td><td style="width: 21.6596%; height: 29.7969px;">4.0.1</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`spomky-labs/otphp`</td><td style="width: 27.8481%; height: 29.7969px;">10.0.3</td><td style="width: 21.6596%; height: 29.7969px;">11.3.0</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`joypixels/emoji-toolkit`</td><td style="width: 27.8481%; height: 29.7969px;">6.6.0</td><td style="width: 21.6596%; height: 29.7969px;">9.0.1</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`phpoffice/phpspreadsheet`</td><td style="width: 27.8481%; height: 29.7969px;">1.8.2</td><td style="width: 21.6596%; height: 29.7969px;">4.1.0</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`phpseclib/phpseclib`</td><td style="width: 27.8481%; height: 29.7969px;">3.0.42</td><td style="width: 21.6596%; height: 29.7969px;">3.0.43</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`jaybizzle/crawler-detect`</td><td style="width: 27.8481%; height: 29.7969px;">1.3.0</td><td style="width: 21.6596%; height: 29.7969px;">1.3.4</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`hubspot/hubspot-php`</td><td style="width: 27.8481%; height: 29.7969px;">2.0</td><td style="width: 21.6596%; height: 29.7969px;">5.3.0</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`slince/shopify-api-php`</td><td style="width: 27.8481%; height: 29.7969px;">2.5</td><td style="width: 21.6596%; height: 29.7969px;">3.1.0</td></tr><tr style="height: 46.5938px;"><td style="width: 50.4923%; height: 46.5938px;">`zendesk/zendesk_api_client_php`</td><td style="width: 27.8481%; height: 46.5938px;">2.29</td><td style="width: 21.6596%; height: 46.5938px;">4.1.0</td></tr><tr style="height: 29.7969px;"><td style="width: 50.4923%; height: 29.7969px;">`lesstif/php-jira-rest-client`</td><td style="width: 27.8481%; height: 29.7969px;">1.41</td><td style="width: 21.6596%; height: 29.7969px;">5.9.0</td></tr></tbody></table>

##### **Libraries added**

- `web-token/jwt-library` 4.0.4
- `php-amqplib/php-amqplib` 3.7.3
- `econea/nusoap` 0.9.18
- `dragonmantank/cron-expression` 3.4.0
- `qualityunit/tnef-decoder` 1.2.9
- `sabre/vobject` 4.5.6
- `flipboxdigital/oauth2-hubspot` 1.0.1
- `stevenmaguire/oauth2-salesforce` 2.0.1
- `stevenmaguire/oauth2-zendesk` 2.1.0
- `automattic/woocommerce` 3.1.0
- `monolog` (replaces log4php) 3.8.1

##### **Libraries moved to vendor**

The following libraries have been moved from various locations to `/vendor/` or `/vendor-extra/`:

- `include/nusoap` → moved to vendor
- `modules/Settings/ProcessMaker/thirdparty/cron-expression` → moved to vendor
- `modules/PDFMaker/classes/simple_html_dom.php` → moved to vendor-extra
- `include/magpierss` → moved to vendor-extra
- `modules/Morphsuit/utils/RSA` → replaced with phpseclib in vendor
- `modules/Newsletter/bounce_driver.class.php` → moved to vendor-extra
- `modules/Messages/src/attachment_tnef` → moved to vendor
- `modules/Messages/src/Squirrelmail.php` → moved to vendor-extra
- `smartoptimizer` → moved to vendor-extra (updated minifier, added brotli and zstandard support)
- `portal/nusoap` → moved to vendor
- `portal/include/htmlpurifier` → removed
- `include/pChart` → patched and moved to vendor-extra
- `include/freetag` → fixed and moved to vendor-extra
- `modules/VteSync/vendor` → moved to main vendor
- `modules/VteSync/VteSyncLib/src/Connector/Jira/vendor` → moved to main vendor

##### **Removed libraries**

- `log4php` - Replaced with monolog
- `vtlib/SimplePie` - No longer used
- `include/antlr` - Not working, grammar unknown and never used
- `modules/SDK/examples/intellisense` - Removed
- `modules/Calendar/iCal/ical-parser-class.php` - Not used
- `modules/Calendar/iCal/iCalendar_*.php` (Bennu) - Replaced with sabre/vobject
- `modules/Calendar/iCal/iCalcreator` - Replaced with sabre/vobject
- `include/Zend` - Simplified
- `gdpr/include/vtwsclib/lib/Zend` - Removed
- `portal/include/Zend` - Removed
- `videlalvaro/php-amqplib` - Replaced with php-amqplib/php-amqplib
- `web-token/jwt-easy`, `web-token/jwt-key-mgmt`, `web-token/signature-pack` - Replaced with web-token/jwt-library

##### **Zend framework replaced with Laminas**

The Zend framework (a discontinued project) has been completely removed and replaced with **Laminas**, its official successor. All Zend dependencies have been migrated to the Laminas equivalents.




#### **Removed config variables**

The following config variables have been removed:

##### **`$display_empty_home_blocks`**

Never used.

##### **`$php_max_execution_time`**

Misleading, since it was applied only in a few cases, and it was always 0, setting the limit to unlimited. Replaced by the `$php_config` and specific overrides when needed (e.g., Report export, PDF generation).




#### **Removed files and folders**

##### **`modules/Dashboard`**

Removed this module, already deprecated and inactive.

##### **`PEAR.php`**

Used only by Dashboard and by obsolete libraries.

##### **`Image/*`**

Used only by Dashboard module.

##### **`include/db_backup`**

Not used anymore (and also a bad idea to make a full backup on admin logout).

##### **`Smarty/templates_c`**

Moved to `cache_local/smarty` to keep the number of writable folders limited.

##### **`plugins/erpconnectorDir`**

This folder was used in the past to perform one/two-way synchronizations with external systems. It has now been replaced by the [Data Import](https://usermanual.vtenext.com/books/user-manual-vtenext-2502/page/1716-data-import).



##### **Old portal (v1)**

The previous version of the portal is **no longer supported**. You can choose one of the following options:

1. **Switch to the new Business Portal**
    
    Go to *Settings → Business Portal* to start using the updated version.
2. **Restore the portal from the upgrade backup**
    
    You may restore the previous version from the upgrade backup and update it to ensure compatibility with the new system. Please note that the old portal is <span class="s1">**not compatible with PHP 8**</span>. For more information and assistance, contact support.

# New developers features in vtenext 26.04

In version 26.04 some additional changes have been made to the code to simplify the development of custom functionalities in certain areas.

<span>View the </span>[developer release notes](https://usermanual.vtenext.com/link/5751#bkmrk-developers).

# Mailscanner (Mail Converter)

Several Mailscanner classes are now exendable via `SDK::setClass`:

- MailScanner
- MailScannerAction
- MailScannerInfo
- MailScannerRule
- MailScannerSpam
- MailScannerMailBox
- MailScannerMailBoxZend

This allows for easier extensibility using our standard SDK.

Moreover, the list of actions (for example: Create Ticket, Update Ticket, ...) for each rule is now stored in the database and not hardcoded in various files. So adding a new a new action is much easier now:

```php
require_once 'modules/Settings/MailScanner/core/MailScannerAction.php';

// extend the class to implement custom actions
SDK::setClass('MailScannerAction', 'MailScannerActionCustom', 'modules/SDK/src/CUSTOMER/MailScannerActionCustom.php');

// add an action to create a task (custom module, or even Calendar)
MailScannerAction::addActionType('CREATE,Task,FROM', 'LBL_CREATE_MS_TASK', 'createTask');

// add the label
SDK::setLanguageEntries('Settings', 'LBL_CREATE_MS_TASK', ['it_it' => 'Crea compito', 'en_us' => 'Create task']);

```

And the extended class in `MailScannerActionCustom.php`:

```php
<?php

class MailScannerActionCustom extends MailScannerAction {
	
	/**
	 * Example of a custom mailscanner action
	 */
	function createTask($mailscanner, $mailrecord, $regexMatchInfo, $compare_parentid, $match_field) {
		
		$subject = $mailrecord->_subject;
		$description = $mailrecord->getBodyText();
		
		// create a record "CustomTask"
		$inst = \CRMEntity::getInstance('CustomTask');
		$inst->mode = '';
		
		// populate some fields
		$inst->column_fields['taskname'] = $subject;
		$inst->column_fields['description'] = $description;
		$inst->column_fields['date'] = date('Y-m-d');

		// save it
		$inst->save('CustomTask');
		
		// Associate any attachement of the email to the record
		$this->__SaveAttachements($mailrecord, $inst->modulename, $inst, $inst);
		
		// create the Messages record
		$this->__CreateNewEmail($mailrecord, $this->module, $inst);

		// return the record id, to signal the correct application of the action
		return $inst->id;
		
	}
	
}

```

Will produce a new action in the rule:

[![image.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/scaled-1680-/NU1image.png)](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/NU1image.png)

# Record conversion

In the latest version the handling of record conversion (converting a Quote to a SalesOrder, or a SalesOrder to an Invoice...) has been generalized and centralized in a single place.

The list of available conversion modes is now in the table `vte_convertmodes`, which is managed by the class `ConvertModesUtils` which gives the possibility to add new conversion modes between standard or custom modules.

For example, to add a new conversion mode from module Quote, to a custom module (with products) PreOrders:

```php
// instantiate the utils class
$CMU = ConvertModesUtils::getInstance();

// define the mapping for the fields, key is destination field, value is the source
$mapping = [
    // dest => source
	'vcf_1_1' => 'subject',
	'description' => 'description'
];

// add the convert mode
$CMU->addConvertMode(
	'quotetopreorder', 			// unique name for the mode, can be any string
	'Quotes',					// starting module
	'PreOrders',				// second module
	'record',					// the parameter in request that will pass the record id. by default "record"
	'quoteid',					// name of the field in the destination module with a reference to the first module, can be null
	true,						// if true, show the button in Quotes
	5,							// sequence of the button, among other conversion buttons
	'handleRecordConversion',	// the method to handle the conversion. You can specify a different one in the PreOrders class if you wish to do something different
	$mapping					// the fields mapping. products block is automatically copied
);
```

The button will appear automatically:

[![image.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/scaled-1680-/goiimage.png)](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/goiimage.png)

# CSV Import

One of the classes used by the standard CSV Import (the one available from any module's ListView) has been made extendable via `SDK::setClass` and highly refactored to split the main method into smaller ones, to ease modification of specific behaviours:

- `Import_Data_Controller`: This class now can be extended

For example, if you need to modify a value of a specific field before being saved to the database:

```php
// register the class
SDK::setClass('Import_Data_Controller', 'Import_Data_ControllerCustom', 'modules/SDK/src/CUSTOMER/ImportCustom.php');
```

And the class:

```php
<?php

require_once('modules/Import/controllers/Import_Data_Controller.php');

class Import_Data_ControllerCustom extends Import_Data_Controller {
	
	/**
	 * Transform a single field value to a format suitable to vte
	 */
	protected function transformFieldValue($fieldName, $fieldValue, $fieldInstance, $moduleMeta) {
		$fieldValue = parent::transformFieldValue($fieldName, $fieldValue, $fieldInstance, $moduleMeta);
		
		if ('Accounts' === $this->module && 'accountname' === $fieldName) {
			// append "IMPORTED" to the accountname
			$fieldValue .= ' - IMPORTED';
		}
		
		return $fieldValue;
	}
}

```

# VteSync (synchronizations)

VteSync connectors are now extendable via `SDK::setClass` so it's easier to add new functionalities or changing the field mapping.

For example, to extend the WooCommerce connector:

```php
// classes are namespaced
SDK::setClass('VteSyncLib\Connector\WooCommerce', 'WooCommerceCustom', 'modules/SDK/src/CUSTOMER/WooCommerceCustom.php');
```

The extended connector:

```php
<?php

// namespace the class, so it's easier to work with it
namespace VteSyncLib\Connector;

class WooCommerceCustom extends WooCommerce {

	// extend the constructor to alter the standard models
	public function __construct($config = array(), $storage = null) {
		parent::__construct($config, $storage);

		// in this case, the class is in the same folder, so no need to include it
		$this->classes['Accounts'] = array(
          'module' => 'Accounts',
          'commonClass' => 'VteSyncLib\Model\CommonRecord', 
          'class' => 'VteSyncLib\Connector\WooCommerce\Model\AccountCustom'
        );
	}
	
	// or it's possible to redefine any existing method
}

```

And the custom model:

```php
<?php

// include parent class
require_once(__DIR__.'/Account.php');

namespace VteSyncLib\Connector\WooCommerce\Model;

class AccountCustom extends Account {

	// here I redefine the mapping, removing or adding fields
    protected static $fieldMap = array(
        // WooCommerce => CommonRecord
        'email' => 'email',
        'username' => 'name',
        //'phone' => 'phone', // THIS IS COMMENTED

        // billing address
        'address' => 'billingstreet',
        'city' => 'billingcity',
        'postcode' => 'billingpostalcode',
        'state' => 'billingstate',
        'country' => 'billingcountry',
        'companybill' => 'companybill',
        'firstnamebill' => 'firstnamebill',
        'lastnamebill' => 'lastnamebill',
        // shipping address
        'address_shipping' => 'shippingstreet',
        'city_shipping' => 'shippingcity',
        'postcode_shipping' => 'shippingpostalcode',
        'state_shipping' => 'shippingstate',
        'country_shipping' => 'shippingcountry',
        'companyship' => 'companyship',
        'firstnameship' => 'firstnameship',
        'lastnameship' => 'lastnameship',
		
		'otherfield' => 'otherfield', // THIS IS CUSTOM!
		// you probably also need to alter the VTE models, to connect this mapping to vte's one
    );
    
	// this function is called to prepare the array to be sent to woocommerce
    public function toRawData($mode) {
		$raw = parent::toRawData($mode);
		
		// when sendind data to woo, hardcode this additional field:
		$row['somefield'] = 'somevalue';
		
		return $raw;
	}

}

```

# ListView extendability

The `ListViewController` class has been refactored to be more perfomant and easily extendable.

For example, now it's much easier to add new icons into the *Actions* column:

```php
SDK::setClass('ListViewController', 'ListViewControllerCustom', 'modules/SDK/src/CUSTOMER/ListViewControllerCustom.php');
```

And the class:

```php
<?php

require_once('include/ListView/ListViewController.php');

class ListViewControllerCustom extends ListViewController {
	
	/**
	 * Generate an array of strings to be concatenated and set as the "action" column
	 */
	public function generateActions($focus, $recordId, array $sqlrow = [], $navigationInfo = []) : array {
		$actionLinkInfo = parent::generateActions($focus, $recordId, $sqlrow, $navigationInfo);
		
		$module = $focus->modulename;
		if ($module === 'Leads') {
			// add an icon to each lead to open the record in the erp:
			$actionLinkInfo[] = "<a href=\"https://myerp.example.com/lead/$recordId\" target=\"_blank\"><i class=\"vteicon\"'>open_in_browser</i></a>";
		}
		
		return $actionLinkInfo;
	}
	
}

```

Resulting in:

[![image.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/scaled-1680-/GMVimage.png)](https://usermanual.vtenext.com/uploads/images/gallery/2026-03/GMVimage.png)

# Webforms

The class WebformCapture is now extendable via `SDK::setClass` and the main method `captureNow` has been split up in several methods to facilitate modification of specific behaviours.

For example, to force the value of a field with a dynamic value upon Lead creation:

First we have to register the extension:

```php
SDK::setClass('WebformCapture', 'WebformCaptureCustom', 'modules/SDK/src/CUSTOMER/WebformCaptureCustom.php');
```

And then extend the prepareParameters method:

```php
<?php

require_once('modules/Webforms/WebformCapture.php');

class WebformCaptureCustom extends WebformCapture {
	
	/**
	 * Read data from request and populate the necessary fields
	 */
	protected function prepareParameters(array $request, Webforms_Model $webform) : array {
        // call the parent method to fill the standard values
		$parameters = parent::prepareParameters($request, $webform);

        // populate the field with a random value (makeRandomString is just an example here)
		$parameters['my_field_random'] = makeRandomString();
		
		return $parameters;
	}
}

```

# Code Review Skill

There is a **skill** available for use with **AI agents** built into your IDE that can help you adapt your code to the new release.  
More details available in the [documentation](https://usermanual.vtenext.com/books/developers/page/skill-review-sdk-files).

Once the analysis is complete, the files are checked and the original ones are overwritten, run `tools/check-requests` to check for any further references to the superglobal variable `$_REQUEST`. If none are found, the `config/request.config.override.php` file will be removed; otherwise, it will be updated, leaving only the new occurrences.

<p class="callout info">If you use a cloud agent, make sure your files do not contain sensitive data or credentials.</p>

# New developers features in vtenext 26.07

# Worker

The Worker is a background daemon that runs alongside the CRM. When a task takes a long time (such as chatting with an AI, running a process, uploading documents for a vector search, etc..), the Worker handles it asynchronously so the browser does not freeze and the user can keep working. Also it allows the processing to be detached from the browser request, allowing the user to close the tab.

---

### Architecture Overview

#### Default setup flows

[![worker_architecture.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-06/scaled-1680-/worker-architecture.png)](https://usermanual.vtenext.com/uploads/images/gallery/2026-06/worker-architecture.png)

The Worker is composed of three processes that work together:

#### 1. Router

**File:** `include/Services/Worker/Router.php`

The Router listens on a Unix socket for incoming connections. When a browser tab or a PHP script connects, the Router identifies who is connecting, decides what to do with the request, and forwards it to the Zygote. It also keeps track of all connected clients and can push live updates (Server-Sent Events) back to browsers.

- Process name: `vte-worker-router`
- Listens on a socket (Unix `cache_local/worker.sock` with systemd, or a TCP/IP address for cluster setups). The address is configured in `config.inc.php` via `$worker_socket_URL` and must match the path in the systemd socket unit.
- Manages client subscriptions for real-time push events

#### 2. Zygote

**File:** `include/Services/Worker/Zygote.php`

The Zygote manages a pool of worker processes. When the Router forwards a job, the Zygote forks a Consumer process to execute it. Up to 10 Consumers can run concurrently; if all are busy, the job is queued and executed when a slot becomes available.

- Process name: `vte-worker-zygote`
- Maximum concurrent Consumers: `CONSUMERS_MAX = 10` (configurable in Zygote.php)
- Queues excess jobs until a Consumer is free

#### 3. Consumer

**File:** `include/Services/Worker/Consumer.php`

The Consumer is a short-lived process that performs the actual work. It connects to the database, loads the user context of the person who requested the task, executes the requested method, and terminates when done. Each Consumer handles exactly one job and then exits.

- Process name: `vte-worker-consumer`
- Process isolation: a crash in one Consumer does not affect others
- Database connection is established fresh for each job

---

### Communication Between Processes

The three processes communicate through **Unix pipes** created by `stream_socket_pair()`. This is faster and lighter than running a full HTTP server inside the Worker.

#### Client Types

There are two kinds of clients that can connect to the Worker:

<table id="bkmrk-client-how-it-connec" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 13.826%;">Client</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 52.801%;">How It Connects</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 33.3731%;">Used By</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 13.826%;">`Web`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 52.801%;">Browser → Apache → `modules/Utilities/Worker.php` (handles web auth) → socket → Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 33.3731%;">Browser tabs (AI chat, notifications). Each web connection holds an Apache process slot.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 13.826%;">`Script`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 52.801%;">PHP code → `ScriptConnector` → Unix socket → Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 33.3731%;">CLI scripts, cron jobs, AJAX handlers</td></tr></tbody></table>

#### For Web Clients (SSE)

When a browser connects, the Router upgrades the connection to **Server-Sent Events (SSE)**. This allows the Consumer to push data back in real-time — for example, streaming an AI response word by word, or sending a notification as soon as it is created.

Relevant files: `WebConnector.php` (client side), `WebHandler.php` (server side), `SharedWorker.js` (browser — optional, reduces connections to one per browser).

#### For Script Clients

PHP code connects using `ScriptConnector` with a 30-second I/O timeout (long enough for background tasks). The connection supports both synchronous calls (wait for response) and asynchronous calls (fire and forget).

Relevant files: `ScriptConnector.php` (client side), `ScriptHandler.php` (server side).

#### Internal Protocol

Messages use a simple text-based format over the socket. Each message is a JSON object with an event type (`init`, `call`, `return`, `error`, `event`) and event-specific data. The `ProtocolTrait` handles encoding and decoding on both sides.

---

### Available Tasks

These are the methods the Worker can execute. Consumer methods are registered in `ScriptMethodsTrait` (`Methods.php`) and implemented in `Consumer.php`. Worker methods execute directly in the Zygote (no Consumer fork).

<table id="bkmrk-method-where-execute" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 21.9309%;">Method</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 17.2825%;">Where Executed</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 40.638%;">Description</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 20.2679%;">Definition</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`llmChat`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Send a message to an AI assistant (Agent, LLM, or External WebService). Streams the response back to the browser via SSE.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Consumer.php:162</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`elaborateRag`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Upload selected CRM documents to the external AI orchestrator and build the vector database for RAG retrieval.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Consumer.php:601</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`delegateProcess`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Execute a BPMN workflow process (ProcessMaker) in the background.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Consumer.php:118</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`resumeProcesses`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Resume queued workflow processes. Runs at Worker startup and on demand.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Consumer.php:133</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`sendToAll`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Push a custom event to a specific user or to all connected browser sessions.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Router.php:249</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`notifyNow`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Send a CRM notification to a specific user in real-time.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Router.php:321</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`workerStats`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Zygote</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Returns uptime, number of active consumers, queued jobs.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Zygote.php:219, Router.php:222</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.9309%;">`workerRestart`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.2825%;">Zygote</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 40.638%;">Restarts the entire Worker (Router + Zygote + all Consumers). Works independently of how the Worker was started (systemd or direct CLI), but only if the Worker is actually running.</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 20.2679%;">Router.php:239</td></tr></tbody></table>

---

### Adding a New Task

To add a new job that the Worker can execute, two files must be modified. Because Consumer processes load PHP classes after being forked from the Zygote, changes to existing methods take effect on the next consumer start without restarting the Router or Zygote. Adding a brand new method requires registering it in the trait (step 2) and may need a full restart only for the trait to be recognized.

#### Step 1: Implement the method in Consumer.php

Add your method inside the `//region Methods` section of `Consumer.php`:

```php
protected function convertPdf(int $documentId) {
    global $adb;
    // ... perform work ...
    $this->client->return($result);
}
```

Useful tools available inside the Consumer:

- `$this->client->return($data)` — send a successful response
- `$this->client->error("message")` — signal an error
- `$this->sendToAll(Roles::web, $userId, 'eventName', $data)` — push a live event to browsers
- `$this->later(function() { ... })` — schedule work for the next event-loop tick

#### Step 2: Register the method in Methods.php

Add the method signature to `ScriptMethodsTrait` in `Protocol/Methods.php`:

```php
/** @return void */
public function convertPdf(int $document_id) {
    return $this->call(__FUNCTION__, get_defined_vars(), ['wait' => false]);
}
```

#### Calling the new method

From PHP code (the connector is reused and reconnects on failure):

```php
$conn = ScriptConnector::reuseInstance();
if ($conn->tryConnect()) {
    $conn->convertPdf(42);
}
```

From the command line:

```bash
php -f include/Services/Worker/run.php call convertPdf 42
```

A method that belongs in `WorkerMethodsTrait` instead (executed by the Zygote without forking a Consumer) uses the same two-step process: add the signature to the trait and implement it in `Zygote.php` or `Router.php`.

---

### Broadcasting Events to the Browser

From any Consumer method, you can push real-time data to connected browsers:

```php
// Send to a specific user
$this->sendToAll(Roles::web, $userId, 'myEvent', ['progress' => 50]);

// Send to ALL users
$this->sendToAll(Roles::web, 0, 'myEvent', $data);
```

On the browser side, events are received through `SharedWorker.js` and `EventsClient.js`. The Router maintains a tree of connected clients indexed by user ID, session ID, and instance ID, and dispatches events to the matching tabs.

---

### Operation

#### Startup

The Worker can be started through systemd socket activation or directly from the command line for testing and custom setups:

```bash
php -f include/Services/Worker/run.php
```

With systemd socket activation:

1. systemd creates the Unix socket (`cache_local/worker.sock`)
2. On the first connection, systemd launches `php run.php`
3. `run.php` loads the CRM environment (config, database, etc.)
4. `Worker::start()` calls `pairedFork()`, splitting into two processes: 
    - The parent becomes the Router (socket listener)
    - The child becomes the Zygote (consumer pool manager)
5. When a job arrives, the Zygote forks a Consumer to execute it
6. The Consumer runs the job and exits

#### Installation

```
sudo tools/worker install
```

This copies the systemd unit files (`vte-worker@.service` and `vte-worker@.socket`) to `/etc/systemd/system/`, enables the socket, and starts it. Both files are [systemd templates](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) that take the relative path from `/var/www/html` as the instance parameter (e.g. `vte-worker@vte-agentic`), which allows running multiple Workers for different VTE installations on the same machine.

If your installation differs from `/var/www/html/PATH`, you must edit the templates manually or with `systemctl edit [--full]` for both `vte-worker@.socket` and `vte-worker@.service`, before installing.

#### Commands

<table id="bkmrk-command-effect-tools" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 29.3453%;">Command</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 70.7739%;">Effect</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 29.3453%;">`tools/worker install`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 70.7739%;">Install systemd units, enable and start the socket</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 29.3453%;">`tools/worker restart`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 70.7739%;">Send a restart signal to the running Worker</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 29.3453%;">`tools/worker status`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 70.7739%;">Print uptime, number of active consumers, queued jobs</td></tr></tbody></table>

#### Signals

<table id="bkmrk-signal-effect-sigter" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 29.7025%;">Signal</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 70.4167%;">Effect</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 29.7025%;">`SIGTERM` / `SIGINT`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 70.4167%;">Graceful shutdown: stop accepting new connections, wait for all running Consumers to finish, then exit.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 29.7025%;">`SIGHUP` / `SIGUSR1`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 70.4167%;">Reload.</td></tr></tbody></table>

#### Configuration

<table id="bkmrk-setting-location-max" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 35.2977%;">Setting</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 64.8215%;">Location</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 35.2977%;">Maximum concurrent Consumers</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 64.8215%;">`Zygote.php:29 — CONSUMERS_MAX`</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 35.2977%;">Socket path</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 64.8215%;">`systemd/vte-worker@.socket — ListenStream`  
  
`config.inc.php — $worker_socket_URL`  
</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 35.2977%;">Log file</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 64.8215%;">`logs/worker.log`</td></tr></tbody></table>

---

### Troubleshooting

#### Enable Logging

Logging is disabled by default. To enable it, set the static flag before starting the Worker:

```
\Vtenext\Services\Worker\Worker::$enableLog = true;
```

Logs are written to `logs/worker.log`. The log format includes a timestamp, the process role, and the message.

In the browser, put the console verbosity to debug.

#### Common Issues

<table id="bkmrk-symptom-likely-cause" style="width: 100%; border-collapse: collapse; margin: 12px 0px; height: 194px;"><tbody><tr style="background: rgb(238, 238, 238); height: 35px;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 28.011%; height: 35px;">Symptom</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 71.989%; height: 35px;">Likely Cause</th></tr><tr style="height: 53px;"><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 28.011%; height: 53px;">Connection refused</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 71.989%; height: 53px;">The Worker is not running or the socket path is incorrect. Check `$worker_socket_URL` in `config.inc.php` and verify the socket file exists.</td></tr><tr style="height: 53px;"><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 28.011%; height: 53px;">Consumer not starting</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 71.989%; height: 53px;">Process limit reached (`RLIMIT_NPROC`). The Worker attempts to raise it to 1000 + `CONSUMERS_MAX` at startup.</td></tr><tr style="height: 53px;"><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 28.011%; height: 53px;">Job queued but never runs</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 71.989%; height: 53px;">All 10 Consumer slots are occupied by long-running tasks. Check `tools/worker status` for current usage.</td></tr></tbody></table>

#### SharedWorker

When the SharedWorker is active, [debugging can done differently on each browser](https://lihautan.com/Debugging%20web%20workers):

- **Firefox** — visit [about:debugging#workers](about:debugging#workers) (copy-paste) and press **Debug** then you can inspect everything and put breakpoints. Logs are also shown in the first tab that spawned the SW (*yes 3 times, it's a browser bug as of June 2026*) and in the browser console in multi-process mode (Ctrl+Shift+J).
- **Chrome** — visit [chrome://inspect/#workers](chrome://inspect/#workers) (copy-paste) and press **Inspect**, same story. No logs are shown in individual tabs.

---

### Technical limitations

#### Browsers connections cap

Web connections from browser tabs rely on Server-Sent Events (SSE), which keep a long-lived HTTP connection open to the server. Browsers enforce a hard limit of **6 concurrent connections per domain** (HTTP/1.1). The `SharedWorker.js` script multiplexes all tabs through a single SSE connection per browser, bypassing the limit entirely.

- When SharedWorker is not available (older or unsupported browsers), each tab opens its own SSE connection and the 6-connection limit per domain applies.
- The Worker's `CONSUMERS_MAX` limit (10 concurrent Consumer processes) is a separate server-side concern — it caps how many jobs run in parallel, not how many connections can remain open.

#### Disconnection detection

TCP provides no built-in notification when a peer disconnects. To detect that a browser has closed the connection, PHP must attempt to write to the socket. The `WebConnector` handles this by writing a newline to the output buffer at every read tick (default every 10 seconds). This means a disconnected browser may not be detected for such time, and an inactive but still connected tab generates a small amount of periodic traffic.

#### Xdebug and `set_time_limit`

When the Xdebug extension is loaded, `set_time_limit(0)` (which normally removes the execution time limit) does not work reliably. Xdebug overrides PHP's internal timer and enforces its own `xdebug.max_nesting_level` and related constraints, which can cause long-running Consumer methods to be terminated prematurely on development environments where Xdebug is active. If the Worker behaves unexpectedly during development, disable Xdebug or set `xdebug.mode=off` in the PHP configuration.

---

### File Reference

```mysql
include/Services/Worker/
├── run.php                          # Entry point (called by systemd)
├── Worker.php                       # Base Worker class + WorkerTrait
├── Router.php                       # Socket listener, client registry, SSE push
├── Zygote.php                       # Consumer pool manager, job queue
├── Consumer.php                     # Task executor (llmChat, elaborateRag, etc.)
├── utils.php                        # shared utilities
├── Protocol/
│   ├── Protocol.php                 # Wire protocol encode/decode
│   ├── Roles.php                    # Role constants (router, zygote, consumer, etc.)
│   ├── Methods.php                  # Method traits (ScriptMethods, WebMethods, WorkerMethods)
│   ├── ClientHandler.php            # Server-side connection handler
│   ├── BaseConnector.php            # Client-side connector base class
│   ├── ScriptHandler.php            # Handler for script connections
│   ├── ScriptConnector.php          # Connector for PHP scripts
│   ├── WebHandler.php               # Handler for web connections (SSE)
│   └── WebConnector.php             # Connector for browser (HTTP to socket bridge)
├── systemd/
│   ├── vte-worker@.service          # systemd service template
│   └── vte-worker@.socket           # systemd socket template
├── SharedWorker.js                  # Browser SharedWorker (multiplexes connections)
├── TabClient.js                     # Client for tab-to-tab messaging
└── EventsClient.js                  # Event subscription client in the browser

modules/Utilities/Worker.php         # HTTP bridge (Apache -> Worker socket)
modules/Settings/WorkerConfig.php    # Admin settings panel
modules/Settings/WorkerConfig.tpl    # Smarty template for the admin panel
tools/worker                         # CLI management tool
logs/worker.log                      # Log file
```

# Agent orchestrator

Python service providing AI agent chat, MCP tools integration, and RAG on CRM documents, based on FastAPI and LangChain. Called by the Worker's Consumer processes via HTTP/SSE.

---

### System Requirements

`llama-cpp-python` is installed as a **pre-built wheel** (not compiled from source). `requirements.txt` specifies the **Vulkan** variant via `--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/vulkan`. Other backends are available by changing the index URL:

<table id="bkmrk-variant-table"><tbody><tr><th>Backend</th><th>Index URL suffix</th></tr><tr><td>cpu</td><td>`.../whl/cpu`</td></tr><tr><td>vulkan **(default)**</td><td>`.../whl/vulkan`</td></tr><tr><td>cuda</td><td>`.../whl/cuda`</td></tr><tr><td>rocm</td><td>`.../whl/rocm`</td></tr><tr><td>metal</td><td>`.../whl/metal`</td></tr><tr><td>sycl</td><td>`.../whl/sycl`</td></tr></tbody></table>

Docker image installs `libvulkan1`. GPU access (`/dev/dri`) is commented out in `compose.yaml` by default — uncomment for hardware acceleration. Falls back to CPU without GPU.

The x86-64-v2 baseline or equivalent is required by NumPy's pre-built wheels (see [NumPy SIMD build options](https://numpy.org/doc/stable/reference/simd/build-options.html)). CPUs without these instructions can still run the orchestrator by **recompiling NumPy from source** with reduced SIMD flags (`NPY_DISABLE_CPU_FEATURES`), or by using a distro that ships a compatible build.

---

### Architecture Summary

Three layers:

1. **PHP CRM** (Worker Consumer), calls orchestrator via HTTP
2. **Python orchestrator** (FastAPI, Docker)
3. **LLM / MCP servers / Chroma**

Relevant Consumer methods: `llmChat()` (chat relay), `elaborateRag()` (document upload + vector build).

---

### RAG Document Building

#### Indexing (Elaboration)

Triggered on agent save with the Documents feature enabled. The Worker Consumer:

1. Reads selected CRM Documents
2. `POST /rag/keep` — prunes stale docs from orchestrator
3. `POST /rag/upload` — uploads new/changed files (multipart), stored as `docs/shared/<md5>.<ext>`, symlinked into `docs/<agent_id>/`
4. `POST /rag/build` — indexes all docs for the agent into Chroma at `vectors/<agent_id>/`.

#### Querying (Runtime)

With `rag: true` in `/agent/run`, Python injects a `query_documents` tool. The LLM decides when to call it. The orchestrator decomposes the question into ≤3 sub-questions (`needs_retrieval` flag), queries Chroma per sub-question, deduplicates by `doc_id`, re-ranks with FlashRank, and returns context. The LLM answer is grounded strictly in retrieved context.

---

### Python Orchestrator Endpoints

The Python service exposes the following REST endpoints. All are mounted on the FastAPI app at port 8120.

<table id="bkmrk-endpoint-table"><tbody><tr><th>Endpoint</th><th>Description</th></tr><tr><td>`POST /agent/run`</td><td>Agent loop: LLM + MCP tools + guardrails + optional RAG. SSE or JSON.</td></tr><tr><td>`POST /tools/inspect`</td><td>Introspect MCP server tools.</td></tr><tr><td>`POST /rag/build`</td><td>Index documents for an `agent_id` into Chroma.</td></tr><tr><td>`POST /rag/run`</td><td>Query vector store with question decomposition.</td></tr><tr><td>`POST /rag/keep`</td><td>Prune agent's doc symlinks to match `{filename: md5}`.</td></tr><tr><td>`POST /rag/upload?agent_id=`</td><td>Upload file to shared pool + symlink into agent's dir.</td></tr></tbody></table>

---

### Installation &amp; Configuration

#### Docker Setup

The Python orchestrator runs in Docker. Quick start:

```bash
cd plugins/agent
docker compose up -d --build
```

Verify: `curl http://localhost:8120/docs` should show the Swagger UI.

**Port** `127.0.0.1:8120:8120` — bound to localhost only, **MUST NOT** be publicly exposed.

[Here](https://usermanual.vtenext.com/books/developers/page/install-docker-for-kitt) a quick snippet to install docker.

#### Environment Variables

<table id="bkmrk-env-table"><tbody><tr><th>Variable</th><th>Default</th></tr><tr><td>`HOST` (inside the container)</td><td>`0.0.0.0`</td></tr><tr><td>`PORT`</td><td>`8120`</td></tr><tr><td>`EMBED_MODEL`</td><td>`nomic-ai/nomic-embed-text-v2-moe-GGUF:Q8_0`</td></tr><tr><td>`RERANK_MODEL`</td><td>`ms-marco-MiniLM-L-12-v2`</td></tr><tr><td>`HF_CACHE_DIR`</td><td>`/app/hf_cache`</td></tr><tr><td>`DOCUMENTS_DIR`</td><td>`/app/docs`</td></tr><tr><td>`VECTORS_DIR`</td><td>`/app/vectors`</td></tr></tbody></table>

---

### Troubleshooting

- **Startup slow:** Embedding model downloads from HuggingFace on first container start — cached in `HF_CACHE_DIR` afterwards.
- **Empty docs:** `/rag/build` raises `RuntimeError` if `docs/<agent_id>/` is empty.
- **GPU not used:** Uncomment `/dev/dri` in `compose.yaml`. Falls back to CPU otherwise.
- **CPU compat:** Verify with `/lib64/ld-linux-x86-64.so.2 --help`

---

### File Reference

```mysql
plugins/agent/
├── compose.yaml
├── app.Dockerfile
├── requirements.txt
├── src/vte_agent/
│   ├── __main__.py
│   ├── config.py
│   ├── schemas.py
│   ├── agent.py              # /agent/run, /tools/inspect, calculator + rag tools
│   ├── rag.py                # /rag/* endpoints
│   ├── docs.py               # doc loaders
│   ├── models.py             # GGUFEmbeddings
│   ├── user_manual.py        # builtin vtenext user manual search tool
│   └── utils.py
├── docs/          
│   ├── shared/        # <md5>.<ext> — deduplicated by content hash
│   └── <agent_id>/    # symlinks → ../shared/<md5>.<ext>
└── vectors/
    └── <agent_id>/    # chroma.sqlite3, parent_docs.json, description.txt

cache_local/
└── huggingface/       # local models cache (embedding, rerank)
```

# SDK uitypes in ProcessMaker actions

SDK uitype fields will all be shown as uitype 1 (text) in ProcessMaker actions

# New properties for processes

If you don't want to attach external dynamic form emails to the record you can set to `false` this prop:  
`modules.processes.dfe.save_cache_link`

If you assign a dynaform to email or portal, you can force the assigned user of the record Processes with these props:  
`modules.processes.assigned_user_id.email`  
`modules.processes.assigned_user_id.portal`  
With the value `related_to`, the process will be assigned to the owner of the linked record. Alternatively, you can enter the ID (int) of a user/group.

# Stronger hashing algorithm for user password

Replaced the password hashing algorithm from md5 to argon2id.

# REST Webservice Methods

Below are all the SDK functions for registering REST methods and all the properties for describing them according to the OpenAPI standard.

## Registering custom Webservice methods

```
SDK::setRestOperation($name, $handlerFilePath, $handlerMethodName, $params, $permission, $mcpSupport, $info)
```

- `$name`: method name called by REST webservice;
- `$handlerFilePath`: file path where the function is defined;
- `$handlerMethodName`: name of the function to use from the specified handler file;
- `$params`: if provided, the associative array of parameter names with their definition (see [Parameter Definition (`$params`)](#bkmrk-parameter-definition))
- `$permission` (since vtenext 23.08): one of “read”, “write” or “readwrite”, describing the kind of operation of this webservice. Used with additional accesskey for the users;
- `$mcpSupport` (since vtenext 26.XX): default to 0 ("disabled"), it can be set to 1 to allow the registration of this method to MCP servers;
- `$info` (since vtenext 26.XX): additional information used to display this method, for documentation purposes (see [`$info` Parameter](#bkmrk-%24info-parameter))

This function returns the id of the new Webservice if created successfully, and `false` otherwise.

```
SDK::setRestOperationInfo(string $name, array $operation = [], array $parameters = [], array $requestExamples = [], array $responses = [])
```

This function takes `name` as the name of the operation, and the arrays described in the [`$info` Parameter](#bkmrk-%24info-parameter) section.

This function returns `true` if the webservice information was set up successfully, and `false` otherwise.

### Parameter Definition (`$params`)

The `$params` function parameter is an associative array that has the name of the parameter as the key, and the type of the parameter.

The types currently registered are `string`, `encoded`, `datetime`, `double`, and `boolean`.

### `$info` Parameter

The `$info` parameter of the function `SDK::setRestOperation` is an optional associative array that has the keys **`operation`**, **`parameters`**, **`examples`**, and **`responses`**.

This parameter is passed accordingly to `SDK::setRestOperationInfo`, which is the method responsible for filling the information for the Webservice method that is needed for the OpenAPI specification and the MCP servers.

#### `operation` Key

The `operation` key is an associative array with two optional keys:

- `description`: `string`, human-readable description of the Webservice method;
- `tags`: `string`, comma-separated list of tags to group the Webservice method with;
- `tool_description`: `string`, specific description for MCP tool, if not provided the MCP server will default the generic description.

#### `parameters` Key

The `parameters` key is an associative array with the name of the parameter as the key, and an associative array that contains additional information, used for documentation purposes.

This information is used to further document the parameters of the webservice method, while still adhereing to the conventions already established with the use of this SDK.

The associative array for the parameter has the following, optional, attributes:

- `required`: `bool`, whether the parameter is mandatory;
- `default_value`: `mixed`, the default value of the parameter;
- `description`: `string`, informative description of the parameter;
- `example`: `mixed`, an example value of the parameter;
- `extra`: associative `array`, complementary information that can't be documented otherwise.

The `extra` attribute has the following, optional, attributes, that are of type `string` except when noted:

- `type`: used, for example, when the parameter is registered as `encoded`, allowing it to "cast" it as `object` or `array`;
- `schema`: the name of the schema definition that describes the structure of the parameter, used when `type` is `object`;
- `items`: required when `type` is `array`, specifies the typing of the values contained in the array; it can be a simple type such as `string`, `int`, `float`, `double`, `bool`, or the name of a schema definition;
- `enum`: `array` of values that are supported by the parameter.

#### `examples` Key

The `examples` key is a list of associative arrays that showcase a full usage of the webservice method. This key is optional.

The associative array of a single example has the following attributes:

- `name`: `string`, the name chosen for the example;
- `summary`: `string`, brief description of the example;
- `data`: associative `array`, the payload passed by the request.

#### `responses` Key

The `responses` key is an associative array with the status code of the response as the key, and an associative array that contains the information of the response.

The associative array of a response has the following optional attributes:

- `description`: `string`, brief description of the response;
- `fields`: associative `array`, the payload of the response, see [`fields` Attribute](#bkmrk-fields-attribute)
- `examples`: associative `array`, example representations of the response, see [`examples` Key](#examples-key);
- `ref`: `string`, predefined information of the response; if provided

##### `fields` Attribute

The `fields` attribute is an associative array with the name of the field as the key, and an associative array that contains the information of the field.

The associative array of a field has the following optional attributes:

- `type`: the type of the field, can be `string`, `int`, `float`, `double`, `bool`, `object`, or `array`; if omitted, the field can be of "any" type;
- `schema`: if `type` is `object`, the name of the schema definition that describes the structure of the field;
- `items`: required when `type` is `array`, specifies the typing of the values contained in the array; it can be a simple type such as `string`, `int`, `float`, `double`, `bool`, or the name of a schema definition;
- `required`: `bool` whether the field is mandatory;
- `default`: `mixed`, the default value of the field;
- `description`: `string`, informative description of the field;
- `example`: `mixed`, an example value of the field;
- `enum`: `array` of values that are supported by the field.

## Deregistering custom Webservice methods

```
SDK::unsetRestOperation(string $name)
```

- `$name`: name of the webservice method to delete.

This function returns `true` on success and `false` on failure.

```
SDK::unsetRestOperationInfo(string $name, string $type = 'all')
```

- `$name`: name of the webservice method whose information need to be deleted;
- `$type`: the type of information to delete, can be `all`, `operation`, `parameters`, `requestExamples`, `responses`, and `responseExamples`.

This function returns `true` on success and `false` on failure.

## Registering custom Webservice schemas

```
SDK::setRestOperationSchema(string $name, array $fields)
```

- `$name`: name of the schema;
- `$fields`: see [`fields` Attribute](#bkmrk-fields-attribute).

This function returns the id of the new webservice schema if created successfully, and `false` otherwise.

## Deregistering custom Webservice schemas

```
SDK::unsetRestOperationSchema(string $name)
```

`$name` is the method name of the webservice schema to delete.

This function returns `true` on success and `false` on failure.

# MCP Server

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a standard that enables AI applications to connect to external systems.  
In VTENEXT, this standard is used to expose REST APIs, custom methods and processes to AI applications, to enable them to interact with the CRM itself.

Currently VTENEXT has set up a basic MCP server with the main webservices (referred here as *tools*) that ensure a wide extent of interoperability with the CRM; the user can choose to extend this server by registering other webservices to it, or even create *custom tools* to be used with the servers. Be mindful that, with roughly more than 30 tools registered to a MCP server, the accuracy of the server might degrade.

In VTENEXT, the MCP servers are fundamentally handled like any other webservice, but they have their own SDK methods to work with them.

## Tools registered in the base MCP server (REST name: `mcp`)

- `create`
- `delete`
- `describe`
- `listtypes`
- `query`
- `relate`
- `retrieve`
- `retrieveInventory`
- `get_currencies`
- `updateRecord`
- `convert_lead`
- `get_current_context`
- `process_text`
- `summarize`
- `tools_manual`
- `translate`

## SDK methods for working with MCP servers

### Setting up a MCP server

```
SDK::setMcpServer(string $name, ?string $description = null, bool $isActive = true, ?string $operationName = null)
```

- `$name`: the name of the MCP server;
- `$description`: human-readable description of the MCP server;
- `$isActive`: set it to `false` to prevent the Server from accepting requests;
- `$operationName`: the name to give to the corresponding REST Webservice method; defaults to the name of the MCP server prefixed with `mcp.`.

This function returns the id of the MCP server if created successfully, and `false` otherwise.

### Registering a tool to a MCP server

```
SDK::registerMcpTool(string $mcp, string $name, string $type, ?string $tool_name = null, ?string $description = null)
```

- `$mcp`: the name of the MCP server;
- `$name`: the name of the webservice method or the custom MCP tool;
- `$type`: the type of the tool to register, can be `operation` for the webservice method or `custom` for the custom tool;
- `$tool_name`: the name to give to the registered tool, defaults to `$name`;
- `$description`: MCP-specific information to give to the registered tool, will take precedence over any information provided by the webservice method/custom tool.

If successful, this function returns an array with the id of the MCP server and the id of the webservice method/custom tool; this function returns `false` on failure.

### Defining a custom tool for MCP servers

```
SDK::setMcpTool(string $name, string $handlerFilePath, ?string $handlerMethodName = null, ?string $description = null, ?array $inputSchema = null, ?array $outputSchema = null)
```

- `$name`: the name of the custom tool;
- `$handlerFilePath`: file path where the function is defined;
- `$handlerMethodName`: name of the function to use from the specified handler file;
- `$description`: informative description of the custom tool;
- `$inputSchema`: the definition of the function input;
- `$outputSchema`: the definition of the function output.

`$inputSchema` and `$outputSchema` follow the [JSON Schema 2020-12 Specification](https://json-schema.org/draft/2020-12).

**N.B.**: the function should always return an array and, if possible, the array should be associative.

### Removing a MCP server

```
SDK::unsetMcpServer(string $mcp, bool $force = false)
```

- `$mcp`: the name of the MCP server to delete;
- `$force`: if `true`, force the deletion of the MCP server, even if it has tools registered to it.

This function returns `true` on success and `false` on failure.

### Deregistering a tool from a MCP server

```
SDK::unregisterMcpTool(string $mcp, string $name, string $type)
```

- `$mcp`: the name of the MCP server;
- `$name`: the name of the tool to deregister;
- `$type`: the type of the tool to deregister, can be `operation` or `custom`.

This function returns `true` on success and `false` on failure.

### Deregistering a custom tool

```
SDK::unsetMcpTool(string $name)
```

- `$name`: name of the custom tool to delete.

This function returns `true` on success and `false` on failure.

# Using AI

<span>Tips on how to use LLM to help writing/refactoring vte code</span>

# Skill - Review SDK files

Skill to (try to) port all custom files to vtenext 26 and make them compatible with vtenext 26.

#### Install to Codex

Create the folder `review-sdk-26` in the skills folder of your IDE example: \[...\]/.codex/skills/ with the structure:

<div id="bkmrk-review-plugin-26%2F%E2%94%9C%E2%94%80%E2%94%80"><div>review-sdk-26/  
├── [SKILL.md](#bkmrk-skill.md)  
└── agents/  
 └── [openai.yaml](#bkmrk-openai.yaml)</div></div>##### SKILL.md

```markdown
---
name: review-sdk-26
description: Revise the SDK code to be compatible with VTENEXT version 26.*
---

# review-sdk-26

Review and update PHP files (>=7.x) to make them:
* Compatible with PHP 8.3
* Aligned with the new project guidelines
* More robust, typed, and modern

You are working on existing VTENEXT application code. DO NOT rewrite everything from scratch.
Keep the original logic, improving it only where necessary.
The project's root directory is specified in the config.inc.php file in `$root_directory`. If that directory is not accessible, use the directory containing the file config.inc.php.

## When to use

This skill should be used after the update to VTENEXT 26.* to ensure that all custom PHP files are compatible with PHP 8.3 and follow the new project guidelines.

## Input

* List of PHP files, if they are not provided execute `tools/skills/review-sdk-26/get_php_customizations.php` to get the list of files to scan and review. The list of files should be provided in batches of 10 if there are too many files to review at once. Do not analyze, process or propose changes to files that are not in the list obtained.

## Instructions

### 1. Prepare the environment

* Create folder modules/SDK/src/review_sdk_26, if already exists empty it
* If there are too many files, run the skill on 10 files at a time (batch)
* If you are running a batch, do not empty the folder
* Also copies unpatched files to the review_sdk_26 folder to maintain the same structure and allow manual review of all files
* If you find any hardcoded credentials or sensitive data while analyzing the code, stop everything and let me know to move this data to a separate configuration file


### 2. PHP 8.3 Compatibility Analysis

* Fix weak comparisons as described [here](https://usermanual.vtenext.com/books/developers/page/weak-comparisons)
* Add type casts where necessary to functions that require a string as parameter, for example `trim()` `strpos()` and `stripos()`
* Do not replace functions if the existing ones work on PHP 8.3
* For empty string comparisons, replace with the `empty()` function
* For comparisons with `$mode` or `$sdk_mode`, the creation mode is usually checked and the empty string comparison is fine. `=== ''`

#### example

old code:
```php
if ($recordid == '') {
```

new one:
```php
if (empty($recordid)) {
```

### 3. Refactoring

#### RequestHandler

* As described [here](https://usermanual.vtenext.com/books/developers/page/requesthandler), access to the superglobal variables `$_REQUEST`, `$_GET`, and `$_POST` is no longer allowed.
* Replace reading their values ​​with the `RH::r`, `RH::g`, and `RH::p` methods of the `RequestHandler` class found in include/utils/RequestHandler.php.
* When writing to superglobals, use the `RH::push_*` and `RH::pop_*` methods as described in the link above.
* Remove calls to `vtlib_purify()`; RequestHandler already handles them internally.

#### Database best practices and escaping

* Apply the new best practices as described [here](https://usermanual.vtenext.com/books/developers/page/escaping-rules)
* In particular, replace `query_result` with `query_result_no_html` and `fetchByAssoc(...)` with `fetchByAssoc(..., -1, false)`
* If HTML is generated in PHP and assigned to a smarty variable, use the `\Vtenext\Types\HtmlString` class to enclose the HTML string
* In custom reports the method `getSDKBlock()` should be return a `\Vtenext\Types\HtmlString` object instead of a string

#### Other substitutions
* Remove include/require of the file `Smarty_setup.php` that no longer exists
* Replace references to the `vtigerCRM_Smarty` class with `VteSmarty`
* Replace `session_start()` con `VteSession::start()`
* For reading and writing in session use the methods of the `VteSession` class found in `include/VteSession.php`

### 4. Coding style

* Apply the guidelines described [here](https://usermanual.vtenext.com/books/developers/page/coding-style)
* Add license header to the top of the file or update it if already present as described in the link above
* Remove the closing tag `?>` at the end of PHP files

### 5. Other examples
If you are modifying a method called get_dependents_list or get_related list and towards the end there is a piece of code similar to `$return_value['CUSTOM_BUTTON'] = $button;`, ensure that the $button variable is of type `\Vtenext\Types\HtmlString`, for example in this way:
```
$return_value['CUSTOM_BUTTON'] = new \Vtenext\Types\HtmlString($button);
```

### 6. Important Rules

* Do NOT change the functional behavior
* Do NOT introduce external dependencies
* Maintain backward compatibility if possible
* Clear code > "smart" code
* Do NOT directly modify the original files
* Do NOT return complete updated code
* Perform a final check on the files with `php -l` or with `php8.3 -l` if the former is not at version 8.3
* When you have finished analyzing all the files suggest to re-check the files copied into the folder

## Output

* List modified files
* Notify the user that modified files will be found in that folder so they can manually verify them and overwrite the originals after their review
```

##### openai.yaml

```yaml
interface:
  display_name: "Review SDK files"
  short_description: "Review the code of SDK customizations and make it compatible with vtenext 26"
  default_prompt: "Use $review-sdk-26 to review code of SDK customizations and make it compatible with vtenext 26."
```

#### Prompt

Select the skill by digit $review-sdk... and indicate the path to the folder.  
example:

```markdown
[$review-sdk-26]([...]/.codex/skills/review-sdk-26/SKILL.md) in the folder [vtenext folder].
The skill contains links to web pages with details and usage examples; read those pages.
```

# Skill - Review plugin

Skill to review the code of a specific folder (plugin) and make it compatible with vtenext 26.

#### Install to Codex

Create the folder `review-plugin-26` in the skills folder of your IDE example: \[...\]/.codex/skills/ with the structure:

<div id="bkmrk-review-plugin-26%2F%E2%94%9C%E2%94%80%E2%94%80"><div>review-plugin-26/  
├── [SKILL.md](#bkmrk-skill.md)  
└── agents/  
 └── [openai.yaml](#bkmrk-openai.yaml)</div></div>##### SKILL.md

```markdown
---
name: review-plugin-26
description: Revise the php code to be compatible with VTENEXT version 26.*
---

# review-plugin-26

Review and update PHP files (>=7.x) to make them:
* Compatible with PHP 8.3
* Aligned with the new project guidelines
* More robust, typed, and modern

You are working on a plugin for VTENEXT application which needs to be reviewed to the new version. DO NOT rewrite everything from scratch.
Keep the original logic, improving it only where necessary.

## Instructions

### 1. Prepare the environment

* Duplicate the folder
* If there are too many files, run the skill on 10 files at a time (batch)
* If you are running a batch, do not empty the folder
* Also copies unpatched files to the new folder to maintain the same structure and allow manual review of all files
* If you find any hardcoded credentials or sensitive data while analyzing the code, stop everything and let me know to move this data to a separate configuration file

### 2. PHP 8.3 Compatibility Analysis

* Fix weak comparisons as described [here](https://usermanual.vtenext.com/books/developers/page/weak-comparisons)
* Add type casts where necessary to functions that require a string as parameter, for example `trim()` `strpos()` and `stripos()`
* Do not replace functions if the existing ones work on PHP 8.3
* For empty string comparisons, replace with the `empty()` function
* For comparisons with `$mode` or `$sdk_mode`, the creation mode is usually checked and the empty string comparison is fine. `=== ''`

#### example

old code:
```php
if ($recordid == '') {
```

new one:
```php
if (empty($recordid)) {
```

### 3. Refactoring

#### RequestHandler

* As described [here](https://usermanual.vtenext.com/books/developers/page/requesthandler), access to the superglobal variables `$_REQUEST`, `$_GET`, and `$_POST` is no longer allowed.
* Replace reading their values ​​with the `RH::r`, `RH::g`, and `RH::p` methods of the `RequestHandler` class found in include/utils/RequestHandler.php.
* When writing to superglobals, use the `RH::push_*` and `RH::pop_*` methods as described in the link above.
* Remove calls to `vtlib_purify()`; RequestHandler already handles them internally.

#### Database best practices and escaping

* Apply the new best practices as described [here](https://usermanual.vtenext.com/books/developers/page/escaping-rules)
* In particular, replace `query_result` with `query_result_no_html` and `fetchByAssoc(...)` with `fetchByAssoc(..., -1, false)`
* If HTML is generated in PHP and assigned to a smarty variable, use the `\Vtenext\Types\HtmlString` class to enclose the HTML string
* In custom reports the method `getSDKBlock()` should be return a `\Vtenext\Types\HtmlString` object instead of a string

#### Other substitutions
* Remove include/require of the file `Smarty_setup.php` that no longer exists
* Replace references to the `vtigerCRM_Smarty` class with `VteSmarty`
* Replace `session_start()` con `VteSession::start()`
* For reading and writing in session use the methods of the `VteSession` class found in `include/VteSession.php`

### 4. Coding style

* Apply the guidelines described [here](https://usermanual.vtenext.com/books/developers/page/coding-style)
* Add license header to the top of the file or update it if already present as described in the link above
* Remove the closing tag `?>` at the end of PHP files

### 5. Other examples
If you are modifying a method called get_dependents_list or get_related list and towards the end there is a piece of code similar to `$return_value['CUSTOM_BUTTON'] = $button;`, ensure that the $button variable is of type `\Vtenext\Types\HtmlString`, for example in this way:
```
$return_value['CUSTOM_BUTTON'] = new \Vtenext\Types\HtmlString($button);
```

### 6. Important Rules

* Do NOT change the functional behavior
* Do NOT introduce external dependencies
* Maintain backward compatibility if possible
* Clear code > "smart" code
* Do NOT directly modify the original files
* Do NOT return complete updated code
* Perform a final check on the files with `php -l` or with `php8.3 -l` if the former is not at version 8.3
* When you have finished analyzing all the files suggest to re-check the files copied into the folder

## Output

* List modified files
* Notify the user that modified files will be found in that folder so they can manually verify them and overwrite the originals after their review
```

##### openai.yaml

```yaml
interface:
  display_name: "Review Plugin"
  short_description: "Review the code of a specific folder (plugin) and make it compatible with vtenext 26"
  default_prompt: "Use $review-plugin-26 to review code of a specific folder (plugin) and make it compatible with vtenext 26."
```

#### Prompt

Select the skill by digit $review-plugin... and indicate the path to the folder.  
example:

```markdown
[$review-plugin-26]([...]/.codex/skills/review-plugin-26/SKILL.md) on the folder [your folder].
The skill contains links to web pages with details and usage examples; read those pages.
```

# Help us improve

**Developer documentation is constantly evolving!**

We are constantly committed to improving the quality and completeness of technical documentation to make your work simpler and more efficient. Your feedback is essential to us.

#### **How you can help us**

There are several ways you can contribute to improving the documentation and code:

- **Report missing or incomplete documentation** - If you can't find the information you're looking for
- **Request clarifications** - If a section is unclear or ambiguous
- **Propose improvements** - Do you have ideas on how to make the documentation more useful?
- **Suggest code optimizations** - Have you found a better way to implement something?
- **Report errors or issues** - Have you found a bug or inconsistency?
- **Share best practices** - Have you developed interesting patterns or solutions?

#### **Contact us via email**

Send your requests, suggestions, or questions to:

**Email:** <support@vtenext.com>

In the email subject line, specify:

- `[DOC]` for documentation requests
- `[BUG]` for bug reports
- `[SUGG]` for new feature suggestions
- `[HELP]` for assistance requests

Your contribution makes a difference! Every report, suggestion, or idea helps us create better documentation for developers.

**Thank you for your support and collaboration!**

# Quick snippets

# Install docker for Kitt

#### Ubuntu

```bash
#!/bin/bash

set -euo pipefail

SELF="${BASH_SOURCE[0]}"
SELF="$(realpath -ms "$SELF")"
cd "$(dirname "$SELF")" || exit 2

if ((UID)) && ! [[ -v SUDO_USER ]]; then
    exec sudo "$SELF" "$@"
fi

curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

chmod a+r /etc/apt/keyrings/docker.asc

arch="$(dpkg --print-architecture)"
release="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")"

cat <<EOF >/etc/apt/sources.list.d/docker.list
deb [arch=${arch} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $release stable
EOF

apt update

pkgs=(
    docker-ce
    docker-ce-cli
    containerd.io
    docker-buildx-plugin
    docker-compose-plugin
)

apt install -y  "${pkgs[@]}"

systemctl enable --now docker.service
```