1<?php 2/** 3 * DokuWiki Syntax Plugin Web Component. 4 * 5 */ 6if (!defined('DOKU_INC')) { 7 die(); 8} 9 10if (!defined('DOKU_PLUGIN')) { 11 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 12} 13 14require_once(__DIR__ . '/../webcomponent.php'); 15 16/** 17 * All DokuWiki plugins to extend the parser/rendering mechanism 18 * need to inherit from this class 19 * 20 * The name of the class must follow a pattern (don't change it) 21 * ie: 22 * syntax_plugin_PluginName_ComponentName 23 */ 24class syntax_plugin_webcomponent_card extends DokuWiki_Syntax_Plugin 25{ 26 27 // Pattern that we expect in a card (teaser) 28 const HEADER_PATTERN = '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)'; 29 const IMAGE_PATTERN = "\{\{(?:[^\}]|(?:\}[^\}]))+\}\}"; 30 31 32 // The elements of a teaser 33 // because they are assembled at the end 34 private $startElement; 35 private $text; 36 private $header; 37 private $image; 38 39 40 /** 41 * Syntax Type. 42 * 43 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 44 * @see DokuWiki_Syntax_Plugin::getType() 45 */ 46 function getType() 47 { 48 return 'protected'; 49 } 50 51 /** 52 * @return array 53 * Allow which kind of plugin inside 54 * 55 * No one of array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 56 * because we manage self the content and we call self the parser 57 */ 58 public function getAllowedTypes() 59 { 60 return array(); 61 } 62 63 /** 64 * How Dokuwiki will add P element 65 * 66 * * 'normal' - The plugin can be used inside paragraphs 67 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 68 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 69 * 70 * @see DokuWiki_Syntax_Plugin::getPType() 71 */ 72 function getPType() 73 { 74 return 'block'; 75 } 76 77 /** 78 * @see Doku_Parser_Mode::getSort() 79 * Higher number than the teaser-columns 80 * because the mode with the lowest sort number will win out 81 */ 82 function getSort() 83 { 84 return 200; 85 } 86 87 /** 88 * Create a pattern that will called this plugin 89 * 90 * @see Doku_Parser_Mode::connectTo() 91 * @param string $mode 92 */ 93 function connectTo($mode) 94 { 95 96 foreach (self::getTags() as $tag) { 97 98 $pattern = webcomponent::getLookAheadPattern($tag); 99 $this->Lexer->addEntryPattern($pattern, $mode, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent()); 100 } 101 102 103 104 } 105 106 public function postConnect() 107 { 108 109 foreach (self::getTags() as $tag) { 110 $this->Lexer->addExitPattern('</' . $tag . '>', 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent()); 111 } 112 113 // Header 114 $this->Lexer->addPattern(self::HEADER_PATTERN, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent()); 115 116 // Image 117 $this->Lexer->addPattern(self::IMAGE_PATTERN, 'plugin_' . webcomponent::PLUGIN_NAME . '_' . $this->getPluginComponent()); 118 119 } 120 121 /** 122 * 123 * The handle function goal is to parse the matched syntax through the pattern function 124 * and to return the result for use in the renderer 125 * This result is always cached until the page is modified. 126 * @see DokuWiki_Syntax_Plugin::handle() 127 * 128 * @param string $match 129 * @param int $state 130 * @param int $pos 131 * @param Doku_Handler $handler 132 * @return array|bool 133 */ 134 function handle($match, $state, $pos, Doku_Handler $handler) 135 { 136 137 switch ($state) { 138 139 case DOKU_LEXER_ENTER: 140 141 // Suppress the component name 142 // Suppress the <> 143 $match = utf8_substr($match, 1, -1); 144 // Suppress the tag name 145 foreach (self::getTags() as $tag) { 146 $match = str_replace( $tag, "",$match); 147 } 148 $parameters = webcomponent::parseMatch($match); 149 return array($state, $parameters); 150 151 case DOKU_LEXER_UNMATCHED : 152 153 return array($state, $match); 154 155 case DOKU_LEXER_MATCHED : 156 157 $parameters = array(); 158 if (preg_match('/' . self::HEADER_PATTERN . '/msSi', $match . DOKU_LF)) { 159 // We have a header 160 $title = trim($match); 161 $level = 7 - strspn($title, '='); 162 if ($level < 1) $level = 1; 163 $title = trim($title, '='); 164 $title = trim($title); 165 $parameters['header']['title'] = $title; 166 $parameters['header']['level'] = $level; 167 } 168 169 if (preg_match('/' . self::IMAGE_PATTERN . '/msSi', $match . DOKU_LF)) { 170 // We have an image, we parse it (Doku_Handler_Parse_Media in handler.php) 171 $parameters['image'] = Doku_Handler_Parse_Media($match); 172 } 173 174 return array($state, $parameters); 175 176 case DOKU_LEXER_EXIT : 177 178 return array($state, ''); 179 180 181 } 182 183 return array(); 184 185 } 186 187 /** 188 * Render the output 189 * @see DokuWiki_Syntax_Plugin::render() 190 * 191 * @param string $mode 192 * @param Doku_Renderer $renderer 193 * @param array $data - what the function handle() return'ed 194 * @return bool 195 */ 196 function render($mode, Doku_Renderer $renderer, $data) 197 { 198 199 if ($mode == 'xhtml') { 200 201 /** @var Doku_Renderer_xhtml $renderer */ 202 list($state, $parameters) = $data; 203 switch ($state) { 204 205 case DOKU_LEXER_ENTER : 206 $this->startElement .= '<div class="card"'; 207 foreach ($parameters as $key => $value) { 208 $this->startElement .= ' ' . $key . '="' . $value . '"'; 209 } 210 $this->startElement .= '>'; 211 break; 212 213 case DOKU_LEXER_UNMATCHED : 214 $instructions = p_get_instructions($parameters); 215 $lastPBlockPosition = sizeof($instructions) - 2; 216 if ($instructions[1][0] == 'p_open') { 217 unset($instructions[1]); 218 } 219 if ($instructions[$lastPBlockPosition][0] == 'p_close') { 220 unset($instructions[$lastPBlockPosition]); 221 } 222 $this->text .= p_render('xhtml', $instructions, $info); 223 break; 224 225 case DOKU_LEXER_MATCHED: 226 227 if (array_key_exists('header', $parameters)) { 228 $title = $parameters['header']['title']; 229 $level = $parameters['header']['level']; 230 $this->header .= '<h' . $level . ' class="card-title">'; 231 $this->header .= $renderer->_xmlEntities($title); 232 $this->header .= "</h$level>"; 233 } 234 235 if (array_key_exists('image', $parameters)) { 236 237 $this->image = $parameters['image']; 238 239 } 240 break; 241 242 case DOKU_LEXER_EXIT : 243 244 $renderer->doc .= $this->startElement . DOKU_LF; 245 246 if ($this->header == "" and $this->text == "" and $this->image != ""){ 247 // An image card without any content 248 $src = $this->image['src']; 249 $width = $this->image['width']; 250 $height = $this->image['height']; 251 $title = $this->image['title']; 252 //Snippet taken from $renderer->doc .= $renderer->internalmedia($src, $linking = 'nolink'); 253 $renderer->doc .= '<img class="card-img" src="' . ml($src, array('w' => $width, 'h' => $height, 'cache' => true)) . '" alt="' . $title . '" width="' . $width . '">'; 254 } else { 255 // A real teaser 256 if ($this->image != "") { 257 $src = $this->image['src']; 258 $width = $this->image['width']; 259 $height = $this->image['height']; 260 $title = $this->image['title']; 261 //Snippet taken from $renderer->doc .= $renderer->internalmedia($src, $linking = 'nolink'); 262 $renderer->doc .= DOKU_TAB . '<img class="card-img-top" src="' . ml($src, array('w' => $width, 'h' => $height, 'cache' => true)) . '" alt="' . $title . '" width="' . $width . '">' .DOKU_LF; 263 } 264 $renderer->doc .= DOKU_TAB . '<div class="card-body">' . DOKU_LF; 265 if ($this->header != "") { 266 $renderer->doc .= DOKU_TAB . DOKU_TAB . $this->header . DOKU_LF; 267 } 268 if ($this->text != "") { 269 $renderer->doc .= DOKU_TAB . DOKU_TAB . '<p class="card-text">' . $this->text . '</p>' . DOKU_LF; 270 } 271 $renderer->doc .= DOKU_TAB . '</div>' . DOKU_LF; 272 } 273 274 $renderer->doc .= "</div>" . DOKU_LF; 275 276 $this->startElement = ""; 277 $this->image = ""; 278 $this->header = ""; 279 $this->text = ""; 280 break; 281 } 282 return true; 283 } 284 return false; 285 } 286 287 288 public static function getTag() 289 { 290 list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_called_class(), 4); 291 return (isset($c) ? $c : ''); 292 } 293 294 public 295 static function getTags() 296 { 297 $elements[] = webcomponent::getTagName(get_called_class()); 298 $elements[] = 'teaser'; 299 return $elements; 300 } 301 302 303} 304