PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / API / REST / AbstractListingEndpoint.php
mailpoet / lib / API / REST Last commit date
API.php 6 months ago AbstractListingEndpoint.php 3 weeks ago ApiException.php 2 months ago Endpoint.php 3 months ago EndpointContainer.php 3 years ago ErrorResponse.php 3 years ago Exception.php 3 years ago ListingRequestValidationTrait.php 1 month ago Request.php 3 years ago Response.php 1 year ago index.php 3 years ago
AbstractListingEndpoint.php
188 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\API\REST;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Listing\Handler as ListingHandler;
9 use MailPoet\Listing\ListingDefinition;
10 use MailPoet\Listing\ListingRepository;
11 use MailPoet\Validator\Builder;
12
13 /**
14 * Base class for REST endpoints that expose a MailPoet listing
15 * (search, sort, filter, pagination) via the shared
16 * {@see ListingRepository} infrastructure.
17 *
18 * Concrete subclasses only need to supply the repository and the
19 * per-row response mapper. The request schema, parameter parsing,
20 * and response shape (`{items, meta:{count, pages}, filters, groups}`)
21 * are standardized so all DataViews-backed listings in the admin UI
22 * consume the same contract.
23 */
24 abstract class AbstractListingEndpoint extends Endpoint {
25 public const DEFAULT_PER_PAGE = 20;
26 public const MAX_PER_PAGE = 100;
27 // Sentinel cap on `page`. Keeps `(page-1) * per_page` from producing
28 // huge OFFSETs that would push MySQL through millions of skipped rows
29 // when a client (or fuzzer) sends `page=999999999`.
30 public const MAX_PAGE = 100000;
31
32 /** @var ListingHandler */
33 private $listingHandler;
34
35 public function __construct(
36 ListingHandler $listingHandler
37 ) {
38 $this->listingHandler = $listingHandler;
39 }
40
41 /**
42 * Subclasses MUST override `checkPermissions()` to declare a real
43 * capability. The inherited default from `Endpoint::checkPermissions()`
44 * checks a non-existent `admin` capability and would fail closed but
45 * silently. PHP does not let us redeclare the parent method as `abstract`
46 * here without breaking other consumers, so this is enforced by review +
47 * tests instead of the type system.
48 */
49 public function handle(Request $request): Response {
50 $definition = $this->buildDefinition($request);
51 $repository = $this->getListingRepository();
52
53 $rows = $repository->getData($definition);
54 $countAndGroups = $this->getCountAndGroups($repository, $definition);
55 $count = $countAndGroups['count'];
56 $perPage = $definition->getLimit() ?: self::DEFAULT_PER_PAGE;
57 $pages = $count === 0 ? 0 : (int)ceil($count / max(1, $perPage));
58
59 return new Response([
60 'items' => $this->buildItems($rows, $definition),
61 'meta' => [
62 'count' => $count,
63 'pages' => $pages,
64 ],
65 'filters' => $repository->getFilters($definition),
66 'groups' => $countAndGroups['groups'],
67 ]);
68 }
69
70 /**
71 * Returns `['count' => int, 'groups' => array]`. Split into a hook so an
72 * endpoint whose group counts already include the current group's count can
73 * compute both from a single query and skip the separate getCount(). The
74 * default keeps the two independent queries, unchanged for every other
75 * listing.
76 *
77 * @return array{count: int, groups: array<int, array<string, mixed>>}
78 */
79 protected function getCountAndGroups(ListingRepository $repository, ListingDefinition $definition): array {
80 return [
81 'count' => $repository->getCount($definition),
82 'groups' => $repository->getGroups($definition),
83 ];
84 }
85
86 public static function getRequestSchema(): array {
87 return [
88 'page' => Builder::integer(),
89 'per_page' => Builder::integer(),
90 'orderby' => Builder::string(),
91 'order' => Builder::string(),
92 'sort_by' => Builder::string(),
93 'sort_order' => Builder::string(),
94 'search' => Builder::string(),
95 'group' => Builder::string(),
96 'filter' => Builder::object(),
97 ];
98 }
99
100 abstract protected function getListingRepository(): ListingRepository;
101
102 /**
103 * @param mixed[] $rows Rows returned by {@see ListingRepository::getData()}.
104 * @param ListingDefinition $definition Parsed request — exposed so subclasses
105 * can branch on filter/group when shaping items without stashing per-request
106 * state on the (shared) endpoint instance.
107 * @return array<int, array<string, mixed>> Items ready to be serialized.
108 */
109 abstract protected function buildItems(array $rows, ListingDefinition $definition): array;
110
111 protected function getDefaultSortBy(): string {
112 return 'id';
113 }
114
115 protected function getDefaultSortOrder(): string {
116 return 'desc';
117 }
118
119 /**
120 * Default group applied when the client does not send one. Useful when a
121 * listing's repository uses groups to gate "all" vs "trash" (or similar)
122 * and would otherwise return mixed results.
123 */
124 protected function getDefaultGroup(): ?string {
125 return null;
126 }
127
128 protected function getDefaultPerPage(): int {
129 return self::DEFAULT_PER_PAGE;
130 }
131
132 protected function getDefaultParameters(): array {
133 return [];
134 }
135
136 /**
137 * Subclasses may override to derive the listing definition's free-form
138 * `params` array from the request (e.g. a `?type=standard` query arg that
139 * routes to the same underlying repository). The default reuses
140 * {@see getDefaultParameters()} so callers without per-request params keep
141 * the existing behavior.
142 *
143 * @return array<string, mixed>
144 */
145 protected function getRequestParameters(Request $request): array {
146 return $this->getDefaultParameters();
147 }
148
149 private function buildDefinition(Request $request): ListingDefinition {
150 $perPageParam = $request->getParam('per_page') ?? $request->getParam('limit');
151 $perPage = is_numeric($perPageParam)
152 ? max(1, min(self::MAX_PER_PAGE, (int)$perPageParam))
153 : $this->getDefaultPerPage();
154
155 $pageParam = $request->getParam('page');
156 $offsetParam = $request->getParam('offset');
157 $offset = is_numeric($pageParam)
158 ? (min(self::MAX_PAGE, max(1, (int)$pageParam)) - 1) * $perPage
159 : (is_numeric($offsetParam) ? (int)$offsetParam : 0);
160
161 $orderByParam = $request->getParam('orderby') ?? $request->getParam('sort_by');
162 $sortBy = is_string($orderByParam) && $orderByParam !== '' ? $orderByParam : $this->getDefaultSortBy();
163
164 $orderParam = $request->getParam('order') ?? $request->getParam('sort_order');
165 $sortOrder = is_string($orderParam) && $orderParam !== '' ? strtolower($orderParam) : $this->getDefaultSortOrder();
166
167 $searchParam = $request->getParam('search');
168 $search = is_string($searchParam) ? $searchParam : null;
169
170 $groupParam = $request->getParam('group');
171 $group = is_string($groupParam) && $groupParam !== '' ? $groupParam : $this->getDefaultGroup();
172
173 $filterParam = $request->getParam('filter');
174 $filters = is_array($filterParam) ? $filterParam : [];
175
176 return $this->listingHandler->getListingDefinition([
177 'offset' => $offset,
178 'limit' => $perPage,
179 'sort_by' => $sortBy,
180 'sort_order' => $sortOrder,
181 'search' => $search,
182 'group' => $group,
183 'filter' => $filters,
184 'params' => $this->getRequestParameters($request),
185 ]);
186 }
187 }
188