* @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 * @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 = '/]*>\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('
', $content); if (count($breaks) > 0) { $this->breakContent($page, $config, $breaks); } else { echo '
'; echo $content; if (isset($page['footer'])) { echo $page['footer']; } echo '
'; } 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 '
'; } $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 '
'; } $this->buildSlide($page, $config, $break); if ($horizontal == true) { echo '
'; } } $index++; } if ($horizontal != true) { echo '
'; } } /** * 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 '
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 '
'; } /** * 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, '\\2', $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]', '', $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; } }