Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / views / src / Plugin / views / pager / SqlBase.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\pager;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\CacheableDependencyInterface;
7 use Drupal\Core\Form\FormStateInterface;
8
9 /**
10  * A common base class for sql based pager.
11  */
12 abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInterface {
13
14   protected function defineOptions() {
15     $options = parent::defineOptions();
16     $options['items_per_page'] = ['default' => 10];
17     $options['offset'] = ['default' => 0];
18     $options['id'] = ['default' => 0];
19     $options['total_pages'] = ['default' => ''];
20     $options['expose'] = [
21       'contains' => [
22         'items_per_page' => ['default' => FALSE],
23         'items_per_page_label' => ['default' => $this->t('Items per page')],
24         'items_per_page_options' => ['default' => '5, 10, 25, 50'],
25         'items_per_page_options_all' => ['default' => FALSE],
26         'items_per_page_options_all_label' => ['default' => $this->t('- All -')],
27
28         'offset' => ['default' => FALSE],
29         'offset_label' => ['default' => $this->t('Offset')],
30       ],
31     ];
32     $options['tags'] = [
33       'contains' => [
34         'previous' => ['default' => $this->t('‹ Previous')],
35         'next' => ['default' => $this->t('Next ›')],
36       ],
37     ];
38     return $options;
39   }
40
41   /**
42    * Provide the default form for setting options.
43    */
44   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
45     parent::buildOptionsForm($form, $form_state);
46     $pager_text = $this->displayHandler->getPagerText();
47     $form['items_per_page'] = [
48       '#title' => $pager_text['items per page title'],
49       '#type' => 'number',
50       '#description' => $pager_text['items per page description'],
51       '#default_value' => $this->options['items_per_page'],
52     ];
53
54     $form['offset'] = [
55       '#type' => 'number',
56       '#title' => $this->t('Offset (number of items to skip)'),
57       '#description' => $this->t('For example, set this to 3 and the first 3 items will not be displayed.'),
58       '#default_value' => $this->options['offset'],
59     ];
60
61     $form['id'] = [
62       '#type' => 'number',
63       '#title' => $this->t('Pager ID'),
64       '#description' => $this->t("Unless you're experiencing problems with pagers related to this view, you should leave this at 0. If using multiple pagers on one page you may need to set this number to a higher value so as not to conflict within the ?page= array. Large values will add a lot of commas to your URLs, so avoid if possible."),
65       '#default_value' => $this->options['id'],
66     ];
67
68     $form['total_pages'] = [
69       '#type' => 'number',
70       '#title' => $this->t('Number of pages'),
71       '#description' => $this->t('Leave empty to show all pages.'),
72       '#default_value' => $this->options['total_pages'],
73     ];
74
75     $form['tags'] = [
76       '#type' => 'details',
77       '#open' => TRUE,
78       '#tree' => TRUE,
79       '#title' => $this->t('Pager link labels'),
80       '#input' => TRUE,
81     ];
82
83     $form['tags']['previous'] = [
84       '#type' => 'textfield',
85       '#title' => $this->t('Previous page link text'),
86       '#default_value' => $this->options['tags']['previous'],
87     ];
88
89     $form['tags']['next'] = [
90       '#type' => 'textfield',
91       '#title' => $this->t('Next page link text'),
92       '#default_value' => $this->options['tags']['next'],
93     ];
94
95     $form['expose'] = [
96       '#type' => 'details',
97       '#open' => TRUE,
98       '#tree' => TRUE,
99       '#title' => $this->t('Exposed options'),
100       '#input' => TRUE,
101       '#description' => $this->t('Allow user to control selected display options for this view.'),
102     ];
103
104     $form['expose']['items_per_page'] = [
105       '#type' => 'checkbox',
106       '#title' => $this->t('Allow user to control the number of items displayed in this view'),
107       '#default_value' => $this->options['expose']['items_per_page'],
108     ];
109
110     $form['expose']['items_per_page_label'] = [
111       '#type' => 'textfield',
112       '#title' => $this->t('Items per page label'),
113       '#required' => TRUE,
114       '#default_value' => $this->options['expose']['items_per_page_label'],
115       '#states' => [
116         'invisible' => [
117           'input[name="pager_options[expose][items_per_page]"]' => ['checked' => FALSE],
118         ],
119       ],
120     ];
121
122     $form['expose']['items_per_page_options'] = [
123       '#type' => 'textfield',
124       '#title' => $this->t('Exposed items per page options'),
125       '#required' => TRUE,
126       '#description' => $this->t('Set between which values the user can choose when determining the items per page. Separated by comma.'),
127       '#default_value' => $this->options['expose']['items_per_page_options'],
128       '#states' => [
129         'invisible' => [
130           'input[name="pager_options[expose][items_per_page]"]' => ['checked' => FALSE],
131         ],
132       ],
133     ];
134
135
136     $form['expose']['items_per_page_options_all'] = [
137       '#type' => 'checkbox',
138       '#title' => $this->t('Allow user to display all items'),
139       '#default_value' => $this->options['expose']['items_per_page_options_all'],
140     ];
141
142     $form['expose']['items_per_page_options_all_label'] = [
143       '#type' => 'textfield',
144       '#title' => $this->t('All items label'),
145       '#default_value' => $this->options['expose']['items_per_page_options_all_label'],
146       '#states' => [
147         'invisible' => [
148           'input[name="pager_options[expose][items_per_page_options_all]"]' => ['checked' => FALSE],
149         ],
150       ],
151     ];
152
153     $form['expose']['offset'] = [
154       '#type' => 'checkbox',
155       '#title' => $this->t('Allow user to specify number of items skipped from beginning of this view.'),
156       '#default_value' => $this->options['expose']['offset'],
157     ];
158
159     $form['expose']['offset_label'] = [
160       '#type' => 'textfield',
161       '#title' => $this->t('Offset label'),
162       '#required' => TRUE,
163       '#default_value' => $this->options['expose']['offset_label'],
164       '#states' => [
165         'invisible' => [
166           'input[name="pager_options[expose][offset]"]' => ['checked' => FALSE],
167         ],
168       ],
169     ];
170   }
171
172   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
173     // Only accept integer values.
174     $error = FALSE;
175     $exposed_options = $form_state->getValue(['pager_options', 'expose', 'items_per_page_options']);
176     if (strpos($exposed_options, '.') !== FALSE) {
177       $error = TRUE;
178     }
179     $options = explode(',', $exposed_options);
180     if (!$error && is_array($options)) {
181       foreach ($options as $option) {
182         if (!is_numeric($option) || intval($option) == 0) {
183           $error = TRUE;
184         }
185       }
186     }
187     else {
188       $error = TRUE;
189     }
190     if ($error) {
191       $form_state->setErrorByName('pager_options][expose][items_per_page_options', $this->t('Insert a list of integer numeric values separated by commas: e.g: 10, 20, 50, 100'));
192     }
193
194     // Make sure that the items_per_page is part of the expose settings.
195     if (!$form_state->isValueEmpty(['pager_options', 'expose', 'items_per_page']) && !$form_state->isValueEmpty(['pager_options', 'items_per_page'])) {
196       $items_per_page = $form_state->getValue(['pager_options', 'items_per_page']);
197       if (array_search($items_per_page, $options) === FALSE) {
198         $form_state->setErrorByName('pager_options][expose][items_per_page_options', $this->t("The <em>Exposed items per page</em> field's options must include the value from the <em>Items per page</em> field (@items_per_page).",
199           ['@items_per_page' => $items_per_page])
200         );
201       }
202     }
203   }
204
205   public function query() {
206     if ($this->itemsPerPageExposed()) {
207       $query = $this->view->getRequest()->query;
208       $items_per_page = $query->get('items_per_page');
209       if ($items_per_page > 0) {
210         $this->options['items_per_page'] = $items_per_page;
211       }
212       elseif ($items_per_page == 'All' && $this->options['expose']['items_per_page_options_all']) {
213         $this->options['items_per_page'] = 0;
214       }
215     }
216     if ($this->isOffsetExposed()) {
217       $query = $this->view->getRequest()->query;
218       $offset = $query->get('offset');
219       if (isset($offset) && $offset >= 0) {
220         $this->options['offset'] = $offset;
221       }
222     }
223
224     $limit = $this->options['items_per_page'];
225     $offset = $this->current_page * $this->options['items_per_page'] + $this->options['offset'];
226     if (!empty($this->options['total_pages'])) {
227       if ($this->current_page >= $this->options['total_pages']) {
228         $limit = $this->options['items_per_page'];
229         $offset = $this->options['total_pages'] * $this->options['items_per_page'];
230       }
231     }
232
233     $this->view->query->setLimit($limit);
234     $this->view->query->setOffset($offset);
235   }
236
237
238   /**
239    * Set the current page.
240    *
241    * @param $number
242    *   If provided, the page number will be set to this. If NOT provided,
243    *   the page number will be set from the global page array.
244    */
245   public function setCurrentPage($number = NULL) {
246     if (isset($number)) {
247       $this->current_page = max(0, $number);
248       return;
249     }
250
251     // If the current page number was not specified, extract it from the global
252     // page array.
253     global $pager_page_array;
254
255     if (empty($pager_page_array)) {
256       $pager_page_array = [];
257     }
258
259     // Fill in missing values in the global page array, in case the global page
260     // array hasn't been initialized before.
261     $page = $this->view->getRequest()->query->get('page');
262     $page = isset($page) ? explode(',', $page) : [];
263
264     for ($i = 0; $i <= $this->options['id'] || $i < count($pager_page_array); $i++) {
265       $pager_page_array[$i] = empty($page[$i]) ? 0 : $page[$i];
266     }
267
268     // Don't allow the number to be less than zero.
269     $this->current_page = max(0, intval($pager_page_array[$this->options['id']]));
270   }
271
272   public function getPagerTotal() {
273     if ($items_per_page = intval($this->getItemsPerPage())) {
274       return ceil($this->total_items / $items_per_page);
275     }
276     else {
277       return 1;
278     }
279   }
280
281   /**
282    * Update global paging info.
283    *
284    * This is called after the count query has been run to set the total
285    * items available and to update the current page if the requested
286    * page is out of range.
287    */
288   public function updatePageInfo() {
289     if (!empty($this->options['total_pages'])) {
290       if (($this->options['total_pages'] * $this->options['items_per_page']) < $this->total_items) {
291         $this->total_items = $this->options['total_pages'] * $this->options['items_per_page'];
292       }
293     }
294
295     // Don't set pager settings for items per page = 0.
296     $items_per_page = $this->getItemsPerPage();
297     if (!empty($items_per_page)) {
298       // Dump information about what we already know into the globals.
299       global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
300       // Set the limit.
301       $pager_limits[$this->options['id']] = $this->options['items_per_page'];
302       // Set the item count for the pager.
303       $pager_total_items[$this->options['id']] = $this->total_items;
304       // Calculate and set the count of available pages.
305       $pager_total[$this->options['id']] = $this->getPagerTotal();
306
307       // See if the requested page was within range:
308       if ($this->current_page >= $pager_total[$this->options['id']]) {
309         // Pages are numbered from 0 so if there are 10 pages, the last page is 9.
310         $this->setCurrentPage($pager_total[$this->options['id']] - 1);
311       }
312
313       // Put this number in to guarantee that we do not generate notices when the pager
314       // goes to look for it later.
315       $pager_page_array[$this->options['id']] = $this->current_page;
316     }
317   }
318
319   public function usesExposed() {
320     return $this->itemsPerPageExposed() || $this->isOffsetExposed();
321   }
322
323   protected function itemsPerPageExposed() {
324     return !empty($this->options['expose']['items_per_page']);
325   }
326
327   protected function isOffsetExposed() {
328     return !empty($this->options['expose']['offset']);
329   }
330
331   public function exposedFormAlter(&$form, FormStateInterface $form_state) {
332     if ($this->itemsPerPageExposed()) {
333       $options = explode(',', $this->options['expose']['items_per_page_options']);
334       $sanitized_options = [];
335       if (is_array($options)) {
336         foreach ($options as $option) {
337           $sanitized_options[intval($option)] = intval($option);
338         }
339         if (!empty($this->options['expose']['items_per_page_options_all']) && !empty($this->options['expose']['items_per_page_options_all_label'])) {
340           $sanitized_options['All'] = $this->options['expose']['items_per_page_options_all_label'];
341         }
342         $form['items_per_page'] = [
343           '#type' => 'select',
344           '#title' => $this->options['expose']['items_per_page_label'],
345           '#options' => $sanitized_options,
346           '#default_value' => $this->getItemsPerPage(),
347         ];
348       }
349     }
350
351     if ($this->isOffsetExposed()) {
352       $form['offset'] = [
353         '#type' => 'textfield',
354         '#size' => 10,
355         '#maxlength' => 10,
356         '#title' => $this->options['expose']['offset_label'],
357         '#default_value' => $this->getOffset(),
358       ];
359     }
360   }
361
362   public function exposedFormValidate(&$form, FormStateInterface $form_state) {
363     if (!$form_state->isValueEmpty('offset') && trim($form_state->getValue('offset'))) {
364       if (!is_numeric($form_state->getValue('offset')) || $form_state->getValue('offset') < 0) {
365         $form_state->setErrorByName('offset', $this->t('Offset must be an number greater or equal than 0.'));
366       }
367     }
368   }
369
370   /**
371    * {@inheritdoc}
372    */
373   public function getCacheMaxAge() {
374     return Cache::PERMANENT;
375   }
376
377   /**
378    * {@inheritdoc}
379    */
380   public function getCacheContexts() {
381     // The rendered link needs to play well with any other query parameter used
382     // on the page, like other pagers and exposed filter.
383     return ['url.query_args'];
384   }
385
386   /**
387    * {@inheritdoc}
388    */
389   public function getCacheTags() {
390     return [];
391   }
392
393 }