diff --git a/CHANGELOG.md b/CHANGELOG.md index 704058f..ed91e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Forked Changes +- Added support for Craft Commerce product types alongside standard entry types +- Improved type handling to work with multiple entry/product types simultaneously +- Added support for passing entry type handles as: + - Single string: `'events'` + - Array of strings: `['events', 'conferences']` +- Added support for passing entry type objects directly: + - Single object: `craft.app.entries.getEntryTypeByHandle('events')` + - Array of objects: `[craft.app.entries.getEntryTypeByHandle('events'), craft.commerce.productTypes.getProductTypeByHandle('conferences')]` +- Priority given to Craft entries over Commerce products when type handles are the same + +Examples: +```twig +{# Single type #} +{% set entries = craft.entries.section('events').isFuture('dateFieldHandle', 'events') %} + +{# Multiple types with array #} +{% set entries = craft.entries.section(['events', 'conferences']).isFuture('dateFieldHandle', ['events', 'conferences']) %} + +{# Using entry type objects directly #} +{% set eventType = craft.app.entries.getEntryTypeByHandle('events') %} +{% set entries = craft.entries.section('events').isFuture('dateFieldHandle', eventType) %} + +{# Using multiple type objects from products #} +{% set types = [ + craft.commerce.productTypes.getProductTypeByHandle('events') + craft.commerce.productTypes.getProductTypeByHandle('conferences') +] %} +{% set entries = craft.products().type(['events', 'conference']).isFuture('dateFieldHandle', types) %} + +{# Using multiple type objects from entries #} +{% set types = [ + craft.app.entries.getEntryTypeByHandle('events'), + craft.app.entries.getEntryTypeByHandle('conferences'), +] %} +{% set entries = craft.entries.section(['events', 'conferences']).isFuture('dateFieldHandle', types) %} +``` + ## 5.0.0-beta.4 - 2024-12-16 ### Fixed - Craft 5 version now works with Postgres ([#48](https://github.com/studioespresso/craft-date-range/issues/48)) @@ -19,11 +57,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## 5.0.0-beta.1 - 2024-03-22 -> [!WARNING] +> [!WARNING] > When upgrading to Craft 5, you'll need to update templates that use ``isPast``, ``isFuture``, ``isOnGoing`` or ``isNotPast``. Please see the [upgrade guide](https://github.com/studioespresso/craft-date-range/tree/v5?tab=readme-ov-file#upgrading-to-craft-5) for more information. ### Added -- Craft 5 support +- Craft 5 support ## 3.0.1 - 2022-06-19 ### Fixed @@ -70,7 +108,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## 2.1.2 - 2020-11-03 -### Fixed +### Fixed - Fixed an issue with ``formatted()`` where the start date was used instead of the end date. @@ -104,7 +142,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added - Added french translation of the field labels (Thanks [@ockam](https://github.com/ockam), [#6](https://github.com/studioespresso/craft-date-range/issues/6)) -- Added dutch translations of the field labels +- Added dutch translations of the field labels - Added optional `includeToday` parameter to entry query behaviour ## 1.2.1 - 2019-12-02 @@ -119,8 +157,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## 1.1.0 - 2019-11-29 ### Added - The field can now be displayed on element overview pages in the CP -- Added the `getFormatted` option to the field to display all data in 1 line +- Added the `getFormatted` option to the field to display all data in 1 line ## 1.0.1 - 2019-11-21 -### Fixed +### Fixed - Fixed an issue where using `isOngoing()` wouldn't use the correct data and woul \ No newline at end of file diff --git a/src/behaviors/EntryQueryBehavior.php b/src/behaviors/EntryQueryBehavior.php index 7714abe..f19bff8 100644 --- a/src/behaviors/EntryQueryBehavior.php +++ b/src/behaviors/EntryQueryBehavior.php @@ -8,7 +8,7 @@ use craft\helpers\Db; use yii\base\Behavior; use yii\base\InvalidConfigException; - +use craft\commerce\Plugin as Commerce; /** * Class EntryQueryBehavior * @@ -32,7 +32,7 @@ class EntryQueryBehavior extends Behavior public $includeToday; - public string|null $entryTypeHandle = null; + public string|array|object|null $entryTypeHandle = null; /** * @inheritdoc @@ -44,7 +44,7 @@ public function events() ]; } - public function isFuture($value, string|bool $entryTypeHandle = null, bool $includeToday = false) + public function isFuture($value, string|array|object|bool $entryTypeHandle = null, bool $includeToday = false) { $value = $this->parseArgumentValue($value, $entryTypeHandle, $includeToday); @@ -56,7 +56,7 @@ public function isFuture($value, string|bool $entryTypeHandle = null, bool $incl return $this->owner; } - public function isPast($value, string|bool $entryTypeHandle = null, $includeToday = false) + public function isPast($value, string|array|object|bool $entryTypeHandle = null, $includeToday = false) { $value = $this->parseArgumentValue($value, $entryTypeHandle, $includeToday); @@ -67,7 +67,7 @@ public function isPast($value, string|bool $entryTypeHandle = null, $includeToda return $this->owner; } - public function isNotPast($value, string|bool $entryTypeHandle = null, $includeToday = false) + public function isNotPast($value, string|array|object|bool $entryTypeHandle = null, $includeToday = false) { $value = $this->parseArgumentValue($value, $entryTypeHandle, $includeToday); @@ -78,7 +78,7 @@ public function isNotPast($value, string|bool $entryTypeHandle = null, $includeT return $this->owner; } - public function isOnGoing($value, string|bool $entryTypeHandle = null, $includeToday = false) + public function isOnGoing($value, string|array|object|bool $entryTypeHandle = null, $includeToday = false) { $value = $this->parseArgumentValue($value, $entryTypeHandle, $includeToday); @@ -96,108 +96,125 @@ public function onAfterPrepare() } if ($this->handle && $this->entryTypeHandle) { - $type = Craft::$app->getEntries()->getEntryTypeByHandle($this->entryTypeHandle); - if (!$type) { - throw new InvalidConfigException("Invalid entryType specified"); + $fieldsForTypes = []; + $entryTypes = $this->getEntryTypes(); + + foreach ($entryTypes as $typeHandle => $entryType) { + $layout = Craft::$app->getFields()->getLayoutById($entryType->fieldLayoutId); + $field = $layout->getFieldByHandle($this->handle); + if ($field) { + $fieldsForTypes[$typeHandle] = $field; + } } - $layout = Craft::$app->getFields()->getLayoutById($type->fieldLayoutId); - $this->field = $layout->getFieldByHandle($this->handle); - } - if (Craft::$app->db->getIsPgsql()) { - /** @var \craft\base\FieldInterface|null $field */ - $field = $this->field; - if ($field && $this->isFuture) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('start'), - date('Y-m-d'), - $this->includeToday ? '>=' : '>' - )); + // If we have fields to work with + if (!empty($fieldsForTypes)) { + $this->processDateQueries($fieldsForTypes); } + } + } - if ($field && $this->isPast) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('end'), - date('Y-m-d'), - $this->includeToday ? '<=' : '<' - )); - } + /** + * Get entry types from either handles or objects + * + * @return array Array of entry type objects indexed by handle + */ + protected function getEntryTypes() + { + $entryTypes = []; - if ($field && $this->isNotPast) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('end'), - date('Y-m-d'), - $this->includeToday ? '>=' : '>' - )); - } + // Convert to array if single value + $types = is_array($this->entryTypeHandle) ? $this->entryTypeHandle : [$this->entryTypeHandle]; - if ($field && $this->isOnGoing) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('start'), - date('Y-m-d'), - $this->includeToday ? '<=' : '<' - )); - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('end'), - date('Y-m-d'), - $this->includeToday ? '>=' : '>' - )); + foreach ($types as $key => $type) { + // If it's an object with fieldLayoutId property, use it directly + if (is_object($type) && property_exists($type, 'fieldLayoutId')) { + $handle = property_exists($type, 'handle') ? $type->handle : 'type_' . $key; + $entryTypes[$handle] = $type; + } + // If it's a string, try to get the entry type + else if (is_string($type)) { + $trimmedType = trim($type); + // Try to get from Entry Types first + $entryType = Craft::$app->getEntries()->getEntryTypeByHandle($trimmedType); + if ($entryType) { + $entryTypes[$trimmedType] = $entryType; + } else { + // If not found, try Commerce Product Types + $productType = Commerce::getInstance()->getProductTypes()->getProductTypeByHandle($trimmedType); + if ($productType) { + $entryTypes[$trimmedType] = $productType; + } else { + throw new InvalidConfigException("Invalid type specified: " . $trimmedType); + } + } } - } elseif (Craft::$app->db->getIsMysql()) { - /** @var \craft\base\FieldInterface|null $field */ - $field = $this->field; - if ($field && $this->isFuture) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( + } + + if (empty($entryTypes)) { + throw new InvalidConfigException("No valid entry types were found"); + } + + return $entryTypes; + } + + protected function processDateQueries($fieldsForTypes) + { + if (Craft::$app->db->getIsPgsql() || Craft::$app->db->getIsMysql()) { + $or = ['or']; + + foreach ($fieldsForTypes as $typeHandle => $field) { + if ($this->isFuture) { + $or[] = Db::parseDateParam( $field->getValueSql('start'), date('Y-m-d'), $this->includeToday ? '>=' : '>' - )); - } + ); + } - if ($field && $this->isPast) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( + if ($this->isPast) { + $or[] = Db::parseDateParam( $field->getValueSql('end'), date('Y-m-d'), $this->includeToday ? '<=' : '<' - )); - } + ); + } - if ($field && $this->isNotPast) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( + if ($this->isNotPast) { + $or[] = Db::parseDateParam( $field->getValueSql('end'), date('Y-m-d'), $this->includeToday ? '>=' : '>' - )); + ); + } + + if ($this->isOnGoing) { + $and = ['and', + Db::parseDateParam( + $field->getValueSql('start'), + date('Y-m-d'), + $this->includeToday ? '<=' : '<' + ), + Db::parseDateParam( + $field->getValueSql('end'), + date('Y-m-d'), + $this->includeToday ? '>=' : '>' + ) + ]; + $or[] = $and; + } } - if ($field && $this->isOnGoing) { - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('start'), - date('Y-m-d'), - $this->includeToday ? '<=' : '<' - )); - $this->owner->subQuery - ->andWhere(Db::parseDateParam( - $field->getValueSql('end'), - date('Y-m-d'), - $this->includeToday ? '>=' : '>' - )); + // Only add the OR condition if we have more than just the 'or' element + if (count($or) > 1) { + $this->owner->subQuery->andWhere($or); } } } protected function parseArgumentValue( string|array $value, - string|bool $entryTypeHandle = null, + string|array|object|bool $entryTypeHandle = null, $includeToday = false, ): array { $handle = null; @@ -205,7 +222,7 @@ protected function parseArgumentValue( if (is_array($value)) { $handle = $value[0] ?? null; $arg2 = $value[1] ?? null; - if (is_string($arg2)) { + if (is_string($arg2) || is_array($arg2) || is_object($arg2)) { $entryTypeHandle = $arg2; } elseif ($arg2 !== null) { $includeToday = $arg2; @@ -214,6 +231,11 @@ protected function parseArgumentValue( $handle = $value; } + // If entryTypeHandle is a comma-separated string, convert it to an array + if (is_string($entryTypeHandle) && strpos($entryTypeHandle, ',') !== false) { + $entryTypeHandle = array_map('trim', explode(',', $entryTypeHandle)); + } + return [ 'handle' => $handle, 'entryTypeHandle' => $entryTypeHandle,