• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 21597170901

02 Feb 2026 03:55PM UTC coverage: 82.607% (-0.01%) from 82.617%
21597170901

push

github

web-flow
Add page status + feed/template improvements (#2306)

Introduce a Status enum and page status handling: add src/Collection/Page/Status.php, set a default 'status' variable on Page, and update Collection::showable() and all() to accept an includeStatus array (so pages with specified statuses can be included).

Feed and template fixes: move sorting into extended/feed.twig and include unpublished pages there; expose a consistent lang variable; adjust Atom/JSONFeed/RSS templates to stop sorting again, add/move published/updated/link elements, and normalize JSONFeed fields (content_text/content_html ordering and language fallback to lang). These changes centralize sorting, support unpublished pages in feeds, and make feed metadata consistent.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

6 of 7 new or added lines in 2 files covered. (85.71%)

70 existing lines in 1 file now uncovered.

3315 of 4013 relevant lines covered (82.61%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.0
/src/Collection/Page/Collection.php
1
<?php
2

3
/**
4
 * This file is part of Cecil.
5
 *
6
 * (c) Arnaud Ligny <arnaud@ligny.fr>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace Cecil\Collection\Page;
15

16
use Cecil\Collection\Collection as CecilCollection;
17
use Cecil\Exception\RuntimeException;
18

19
/**
20
 * Pages collection class.
21
 *
22
 * Represents a collection of pages, providing methods to filter and sort them.
23
 */
24
class Collection extends CecilCollection
25
{
26
    /**
27
     * Returns all "showable" pages.
28
     */
29
    public function showable(array $includeStatus = []): self
30
    {
31
        return $this->filter(function (Page $page) use ($includeStatus) {
1✔
32
            // check if page status should be explicitly included
33
            $statusIncluded = \in_array($page->getVariable('status'), $includeStatus, true);
1✔
34

35
            // standard showable criteria, with optional status-based inclusion
36
            if (
37
                (
38
                    $page->getVariable('published') === true  // page is published
1✔
39
                    || $statusIncluded                         // or explicitly included by status
1✔
40
                )
41
                && (
42
                    $page->getVariable('excluded') !== true   // page is listed
1✔
43
                    && $page->getVariable('exclude') !== true // backward compatibility
1✔
44
                )
45
                && $page->isVirtual() === false               // page is created from a file
1✔
46
                && $page->getVariable('redirect') === null    // page is not a redirection
1✔
47
            ) {
48
                return true;
1✔
49
            }
50
            return false;
1✔
51
        });
1✔
52
    }
53

54
    /**
55
     * Alias of showable().
56
     */
57
    public function all(array $includeStatus = []): self
58
    {
NEW
59
        return $this->showable($includeStatus);
×
60
    }
61

62
    /**
63
     * Sorts pages by.
64
     *
65
     * $options:
66
     * [date|updated|title|weight]
67
     * or
68
     * [
69
     *   variable   => date|updated|title|weight
70
     *   desc_title => false|true
71
     *   reverse    => false|true
72
     * ]
73
     */
74
    public function sortBy(string|array|null $options): self
75
    {
76
        $sortBy = \is_string($options) ? $options : $options['variable'] ?? 'date';
1✔
77
        $sortMethod = \sprintf('sortBy%s', ucfirst(str_replace('updated', 'date', $sortBy)));
1✔
78
        if (!method_exists($this, $sortMethod)) {
1✔
79
            throw new RuntimeException(\sprintf('"%s" is not a valid value for `sortby` to sort collection "%s".', $sortBy, $this->getId()));
×
80
        }
81

82
        return $this->$sortMethod($options);
1✔
83
    }
84

85
    /**
86
     * Sorts pages by date (or 'updated'): the most recent first.
87
     */
88
    public function sortByDate(string|array|null $options = null): self
89
    {
90
        $opt = [];
1✔
91
        // backward compatibility (i.e. $options = 'updated')
92
        if (\is_string($options)) {
1✔
93
            $opt['variable'] = $options;
1✔
94
        }
95
        // options
96
        $opt['variable'] = $options['variable'] ?? 'date';
1✔
97
        $opt['descTitle'] = $options['descTitle'] ?? false;
1✔
98
        $opt['reverse'] = $options['reverse'] ?? false;
1✔
99
        // sort
100
        $pages = $this->usort(function ($a, $b) use ($opt) {
1✔
101
            if ($a[$opt['variable']] == $b[$opt['variable']]) {
1✔
102
                // if dates are equal and "descTitle" is true
103
                if ($opt['descTitle'] && (isset($a['title']) && isset($b['title']))) {
1✔
104
                    return strnatcmp($b['title'], $a['title']);
×
105
                }
106

107
                return 0;
1✔
108
            }
109

110
            return $a[$opt['variable']] > $b[$opt['variable']] ? -1 : 1;
1✔
111
        });
1✔
112
        if ($opt['reverse']) {
1✔
113
            $pages = $pages->reverse();
×
114
        }
115

116
        return $pages;
1✔
117
    }
118

119
    /**
120
     * Sorts pages by title (natural sort).
121
     */
122
    public function sortByTitle(string|array|null $options = null): self
123
    {
124
        $opt = [];
1✔
125
        // options
126
        $opt['reverse'] = $options['reverse'] ?? false;
1✔
127
        // sort
128
        return $this->usort(function ($a, $b) use ($opt) {
1✔
129
            return ($opt['reverse'] ? -1 : 1) * strnatcmp($a['title'], $b['title']);
1✔
130
        });
1✔
131
    }
132

133
    /**
134
     * Sorts by weight (the heaviest first).
135
     */
136
    public function sortByWeight(string|array|null $options = null): self
137
    {
138
        $opt = [];
×
139
        // options
140
        $opt['reverse'] = $options['reverse'] ?? false;
×
141
        // sort
142
        return $this->usort(function ($a, $b) use ($opt) {
×
143
            if ($a['weight'] == $b['weight']) {
×
144
                return 0;
×
145
            }
146

147
            return ($opt['reverse'] ? -1 : 1) * ($a['weight'] < $b['weight'] ? -1 : 1);
×
148
        });
×
149
    }
150

151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function get(string $id): Page
155
    {
156
        return parent::get($id);
1✔
157
    }
158

159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function first(): ?Page
163
    {
164
        return parent::first();
1✔
165
    }
166

167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function filter(\Closure $callback): self
171
    {
172
        return parent::filter($callback);
1✔
173
    }
174

175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function usort(?\Closure $callback = null): self
179
    {
180
        return parent::usort($callback);
1✔
181
    }
182

183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function reverse(): self
187
    {
188
        return parent::reverse();
×
189
    }
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc