Add hierarchical tree view for category selection
Features: - Tree view mode for categories with expand/collapse - Product count badges with clickable preview popover - Select parent with all children button - Client-side tree filtering (refine search) - Keyboard shortcuts: Ctrl+A (select all), Ctrl+D (clear) - View mode switching between tree/list/columns - Tree view as default for categories, respects user preference Backend: - Add previewCategoryProducts and previewCategoryPages AJAX handlers - Support pagination and filtering in category previews Styling: - Consistent count-badge styling across tree and other views - Loading and popover-open states for count badges Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
937
README.md
937
README.md
@@ -1,574 +1,449 @@
|
||||
# PrestaShop Condition Traits
|
||||
# PrestaShop Entity Selector
|
||||
|
||||
A collection of reusable PHP traits for PrestaShop admin controllers that provide powerful UI widgets for targeting and scheduling.
|
||||
|
||||
## Available Traits
|
||||
|
||||
| Trait | Purpose |
|
||||
|-------|---------|
|
||||
| **TargetConditions** | Multi-criteria targeting (countries, currencies, carriers, groups, languages) |
|
||||
| **ScheduleConditions** | Time-based scheduling (datetime range, weekly schedule, holiday exclusions) |
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Composer
|
||||
|
||||
```bash
|
||||
composer require myprestarocks/prestashop-target-conditions
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
Copy the package to your module's vendor directory:
|
||||
```
|
||||
modules/your_module/vendor/myprestarocks/prestashop-target-conditions/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# TargetConditions Trait
|
||||
|
||||
A powerful multi-criteria targeting widget. Allows users to define conditions based on countries, currencies, carriers, customer groups, and languages.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### 1. Include the Trait in Your Admin Controller
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use MyPrestaRocks\TargetConditions\TargetConditions;
|
||||
|
||||
class AdminYourModuleController extends ModuleAdminController
|
||||
{
|
||||
use TargetConditions;
|
||||
|
||||
// Your controller code...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Register Assets in setMedia()
|
||||
|
||||
```php
|
||||
public function setMedia($isNewTheme = false)
|
||||
{
|
||||
parent::setMedia($isNewTheme);
|
||||
|
||||
// Register the target conditions assets
|
||||
$this->registerTargetConditionsAssets();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add the Widget to Your Form
|
||||
|
||||
In your form rendering method (e.g., `renderForm()`), add the widget:
|
||||
|
||||
```php
|
||||
public function renderForm()
|
||||
{
|
||||
// Get existing restrictions from database (JSON string or array)
|
||||
$restrictions = $this->object->restrictions ?? '[]';
|
||||
|
||||
$this->fields_form = [
|
||||
'legend' => [
|
||||
'title' => $this->l('Configuration'),
|
||||
],
|
||||
'input' => [
|
||||
[
|
||||
'type' => 'free',
|
||||
'label' => $this->l('Target Conditions'),
|
||||
'name' => 'restrictions_widget',
|
||||
'desc' => $this->l('Define where this item should be available'),
|
||||
],
|
||||
],
|
||||
// ... other fields
|
||||
];
|
||||
|
||||
// Render the widget HTML
|
||||
$this->fields_value['restrictions_widget'] = $this->renderTargetConditionsHtml(
|
||||
'restrictions', // Field name for form submission
|
||||
$restrictions, // Current value (JSON or array)
|
||||
[
|
||||
// Configuration options (see below)
|
||||
]
|
||||
);
|
||||
|
||||
return parent::renderForm();
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Process the Submitted Data
|
||||
|
||||
```php
|
||||
public function postProcess()
|
||||
{
|
||||
if (Tools::isSubmit('submitYourForm')) {
|
||||
$restrictions = Tools::getValue('restrictions');
|
||||
|
||||
// The value is already JSON-encoded from the widget
|
||||
// Store it in your database
|
||||
$this->object->restrictions = $restrictions;
|
||||
$this->object->save();
|
||||
}
|
||||
|
||||
return parent::postProcess();
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The `renderTargetConditionsHtml()` method accepts a configuration array as the third parameter:
|
||||
|
||||
```php
|
||||
$this->renderTargetConditionsHtml('field_name', $value, [
|
||||
// Available condition groups to show
|
||||
// Default: all groups enabled
|
||||
'groups' => [
|
||||
'countries' => true, // Country selection
|
||||
'currencies' => true, // Currency selection
|
||||
'carriers' => true, // Carrier/shipping method selection
|
||||
'groups' => true, // Customer group selection
|
||||
'languages' => true, // Language selection
|
||||
],
|
||||
|
||||
// Empty selection behavior
|
||||
// true = empty means "all included" (available everywhere)
|
||||
// false = empty means "none selected" (not available anywhere)
|
||||
'empty_means_all' => true,
|
||||
|
||||
// Show "All X" toggle badges
|
||||
// When set, displays toggle buttons showing selection state
|
||||
'show_all_toggle' => [
|
||||
'empty' => $this->l('Available Everywhere'), // Label when nothing selected
|
||||
'selected' => $this->l('Restricted'), // Label when items selected
|
||||
],
|
||||
|
||||
// Show group modifiers (Limit/Sort controls)
|
||||
// true = show limit dropdown and sort controls per group
|
||||
// false = hide modifiers (cleaner UI for targeting contexts)
|
||||
'show_modifiers' => true,
|
||||
|
||||
// Pre-filter available options based on external restrictions
|
||||
// Useful when parent entity has its own restrictions
|
||||
'restrictions' => [
|
||||
'countries' => ['FR', 'DE', 'PL'], // Only show these countries
|
||||
'currencies' => ['EUR', 'PLN'], // Only show these currencies
|
||||
// ... etc
|
||||
],
|
||||
|
||||
// Custom labels for condition groups
|
||||
'labels' => [
|
||||
'countries' => $this->l('Countries'),
|
||||
'currencies' => $this->l('Currencies'),
|
||||
'carriers' => $this->l('Carriers'),
|
||||
'groups' => $this->l('Customer Groups'),
|
||||
'languages' => $this->l('Languages'),
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
## Use Case Examples
|
||||
|
||||
### Payment Method Targeting
|
||||
|
||||
Restrict payment methods to specific countries/currencies:
|
||||
|
||||
```php
|
||||
$this->renderTargetConditionsHtml('restrictions', $paymentOption->restrictions, [
|
||||
'groups' => [
|
||||
'countries' => true,
|
||||
'currencies' => true,
|
||||
'carriers' => false, // Not relevant for payments
|
||||
'groups' => true,
|
||||
'languages' => false,
|
||||
],
|
||||
'empty_means_all' => true,
|
||||
'show_modifiers' => false, // No need for limit/sort on targeting
|
||||
'show_all_toggle' => [
|
||||
'empty' => $this->l('Available Everywhere'),
|
||||
'selected' => $this->l('Restricted'),
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### Product Selection Widget
|
||||
|
||||
Select products with quantity limits:
|
||||
|
||||
```php
|
||||
$this->renderTargetConditionsHtml('selected_products', $rule->products, [
|
||||
'groups' => [
|
||||
'products' => true,
|
||||
],
|
||||
'empty_means_all' => false, // Must explicitly select products
|
||||
'show_modifiers' => true, // Allow limiting quantity per selection
|
||||
]);
|
||||
```
|
||||
|
||||
### Shipping Rules
|
||||
|
||||
Define carrier availability by zone:
|
||||
|
||||
```php
|
||||
$this->renderTargetConditionsHtml('availability', $carrier->zones, [
|
||||
'groups' => [
|
||||
'countries' => true,
|
||||
'groups' => true, // Different rates for customer groups
|
||||
],
|
||||
'empty_means_all' => true,
|
||||
'show_all_toggle' => [
|
||||
'empty' => $this->l('Ships Everywhere'),
|
||||
'selected' => $this->l('Limited Zones'),
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
## Data Format
|
||||
|
||||
The widget stores data as a JSON object with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"countries": {
|
||||
"items": ["FR", "DE", "PL"],
|
||||
"limit": 0,
|
||||
"sort": "default"
|
||||
},
|
||||
"currencies": {
|
||||
"items": ["EUR", "PLN"],
|
||||
"limit": 0,
|
||||
"sort": "default"
|
||||
},
|
||||
"carriers": {
|
||||
"items": [],
|
||||
"limit": 0,
|
||||
"sort": "default"
|
||||
},
|
||||
"groups": {
|
||||
"items": [1, 2, 3],
|
||||
"limit": 0,
|
||||
"sort": "default"
|
||||
},
|
||||
"languages": {
|
||||
"items": [],
|
||||
"limit": 0,
|
||||
"sort": "default"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
- **items**: Array of selected IDs or ISO codes
|
||||
- **limit**: Maximum items to use (0 = no limit)
|
||||
- **sort**: Sort order for items ("default", "asc", "desc", "random")
|
||||
|
||||
## Selection Logic
|
||||
|
||||
### When `empty_means_all` is `true` (default):
|
||||
- Empty selection = item available for ALL values in that group
|
||||
- Selection made = item restricted to ONLY selected values
|
||||
|
||||
### When `empty_means_all` is `false`:
|
||||
- Empty selection = item NOT available (nothing selected)
|
||||
- Selection made = item available for selected values only
|
||||
|
||||
### Restriction Inheritance
|
||||
|
||||
When using the `restrictions` config option, the widget will only display options that match the parent restrictions. This is useful for hierarchical configurations where child items cannot exceed parent permissions.
|
||||
|
||||
Example: A payment method restricted to France and Germany cannot have a sub-option available in Poland.
|
||||
|
||||
---
|
||||
|
||||
# ScheduleConditions Trait
|
||||
|
||||
A time-based scheduling widget with visual timeline controls. Allows users to define when something should be active using datetime ranges, weekly schedules, and public holiday exclusions.
|
||||
A reusable entity selection component for PrestaShop admin interfaces. Provides a flexible, searchable UI for targeting entities with multiple selection methods, grouping, include/exclude logic, and preview functionality.
|
||||
|
||||
## Features
|
||||
|
||||
- **Datetime Range**: Specific start/end period with datetime pickers
|
||||
- **Weekly Schedule**: Per-day time ranges with visual timeline sliders
|
||||
- **Public Holiday Exclusions**: Optional integration with holiday providers
|
||||
- **18 Entity Types** - Products, categories, customers, carriers, and more
|
||||
- **130+ Selection Methods** - Filter by properties, patterns, ranges, related entities
|
||||
- **Include/Exclude Logic** - Complex targeting with exceptions
|
||||
- **Grouping** - Multiple selection groups with AND/OR logic
|
||||
- **Live Preview** - See matching items in real-time
|
||||
- **Modifiers** - Limit and sort results per group
|
||||
|
||||
## Basic Usage
|
||||
---
|
||||
|
||||
### 1. Include the Trait
|
||||
## Supported Entities
|
||||
|
||||
| Entity | Icon | Methods | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| Products | `icon-cube` | 30+ | Full product targeting with attributes, features, conditions |
|
||||
| Categories | `icon-folder-open` | 6 | Product categories |
|
||||
| Manufacturers | `icon-building` | 5 | Brand/manufacturer targeting |
|
||||
| Suppliers | `icon-truck` | 5 | Supplier targeting |
|
||||
| CMS Pages | `icon-file-text-o` | 6 | Static content pages |
|
||||
| CMS Categories | `icon-folder-o` | 5 | CMS category groupings |
|
||||
| Employees | `icon-user-secret` | 5 | Back-office users |
|
||||
| Customers | `icon-users` | 12 | Front-office customers |
|
||||
| Customer Groups | `icon-group` | 4 | Customer segmentation groups |
|
||||
| Carriers | `icon-truck` | 10 | Shipping carriers |
|
||||
| Zones | `icon-globe` | 4 | Geographic zones |
|
||||
| Countries | `icon-flag` | 9 | Country targeting |
|
||||
| Currencies | `icon-money` | 4 | Currency selection |
|
||||
| Languages | `icon-language` | 5 | Language targeting |
|
||||
| Shops | `icon-shopping-cart` | 4 | Multi-store shop selection |
|
||||
| Profiles | `icon-key` | 3 | Employee access profiles |
|
||||
| Order States | `icon-tasks` | 6 | Order status targeting |
|
||||
| Taxes | `icon-money` | 5 | Tax rule selection |
|
||||
|
||||
---
|
||||
|
||||
## Products - Selection Methods
|
||||
|
||||
### All Products
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All products (no criteria) | none |
|
||||
|
||||
### By Entity
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `specific` | Specific individual products | entity_search |
|
||||
| `by_category` | Products in category(ies) | entity_search |
|
||||
| `by_manufacturer` | Products by manufacturer | entity_search |
|
||||
| `by_supplier` | Products by supplier | entity_search |
|
||||
| `by_tag` | Products by tag | entity_search |
|
||||
| `by_attribute` | Products with attribute(s) | entity_search |
|
||||
| `by_feature` | Products with feature(s) | entity_search |
|
||||
| `by_combination` | Specific combinations | combination_attributes |
|
||||
|
||||
### By Property
|
||||
| Method | Description | Options |
|
||||
|--------|-------------|---------|
|
||||
| `by_condition` | Product condition | New, Used, Refurbished |
|
||||
| `by_visibility` | Visibility setting | Everywhere, Catalog only, Search only, Nowhere |
|
||||
| `by_active_status` | Active status | Active, Inactive |
|
||||
| `by_stock_status` | Stock status | In stock, Out of stock, Low stock |
|
||||
| `by_on_sale` | On sale flag | Yes, No |
|
||||
| `by_has_specific_price` | Has discount | Yes, No |
|
||||
| `by_is_virtual` | Virtual product | Yes, No |
|
||||
| `by_is_pack` | Pack product | Yes, No |
|
||||
| `by_has_combinations` | Has combinations | Yes, No |
|
||||
| `by_available_for_order` | Available for order | Yes, No |
|
||||
| `by_online_only` | Online only | Yes, No |
|
||||
| `by_has_related` | Has related products | Yes, No |
|
||||
| `by_has_customization` | Has customization | Yes, No |
|
||||
| `by_has_attachments` | Has attachments | Yes, No |
|
||||
| `by_out_of_stock_behavior` | Out of stock behavior | Deny orders, Allow orders, Use default |
|
||||
| `by_delivery_time` | Delivery time setting | None, Default, Specific |
|
||||
| `by_has_additional_shipping` | Additional shipping cost | Yes, No |
|
||||
| `by_carrier_restriction` | Carrier restriction | Has restriction, All carriers |
|
||||
|
||||
### By Text Pattern
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `by_name_pattern` | Product name contains | pattern |
|
||||
| `by_reference_pattern` | Reference contains | pattern |
|
||||
| `by_description_pattern` | Short description contains | pattern |
|
||||
| `by_long_description_pattern` | Long description contains | pattern |
|
||||
| `by_ean13_pattern` | EAN-13 contains | pattern |
|
||||
| `by_upc_pattern` | UPC contains | pattern |
|
||||
| `by_isbn_pattern` | ISBN contains | pattern |
|
||||
| `by_mpn_pattern` | MPN contains | pattern |
|
||||
| `by_meta_title_pattern` | Meta title contains | pattern |
|
||||
| `by_meta_description_pattern` | Meta description contains | pattern |
|
||||
|
||||
### By Range
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `by_id_range` | Product ID range | numeric_range |
|
||||
| `by_price_range` | Price range | numeric_range |
|
||||
| `by_weight_range` | Weight range | numeric_range |
|
||||
| `by_quantity_range` | Stock quantity range | numeric_range |
|
||||
| `by_position_range` | Position range | numeric_range |
|
||||
| `by_date_added` | Date added range | date_range |
|
||||
| `by_date_updated` | Date modified range | date_range |
|
||||
|
||||
---
|
||||
|
||||
## Categories - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All categories | none |
|
||||
| `specific` | Specific category(ies) | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_product_count` | Product count range | numeric_range |
|
||||
| `by_depth_level` | Depth level range | numeric_range |
|
||||
| `by_active_status` | Active status | select |
|
||||
|
||||
---
|
||||
|
||||
## Manufacturers - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All manufacturers | none |
|
||||
| `specific` | Specific manufacturer(s) | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_product_count` | Product count range | numeric_range |
|
||||
| `by_active_status` | Active status | select |
|
||||
|
||||
---
|
||||
|
||||
## Suppliers - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All suppliers | none |
|
||||
| `specific` | Specific supplier(s) | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_product_count` | Product count range | numeric_range |
|
||||
| `by_active_status` | Active status | select |
|
||||
|
||||
---
|
||||
|
||||
## CMS Pages - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All CMS pages | none |
|
||||
| `specific` | Specific page(s) | entity_search |
|
||||
| `by_cms_category` | Pages in CMS category | entity_search |
|
||||
| `by_name_pattern` | Title contains | pattern |
|
||||
| `by_active_status` | Active status | select |
|
||||
| `by_indexable` | Indexable status | select |
|
||||
|
||||
---
|
||||
|
||||
## Customers - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All customers | none |
|
||||
| `specific` | Specific customer(s) | entity_search |
|
||||
| `by_group` | By customer group | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_email_pattern` | Email contains | pattern |
|
||||
| `by_company` | Has company | select |
|
||||
| `by_company_pattern` | Company name contains | pattern |
|
||||
| `by_address_count` | Address count range | numeric_range |
|
||||
| `by_order_count` | Order count range | numeric_range |
|
||||
| `by_turnover` | Total spent range | numeric_range |
|
||||
| `by_active_status` | Active status | select |
|
||||
| `by_newsletter` | Newsletter subscription | select |
|
||||
| `by_guest` | Guest or registered | select |
|
||||
|
||||
---
|
||||
|
||||
## Carriers - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All carriers | none |
|
||||
| `specific` | Specific carrier(s) | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_active_status` | Active status | select |
|
||||
| `by_shipping_handling` | Handling fee | select |
|
||||
| `by_free_shipping` | Free shipping | select |
|
||||
| `by_zone` | By zone | entity_search |
|
||||
| `by_customer_group` | By customer group | entity_search |
|
||||
| `by_price_range` | Shipping price range | numeric_range |
|
||||
| `by_weight_range` | Max weight range | numeric_range |
|
||||
|
||||
---
|
||||
|
||||
## Countries - Selection Methods
|
||||
|
||||
| Method | Description | Value Type |
|
||||
|--------|-------------|------------|
|
||||
| `all` | All countries | none |
|
||||
| `specific` | Specific country(ies) | entity_search |
|
||||
| `by_zone` | By zone | entity_search |
|
||||
| `by_name_pattern` | Name contains | pattern |
|
||||
| `by_active_status` | Active status | select |
|
||||
| `by_contains_states` | Has states | select |
|
||||
| `by_need_zip_code` | Requires ZIP | select |
|
||||
| `by_zip_format` | ZIP format contains | pattern |
|
||||
| `by_need_identification` | Requires ID number | select |
|
||||
|
||||
---
|
||||
|
||||
## Other Entities
|
||||
|
||||
### Customer Groups
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_price_display` | various |
|
||||
|
||||
### Zones
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_active_status` | various |
|
||||
|
||||
### Currencies
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_active_status` | various |
|
||||
|
||||
### Languages
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_active_status`, `by_rtl` | various |
|
||||
|
||||
### Shops
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_active_status` | various |
|
||||
|
||||
### Profiles
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern` | various |
|
||||
|
||||
### Employees
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_profile`, `by_name_pattern`, `by_active_status` | various |
|
||||
|
||||
### Order States
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_paid`, `by_shipped`, `by_delivery` | various |
|
||||
|
||||
### Taxes
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_rate_range`, `by_active_status` | various |
|
||||
|
||||
### CMS Categories
|
||||
| Method | Value Type |
|
||||
|--------|------------|
|
||||
| `all`, `specific`, `by_name_pattern`, `by_active_status`, `by_page_count` | various |
|
||||
|
||||
---
|
||||
|
||||
## Value Types
|
||||
|
||||
| Type | UI Component | Description |
|
||||
|------|--------------|-------------|
|
||||
| `none` | No input | Used for "All" methods |
|
||||
| `entity_search` | Searchable dropdown with chips | Select related entities |
|
||||
| `pattern` | Text input | Text/regex pattern matching |
|
||||
| `multi_select_tiles` | Toggle buttons | Multiple Yes/No options |
|
||||
| `select` | Single dropdown | Single option selection |
|
||||
| `numeric_range` | Min/Max inputs | Numeric range filtering |
|
||||
| `multi_numeric_range` | Multiple ranges | Multiple numeric ranges |
|
||||
| `date_range` | Date pickers | Date range filtering |
|
||||
| `combination_attributes` | Attribute selector | Product combination selection |
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### PHP Integration
|
||||
|
||||
```php
|
||||
<?php
|
||||
use MyPrestaRocks\EntitySelector\EntitySelector;
|
||||
|
||||
use MyPrestaRocks\TargetConditions\ScheduleConditions;
|
||||
|
||||
class AdminYourModuleController extends ModuleAdminController
|
||||
class AdminMyController extends ModuleAdminController
|
||||
{
|
||||
use ScheduleConditions;
|
||||
use EntitySelector;
|
||||
|
||||
// Your controller code...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Initialize Assets in setMedia()
|
||||
|
||||
```php
|
||||
public function setMedia($isNewTheme = false)
|
||||
{
|
||||
parent::setMedia($isNewTheme);
|
||||
|
||||
// Initialize schedule conditions (loads CSS + JS)
|
||||
$this->initScheduleConditions();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Render the Widget
|
||||
|
||||
```php
|
||||
public function renderForm()
|
||||
{
|
||||
// Get saved schedule data (JSON string from database)
|
||||
$scheduleJson = $this->object->schedule ?? '{}';
|
||||
$scheduleData = json_decode($scheduleJson, true) ?: [];
|
||||
|
||||
$this->fields_form = [
|
||||
'legend' => ['title' => $this->l('Configuration')],
|
||||
'input' => [
|
||||
[
|
||||
'type' => 'free',
|
||||
'label' => $this->l('Schedule'),
|
||||
'name' => 'schedule_widget',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Render schedule widget
|
||||
$this->fields_value['schedule_widget'] = $this->renderScheduleConditionsHtml(
|
||||
[
|
||||
'id' => 'my-schedule',
|
||||
'name_prefix' => 'schedule_',
|
||||
'title' => $this->l('Availability Schedule'),
|
||||
'subtitle' => $this->l('Define when this should be active'),
|
||||
'show_datetime_range' => true,
|
||||
'show_weekly_schedule' => true,
|
||||
'show_holiday_exclusions' => true,
|
||||
],
|
||||
$scheduleData
|
||||
);
|
||||
|
||||
return parent::renderForm();
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Process Form Submission
|
||||
|
||||
```php
|
||||
public function postProcess()
|
||||
{
|
||||
if (Tools::isSubmit('submitYourForm')) {
|
||||
// Parse schedule data from form
|
||||
$scheduleData = $this->parseScheduleConditionsFromPost('schedule_');
|
||||
|
||||
// Serialize to JSON for storage
|
||||
$this->object->schedule = $this->serializeScheduleConditions($scheduleData);
|
||||
$this->object->save();
|
||||
public function setMedia($isNewTheme = false)
|
||||
{
|
||||
parent::setMedia($isNewTheme);
|
||||
$this->initEntitySelector();
|
||||
}
|
||||
|
||||
return parent::postProcess();
|
||||
public function renderForm()
|
||||
{
|
||||
$html = $this->renderEntitySelectorHtml(
|
||||
[
|
||||
'id' => 'my-selector',
|
||||
'blocks' => ['products', 'categories'],
|
||||
'show_modifiers' => true,
|
||||
],
|
||||
$savedData
|
||||
);
|
||||
// Add $html to your form
|
||||
}
|
||||
|
||||
public function ajaxProcessEntitySelector()
|
||||
{
|
||||
$this->handleEntitySelectorAjax();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Evaluate Schedule at Runtime
|
||||
### Configuration Options
|
||||
|
||||
```php
|
||||
// Check if current time matches the schedule
|
||||
$scheduleData = $this->parseScheduleConditions($object->schedule);
|
||||
|
||||
if ($this->evaluateScheduleConditions($scheduleData)) {
|
||||
// Schedule allows - item is active
|
||||
} else {
|
||||
// Schedule blocks - item is inactive
|
||||
}
|
||||
|
||||
// Or check against a specific timestamp
|
||||
$futureTimestamp = strtotime('+1 day');
|
||||
$willBeActive = $this->evaluateScheduleConditions($scheduleData, $futureTimestamp);
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
```php
|
||||
$this->renderScheduleConditionsHtml([
|
||||
// Unique ID for the widget
|
||||
'id' => 'schedule-conditions',
|
||||
|
||||
// Form field name prefix
|
||||
'name_prefix' => 'schedule_',
|
||||
|
||||
// Header text
|
||||
'title' => 'Schedule',
|
||||
'subtitle' => 'Define when this should be active',
|
||||
|
||||
// Which sections to show
|
||||
'show_datetime_range' => true, // Start/end datetime pickers
|
||||
'show_weekly_schedule' => true, // Per-day time range sliders
|
||||
'show_holiday_exclusions' => true, // Holiday exclusion controls
|
||||
$this->renderEntitySelectorHtml([
|
||||
'id' => 'selector-id', // Unique ID
|
||||
'blocks' => ['products'], // Which entity tabs to show
|
||||
'mode' => 'multi', // 'single' or 'multi' group mode
|
||||
'show_modifiers' => true, // Show limit/sort per group
|
||||
'show_preview' => true, // Show preview button
|
||||
'default_method' => 'all', // Default selection method
|
||||
], $savedData);
|
||||
```
|
||||
|
||||
### JavaScript Events
|
||||
|
||||
```javascript
|
||||
// Listen for selection changes
|
||||
$(document).on('entitySelector:change', function(e, data) {
|
||||
console.log('Selection changed:', data);
|
||||
});
|
||||
|
||||
// Get current selection
|
||||
var data = window.EntitySelector.serialize('#my-selector');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Format
|
||||
|
||||
The schedule data is stored as JSON:
|
||||
Selection data is stored as JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"datetime_start": "2024-12-24 08:00:00",
|
||||
"datetime_end": "2024-12-24 18:00:00",
|
||||
"weekly_schedule": {
|
||||
"0": {"enabled": false, "start": 0, "end": 1440},
|
||||
"1": {"enabled": true, "start": 540, "end": 1020},
|
||||
"2": {"enabled": true, "start": 540, "end": 1020},
|
||||
"3": {"enabled": true, "start": 540, "end": 1020},
|
||||
"4": {"enabled": true, "start": 540, "end": 1020},
|
||||
"5": {"enabled": true, "start": 540, "end": 1020},
|
||||
"6": {"enabled": false, "start": 0, "end": 1440}
|
||||
},
|
||||
"exclude_holidays": true,
|
||||
"holiday_countries": [1, 14, 17]
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
- **enabled**: Whether scheduling is active (false = always active)
|
||||
- **datetime_start/end**: ISO datetime strings for absolute date range
|
||||
- **weekly_schedule**: Per-day configuration (0=Sunday, 1=Monday, etc.)
|
||||
- **enabled**: Whether this day is active
|
||||
- **start/end**: Minutes from midnight (0-1440, e.g., 540 = 09:00)
|
||||
- **exclude_holidays**: Whether to exclude public holidays
|
||||
- **holiday_countries**: Country IDs to check for holidays
|
||||
|
||||
## Public Holiday Integration
|
||||
|
||||
To enable holiday exclusions, implement the `HolidayProviderInterface`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use MyPrestaRocks\TargetConditions\HolidayProviderInterface;
|
||||
|
||||
class MyHolidayProvider implements HolidayProviderInterface
|
||||
{
|
||||
/**
|
||||
* Check if a date is a holiday for any of the given countries
|
||||
*/
|
||||
public static function isHoliday($date, array $countryIds)
|
||||
{
|
||||
// Your implementation
|
||||
return Db::getInstance()->getValue('
|
||||
SELECT 1 FROM ps_holidays
|
||||
WHERE holiday_date = "' . pSQL($date) . '"
|
||||
AND id_country IN (' . implode(',', array_map('intval', $countryIds)) . ')
|
||||
') ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of country IDs that have holidays configured
|
||||
*/
|
||||
public static function getCountriesWithHolidays()
|
||||
{
|
||||
return array_column(
|
||||
Db::getInstance()->executeS('SELECT DISTINCT id_country FROM ps_holidays'),
|
||||
'id_country'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get holidays within a date range
|
||||
*/
|
||||
public static function getHolidaysInRange(array $countryIds, $startDate, $endDate)
|
||||
{
|
||||
return Db::getInstance()->executeS('
|
||||
SELECT holiday_date, display_name, country_iso
|
||||
FROM ps_holidays h
|
||||
JOIN ps_country c ON c.id_country = h.id_country
|
||||
WHERE h.id_country IN (' . implode(',', array_map('intval', $countryIds)) . ')
|
||||
AND holiday_date BETWEEN "' . pSQL($startDate) . '" AND "' . pSQL($endDate) . '"
|
||||
');
|
||||
"products": {
|
||||
"groups": [
|
||||
{
|
||||
"name": "Group 1",
|
||||
"include": {
|
||||
"method": "by_category",
|
||||
"values": [3, 5, 8]
|
||||
},
|
||||
"excludes": [
|
||||
{
|
||||
"method": "specific",
|
||||
"values": [42, 99]
|
||||
}
|
||||
],
|
||||
"limit": 10,
|
||||
"sort": "bestsellers"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then override the provider method in your controller:
|
||||
|
||||
```php
|
||||
protected function getScheduleHolidayProvider()
|
||||
{
|
||||
return 'MyHolidayProvider';
|
||||
}
|
||||
```
|
||||
|
||||
## Helper Methods
|
||||
|
||||
```php
|
||||
// Get human-readable description of schedule
|
||||
$description = $this->getScheduleDescription($scheduleData);
|
||||
// Returns: "Dec 24 08:00 to Dec 24 18:00 • Weekdays only • Excl. holidays (3 countries)"
|
||||
|
||||
// Format minutes to time string
|
||||
$time = $this->formatMinutesToTime(540); // Returns "09:00"
|
||||
|
||||
// Parse time string to minutes
|
||||
$minutes = $this->parseTimeToMinutes("09:00"); // Returns 540
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Assets
|
||||
## File Structure
|
||||
|
||||
Both traits share a common CSS file with trait-specific styling:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `assets/css/admin/condition-traits.css` | Shared styling for both traits |
|
||||
| `assets/js/admin/target-conditions.js` | TargetConditions widget functionality |
|
||||
| `assets/js/admin/schedule-conditions.js` | ScheduleConditions widget functionality |
|
||||
|
||||
## CSS Classes Reference
|
||||
|
||||
### TargetConditions
|
||||
|
||||
```css
|
||||
.condition-trait { } /* Main wrapper */
|
||||
.condition-group { } /* Individual group container */
|
||||
.condition-group-header { } /* Group header with label */
|
||||
.condition-group-items { } /* Items container */
|
||||
.condition-item { } /* Individual selectable item */
|
||||
.condition-item.selected { } /* Selected state */
|
||||
.group-modifiers { } /* Limit/sort controls container */
|
||||
.condition-trait-toggle { } /* All/restricted toggle badge */
|
||||
```
|
||||
|
||||
### ScheduleConditions
|
||||
|
||||
```css
|
||||
.schedule-conditions { } /* Main wrapper */
|
||||
.schedule-section { } /* Section container */
|
||||
.schedule-datetime-range { } /* Datetime range section */
|
||||
.schedule-weekly { } /* Weekly schedule section */
|
||||
.schedule-holidays { } /* Holiday exclusions section */
|
||||
.day-timeline { } /* Individual day row */
|
||||
.timeline-track { } /* Time range slider track */
|
||||
.timeline-range { } /* Active time range bar */
|
||||
.timeline-handle { } /* Draggable handles */
|
||||
prestashop-entity-selector/
|
||||
├── src/
|
||||
│ ├── EntitySelector.php # Main trait (orchestrator)
|
||||
│ └── EntitySelector/
|
||||
│ ├── ProductConditionResolver.php
|
||||
│ ├── EntityQueryHandler.php
|
||||
│ ├── EntitySelectorRenderer.php
|
||||
│ ├── EntitySearchEngine.php
|
||||
│ ├── EntityPreviewHandler.php
|
||||
│ └── MethodHelpProvider.php
|
||||
├── sources/
|
||||
│ ├── scss/
|
||||
│ │ ├── main.scss
|
||||
│ │ ├── _variables.scss
|
||||
│ │ ├── _mixins.scss
|
||||
│ │ └── components/
|
||||
│ │ ├── _entity-selector.scss
|
||||
│ │ ├── _dropdown.scss
|
||||
│ │ ├── _chips.scss
|
||||
│ │ ├── _groups.scss
|
||||
│ │ ├── _modal.scss
|
||||
│ │ └── ...
|
||||
│ └── js/admin/entity-selector/
|
||||
│ ├── _core.js
|
||||
│ ├── _events.js
|
||||
│ ├── _dropdown.js
|
||||
│ ├── _search.js
|
||||
│ ├── _groups.js
|
||||
│ ├── _chips.js
|
||||
│ ├── _methods.js
|
||||
│ ├── _preview.js
|
||||
│ ├── _filters.js
|
||||
│ └── _utils.js
|
||||
├── assets/
|
||||
│ ├── css/admin/entity-selector.css
|
||||
│ └── js/admin/
|
||||
│ ├── entity-selector.js
|
||||
│ └── entity-selector.min.js
|
||||
├── gulpfile.js
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Requirements
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build CSS and JS
|
||||
npm run build
|
||||
|
||||
# Watch for changes during development
|
||||
npm run watch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- PrestaShop 1.7.x or 8.x
|
||||
- PHP 7.1+
|
||||
- Node.js 16+ (for building)
|
||||
|
||||
# License
|
||||
---
|
||||
|
||||
MIT License
|
||||
## License
|
||||
|
||||
Proprietary - MyPrestaRocks
|
||||
|
||||
Reference in New Issue
Block a user