Current File : /home/n742ef5/royalanteam.com/wp-content/plugins/myhome-core/framework/app/Search/SearchModels.php
<?php

namespace Tangibledesign\Framework\Search;

use Tangibledesign\Framework\Actions\Search\SearchTitleAndDescriptionAction;
use Tangibledesign\Framework\Core\Collection;
use Tangibledesign\Framework\Models\Field\GalleryField;
use Tangibledesign\Framework\Models\Field\LocationField;
use Tangibledesign\Framework\Models\Field\TaxonomyField;
use Tangibledesign\Framework\Models\Model;
use Tangibledesign\Framework\Models\Post\PostStatus;
use Tangibledesign\Framework\Search\Query\TaxonomyQueryModifier;
use WP_Query;

class SearchModels
{
    protected string $postType = '';
    protected array $params;
    protected ?int $count = null;
    protected static array $cache = [];
    protected ?Collection $urlPartials = null;
    protected bool $includeExcluded = false;
    protected ?LocationField $locationField = null;
    protected ?array $modelExcludedByLocationIds = null;
    protected ?Collection $listings = null;
    protected ?array $initialModelIds = null;

    public function __construct(array $params = [], ?LocationField $locationField = null)
    {
        $this->params = $params;

        $this->locationField = $locationField;

        $this->postType = tdf_model_post_type();
    }

    public function includeExcluded(): void
    {
        $this->includeExcluded = true;
    }

    private function getFilters(): array
    {
        return $this->params['filters'] ?? [];
    }

    private function getParams(): array
    {
        return $this->params['params'] ?? [];
    }

    private function getUserIds(): array
    {
        $userIds = $this->params['userIds'] ?? [];
        if (empty($userIds)) {
            return [];
        }

        return array_map('intval', $userIds);
    }

    public function getResults(): array
    {
        $filters = $this->getFilters();
        $modelsIds = $this->getModelIds(
            tdf_app('search_results_modifiers'),
            $this->getFilters(),
            $this->getParams()
        );

        if ($modelsIds === false) {
            return [
                'template' => '',
                'url' => $this->getUrl($filters, $this->getParams()),
                'count' => 0,
                'termCount' => [],
                'breadcrumbs' => $this->getBreadcrumbs(),
                'markers' => [],
                'title' => '',
            ];
        }

        $searchTitleAndDescriptionAction = new SearchTitleAndDescriptionAction();

        $response = [
            'url' => $this->getUrl($filters, $this->getParams()),
            'count' => $this->count,
            'termCount' => $this->getTermsCount($filters, $this->getParams()),
            'breadcrumbs' => $this->getBreadcrumbs(),
            'markers' => [],
            'title' => $searchTitleAndDescriptionAction->getTitle($filters),
            'description' => $searchTitleAndDescriptionAction->getDescription($filters),
        ];

        if (!empty($this->params['template'])) {
            $response['template'] = $this->getTemplate($modelsIds);
        }

        if (!empty($this->params['map'])) {
            $response['markers'] = $this->getMarkers($modelsIds);
        }

        return $response;
    }

    private function getModelIdsExcludedByLocationField(): array
    {
        if (is_array($this->modelExcludedByLocationIds)) {
            return $this->modelExcludedByLocationIds;
        }

        if (!$this->locationField) {
            return [];
        }

        $locationFieldKey = $this->locationField->getKey();

        $this->modelExcludedByLocationIds = (new WP_Query([
            'post_type' => tdf_model_post_type(),
            'posts_per_page' => -1,
            'fields' => 'ids',
            'post_status' => PostStatus::PUBLISH,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'no_found_rows' => true,
            'meta_query' => [
                'relation' => 'OR',
                [
                    'relation' => 'AND',
                    [
                        'key' => $locationFieldKey . '_lat',
                        'compare' => 'NOT EXISTS'
                    ],
                    [
                        'key' => $locationFieldKey . '_lng',
                        'compare' => 'NOT EXISTS'
                    ],
                ],
                [
                    'relation' => 'AND',
                    [
                        'key' => $locationFieldKey . '_lat',
                        'value' => '0',
                    ],
                    [
                        'key' => $locationFieldKey . '_lng',
                        'value' => '0',
                    ],
                ]
            ]
        ]))->posts;

        return $this->modelExcludedByLocationIds;
    }

