Passed
Push — master ( 7ac5bb...53a731 )
by Andreas
11:19
created

net_nehmer_blog_handler_archive   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Test Coverage

Coverage 84.62%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 172
dl 0
loc 368
ccs 143
cts 169
cp 0.8462
rs 9.28
c 1
b 0
f 0
wmc 39

10 Methods

Rating   Name   Duplication   Size   Complexity  
A _handler_welcome() 0 11 2
A _compute_welcome_posting_count() 0 9 1
A _set_startend_from_month() 0 26 6
B _compute_welcome_data() 0 66 6
A _show_list() 0 35 5
A _show_welcome() 0 13 2
A _set_startend_from_year() 0 14 3
A _compute_welcome_first_post() 0 20 3
A _get_month_names() 0 9 2
B _handler_list() 0 59 9
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
        // Helpers
104 1
        $prefix = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX) . 'archive/';
0 ignored issues
show
Bug introduced by
Are you sure midcom_core_context::get...M_CONTEXT_ANCHORPREFIX) 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

104
        $prefix = /** @scrutinizer ignore-type */ midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX) . 'archive/';
Loading history...
105
106
        // First step of request data: Overall info
107 1
        $total_count = 0;
108 1
        $year_data = [];
109 1
        $first_post = $this->_compute_welcome_first_post();
110 1
        $this->_request_data['first_post'] = $first_post;
111 1
        $this->_request_data['total_count'] =& $total_count;
112 1
        $this->_request_data['year_data'] =& $year_data;
113 1
        if (!$first_post) {
114
            return;
115
        }
116
117
        // Second step of request data: Years and months.
118 1
        $now = new DateTime();
119 1
        $first_year = $first_post->format('Y');
120 1
        $last_year = $now->format('Y');
121
122 1
        $month_names = $this->_get_month_names();
123
124 1
        for ($year = $last_year; $year >= $first_year; $year--) {
125 1
            $year_url = "{$prefix}year/{$year}/";
126 1
            $year_count = 0;
127 1
            $month_data = [];
128
129
            // Loop over the months, start month is either first posting month
130
            // or January in all other cases. End months are treated similarly,
131
            // being december by default unless for the current year.
132 1
            if ($year == $first_year) {
133 1
                $first_month = $first_post->format('n');
134
            } else {
135
                $first_month = 1;
136
            }
137
138 1
            if ($year == $last_year) {
139 1
                $last_month = $now->format('n');
140
            } else {
141
                $last_month = 12;
142
            }
143
144 1
            for ($month = $first_month; $month <= $last_month; $month++) {
145 1
                $start_time = new DateTime();
146 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

146
                $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

146
                $start_time->setDate(/** @scrutinizer ignore-type */ $year, $month, 1);
Loading history...
147 1
                $end_time = clone $start_time;
148 1
                $end_time->modify('+1 month');
149
150 1
                $month_url = "{$prefix}month/{$year}/{$month}/";
151 1
                $month_count = $this->_compute_welcome_posting_count($start_time, $end_time);
152 1
                $year_count += $month_count;
153 1
                $total_count += $month_count;
154 1
                $month_data[$month] = [
155 1
                    'month' => $month,
156 1
                    'name' => $month_names[$month],
157 1
                    'url' => $month_url,
158 1
                    'count' => $month_count,
159 1
                ];
160
            }
161
162 1
            $year_data[$year] = [
163 1
                'year' => $year,
164 1
                'url' => $year_url,
165 1
                'count' => $year_count,
166 1
                'month_data' => $month_data,
167 1
            ];
168
        }
169
    }
170
171 1
    private function _get_month_names() : array
172
    {
173 1
        $names = [];
174 1
        $formatter = $this->_l10n->get_formatter();
175 1
        for ($i = 1; $i < 13; $i++) {
176 1
            $timestamp = mktime(0, 0, 0, $i, 1, 2011);
177 1
            $names[$i] = $formatter->customdate($timestamp, 'MMMM');
178
        }
179 1
        return $names;
180
    }
