Passed
Push — master ( d76f23...1b6466 )
by Andreas
26:38 queued 05:10
created

net_nehmer_blog_handler_archive   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 397
Duplicated Lines 0 %

Test Coverage

Coverage 85.39%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 174
c 1
b 0
f 0
dl 0
loc 397
ccs 152
cts 178
cp 0.8539
rs 9.2
wmc 40

11 Methods

Rating   Name   Duplication   Size   Complexity  
A _prepare_request_data() 0 4 1
A _handler_welcome() 0 12 2
A _compute_welcome_posting_count() 0 9 1
B _compute_welcome_data() 0 66 6
A _compute_welcome_first_post() 0 20 3
A _get_month_names() 0 9 2
A _show_welcome() 0 13 2
B _handler_list() 0 58 9
A _set_startend_from_month() 0 26 6
A _show_list() 0 35 5
A _set_startend_from_year() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like net_nehmer_blog_handler_archive often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use net_nehmer_blog_handler_archive, and based on these observations, apply Extract Interface, too.

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: $admin, $name, $auth, $url, $guid, $revised, $metadata, $user
Loading history...
21
22
    /**
23
     * The articles to display
24
     *
25
     * @var array
26
     */
27
    private $_articles;
28
29
    /**
30
     * The start date of the Archive listing.
31
     *
32
     * @var DateTime
33
     */
34
    private $_start;
35
36
    /**
37
     * The end date of the Archive listing.
38
     *
39
     * @var DateTime
40
     */
41
    private $_end;
42
43
    /**
44
     * Simple helper which references all important members to the request data listing
45
     * for usage within the style listing.
46
     */
47 3
    private function _prepare_request_data()
48
    {
49 3
        $this->_request_data['start'] = $this->_start;
50 3
        $this->_request_data['end'] = $this->_end;
51 3
    }
52
53
    /**
54
     * Shows the archive welcome page: A listing of years/months along with total post counts
55
     * and similar stuff.
56
     *
57
     * The handler computes all necessary data and populates the request array accordingly.
58
     */
59 1
    public function _handler_welcome()
60
    {
61 1
        $this->_compute_welcome_data();
62 1
        $this->_prepare_request_data();
63
64 1
        if ($this->_config->get('archive_in_navigation')) {
65 1
            $this->set_active_leaf($this->_topic->id . '_ARCHIVE');
66
        }
67
68 1
        midcom::get()->head->set_pagetitle("{$this->_topic->extra}: " . $this->_l10n->get('archive'));
69
70 1
        midcom::get()->metadata->set_request_metadata($this->get_last_modified(), $this->_topic->guid);
71 1
    }
72
73
    /**
74
     * Loads the first posting time from the DB. This is the base for all operations on the
75
     * resultset.
76
     *
77
     * This is done under sudo if possible, to avoid problems arising if the first posting
78
     * is hidden. This keeps up performance, as an execute_unchecked() can be made in this case.
79
     * If sudo cannot be acquired, the system falls back to execute().
80
     */
81 1
    private function _compute_welcome_first_post() : ?DateTime
82
    {
83 1
        $qb = midcom_db_article::new_query_builder();
84 1
        $this->article_qb_constraints($qb);
85 1
        $qb->add_constraint('metadata.published', '>', '1970-01-02 23:59:59');
86
87 1
        $qb->add_order('metadata.published');
88 1
        $qb->set_limit(1);
89
90 1
        if (midcom::get()->auth->request_sudo($this->_component)) {
91 1
            $result = $qb->execute_unchecked();
92 1
            midcom::get()->auth->drop_sudo();
93
        } else {
94
            $result = $qb->execute();
95
        }
96
97 1
        if (!empty($result)) {
98 1
            return new DateTime(strftime('%Y-%m-%d %H:%M:%S', $result[0]->metadata->published));
99
        }
100
        return null;
101
    }
102
103
    /**
104
     * Computes the number of postings in a given timeframe.
105
     *
106
     * @param DateTime $start Start of the timeframe (inclusive)
107
     * @param DateTime $end End of the timeframe (exclusive)
108
     */
109 1
    private function _compute_welcome_posting_count(DateTime $start, DateTime $end) : int
110
    {
111 1
        $qb = midcom_db_article::new_query_builder();
112
113 1
        $qb->add_constraint('metadata.published', '>=', $start->format('Y-m-d H:i:s'));
114 1
        $qb->add_constraint('metadata.published', '<', $end->format('Y-m-d H:i:s'));
115 1
        $this->article_qb_constraints($qb);
116
117 1
        return $qb->count();
118
    }
119
120
    /**
121
     * Computes the data nececssary for the welcome screen. Automatically put into the request
122
     * data array.
123
     */
124 1
    private function _compute_welcome_data()
125
    {
126
        // Helpers
127 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

127
        $prefix = /** @scrutinizer ignore-type */ midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX) . 'archive/';
Loading history...
128
129
        // First step of request data: Overall info
130 1
        $total_count = 0;
131 1
        $year_data = [];
132 1
        $first_post = $this->_compute_welcome_first_post();
133 1
        $this->_request_data['first_post'] = $first_post;
134 1
        $this->_request_data['total_count'] =& $total_count;
135 1
        $this->_request_data['year_data'] =& $year_data;
136 1
        if (!$first_post) {
137
            return;
138
        }
139
140
        // Second step of request data: Years and months.
141 1
        $now = new DateTime();
142 1
        $first_year = $first_post->format('Y');
143 1
        $last_year = $now->format('Y');
144
145 1
        $month_names = $this->_get_month_names();
146
147 1
        for ($year = $last_year; $year >= $first_year; $year--) {
148 1
            $year_url = "{$prefix}year/{$year}/";
149 1
            $year_count = 0;
150 1
            $month_data = [];
151
152
            // Loop over the months, start month is either first posting month
153
            // or January in all other cases. End months are treated similarly,
154
            // being december by default unless for the current year.
155 1
            if ($year == $first_year) {
156 1
                $first_month = $first_post->format('n');
157
            } else {
158
                $first_month = 1;
159
            }
160
161 1
            if ($year == $last_year) {
162 1
                $last_month = $now->format('n');
163
            } else {
164
                $last_month = 12;
165
            }
166
167 1
            for ($month = $first_month; $month <= $last_month; $month++) {
168 1
                $start_time = new DateTime();
169 1
                $start_time->setDate($year, $month, 1);
0 ignored issues
show
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

169
                $start_time->setDate(/** @scrutinizer ignore-type */ $year, $month, 1);
Loading history...
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

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

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

400
                $data['view_url'] = /** @scrutinizer ignore-type */ $prefix . $this->get_url($article, $this->_config->get('link_to_external_url'));
Loading history...
401 2
                $data['local_view_url'] = $data['view_url'];
402 2
                $data['linked'] = ($article->topic !== $this->_topic->id);
403 2
                if ($data['linked']) {
404
                    $nap = new midcom_helper_nav();
405
                    $data['node'] = $nap->get_node($article->topic);
406
                }
407
408 2
                midcom_show_style('archive-list-item');
409
            }
410
        } else {
411
            midcom_show_style('archive-list-empty');
412
        }
413
414 2
        midcom_show_style('archive-list-end');
415 2
    }
416
}
417