Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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.


Expand Down Expand Up @@ -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
Expand All @@ -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
188 changes: 105 additions & 83 deletions src/behaviors/EntryQueryBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use craft\helpers\Db;
use yii\base\Behavior;
use yii\base\InvalidConfigException;

use craft\commerce\Plugin as Commerce;
/**
* Class EntryQueryBehavior
*
Expand All @@ -32,7 +32,7 @@ class EntryQueryBehavior extends Behavior

public $includeToday;

public string|null $entryTypeHandle = null;
public string|array|object|null $entryTypeHandle = null;

/**
* @inheritdoc
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -96,116 +96,133 @@ 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;

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;
Expand All @@ -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,
Expand Down