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 15use dokuwiki\Extension\SyntaxPlugin; 16use syntax_plugin_combo_media; 17use syntax_plugin_combo_pageimage; 18 19 20/** 21 * Class Call 22 * @package ComboStrap 23 * 24 * A wrapper around what's called a call 25 * which is an array of information such 26 * the mode, the data 27 * 28 * The {@link CallStack} is the only syntax representation that 29 * is available in DokuWiki 30 */ 31class Call 32{ 33 34 const INLINE_DISPLAY = "inline"; 35 const BlOCK_DISPLAY = "block"; 36 /** 37 * List of inline components 38 * Used to manage white space before an unmatched string. 39 * The syntax tree of Dokuwiki (ie {@link \Doku_Handler::$calls}) 40 * has only data and no class, for now, we create this 41 * lists manually because this is a hassle to retrieve this information from {@link \DokuWiki_Syntax_Plugin::getType()} 42 */ 43 const INLINE_DOKUWIKI_COMPONENTS = array( 44 /** 45 * Formatting https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 46 * Comes from the {@link \dokuwiki\Parsing\ParserMode\Formatting} class 47 */ 48 "cdata", 49 "unformatted", // ie %% or nowiki 50 "doublequoteclosing", // https://www.dokuwiki.org/config:typography / https://www.dokuwiki.org/wiki:syntax#text_to_html_conversions 51 "doublequoteopening", 52 "singlequoteopening", 53 "singlequoteclosing", 54 "multiplyentity", 55 "apostrophe", 56 "strong", 57 "emphasis", 58 "emphasis_open", 59 "emphasis_close", 60 "underline", 61 "underline_close", 62 "monospace", 63 "subscript", 64 "superscript", 65 "deleted", 66 "footnote", 67 /** 68 * Others 69 */ 70 "acronym", // abbr 71 "strong_close", 72 "strong_open", 73 "monospace_open", 74 "monospace_close", 75 "doublequoteopening", // ie the character " in "The" 76 "entity", // for instance `...` are transformed in character 77 "linebreak", 78 "externallink", 79 "internallink", 80 MediaLink::INTERNAL_MEDIA_CALL_NAME, 81 MediaLink::EXTERNAL_MEDIA_CALL_NAME, 82 /** 83 * The inline of combo 84 */ 85 \syntax_plugin_combo_link::TAG, 86 \syntax_plugin_combo_icon::TAG, 87 \syntax_plugin_combo_inote::TAG, 88 \syntax_plugin_combo_button::TAG, 89 \syntax_plugin_combo_tooltip::TAG, 90 \syntax_plugin_combo_pipeline::TAG, 91 ); 92 93 94 const BLOCK_MARKUP_DOKUWIKI_COMPONENTS = array( 95 "listu_open", // ul 96 "listu_close", 97 "listitem_open", //li 98 "listitem_close", 99 "listcontent_open", // after li ??? 100 "listcontent_close", 101 "table_open", 102 "table_close", 103 ); 104 105 /** 106 * A media is not really an image 107 * but it may contains one 108 */ 109 const IMAGE_TAGS = [ 110 syntax_plugin_combo_media::TAG, 111 syntax_plugin_combo_pageimage::TAG 112 ]; 113 const CANONICAL = "call"; 114 115 private $call; 116 117 /** 118 * The key identifier in the {@link CallStack} 119 * @var mixed|string 120 */ 121 private $key; 122 123 /** 124 * Call constructor. 125 * @param $call - the instruction array (ie called a call) 126 */ 127 public function __construct(&$call, $key = "") 128 { 129 $this->call = &$call; 130 $this->key = $key; 131 } 132 133 /** 134 * Insert a tag above 135 * @param $tagName 136 * @param $state 137 * @param array $attribute 138 * @param string|null $rawContext 139 * @param string|null $content - the parsed content 140 * @param string|null $payload - the payload after handler 141 * @param int|null $position 142 * @return Call - a call 143 */ 144 public static function createComboCall($tagName, $state, array $attribute = array(), string $rawContext = null, string $content = null, string $payload = null, int $position = null): Call 145 { 146 $data = array( 147 PluginUtility::ATTRIBUTES => $attribute, 148 PluginUtility::CONTEXT => $rawContext, 149 PluginUtility::STATE => $state, 150 PluginUtility::POSITION => $position 151 ); 152 if ($payload != null) { 153 $data[PluginUtility::PAYLOAD] = $payload; 154 } 155 $positionInText = $position; 156 157 $call = [ 158 "plugin", 159 array( 160 PluginUtility::getComponentName($tagName), 161 $data, 162 $state, 163 $content 164 ), 165 $positionInText 166 ]; 167 return new Call($call); 168 } 169 170 /** 171 * Insert a dokuwiki call 172 * @param $callName 173 * @param $array 174 * @param $positionInText 175 * @return Call 176 */ 177 public static function createNativeCall($callName, $array = [], $positionInText = null): Call 178 { 179 $call = [ 180 $callName, 181 $array, 182 $positionInText 183 ]; 184 return new Call($call); 185 } 186 187 public static function createFromInstruction($instruction) 188 { 189 return new Call($instruction); 190 } 191 192 /** 193 * @param Call $call 194 * @return Call 195 */ 196 public static function createFromCall(Call $call): Call 197 { 198 return self::createFromInstruction($call->toCallArray()); 199 } 200 201 202 /** 203 * 204 * Return the tag name from a call array 205 * 206 * This is not the logical tag. 207 * This is much more what's called: 208 * * the component name for a plugin 209 * * or the handler name for dokuwiki 210 * 211 * For a plugin, this is equivalent 212 * to the {@link SyntaxPlugin::getPluginComponent()} 213 * 214 * This is not the fully qualified component name: 215 * * with the plugin as prefix such as in {@link Call::getComponentName()} 216 * * or with the `open` and `close` prefix such as `p_close` ... 217 * 218 * @return mixed|string 219 */ 220 public function getTagName() 221 { 222 $mode = $this->call[0]; 223 if ($mode != "plugin") { 224 225 /** 226 * This is a standard dokuwiki node 227 */ 228 $dokuWikiNodeName = $this->call[0]; 229 230 /** 231 * The dokwuiki node name has also the open and close notion 232 * We delete this is not in the doc and therefore not logical 233 */ 234 $tagName = str_replace("_close", "", $dokuWikiNodeName); 235 $tagName = str_replace("_open", "", $tagName); 236 237 } else { 238 239 /** 240 * This is a plugin node 241 */ 242 $pluginDokuData = $this->call[1]; 243 $component = $pluginDokuData[0]; 244 if (!is_array($component)) { 245 /** 246 * Tag name from class 247 */ 248 $componentNames = explode("_", $component); 249 /** 250 * To take care of 251 * PHP Warning: sizeof(): Parameter must be an array or an object that implements Countable 252 * in lib/plugins/combo/class/Tag.php on line 314 253 */ 254 if (is_array($componentNames)) { 255 $tagName = $componentNames[sizeof($componentNames) - 1]; 256 } else { 257 $tagName = $component; 258 } 259 } else { 260 // To resolve: explode() expects parameter 2 to be string, array given 261 LogUtility::msg("The call (" . print_r($this->call, true) . ") has an array and not a string as component (" . print_r($component, true) . "). Page: " . Page::createPageFromRequestedPage(), LogUtility::LVL_MSG_ERROR); 262 $tagName = ""; 263 } 264 265 266 } 267 return $tagName; 268 269 } 270 271 272 /** 273 * The parser state 274 * @return mixed 275 * May be null (example eol, internallink, ...) 276 */ 277 public function getState() 278 { 279 $mode = $this->call[0]; 280 if ($mode !== "plugin") { 281 282 /** 283 * There is no state because this is a standard 284 * dokuwiki syntax found in {@link \Doku_Renderer_xhtml} 285 * check if this is not a `...._close` or `...._open` 286 * to derive the state 287 */ 288 $mode = $this->call[0]; 289 $lastPositionSepName = strrpos($mode, "_"); 290 $closeOrOpen = substr($mode, $lastPositionSepName + 1); 291 switch ($closeOrOpen) { 292 case "open": 293 return DOKU_LEXER_ENTER; 294 case "close": 295 return DOKU_LEXER_EXIT; 296 default: 297 return null; 298 } 299 300 } else { 301 // Plugin 302 $returnedArray = $this->call[1]; 303 if (array_key_exists(2, $returnedArray)) { 304 return $returnedArray[2]; 305 } else { 306 return null; 307 } 308 } 309 } 310 311 /** 312 * @return mixed the data returned from the {@link DokuWiki_Syntax_Plugin::handle} (ie attributes, payload, ...) 313 */ 314 public function &getPluginData($attribute = null) 315 { 316 $data = &$this->call[1][1]; 317 if ($attribute === null) { 318 return $data; 319 } 320 return $data[$attribute]; 321 322 } 323 324 /** 325 * @return mixed the matched content from the {@link DokuWiki_Syntax_Plugin::handle} 326 */ 327 public function getCapturedContent() 328 { 329 $caller = $this->call[0]; 330 switch ($caller) { 331 case "plugin": 332 return $this->call[1][3]; 333 case "internallink": 334 return '[[' . $this->call[1][0] . '|' . $this->call[1][1] . ']]'; 335 case "eol": 336 return DOKU_LF; 337 case "header": 338 case "cdata": 339 return $this->call[1][0]; 340 default: 341 if (isset($this->call[1][0]) && is_string($this->call[1][0])) { 342 return $this->call[1][0]; 343 } else { 344 return ""; 345 } 346 } 347 } 348 349 350 public function getAttributes(): ?array 351 { 352 353 $tagName = $this->getTagName(); 354 switch ($tagName) { 355 case MediaLink::INTERNAL_MEDIA_CALL_NAME: 356 return $this->call[1]; 357 default: 358 $data = $this->getPluginData(); 359 if (isset($data[PluginUtility::ATTRIBUTES])) { 360 $attributes = $data[PluginUtility::ATTRIBUTES]; 361 if (!is_array($attributes)) { 362 $message = "The attributes value are not an array for the call ($this)"; 363 if (PluginUtility::isDevOrTest()) { 364 throw new ExceptionComboRuntime($message, self::CANONICAL); 365 } 366 LogUtility::msg($message); 367 return null; 368 } 369 return $attributes; 370 } else { 371 return null; 372 } 373 } 374 } 375 376 public function removeAttributes() 377 { 378 379 $data = &$this->getPluginData(); 380 if (isset($data[PluginUtility::ATTRIBUTES])) { 381 unset($data[PluginUtility::ATTRIBUTES]); 382 } 383 384 } 385 386 public function updateToPluginComponent($component, $state, $attributes = array()) 387 { 388 if ($this->call[0] == "plugin") { 389 $match = $this->call[1][3]; 390 } else { 391 $this->call[0] = "plugin"; 392 $match = ""; 393 } 394 $this->call[1] = array( 395 0 => $component, 396 1 => array( 397 PluginUtility::ATTRIBUTES => $attributes, 398 PluginUtility::STATE => $state, 399 ), 400 2 => $state, 401 3 => $match 402 ); 403 404 } 405 406 /** 407 * Does the display has been set 408 * to override the dokuwiki default 409 * ({@link Syntax::getPType()} 410 * 411 * because an image is by default a inline component 412 * but can be a block (ie top image of a card) 413 * @return bool 414 */ 415 public function isDisplaySet(): bool 416 { 417 return isset($this->call[1][1][PluginUtility::DISPLAY]); 418 } 419 420 public function getDisplay() 421 { 422 $mode = $this->getMode(); 423 if ($mode == "plugin") { 424 if ($this->isDisplaySet()) { 425 return $this->call[1][1][PluginUtility::DISPLAY]; 426 } 427 } 428 429 if ($this->getState() == DOKU_LEXER_UNMATCHED) { 430 /** 431 * Unmatched are content (ie text node in XML/HTML) and have 432 * no display 433 */ 434 return Call::INLINE_DISPLAY; 435 } else { 436 $mode = $this->call[0]; 437 if ($mode == "plugin") { 438 global $DOKU_PLUGINS; 439 $component = $this->getComponentName(); 440 /** 441 * @var SyntaxPlugin $syntaxPlugin 442 */ 443 $syntaxPlugin = $DOKU_PLUGINS['syntax'][$component]; 444 $pType = $syntaxPlugin->getPType(); 445 switch ($pType) { 446 case "normal": 447 return Call::INLINE_DISPLAY; 448 case "block": 449 case "stack": 450 return Call::BlOCK_DISPLAY; 451 default: 452 LogUtility::msg("The ptype (" . $pType . ") is unknown."); 453 return null; 454 } 455 } else { 456 if ($mode == "eol") { 457 /** 458 * Control character 459 * We return it as it's used in the 460 * {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()} 461 * to create the paragraph 462 * This is not a block, nor an inline 463 */ 464 return $mode; 465 } 466 467 if (in_array($mode, self::INLINE_DOKUWIKI_COMPONENTS)) { 468 return Call::INLINE_DISPLAY; 469 } 470 471 if (in_array($mode, self::BLOCK_MARKUP_DOKUWIKI_COMPONENTS)) { 472 return Call::BlOCK_DISPLAY; 473 } 474 475 LogUtility::msg("The display of the call with the mode " . $mode . " is unknown"); 476 return null; 477 478 479 } 480 } 481 482 } 483 484 /** 485 * Same as {@link Call::getTagName()} 486 * but fully qualified 487 * @return string 488 */ 489 public function getComponentName() 490 { 491 $mode = $this->call[0]; 492 if ($mode == "plugin") { 493 $pluginDokuData = $this->call[1]; 494 return $pluginDokuData[0]; 495 } else { 496 return $mode; 497 } 498 } 499 500 public function updateEolToSpace() 501 { 502 $mode = $this->call[0]; 503 if ($mode != "eol") { 504 LogUtility::msg("You can't update a " . $mode . " to a space. It should be a eol", LogUtility::LVL_MSG_WARNING, "support"); 505 } else { 506 $this->call[0] = "cdata"; 507 $this->call[1] = array( 508 0 => " " 509 ); 510 } 511 512 } 513 514 public function addAttribute($key, $value) 515 { 516 $mode = $this->call[0]; 517 if ($mode == "plugin") { 518 $this->call[1][1][PluginUtility::ATTRIBUTES][$key] = $value; 519 } else { 520 LogUtility::msg("You can't add an attribute to the non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support"); 521 } 522 } 523 524 public function getContext() 525 { 526 $mode = $this->call[0]; 527 if ($mode == "plugin") { 528 return $this->call[1][1][PluginUtility::CONTEXT]; 529 } else { 530 LogUtility::msg("You can't ask for a context from a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING, "support"); 531 return null; 532 } 533 } 534 535 /** 536 * 537 * @return array 538 */ 539 public function toCallArray() 540 { 541 return $this->call; 542 } 543 544 public function __toString() 545 { 546 $name = $this->key; 547 if (!empty($name)) { 548 $name .= " - "; 549 } 550 $name .= $this->getTagName(); 551 return $name; 552 } 553 554 /** 555 * @return string|null 556 * 557 * If the type returned is a boolean attribute, 558 * it means you need to define the expected types 559 * in the function {@link TagAttributes::createFromTagMatch()} 560 * as third attribute 561 */ 562 public function getType(): ?string 563 { 564 if ($this->getState() == DOKU_LEXER_UNMATCHED) { 565 return null; 566 } else { 567 return $this->getAttribute(TagAttributes::TYPE_KEY); 568 } 569 } 570 571 /** 572 * @param $key 573 * @param null $default 574 * @return array|string|null 575 */ 576 public function getAttribute($key, $default = null) 577 { 578 $attributes = $this->getAttributes(); 579 if (isset($attributes[$key])) { 580 return $attributes[$key]; 581 } 582 return $default; 583 584 } 585 586 public 587 function getPayload() 588 { 589 $mode = $this->call[0]; 590 if ($mode == "plugin") { 591 return $this->call[1][1][PluginUtility::PAYLOAD]; 592 } else { 593 LogUtility::msg("You can't ask for a payload from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support"); 594 return null; 595 } 596 } 597 598 public 599 function setContext($value) 600 { 601 $this->call[1][1][PluginUtility::CONTEXT] = $value; 602 return $this; 603 } 604 605 public 606 function hasAttribute($attributeName): bool 607 { 608 $attributes = $this->getAttributes(); 609 if (isset($attributes[$attributeName])) { 610 return true; 611 } else { 612 if ($this->getType() == $attributeName) { 613 return true; 614 } else { 615 return false; 616 } 617 } 618 } 619 620 public 621 function isPluginCall() 622 { 623 return $this->call[0] === "plugin"; 624 } 625 626 /** 627 * @return mixed|string the position (ie key) in the array 628 */ 629 public 630 function getKey() 631 { 632 return $this->key; 633 } 634 635 public 636 function &getCall() 637 { 638 return $this->call; 639 } 640 641 public 642 function setState($state) 643 { 644 if ($this->call[0] == "plugin") { 645 // for dokuwiki 646 $this->call[1][2] = $state; 647 // for the combo plugin if any 648 if (isset($this->call[1][1][PluginUtility::STATE])) { 649 $this->call[1][1][PluginUtility::STATE] = $state; 650 } 651 } else { 652 LogUtility::msg("This modification of state is not yet supported for a native call"); 653 } 654 } 655 656 657 /** 658 * Return the position of the first matched character in the text file 659 * @return mixed 660 */ 661 public 662 function getFirstMatchedCharacterPosition() 663 { 664 665 return $this->call[2]; 666 667 } 668 669 /** 670 * Return the position of the last matched character in the text file 671 * 672 * This is the {@link Call::getFirstMatchedCharacterPosition()} 673 * plus the length of the {@link Call::getCapturedContent()} 674 * matched content 675 * @return int|mixed 676 */ 677 public 678 function getLastMatchedCharacterPosition() 679 { 680 return $this->getFirstMatchedCharacterPosition() + strlen($this->getCapturedContent()); 681 } 682 683 /** 684 * @param $value string the class string to add 685 * @return Call 686 */ 687 public 688 function addClassName(string $value): Call 689 { 690 $class = $this->getAttribute("class"); 691 if ($class != null) { 692 $value = "$class $value"; 693 } 694 $this->addAttribute("class", $value); 695 return $this; 696 697 } 698 699 /** 700 * @param $key 701 * @return mixed|null - the delete value of null if not found 702 */ 703 public 704 function removeAttribute($key) 705 { 706 707 $data = &$this->getPluginData(); 708 if (isset($data[PluginUtility::ATTRIBUTES][$key])) { 709 $value = $data[PluginUtility::ATTRIBUTES][$key]; 710 unset($data[PluginUtility::ATTRIBUTES][$key]); 711 return $value; 712 } else { 713 // boolean attribute as first attribute 714 if ($this->getType() == $key) { 715 unset($data[PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY]); 716 return true; 717 } 718 return null; 719 } 720 721 } 722 723 public 724 function setPayload($text) 725 { 726 if ($this->isPluginCall()) { 727 $this->call[1][1][PluginUtility::PAYLOAD] = $text; 728 } else { 729 LogUtility::msg("Setting the payload for a non-native call ($this) is not yet implemented"); 730 } 731 } 732 733 /** 734 * @return bool true if the call is a text call (same as dom text node) 735 */ 736 public 737 function isTextCall() 738 { 739 return ( 740 $this->getState() == DOKU_LEXER_UNMATCHED || 741 $this->getTagName() == "cdata" || 742 $this->getTagName() == "acronym" 743 ); 744 } 745 746 public 747 function setType($type) 748 { 749 if ($this->isPluginCall()) { 750 $this->call[1][1][PluginUtility::ATTRIBUTES][TagAttributes::TYPE_KEY] = $type; 751 } else { 752 LogUtility::msg("This is not a plugin call ($this), you can't set the type"); 753 } 754 } 755 756 public 757 function addCssStyle($key, $value) 758 { 759 $style = $this->getAttribute("style"); 760 $cssValue = "$key:$value"; 761 if ($style !== null) { 762 $cssValue = "$style; $cssValue"; 763 } 764 $this->addAttribute("style", $cssValue); 765 } 766 767 public 768 function setSyntaxComponentFromTag($tag) 769 { 770 771 if ($this->isPluginCall()) { 772 $this->call[1][0] = PluginUtility::getComponentName($tag); 773 } else { 774 LogUtility::msg("The call ($this) is a native call and we don't support yet the modification of the component to ($tag)"); 775 } 776 } 777 778 /** 779 * @param Page $page 780 * @return Call 781 */ 782 public 783 function render(Page $page) 784 { 785 return $this->renderFromData(TemplateUtility::getMetadataDataFromPage($page)); 786 } 787 788 public 789 function renderFromData(array $array): Call 790 { 791 792 /** 793 * Render all attributes 794 */ 795 $attributes = $this->getAttributes(); 796 if ($attributes !== null) { 797 foreach ($attributes as $key => $value) { 798 if (is_string($value)) { 799 $this->addAttribute($key, TemplateUtility::renderStringTemplateFromDataArray($value, $array)); 800 } 801 } 802 } 803 804 /** 805 * Content rendering 806 */ 807 $state = $this->getState(); 808 if ($state == DOKU_LEXER_UNMATCHED) { 809 if ($this->isPluginCall()) { 810 $payload = $this->getPayload(); 811 if (!empty($payload)) { 812 $this->setPayload(TemplateUtility::renderStringTemplateFromDataArray($payload, $array)); 813 } 814 } 815 } else { 816 $tagName = $this->getTagName(); 817 switch ($tagName) { 818 case "eol": 819 break; 820 case "cdata": 821 $payload = $this->getCapturedContent(); 822 $this->setCapturedContent(TemplateUtility::renderStringTemplateFromDataArray($payload, $array)); 823 break; 824 case \syntax_plugin_combo_pipeline::TAG: 825 $pageTemplate = PluginUtility::getTagContent($this->getCapturedContent()); 826 $script = TemplateUtility::renderStringTemplateFromDataArray($pageTemplate, $array); 827 $string = PipelineUtility::execute($script); 828 $this->setPayload($string); 829 break; 830 } 831 } 832 833 return $this; 834 } 835 836 public 837 function setCapturedContent($content) 838 { 839 $tagName = $this->getTagName(); 840 switch ($tagName) { 841 case "cdata": 842 $this->call[1][0] = $content; 843 break; 844 default: 845 LogUtility::msg("Setting the captured content on a call for the tag ($tagName) is not yet implemented", LogUtility::LVL_MSG_ERROR); 846 } 847 } 848 849 /** 850 * Set the display to block or inline 851 * One of `block` or `inline` 852 */ 853 public 854 function setDisplay($display): Call 855 { 856 $mode = $this->getMode(); 857 if ($mode == "plugin") { 858 $this->call[1][1][PluginUtility::DISPLAY] = $display; 859 } else { 860 LogUtility::msg("You can't set a display on a non plugin call mode (" . $mode . ")", LogUtility::LVL_MSG_WARNING); 861 } 862 return $this; 863 864 } 865 866 /** 867 * The plugin or not 868 * @return mixed 869 */ 870 private 871 function getMode() 872 { 873 return $this->call[0]; 874 } 875 876 /** 877 * Return if this an unmatched call with space 878 * in captured content 879 * @return bool 880 */ 881 public 882 function isUnMatchedEmptyCall(): bool 883 { 884 if ($this->getState() === DOKU_LEXER_UNMATCHED && trim($this->getCapturedContent()) === "") { 885 return true; 886 } 887 return false; 888 } 889 890 public 891 function getExitCode() 892 { 893 $mode = $this->call[0]; 894 if ($mode == "plugin") { 895 $value = $this->call[1][1][PluginUtility::EXIT_CODE]; 896 if ($value === null) { 897 return 0; 898 } 899 return $value; 900 } else { 901 LogUtility::msg("You can't ask for the exit code from a non plugin call mode (" . $mode . ").", LogUtility::LVL_MSG_WARNING, "support"); 902 return 0; 903 } 904 } 905 906 public function setAttribute(string $name, $value): Call 907 { 908 $this->getPluginData()[PluginUtility::ATTRIBUTES][$name] = $value; 909 return $this; 910 } 911 912 public function setPluginData(string $name, $value): Call 913 { 914 $this->getPluginData()[$name] = $value; 915 return $this; 916 } 917 918 919} 920