vendor/pimcore/pimcore/models/Document/Editable/Link.php line 298

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore\Model\Document\Editable;
  15. use Pimcore\Logger;
  16. use Pimcore\Model;
  17. use Pimcore\Model\Asset;
  18. use Pimcore\Model\DataObject;
  19. use Pimcore\Model\Document;
  20. /**
  21. * @method \Pimcore\Model\Document\Editable\Dao getDao()
  22. */
  23. class Link extends Model\Document\Editable implements IdRewriterInterface, EditmodeDataInterface
  24. {
  25. /**
  26. * Contains the data for the link
  27. *
  28. * @internal
  29. *
  30. * @var array|null
  31. */
  32. protected $data;
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function getType()
  37. {
  38. return 'link';
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. public function getData()
  44. {
  45. // update path if internal link
  46. $this->updatePathFromInternal(true);
  47. return $this->data;
  48. }
  49. /**
  50. * {@inheritdoc}
  51. */
  52. public function getDataEditmode() /** : mixed */
  53. {
  54. // update path if internal link
  55. $this->updatePathFromInternal(true, true);
  56. return $this->data;
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. protected function getEditmodeElementClasses($options = []): array
  62. {
  63. // we don't want the class attribute being applied to the editable container element (<div>, only to the <a> tag inside
  64. // the default behavior of the parent method is to include the "class" attribute
  65. $classes = [
  66. 'pimcore_editable',
  67. 'pimcore_editable_' . $this->getType(),
  68. ];
  69. return $classes;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function frontend()
  75. {
  76. $url = $this->getHref();
  77. if (strlen($url) > 0) {
  78. if (!is_array($this->config)) {
  79. $this->config = [];
  80. }
  81. $prefix = '';
  82. $suffix = '';
  83. $noText = false;
  84. if (array_key_exists('textPrefix', $this->config)) {
  85. $prefix = $this->config['textPrefix'];
  86. unset($this->config['textPrefix']);
  87. }
  88. if (array_key_exists('textSuffix', $this->config)) {
  89. $suffix = $this->config['textSuffix'];
  90. unset($this->config['textSuffix']);
  91. }
  92. if (isset($this->config['noText']) && $this->config['noText'] == true) {
  93. $noText = true;
  94. unset($this->config['noText']);
  95. }
  96. // add attributes to link
  97. $allowedAttributes = [
  98. 'charset',
  99. 'coords',
  100. 'hreflang',
  101. 'name',
  102. 'rel',
  103. 'rev',
  104. 'shape',
  105. 'target',
  106. 'accesskey',
  107. 'class',
  108. 'dir',
  109. 'draggable',
  110. 'dropzone',
  111. 'contextmenu',
  112. 'id',
  113. 'lang',
  114. 'style',
  115. 'tabindex',
  116. 'title',
  117. 'media',
  118. 'download',
  119. 'ping',
  120. 'type',
  121. 'referrerpolicy',
  122. 'xml:lang',
  123. ];
  124. $defaultAttributes = [];
  125. if (!is_array($this->data)) {
  126. $this->data = [];
  127. }
  128. $availableAttribs = array_merge($defaultAttributes, $this->data, $this->config);
  129. // add attributes to link
  130. $attribs = [];
  131. foreach ($availableAttribs as $key => $value) {
  132. if ((is_string($value) || is_numeric($value))
  133. && (strpos($key, 'data-') === 0 ||
  134. strpos($key, 'aria-') === 0 ||
  135. in_array($key, $allowedAttributes))) {
  136. if (!empty($this->data[$key]) && !empty($this->config[$key])) {
  137. $attribs[] = $key.'="'. htmlspecialchars($this->data[$key]) .' '. htmlspecialchars($this->config[$key]) .'"';
  138. } elseif (!empty($value)) {
  139. $attribs[] = $key.'="'.htmlspecialchars($value).'"';
  140. }
  141. }
  142. }
  143. $attribs = array_unique($attribs);
  144. if (array_key_exists('attributes', $this->data) && !empty($this->data['attributes'])) {
  145. trigger_deprecation(
  146. 'pimcore/pimcore',
  147. '10.6',
  148. 'Using the "attributes" field in the link editable is deprecated. The field will be removed in Pimcore 11.0.'
  149. );
  150. $attribs[] = $this->data['attributes'];
  151. }
  152. return '<a href="'.$url.'" '.implode(' ', $attribs).'>' . $prefix . ($noText ? '' : htmlspecialchars($this->data['text'])) . $suffix . '</a>';
  153. }
  154. return '';
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. public function checkValidity()
  160. {
  161. $sane = true;
  162. if (is_array($this->data) && isset($this->data['internal']) && $this->data['internal']) {
  163. if ($this->data['internalType'] == 'document') {
  164. $doc = Document::getById($this->data['internalId']);
  165. if (!$doc) {
  166. $sane = false;
  167. Logger::notice(
  168. 'Detected insane relation, removing reference to non existent document with id ['.$this->getDocumentId(
  169. ).']'
  170. );
  171. $this->data = null;
  172. }
  173. } elseif ($this->data['internalType'] == 'asset') {
  174. $asset = Asset::getById($this->data['internalId']);
  175. if (!$asset) {
  176. $sane = false;
  177. Logger::notice(
  178. 'Detected insane relation, removing reference to non existent asset with id ['.$this->getDocumentId(
  179. ).']'
  180. );
  181. $this->data = null;
  182. }
  183. } elseif ($this->data['internalType'] == 'object') {
  184. $object = Model\DataObject\Concrete::getById($this->data['internalId']);
  185. if (!$object) {
  186. $sane = false;
  187. Logger::notice(
  188. 'Detected insane relation, removing reference to non existent object with id ['.$this->getDocumentId(
  189. ).']'
  190. );
  191. $this->data = null;
  192. }
  193. }
  194. }
  195. return $sane;
  196. }
  197. /**
  198. * @return string
  199. */
  200. public function getHref()
  201. {
  202. $this->updatePathFromInternal();
  203. $url = $this->data['path'] ?? '';
  204. if (strlen($this->data['parameters'] ?? '') > 0) {
  205. $url .= (strpos($url, '?') !== false ? '&' : '?') . htmlspecialchars(str_replace('?', '', $this->getParameters()));
  206. }
  207. if (strlen($this->data['anchor'] ?? '') > 0) {
  208. $anchor = str_replace('"', urlencode('"'), htmlspecialchars($this->getAnchor()));
  209. $url .= '#' . str_replace('#', '', $anchor);
  210. }
  211. return $url;
  212. }
  213. /**
  214. * @param bool $realPath
  215. * @param bool $editmode
  216. */
  217. private function updatePathFromInternal($realPath = false, $editmode = false)
  218. {
  219. $method = 'getFullPath';
  220. if ($realPath) {
  221. $method = 'getRealFullPath';
  222. }
  223. if (isset($this->data['internal']) && $this->data['internal']) {
  224. if ($this->data['internalType'] == 'document') {
  225. if ($doc = Document::getById($this->data['internalId'])) {
  226. if ($editmode || (!Document::doHideUnpublished() || $doc->isPublished())) {
  227. $this->data['path'] = $doc->$method();
  228. } else {
  229. $this->data['path'] = '';
  230. }
  231. }
  232. } elseif ($this->data['internalType'] == 'asset') {
  233. if ($asset = Asset::getById($this->data['internalId'])) {
  234. $this->data['path'] = $asset->$method();
  235. }
  236. } elseif ($this->data['internalType'] == 'object') {
  237. if ($object = Model\DataObject::getById($this->data['internalId'])) {
  238. if ($editmode) {
  239. $this->data['path'] = $object->getFullPath();
  240. } else {
  241. if ($object instanceof Model\DataObject\Concrete) {
  242. if ($linkGenerator = $object->getClass()->getLinkGenerator()) {
  243. if ($realPath) {
  244. $this->data['path'] = $object->getFullPath();
  245. } else {
  246. $this->data['path'] = $linkGenerator->generate(
  247. $object,
  248. [
  249. 'document' => $this->getDocument(),
  250. 'context' => $this,
  251. ]
  252. );
  253. }
  254. }
  255. }
  256. }
  257. }
  258. }
  259. }
  260. // sanitize attributes
  261. if ($this->getEditmode() === false && isset($this->data['attributes'])) {
  262. trigger_deprecation(
  263. 'pimcore/pimcore',
  264. '10.6',
  265. 'Using the "attributes" field in the link editable is deprecated. The field will be removed in Pimcore 11.0.'
  266. );
  267. $this->data['attributes'] = htmlspecialchars($this->data['attributes'], HTML_ENTITIES);
  268. }
  269. // deletes unnecessary attribute, which was set by mistake in earlier versions, see also
  270. // https://github.com/pimcore/pimcore/issues/7394
  271. if (isset($this->data['type'])) {
  272. unset($this->data['type']);
  273. }
  274. }
  275. /**
  276. * @return string
  277. */
  278. public function getText()
  279. {
  280. return $this->data['text'] ?? '';
  281. }
  282. /**
  283. * @param string $text
  284. */
  285. public function setText($text)
  286. {
  287. $this->data['text'] = $text;
  288. }
  289. /**
  290. * @return string
  291. */
  292. public function getTarget()
  293. {
  294. return $this->data['target'] ?? '';
  295. }
  296. /**
  297. * @return string
  298. */
  299. public function getParameters()
  300. {
  301. return $this->data['parameters'] ?? '';
  302. }
  303. /**
  304. * @return string
  305. */
  306. public function getAnchor()
  307. {
  308. return $this->data['anchor'] ?? '';
  309. }
  310. /**
  311. * @return string
  312. */
  313. public function getTitle()
  314. {
  315. return $this->data['title'] ?? '';
  316. }
  317. /**
  318. * @return string
  319. */
  320. public function getRel()
  321. {
  322. return $this->data['rel'] ?? '';
  323. }
  324. /**
  325. * @return string
  326. */
  327. public function getTabindex()
  328. {
  329. return $this->data['tabindex'] ?? '';
  330. }
  331. /**
  332. * @return string
  333. */
  334. public function getAccesskey()
  335. {
  336. return $this->data['accesskey'] ?? '';
  337. }
  338. /**
  339. * @return mixed
  340. */
  341. public function getClass()
  342. {
  343. return $this->data['class'] ?? '';
  344. }
  345. /**
  346. * @return mixed
  347. *
  348. * @deprecated This method is deprecated and will be removed in Pimcore 11.
  349. */
  350. public function getAttributes()
  351. {
  352. return $this->data['attributes'] ?? '';
  353. }
  354. /**
  355. * {@inheritdoc}
  356. */
  357. public function setDataFromResource($data)
  358. {
  359. $this->data = \Pimcore\Tool\Serialize::unserialize($data);
  360. if (!is_array($this->data)) {
  361. $this->data = [];
  362. }
  363. return $this;
  364. }
  365. /**
  366. * {@inheritdoc}
  367. */
  368. public function setDataFromEditmode($data)
  369. {
  370. if (!is_array($data)) {
  371. $data = [];
  372. }
  373. $path = $data['path'] ?? null;
  374. if (!empty($path)) {
  375. $target = null;
  376. if ($data['linktype'] == 'internal' && $data['internalType']) {
  377. $target = Model\Element\Service::getElementByPath($data['internalType'], $path);
  378. if ($target) {
  379. $data['internal'] = true;
  380. $data['internalId'] = $target->getId();
  381. }
  382. }
  383. if (!$target) {
  384. if ($target = Document::getByPath($path)) {
  385. $data['internal'] = true;
  386. $data['internalId'] = $target->getId();
  387. $data['internalType'] = 'document';
  388. } elseif ($target = Asset::getByPath($path)) {
  389. $data['internal'] = true;
  390. $data['internalId'] = $target->getId();
  391. $data['internalType'] = 'asset';
  392. } elseif ($target = Model\DataObject\Concrete::getByPath($path)) {
  393. $data['internal'] = true;
  394. $data['internalId'] = $target->getId();
  395. $data['internalType'] = 'object';
  396. } else {
  397. $data['internal'] = false;
  398. $data['internalId'] = null;
  399. $data['internalType'] = null;
  400. $data['linktype'] = 'direct';
  401. }
  402. if ($target) {
  403. $data['linktype'] = 'internal';
  404. }
  405. }
  406. }
  407. $this->data = $data;
  408. return $this;
  409. }
  410. /**
  411. * {@inheritdoc}
  412. */
  413. public function isEmpty()
  414. {
  415. return strlen($this->getHref()) < 1;
  416. }
  417. /**
  418. * {@inheritdoc}
  419. */
  420. public function resolveDependencies()
  421. {
  422. $dependencies = [];
  423. $isInternal = $this->data['internal'] ?? false;
  424. if (is_array($this->data) && $isInternal) {
  425. if ((int)$this->data['internalId'] > 0) {
  426. if ($this->data['internalType'] == 'document') {
  427. if ($doc = Document::getById($this->data['internalId'])) {
  428. $key = 'document_'.$doc->getId();
  429. $dependencies[$key] = [
  430. 'id' => $doc->getId(),
  431. 'type' => 'document',
  432. ];
  433. }
  434. } elseif ($this->data['internalType'] == 'asset') {
  435. if ($asset = Asset::getById($this->data['internalId'])) {
  436. $key = 'asset_' . $asset->getId();
  437. $dependencies[$key] = [
  438. 'id' => $asset->getId(),
  439. 'type' => 'asset',
  440. ];
  441. }
  442. } elseif ($this->data['internalType'] == 'object') {
  443. if ($object = DataObject\Concrete::getById($this->data['internalId'])) {
  444. $key = 'object_' . $object->getId();
  445. $dependencies[$key] = [
  446. 'id' => $object->getId(),
  447. 'type' => 'object',
  448. ];
  449. }
  450. }
  451. }
  452. }
  453. return $dependencies;
  454. }
  455. /**
  456. * { @inheritdoc }
  457. */
  458. public function rewriteIds($idMapping) /** : void */
  459. {
  460. if (isset($this->data['internal']) && $this->data['internal']) {
  461. $type = $this->data['internalType'];
  462. $id = (int)$this->data['internalId'];
  463. if (array_key_exists($type, $idMapping)) {
  464. if (array_key_exists($id, $idMapping[$type])) {
  465. $this->data['internalId'] = $idMapping[$type][$id];
  466. $this->getHref();
  467. }
  468. }
  469. }
  470. }
  471. }