Passed
Push — master ( 2207eb...9f340b )
by Andreas
10:12
created

net_nehmer_blog_handler_archive   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Test Coverage

Coverage 84.91%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 162
c 1
b 0
f 0
dl 0
loc 351
ccs 135
cts 159
cp 0.8491
rs 9.52
wmc 36

10 Methods

Rating   Name   Duplication   Size   Complexity  
A _handler_welcome() 0 11 2
A _compute_welcome_posting_count() 0 9 1
A _compute_welcome_first_post() 0 20 3
B _compute_welcome_data() 0 61 6
A _show_welcome() 0 13 2
A _get_month_names() 0 9 2
B _handler_list() 0 56 8
A _set_startend_from_month() 0 22 5
A _show_list() 0 35 5
A _set_startend_from_year() 0 10 2
1
<?php
2
/**
3
 * @package net.nehmer.blog
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use midcom\datamanager\datamanager;
10
11
/**
12
 * Blog Archive pages handler
13
 *
14
 * Shows the various archive views.
15
 *
16
 * @package net.nehmer.blog
17
 */
18
class net_nehmer_blog_handler_archive extends midcom_baseclasses_components_handler
19
{
20
    use net_nehmer_blog_handler;
0 ignored issues
show
introduced by
The trait net_nehmer_blog_handler requires some properties which are not provided by net_nehmer_blog_handler_archive: $name, $url, $guid, $revised, $metadata
Loading history...
21
22
    /**
23
     * @var midcom_db_article[]
24
     */
25
    private array $_articles;
26
27
    private DateTime $_start;
28
29
    private DateTime $_end;
30
31
    /**
32
     * Shows the archive welcome page: A listing of years/months along with total post counts
33
     * and similar stuff.
34
     *
35
     * The handler computes all necessary data and populates the request array accordingly.
36
     */
37 1
    public function _handler_welcome()
38
    {
39 1
        $this->_compute_welcome_data();
40
41 1
        if ($this->_config->get('archive_in_navigation')) {
42 1
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE');
43
        }
44
45 1
        midcom::get()->head->set_pagetitle("{$this->_topic->extra}: " . $this->_l10n->get('archive'));
46
47 1
        midcom::get()->metadata->set_request_metadata($this->get_last_modified(), $this->_topic->guid);
48
    }
49
50
    /**
51
     * Loads the first posting time from the DB. This is the base for all operations on the
52
     * resultset.
53
     *
54
     * This is done under sudo if possible, to avoid problems arising if the first posting
55
     * is hidden. This keeps up performance, as an execute_unchecked() can be made in this case.
56
     * If sudo cannot be acquired, the system falls back to execute().
57
     */
58 1
    private function _compute_welcome_first_post() : ?DateTime
59
    {
60 1
        $qb = midcom_db_article::new_query_builder();
61 1
        $this->article_qb_constraints($qb);
62 1
        $qb->add_constraint('metadata.published', '>', '1970-01-02 23:59:59');
63
64 1
        $qb->add_order('metadata.published');
65 1
        $qb->set_limit(1);
66
67 1
        if (midcom::get()->auth->request_sudo($this->_component)) {
68 1
            $result = $qb->execute_unchecked();
69 1
            midcom::get()->auth->drop_sudo();
70
        } else {
71
            $result = $qb->execute();
72
        }
73
74 1
        if (!empty($result)) {
75 1
            return new DateTime('@' . $result[0]->metadata->published);
76
        }
77
        return null;
78
    }
79
80
    /**
81
     * Computes the number of postings in a given timeframe.
82
     *
83
     * @param DateTime $start Start of the timeframe (inclusive)
84
     * @param DateTime $end End of the timeframe (exclusive)
85
     */
86 1
    private function _compute_welcome_posting_count(DateTime $start, DateTime $end) : int
87
    {
88 1
        $qb = midcom_db_article::new_query_builder();
89
90 1
        $qb->add_constraint('metadata.published', '>=', $start->format('Y-m-d H:i:s'));
91 1
        $qb->add_constraint('metadata.published', '<', $end->format('Y-m-d H:i:s'));
92 1
        $this->article_qb_constraints($qb);
93
94 1
        return $qb->count();
95
    }
96
97
    /**
98
     * Computes the data necessary for the welcome screen. Automatically put into the request
99
     * data array.
100
     */
101 1
    private function _compute_welcome_data()
102
    {
103
        // First step of request data: Overall info
104 1
        $total_count = 0;
105 1
        $year_data = [];
106 1
        $first_post = $this->_compute_welcome_first_post();
107 1
        $this->_request_data['first_post'] = $first_post;
108 1
        $this->_request_data['total_count'] =& $total_count;
109 1
        $this->_request_data['year_data'] =& $year_data;
110 1
        if (!$first_post) {
111
            return;
112
        }
113
114
        // Second step of request data: Years and months.
115 1
        $now = new DateTime();
116 1
        $first_year = $first_post->format('Y');
117 1
        $last_year = $now->format('Y');
118
119 1
        $month_names = $this->_get_month_names();
120
121 1
        for ($year = $last_year; $year >= $first_year; $year--) {
122 1
            $year_count = 0;
123 1
            $month_data = [];
124
125
            // Loop over the months, start month is either first posting month
126
            // or January in all other cases. End months are treated similarly,
127
            // being december by default unless for the current year.
128 1
            if ($year == $first_year) {
129 1
                $first_month = $first_post->format('n');
130
            } else {
131
                $first_month = 1;
132
            }
133
134 1
            if ($year == $last_year) {
135 1
                $last_month = $now->format('n');
136
            } else {
137
                $last_month = 12;
138
            }
139
140 1
            for ($month = $first_month; $month <= $last_month; $month++) {
141 1
                $start_time = new DateTime();
142 1
                $start_time->setDate($year, $month, 1);
0 ignored issues
show
Bug introduced by
It seems like $month can also be of type string; however, parameter $month of DateTime::setDate() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
                $start_time->setDate($year, /** @scrutinizer ignore-type */ $month, 1);
Loading history...
Bug introduced by
$year of type string is incompatible with the type integer expected by parameter $year of DateTime::setDate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
                $start_time->setDate(/** @scrutinizer ignore-type */ $year, $month, 1);
Loading history...
143 1
                $end_time = clone $start_time;
144 1
                $end_time->modify('+1 month');
145
146 1
                $month_count = $this->_compute_welcome_posting_count($start_time, $end_time);
147 1
                $year_count += $month_count;
148 1
                $total_count += $month_count;
149 1
                $month_data[$month] = [
150 1
                    'month' => $month,
151 1
                    'name' => $month_names[$month],
152 1
                    'url' => $this->router->generate('archive-month', ['year' => $year, 'month' => $month]),
153 1
                    'count' => $month_count,
154 1
                ];
155
            }
156
157 1
            $year_data[$year] = [
158 1
                'year' => $year,
159 1
                'url' => $this->router->generate('archive-year', ['year' => $year]),
160 1
                'count' => $year_count,
161 1
                'month_data' => $month_data,
162 1
            ];
163
        }
164
    }
165
166 1
    private function _get_month_names() : array
167
    {
168 1
        $names = [];
169 1
        $formatter = $this->_l10n->get_formatter();
170 1
        for ($i = 1; $i < 13; $i++) {
171 1
            $timestamp = mktime(0, 0, 0, $i, 1, 2011);
172 1
            $names[$i] = $formatter->customdate($timestamp, 'MMMM');
173
        }
174 1
        return $names;
175
    }
176
177
    /**
178
     * Displays the welcome page.
179
     *
180
     * Element sequence:
181
     *
182
     * - archive-welcome-start (Start of the archive welcome page)
183
     * - archive-welcome-year (Display of a single year, may not be called when there are no postings)
184
     * - archive-welcome-end (End of the archive welcome page)
185
     *
186
     * Context data for all elements:
187
     *
188
     * - int total_count (total number of postings w/o ACL restrictions)
189
     * - DateTime first_post (the first posting date, may be null)
190
     * - Array year_data (the year data, contains the year context info as outlined below)
191
     *
192
     * Context data for year elements:
193
     *
194
     * - int year (the year displayed)
195
     * - string url (url to display the complete year)
196
     * - int count (Number of postings in that year)
197
     * - array month_data (the monthly data)
198
     *
199
     * month_data will contain an associative array containing the following array of data
200
     * indexed by month number (1-12):
201
     *
202
     * - string 'url' => The URL to the month.
203
     * - string 'name' => The localized name of the month.
204
     * - int 'count' => The number of postings in that month.
205
     */
206 1
    public function _show_welcome(string $handler_id, array &$data)
207
    {
208 1
        midcom_show_style('archive-welcome-start');
209
210 1
        foreach ($data['year_data'] as $year => $year_data) {
211 1
            $data['year'] = $year;
212 1
            $data['url'] = $year_data['url'];
213 1
            $data['count'] = $year_data['count'];
214 1
            $data['month_data'] = $year_data['month_data'];
215 1
            midcom_show_style('archive-welcome-year');
216
        }
217
218 1
        midcom_show_style('archive-welcome-end');
219
    }
220
221
    /**
222
     * Shows the archive. Depending on the selected handler various constraints are added to
223
     * the QB. See the add_*_constraint methods for details.
224
     */
225 2
    public function _handler_list(array &$data, ?int $year = null, ?int $month = null, ?string $category = null)
226
    {
227 2
        $data['datamanager'] = new datamanager($data['schemadb']);
228
        // Get Articles, distinguish by handler.
229 2
        $qb = midcom_db_article::new_query_builder();
230 2
        $this->article_qb_constraints($qb);
231
232
        // Use helper functions to determine start/end
233 2
        if ($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
234 1
            $this->_set_startend_from_month($year, $month);
0 ignored issues
show
Bug introduced by
It seems like $year can also be of type null; however, parameter $year of net_nehmer_blog_handler_...t_startend_from_month() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
            $this->_set_startend_from_month(/** @scrutinizer ignore-type */ $year, $month);
Loading history...
235
        } else {
236 1
            if (!$this->_config->get('archive_years_enable')) {
237
                throw new midcom_error_notfound('Year archive not allowed');
238
            }
239
240 1
            $this->_set_startend_from_year($year);
0 ignored issues
show
Bug introduced by
It seems like $year can also be of type null; however, parameter $year of net_nehmer_blog_handler_...et_startend_from_year() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

240
            $this->_set_startend_from_year(/** @scrutinizer ignore-type */ $year);
Loading history...
241
242 1
            if ($category) {
243
                $category = trim(strip_tags($category));
244
                if (   $data['datamanager']->get_schema('default')->has_field('categories')
245
                    && !$data['datamanager']->get_schema('default')->get_field('categories')['type_config']['allow_multiple']) {
246
                    $qb->add_constraint('extra1', '=', $category);
247
                } else {
248
                    $qb->add_constraint('extra1', 'LIKE', "%|{$category}|%");
249
                }
250
            }
251
        }
252
253
254 2
        $qb->add_constraint('metadata.published', '>=', $this->_start->format('Y-m-d H:i:s'));
255 2
        $qb->add_constraint('metadata.published', '<', $this->_end->format('Y-m-d H:i:s'));
256 2
        $qb->add_order('metadata.published', $this->_config->get('archive_item_order'));
0 ignored issues
show
Bug introduced by
It seems like $this->_config->get('archive_item_order') can also be of type false; however, parameter $direction of midcom_core_query::add_order() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

256
        $qb->add_order('metadata.published', /** @scrutinizer ignore-type */ $this->_config->get('archive_item_order'));
Loading history...
257 2
        $this->_articles = $qb->execute();
258
259
        // Move end date one day backwards for display purposes.
260 2
        $now = new DateTime();
261 2
        if ($now < $this->_end) {
262 2
            $this->_end = $now;
263
        } else {
264
            $this->_end->modify('-1 day');
265
        }
266
267 2
        $timeframe = $this->_l10n->get_formatter()->timeframe($this->_start, $this->_end, 'date');
268 2
        $this->add_breadcrumb($this->router->generate('archive-year', ['year' => $year]), $timeframe);
269
270 2
        $data['start'] = $this->_start;
271 2
        $data['end'] = $this->_end;
272
273 2
        if ($this->_config->get('archive_in_navigation')) {
274 2
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE');
275
        } else {
276
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE_' . $year);
277
        }
278
279 2
        midcom::get()->metadata->set_request_metadata($this->get_last_modified(), $this->_topic->guid);
280 2
        midcom::get()->head->set_pagetitle("{$this->_topic->extra}: {$timeframe}");
281
    }
282
283
    /**
284
     * Computes the start/end dates to only query a given year. It will do validation
285
     * before processing, throwing 404 in case of incorrectly formatted dates.
286
     *
287
     * This is used by the archive-year handler.
288
     */
289 1
    private function _set_startend_from_year(int $year)
290
    {
291 1
        $now = new DateTime();
292 1
        if ($year > (int) $now->format('Y')) {
293
            throw new midcom_error_notfound("The year '{$year}' is in the future, no archive available.");
294
        }
295
296 1
        $endyear = $year + 1;
297 1
        $this->_start = new DateTime("{$year}-01-01 00:00:00");
298 1
        $this->_end = new DateTime("{$endyear}-01-01 00:00:00");
299
    }
300
301
    /**
302
     * Computes the start/end dates to only query a given month. It will do validation
303
     * before processing, throwing 404 in case of incorrectly formatted dates.
304
     *
305
     * This is used by the archive-month handler.
306
     */
307 1
    private function _set_startend_from_month(int $year, int $month)
308
    {
309 1
        if (   $month < 1
310 1
            || $month > 12) {
311
            throw new midcom_error_notfound("The month {$month} is not valid.");
312
        }
313
314 1
        $now = new DateTime();
315 1
        $this->_start = new DateTime("{$year}-" . sprintf('%02d', $month) . "-01 00:00:00");
316 1
        if ($this->_start > $now) {
317
            throw new midcom_error_notfound("The month '{$year}-" . sprintf('%02d', $month) . "' is in the future, no archive available.");
318
        }
319
320 1
        if ($month == 12) {
321
            $endyear = $year + 1;
322
            $endmonth = 1;
323
        } else {
324 1
            $endyear = $year;
325 1
            $endmonth = $month + 1;
326
        }
327
328 1
        $this->_end = new DateTime("{$endyear}-" . sprintf('%02d', $endmonth) . "-01 00:00:00");
329
    }
330
331
    /**
332
     * Displays the archive.
333
     */
334 2
    public function _show_list(string $handler_id, array &$data)
335
    {
336 2
        midcom_show_style('archive-list-start');
337 2
        if ($this->_articles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_articles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
338 2
            $data['index_fulltext'] = $this->_config->get('index_fulltext');
339
340 2
            $total_count = count($this->_articles);
341 2
            $prefix = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX);
342
343 2
            foreach ($this->_articles as $article_counter => $article) {
344
                try {
345 2
                    $data['datamanager']->set_storage($article);
346
                } catch (midcom_error $e) {
347
                    $e->log();
348
                    continue;
349
                }
350
351 2
                $data['article'] = $article;
352 2
                $data['article_counter'] = $article_counter;
353 2
                $data['article_count'] = $total_count;
354 2
                $data['view_url'] = $prefix . $this->get_url($article, $this->_config->get('link_to_external_url'));
0 ignored issues
show
Bug introduced by
Are you sure $prefix of type false|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

354
                $data['view_url'] = /** @scrutinizer ignore-type */ $prefix . $this->get_url($article, $this->_config->get('link_to_external_url'));
Loading history...
355 2
                $data['local_view_url'] = $data['view_url'];
356 2
                $data['linked'] = ($article->topic !== $this->_topic->id);
357 2
                if ($data['linked']) {
358
                    $nap = new midcom_helper_nav();
359
                    $data['node'] = $nap->get_node($article->topic);
360
                }
361
362 2
                midcom_show_style('archive-list-item');
363
            }
364
        } else {
365
            midcom_show_style('archive-list-empty');
366
        }
367
368 2
        midcom_show_style('archive-list-end');
369
    }
370
}
371