diff --git a/app/Helpers/Collector/Extensions/AccountCollection.php b/app/Helpers/Collector/Extensions/AccountCollection.php index 762c47c7a4..41f3aa35b7 100644 --- a/app/Helpers/Collector/Extensions/AccountCollection.php +++ b/app/Helpers/Collector/Extensions/AccountCollection.php @@ -216,9 +216,9 @@ trait AccountCollection $this->query->leftJoin('account_types as dest_account_type', 'dest_account_type.id', '=', 'dest_account.account_type_id'); // and add fields: - $this->fields[] = 'dest_account.name as destination_account_name'; - $this->fields[] = 'dest_account.iban as destination_account_iban'; - $this->fields[] = 'dest_account_type.type as destination_account_type'; + $this->fields[] = 'dest_account.name as destination_account_name'; + $this->fields[] = 'dest_account.iban as destination_account_iban'; + $this->fields[] = 'dest_account_type.type as destination_account_type'; $this->hasAccountInfo = true; } diff --git a/app/Helpers/Collector/Extensions/CollectorProperties.php b/app/Helpers/Collector/Extensions/CollectorProperties.php index 92b74ab5c3..544478e881 100644 --- a/app/Helpers/Collector/Extensions/CollectorProperties.php +++ b/app/Helpers/Collector/Extensions/CollectorProperties.php @@ -32,20 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ trait CollectorProperties { - private array $fields; - private bool $hasAccountInfo; - private bool $hasBillInformation; - private bool $hasBudgetInformation; - private bool $hasCatInformation; - private bool $hasJoinedAttTables; - private bool $hasJoinedMetaTables; - private bool $hasJoinedTagTables; - private bool $hasNotesInformation; - private array $integerFields; - private ?int $limit; - private ?int $page; + private array $fields; + private bool $hasAccountInfo; + private bool $hasBillInformation; + private bool $hasBudgetInformation; + private bool $hasCatInformation; + private bool $hasJoinedAttTables; + private bool $hasJoinedMetaTables; + private bool $hasJoinedTagTables; + private bool $hasNotesInformation; + private array $integerFields; + private ?int $limit; + private ?int $page; + private array $postFilters; private HasMany $query; - private int $total; - private ?User $user; - private array $postFilters; + private int $total; + private ?User $user; } diff --git a/app/Helpers/Collector/Extensions/MetaCollection.php b/app/Helpers/Collector/Extensions/MetaCollection.php index 0d8666f9fd..de0843fe49 100644 --- a/app/Helpers/Collector/Extensions/MetaCollection.php +++ b/app/Helpers/Collector/Extensions/MetaCollection.php @@ -39,6 +39,50 @@ use Log; */ trait MetaCollection { + /** + * @inheritDoc + */ + public function externalIdContains(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'external_id'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId)); + + return $this; + } + + /** + * @inheritDoc + */ + public function externalIdEnds(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'external_id'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId)); + + return $this; + } + + /** + * @inheritDoc + */ + public function externalIdStarts(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'external_id'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId)); + + return $this; + } /** * Where has no tags. @@ -53,6 +97,82 @@ trait MetaCollection return $this; } + /** + * @return GroupCollectorInterface + */ + public function withTagInformation(): GroupCollectorInterface + { + $this->fields[] = 'tags.id as tag_id'; + $this->fields[] = 'tags.tag as tag_name'; + $this->fields[] = 'tags.date as tag_date'; + $this->fields[] = 'tags.description as tag_description'; + $this->fields[] = 'tags.latitude as tag_latitude'; + $this->fields[] = 'tags.longitude as tag_longitude'; + $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; + + $this->joinTagTables(); + + return $this; + } + + /** + * Join table to get tag information. + */ + protected function joinTagTables(): void + { + if (false === $this->hasJoinedTagTables) { + // join some extra tables: + $this->hasJoinedTagTables = true; + $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); + } + } + + /** + * @inheritDoc + */ + public function internalReferenceContains(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'internal_reference'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId)); + + return $this; + } + + /** + * @inheritDoc + */ + public function internalReferenceEnds(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'internal_reference'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId)); + + return $this; + } + + /** + * @inheritDoc + */ + public function internalReferenceStarts(string $externalId): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where('journal_meta.name', '=', 'internal_reference'); + $this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId)); + + return $this; + } + /** * @param string $value * @@ -66,6 +186,29 @@ trait MetaCollection return $this; } + /** + * @inheritDoc + */ + public function withNotes(): GroupCollectorInterface + { + if (false === $this->hasNotesInformation) { + // join bill table + $this->query->leftJoin( + 'notes', + static function (JoinClause $join) { + $join->on('notes.noteable_id', '=', 'transaction_journals.id'); + $join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal'); + $join->whereNull('notes.deleted_at'); + } + ); + // add fields + $this->fields[] = 'notes.text as notes'; + $this->hasNotesInformation = true; + } + + return $this; + } + /** * @param string $value * @@ -120,6 +263,25 @@ trait MetaCollection return $this; } + /** + * Will include bill name + ID, if any. + * + * @return GroupCollectorInterface + */ + public function withBillInformation(): GroupCollectorInterface + { + if (false === $this->hasBillInformation) { + // join bill table + $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); + // add fields + $this->fields[] = 'bills.id as bill_id'; + $this->fields[] = 'bills.name as bill_name'; + $this->hasBillInformation = true; + } + + return $this; + } + /** * Limit the search to a specific set of bills. * @@ -150,6 +312,27 @@ trait MetaCollection return $this; } + /** + * Will include budget ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withBudgetInformation(): GroupCollectorInterface + { + if (false === $this->hasBudgetInformation) { + // join link table + $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); + // add fields + $this->fields[] = 'budgets.id as budget_id'; + $this->fields[] = 'budgets.name as budget_name'; + $this->hasBudgetInformation = true; + } + + return $this; + } + /** * Limit the search to a specific set of budgets. * @@ -184,6 +367,27 @@ trait MetaCollection return $this; } + /** + * Will include category ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withCategoryInformation(): GroupCollectorInterface + { + if (false === $this->hasCatInformation) { + // join link table + $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); + // add fields + $this->fields[] = 'categories.id as category_id'; + $this->fields[] = 'categories.name as category_name'; + $this->hasCatInformation = true; + } + + return $this; + } + /** * Limit the search to a specific category. * @@ -214,87 +418,6 @@ trait MetaCollection return $this; } - /** - * @inheritDoc - */ - public function externalIdContains(string $externalId): GroupCollectorInterface - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - } - $this->query->where('journal_meta.name', '=', 'external_id'); - $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId)); - - return $this; - } - - /** - * @inheritDoc - */ - public function externalIdEnds(string $externalId): GroupCollectorInterface - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - } - $this->query->where('journal_meta.name', '=', 'external_id'); - $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId)); - - return $this; - } - - /** - * @inheritDoc - */ - public function externalIdStarts(string $externalId): GroupCollectorInterface - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - } - $this->query->where('journal_meta.name', '=', 'external_id'); - $this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId)); - - return $this; - } - - /** - * @inheritDoc - */ - public function withoutExternalUrl(): GroupCollectorInterface - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - } - $this->query->where(function (Builder $q1) { - $q1->where(function (Builder $q2) { - $q2->where('journal_meta.name', '=', 'external_url'); - $q2->whereNull('journal_meta.data'); - })->orWhere(function (Builder $q3) { - $q3->where('journal_meta.name', '!=', 'external_url'); - }); - }); - - return $this; - } - - /** - * @inheritDoc - */ - public function withExternalUrl(): GroupCollectorInterface - { - if (false === $this->hasJoinedMetaTables) { - $this->hasJoinedMetaTables = true; - $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); - } - $this->query->where('journal_meta.name', '=', 'external_url'); - $this->query->whereNotNull('journal_meta.data'); - - return $this; - } - /** * @inheritDoc */ @@ -353,11 +476,11 @@ trait MetaCollection $this->withTagInformation(); // this method adds a "postFilter" to the collector. - $list = $tags->pluck('tag')->toArray(); - $filter = function (int $index, array $object) use ($list): bool { - foreach($object['transactions'] as $transaction) { - foreach($transaction['tags'] as $tag) { - if(in_array($tag['name'], $list)) { + $list = $tags->pluck('tag')->toArray(); + $filter = function (int $index, array $object) use ($list): bool { + foreach ($object['transactions'] as $transaction) { + foreach ($transaction['tags'] as $tag) { + if (in_array($tag['name'], $list)) { return false; } } @@ -394,25 +517,6 @@ trait MetaCollection return $this; } - /** - * Will include bill name + ID, if any. - * - * @return GroupCollectorInterface - */ - public function withBillInformation(): GroupCollectorInterface - { - if (false === $this->hasBillInformation) { - // join bill table - $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); - // add fields - $this->fields[] = 'bills.id as bill_id'; - $this->fields[] = 'bills.name as bill_name'; - $this->hasBillInformation = true; - } - - return $this; - } - /** * Limit results to a transactions without a budget.. * @@ -426,27 +530,6 @@ trait MetaCollection return $this; } - /** - * Will include budget ID + name, if any. - * - * @return GroupCollectorInterface - */ - public function withBudgetInformation(): GroupCollectorInterface - { - if (false === $this->hasBudgetInformation) { - // join link table - $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); - // add fields - $this->fields[] = 'budgets.id as budget_id'; - $this->fields[] = 'budgets.name as budget_name'; - $this->hasBudgetInformation = true; - } - - return $this; - } - /** * Limit results to a transactions without a category. * @@ -460,64 +543,17 @@ trait MetaCollection return $this; } - /** - * Will include category ID + name, if any. - * - * @return GroupCollectorInterface - */ - public function withCategoryInformation(): GroupCollectorInterface - { - if (false === $this->hasCatInformation) { - // join link table - $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - // join cat table - $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); - // add fields - $this->fields[] = 'categories.id as category_id'; - $this->fields[] = 'categories.name as category_name'; - $this->hasCatInformation = true; - } - - return $this; - } - /** * @inheritDoc */ - public function withNotes(): GroupCollectorInterface + public function withExternalUrl(): GroupCollectorInterface { - if (false === $this->hasNotesInformation) { - // join bill table - $this->query->leftJoin( - 'notes', - static function (JoinClause $join) { - $join->on('notes.noteable_id', '=', 'transaction_journals.id'); - $join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal'); - $join->whereNull('notes.deleted_at'); - } - ); - // add fields - $this->fields[] = 'notes.text as notes'; - $this->hasNotesInformation = true; + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); } - - return $this; - } - - /** - * @return GroupCollectorInterface - */ - public function withTagInformation(): GroupCollectorInterface - { - $this->fields[] = 'tags.id as tag_id'; - $this->fields[] = 'tags.tag as tag_name'; - $this->fields[] = 'tags.date as tag_date'; - $this->fields[] = 'tags.description as tag_description'; - $this->fields[] = 'tags.latitude as tag_latitude'; - $this->fields[] = 'tags.longitude as tag_longitude'; - $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; - - $this->joinTagTables(); + $this->query->where('journal_meta.name', '=', 'external_url'); + $this->query->whereNotNull('journal_meta.data'); return $this; } @@ -560,6 +596,27 @@ trait MetaCollection return $this; } + /** + * @inheritDoc + */ + public function withoutExternalUrl(): GroupCollectorInterface + { + if (false === $this->hasJoinedMetaTables) { + $this->hasJoinedMetaTables = true; + $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); + } + $this->query->where(function (Builder $q1) { + $q1->where(function (Builder $q2) { + $q2->where('journal_meta.name', '=', 'external_url'); + $q2->whereNull('journal_meta.data'); + })->orWhere(function (Builder $q3) { + $q3->where('journal_meta.name', '!=', 'external_url'); + }); + }); + + return $this; + } + /** * @return GroupCollectorInterface */ @@ -586,17 +643,4 @@ trait MetaCollection return $this; } - - /** - * Join table to get tag information. - */ - protected function joinTagTables(): void - { - if (false === $this->hasJoinedTagTables) { - // join some extra tables: - $this->hasJoinedTagTables = true; - $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); - } - } } diff --git a/app/Helpers/Collector/Extensions/TimeCollection.php b/app/Helpers/Collector/Extensions/TimeCollection.php index 938fc29315..fd238b7f81 100644 --- a/app/Helpers/Collector/Extensions/TimeCollection.php +++ b/app/Helpers/Collector/Extensions/TimeCollection.php @@ -33,6 +33,45 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface; trait TimeCollection { + public function dayAfter(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '>=', $day); + return $this; + } + + public function dayBefore(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '<=', $day); + return $this; + } + + public function dayIs(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '=', $day); + return $this; + } + + public function monthAfter(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '>=', $month); + return $this; + + } + + public function monthBefore(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '<=', $month); + return $this; + + } + + public function monthIs(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '=', $month); + return $this; + + } + /** * Collect transactions after a specific date. * @@ -120,22 +159,9 @@ trait TimeCollection return $this; } - public function yearIs(string $year): GroupCollectorInterface + public function yearAfter(string $year): GroupCollectorInterface { - $this->query->whereYear('transaction_journals.date', '=', $year); - return $this; - } - - public function monthIs(string $month): GroupCollectorInterface - { - $this->query->whereMonth('transaction_journals.date', '=', $month); - return $this; - - } - - public function dayIs(string $day): GroupCollectorInterface - { - $this->query->whereDay('transaction_journals.date', '=', $day); + $this->query->whereYear('transaction_journals.date', '>=', $year); return $this; } @@ -145,35 +171,9 @@ trait TimeCollection return $this; } - public function monthBefore(string $month): GroupCollectorInterface + public function yearIs(string $year): GroupCollectorInterface { - $this->query->whereMonth('transaction_journals.date', '<=', $month); - return $this; - - } - - public function dayBefore(string $day): GroupCollectorInterface - { - $this->query->whereDay('transaction_journals.date', '<=', $day); - return $this; - } - - public function yearAfter(string $year): GroupCollectorInterface - { - $this->query->whereYear('transaction_journals.date', '>=', $year); - return $this; - } - - public function monthAfter(string $month): GroupCollectorInterface - { - $this->query->whereMonth('transaction_journals.date', '>=', $month); - return $this; - - } - - public function dayAfter(string $day): GroupCollectorInterface - { - $this->query->whereDay('transaction_journals.date', '>=', $day); + $this->query->whereYear('transaction_journals.date', '=', $year); return $this; } } diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 6ecbde2139..257f1ab0e9 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -203,6 +203,26 @@ class GroupCollector implements GroupCollectorInterface return $this; } + /** + * + */ + public function dumpQuery(): void + { + echo $this->query->select($this->fields)->toSql(); + echo '
';
+        print_r($this->query->getBindings());
+        echo '
'; + } + + /** + * + */ + public function dumpQueryInLogs(): void + { + Log::debug($this->query->select($this->fields)->toSql()); + Log::debug('Bindings', $this->query->getBindings()); + } + /** * @inheritDoc */ @@ -263,388 +283,6 @@ class GroupCollector implements GroupCollectorInterface return $collection; } - /** - * Same as getGroups but everything is in a paginator. - * - * @return LengthAwarePaginator - */ - public function getPaginatedGroups(): LengthAwarePaginator - { - $set = $this->getGroups(); - if (0 === $this->limit) { - $this->setLimit(50); - } - - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); - } - - /** - * Has attachments - * - * @return GroupCollectorInterface - */ - public function hasAttachments(): GroupCollectorInterface - { - Log::debug('Add filter on attachment ID.'); - $this->joinAttachmentTables(); - $this->query->whereNotNull('attachments.attachable_id'); - - return $this; - } - - /** - * Limit results to a specific currency, either foreign or normal one. - * - * @param TransactionCurrency $currency - * - * @return GroupCollectorInterface - */ - public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where( - static function (EloquentBuilder $q) use ($currency) { - $q->where('source.transaction_currency_id', $currency->id); - $q->orWhere('source.foreign_currency_id', $currency->id); - } - ); - - return $this; - } - - /** - * @inheritDoc - */ - public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface - { - $this->query->where('source.foreign_currency_id', $currency->id); - - return $this; - } - - /** - * Limit the result to a set of specific transaction groups. - * - * @param array $groupIds - * - * @return GroupCollectorInterface - */ - public function setIds(array $groupIds): GroupCollectorInterface - { - - $this->query->whereIn('transaction_groups.id', $groupIds); - - return $this; - } - - /** - * Limit the result to a set of specific journals. - * - * @param array $journalIds - * - * @return GroupCollectorInterface - */ - public function setJournalIds(array $journalIds): GroupCollectorInterface - { - if (!empty($journalIds)) { - // make all integers. - $integerIDs = array_map('intval', $journalIds); - - - $this->query->whereIn('transaction_journals.id', $integerIDs); - } - - return $this; - } - - /** - * Limit the number of returned entries. - * - * @param int $limit - * - * @return GroupCollectorInterface - */ - public function setLimit(int $limit): GroupCollectorInterface - { - $this->limit = $limit; - app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); - - return $this; - } - - /** - * Set the page to get. - * - * @param int $page - * - * @return GroupCollectorInterface - */ - public function setPage(int $page): GroupCollectorInterface - { - $page = 0 === $page ? 1 : $page; - $this->page = $page; - app('log')->debug(sprintf('GroupCollector: page is now %d', $page)); - - return $this; - } - - /** - * Search for words in descriptions. - * - * @param array $array - * - * @return GroupCollectorInterface - */ - public function setSearchWords(array $array): GroupCollectorInterface - { - $this->query->where( - static function (EloquentBuilder $q) use ($array) { - $q->where( - static function (EloquentBuilder $q1) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q1->where('transaction_journals.description', 'LIKE', $keyword); - } - } - ); - $q->orWhere( - static function (EloquentBuilder $q2) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q2->where('transaction_groups.title', 'LIKE', $keyword); - } - } - ); - } - ); - - return $this; - } - - /** - * Limit the search to one specific transaction group. - * - * @param TransactionGroup $transactionGroup - * - * @return GroupCollectorInterface - */ - public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface - { - $this->query->where('transaction_groups.id', $transactionGroup->id); - - return $this; - } - - /** - * Limit the included transaction types. - * - * @param array $types - * - * @return GroupCollectorInterface - */ - public function setTypes(array $types): GroupCollectorInterface - { - $this->query->whereIn('transaction_types.type', $types); - - return $this; - } - - /** - * Set the user object and start the query. - * - * @param User $user - * - * @return GroupCollectorInterface - */ - public function setUser(User $user): GroupCollectorInterface - { - if (null === $this->user) { - $this->user = $user; - $this->startQuery(); - - } - - return $this; - } - - /** - * Automatically include all stuff required to make API calls work. - * - * @return GroupCollectorInterface - */ - public function withAPIInformation(): GroupCollectorInterface - { - // include source + destination account name and type. - $this->withAccountInformation() - // include category ID + name (if any) - ->withCategoryInformation() - // include budget ID + name (if any) - ->withBudgetInformation() - // include bill ID + name (if any) - ->withBillInformation(); - - return $this; - } - - /** - * @inheritDoc - */ - public function withAttachmentInformation(): GroupCollectorInterface - { - $this->fields[] = 'attachments.id as attachment_id'; - $this->fields[] = 'attachments.uploaded as attachment_uploaded'; - $this->joinAttachmentTables(); - - return $this; - } - - /** - * Join table to get attachment information. - */ - private function joinAttachmentTables(): void - { - if (false === $this->hasJoinedAttTables) { - // join some extra tables: - $this->hasJoinedAttTables = true; - $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') - ->where( - static function (EloquentBuilder $q1) { - $q1->where('attachments.attachable_type', TransactionJournal::class); - //$q1->where('attachments.uploaded', true); - $q1->orWhereNull('attachments.attachable_type'); - } - ); - } - } - - /** - * Build the query. - */ - private function startQuery(): void - { - //app('log')->debug('GroupCollector::startQuery'); - $this->query = $this->user - //->transactionGroups() - //->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') - ->transactionJournals() - ->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id') - - // join source transaction. - ->leftJoin( - 'transactions as source', - function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') - ->where('source.amount', '<', 0); - } - ) - // join destination transaction - ->leftJoin( - 'transactions as destination', - function (JoinClause $join) { - $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') - ->where('destination.amount', '>', 0); - } - ) - // left join transaction type. - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id') - ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id') - ->whereNull('transaction_groups.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->whereNull('source.deleted_at') - ->whereNull('destination.deleted_at') - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->orderBy('transaction_journals.description', 'DESC') - ->orderBy('source.amount', 'DESC'); - } - - /** - * - */ - public function dumpQuery(): void - { - echo $this->query->select($this->fields)->toSql(); - echo '
';
-        print_r($this->query->getBindings());
-        echo '
'; - } - - /** - * - */ - public function dumpQueryInLogs(): void - { - Log::debug($this->query->select($this->fields)->toSql()); - Log::debug('Bindings', $this->query->getBindings()); - } - - /** - * Convert a selected set of fields to arrays. - * - * @param array $array - * - * @return array - */ - private function convertToInteger(array $array): array - { - foreach ($this->integerFields as $field) { - $array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null; - } - - return $array; - } - - /** - * @param array $existingJournal - * @param TransactionJournal $newJournal - * - * @return array - */ - private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array - { - $newArray = $newJournal->toArray(); - if (array_key_exists('attachment_id', $newArray)) { - $attachmentId = (int) $newJournal['attachment_id']; - - $existingJournal['attachments'][$attachmentId] = [ - 'id' => $attachmentId, - ]; - } - - return $existingJournal; - } - - /** - * @param array $existingJournal - * @param TransactionJournal $newJournal - * - * @return array - */ - private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array - { - $newArray = $newJournal->toArray(); - if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well. - $tagId = (int) $newJournal['tag_id']; - - $tagDate = null; - try { - $tagDate = Carbon::parse($newArray['tag_date']); - } catch (InvalidDateException $e) { - Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); - } - - $existingJournal['tags'][$tagId] = [ - 'id' => (int) $newArray['tag_id'], - 'name' => $newArray['tag_name'], - 'date' => $tagDate, - 'description' => $newArray['tag_description'], - ]; - } - - return $existingJournal; - } - /** * @param Collection $collection * @@ -754,6 +392,72 @@ class GroupCollector implements GroupCollectorInterface return $result; } + /** + * Convert a selected set of fields to arrays. + * + * @param array $array + * + * @return array + */ + private function convertToInteger(array $array): array + { + foreach ($this->integerFields as $field) { + $array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null; + } + + return $array; + } + + /** + * @param array $existingJournal + * @param TransactionJournal $newJournal + * + * @return array + */ + private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array + { + $newArray = $newJournal->toArray(); + if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well. + $tagId = (int) $newJournal['tag_id']; + + $tagDate = null; + try { + $tagDate = Carbon::parse($newArray['tag_date']); + } catch (InvalidDateException $e) { + Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); + } + + $existingJournal['tags'][$tagId] = [ + 'id' => (int) $newArray['tag_id'], + 'name' => $newArray['tag_name'], + 'date' => $tagDate, + 'description' => $newArray['tag_description'], + ]; + } + + return $existingJournal; + } + + /** + * @param array $existingJournal + * @param TransactionJournal $newJournal + * + * @return array + */ + private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array + { + $newArray = $newJournal->toArray(); + if (array_key_exists('attachment_id', $newArray)) { + $attachmentId = (int) $newJournal['attachment_id']; + + $existingJournal['attachments'][$attachmentId] = [ + 'id' => $attachmentId, + ]; + } + + return $existingJournal; + } + /** * @param array $groups * @@ -824,4 +528,300 @@ class GroupCollector implements GroupCollectorInterface } return $newCollection; } + + /** + * Same as getGroups but everything is in a paginator. + * + * @return LengthAwarePaginator + */ + public function getPaginatedGroups(): LengthAwarePaginator + { + $set = $this->getGroups(); + if (0 === $this->limit) { + $this->setLimit(50); + } + + return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + } + + /** + * Limit the number of returned entries. + * + * @param int $limit + * + * @return GroupCollectorInterface + */ + public function setLimit(int $limit): GroupCollectorInterface + { + $this->limit = $limit; + app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); + + return $this; + } + + /** + * Has attachments + * + * @return GroupCollectorInterface + */ + public function hasAttachments(): GroupCollectorInterface + { + Log::debug('Add filter on attachment ID.'); + $this->joinAttachmentTables(); + $this->query->whereNotNull('attachments.attachable_id'); + + return $this; + } + + /** + * Join table to get attachment information. + */ + private function joinAttachmentTables(): void + { + if (false === $this->hasJoinedAttTables) { + // join some extra tables: + $this->hasJoinedAttTables = true; + $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') + ->where( + static function (EloquentBuilder $q1) { + $q1->where('attachments.attachable_type', TransactionJournal::class); + //$q1->where('attachments.uploaded', true); + $q1->orWhereNull('attachments.attachable_type'); + } + ); + } + } + + /** + * Limit results to a specific currency, either foreign or normal one. + * + * @param TransactionCurrency $currency + * + * @return GroupCollectorInterface + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where( + static function (EloquentBuilder $q) use ($currency) { + $q->where('source.transaction_currency_id', $currency->id); + $q->orWhere('source.foreign_currency_id', $currency->id); + } + ); + + return $this; + } + + /** + * @inheritDoc + */ + public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where('source.foreign_currency_id', $currency->id); + + return $this; + } + + /** + * Limit the result to a set of specific transaction groups. + * + * @param array $groupIds + * + * @return GroupCollectorInterface + */ + public function setIds(array $groupIds): GroupCollectorInterface + { + + $this->query->whereIn('transaction_groups.id', $groupIds); + + return $this; + } + + /** + * Limit the result to a set of specific journals. + * + * @param array $journalIds + * + * @return GroupCollectorInterface + */ + public function setJournalIds(array $journalIds): GroupCollectorInterface + { + if (!empty($journalIds)) { + // make all integers. + $integerIDs = array_map('intval', $journalIds); + + + $this->query->whereIn('transaction_journals.id', $integerIDs); + } + + return $this; + } + + /** + * Set the page to get. + * + * @param int $page + * + * @return GroupCollectorInterface + */ + public function setPage(int $page): GroupCollectorInterface + { + $page = 0 === $page ? 1 : $page; + $this->page = $page; + app('log')->debug(sprintf('GroupCollector: page is now %d', $page)); + + return $this; + } + + /** + * Search for words in descriptions. + * + * @param array $array + * + * @return GroupCollectorInterface + */ + public function setSearchWords(array $array): GroupCollectorInterface + { + $this->query->where( + static function (EloquentBuilder $q) use ($array) { + $q->where( + static function (EloquentBuilder $q1) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q1->where('transaction_journals.description', 'LIKE', $keyword); + } + } + ); + $q->orWhere( + static function (EloquentBuilder $q2) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q2->where('transaction_groups.title', 'LIKE', $keyword); + } + } + ); + } + ); + + return $this; + } + + /** + * Limit the search to one specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface + { + $this->query->where('transaction_groups.id', $transactionGroup->id); + + return $this; + } + + /** + * Limit the included transaction types. + * + * @param array $types + * + * @return GroupCollectorInterface + */ + public function setTypes(array $types): GroupCollectorInterface + { + $this->query->whereIn('transaction_types.type', $types); + + return $this; + } + + /** + * Set the user object and start the query. + * + * @param User $user + * + * @return GroupCollectorInterface + */ + public function setUser(User $user): GroupCollectorInterface + { + if (null === $this->user) { + $this->user = $user; + $this->startQuery(); + + } + + return $this; + } + + /** + * Build the query. + */ + private function startQuery(): void + { + //app('log')->debug('GroupCollector::startQuery'); + $this->query = $this->user + //->transactionGroups() + //->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + ->transactionJournals() + ->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + + // join source transaction. + ->leftJoin( + 'transactions as source', + function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') + ->where('source.amount', '<', 0); + } + ) + // join destination transaction + ->leftJoin( + 'transactions as destination', + function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') + ->where('destination.amount', '>', 0); + } + ) + // left join transaction type. + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id') + ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id') + ->whereNull('transaction_groups.deleted_at') + ->whereNull('transaction_journals.deleted_at') + ->whereNull('source.deleted_at') + ->whereNull('destination.deleted_at') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->orderBy('transaction_journals.description', 'DESC') + ->orderBy('source.amount', 'DESC'); + } + + /** + * Automatically include all stuff required to make API calls work. + * + * @return GroupCollectorInterface + */ + public function withAPIInformation(): GroupCollectorInterface + { + // include source + destination account name and type. + $this->withAccountInformation() + // include category ID + name (if any) + ->withCategoryInformation() + // include budget ID + name (if any) + ->withBudgetInformation() + // include bill ID + name (if any) + ->withBillInformation(); + + return $this; + } + + /** + * @inheritDoc + */ + public function withAttachmentInformation(): GroupCollectorInterface + { + $this->fields[] = 'attachments.id as attachment_id'; + $this->fields[] = 'attachments.uploaded as attachment_uploaded'; + $this->joinAttachmentTables(); + + return $this; + } } diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index f8ea25d59b..03e4a457d5 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -66,6 +66,24 @@ interface GroupCollectorInterface */ public function amountMore(string $amount): GroupCollectorInterface; + /** + * @param string $day + * @return GroupCollectorInterface + */ + public function dayAfter(string $day): GroupCollectorInterface; + + /** + * @param string $day + * @return GroupCollectorInterface + */ + public function dayBefore(string $day): GroupCollectorInterface; + + /** + * @param string $day + * @return GroupCollectorInterface + */ + public function dayIs(string $day): GroupCollectorInterface; + /** * End of the description must match: * @@ -111,6 +129,24 @@ interface GroupCollectorInterface */ public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface; + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function externalIdContains(string $externalId): GroupCollectorInterface; + + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function externalIdEnds(string $externalId): GroupCollectorInterface; + + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function externalIdStarts(string $externalId): GroupCollectorInterface; + /** * Ensure the search will find nothing at all, zero results. * @@ -151,6 +187,42 @@ interface GroupCollectorInterface */ public function hasAttachments(): GroupCollectorInterface; + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function internalReferenceContains(string $externalId): GroupCollectorInterface; + + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function internalReferenceEnds(string $externalId): GroupCollectorInterface; + + /** + * @param string $externalId + * @return GroupCollectorInterface + */ + public function internalReferenceStarts(string $externalId): GroupCollectorInterface; + + /** + * @param string $month + * @return GroupCollectorInterface + */ + public function monthAfter(string $month): GroupCollectorInterface; + + /** + * @param string $month + * @return GroupCollectorInterface + */ + public function monthBefore(string $month): GroupCollectorInterface; + + /** + * @param string $month + * @return GroupCollectorInterface + */ + public function monthIs(string $month): GroupCollectorInterface; + /** * @param string $value * @@ -305,38 +377,6 @@ interface GroupCollectorInterface */ public function setExternalId(string $externalId): GroupCollectorInterface; - /** - * @param string $externalId - * @return GroupCollectorInterface - */ - public function externalIdContains(string $externalId): GroupCollectorInterface; - - /** - * @param string $externalId - * @return GroupCollectorInterface - */ - public function externalIdStarts(string $externalId): GroupCollectorInterface; - - /** - * @param string $externalId - * @return GroupCollectorInterface - */ - public function externalIdEnds(string $externalId): GroupCollectorInterface; - - /** - * Transactions without an external URL - * - * @return GroupCollectorInterface - */ - public function withoutExternalUrl(): GroupCollectorInterface; - - /** - * Transactions with an external URL - * - * @return GroupCollectorInterface - */ - public function withExternalUrl(): GroupCollectorInterface; - /** * Limit results to a specific foreign currency. * @@ -437,15 +477,6 @@ interface GroupCollectorInterface */ public function setTags(Collection $tags): GroupCollectorInterface; - /** - * Only when does not have these tags - * - * @param Collection $tags - * - * @return GroupCollectorInterface - */ - public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface; - /** * Limit the search to one specific transaction group. * @@ -482,6 +513,15 @@ interface GroupCollectorInterface */ public function setUser(User $user): GroupCollectorInterface; + /** + * Only when does not have these tags + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface; + /** * Either account can be set, but NOT both. This effectively excludes internal transfers. * @@ -561,6 +601,13 @@ interface GroupCollectorInterface */ public function withCategoryInformation(): GroupCollectorInterface; + /** + * Transactions with an external URL + * + * @return GroupCollectorInterface + */ + public function withExternalUrl(): GroupCollectorInterface; + /** * Will include notes. * @@ -596,6 +643,13 @@ interface GroupCollectorInterface */ public function withoutCategory(): GroupCollectorInterface; + /** + * Transactions without an external URL + * + * @return GroupCollectorInterface + */ + public function withoutExternalUrl(): GroupCollectorInterface; + /** * @return GroupCollectorInterface */ @@ -606,15 +660,22 @@ interface GroupCollectorInterface */ public function withoutTags(): GroupCollectorInterface; - - public function yearIs(string $year): GroupCollectorInterface; - public function monthIs(string $month): GroupCollectorInterface; - public function dayIs(string $day): GroupCollectorInterface; - public function yearBefore(string $year): GroupCollectorInterface; - public function monthBefore(string $month): GroupCollectorInterface; - public function dayBefore(string $day): GroupCollectorInterface; + /** + * @param string $year + * @return GroupCollectorInterface + */ public function yearAfter(string $year): GroupCollectorInterface; - public function monthAfter(string $month): GroupCollectorInterface; - public function dayAfter(string $day): GroupCollectorInterface; + + /** + * @param string $year + * @return GroupCollectorInterface + */ + public function yearBefore(string $year): GroupCollectorInterface; + + /** + * @param string $year + * @return GroupCollectorInterface + */ + public function yearIs(string $year): GroupCollectorInterface; } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index fcd52121df..10de656277 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -757,9 +757,18 @@ class OperatorQuerySearch implements SearchInterface case 'external_id_ends': $this->collector->externalIdEnds($value); break; - case 'internal_reference': + case 'internal_reference_is': $this->collector->setInternalReference($value); break; + case 'internal_reference_contains': + $this->collector->internalReferenceContains($value); + break; + case 'internal_reference_starts': + $this->collector->internalReferenceStarts($value); + break; + case 'internal_reference_ends': + $this->collector->internalReferenceEnds($value); + break; } return true; diff --git a/config/search.php b/config/search.php index 3b296746c2..14b04aee37 100644 --- a/config/search.php +++ b/config/search.php @@ -31,7 +31,6 @@ return [ 'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true,], 'source_account_starts' => ['alias' => false, 'needs_context' => true,], 'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true,], - 'source_account_nr_is' => ['alias' => false, 'needs_context' => true,], 'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true,], 'source_account_nr_contains' => ['alias' => false, 'needs_context' => true,], @@ -40,7 +39,6 @@ return [ 'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true,], 'source_account_nr_starts' => ['alias' => false, 'needs_context' => true,], 'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true,], - 'destination_account_is' => ['alias' => false, 'needs_context' => true,], 'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true,], 'destination_account_contains' => ['alias' => false, 'needs_context' => true,], @@ -51,7 +49,6 @@ return [ 'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true,], 'destination_account_starts' => ['alias' => false, 'needs_context' => true,], 'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true,], - 'destination_account_nr_is' => ['alias' => false, 'needs_context' => true,], 'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true,], 'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true,], @@ -60,17 +57,14 @@ return [ 'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true,], 'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true,], 'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true,], - 'account_is' => ['alias' => false, 'needs_context' => true,], 'account_contains' => ['alias' => false, 'needs_context' => true,], 'account_ends' => ['alias' => false, 'needs_context' => true,], 'account_starts' => ['alias' => false, 'needs_context' => true,], - 'account_nr_is' => ['alias' => false, 'needs_context' => true,], 'account_nr_contains' => ['alias' => false, 'needs_context' => true,], 'account_nr_ends' => ['alias' => false, 'needs_context' => true,], 'account_nr_starts' => ['alias' => false, 'needs_context' => true,], - 'category_is' => ['alias' => false, 'needs_context' => true,], 'category_contains' => ['alias' => false, 'needs_context' => true,], 'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true,], @@ -86,19 +80,19 @@ return [ 'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true,], 'bill_ends' => ['alias' => false, 'needs_context' => true,], 'bill_starts' => ['alias' => false, 'needs_context' => true,], - 'external_id_is' => ['alias' => false, 'needs_context' => true,], 'external_id_contains' => ['alias' => false, 'needs_context' => true,], 'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true,], 'external_id_ends' => ['alias' => false, 'needs_context' => true,], 'external_id_starts' => ['alias' => false, 'needs_context' => true,], - // TODO here we are! 'internal_reference_is' => ['alias' => false, 'needs_context' => true,], 'internal_reference_contains' => ['alias' => false, 'needs_context' => true,], 'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true,], 'internal_reference_ends' => ['alias' => false, 'needs_context' => true,], 'internal_reference_starts' => ['alias' => false, 'needs_context' => true,], + + // TODO here we are. 'external_url_is' => ['alias' => false, 'needs_context' => true,], 'external_url_contains' => ['alias' => false, 'needs_context' => true,], 'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true,],