1<?php 2/** 3 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15 16use ComboStrap\Web\Url; 17 18/** 19 * @package ComboStrap 20 * 21 * Public interface of {@link Snippet} 22 * 23 * All plugin/component should use the attach functions to add a internal or external 24 * stylesheet/javascript to a slot or request scoped 25 * 26 * Note: 27 * All function with the suffix 28 * * `ForSlot` are snippets for a bar (ie page, sidebar, ...) - cached 29 * * `ForRequests` are snippets added for the HTTP request - not cached. Example of request component: message, anchor 30 * 31 * 32 * Minification: 33 * Wrapper: https://packagist.org/packages/jalle19/php-yui-compressor 34 * Require Yui compressor: https://packagist.org/packages/nervo/yuicompressor 35 * sudo apt-get install default-jre 36 * 37 */ 38class SnippetSystem 39{ 40 41 42 const CANONICAL = "snippet-system"; 43 44 45 /** 46 * @return SnippetSystem - the global reference 47 * that is set for every run at the end of this file 48 * TODO: migrate the attach function to {@link Snippet} 49 * because Snippet has already a global variable {@link Snippet::getOrCreateFromComponentId()} 50 */ 51 public static function getFromContext(): SnippetSystem 52 { 53 54 $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 55 try { 56 return $executionContext->getRuntimeObject(self::CANONICAL); 57 } catch (ExceptionNotFound $e) { 58 $snippetSystem = new SnippetSystem(); 59 $executionContext->setRuntimeObject(self::CANONICAL, $snippetSystem); 60 return $snippetSystem; 61 } 62 63 } 64 65 /** 66 * @param Snippet[] $snippets 67 * @return string 68 */ 69 public static function toHtmlFromSnippetArray(array $snippets): string 70 { 71 $xhtmlContent = ""; 72 foreach ($snippets as $snippet) { 73 74 if ($snippet->hasHtmlOutputAlreadyOccurred()) { 75 continue; 76 } 77 78 $xhtmlContent .= $snippet->toXhtml(); 79 80 81 } 82 return $xhtmlContent; 83 } 84 85 86 /** 87 * Returns all snippets (request and slot scoped) 88 * 89 * @return Snippet[] of node type and an array of array of html attributes 90 */ 91 public function getAllSnippets(): array 92 { 93 return Snippet::getSnippets(); 94 } 95 96 /** 97 * @return Snippet[] - the slot snippets (not the request snippet) 98 */ 99 private function getSlotSnippets(): array 100 { 101 $snippets = Snippet::getSnippets(); 102 $slotSnippets = []; 103 foreach ($snippets as $snippet) { 104 if ($snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 105 continue; 106 } 107 $slotSnippets[] = $snippet; 108 } 109 return $slotSnippets; 110 } 111 112 113 public static 114 function toJsonArrayFromSlotSnippets($snippetsForSlot): array 115 { 116 117 $jsonSnippets = []; 118 foreach ($snippetsForSlot as $snippet) { 119 $jsonSnippets[] = $snippet->toJsonArray(); 120 } 121 return $jsonSnippets; 122 123 } 124 125 /** 126 * @param array $array 127 * @param string $slot 128 * @return null|Snippet[] 129 * @throws ExceptionCompile 130 */ 131 public 132 function getSlotSnippetsFromJsonArray(array $array, string $slot): ?array 133 { 134 $snippets = null; 135 foreach ($array as $element) { 136 $snippets[] = Snippet::createFromJson($element) 137 ->addElement($slot); 138 } 139 return $snippets; 140 } 141 142 143 /** 144 * @param $componentId 145 * @param string|null $script - the css snippet to add, otherwise it takes the file 146 * @return Snippet a snippet not in a slot 147 * 148 * If you need to split the css by type of action, see {@link \action_plugin_combo_docss::handleCssForDoAction()} 149 */ 150 public 151 function &attachCssInternalStyleSheet($componentId, string $script = null): Snippet 152 { 153 $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_CSS); 154 if ($script !== null) { 155 $snippet->setInlineContent($script); 156 } 157 return $snippet; 158 } 159 160 161 /** 162 * @param $componentId 163 * @param string|null $script 164 * @return Snippet a snippet in a slot 165 */ 166 public function attachJavascriptFromComponentId($componentId, string $script = null): Snippet 167 { 168 $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_JS); 169 if ($script !== null) { 170 try { 171 $content = "{$snippet->getInternalDynamicContent()} $script"; 172 } catch (ExceptionNotFound $e) { 173 $content = $script; 174 } 175 $snippet->setInlineContent($content); 176 } 177 return $snippet; 178 } 179 180 181 public 182 function attachInternalJavascriptFromPathForRequest($componentId, Path $path): Snippet 183 { 184 return Snippet::getOrCreateFromContext($path) 185 ->addElement(Snippet::REQUEST_SCOPE) 186 ->setComponentId($componentId); 187 } 188 189 190 /** 191 * @param $componentId 192 * @return Snippet[] 193 */ 194 public function getSnippetsForComponent($componentId): array 195 { 196 $snippets = []; 197 foreach ($this->getSnippets() as $snippet) { 198 try { 199 if ($snippet->getComponentId() === $componentId) { 200 $snippets[] = $snippet; 201 } 202 } catch (ExceptionNotFound $e) { 203 // 204 } 205 } 206 return $snippets; 207 } 208 209 /** 210 * Utility function used in test 211 * or to show how to test if snippets are present 212 * @param $componentId 213 * @return bool 214 */ 215 public function hasSnippetsForComponent($componentId): bool 216 { 217 return count($this->getSnippetsForComponent($componentId)) > 0; 218 } 219 220 /** 221 * @param $componentId 222 * @param $type 223 * @return Snippet 224 * @deprecated - the slot is now added automatically at creation time via the context system 225 */ 226 private 227 function attachSnippetFromRequest($componentId, $type): Snippet 228 { 229 return Snippet::getOrCreateFromComponentId($componentId, $type) 230 ->addElement(Snippet::REQUEST_SCOPE); 231 } 232 233 234 /** 235 * @param string $snippetId 236 * @param string $pathFromComboDrive 237 * @param string|null $integrity 238 * @return Snippet 239 */ 240 public 241 function attachJavascriptComboResourceForSlot(string $snippetId, string $pathFromComboDrive, string $integrity = null): Snippet 242 { 243 244 $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 245 return Snippet::getOrCreateFromContext($dokuPath) 246 ->setComponentId($snippetId) 247 ->setIntegrity($integrity); 248 249 } 250 251 /** 252 * Add a local javascript script as tag 253 * (ie same as {@link SnippetSystem::attachRemoteJavascriptLibrary()}) 254 * but for local resource combo file (library) 255 * 256 * For instance: 257 * * library:combo:combo.js 258 * * for a file located at dokuwiki_home\lib\plugins\combo\resources\library\combo\combo.js 259 * @return Snippet 260 */ 261 public 262 function attachJavascriptComboLibrary(): Snippet 263 { 264 265 $wikiPath = ":library:combo:combo.min.js"; 266 $componentId = "combo"; 267 return $this->attachSnippetFromComboResourceDrive($wikiPath, $componentId); 268 269 } 270 271 public function attachSnippetFromComboResourceDrive(string $pathFromComboDrive, string $componentId): Snippet 272 { 273 274 $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 275 return Snippet::getOrCreateFromContext($dokuPath) 276 ->setComponentId($componentId); 277 278 } 279 280 /** 281 * @throws ExceptionBadSyntax 282 * @throws ExceptionBadArgument 283 */ 284 public 285 function attachRemoteJavascriptLibrary(string $componentId, string $url, string $integrity = null): Snippet 286 { 287 $url = Url::createFromString($url); 288 return Snippet::getOrCreateFromRemoteUrl($url) 289 ->setIntegrity($integrity) 290 ->setComponentId($componentId); 291 } 292 293 /** 294 * @param string $componentId - the component id attached to this URL 295 * @param string $url - the external url (The URL should have a file name as last name in the path) 296 * @param string|null $integrity - the file integrity 297 * @return Snippet 298 * @throws ExceptionBadArgument 299 * @throws ExceptionBadSyntax 300 * @throws ExceptionNotFound 301 */ 302 public 303 function attachRemoteCssStyleSheet(string $componentId, string $url, string $integrity = null): Snippet 304 { 305 $url = Url::createFromString($url); 306 307 return Snippet::getOrCreateFromRemoteUrl($url) 308 ->setIntegrity($integrity) 309 ->setRemoteUrl($url) 310 ->setComponentId($componentId); 311 } 312 313 314 /** 315 * @return Snippet[] 316 */ 317 public 318 function getSnippets(): array 319 { 320 return Snippet::getSnippets(); 321 } 322 323 private 324 function getRequestSnippets(): array 325 { 326 $snippets = Snippet::getSnippets(); 327 $slotSnippets = []; 328 foreach ($snippets as $snippet) { 329 if (!$snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 330 continue; 331 } 332 $slotSnippets[] = $snippet; 333 } 334 return $slotSnippets; 335 } 336 337 /** 338 * Output the snippet in HTML format 339 * The scope is mandatory: 340 * * {@link Snippet::ALL_SCOPE} 341 * * {@link Snippet::REQUEST_SCOPE} 342 * * {@link Snippet::SLOT_SCOPE} 343 * 344 * @return string - html string 345 */ 346 private 347 function toHtml($scope): string 348 { 349 switch ($scope) { 350 case Snippet::SLOT_SCOPE: 351 $snippets = $this->getSlotSnippets(); 352 break; 353 case Snippet::REQUEST_SCOPE: 354 $snippets = $this->getRequestSnippets(); 355 break; 356 default: 357 case Snippet::ALL_SCOPE: 358 $snippets = $this->getAllSnippets(); 359 if ($scope !== Snippet::ALL_SCOPE) { 360 LogUtility::internalError("Scope ($scope) is unknown, we have defaulted to all"); 361 } 362 break; 363 } 364 365 366 return self::toHtmlFromSnippetArray($snippets); 367 } 368 369 public 370 function toHtmlForAllSnippets(): string 371 { 372 return $this->toHtml(Snippet::ALL_SCOPE); 373 } 374 375 public 376 function toHtmlForSlotSnippets(): string 377 { 378 return $this->toHtml(Snippet::SLOT_SCOPE); 379 } 380 381 public function addPopoverLibrary(): SnippetSystem 382 { 383 $this->attachJavascriptFromComponentId(Snippet::COMBO_POPOVER); 384 $this->attachCssInternalStylesheet(Snippet::COMBO_POPOVER); 385 return $this; 386 } 387 388 /** 389 * @param $slot 390 * @return Snippet[] 391 */ 392 public function getSnippetsForSlot($slot): array 393 { 394 $snippets = Snippet::getSnippets(); 395 return array_filter($snippets, 396 function ($s) use ($slot) { 397 return $s->hasSlot($slot); 398 }); 399 } 400 401 402} 403