├── src
├── templates
│ ├── blog-page.html.twig
│ ├── blog-authors.html.twig
│ ├── blog-archives.html.twig
│ ├── blog-tags.html.twig
│ ├── blog-post.html.twig
│ └── blog-listings.html.twig
├── Twig
│ ├── Parser.php
│ ├── Markdown.php
│ ├── Object.php
│ ├── Page.php
│ ├── ExpressionParser.php
│ └── Theme.php
├── Component.php
└── Blog.php
├── CHANGELOG.md
├── LICENSE.md
├── composer.json
└── README.md
/src/templates/blog-page.html.twig:
--------------------------------------------------------------------------------
1 | {{ content }}
2 |
--------------------------------------------------------------------------------
/src/Twig/Parser.php:
--------------------------------------------------------------------------------
1 | env = $env;
10 | $this->expressionParser = new ExpressionParser($this, $this->env);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/templates/blog-authors.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for name, href in breadcrumbs %}
3 | {% if loop.last %}
4 | - {{ name }}
5 | {% else %}
6 | - {{ name }}
7 | {% endif %}
8 | {% endfor %}
9 |
10 |
11 | {{ page.set('title', 'Authors @ ' ~ blog.name) }}
12 |
13 | {% for author in authors %}
14 | {{ author.name }} {{ author.count }}
15 | {% endfor %}
--------------------------------------------------------------------------------
/src/Twig/Markdown.php:
--------------------------------------------------------------------------------
1 | theme = $theme;
17 | }
18 |
19 | /**
20 | * {@inheritdoc}
21 | */
22 | public function transform($content)
23 | {
24 | return $this->theme->markdown($content);
25 | }
26 |
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function getName()
31 | {
32 | return 'Blog\Markdown';
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Twig/Object.php:
--------------------------------------------------------------------------------
1 | properties = $properties;
13 | $this->methods = $methods;
14 | }
15 |
16 | public function __get($name)
17 | {
18 | return (isset($this->properties[$name])) ? $this->properties[$name] : null;
19 | }
20 |
21 | public function __isset($name)
22 | {
23 | return (isset($this->methods[$name])) ? false : true;
24 | }
25 |
26 | public function __call($name, $arguments)
27 | {
28 | if (isset($this->methods[$name]) && is_callable($this->methods[$name])) {
29 | return call_user_func_array($this->methods[$name], $arguments);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Twig/Page.php:
--------------------------------------------------------------------------------
1 | methods[$name] = array($page, $name);
16 | }
17 | }
18 |
19 | public function __get($name)
20 | {
21 | return PageClone::html()->$name;
22 | }
23 |
24 | public function __isset($name)
25 | {
26 | return (isset($this->methods[$name])) ? false : true;
27 | }
28 |
29 | public function __call($name, $arguments)
30 | {
31 | if (isset($this->methods[$name]) && is_callable($this->methods[$name])) {
32 | return call_user_func_array($this->methods[$name], $arguments);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [Unreleased]
8 |
9 | ## [1.4] - 2017-01-08
10 | ### Added
11 | - ParsedownExtra as default markdown provider
12 | - Custom callable markdown implementations
13 |
14 | ## [1.3] - 2017-01-04
15 | ### Added
16 | - Microtimes to Twig templates debug data
17 | - Static Theme dumper method
18 |
19 | ## [1.2] - 2016-11-29
20 | ### Added
21 | - Twig dir function for relative paths
22 |
23 | ### Changed
24 | - Only save html pages in database
25 |
26 | ### Fixed
27 | - Empty category undefined offset
28 |
29 | ## [1.1] - 2016-10-25
30 | ### Added
31 | - Optionally specify Twig array keys via '**:**' colon, similar to hashes
32 | - Improved Global Vars Dump
33 |
34 | ### Fixed
35 | - Relative asset paths for Twig v1.27.0
36 |
37 | ## [1.0] - 2016-10-13
38 | - Initial Release
39 |
--------------------------------------------------------------------------------
/src/templates/blog-archives.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for name, href in breadcrumbs %}
3 | {% if loop.last %}
4 | - {{ name }}
5 | {% else %}
6 | - {{ name }}
7 | {% endif %}
8 | {% endfor %}
9 |
10 |
11 | {{ page.set('title', 'The Archives @ ' ~ blog.name) }}
12 |
13 | {% for Y, years in archives|reverse(true) %}
14 |
15 |
16 |
17 |
18 | {% set columns = [] %}
19 | {% for M, months in years.months %}
20 |
29 | {% endfor %}
30 |
31 |
32 | {% endfor %}
--------------------------------------------------------------------------------
/src/templates/blog-tags.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for name, href in breadcrumbs %}
3 | {% if loop.last %}
4 | - {{ name }}
5 | {% else %}
6 | - {{ name }}
7 | {% endif %}
8 | {% endfor %}
9 |
10 |
11 | {{ page.set('title', 'Tag Cloud @ ' ~ blog.name) }}
12 |
13 |
14 | {% for tag in tags %}
15 | {% if tag.rank == 1 %}
16 | {{ tag.name }}
17 | {% elseif tag.rank == 2 %}
18 | {{ tag.name }}
19 | {% elseif tag.rank == 3 %}
20 | {{ tag.name }}
21 | {% elseif tag.rank == 4 %}
22 | {{ tag.name }}
23 | {% elseif tag.rank == 5 %}
24 | {{ tag.name }}
25 | {% endif %}
26 | {% endfor %}
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Kyle Gadd
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/templates/blog-post.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for name, href in breadcrumbs %}
3 | {% if loop.last %}
4 | -
5 | {% else %}
6 | - {{ name }}
7 | {% endif %}
8 | {% endfor %}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Published: {{ post.published|date('F j, Y') }}
17 | {% if post.author %}
18 | by {{ post.author.name }}
19 | {% endif %}
20 |
21 |
22 | {{ post.content }}
23 |
24 | {% for tag in post.tags %}
25 | {% if loop.first %}
26 | Tagged:
27 | {% endif %}
28 |
29 | {{ tag.name }}
30 |
31 | {% if loop.last %}
32 |
33 | {% endif %}
34 | {% endfor %}
35 |
36 |
37 |
38 | {{ pagination.pager(post.previous, post.next) }}
39 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bootpress/blog",
3 | "description": "A flat file Blog and CMS that doesn't skimp on features, and can be utilized in any website.",
4 | "keywords": ["bootpress", "blog", "flat-file", "cms", "twig", "markdown"],
5 | "homepage": "https://www.bootpress.org/components/blog.html",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Kyle Gadd",
10 | "email": "kyle.gadd@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.4",
15 | "bootpress/page": "^1.0",
16 | "bootpress/asset": "^1.0",
17 | "bootpress/sqlite": "^1.0",
18 | "bootpress/sitemap": "^1.0",
19 | "bootpress/hierarchy": "^1.0",
20 | "bootpress/pagination": "^1.0",
21 | "symfony/var-dumper": "^2.3||^3.0",
22 | "symfony/finder": "^2.3||^3.0",
23 | "symfony/yaml": "^2.3||^3.0",
24 | "jbroadway/urlify": "^1.0",
25 | "xsirix/phpuri": "^1.0",
26 | "spartz/text-formatter": "^1.0",
27 | "aptoma/twig-markdown": "^2.0",
28 | "erusev/parsedown-extra": "^0.7",
29 | "twig/twig": "^1.27"
30 | },
31 | "require-dev": {
32 | "bootpress/htmlunit": "^1.0",
33 | "friendsofphp/php-cs-fixer": "^1.0",
34 | "squizlabs/php_codesniffer": "^2.5"
35 | },
36 | "autoload": {
37 | "psr-4": { "BootPress\\Blog\\": "src" }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Twig/ExpressionParser.php:
--------------------------------------------------------------------------------
1 | parser->getStream();
13 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
14 |
15 | $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
16 | $first = true;
17 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
18 | if (!$first) {
19 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
20 |
21 | // trailing ,?
22 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
23 | break;
24 | }
25 | }
26 | $first = false;
27 |
28 | // $node->addElement($this->parseExpression());
29 | $value = $this->parseExpression();
30 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
31 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':');
32 | $node->addElement($this->parseExpression(), $value);
33 | } else {
34 | $node->addElement($value);
35 | }
36 | }
37 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
38 |
39 | return $node;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/templates/blog-listings.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for name, href in breadcrumbs %}
3 | {% if loop.last %}
4 | {% if search %}
5 | - Search: {{ search|escape }}
6 | {% else %}
7 | - {{ name }}
8 | {% endif %}
9 | {% else %}
10 | - {{ name }}
11 | {% endif %}
12 | {% endfor %}
13 |
14 |
15 | {% if search %}
16 |
17 | {% if blog.page == 'index' %}
18 |
19 | {{ page.set('title', 'Search: ' ~ search|escape('html_attr') ~ ' @ ' ~ blog.name) }}
20 |
21 | {% elseif blog.page == 'category' %}
22 |
23 | {{ page.set('title', 'Search: ' ~ search|escape('html_attr') ~ ' @ ' ~ category|join(' » ')) }}
24 |
25 | {% endif %}
26 |
27 | {% elseif blog.page == 'index' %}
28 |
29 | {{ page.set('title', blog.name) }}
30 |
31 | {% elseif blog.page == 'category' %}
32 |
33 | {{ page.set('title', category|join(' » ') ~ ' @ ' ~ blog.name) }}
34 |
35 | {% elseif blog.page == 'archives' %}
36 |
37 | {% if archive|length == 4 %}
38 | {% set date = archive.date|date('F j, Y') %}
39 | {% elseif archive|length == 3 %}
40 | {% set date = archive.date|date('F Y') %}
41 | {% elseif archive|length == 2 %}
42 | {% set date = archive.date|date('Y') %}
43 | {% endif %}
44 |
45 | {{ page.set('title', date ~ ' Archives @ ' ~ blog.name) }}
46 |
47 | {% elseif blog.page == 'authors' %}
48 |
49 | {{ page.set('title', author.name ~ ' @ ' ~ blog.name) }}
50 |
51 | {% elseif blog.page == 'tags' %}
52 |
53 | {{ page.set('title', tag.name ~ ' @ ' ~ blog.name) }}
54 |
55 | {% endif %}
56 |
57 | {% if not pagination.set('page', 10) %}
58 | {{ pagination.total(blog.query(listings, 'count')) }}
59 | {% endif %}
60 |
61 | {% set posts = blog.query(listings, pagination) %}
62 |
63 | {% for post in posts %}
64 |
65 |
66 |
67 |
68 |
69 |
70 | {% if post.page.image %}
71 |
72 | {% endif %}
73 | {% if search %}
74 | {{ post.snippet }}
75 | {% else %}
76 | {{ post.content|striptags|length > 500 ? post.content|striptags|slice(0,500) ~ '…' : post.content|striptags }}
77 | {% endif %}
78 |
79 |
80 | Read more →
81 |
82 |
83 |
84 | {% endfor %}
85 |
86 | {{ pagination.pager() }}
87 |
--------------------------------------------------------------------------------
/src/Component.php:
--------------------------------------------------------------------------------
1 | url['path']`` Twig template, a string if ``$page->url['format'] != 'html'``, or an array suitable for passing to ``$blog->theme->renderTwig($file)`` with the following keys:
18 | *
19 | * - '**file**' => The appropriate Twig template that is equipped to deal with these '**type**' of '**vars**'.
20 | * - '**type**' => The kind of Blog page you are working with. Either *'page'*, *'post'*, *'category'*, *'index'*, *'archives'*, *'authors'*, or *'tags'*.
21 | * - '**vars**' => For the Twig template to utilize.
22 | * - '**default**' => An alternate '**file**' to use if it's missing in your theme.
23 | *
24 | * ```php
25 | * if (is_array($file = $blog->page())) { // An 'index.html.twig' file
26 | * $html = $blog->theme->renderTwig($file);
27 | * } elseif ($file !== false) { // A 'txt', 'json', 'xml', 'rdf', 'rss', or 'atom' page
28 | * $page->send(Asset::dispatch($page->url['format'], $file));
29 | * }
30 | * ```
31 | */
32 | public function page()
33 | {
34 | $page = Page::html();
35 | $listings = $this->config('blog', 'listings');
36 | if ($page->url['format'] != 'html') {
37 | $file = (preg_match('/\.(txt|json|xml|rdf|rss|atom)$/', $page->url['path'])) ? $this->folder.'content/'.$page->url['path'].'.twig' : null;
38 |
39 | return (is_file($file)) ? trim($this->theme->renderTwig($file)) : false;
40 | } elseif ($id = $this->file($page->url['path'])) {
41 | $params = array('id' => $id, 'path' => $page->url['path']);
42 | $method = 'blog';
43 | } elseif ($route = $page->routes(array(
44 | $listings,
45 | $listings.'/[archives:path]/[i:year]?/[i:month]?/[i:day]?',
46 | $listings.'/[authors|tags:path]/[:name]?',
47 | '[**:category]',
48 | ))) {
49 | $params = $route['params'];
50 | $method = (isset($params['path'])) ? $params['path'] : 'listings';
51 | if ($method == 'listings' && $search = $page->request->query->get('search')) {
52 | $params['search'] = $search;
53 | }
54 | }
55 | if (!isset($method) || (isset($params['category']) && !is_dir($this->folder.'content/'.$params['category']))) {
56 | $template = false;
57 | } elseif ($template = $this->$method($params)) {
58 | $template = array_combine(array('file', 'type', 'vars'), $template);
59 | $template['default'] = __DIR__.'/templates/';
60 | $this->theme->vars['blog']->properties['page'] = $template['type'];
61 | }
62 |
63 | return $template;
64 | }
65 |
66 | private function blog($params, array $vars = array())
67 | {
68 | $page = Page::html();
69 | extract($params); // 'id' and 'path'
70 | $page->enforce($path);
71 | $info = $this->info($id);
72 | if (is_bool($info['published'])) {
73 | return array('blog-page.html.twig', 'post', $info);
74 | }
75 | $vars['post'] = $info;
76 | if ($search = $page->request->query->get('search')) {
77 | $sitemap = new Sitemap();
78 | if ($docid = $sitemap->db->value('SELECT docid FROM sitemap WHERE path = ?', $path)) {
79 | $words = $sitemap->words($search, $docid);
80 | if (!empty($words)) {
81 | $vars['search'] = $words;
82 | }
83 | }
84 | unset($sitemap);
85 | }
86 | $vars['breadcrumbs'] = $this->breadcrumbs();
87 | foreach ($vars['post']['categories'] as $category) {
88 | $vars['breadcrumbs'][$category['name']] = $category['url'];
89 | }
90 | $vars['breadcrumbs'][$vars['post']['title']] = $vars['post']['url'];
91 |
92 | return array('blog-post.html.twig', 'post', $vars);
93 | }
94 |
95 | private function listings($params, array $vars = array())
96 | {
97 | $page = Page::html();
98 | extract($params); // 'path'?, 'category'? and 'search'?
99 | if (isset($category)) {
100 | $url = $page->url('base', $category);
101 | } else {
102 | $url = $page->url('blog/listings');
103 | }
104 | $page->enforce($url);
105 | $type = (isset($category)) ? 'category' : 'index';
106 | $vars['listings'] = array();
107 | $vars['breadcrumbs'] = $this->breadcrumbs();
108 | if (isset($category)) {
109 | $hier = new Hierarchy($this->db, 'categories');
110 | $path = $hier->path('path', $category, array('path', 'name'));
111 | if (empty($path)) {
112 | return false;
113 | }
114 | $tree = $hier->tree(array('path', 'name'), 'path', $category);
115 | $counts = $hier->counts('blog', 'category_id');
116 | foreach ($tree as $id => $fields) {
117 | $tree[$id]['count'] = (isset($counts[$id])) ? $counts[$id] : 0;
118 | }
119 | foreach ($path as $row) {
120 | $vars['category'][] = $row['name'];
121 | $vars['breadcrumbs'][$row['name']] = $page->url('base', $row['path']);
122 | }
123 | $vars['categories'] = $this->query('categories', array('nest' => $hier->nestify($tree), 'tree' => $tree));
124 | $vars['listings']['categories'] = array_keys($tree);
125 | }
126 | if (isset($search) && !empty($search)) {
127 | $vars['search'] = $search;
128 | $vars['breadcrumbs']['Search'] = $page->url('add', $url, 'search', $search);
129 | $vars['listings']['search'] = $search;
130 | }
131 |
132 | return array('blog-listings.html.twig', $type, $vars);
133 | }
134 |
135 | private function archives($params, array $vars = array())
136 | {
137 | $page = Page::html();
138 | list($path, $Y, $m, $d) = array_pad(array_values($params), 4, '');
139 | if (!empty($d)) {
140 | list($from, $to) = $this->range($Y, $m, $d);
141 | $page->enforce($page->url('blog/listings', $path, date('/Y/m/d', $from)));
142 | $vars['archive'] = array_combine(array('date', 'year', 'month', 'day'), explode(' ', date($from.' Y F j', $from)));
143 | $vars['breadcrumbs'] = $this->breadcrumbs(array(
144 | 'Archives' => $path,
145 | $vars['archive']['year'] => $Y,
146 | $vars['archive']['month'] => $m,
147 | $vars['archive']['day'] => $d,
148 | ));
149 | } elseif (!empty($m)) {
150 | list($from, $to) = $this->range($Y, $m);
151 | $page->enforce($page->url('blog/listings', $path, date('/Y/m', $from)));
152 | $vars['archive'] = array_combine(array('date', 'year', 'month'), explode(' ', date($from.' Y F', $from)));
153 | $vars['breadcrumbs'] = $this->breadcrumbs(array(
154 | 'Archives' => $path,
155 | $vars['archive']['year'] => $Y,
156 | $vars['archive']['month'] => $m,
157 | ));
158 | } elseif (!empty($Y)) {
159 | list($from, $to) = $this->range($Y);
160 | $page->enforce($page->url('blog/listings', $path, date('/Y', $from)));
161 | $vars['archive'] = array_combine(array('date', 'year'), explode(' ', date($from.' Y', $from)));
162 | $vars['breadcrumbs'] = $this->breadcrumbs(array(
163 | 'Archives' => $path,
164 | $vars['archive']['year'] => $Y,
165 | ));
166 | } else {
167 | $page->enforce($page->url('blog/listings', $path));
168 | $vars['archives'] = $this->query('archives');
169 | $vars['breadcrumbs'] = $this->breadcrumbs(array(
170 | 'Archives' => $path,
171 | ));
172 |
173 | return array('blog-archives.html.twig', 'archives', $vars);
174 | }
175 | $vars['listings']['archives'] = array($from, $to);
176 |
177 | return array('blog-listings.html.twig', 'archives', $vars);
178 | }
179 |
180 | private function authors($params, array $vars = array())
181 | {
182 | $page = Page::html();
183 | extract($params); // 'path' and 'name'?
184 | $vars['breadcrumbs'] = $this->breadcrumbs(array('Authors' => $path));
185 | if (!isset($name)) { // just authors, no posts
186 | $page->enforce($page->url('blog/listings', $path));
187 | $vars['authors'] = $this->query('authors');
188 |
189 | return array('blog-authors.html.twig', 'authors', $vars);
190 | }
191 | $vars['author'] = $this->query('authors', $name);
192 | if (empty($vars['author'])) {
193 | $page->eject($page->url('blog/listings', $path));
194 |
195 | return false;
196 | }
197 | $vars['breadcrumbs'][$vars['author']['name']] = $page->url('blog/listings', $path, $vars['author']['path']);
198 | $vars['listings']['count'] = $vars['author']['count'];
199 | $vars['listings']['authors'] = $name;
200 |
201 | return array('blog-listings.html.twig', 'authors', $vars);
202 | }
203 |
204 | private function tags($params, array $vars = array())
205 | {
206 | $page = Page::html();
207 | extract($params); // 'path' and 'name'?
208 | $vars['breadcrumbs'] = $this->breadcrumbs(array('Tags' => $path));
209 | if (!isset($name)) { // search all tags and get a frequency count
210 | $page->enforce($page->url('blog/listings', $path));
211 | $vars['tags'] = $this->query('tags');
212 |
213 | return array('blog-tags.html.twig', 'tags', $vars);
214 | }
215 | $vars['tag'] = $this->query('tags', $name);
216 | if (empty($vars['tag'])) {
217 | $page->eject($page->url('blog/listings', $path));
218 |
219 | return false;
220 | }
221 | $vars['breadcrumbs'][$vars['tag']['name']] = $page->url('blog/listings', $path, $vars['tag']['path']);
222 | $vars['listings']['count'] = $vars['tag']['count'];
223 | $vars['listings']['tags'] = $vars['tag']['path'];
224 |
225 | return array('blog-listings.html.twig', 'tags', $vars);
226 | }
227 |
228 | private function breadcrumbs(array $links = array())
229 | {
230 | $breadcrumbs = array();
231 | $breadcrumbs[$this->config('blog', 'breadcrumb')] = Page::html()->url('blog/listings');
232 | $previous = '';
233 | foreach ($links as $name => $path) {
234 | $breadcrumbs[$name] = Page::html()->url('blog/listings', $previous.$path);
235 | $previous .= $path.'/';
236 | }
237 |
238 | return $breadcrumbs;
239 | }
240 |
241 | private function range($Y, $m = null, $d = null)
242 | {
243 | if (!empty($d)) {
244 | $from = mktime(0, 0, 0, (int) $m, (int) $d, (int) $Y);
245 | $to = mktime(23, 59, 59, (int) $m, (int) $d, (int) $Y);
246 | } elseif (!empty($m)) {
247 | $from = mktime(0, 0, 0, (int) $m, 1, (int) $Y);
248 | $to = mktime(23, 59, 59, (int) $m + 1, 0, (int) $Y);
249 | } else {
250 | $from = mktime(0, 0, 0, 1, 1, (int) $Y);
251 | $to = mktime(23, 59, 59, 1, 0, (int) $Y + 1);
252 | }
253 |
254 | return array($from, $to);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # use BootPress\Blog\Component as Blog;
2 |
3 | [![Packagist][badge-version]][link-packagist]
4 | [![License MIT][badge-license]](LICENSE.md)
5 | [![HHVM Tested][badge-hhvm]][link-travis]
6 | [![PHP 7 Supported][badge-php]][link-travis]
7 | [![Build Status][badge-travis]][link-travis]
8 | [![Code Climate][badge-code-climate]][link-code-climate]
9 | [![Test Coverage][badge-coverage]][link-coverage]
10 |
11 | A flat file Blog and CMS that doesn't skimp on features, and can be utilized in any website.
12 |
13 | ## Installation
14 |
15 | Add the following to your ``composer.json`` file.
16 |
17 | ``` bash
18 | {
19 | "require": {
20 | "bootpress/blog": "^1.0"
21 | }
22 | }
23 | ```
24 |
25 | Create an ``.htaccess`` file in your website's public root folder to redirect everything that doesn't exist to an ``index.php`` file.
26 |
27 | ```htaccess
28 | # Prevent directory browsing
29 | Options All -Indexes
30 |
31 | # Turn on URL re-writing (remove 'example.com/' if not on localhost)
32 | RewriteEngine On
33 | RewriteBase /example.com/
34 |
35 | # If the file exists, then that's all folks
36 | RewriteCond %{REQUEST_FILENAME} -f
37 | RewriteRule .+ - [L]
38 |
39 | # For everything else, there's BootPress
40 | RewriteRule ^(.*)$ index.php [L]
41 | ```
42 |
43 | Your ``index.php`` file should then look something like this:
44 |
45 | ```php
46 | '../page', // a private (root) directory
58 | 'base' => 'http://localhost/example.com',
59 | 'suffix' => '.html',
60 | ));
61 | $html = '';
62 |
63 | // Deliver sitemap and assets first
64 | if ($asset = Asset::cached('assets')) {
65 | $page->send($asset);
66 | } elseif ($xml = Sitemap::page()) {
67 | $page->send($xml);
68 | }
69 |
70 | // Implement a blog
71 | $blog = new Blog();
72 | if (false !== $file = $blog->page()) {
73 | if (is_array($file)) { // An 'index.html.twig' file
74 | $html = $blog->theme->renderTwig($file);
75 | } else { // A 'txt', 'json', 'xml', 'rdf', 'rss', or 'atom' page
76 | $page->send(Asset::dispatch($page->url['format'], $file));
77 | }
78 | } else {
79 | $page->send(404);
80 | }
81 |
82 | // Create the layout
83 | $html = $page->display($blog->theme->layout($html));
84 |
85 | // Send to user
86 | $page->send(Asset::dispatch('html', $html));
87 | ```
88 |
89 | ## Setup Blog
90 |
91 | Create a ``../page/blog/config.yml`` file with the following information:
92 |
93 | ```yaml
94 | blog:
95 | name: Example # The name of your website
96 | image: logo.png # The main image relative to this directory
97 | listings: blog # The url base for all your listing pages - authors, archives, tags, etc.
98 | breadcrumb: Blog # How to reference the listings in your breadcrumbs array
99 | theme: default # The main theme for your site
100 | ```
101 |
102 | You can access any of these in your Twig templates eg. ``{{ blog.name }}``, including the ``{{ blog.page }}`` you are on. Eventually this file will be full of authors, categories, and tags that you can easily manage as well. You can create a [Bootstrap list group](http://getbootstrap.com/components/#list-group) of categories by:
103 |
104 | ```twig
105 |
106 | {% for category in blog.query('categories') %}
107 | -
108 | {{ category.count }}
109 | {{ category.name }}
110 | {# if category.subs #}
111 |
112 | {% endfor %}
113 |
114 | ```
115 |
116 | Other ``{{ blog.query(...) }}``'s include '**tags**', '**authors**', '**archives**', '**recent**', '**featured**', '**similar**', '**posts**', and **[...]** listings of every sort, otherwise known as "The Loop".
117 |
118 | ## Create Content
119 |
120 | A BootPress Blog is a flat-file CMS, which means you don't need any fancy admin interface to manage all of the content that is scattered througout a database. You simply create files. All of your blog's posts and pages will reside in the ``../page/blog/content/`` directory, and if you look at a URL, you will be able to follow the folders straight to your ``index.html.twig`` file. For example:
121 |
122 | | URL | File |
123 | | ------------------------------------- | ------------------------------------------------------------ |
124 | | / | blog/content/index.html.twig |
125 | | /feed.rss | blog/content/feed.rss.twig |
126 | | /about-me.html | blog/content/about-me/index.html.twig |
127 | | /category/post.html | blog/content/category/post/index.html.twig |
128 | | /category/subcategory/long-title.html | blog/content/category/subcategory/long-title/index.html.twig |
129 |
130 | Why not have the '**/about-me.html**' URL file at '**content/about-me.html.twig**' instead of '**content/about-me/index.html.twig**' instead, right? This is so you can have all of the assets that you want to use, right there where you want to use them. Linking to them is even easier. Place an '**image.jpg**' in the '**content/about-me/**' folder, and link to ``{{ 'image.jpg'|asset }}`` in the '**index.html.twig**' file. Would you like to resize that? Try an ``{{ 'image.jpg?w=300'|asset }}``. To see all the options, check out the [Quick Reference "Glide"](http://glide.thephpleague.com/1.0/api/quick-reference/).
131 |
132 | Non-HTML files are accessed according to the '**/feed.rss**' URL example above.
133 |
134 | ## Twig Templates
135 |
136 | Every ``index.html.twig`` file is a Twig template that receives the [BootPress Page Component](https://packagist.org/packages/bootpress/page), so that you can interact with your HTML Page. The methods available to you are:
137 |
138 | - ``{{ page.set() }}`` - Set HTML Page properties. Things like the title, keywords (tags), author, etc.
139 | - ``{{ page.url() }} `` - Either create a url, or manipulate it's query string and fragment.
140 | - ``{{ page.get() }} `` - Access $_GET parameters.
141 | - ``{{ page.post() }}`` - Access $_POST parameters.
142 | - ``{{ page.tag() }} `` - Generate an HTML tag programatically.
143 | - ``{{ page.meta() }} `` - Insert ```` tag(s) into the ```` section of your page.
144 | - ``{{ page.link() }} `` - Include js, css, ico, etc links in your page.
145 | - ``{{ page.style() }} `` - Add CSS ``