    private function getInitialModelIds(): ?array
    {
        if ($this->initialModelIds !== null) {
            return $this->initialModelIds;
        }

        $userIds = $this->getUserIds();
        if (empty($userIds)) {
            return [];
        }

        $this->initialModelIds = (new WP_Query([
            'post_type' => tdf_model_post_type(),
            'posts_per_page' => -1,
            'fields' => 'ids',
            'post_status' => PostStatus::PUBLISH,
            'author__in' => $userIds,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'no_found_rows' => true,
        ]))->posts;

        if (empty($this->initialModelIds)) {
            return null;
        }

        return $this->initialModelIds;
    }

    /**
     * @param Collection $searchResultsModifiers
     * @param array $filters
     * @param array $params
     * @return array|false
     */
    public function getModelIds(Collection $searchResultsModifiers, array $filters, array $params = [])
    {
        $modelIds = $this->getInitialModelIds();
        if ($modelIds === null) {
            return false;
        }

        $flag = false;

        foreach ($searchResultsModifiers as $searchResultsModifier) {
            /* @var SearchResultsModifier $searchResultsModifier */
            $ids = $searchResultsModifier->getModelIds($this->postType, $filters);
            if ($ids === false) {
                continue;
            }

            if (empty($ids)) {
                return false;
            }

            if (!$flag) {
                $modelIds = $ids;
                $flag = true;
            } else {
                $modelIds = array_intersect($modelIds, $ids);
            }
        }

        $modelIds = array_values(array_unique($modelIds));
        if (!$this->includeExcluded) {
            $modelIds = $this->filterExcludedFromSearch($modelIds);
        }

        if ($modelIds === null || ($flag && empty($modelIds))) {
            return false;
        }

        $modelIds = tdf_collect($modelIds)->filter(static function ($modelId) {
            return !in_array($modelId, tdf_app('models_excluded_from_search'), true);
        })->values();

        return $this->applyParams($modelIds, $params, true);
    }

    private function applyParams(array $modelIds, array $params, bool $setCount = false): array
    {
        $args = [
            'post_type' => $this->postType,
            'post__in' => $modelIds,
            'posts_per_page' => -1,
            'post_status' => PostStatus::PUBLISH,
            'fields' => 'ids',
            'author__in' => $this->getUserIds(),
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ];

        if (!$this->includeExcluded) {
            $args['post__not_in'] = array_merge(tdf_app('models_excluded_from_search'),
                $this->getModelIdsExcludedByLocationField());
        } else {
            $args['post__not_in'] = $this->getModelIdsExcludedByLocationField();
        }

        $args['post__not_in'] = apply_filters(tdf_prefix() . '/search/excluded', $args['post__not_in']);

        if (!empty($args['post__in']) && !empty($args['post__not_in'])) {
            $args['post__in'] = tdf_collect($args['post__in'])->filter(static function ($id) use ($args) {
                return !in_array($id, $args['post__not_in'], true);
            })->values();
        }

        foreach (tdf_app('search_params_modifiers') as $searchParamModifier) {
            /* @var SearchParamsModifier $searchParamModifier */
            $args = $searchParamModifier->applyParams($args, $params) + $args;
        }

        $cacheKey = md5(json_encode($args));
        if (!isset(self::$cache[$cacheKey])) {
            $query = new WP_Query($args);
            if ($setCount) {
                if ($args['posts_per_page'] === -1) {
                    self::$cache[$cacheKey . '_count'] = count($query->posts);
                } elseif (isset($args['meta_key']) && $args['meta_key'] === 'featured') {
                    $tempArgs = $args;
                    unset($tempArgs['orderby'], $tempArgs['meta_key']);

                    self::$cache[$cacheKey . '_count'] = (new WP_Query($tempArgs))->found_posts;
                } else {
                    self::$cache[$cacheKey . '_count'] = $query->found_posts;
                }

                $this->count = self::$cache[$cacheKey . '_count'];
            }
            self::$cache[$cacheKey] = $query->posts;
        } elseif ($setCount) {
            $this->count = self::$cache[$cacheKey . '_count'];
        }

        return self::$cache[$cacheKey];
    }

