/vendor/anahkiasen/html-object/src/Traits/Tag.php

https://gitlab.com/hatemdigify/digifyblog · PHP · 553 lines · 247 code · 75 blank · 231 comment · 31 complexity · f6af35f23bb31353b8c079a5a23f36c9 MD5 · raw file

  1. <?php
  2. namespace HtmlObject\Traits;
  3. use HtmlObject\Element;
  4. /**
  5. * An abstraction of an HTML element.
  6. */
  7. abstract class Tag extends TreeObject
  8. {
  9. /**
  10. * The element name.
  11. *
  12. * @type string
  13. */
  14. protected $element;
  15. /**
  16. * The object's value.
  17. *
  18. * @type string|null|Tag
  19. */
  20. protected $value;
  21. /**
  22. * The object's attribute.
  23. *
  24. * @type array
  25. */
  26. protected $attributes = array();
  27. /**
  28. * Whether the element is self closing.
  29. *
  30. * @type boolean
  31. */
  32. protected $isSelfClosing = false;
  33. /**
  34. * Whether the current tag is opened or not.
  35. *
  36. * @type boolean
  37. */
  38. protected $isOpened = false;
  39. /**
  40. * A list of class properties to be added to attributes.
  41. *
  42. * @type array
  43. */
  44. protected $injectedProperties = array('value');
  45. // Configuration options ----------------------------------------- /
  46. /**
  47. * The base configuration inherited by classes.
  48. *
  49. * @type array
  50. */
  51. public static $config = array(
  52. 'doctype' => 'html',
  53. );
  54. ////////////////////////////////////////////////////////////////////
  55. //////////////////////////// CORE METHODS //////////////////////////
  56. ////////////////////////////////////////////////////////////////////
  57. /**
  58. * Set up a new tag.
  59. *
  60. * @param string $element Its element
  61. * @param string|null $value Its value
  62. * @param array $attributes Its attributes
  63. */
  64. protected function setTag($element, $value = null, $attributes = array())
  65. {
  66. $this->setValue($value);
  67. $this->setElement($element);
  68. $this->replaceAttributes($attributes);
  69. }
  70. /**
  71. * Wrap the Element in another element.
  72. *
  73. * @param string|Element $element The element's tag
  74. *
  75. * @return Element
  76. */
  77. public function wrapWith($element, $name = null)
  78. {
  79. if (!$element instanceof Tag) {
  80. $element = Element::create($element);
  81. }
  82. if ($this->parent) {
  83. $this->parent->nest($element, $name);
  84. $children = $this->parent->children;
  85. unset($children[$this->parentIndex]);
  86. $this->parent->children = $children;
  87. $name = $this->parentIndex;
  88. }
  89. $element->nest($this, $name);
  90. return $this;
  91. }
  92. /**
  93. * Render on string conversion.
  94. *
  95. * @return string|null
  96. */
  97. public function __toString()
  98. {
  99. return $this->render();
  100. }
  101. ////////////////////////////////////////////////////////////////////
  102. ///////////////////////// ELEMENT RENDERING ////////////////////////
  103. ////////////////////////////////////////////////////////////////////
  104. /**
  105. * Opens the Tag.
  106. *
  107. * @return string|null
  108. */
  109. public function open()
  110. {
  111. $this->isOpened = true;
  112. // If self closing, put value as attribute
  113. foreach ($this->injectProperties() as $attribute => $property) {
  114. if (!$this->isSelfClosing && $attribute == 'value') {
  115. continue;
  116. }
  117. if (is_null($property) && !is_empty($property)) {
  118. continue;
  119. }
  120. $this->attributes[$attribute] = $property;
  121. }
  122. // Invisible tags
  123. if (!$this->element) {
  124. return;
  125. }
  126. return '<'.$this->element.Helpers::parseAttributes($this->attributes).$this->getTagCloser();
  127. }
  128. /**
  129. * Open the tag tree on a particular child.
  130. *
  131. * @param string $onChild The child's key
  132. *
  133. * @return string
  134. */
  135. public function openOn($onChildren)
  136. {
  137. $onChildren = explode('.', $onChildren);
  138. $element = $this->open();
  139. $element .= $this->value;
  140. $subject = $this;
  141. foreach ($onChildren as $onChild) {
  142. foreach ($subject->getChildren() as $childName => $child) {
  143. if ($childName != $onChild) {
  144. $element .= $child;
  145. } else {
  146. $subject = $child;
  147. $element .= $child->open();
  148. break;
  149. }
  150. }
  151. }
  152. return $element;
  153. }
  154. /**
  155. * Check if the tag is opened.
  156. *
  157. * @return boolean
  158. */
  159. public function isOpened()
  160. {
  161. return $this->isOpened;
  162. }
  163. /**
  164. * Returns the Tag's content.
  165. *
  166. * @return string
  167. */
  168. public function getContent()
  169. {
  170. $value = $this->value;
  171. if ($value instanceof Tag) {
  172. $value = $value->render();
  173. }
  174. return $value.$this->renderChildren();
  175. }
  176. /**
  177. * Close the Tag.
  178. *
  179. * @return string|null
  180. */
  181. public function close()
  182. {
  183. $this->isOpened = false;
  184. $openedOn = null;
  185. $element = null;
  186. foreach ($this->children as $childName => $child) {
  187. if ($child->isOpened && !$child->isSelfClosing) {
  188. $openedOn = $childName;
  189. $element .= $child->close();
  190. } elseif ($openedOn && $child->isAfter($openedOn)) {
  191. $element .= $child;
  192. }
  193. }
  194. // Invisible tags
  195. if (!$this->element) {
  196. return;
  197. }
  198. return $element .= '</'.$this->element.'>';
  199. }
  200. /**
  201. * Default rendering method.
  202. *
  203. * @return string|null
  204. */
  205. public function render()
  206. {
  207. // If it's a self closing tag
  208. if ($this->isSelfClosing) {
  209. return $this->open();
  210. }
  211. return $this->open().$this->getContent().$this->close();
  212. }
  213. /**
  214. * Get the preferred way to close a tag.
  215. *
  216. * @return string
  217. */
  218. protected function getTagCloser()
  219. {
  220. if ($this->isSelfClosing && static::$config['doctype'] == 'xhtml') {
  221. return ' />';
  222. }
  223. return '>';
  224. }
  225. ////////////////////////////////////////////////////////////////////
  226. /////////////////////////// MAGIC METHODS //////////////////////////
  227. ////////////////////////////////////////////////////////////////////
  228. /**
  229. * Dynamically set attributes.
  230. *
  231. * @param string $method An attribute
  232. * @param array $parameters Its value(s)
  233. *
  234. * @return $this
  235. */
  236. public function __call($method, $parameters)
  237. {
  238. // Replace underscores
  239. $method = Helpers::hyphenated($method);
  240. $method = str_replace('_', '-', $method);
  241. // Get value and set it
  242. $value = Helpers::arrayGet($parameters, 0, 'true');
  243. $this->$method = $value;
  244. return $this;
  245. }
  246. /**
  247. * Dynamically set an attribute.
  248. *
  249. * @param string $attribute The attribute
  250. * @param string $value Its value
  251. */
  252. public function __set($attribute, $value)
  253. {
  254. $this->attributes[$attribute] = $value;
  255. return $this;
  256. }
  257. /**
  258. * Get an attribute or a child.
  259. *
  260. * @param string $item The desired child/attribute
  261. *
  262. * @return mixed
  263. */
  264. public function __get($item)
  265. {
  266. if (array_key_exists($item, $this->attributes)) {
  267. return $this->attributes[$item];
  268. }
  269. // Get a child by snake case
  270. $child = preg_replace_callback('/([A-Z])/', function ($match) {
  271. return '.'.strtolower($match[1]);
  272. }, $item);
  273. $child = $this->getChild($child);
  274. return $child;
  275. }
  276. ////////////////////////////////////////////////////////////////////
  277. //////////////////////////////// VALUE /////////////////////////////
  278. ////////////////////////////////////////////////////////////////////
  279. /**
  280. * Changes the Tag's element.
  281. *
  282. * @param string $element
  283. *
  284. * @return $this
  285. */
  286. public function setElement($element)
  287. {
  288. $this->element = $element;
  289. return $this;
  290. }
  291. /**
  292. * Change the object's value.
  293. *
  294. * @param string $value
  295. *
  296. * @return $this
  297. */
  298. public function setValue($value)
  299. {
  300. if (is_array($value)) {
  301. $this->nestChildren($value);
  302. } else {
  303. $this->value = $value;
  304. }
  305. return $this;
  306. }
  307. /**
  308. * Wrap the value in a tag.
  309. *
  310. * @param string $tag The tag
  311. *
  312. * @return $this
  313. */
  314. public function wrapValue($tag)
  315. {
  316. $this->value = Element::create($tag, $this->value);
  317. return $this;
  318. }
  319. /**
  320. * Get the value.
  321. *
  322. * @return string|null
  323. */
  324. public function getValue()
  325. {
  326. return $this->value;
  327. }
  328. /**
  329. * Get all the children as a string.
  330. *
  331. * @return string
  332. */
  333. protected function renderChildren()
  334. {
  335. $children = $this->children;
  336. foreach ($children as $key => $child) {
  337. if ($child instanceof Tag) {
  338. $children[$key] = $child->render();
  339. }
  340. }
  341. return implode($children);
  342. }
  343. ////////////////////////////////////////////////////////////////////
  344. //////////////////////////// ATTRIBUTES ////////////////////////////
  345. ////////////////////////////////////////////////////////////////////
  346. /**
  347. * Return an array of protected properties to bind as attributes.
  348. *
  349. * @return array
  350. */
  351. protected function injectProperties()
  352. {
  353. $properties = array();
  354. foreach ($this->injectedProperties as $property) {
  355. if (!isset($this->$property)) {
  356. continue;
  357. }
  358. $properties[$property] = $this->$property;
  359. }
  360. return $properties;
  361. }
  362. /**
  363. * Set an attribute.
  364. *
  365. * @param string $attribute An attribute
  366. * @param string|null $value Its value
  367. *
  368. * @return $this
  369. */
  370. public function setAttribute($attribute, $value = null)
  371. {
  372. $this->attributes[$attribute] = $value;
  373. return $this;
  374. }
  375. /**
  376. * Set a bunch of parameters at once.
  377. *
  378. * @param array $attributes The attributes to add to the existing ones
  379. *
  380. * @return $this
  381. */
  382. public function setAttributes($attributes)
  383. {
  384. $this->attributes = array_merge($this->attributes, (array) $attributes);
  385. return $this;
  386. }
  387. /**
  388. * Get all attributes.
  389. *
  390. * @return array
  391. */
  392. public function getAttributes()
  393. {
  394. return $this->attributes;
  395. }
  396. /**
  397. * Get an attribute.
  398. *
  399. * @param string $attribute
  400. *
  401. * @return mixed
  402. */
  403. public function getAttribute($attribute)
  404. {
  405. return Helpers::arrayGet($this->attributes, $attribute);
  406. }
  407. /**
  408. * Remove an attribute.
  409. *
  410. * @param string $attribute
  411. *
  412. * @return $this
  413. */
  414. public function removeAttribute($attribute)
  415. {
  416. if (array_key_exists($attribute, $this->attributes)) {
  417. unset($this->attributes[$attribute]);
  418. }
  419. return $this;
  420. }
  421. /**
  422. * Replace all attributes with the provided array.
  423. *
  424. * @param array $attributes The attributes to replace with
  425. *
  426. * @return $this
  427. */
  428. public function replaceAttributes($attributes)
  429. {
  430. $this->attributes = (array) $attributes;
  431. return $this;
  432. }
  433. /**
  434. * Add one or more classes to the current field.
  435. *
  436. * @param string $class The class(es) to add
  437. *
  438. * @return $this
  439. */
  440. public function addClass($class)
  441. {
  442. if (is_array($class)) {
  443. $class = implode(' ', $class);
  444. }
  445. // Create class attribute if it isn't already
  446. if (!isset($this->attributes['class'])) {
  447. $this->attributes['class'] = null;
  448. }
  449. // Prevent adding a class twice
  450. $classes = explode(' ', $this->attributes['class']);
  451. if (!in_array($class, $classes)) {
  452. $this->attributes['class'] = trim($this->attributes['class'].' '.$class);
  453. }
  454. return $this;
  455. }
  456. /**
  457. * Remove one or more classes to the current field.
  458. *
  459. * @param string $classes The class(es) to remove
  460. *
  461. * @return $this
  462. */
  463. public function removeClass($classes)
  464. {
  465. if (!is_array($classes)) {
  466. $classes = explode(' ', $classes);
  467. }
  468. $thisClasses = explode(' ', Helpers::arrayGet($this->attributes, 'class'));
  469. foreach ($classes as $class) {
  470. $exists = array_search($class, $thisClasses);
  471. if (!is_null($exists)) {
  472. unset($thisClasses[$exists]);
  473. }
  474. }
  475. $this->attributes['class'] = implode(' ', $thisClasses);
  476. return $this;
  477. }
  478. }