https://github.com/lexo-ch/lexo-forms
Forms management and CleverReach integration
https://github.com/lexo-ch/lexo-forms
php8 plugin releases updates wordpress wordpress-plugin
Last synced: 5 months ago
JSON representation
Forms management and CleverReach integration
- Host: GitHub
- URL: https://github.com/lexo-ch/lexo-forms
- Owner: lexo-ch
- License: gpl-2.0
- Created: 2025-10-31T11:42:15.000Z (8 months ago)
- Default Branch: master
- Last Pushed: 2026-01-12T10:09:37.000Z (6 months ago)
- Last Synced: 2026-01-12T18:51:00.956Z (6 months ago)
- Topics: php8, plugin, releases, updates, wordpress, wordpress-plugin
- Language: PHP
- Homepage:
- Size: 518 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# LEXO Forms
WordPress plugin for advanced form management with CleverReach integration.
## Versioning
Release tags are created with Semantic versioning in mind. Commit messages were following convention of [Conventional Commits](https://www.conventionalcommits.org/).
## Compatibility
- WordPress version `>=6.4`. Tested and works fine up to `6.8.3`.
- PHP version `>=7.4.1`. Tested and works fine up to `8.4.4`.
## Requirements
- **Advanced Custom Fields PRO** (`advanced-custom-fields-pro`)
- **LEXO Captcha** (`lexo-captcha`)
## Installation
1. Download the [latest release](https://github.com/lexo-ch/lexo-forms/releases/latest).
2. Under Assets, click on the link named `Version x.y.z`. It's a compiled build.
3. Extract zip file and copy the folder into your `wp-content/plugins` folder and activate LEXO Forms in plugins admin page. Alternatively, you can use downloaded zip file to install it directly from your plugin admin page.
## Usage
### Creating a Form
1. Go to **Forms** > **Add New** in WordPress admin.
2. Configure the form settings:
- **General Settings:** Select template, success/fail messages
- **Email Settings:** Configure recipients, subject, sender
- **CleverReach Integration:** Select group, form type, double opt-in settings
3. Publish the form.
### Inserting Forms
#### Shortcode
```php
[lexo_form id="123"]
```
#### TinyMCE Button
Use the "Forms" button in the WordPress editor toolbar to visually select and insert a form.
#### PHP
```php
echo do_shortcode('[lexo_form id="123"]');
```
### Form Templates
Forms are built using PHP templates located in `template-forms/`.
Custom templates can also be registered by creating a `template-forms/` directory in your active theme and placing template files inside it. The plugin automatically loads templates from both locations.
### Form Preview Images
Each form template can have a preview image displayed in the admin template selector. Preview images help users visually identify templates.
#### How It Works
1. Place preview images in `template-forms/previews/` directory
2. Name the image file the same as the template (e.g., `contact.svg` for `contact.php`)
3. The plugin automatically detects and displays the preview
#### Supported Formats
Images are searched in this priority order:
- `.webp` (recommended for best compression)
- `.png`
- `.jpg` / `.jpeg`
- `.svg` (recommended for scalable graphics)
#### Directory Structure
```
template-forms/
├── contact.php
├── newsletter.php
└── previews/
├── contact.svg
└── newsletter.svg
```
#### Theme Override
Theme templates can also have previews:
```
your-theme/
└── template-forms/
├── custom-form.php
└── previews/
└── custom-form.webp
```
#### Lightbox Feature
Clicking on a preview image opens a lightbox for a larger view. This is handled automatically by the admin JavaScript (`LexoFormsLightbox` module).
To disable zoom for specific previews, add the `lexoforms-preview-wrap--no-zoom` CSS class.
#### Contact Form Template
```php
'firstname',
'type' => 'text',
'label' => ['de' => 'Name', 'en' => 'Name'],
'required' => true,
'send_to_cr' => true
],
// ... more fields
];
```
#### Available Template Fields
- `name` — Internal field name
- `type` — Field type (`text`, `email`, `textarea`, `file`, `tel`, `checkbox`)
- `html_type` — HTML element (`input`, `textarea`, `select`)
- `label` — Multi-language labels (array with language codes)
- `placeholder` — Multi-language placeholders
- `required` — Required field flag (boolean)
- `email_label` — Label for email notifications
- `cr_description` — CleverReach field description
- `global` — CleverReach global attribute flag
- `send_to_cr` — Send to CleverReach flag
#### Form Handler Types
- **Email Only** — Send only email notifications.
- **CleverReach Only** — Send only to CleverReach.
- **Email & CleverReach** — Send to both (recommended).
### Visitor Email Variants
Visitor Email Variants allow different confirmation emails based on user selection in the form (e.g., different emails for "Sales inquiry" vs "Support request").
#### How It Works
1. Template defines a `controlling_field` (e.g., a select/radio field)
2. Template defines `variants` with keys matching the field options
3. Admin configures subject, content, and attachment for each variant
4. On submission, the system sends the email matching the user's selection
#### Template Configuration
Add `visitor_email_variants` to your template's return array:
```php
// template-forms/inquiry.php
return [
'name' => $name,
'fields' => $fields,
'html' => $html,
'visitor_email_variants' => [
'controlling_field' => 'inquiry_type', // Field name that controls variant
'variants' => [
'sales' => [
'label' => 'Sales Inquiry', // Label shown in admin
],
'support' => [
'label' => 'Support Request',
],
'partnership' => [
'label' => 'Partnership',
],
],
],
];
```
#### Form Field Example
The controlling field must be defined in `$fields`:
```php
$fields = [
[
'name' => 'inquiry_type',
'type' => 'text',
'html_type' => 'select',
'label' => ['de' => 'Anfragetyp', 'en' => 'Inquiry Type'],
'options' => [
'sales' => ['de' => 'Verkauf', 'en' => 'Sales'],
'support' => ['de' => 'Support', 'en' => 'Support'],
'partnership' => ['de' => 'Partnerschaft', 'en' => 'Partnership'],
],
'required' => true,
'email_label' => ['de' => 'Typ', 'en' => 'Type'],
],
// ... other fields
];
```
#### Admin Configuration
When a template with variants is selected:
1. The "Visitor Email Variants" section appears in Email Settings
2. Each variant has its own tab with:
- Subject field
- Content (WYSIWYG editor)
- Attachment (optional file)
#### Behavior
- If template has variants → standard visitor email fields are hidden
- Variant selection is automatic based on form submission data
- If no matching variant found → no visitor email sent
- Each variant can have completely different content and attachments
### Email Configuration
#### Admin Notification Emails
Configure in the form's **Email Settings** section:
- **Recipients** — Multiple email addresses (repeater field)
- **Subject** — Email subject line
- **Sender Email** — From email address
- **Sender Name** — From name
#### Confirmation Emails
Enable in **Email Settings** > **Enable Additional Email**:
- **Subject** — Confirmation email subject
- **Body** — HTML email body with placeholder support
- **Sender Email** — From email address
- **Sender Name** — From name
- **Attachment** — PDF or other file attachment
## Filters
### CleverReach Email Filters
#### `lexo-forms/cr/email/config`
Filter email configuration for form submissions.
```php
apply_filters('lexo-forms/cr/email/config', array $config, int $form_id, array $form_data);
```
**Parameters**
- `$config` (array) — Email configuration
- `recipients` (array) — Email recipients
- `subject` (string) — Email subject
- `from_email` (string) — Sender email
- `from_name` (string) — Sender name
- `reply_to_email` (string) — Reply-to email
- `reply_to_name` (string) — Reply-to name
- `$form_id` (int) — Form post ID
- `$form_data` (array) — Submitted form data
**Example**
```php
add_filter('lexo-forms/cr/email/config', function($config, $form_id, $form_data) {
if ($form_id === 123) {
$config['recipients'] = ['admin@example.com'];
$config['subject'] = 'New Contact Form Submission';
}
return $config;
}, 10, 3);
```
---
#### `lexo-forms/cr/email/confirmation/subject`
Filter confirmation email subject.
```php
apply_filters('lexo-forms/cr/email/confirmation/subject', string $subject, int $form_id);
```
**Default:** Value from `FormMessages::getConfirmationEmailSubject()`
**Example**
```php
add_filter('lexo-forms/cr/email/confirmation/subject', function($subject, $form_id) {
return 'Thank you for contacting us!';
}, 10, 2);
```
---
#### `lexo-forms/cr/email/confirmation/sender`
Filter confirmation email sender address.
```php
apply_filters('lexo-forms/cr/email/confirmation/sender', string $sender_email, int $form_id);
```
**Default:** `EMAIL_FROM_EMAIL` constant
---
#### `lexo-forms/cr/email/confirmation/sender-name`
Filter confirmation email sender name.
```php
apply_filters('lexo-forms/cr/email/confirmation/sender-name', string $sender_name, int $form_id);
```
**Default:** `EMAIL_FROM_NAME` constant
---
#### `lexo-forms/cr/email/confirmation/body`
Filter confirmation email body content.
```php
apply_filters('lexo-forms/cr/email/confirmation/body', string $body, int $form_id);
```
**Default:** Empty string
---
#### `lexo-forms/cr/email/admin-notification/label-language`
Filter language for field labels in admin notification emails.
```php
apply_filters('lexo-forms/cr/email/admin-notification/label-language', string $language);
```
**Default:** `'de'`
**Example**
```php
add_filter('lexo-forms/cr/email/admin-notification/label-language', function($language) {
return 'en'; // Use English labels
});
```
---
#### `lexo-forms/cr/email/admin-notification/field-label`
Filter individual field label in admin notification emails.
```php
apply_filters('lexo-forms/cr/email/admin-notification/field-label', string $label, string $field_name, array $field_config);
```
**Parameters**
- `$label` (string) — Field label
- `$field_name` (string) — Field name
- `$field_config` (array) — Complete field configuration
**Example**
```php
add_filter('lexo-forms/cr/email/admin-notification/field-label', function($label, $field_name, $field_config) {
if ($field_name === 'email') {
return 'Customer Email';
}
return $label;
}, 10, 3);
```
---
### Forms Filters
#### `lexo-forms/forms/grid/columns`
Filter number of grid columns for form layout.
```php
apply_filters('lexo-forms/forms/grid/columns', int $columns, string $form_type);
```
**Parameters**
- `$columns` (int) — Number of columns (default: 2 for contact, 1 for newsletter)
- `$form_type` (string) — Form type identifier (`contact`, `newsletter`)
**Example**
```php
add_filter('lexo-forms/forms/grid/columns', function($columns, $form_type) {
if ($form_type === 'contact') {
return 3; // Use 3 columns for contact forms
}
return $columns;
}, 10, 2);
```
---
#### `lexo-forms/forms/grid/classes`
Filter CSS classes for form grid wrapper.
```php
apply_filters('lexo-forms/forms/grid/classes', string $classes, string $form_type, int $grid_columns);
```
**Parameters**
- `$classes` (string) — Default CSS classes
- `$form_type` (string) — Form type identifier
- `$grid_columns` (int) — Number of columns
**Example**
```php
add_filter('lexo-forms/forms/grid/classes', function($classes, $form_type, $grid_columns) {
return $classes . ' custom-form-styling';
}, 10, 3);
```
---
#### `lexo-forms/forms/toolbar/additional`
Filter additional ACF WYSIWYG toolbars where forms button appears.
```php
apply_filters('lexo-forms/forms/toolbar/additional', array $toolbars);
```
**Default:** `[]` (only "Full" and "Full FC" by default)
**Example**
```php
add_filter('lexo-forms/forms/toolbar/additional', function($toolbars) {
$toolbars[] = 'Basic';
$toolbars[] = 'Custom';
return $toolbars;
});
```
---
#### `lexo-forms/forms/toolbar/email/buttons`
Filter buttons in additional email WYSIWYG toolbar.
```php
apply_filters('lexo-forms/forms/toolbar/email/buttons', array $toolbar);
```
**Parameters**
- `$toolbar` (array) — Toolbar configuration with rows
**Default Buttons (Row 1)**
- `formatselect`, `bold`, `italic`, `underline`, `bullist`, `numlist`, `link`, `unlink`, `alignleft`, `aligncenter`, `alignright`, `removeformat`, `pastetext`, `undo`, `redo`
**Example**
```php
add_filter('lexo-forms/forms/toolbar/email/buttons', function($toolbar) {
// Add strikethrough to row 1
$toolbar[1][] = 'strikethrough';
// Add color buttons to row 2
$toolbar[2] = ['forecolor', 'backcolor'];
return $toolbar;
});
```
---
#### `lexo-forms/forms/email/label-language`
Filter language for email labels in form submission emails.
```php
apply_filters('lexo-forms/forms/email/label-language', string $language);
```
**Default:** `'de'`
**Example**
```php
add_filter('lexo-forms/forms/email/label-language', function($language) {
return 'en';
});
```
---
### Contact Form Privacy Filters
These filters allow customization of the privacy notice displayed in the contact form template.
#### `lexo-forms/forms/contact/privacy_url`
Filter the privacy policy page URL.
```php
apply_filters('lexo-forms/forms/contact/privacy_url', string $url);
```
**Default:** `''` (empty, privacy notice hidden when empty)
**Example**
```php
add_filter('lexo-forms/forms/contact/privacy_url', function($url) {
return get_privacy_policy_url(); // WordPress privacy page
});
// Or custom URL
add_filter('lexo-forms/forms/contact/privacy_url', function($url) {
return 'https://example.com/datenschutz';
});
```
---
#### `lexo-forms/forms/contact/privacy_text`
Filter the privacy link text (multilingual array).
```php
apply_filters('lexo-forms/forms/contact/privacy_text', array $text);
```
**Default:**
```php
[
'de' => 'Datenschutz',
'en' => 'Privacy Policy',
'fr' => 'Politique de confidentialité',
'it' => 'Informativa sulla privacy'
]
```
**Example**
```php
add_filter('lexo-forms/forms/contact/privacy_text', function($text) {
$text['de'] = 'Datenschutzerklärung';
$text['en'] = 'Data Protection';
return $text;
});
```
---
#### `lexo-forms/forms/contact/privacy_message`
Filter the message displayed after the privacy link.
```php
apply_filters('lexo-forms/forms/contact/privacy_message', array $message);
```
**Default:**
```php
[
'de' => ' ist uns wichtig.',
'en' => ' is important to us.',
'fr' => ' est importante pour nous.',
'it' => ' è importante per noi.'
]
```
**Example**
```php
add_filter('lexo-forms/forms/contact/privacy_message', function($message) {
return [
'de' => ' - Ihre Daten sind bei uns sicher.',
'en' => ' - Your data is safe with us.',
];
});
```
**Complete Example**
```php
// Enable privacy notice with all customizations
add_filter('lexo-forms/forms/contact/privacy_url', function() {
return get_privacy_policy_url();
});
add_filter('lexo-forms/forms/contact/privacy_text', function() {
return ['de' => 'Datenschutz', 'en' => 'Privacy'];
});
add_filter('lexo-forms/forms/contact/privacy_message', function() {
return ['de' => ' ist uns wichtig.', 'en' => ' matters to us.'];
});
```
This renders as: `[Privacy](url) matters to us.`
---
### Forms Messages & Errors Filters
#### `lexo-forms/forms/messages/success`
Filter the default success message shown after successful form submission.
```php
apply_filters('lexo-forms/forms/messages/success', string $message);
```
**Default:** `'Your message has been sent successfully. Thank you!'`
**Example**
```php
add_filter('lexo-forms/forms/messages/success', function($message) {
return 'Thank you! We will get back to you soon.';
});
```
---
#### `lexo-forms/forms/messages/email-fail`
Filter the message shown when email sending fails.
```php
apply_filters('lexo-forms/forms/messages/email-fail', string $message);
```
**Default:** `'Failed to send email'`
---
#### `lexo-forms/forms/messages/captcha-fail`
Filter the message shown when captcha validation fails.
```php
apply_filters('lexo-forms/forms/messages/captcha-fail', string $message);
```
**Default:** `'Captcha validation failed. Please try again.'`
---
#### `lexo-forms/forms/messages/invalid-email`
Filter the message shown when email field contains invalid format.
```php
apply_filters('lexo-forms/forms/messages/invalid-email', string $message, string $field_label);
```
**Parameters**
- `$message` (string) — Error message
- `$field_label` (string) — Label of the email field
**Default:** `'Invalid email format in field: {field_label}'`
---
#### `lexo-forms/forms/messages/confirmation-email-subject`
Filter the default subject for confirmation emails sent to visitors.
```php
apply_filters('lexo-forms/forms/messages/confirmation-email-subject', string $subject);
```
**Default:** `'Thank you for your message'`
---
#### `lexo-forms/forms/errors/form-id-required`
Filter the error message when form ID is missing.
```php
apply_filters('lexo-forms/forms/errors/form-id-required', string $message);
```
**Default:** `'Error: Form ID is required.'`
---
#### `lexo-forms/forms/errors/form-not-found`
Filter the error message when form is not found.
```php
apply_filters('lexo-forms/forms/errors/form-not-found', string $message);
```
**Default:** `'Error: Form not found.'`
---
#### `lexo-forms/forms/errors/template-not-configured`
Filter the error message when form template is not configured.
```php
apply_filters('lexo-forms/forms/errors/template-not-configured', string $message);
```
**Default:** `'Error: Form template not configured.'`
---
#### `lexo-forms/forms/errors/template-not-found`
Filter the error message when template file is not found.
```php
apply_filters('lexo-forms/forms/errors/template-not-found', string $message);
```
**Default:** `'Error: Template not found.'`
---
#### `lexo-forms/forms/errors/no-fields-configured`
Filter the error message when no fields are configured in template.
```php
apply_filters('lexo-forms/forms/errors/no-fields-configured', string $message);
```
**Default:** `'No fields configured for this form'`
---
#### `lexo-forms/forms/errors/required-fields-missing`
Filter the error message when required fields are missing.
```php
apply_filters('lexo-forms/forms/errors/required-fields-missing', string $message, string $field_list);
```
**Parameters**
- `$message` (string) — Error message
- `$field_list` (string) — Comma-separated list of missing field names
**Default:** `'Required fields are missing: {field_list}'`
---
### CleverReach Messages & Errors Filters
#### `lexo-forms/cr/messages/error`
Filter the message shown when CleverReach submission fails.
```php
apply_filters('lexo-forms/cr/messages/error', string $message);
```
**Default:** `'We encountered an issue submitting your information. Our team has been notified and will contact you shortly.'`
**Example**
```php
add_filter('lexo-forms/cr/messages/error', function($message) {
return 'Submission failed. Please try again later.';
});
```
---
#### `lexo-forms/cr/messages/already-subscribed`
Filter the message shown when email is already subscribed.
```php
apply_filters('lexo-forms/cr/messages/already-subscribed', string $message);
```
**Default:** `'This email address is already subscribed.'`
---
#### `lexo-forms/cr/errors/form-creation-failed`
Filter the error message when CleverReach form creation fails.
```php
apply_filters('lexo-forms/cr/errors/form-creation-failed', string $message);
```
**Default:** `'Failed to determine or create form'`
---
#### `lexo-forms/cr/errors/group-id-failed`
Filter the error message when retrieving CleverReach group ID fails.
```php
apply_filters('lexo-forms/cr/errors/group-id-failed', string $message);
```
**Default:** `'Failed to get group ID from form'`
---
### Core Filters
#### `lexoforms/load_styles`
Filter whether to load plugin styles.
```php
apply_filters('lexoforms/load_styles', bool $load);
```
**Default:** `true`
**Example**
```php
add_filter('lexoforms/load_styles', function($load) {
// Don't load on specific pages
if (is_page('custom-page')) {
return false;
}
return $load;
});
```
---
#### `lexoforms/enqueue/{basename}`
Filter whether to enqueue specific stylesheet.
```php
apply_filters('lexoforms/enqueue/{basename}', bool $enqueue);
```
**Example**
```php
// Don't load admin-lf.css on dashboard
add_filter('lexoforms/enqueue/admin-lf.css', function($enqueue) {
if (is_admin() && get_current_screen()->id === 'dashboard') {
return false;
}
return $enqueue;
});
```
---
#### `lexoforms/load_scripts`
Filter whether to load plugin scripts.
```php
apply_filters('lexoforms/load_scripts', bool $load);
```
**Default:** `true`
---
#### `lexoforms/enqueue/{basename}`
Filter whether to enqueue specific script.
```php
apply_filters('lexoforms/enqueue/{basename}', bool $enqueue);
```
**Example**
```php
// Don't load admin-lf.js on specific pages
add_filter('lexoforms/enqueue/admin-lf.js', function($enqueue) {
if (is_admin() && get_current_screen()->id === 'plugins') {
return false;
}
return $enqueue;
});
```
---
#### `lexoforms/load_editor_styles`
Filter whether to load editor styles.
```php
apply_filters('lexoforms/load_editor_styles', bool $load);
```
**Default:** `true`
---
#### `lexoforms/add_editor_style/{basename}`
Filter whether to add specific editor style.
```php
apply_filters('lexoforms/add_editor_style/{basename}', bool $add);
```
**Default:** `true`
---
#### `lexo-forms/plugin_sections`
Filter plugin update information sections.
```php
apply_filters('lexo-forms/plugin_sections', array $sections);
```
**Parameters**
- `$sections` (array) — Array with `description` and `changelog` keys
---
#### `lexoforms/admin_localized_script`
Filter admin localized script variables.
```php
apply_filters('lexoforms/admin_localized_script', array $vars);
```
**Parameters**
- `$vars` (array) — Array of variables passed to `wp_localize_script`
- `ajaxurl` (string) — WordPress AJAX URL
- `nonce` (string) — Nonce for AJAX requests
- `plugin_name` (string) — Plugin name
- `plugin_slug` (string) — Plugin slug
- `plugin_version` (string) — Plugin version
- `min_php_version` (string) — Minimum PHP version
- `min_wp_version` (string) — Minimum WordPress version
- `text_domain` (string) — Text domain
**Example**
```php
add_filter('lexoforms/admin_localized_script', function($vars) {
$vars['custom_data'] = 'My custom value';
return $vars;
});
```
---
#### `lexoforms/options-page/parent-slug`
Filter the parent slug for the settings page in admin menu.
```php
apply_filters('lexoforms/options-page/parent-slug', string $slug);
```
**Default:** `'options-general.php'`
**Example**
```php
add_filter('lexoforms/options-page/parent-slug', function($slug) {
return 'admin.php'; // Move to main admin menu
});
```
---
#### `lexoforms/options-page/capability`
Filter the capability required to access plugin settings.
```php
apply_filters('lexoforms/options-page/capability', string $capability);
```
**Default:** `'manage_options'`
**Example**
```php
add_filter('lexoforms/options-page/capability', function($capability) {
return 'edit_posts'; // Allow editors and above
});
```
---
## Actions
### `lexoforms/init`
Fires during plugin initialization.
```php
do_action('lexoforms/init');
```
**Usage**
```php
add_action('lexoforms/init', function() {
// Custom initialization code
});
```
---
### `lexoforms/localize/{script-basename}`
Fires when localizing scripts for specific script file.
```php
do_action('lexoforms/localize/{script-basename}');
```
**Example**
```php
add_action('lexoforms/localize/admin-lf.js', function() {
wp_localize_script('lexoforms/admin-lf.js', 'customData', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('custom_nonce'),
]);
});
```
---
### `lexo-forms/cr/after-submission`
Fires after successful CleverReach form submission.
```php
do_action('lexo-forms/cr/after-submission', int $form_id, array $form_data, array $template);
```
**Parameters**
- `$form_id` (int) — Form post ID
- `$form_data` (array) — Submitted and sanitized form data
- `$template` (array) — Form template configuration
**Example**
```php
add_action('lexo-forms/cr/after-submission', function($form_id, $form_data, $template) {
// Log submission to custom analytics
error_log('Form submitted: ' . $form_id);
// Send to additional service
send_to_crm($form_data);
}, 10, 3);
```
---
### `lexo-forms/core/log`
Fires when plugin logs a message.
```php
do_action('lexo-forms/core/log', string $level, string $category, string $message, array $context);
```
**Parameters**
- `$level` (string) — Log level (`debug`, `info`, `warning`, `error`)
- `$category` (string) — Log category
- `$message` (string) — Log message
- `$context` (array) — Additional context data
**Example**
```php
add_action('lexo-forms/core/log', function($level, $category, $message, $context) {
if ($level === 'error') {
// Send error notification
mail('admin@example.com', 'Plugin Error', $message);
}
}, 10, 4);
```
---
## Changelog
For detailed changelog, see [Releases](https://github.com/lexo-ch/lexo-forms/releases).
---
## Author
- **LEXO GmbH**
- Website: [https://www.lexo.ch](https://www.lexo.ch)
- GitHub: [https://github.com/lexo-ch](https://github.com/lexo-ch)