    public function getUrl(array $filters, array $params): string
    {
        $urlPartials = tdf_collect();

        foreach (tdf_app('search_url_modifiers') as $searchUrlModifier) {
            /* @var SearchUrlModifier $searchUrlModifier */
            $urlPartials[] = $searchUrlModifier->geSearchUrlPartials($filters, $params);
        }

        $this->urlPartials = apply_filters(tdf_prefix() . '/search/urlPartials', $urlPartials,
            $this->params['template'] ?? '');

        $url = apply_filters(tdf_prefix() . '/term/url', $this->urlPartials->filter(static function ($urlPartial) {
            return !empty($urlPartial);
        })->implode('&'), false, false);

        if (tdf_settings()->prettyUrls()) {
            return $url !== '?=' ? $url : '';
        }

        if (!empty($url) && !str_contains($url, '?')) {
            $url = '?' . $url;
        }

        return $url !== '?=' ? $url : '';
    }

    public function getCount(): int
    {
        return $this->count ?? 0;
    }

    private function filterExcludedFromSearch(array $modelIds): ?array
    {
        if (empty($modelIds)) {
            return $modelIds;
        }

        if (empty(tdf_settings()->getExcludedFromSearchTermIds())) {
            return $modelIds;
        }

        $modelIds = tdf_collect($modelIds)->filter(static function ($modelId) {
            return !in_array((int)$modelId, tdf_app('models_excluded_from_search'), true);
        })->values();

        if (empty($modelIds)) {
            return null;
        }

        return $modelIds;
    }

    public function getTermsCount(array $filters = [], array $params = []): array
    {
        $allTerms = [];

        tdf_taxonomy_fields()->each(function ($taxonomy) use ($params, &$allTerms, $filters) {
            /* @var TaxonomyField $taxonomy */
            /** @noinspection NullPointerExceptionInspection */
            $searchFilters = tdf_app('search_results_modifiers')
                ->filter(static function ($searchFilter) use ($taxonomy) {
                    if (!$searchFilter instanceof TaxonomyQueryModifier) {
                        return true;
                    }

                    $searchFilterTaxonomy = $searchFilter->getField();

                    return !(
                        ($searchFilterTaxonomy->getId() === $taxonomy->getId() && ($taxonomy->isSearchLoginOr() || $taxonomy->isMultilevel()))
//                        || $taxonomy->isParentTaxonomy($searchFilterTaxonomy)
                        || $searchFilterTaxonomy->isParentTaxonomy($taxonomy)
                    );
                });

            $allTerms = array_merge(
                $allTerms,
                $this->getTermsCountForTaxonomy($taxonomy, $this->getModelIds($searchFilters, $filters))
            );
        });

        return $allTerms;
    }

    private function getTermsCountForTaxonomy(TaxonomyField $taxonomy, $modelIds = []): array
    {
        if ($modelIds === false) {
            $cacheKey = tdf_prefix() . '_cache_' . $taxonomy->getKey() . '_count';
            if (isset(self::$cache[$cacheKey])) {
                $modelIds = self::$cache[$cacheKey];
            } else {
                $query = new WP_Query([
                    'post_type' => $this->postType,
                    'posts_per_page' => '-1',
                    'fields' => 'ids',
                    'post_status' => PostStatus::PUBLISH,
                    'author__in' => $this->getUserIds(),
                    'update_post_meta_cache' => false,
                    'update_post_term_cache' => false,
                    'no_found_rows' => true,
                ]);

                self::$cache[$cacheKey] = $query->posts;

                $modelIds = $query->posts;
            }
        }

        $modelIdsString = implode(',', $modelIds);

        $cacheKey = tdf_prefix() . '_cache_' . $taxonomy->getKey() . '_' . md5($modelIdsString) . '_count';
        if (isset(self::$cache[$cacheKey])) {
            return self::$cache[$cacheKey];
        }

        if (tdf_settings()->isCacheEnabled()) {
            $value = get_transient($cacheKey);
            if ($value !== false) {
                self::$cache[$cacheKey] = $value;

                return $value;
            }
        }

        global $wpdb;
        if (empty($modelIds)) {
            $modelIdsQuery = 'AND p.post_status = "publish"';

            $sql = "
                SELECT tt.term_id as id, COUNT(tr.term_taxonomy_id) as count FROM {$wpdb->posts} p
                    LEFT OUTER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
                    LEFT OUTER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                WHERE tt.taxonomy = '" . $taxonomy->getKey() . "' $modelIdsQuery
                GROUP BY tr.term_taxonomy_id
            ";

            $terms = $wpdb->get_results($sql);
        } else {
            $terms = $this->fetchTermsCount($taxonomy, $modelIds);
        }

        if (!is_array($terms)) {
            return [
                [
                    'id' => $taxonomy->getKey(),
                    'count' => count($modelIds)
                ]
            ];
        }

        $output = tdf_collect($terms)->map(static function ($term) {
            return [
                'id' => (int)$term->id,
                'count' => (int)$term->count
            ];
        })->values();

        $output[] = [
            'id' => $taxonomy->getKey(),
            'count' => count($modelIds)
        ];

        self::$cache[$cacheKey] = $output;

        if (tdf_settings()->isCacheEnabled()) {
            set_transient($cacheKey, $output, tdf_settings()->getCacheDuration());
        }

        return $output;
    }