181
182
    /**
183
     * Displays the welcome page.
184
     *
185
     * Element sequence:
186
     *
187
     * - archive-welcome-start (Start of the archive welcome page)
188
     * - archive-welcome-year (Display of a single year, may not be called when there are no postings)
189
     * - archive-welcome-end (End of the archive welcome page)
190
     *
191
     * Context data for all elements:
192
     *
193
     * - int total_count (total number of postings w/o ACL restrictions)
194
     * - DateTime first_post (the first posting date, may be null)
195
     * - Array year_data (the year data, contains the year context info as outlined below)
196
     *
197
     * Context data for year elements:
198
     *
199
     * - int year (the year displayed)
200
     * - string url (url to display the complete year)
201
     * - int count (Number of postings in that year)
202
     * - array month_data (the monthly data)
203
     *
204
     * month_data will contain an associative array containing the following array of data
205
     * indexed by month number (1-12):
206
     *
207
     * - string 'url' => The URL to the month.
208
     * - string 'name' => The localized name of the month.
209
     * - int 'count' => The number of postings in that month.
210
     */
211 1
    public function _show_welcome(string $handler_id, array &$data)
212
    {
213 1
        midcom_show_style('archive-welcome-start');
214
215 1
        foreach ($data['year_data'] as $year => $year_data) {
216 1
            $data['year'] = $year;
217 1
            $data['url'] = $year_data['url'];
218 1
            $data['count'] = $year_data['count'];
219 1
            $data['month_data'] = $year_data['month_data'];
220 1
            midcom_show_style('archive-welcome-year');
221
        }
222
223 1
        midcom_show_style('archive-welcome-end');
224
    }
225
226
    /**
227
     * Shows the archive. Depending on the selected handler various constraints are added to
228
     * the QB. See the add_*_constraint methods for details.
229
     */
230 2
    public function _handler_list(string $handler_id, array $args, array &$data)
231
    {
232 2
        $data['datamanager'] = new datamanager($data['schemadb']);
233
        // Get Articles, distinguish by handler.
234 2
        $qb = midcom_db_article::new_query_builder();
235 2
        $this->article_qb_constraints($qb);
236
237
        // Use helper functions to determine start/end
238
        switch ($handler_id) {
239 2
            case 'archive-year-category':
240
                $category = trim(strip_tags($args[1]));
241
                if (   $data['datamanager']->get_schema('default')->has_field('categories')
242
                    && !$data['datamanager']->get_schema('default')->get_field('categories')['type_config']['allow_multiple']) {
243
                    $qb->add_constraint('extra1', '=', $category);
244
                } else {
245
                    $qb->add_constraint('extra1', 'LIKE', "%|{$category}|%");
246
                }
247
                //Fall-through
248
249 2
            case 'archive-year':
250 1
                if (!$this->_config->get('archive_years_enable')) {
251
                    throw new midcom_error_notfound('Year archive not allowed');
252
                }
253
254 1
                $this->_set_startend_from_year($args[0]);
255 1
                break;
256
257 1
            case 'archive-month':
258 1
                $this->_set_startend_from_month($args[0], $args[1]);
259 1
                break;
260
        }
261
262 2
        $qb->add_constraint('metadata.published', '>=', $this->_start->format('Y-m-d H:i:s'));
263 2
        $qb->add_constraint('metadata.published', '<', $this->_end->format('Y-m-d H:i:s'));
264 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

264
        $qb->add_order('metadata.published', /** @scrutinizer ignore-type */ $this->_config->get('archive_item_order'));
Loading history...
265 2
        $this->_articles = $qb->execute();
266
267
        // Move end date one day backwards for display purposes.
268 2
        $now = new DateTime();
269 2
        if ($now < $this->_end) {
270 2
            $this->_end = $now;
271
        } else {
272
            $this->_end->modify('-1 day');
273
        }
274
275 2
        $timeframe = $this->_l10n->get_formatter()->timeframe($this->_start, $this->_end, 'date');
276 2
        $this->add_breadcrumb("archive/year/{$args[0]}/", $timeframe);
277
278 2
        $data['start'] = $this->_start;
279 2
        $data['end'] = $this->_end;
280
281 2
        if ($this->_config->get('archive_in_navigation')) {
282 2
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE');
283
        } else {
284
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE_' . $args[0]);
285
        }
286
287 2
        midcom::get()->metadata->set_request_metadata($this->get_last_modified(), $this->_topic->guid);
288 2
        midcom::get()->head->set_pagetitle("{$this->_topic->extra}: {$timeframe}");
289
    }
