# PrestaShop Condition Traits 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 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. ## 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 ## Basic Usage ### 1. Include the Trait ```php 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(); } return parent::postProcess(); } ``` ### 5. Evaluate Schedule at Runtime ```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 ], $savedData); ``` ## Data Format The schedule 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 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) . '" '); } } ``` 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 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 */ ``` --- # Requirements - PrestaShop 1.7.x or 8.x - PHP 7.1+ # License MIT License