doc/dev/plugins/presentation/classes/Content.php

380 lines
13 KiB
PHP

<?php
/**
* Presentation Plugin, Content API
*
* PHP version 7
*
* @category API
* @package Grav\Plugin\PresentationPlugin
* @subpackage Grav\Plugin\PresentationPlugin\API
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-presentation
*/
namespace Grav\Plugin\PresentationPlugin\API;
use Grav\Common\Utils;
use Michelf\SmartyPants;
/**
* Content API
*
* Content API for aggregating content
*
* @category Extensions
* @package Grav\Plugin\PresentationPlugin\API
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-presentation
*/
class Content implements ContentInterface
{
/**
* Regular expressions
*/
const REGEX_FRAGMENT_SHORTCODE = '~\[fragment=*"*([a-zA-Z-]*)"*\](.*)"*\[\/fragment\]~im';
const REGEX_P_EMPTY = '/<p[^>]*>\n* *<\/p[^>]*>/im';
/**
* Instantiate Content API
*
* @param Grav $grav Grav-instance
* @param array $config Plugin configuration
* @param Parser $parser Parser utility
* @param Transport $transport Transport API
*/
public function __construct($grav, $config, $parser, $transport)
{
$this->grav = $grav;
$this->config = $config;
$this->parser = $parser;
$this->transport = $transport;
if ($this->grav['config']->get('system.pages.markdown.extra')) {
$this->parsedown = new \ParsedownExtra();
} else {
$this->parsedown = new \Parsedown();
}
}
/**
* Creates page-structure recursively
*
* @param string $route Route to page
* @param string $mode Reserved collection-mode for handling child-pages
* @param integer $depth Reserved placeholder for recursion depth
*
* @return array Page-structure with children
*/
public function buildTree(string $route, $mode = false, $depth = 0)
{
$page = $this->grav['page'];
$depth++;
$mode = '@page.self';
$config = $this->config;
if (isset($page->header()->Reveal)) {
$config = array_merge($config, $page->header()->Reveal);
}
if ($depth > 1) {
$mode = '@page.children';
}
$pages = $page->evaluate([$mode => $route]);
$pages = $pages->published()->order(
$config['order']['by'],
$config['order']['dir']
);
$paths = array();
foreach ($pages as $page) {
$route = $page->rawRoute();
$paths[$route]['depth'] = $depth;
$paths[$route]['title'] = $page->title();
$paths[$route]['menu'] = array(
'anchor' => $page->slug(),
'title' => $page->title()
);
$paths[$route]['route'] = $route;
$paths[$route]['slug'] = $page->slug();
$paths[$route]['path'] = $page->path();
$paths[$route]['header'] = $page->header();
if (isset($page->header()->type)) {
$paths[$route]['type'] = $page->header()->type;
}
if (isset($config['footer'])) {
$paths[$route]['footer'] = $config['footer'];
}
if (isset($page->header()->footer)) {
$paths[$route]['footer'] = $page->header()->footer;
}
if (!empty($paths[$route]['footer'])) {
$paths[$route]['footer'] = $this->grav['twig']->processTemplate(
$paths[$route]['footer'],
['page' => $page]
);
}
if (isset($this->config['process']) && $this->config['process'] == "markdown") {
$paths[$route]['content'] = $page->rawMarkdown();
} else {
$paths[$route]['content'] = $page->content();
}
if (!empty($paths[$route])) {
$children = $this->buildTree($route, $mode, $depth);
if (!empty($children)) {
$paths[$route]['children'] = $children;
}
}
}
if (isset($config['styles'])
&& is_array($config['styles'])
&& !empty($config['styles'])
) {
foreach ($config['styles'] as $property => $value) {
$this->parser->styleProcessor($page->slug(), $property, $value);
}
}
if (!empty($paths)) {
return $paths;
} else {
return null;
}
}
/**
* Create HTML to use with Reveal.js
*
* @param array $pages Page-structure with children
*
* @return string HTML-structure
*/
public function buildContent(array $pages)
{
include_once __DIR__ . '/../vendor/autoload.php';
$return = '';
foreach ($pages as $route => $page) {
ob_start();
$styleIndex = 0;
$styles = array();
$config = $this->config;
$config['route'] = $route;
$content = $page['content'];
if (preg_match(self::REGEX_FRAGMENT_SHORTCODE, $content)) {
$content = $this->processFragments($content);
}
if (isset($this->config['process']) && $this->config['process'] == "markdown") {
$content = $this->parsedown->text($content);
}
$breaks = explode('<hr />', $content);
if (count($breaks) > 0) {
$this->breakContent($page, $config, $breaks);
} else {
echo '<section>';
echo $content;
if (isset($page['footer'])) {
echo $page['footer'];
}
echo '</section>';
}
if (isset($page['header']->Reveal)) {
$config = array_merge($config, $page['header']->Reveal);
}
$return .= ob_get_contents();
ob_end_clean();
if (isset($page['children'])) {
$return .= $this->buildContent($page['children']);
}
}
return $return;
}
/**
* Create HTML from content
*
* @param array $page Grav Page-instance
* @param array $config Plugin- and slide-configuration
* @param array $breaks Slides from Markdown
*
* @return void
*/
public function breakContent(array $page, array $config, array $breaks)
{
$header = (array) $page['header'];
$horizontal = false;
if (isset($config['horizontal'])) {
$horizontal = $config['horizontal'];
}
if (isset($header['horizontal'])) {
$horizontal = $header['horizontal'];
}
if ($horizontal != true) {
echo '<section id="' . $page['slug'] . '" ';
echo 'data-title="' . $page['title'] . '">';
}
$index = 0;
foreach ($breaks as $break) {
$config['id'] = $page['slug'] . '-' . $index;
$config['class'] = '';
$hide = false;
$config['styles'] = array();
if ($this->config['unwrap_images']) {
$break = $this->parser::unwrapImage($break);
}
if (isset($page['header']->style)) {
$config['styles'] = array_merge(
$config['styles'],
$page['header']->style
);
}
if (isset($config['textsizing'])) {
if (isset($config['textsize']['scale'])) {
$config['class'] .= ' textsizing';
$scale = $config['textsize']['scale'];
}
if (isset($page['header']->textsize['scale'])) {
$config['class'] .= ' textsizing';
$scale = $page['header']->textsize['scale'];
}
if (isset($config['textsize']['modifier'])) {
$modifier = (float) $config['textsize']['modifier'];
}
if (isset($page['header']->textsize['modifier'])) {
$modifier = (float) $page['header']->textsize['modifier'];
}
if (!empty($scale) && !empty($modifier)) {
$this->parser->setModularScale(
$config['id'],
$scale ?? 1.25,
$modifier ?? 1
);
}
}
if (isset($page['header']->class) && !empty($page['header']->class)) {
foreach ($page['header']->class as $item) {
$config['class'] .= ' ' . $item;
}
}
if ($hide !== true) {
if ($horizontal == true) {
echo '<section>';
}
$this->buildSlide($page, $config, $break);
if ($horizontal == true) {
echo '</section>';
}
}
$index++;
}
if ($horizontal != true) {
echo '</section>';
}
}
/**
* Create HTML for slides
*
* @param array $page Grav Page-instance
* @param array $config Plugin- and slide-configuration
* @param string $break Slides from Markdown
*
* @return void
*/
public function buildSlide(array $page, array $config, string $break)
{
$styles = $page['header']->style ??
$page['header']->styles ??
$page['header']->presentation['style'] ??
$page['header']->presentation['styles'] ??
[];
if (!empty($styles) && is_array($styles) && Utils::arrayIsAssociative($styles)) {
foreach ($styles as $property => $value) {
$this->parser->styleProcessor($config['id'], $property, $value, $page);
}
}
if ($config['shortcodes']) {
$break = self::pushNotes($break);
$shortcodes = $this->parser->processShortcodes($break, $config['id'], $page);
$break = $shortcodes['content'];
$break = SmartyPants::defaultTransform($break);
if ($this->transport->getDataAttribute($config['id'], 'data-hide')) {
$hide = true;
}
}
echo '<section id="' . $config['id'] . '" ';
if ($this->transport->getClasses($config['id'])) {
echo 'class="' . $this->transport->getClasses($config['id']) . '" ';
}
echo 'data-title="' . $page['title'] . '"';
if (isset($page['header']->textsize['scale'])) {
echo ' data-textsize-scale="' . (float) $page['header']->textsize['scale'] . '"';
if (isset($page['header']->textsize['modifier'])) {
echo ' data-textsize-modifier="' . (float) $page['header']->textsize['modifier'] . '"';
}
}
if (!empty($this->transport->getAriaAttributes($config['id']))) {
echo $this->transport->getAriaAttributes($config['id']);
}
if (!empty($this->transport->getDataAttributes($config['id']))) {
echo $this->transport->getDataAttributes($config['id']);
}
echo '>';
$break = preg_replace(self::REGEX_P_EMPTY, '', $break);
echo $break;
if (isset($page['footer'])) {
echo $page['footer'];
}
echo '</section>';
}
/**
* Create HTML for fragments
*
* @param string $content Markdown content in Page
*
* @return string Processed contents
*/
public function processFragments(string $content)
{
$content = preg_replace(
self::REGEX_FRAGMENT_SHORTCODE,
'<span class="fragment \\1">\\2</span>',
$content
);
return $content;
}
/**
* Create HTML from Notes-shortcodes
*
* @param string $content Markdown content in Page
*
* @return string Processed content
*/
public function pushNotes(string $content)
{
$content = str_replace('[notes]', '<aside class="notes">', $content);
$content = str_replace('[/notes]', '</aside>', $content);
return $content;
}
/**
* Generate menu with anchors and titles from pages
*
* @param array $tree Page-structure with children
*
* @return array Slide-anchors with titles
*/
public function buildMenu(array $tree)
{
$items = array();
foreach ($tree as $key => $value) {
if (is_array($value['menu'])) {
$items[$value['menu']['anchor']] = $value['menu']['title'];
}
if (isset($value['children'])) {
$items[] = $this->buildMenu($value['children']);
}
}
return $items;
}
}