Skip to content

Commit

Permalink
add Twig filtering and custom loop, add css classes
Browse files Browse the repository at this point in the history
  • Loading branch information
nliautaud committed Sep 18, 2017
1 parent a3fa9f2 commit 2eea787
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 105 deletions.
130 changes: 61 additions & 69 deletions PicoPagesList.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?php

/**
* A nested pages list plugin for Pico CMS.
*
Expand All @@ -10,30 +9,12 @@
*/
class PicoPagesList extends AbstractPicoPlugin
{
private $pages;
private $pages_urls;
private $current_url;
public $items;
private $base_url;
private $hide_list;

/**
* Register Pico base url and hide_list config.
*
* Triggered after Pico has read its configuration
*
* @see Pico::getConfig()
* @param array &$config array of config variables
* @return void
*/
public function onConfigLoaded(array &$config)
{
$this->base_url = rtrim($config['base_url'], '/') . '/';
$this->hide_list = array_map('trim', explode(',', $config['hide_pages']));
}
private $current_url;

/**
* Store existing Pico pages urls, the current url
* and construct the nested pages array.
* Store the current url and construct the nested pages array.
*
* Triggered after Pico has read all known pages
*
Expand All @@ -56,17 +37,12 @@ public function onPagesLoaded(
array &$previousPage = null,
array &$nextPage = null
) {
$this->pages_urls = array();
foreach ($pages as $p) {
$this->pages_urls[] = $p['url'];
}
$this->pages = array();
$this->current_url = $currentPage['url'];
$this->construct_pages($pages);
}

/**
* Register the html output in the Twig {{ pages_list }} variable.
* Register `$this` in the Twig `{{ PagesList }}` variable.
*
* Triggered before Pico renders the page
*
Expand All @@ -79,52 +55,46 @@ public function onPagesLoaded(
*/
public function onPageRendering(Twig_Environment &$twig, array &$twigVariables, &$templateName)
{
$twigVariables['pages_list'] = $this->output($this->pages);
$twigVariables['PagesList'] = $this;
$twigVariables['pages_list'] = $this->html(); // backward compatibility
}


// CORE ---------------

/**
* Create a nested array of the pages, according to their paths.
* Merge all individual pages *nested_path*.
* Create the nested pages array according to the pages paths.
*
* @see nested_path
* @param array $pages Pico pages flat array
*/
private function construct_pages($pages)
{
foreach ($pages as $page)
{
$page['path'] = rtrim(str_replace($this->base_url,'',$page['url']), '/');
$this->base_url = $this->getConfig('base_url');
$this->items = array();
foreach ($pages as $page) {
$nested_path = $this->nested_path($page);
$this->pages = array_merge_recursive($this->pages, $nested_path);
$this->items = array_merge_recursive($this->items, $nested_path);
}
}

/**
* Create a nested path of a given path, with page infos at the end.
* Each path fragment is a "_child" of its parent fragment.
* Create a nested array of a given path, with the page at the end.
* Each path fragment is in "_childs" of the parent.
*
* @param array $page the corresponding page data, with 'path' key.
* @return array the nested path
* @param array $page the page array
* @param array $base_url the base url, substracted from the page url
* @return array the nested path relative to $base_url
*/
private function nested_path($page)
{
$parts = explode('/', $page['path']);
$path = rtrim(str_replace($this->base_url, '', $page['url']), '/');
$parts = explode('/', $path);
$count = count($parts);

$arr = array();
$parent = &$arr;
foreach($parts as $id => $part) {
$value = array();
if(!$part || $id == $count-1) {
$value = array(
'url'=>$page['url'],
'path'=>$page['path'],
'title'=>$page['title'],
'hide'=>$page['hide']
);
$value = $page;
}
if(!$part) {
$parent = $value;
Expand All @@ -137,35 +107,57 @@ private function nested_path($page)
}

/**
* Create an html list based on the nested pages array.
* Render a nested html list of pages.
*
* @param array $pages a nested pages array
* @return string the html list
* @param array|string $paths array or comma-separated list of pages paths to filter
* @param boolean $exclude if true render all but the given paths
* @return string the html list
*/
private function output($pages)
public function html($paths = array(), $exclude = false)
{
if(!isset($pages['_childs'])) return '';
if (!is_array($paths)) $paths = explode(',', $paths);
return $this->output($this->items, $paths, $exclude);
}

/**
* Return an html nested list based on a nested pages array.
*
* @param array $pages a nested pages array
* @param array $paths_filters array of paths to keep or skip
* @param string $currentPath the current walked path
* @return string the html list
*/
private function output($pages, $paths_filters, $isFilterExcluding, $currentPath = '')
{
if(!$pages['_childs']) return;
$html = '<ul>';
foreach ($pages['_childs'] as $key => $page)
foreach ($pages['_childs'] as $pageID => $page)
{
if($this->is_hidden($page['path'])) continue;
$childPath = $currentPath ? $currentPath.'/'.$pageID : $pageID;

$url = $page['url'];
$filename = basename($url);
$childs = $this->output($page);
$is_filtered = $this->isConcerned($childPath, $paths_filters);
if($is_filtered && $isFilterExcluding) continue;
if($paths_filters && !$is_filtered && !$isFilterExcluding) continue;

$childs = $this->output($page, $paths_filters, $childPath);

$url = isset($page['url']) ? $page['url'] : false;

// use title if the page have one, and make a link if the page exists.
$item = !empty($page['title']) ? $page['title'] : ($filename ? $filename : $key);
if($url && in_array($url, $this->pages_urls))
$item = '<a href="'.$url.'">'.$item.'</a>';
if(!$url) $item = "<span>$pageID</span>";
else {
$name = !empty($page['title']) ? $page['title'] : $pageID;
$item = "<a href=\"$url\">$name</a>";
}

// add the filename in class, and indicates if is current or parent
$class = $filename;
if($this->current_url == $url) $class .= ' is-current';
elseif(strpos($this->current_url, $url) === 0) $class .= ' is-parent';
// add the pageID in class, and indicates if is current or parent of current
$class = $pageID;
$class .= $url ? ' is-page' : ' is-directory';
if ($childs) $class .= ' has-childs';
if ($this->current_url == $url) $class .= ' is-current';
if (strpos($this->current_url, $this->base_url . $childPath) === 0) $class .= ' is-active';

$html .= '<li class="'.$class.'">' . $item . $childs . '</li>';
$html .= "<li class=\"$class\">$item$childs</li>";
}
$html .= '</ul>';
return $html;
Expand All @@ -177,9 +169,9 @@ private function output($pages)
* @param string $path the page short path
* @return boolean
*/
private function is_hidden($path)
private static function isConcerned($path, $excluded_paths)
{
foreach($this->hide_list as $p)
foreach($excluded_paths as $p)
{
if( !$p ) continue;
if( $path == $p ) return true;
Expand Down
119 changes: 83 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Copy `PicoPagesList.php` to the `plugins` directory of your Pico Project.

## Usage

Add a generated nested pages list in your theme by using the following Twig variable :
Add a nested navigation in your theme by using the following Twig command :

```twig
{{ pages_list }}
{{ PagesList.html }}
```

You'll automatically get something like :
Expand All @@ -23,69 +23,116 @@ You'll automatically get something like :
* [A page]()
* [untitled]()

Under the hood :
### Styling

The default html output is a clean nested list with extra classes that provides the possibility to build hierarchical navigations and to target specific pages and directories.

```html
<ul>
<li class="titled">
<li class="titled is-page">
<a href="http://mysite.com/titled">A cool page</a>
</li>
<li class="foo-page is-parent">
<a href="http://mysite.com/foo-page">Sub-page is coming</a>
<li class="foo is-page has-childs is-current">
<a href="http://mysite.com/foo">Sub-page is coming</a>
<ul>
<li class="child is-current">
<a href="http://mysite.com/foo-page/child">The choosen one</a>
<li class="child is-page has-childs is-current is-active">
<a href="http://mysite.com/foo/child">The choosen one</a>
</li>
<li class="category">
category
<li class="category is-directory has-childs">
<span>category</span>
<ul>
<li class="bar">
<a href="http://mysite.com/category/bar">A page</a>
<li class="bar is-page">
<a href="http://mysite.com/foo/category/bar">A page</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="untitled">
<li class="untitled is-page">
<a href="http://mysite.com/untitled">untitled</a>
</li>
</ul>
```

## Features

The plugin generate a clean nested html list, using links only if the page exists. The page title is used if possible.
```css
.foo-item { /* an item named "foo-item" */ }
.foo-item > a { /* the link of a page named "foo-item" */ }
.foo-item > span { /* the name of a directory named "foo-item" */ }
.foo-item > ul { /* the childs of "foo-item" */ }
.foo-item > ul ul { /* the deep childs of "foo-item" */ }

.is-page { /* the pages, with links */ }
.is-directory { /* the directories, with simple names */ }
.is-current { /* the current page */ }
.is-active { /* the items in the path of the current page */ }
.has-childs { /* the items with childs */ }
```

The lists items are defined by css classes allowing per-page or general manipulations :
As a simple example, you may show sub-pages only if their parent is active :

```css
#nav .foo-page a {
/* access to a specific page link */
}
#nav .foo-page .child a {
/* access to a specific foo-page/child link */
}
#nav .is-current {
/* access to the current page item */
}
#nav .is-parent {
/* access to every parent item of the current one */
.mymenu li.is-page:not(.is-active) ul {
display: none;
}
```

## Settings
### Filtering output

The settings are defined in the configuration file of Pico `config.php`.
You can target or exclude specific paths from the output with `PagesList.html()` parameters.

To sort the pages list, use the default settings :
```twig
{{ PagesList.html(paths, exclude) }}
```php
$config['pages_order_by'] = 'date';
$config['pages_order'] = 'desc';
{{ PagesList.html }} // all
{{ PagesList.html('foo/bar') }} // only foo/bar childs
{{ PagesList.html('foo/bar', true) }} // all except foo/bar childs
```

Exclude specific pages or directories with the setting `hide_pages`, by indicating pages or directory pathes separated by commas. Childs of a path will be excluded with their parent.
You can specify multiple paths by using an array or a comma-separated string.

```twig
// filter multiple paths
{{ PagesList.html('foo/bar,other') }}
{{ PagesList.html(['foo/bar', 'other']) }}
```

## Custom loop

You can access the items within `PagesList.items`.

Every item may contain child entries in `_childs`, so you may want a recursive Twig template or macro to walk trough it.

```twig
{% macro menu(item) %}
{% import _self as macros %}
{% for name,child in item._childs %}
<li>
{% if child.url %}
<a href="{{ child.url }}">{{ child.title }}</a>
{% else %}
<span>{{ name }}</span>
{% endif %}
{% if child._childs %}
<ul>
{{ macros.menu(child) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macros %}
<ul class="main-menu">
{{ macros.menu(PagesList.items) }}
</ul>
```

## Settings

The lists are sorted according to the default settings in Pico `config.php`.

```php
$config['hide_pages'] = 'this/page,all/in/here/';
$config['pages_order_by'] = 'date';
$config['pages_order'] = 'desc';
```

0 comments on commit 2eea787

Please sign in to comment.