    private function fetchTermsCount(TaxonomyField $taxonomy, array $modelIds)
    {
        global $wpdb;

        $modelIdsQuery = "AND tr.object_id IN (" . implode(',', $modelIds) . ")";

        $sql = "
            SELECT tt.term_id as id, COUNT(tr.term_taxonomy_id) as count
            FROM {$wpdb->term_relationships} tr
                LEFT OUTER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
            WHERE tt.taxonomy = '" . $taxonomy->getKey() . "' $modelIdsQuery
            GROUP BY tr.term_taxonomy_id
            ";

        return $wpdb->get_results($sql);
    }

    private function getBreadcrumbs(): array
    {
        if (!$this->urlPartials instanceof Collection) {
            return [];
        }

        $args = [];

        foreach ($this->urlPartials as $partial) {
            if (empty($partial)) {
                continue;
            }

            $data = explode('=', $partial);
            if (count($data) !== 2) {
                continue;
            }

            $args[$data[0]] = $data[1];
        }

        return apply_filters(tdf_prefix() . '/breadcrumbs/listings', $args);
    }

    private function fetchListings(array $modelIds): void
    {
        global ${tdf_short_prefix() . 'CurrentListings'};

        $this->listings = ${tdf_short_prefix() . 'CurrentListings'} = tdf_query_models()
            ->in($modelIds)
            ->orderByIn()
            ->get();
    }

    public function getTemplate(array $modelIds): string
    {
        if ($this->listings === null) {
            $this->fetchListings($modelIds);
        }

        ob_start();

        if (!in_array($this->params['template'], [
            'templates/widgets/general/search/partials/search_results_card',
            'templates/widgets/general/search/partials/search_results_row',
            // @todo: fix it
            'templates/partials/search_results_card_regular',
            'templates/partials/search_results_card_small',
            'templates/partials/search_results_simple_card',
            'templates/partials/search_results_row',
            'templates/partials/search_results_row_regular',
            'templates/partials/search_results_row_regular_v2',
        ], true)) {
            get_template_part('templates/partials/search_results_row');

//            get_template_part('templates/widgets/general/search/partials/search_results_row');
        } else {
            get_template_part($this->params['template']);
        }

        return ob_get_clean();
    }

    public function getMarkers(array $modelIds): array
    {
        if ($this->listings === null) {
            $this->fetchListings($modelIds);
        }

        $markers = [];
        $mainValueFields = tdf_settings()->getCardMainValueFields();
        $locationField = tdf_settings()->getCardLocationField();

        if (!$locationField instanceof LocationField) {
            $locationField = tdf_location_fields()->first(false);
        }

        global ${tdf_short_prefix() . 'CurrentListings'};
        foreach (${tdf_short_prefix() . 'CurrentListings'} as $listing) {
            /* @var Model $listing */
            $mainValue = '';
            foreach ($mainValueFields as $mainValueField) {
                if (!empty($mainValueField->getValueByCurrency($listing))) {
                    $mainValue = $mainValueField->getValueByCurrency($listing);
                }
            }

            $galleryField = tdf_settings()->getCardGalleryField();

            $markers[] = [
                'id' => $listing->getId(),
                'label' => $listing->getName(),
                'url' => $listing->getUrl(),
                'image' => $galleryField instanceof GalleryField ? $galleryField->getImageUrl($listing) : '',
                'price' => $mainValue,
                'location' => $locationField instanceof LocationField ? $locationField->getLocation($listing) : false,
            ];
        }

        return $markers;
    }
}