Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaPickerTrait
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 7
182
0.00% covered (danger)
0.00%
0 / 1
 buildPickerQuery
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 handlePickerSearch
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 setupPickerPagination
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 handlePickerAjaxResponse
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 applyPickerExclusion
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getRequestLimit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRequestPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare(strict_types=1);
3
4namespace App\Controller\Component;
5
6use Cake\Http\Response;
7use Cake\ORM\Query;
8use Cake\ORM\Table;
9
10/**
11 * MediaPickerTrait
12 *
13 * Provides shared functionality for media picker methods across controllers.
14 * Handles common patterns for search, pagination, and AJAX responses in picker interfaces.
15 */
16trait MediaPickerTrait
17{
18    /**
19     * Build base picker query with common select fields and ordering
20     *
21     * @param \Cake\ORM\Table $table The table to query
22     * @param array $selectFields Fields to select
23     * @param array $options Additional query options
24     * @return \Cake\ORM\Query
25     */
26    protected function buildPickerQuery(Table $table, array $selectFields, array $options = []): Query
27    {
28        $query = $table->find()->select($selectFields);
29
30        // Apply default ordering
31        if (isset($options['order'])) {
32            $query->orderBy($options['order']);
33        } else {
34            $query->orderBy([$table->getAlias() . '.created' => 'DESC']);
35        }
36
37        // Apply any containments
38        if (isset($options['contain'])) {
39            $query->contain($options['contain']);
40        }
41
42        return $query;
43    }
44
45    /**
46     * Handle search filtering for picker queries
47     *
48     * @param \Cake\ORM\Query $query Query to filter
49     * @param string|null $searchTerm Search term
50     * @param array $searchFields Fields to search in
51     * @return \Cake\ORM\Query
52     */
53    protected function handlePickerSearch(Query $query, ?string $searchTerm, array $searchFields): Query
54    {
55        if (!empty($searchTerm)) {
56            $conditions = [];
57            foreach ($searchFields as $field) {
58                $conditions[] = [$field . ' LIKE' => '%' . $searchTerm . '%'];
59            }
60            $query->where(['OR' => $conditions]);
61        }
62
63        return $query;
64    }
65
66    /**
67     * Setup pagination configuration for picker
68     *
69     * @param array $options Pagination options
70     * @return array
71     */
72    protected function setupPickerPagination(array $options = []): array
73    {
74        $defaults = [
75            'limit' => 12,
76            'maxLimit' => 24,
77        ];
78
79        return array_merge($defaults, $options);
80    }
81
82    /**
83     * Handle picker AJAX response
84     *
85     * @param mixed $results Results to return
86     * @param string|null $search Search term
87     * @param string $template Template to render for AJAX
88     * @return \Cake\Http\Response|null
89     */
90    protected function handlePickerAjaxResponse(mixed $results, ?string $search, string $template): ?Response
91    {
92        if ($this->request->is('ajax')) {
93            $this->set(compact('results', 'search'));
94            $this->set('_serialize', ['results', 'search']);
95            $this->viewBuilder()->setLayout('ajax');
96
97            return $this->render($template);
98        }
99
100        return null;
101    }
102
103    /**
104     * Apply exclusion filter for picker (e.g., exclude images already in gallery)
105     *
106     * @param \Cake\ORM\Query $query Query to filter
107     * @param \Cake\ORM\Table $pivotTable Pivot table for relationships
108     * @param string $foreignKey Foreign key field name
109     * @param string $recordId Record ID to exclude related items from
110     * @param string $excludeField Field name to exclude
111     * @return \Cake\ORM\Query
112     */
113    protected function applyPickerExclusion(
114        Query $query,
115        Table $pivotTable,
116        string $foreignKey,
117        string $recordId,
118        string $excludeField,
119    ): Query {
120        $excludeIds = $pivotTable
121            ->find()
122            ->select([$excludeField])
123            ->where([$foreignKey => $recordId])
124            ->all()
125            ->extract($excludeField)
126            ->toArray();
127
128        if (!empty($excludeIds)) {
129            $tableAlias = $query->getRepository()->getAlias();
130            $query->where([$tableAlias . '.id NOT IN' => $excludeIds]);
131        }
132
133        return $query;
134    }
135
136    /**
137     * Handle request limit parameter with validation
138     *
139     * @param int $default Default limit
140     * @param int $max Maximum allowed limit
141     * @return int
142     */
143    protected function getRequestLimit(int $default = 12, int $max = 24): int
144    {
145        return min((int)$this->request->getQuery('limit', $default), $max);
146    }
147
148    /**
149     * Get current page from request
150     *
151     * @param int $default Default page number
152     * @return int
153     */
154    protected function getRequestPage(int $default = 1): int
155    {
156        return (int)$this->request->getQuery('page', $default);
157    }
158}