290
291
    /**
292
     * Computes the start/end dates to only query a given year. It will do validation
293
     * before processing, throwing 404 in case of incorrectly formatted dates.
294
     *
295
     * This is used by the archive-year handler, which expects the year to be in $args[0].
296
     */
297 1
    private function _set_startend_from_year(int $year)
298
    {
299 1
        if (strlen($year) != 4) {
300
            throw new midcom_error_notfound("The year '{$year}' is not valid.");
301
        }
302
303 1
        $now = new DateTime();
304 1
        if ($year > (int) $now->format('Y')) {
305
            throw new midcom_error_notfound("The year '{$year}' is in the future, no archive available.");
306
        }
307
308 1
        $endyear = $year + 1;
309 1
        $this->_start = new DateTime("{$year}-01-01 00:00:00");
310 1
        $this->_end = new DateTime("{$endyear}-01-01 00:00:00");
311
    }
312
313
    /**
314
     * Computes the start/end dates to only query a given month. It will do validation
315
     * before processing, throwing 404 in case of incorrectly formatted dates.
316
     *
317
     * This is used by the archive-month handler, which expects the year to be in $args[0]
318
     * and the month to be in $args[1].
319
     */
320 1
    private function _set_startend_from_month(int $year, int $month)
321
    {
322 1
        if (strlen($year) != 4) {
323
            throw new midcom_error_notfound("The year '{$year}' is not valid.");
324
        }
325
326 1
        if (   $month < 1
327 1
            || $month > 12) {
328
            throw new midcom_error_notfound("The month {$month} is not valid.");
329
        }
330
331 1
        $now = new DateTime();
332 1
        $this->_start = new DateTime("{$year}-" . sprintf('%02d', $month) . "-01 00:00:00");
333 1
        if ($this->_start > $now) {
334
            throw new midcom_error_notfound("The month '{$year}-" . sprintf('%02d', $month) . "' is in the future, no archive available.");
335
        }
336
337 1
        if ($month == 12) {
338
            $endyear = $year + 1;
339
            $endmonth = 1;
340
        } else {
341 1
            $endyear = $year;
342 1
            $endmonth = $month + 1;
343
        }
344
345 1
        $this->_end = new DateTime("{$endyear}-" . sprintf('%02d', $endmonth) . "-01 00:00:00");
346
    }
347
348
    /**
349
     * Displays the archive.
350
     */
351 2
    public function _show_list(string $handler_id, array &$data)
352
    {
353 2
        midcom_show_style('archive-list-start');
354 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...
355 2
            $data['index_fulltext'] = $this->_config->get('index_fulltext');
356
357 2
            $total_count = count($this->_articles);
358 2
            $prefix = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX);
359
360 2
            foreach ($this->_articles as $article_counter => $article) {
361
                try {
362 2
                    $data['datamanager']->set_storage($article);
363
                } catch (midcom_error $e) {
364
                    $e->log();
365
                    continue;
366
                }
367
368 2
                $data['article'] = $article;
369 2
                $data['article_counter'] = $article_counter;
370 2
                $data['article_count'] = $total_count;
371 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

371
                $data['view_url'] = /** @scrutinizer ignore-type */ $prefix . $this->get_url($article, $this->_config->get('link_to_external_url'));
Loading history...
372 2
                $data['local_view_url'] = $data['view_url'];
373 2
                $data['linked'] = ($article->topic !== $this->_topic->id);
374 2
                if ($data['linked']) {
375
                    $nap = new midcom_helper_nav();
376
                    $data['node'] = $nap->get_node($article->topic);
377
                }
378
379 2
                midcom_show_style('archive-list-item');
380
            }
381
        } else {
382
            midcom_show_style('archive-list-empty');
383
        }
384
385 2
        midcom_show_style('archive-list-end');
386
    }
387
}
388