vendor/pimcore/pimcore/models/Document/Editable/Video.php line 27

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\Bundle\CoreBundle\EventListener\Frontend\FullPageCacheListener;
  16. use Pimcore\Logger;
  17. use Pimcore\Model;
  18. use Pimcore\Model\Asset;
  19. use Pimcore\Tool;
  20. /**
  21. * @method \Pimcore\Model\Document\Editable\Dao getDao()
  22. */
  23. class Video extends Model\Document\Editable implements IdRewriterInterface
  24. {
  25. public const TYPE_ASSET = 'asset';
  26. public const TYPE_YOUTUBE = 'youtube';
  27. public const TYPE_VIMEO = 'vimeo';
  28. public const TYPE_DAILYMOTION = 'dailymotion';
  29. public const ALLOWED_TYPES = [
  30. self::TYPE_ASSET,
  31. self::TYPE_YOUTUBE,
  32. self::TYPE_VIMEO,
  33. self::TYPE_DAILYMOTION,
  34. ];
  35. /**
  36. * contains depending on the type of the video the unique identifier eg. "http://www.youtube.com", "789", ...
  37. *
  38. * @internal
  39. *
  40. * @var int|string|null
  41. */
  42. protected $id;
  43. /**
  44. * one of self::ALLOWED_TYPES
  45. *
  46. * @internal
  47. *
  48. * @var string|null
  49. */
  50. protected $type;
  51. /**
  52. * asset ID of poster image
  53. *
  54. * @internal
  55. *
  56. * @var int|null
  57. */
  58. protected $poster;
  59. /**
  60. * @internal
  61. *
  62. * @var string
  63. */
  64. protected $title = '';
  65. /**
  66. * @internal
  67. *
  68. * @var string
  69. */
  70. protected $description = '';
  71. /**
  72. * @internal
  73. *
  74. * @var array|null
  75. */
  76. protected $allowedTypes;
  77. /**
  78. * @param int|string|null $id
  79. *
  80. * @return $this
  81. */
  82. public function setId($id)
  83. {
  84. $this->id = $id;
  85. return $this;
  86. }
  87. /**
  88. * @return int|string|null
  89. */
  90. public function getId()
  91. {
  92. return $this->id;
  93. }
  94. /**
  95. * @param string $title
  96. *
  97. * @return $this
  98. */
  99. public function setTitle($title)
  100. {
  101. $this->title = $title;
  102. return $this;
  103. }
  104. /**
  105. * @return string
  106. */
  107. public function getTitle()
  108. {
  109. if (!$this->title && $this->getVideoAsset()) {
  110. // default title for microformats
  111. return $this->getVideoAsset()->getFilename();
  112. }
  113. return $this->title;
  114. }
  115. /**
  116. * @param string $description
  117. *
  118. * @return $this
  119. */
  120. public function setDescription($description)
  121. {
  122. $this->description = $description;
  123. return $this;
  124. }
  125. /**
  126. * @return string
  127. */
  128. public function getDescription()
  129. {
  130. if (!$this->description) {
  131. // default description for microformats
  132. return $this->getTitle();
  133. }
  134. return $this->description;
  135. }
  136. /**
  137. * @param int|null $id
  138. *
  139. * @return $this
  140. */
  141. public function setPoster($id)
  142. {
  143. $this->poster = $id;
  144. return $this;
  145. }
  146. /**
  147. * @return int|null
  148. */
  149. public function getPoster()
  150. {
  151. return $this->poster;
  152. }
  153. /**
  154. * @param string $type
  155. *
  156. * @return $this
  157. */
  158. public function setType($type)
  159. {
  160. $this->type = $type;
  161. return $this;
  162. }
  163. /**
  164. * {@inheritdoc}
  165. */
  166. public function getType()
  167. {
  168. return 'video';
  169. }
  170. /**
  171. * @param array $allowedTypes
  172. *
  173. * @return $this
  174. */
  175. public function setAllowedTypes($allowedTypes)
  176. {
  177. $this->allowedTypes = $allowedTypes;
  178. return $this;
  179. }
  180. /**
  181. * @return array
  182. */
  183. public function getAllowedTypes()
  184. {
  185. if ($this->allowedTypes === null) {
  186. $this->updateAllowedTypesFromConfig($this->getConfig());
  187. }
  188. return $this->allowedTypes;
  189. }
  190. /**
  191. * {@inheritdoc}
  192. */
  193. public function getData()
  194. {
  195. $path = $this->id;
  196. if ($this->type === self::TYPE_ASSET && ($video = Asset::getById($this->id))) {
  197. $path = $video->getFullPath();
  198. }
  199. $allowedTypes = $this->getAllowedTypes();
  200. if (
  201. empty($this->type) === true
  202. || in_array($this->type, $allowedTypes, true) === false
  203. ) {
  204. // Set the first type in array as default selection for dropdown
  205. $this->type = $allowedTypes[0];
  206. // Reset "id" and "path" to prevent invalid references
  207. $this->id = '';
  208. $path = '';
  209. }
  210. $poster = Asset::getById($this->poster);
  211. return [
  212. 'id' => $this->id,
  213. 'type' => $this->type,
  214. 'allowedTypes' => $allowedTypes,
  215. 'title' => $this->title,
  216. 'description' => $this->description,
  217. 'path' => $path,
  218. 'poster' => $poster ? $poster->getFullPath() : '',
  219. ];
  220. }
  221. /**
  222. * @return mixed
  223. */
  224. protected function getDataEditmode()
  225. {
  226. $data = $this->getData();
  227. $poster = Asset::getById($this->poster);
  228. if ($poster) {
  229. $data['poster'] = $poster->getRealFullPath();
  230. }
  231. if ($this->type === self::TYPE_ASSET && ($video = Asset::getById($this->id))) {
  232. $data['path'] = $video->getRealFullPath();
  233. }
  234. return $data;
  235. }
  236. /**
  237. * {@inheritdoc}
  238. */
  239. public function getDataForResource()
  240. {
  241. return [
  242. 'id' => $this->id,
  243. 'type' => $this->type,
  244. 'allowedTypes' => $this->getAllowedTypes(),
  245. 'title' => $this->title,
  246. 'description' => $this->description,
  247. 'poster' => $this->poster,
  248. ];
  249. }
  250. /**
  251. * {@inheritdoc}
  252. */
  253. public function frontend()
  254. {
  255. $inAdmin = false;
  256. $args = func_get_args();
  257. if (array_key_exists(0, $args)) {
  258. $inAdmin = $args[0];
  259. }
  260. if (
  261. empty($this->id) === true
  262. || empty($this->type) === true
  263. || in_array($this->type, $this->getAllowedTypes(), true) === false
  264. ) {
  265. return $this->getEmptyCode();
  266. } elseif ($this->type === self::TYPE_ASSET) {
  267. return $this->getAssetCode($inAdmin);
  268. } elseif ($this->type === self::TYPE_YOUTUBE) {
  269. return $this->getYoutubeCode($inAdmin);
  270. } elseif ($this->type === self::TYPE_VIMEO) {
  271. return $this->getVimeoCode($inAdmin);
  272. } elseif ($this->type === self::TYPE_DAILYMOTION) {
  273. return $this->getDailymotionCode($inAdmin);
  274. } elseif ($this->type === 'url') {
  275. return $this->getUrlCode();
  276. }
  277. return $this->getEmptyCode();
  278. }
  279. /**
  280. * {@inheritdoc}
  281. */
  282. public function resolveDependencies()
  283. {
  284. $dependencies = [];
  285. if ($this->type === self::TYPE_ASSET) {
  286. $asset = Asset::getById($this->id);
  287. if ($asset instanceof Asset) {
  288. $key = 'asset_' . $asset->getId();
  289. $dependencies[$key] = [
  290. 'id' => $asset->getId(),
  291. 'type' => self::TYPE_ASSET,
  292. ];
  293. }
  294. }
  295. if ($poster = Asset::getById($this->poster)) {
  296. $key = 'asset_' . $poster->getId();
  297. $dependencies[$key] = [
  298. 'id' => $poster->getId(),
  299. 'type' => self::TYPE_ASSET,
  300. ];
  301. }
  302. return $dependencies;
  303. }
  304. /**
  305. * {@inheritdoc}
  306. */
  307. public function checkValidity()
  308. {
  309. $valid = true;
  310. if ($this->type === self::TYPE_ASSET && !empty($this->id)) {
  311. $el = Asset::getById($this->id);
  312. if (!$el instanceof Asset) {
  313. $valid = false;
  314. Logger::notice('Detected invalid relation, removing reference to non existent asset with id ['.$this->id.']');
  315. $this->id = null;
  316. $this->type = null;
  317. }
  318. }
  319. if (!($poster = Asset::getById($this->poster))) {
  320. $valid = false;
  321. Logger::notice('Detected invalid relation, removing reference to non existent asset with id ['.$this->id.']');
  322. $this->poster = null;
  323. }
  324. return $valid;
  325. }
  326. /**
  327. * {@inheritdoc}
  328. */
  329. public function admin()
  330. {
  331. $html = parent::admin();
  332. // get frontendcode for preview
  333. // put the video code inside the generic code
  334. $html = str_replace('</div>', $this->frontend(true) . '</div>', $html);
  335. return $html;
  336. }
  337. /**
  338. * {@inheritdoc}
  339. */
  340. public function setDataFromResource($data)
  341. {
  342. if (!empty($data)) {
  343. $data = \Pimcore\Tool\Serialize::unserialize($data);
  344. }
  345. $this->id = $data['id'];
  346. $this->type = $data['type'];
  347. $this->poster = $data['poster'];
  348. $this->title = $data['title'];
  349. $this->description = $data['description'];
  350. return $this;
  351. }
  352. /**
  353. * {@inheritdoc}
  354. */
  355. public function setDataFromEditmode($data)
  356. {
  357. if (isset($data['type'])
  358. && in_array($data['type'], self::ALLOWED_TYPES, true) === true
  359. ) {
  360. $this->type = $data['type'];
  361. }
  362. if (isset($data['title'])) {
  363. $this->title = $data['title'];
  364. }
  365. if (isset($data['description'])) {
  366. $this->description = $data['description'];
  367. }
  368. // this is to be backward compatible to <= v 1.4.7
  369. if (isset($data['id']) && $data['id']) {
  370. $data['path'] = $data['id'];
  371. }
  372. $video = Asset::getByPath($data['path']);
  373. if ($video instanceof Asset\Video) {
  374. $this->id = $video->getId();
  375. } else {
  376. $this->id = $data['path'];
  377. }
  378. $this->poster = null;
  379. $poster = Asset::getByPath($data['poster']);
  380. if ($poster instanceof Asset\Image) {
  381. $this->poster = $poster->getId();
  382. }
  383. return $this;
  384. }
  385. /**
  386. * @return int|string
  387. */
  388. public function getWidth()
  389. {
  390. return $this->getConfig()['width'] ?? '100%';
  391. }
  392. /**
  393. * @return string
  394. */
  395. private function getWidthWithUnit()
  396. {
  397. $width = $this->getWidth();
  398. if (is_numeric($width)) {
  399. $width .= 'px';
  400. }
  401. return $width;
  402. }
  403. /**
  404. * @return string
  405. */
  406. private function getHeightWithUnit()
  407. {
  408. $height = $this->getHeight();
  409. if (is_numeric($height)) {
  410. $height .= 'px';
  411. }
  412. return $height;
  413. }
  414. /**
  415. * @return int|string
  416. */
  417. public function getHeight()
  418. {
  419. return $this->getConfig()['height'] ?? 300;
  420. }
  421. private function getAssetCode(bool $inAdmin = false): string
  422. {
  423. $asset = Asset::getById($this->id);
  424. $config = $this->getConfig();
  425. $thumbnailConfig = $config['thumbnail'] ?? null;
  426. // compatibility mode when FFMPEG is not present or no thumbnail config is given
  427. if (!\Pimcore\Video::isAvailable() || !$thumbnailConfig) {
  428. if ($asset instanceof Asset\Video && preg_match("/\.(f4v|flv|mp4)/i", $asset->getFullPath())) {
  429. $image = $this->getPosterThumbnailImage($asset);
  430. return $this->getHtml5Code(['mp4' => (string) $asset], $image);
  431. }
  432. return $this->getErrorCode('Asset is not a video, or missing thumbnail configuration');
  433. }
  434. if ($asset instanceof Asset\Video) {
  435. $thumbnail = $asset->getThumbnail($thumbnailConfig);
  436. if ($thumbnail) {
  437. $image = $this->getPosterThumbnailImage($asset);
  438. if ($inAdmin && isset($config['editmodeImagePreview']) && $config['editmodeImagePreview']) {
  439. $code = '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">';
  440. $code .= '<img width="' . $this->getWidth() . '" src="' . $image . '" />';
  441. $code .= '</div>';
  442. return $code;
  443. }
  444. if ($thumbnail['status'] === 'finished') {
  445. return $this->getHtml5Code($thumbnail['formats'], $image);
  446. }
  447. if ($thumbnail['status'] === 'inprogress') {
  448. // disable the output-cache if enabled
  449. $cacheService = \Pimcore::getContainer()->get(FullPageCacheListener::class);
  450. $cacheService->disable('Video rendering in progress');
  451. return $this->getProgressCode($image);
  452. }
  453. return $this->getErrorCode('The video conversion failed, please see the log files in /var/log for more details.');
  454. }
  455. return $this->getErrorCode("The given thumbnail doesn't exist: '" . $thumbnailConfig . "'");
  456. }
  457. return $this->getEmptyCode();
  458. }
  459. /**
  460. * @return Asset\Image\Thumbnail|Asset\Video\ImageThumbnail
  461. */
  462. private function getPosterThumbnailImage(Asset\Video $asset)
  463. {
  464. $config = $this->getConfig();
  465. if (!array_key_exists('imagethumbnail', $config) || empty($config['imagethumbnail'])) {
  466. $thumbnailConfig = $asset->getThumbnailConfig($config['thumbnail'] ?? null);
  467. if ($thumbnailConfig instanceof Asset\Video\Thumbnail\Config) {
  468. // try to get the dimensions out ouf the video thumbnail
  469. $imageThumbnailConf = $thumbnailConfig->getEstimatedDimensions();
  470. $imageThumbnailConf['format'] = 'JPEG';
  471. }
  472. } else {
  473. $imageThumbnailConf = $config['imagethumbnail'];
  474. }
  475. if (empty($imageThumbnailConf)) {
  476. $imageThumbnailConf['width'] = 800;
  477. $imageThumbnailConf['format'] = 'JPEG';
  478. }
  479. if ($this->poster && ($poster = Asset\Image::getById($this->poster))) {
  480. return $poster->getThumbnail($imageThumbnailConf);
  481. }
  482. if (
  483. $asset->getCustomSetting('image_thumbnail_asset') &&
  484. ($customPreviewAsset = Asset\Image::getById($asset->getCustomSetting('image_thumbnail_asset')))
  485. ) {
  486. return $customPreviewAsset->getThumbnail($imageThumbnailConf);
  487. }
  488. return $asset->getImageThumbnail($imageThumbnailConf);
  489. }
  490. /**
  491. * @return string
  492. */
  493. private function getUrlCode()
  494. {
  495. return $this->getHtml5Code(['mp4' => (string) $this->id]);
  496. }
  497. /**
  498. * @param string $message
  499. *
  500. * @return string
  501. */
  502. private function getErrorCode($message = '')
  503. {
  504. $width = $this->getWidth();
  505. // If contains at least one digit (0-9), then assume it is a value that can be calculated,
  506. // otherwise it is likely be `auto`,`inherit`,etc..
  507. if (preg_match('/[\d]/', $width)) {
  508. // when is numeric, assume there are no length units nor %, and considering the value as pixels
  509. if (is_numeric($width)) {
  510. $width .= 'px';
  511. }
  512. $width = 'calc(' . $width . ' - 1px)';
  513. }
  514. $height = $this->getHeight();
  515. if (preg_match('/[\d]/', $height)) {
  516. if (is_numeric($height)) {
  517. $height .= 'px';
  518. }
  519. $height = 'calc(' . $height . ' - 1px)';
  520. }
  521. // only display error message in debug mode
  522. if (!\Pimcore::inDebugMode()) {
  523. $message = '';
  524. }
  525. $code = '
  526. <div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video">
  527. <div class="pimcore_editable_video_error" style="text-align:center; width: ' . $width . '; height: ' . $height . '; border:1px solid #000; background: url(/bundles/pimcoreadmin/img/filetype-not-supported.svg) no-repeat center center #fff;">
  528. ' . $message . '
  529. </div>
  530. </div>';
  531. return $code;
  532. }
  533. /**
  534. * @return string
  535. */
  536. private function parseYoutubeId()
  537. {
  538. $youtubeId = '';
  539. if ($this->type === self::TYPE_YOUTUBE) {
  540. if ($youtubeId = $this->id) {
  541. if (strpos($youtubeId, '//') !== false) {
  542. $parts = parse_url($this->id);
  543. if (array_key_exists('query', $parts)) {
  544. parse_str($parts['query'], $vars);
  545. if (isset($vars['v']) && $vars['v']) {
  546. $youtubeId = $vars['v'];
  547. }
  548. }
  549. //get youtube id if form urls like http://www.youtube.com/embed/youtubeId
  550. if (strpos($this->id, 'embed') !== false) {
  551. $explodedPath = explode('/', $parts['path']);
  552. $youtubeId = $explodedPath[array_search('embed', $explodedPath) + 1];
  553. }
  554. if (isset($parts['host']) && $parts['host'] === 'youtu.be') {
  555. $youtubeId = trim($parts['path'], ' /');
  556. }
  557. }
  558. }
  559. }
  560. return $youtubeId;
  561. }
  562. private function getYoutubeCode(bool $inAdmin = false): string
  563. {
  564. if (!$this->id) {
  565. return $this->getEmptyCode();
  566. }
  567. $config = $this->getConfig();
  568. $code = '';
  569. $youtubeId = $this->parseYoutubeId();
  570. if (!$youtubeId) {
  571. return $this->getEmptyCode();
  572. }
  573. if ($inAdmin && isset($config['editmodeImagePreview']) && $config['editmodeImagePreview'] === true) {
  574. return '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  575. <img src="https://img.youtube.com/vi/' . $youtubeId . '/0.jpg">
  576. </div>';
  577. }
  578. $width = '100%';
  579. if (array_key_exists('width', $config)) {
  580. $width = $config['width'];
  581. }
  582. $height = '300';
  583. if (array_key_exists('height', $config)) {
  584. $height = $config['height'];
  585. }
  586. $wmode = '?wmode=transparent';
  587. $seriesPrefix = '';
  588. if (strpos($youtubeId, 'PL') === 0) {
  589. $wmode = '';
  590. $seriesPrefix = 'videoseries?list=';
  591. }
  592. $valid_youtube_prams = [ 'autohide',
  593. 'autoplay',
  594. 'cc_load_policy',
  595. 'color',
  596. 'controls',
  597. 'disablekb',
  598. 'enablejsapi',
  599. 'end',
  600. 'fs',
  601. 'playsinline',
  602. 'hl',
  603. 'iv_load_policy',
  604. 'list',
  605. 'listType',
  606. 'loop',
  607. 'modestbranding',
  608. 'mute',
  609. 'origin',
  610. 'playerapiid',
  611. 'playlist',
  612. 'rel',
  613. 'showinfo',
  614. 'start',
  615. 'theme',
  616. ];
  617. $additional_params = '';
  618. $clipConfig = [];
  619. if (isset($config['config']['clip']) && is_array($config['config']['clip'])) {
  620. $clipConfig = $config['config']['clip'];
  621. }
  622. // this is to be backward compatible to <= v 1.4.7
  623. $configurations = $clipConfig;
  624. if (array_key_exists(self::TYPE_YOUTUBE, $config) && is_array($config[self::TYPE_YOUTUBE])) {
  625. $configurations = array_merge($clipConfig, $config[self::TYPE_YOUTUBE]);
  626. }
  627. if (!empty($configurations)) {
  628. foreach ($configurations as $key => $value) {
  629. if (in_array($key, $valid_youtube_prams)) {
  630. if (is_bool($value)) {
  631. if ($value) {
  632. $additional_params .= '&'.$key.'=1';
  633. } else {
  634. $additional_params .= '&'.$key.'=0';
  635. }
  636. } else {
  637. $additional_params .= '&'.$key.'='.$value;
  638. }
  639. }
  640. }
  641. }
  642. $code .= '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  643. <iframe width="' . $width . '" height="' . $height . '" src="https://www.youtube-nocookie.com/embed/' . $seriesPrefix . $youtubeId . $wmode . $additional_params .'" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen allow="fullscreen" data-type="pimcore_video_editable"></iframe>
  644. </div>';
  645. return $code;
  646. }
  647. private function getVimeoCode(bool $inAdmin = false): string
  648. {
  649. if (!$this->id) {
  650. return $this->getEmptyCode();
  651. }
  652. $config = $this->getConfig();
  653. $code = '';
  654. $uid = 'video_' . uniqid();
  655. // get vimeo id
  656. if (preg_match("@vimeo.*/([\d]+)@i", $this->id, $matches)) {
  657. $vimeoId = (int)$matches[1];
  658. } else {
  659. // for object-videos
  660. $vimeoId = $this->id;
  661. }
  662. if (ctype_digit($vimeoId)) {
  663. if ($inAdmin && isset($config['editmodeImagePreview']) && $config['editmodeImagePreview'] === true) {
  664. return '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  665. <img src="https://vumbnail.com/' . $vimeoId . '.jpg">
  666. </div>';
  667. }
  668. $width = '100%';
  669. if (array_key_exists('width', $config)) {
  670. $width = $config['width'];
  671. }
  672. $height = '300';
  673. if (array_key_exists('height', $config)) {
  674. $height = $config['height'];
  675. }
  676. $valid_vimeo_prams = [
  677. 'autoplay',
  678. 'background',
  679. 'loop',
  680. 'muted',
  681. ];
  682. $additional_params = '';
  683. $clipConfig = [];
  684. if (isset($config['config']['clip']) && is_array($config['config']['clip'])) {
  685. $clipConfig = $config['config']['clip'];
  686. }
  687. // this is to be backward compatible to <= v 1.4.7
  688. $configurations = $clipConfig;
  689. if (isset($config[self::TYPE_VIMEO]) && is_array($config[self::TYPE_VIMEO])) {
  690. $configurations = array_merge($clipConfig, $config[self::TYPE_VIMEO]);
  691. }
  692. if (!empty($configurations)) {
  693. foreach ($configurations as $key => $value) {
  694. if (in_array($key, $valid_vimeo_prams)) {
  695. if (is_bool($value)) {
  696. if ($value) {
  697. $additional_params .= '&'.$key.'=1';
  698. } else {
  699. $additional_params .= '&'.$key.'=0';
  700. }
  701. } else {
  702. $additional_params .= '&'.$key.'='.$value;
  703. }
  704. }
  705. }
  706. }
  707. $code .= '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  708. <iframe src="https://player.vimeo.com/video/' . $vimeoId . '?dnt=1&title=0&amp;byline=0&amp;portrait=0'. $additional_params .'" width="' . $width . '" height="' . $height . '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen allow="fullscreen" data-type="pimcore_video_editable"></iframe>
  709. </div>';
  710. return $code;
  711. }
  712. // default => return the empty code
  713. return $this->getEmptyCode();
  714. }
  715. private function getDailymotionCode(bool $inAdmin = false): string
  716. {
  717. if (!$this->id) {
  718. return $this->getEmptyCode();
  719. }
  720. $config = $this->getConfig();
  721. $code = '';
  722. $uid = 'video_' . uniqid();
  723. // get dailymotion id
  724. if (preg_match('@dailymotion.*/video/([^_]+)@i', $this->id, $matches)) {
  725. $dailymotionId = $matches[1];
  726. } else {
  727. // for object-videos
  728. $dailymotionId = $this->id;
  729. }
  730. if ($dailymotionId) {
  731. if ($inAdmin && isset($config['editmodeImagePreview']) && $config['editmodeImagePreview'] === true) {
  732. return '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  733. <img src="https://www.dailymotion.com/thumbnail/video/' . $dailymotionId . '">
  734. </div>';
  735. }
  736. $width = $config['width'] ?? '100%';
  737. $height = $config['height'] ?? '300';
  738. $valid_dailymotion_prams = [
  739. 'autoplay',
  740. 'loop',
  741. 'mute', ];
  742. $additional_params = '';
  743. $clipConfig = isset($config['config']['clip']) && is_array($config['config']['clip']) ? $config['config']['clip'] : [];
  744. // this is to be backward compatible to <= v 1.4.7
  745. $configurations = $clipConfig;
  746. if (isset($config[self::TYPE_DAILYMOTION]) && is_array($config[self::TYPE_DAILYMOTION])) {
  747. $configurations = array_merge($clipConfig, $config[self::TYPE_DAILYMOTION]);
  748. }
  749. if (!empty($configurations)) {
  750. foreach ($configurations as $key => $value) {
  751. if (in_array($key, $valid_dailymotion_prams)) {
  752. if (is_bool($value)) {
  753. if ($value) {
  754. $additional_params .= '&'.$key.'=1';
  755. } else {
  756. $additional_params .= '&'.$key.'=0';
  757. }
  758. } else {
  759. $additional_params .= '&'.$key.'='.$value;
  760. }
  761. }
  762. }
  763. }
  764. $code .= '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video '. ($config['class'] ?? '') .'">
  765. <iframe src="https://www.dailymotion.com/embed/video/' . $dailymotionId . '?' . $additional_params .'" width="' . $width . '" height="' . $height . '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen allow="fullscreen" data-type="pimcore_video_editable"></iframe>
  766. </div>';
  767. return $code;
  768. }
  769. // default => return the empty code
  770. return $this->getEmptyCode();
  771. }
  772. /**
  773. * @param array $urls
  774. * @param Asset\Image\Thumbnail|Asset\Video\ImageThumbnail|null $thumbnail
  775. *
  776. * @return string
  777. */
  778. private function getHtml5Code($urls = [], $thumbnail = null)
  779. {
  780. $code = '';
  781. $video = $this->getVideoAsset();
  782. if ($video) {
  783. $code .= '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video">' . "\n";
  784. $uploadDate = new \DateTime();
  785. $uploadDate->setTimestamp($video->getCreationDate());
  786. $jsonLd = [
  787. '@context' => 'https://schema.org',
  788. '@type' => 'VideoObject',
  789. 'name' => $this->getTitle(),
  790. 'description' => $this->getDescription(),
  791. 'uploadDate' => $uploadDate->format('Y-m-d\TH:i:sO'),
  792. //'contentUrl' => Tool::getHostUrl() . $urls['mp4'],
  793. //"embedUrl" => "http://www.example.com/videoplayer.swf?video=123",
  794. //"interactionCount" => "1234",
  795. ];
  796. $duration = $video->getDuration();
  797. if ($duration !== null) {
  798. $jsonLd['duration'] = $this->getDurationString($duration);
  799. }
  800. if (!$thumbnail) {
  801. $thumbnail = $video->getImageThumbnail([]);
  802. }
  803. $jsonLd['contentUrl'] = $urls['mp4'];
  804. if (!preg_match('@https?://@', $urls['mp4'])) {
  805. $jsonLd['contentUrl'] = Tool::getHostUrl() . $urls['mp4'];
  806. }
  807. $thumbnailUrl = (string)$thumbnail;
  808. $jsonLd['thumbnailUrl'] = $thumbnailUrl;
  809. if (!preg_match('@https?://@', $thumbnailUrl)) {
  810. $jsonLd['thumbnailUrl'] = Tool::getHostUrl() . $thumbnailUrl;
  811. }
  812. $code .= "\n\n<script type=\"application/ld+json\">\n" . json_encode($jsonLd) . "\n</script>\n\n";
  813. // default attributes
  814. $attributesString = '';
  815. $attributes = [
  816. 'width' => $this->getWidth(),
  817. 'height' => $this->getHeight(),
  818. 'poster' => $thumbnailUrl,
  819. 'controls' => 'controls',
  820. 'class' => 'pimcore_video',
  821. ];
  822. $config = $this->getConfig();
  823. if (array_key_exists('attributes', $config)) {
  824. $attributes = array_merge($attributes, $config['attributes']);
  825. }
  826. if (isset($config['removeAttributes']) && is_array($config['removeAttributes'])) {
  827. foreach ($config['removeAttributes'] as $attribute) {
  828. unset($attributes[$attribute]);
  829. }
  830. }
  831. // do not allow an empty controls editable
  832. if (isset($attributes['controls']) && !$attributes['controls']) {
  833. unset($attributes['controls']);
  834. }
  835. if (isset($urls['mpd'])) {
  836. $attributes['data-dashjs-player'] = null;
  837. }
  838. foreach ($attributes as $key => $value) {
  839. $attributesString .= ' ' . $key;
  840. if (!empty($value)) {
  841. $quoteChar = '"';
  842. if (is_string($value) && strpos($value, '"')) {
  843. $quoteChar = "'";
  844. }
  845. $attributesString .= '=' . $quoteChar . $value . $quoteChar;
  846. }
  847. }
  848. $code .= '<video' . $attributesString . '>' . "\n";
  849. foreach ($urls as $type => $url) {
  850. if ($type == 'medias') {
  851. foreach ($url as $format => $medias) {
  852. foreach ($medias as $media => $mediaUrl) {
  853. $code .= '<source type="video/' . $format . '" src="' . $mediaUrl . '" media="' . $media . '" />' . "\n";
  854. }
  855. }
  856. } else {
  857. $code .= '<source type="video/' . $type . '" src="' . $url . '" />' . "\n";
  858. }
  859. }
  860. $code .= '</video>' . "\n";
  861. $code .= '</div>' . "\n";
  862. }
  863. return $code;
  864. }
  865. private function getDurationString(float $duration): string
  866. {
  867. $duration = ceil($duration);
  868. $durationParts = ['PT'];
  869. // hours
  870. if ($duration / 3600 >= 1) {
  871. $hours = floor($duration / 3600);
  872. $durationParts[] = $hours . 'H';
  873. $duration = $duration - $hours * 3600;
  874. }
  875. // minutes
  876. if ($duration / 60 >= 1) {
  877. $minutes = floor($duration / 60);
  878. $durationParts[] = $minutes . 'M';
  879. $duration = $duration - $minutes * 60;
  880. }
  881. $durationParts[] = $duration . 'S';
  882. return implode('', $durationParts);
  883. }
  884. /**
  885. * @param string|null $thumbnail
  886. *
  887. * @return string
  888. */
  889. private function getProgressCode($thumbnail = null)
  890. {
  891. $uid = 'video_' . uniqid();
  892. $code = '
  893. <div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video">
  894. <style type="text/css">
  895. #' . $uid . ' .pimcore_editable_video_progress_status {
  896. box-sizing:content-box;
  897. background:#fff url(/bundles/pimcoreadmin/img/video-loading.gif) center center no-repeat;
  898. width:66px;
  899. height:66px;
  900. padding:20px;
  901. border:1px solid #555;
  902. box-shadow: 2px 2px 5px #333;
  903. border-radius:20px;
  904. margin: 0 20px 0 20px;
  905. top: calc(50% - 66px);
  906. left: calc(50% - 66px);
  907. position:absolute;
  908. opacity: 0.8;
  909. }
  910. </style>
  911. <div class="pimcore_editable_video_progress" id="' . $uid . '">
  912. <img src="' . $thumbnail . '" style="width: ' . $this->getWidthWithUnit() . '; height: ' . $this->getHeightWithUnit() . ';">
  913. <div class="pimcore_editable_video_progress_status"></div>
  914. </div>
  915. </div>';
  916. return $code;
  917. }
  918. /**
  919. * @return string
  920. */
  921. private function getEmptyCode(): string
  922. {
  923. $uid = 'video_' . uniqid();
  924. return '<div id="pimcore_video_' . $this->getName() . '" class="pimcore_editable_video"><div class="pimcore_editable_video_empty" id="' . $uid . '" style="width: ' . $this->getWidthWithUnit() . '; height: ' . $this->getHeightWithUnit() . ';"></div></div>';
  925. }
  926. private function updateAllowedTypesFromConfig(array $config): void
  927. {
  928. $this->allowedTypes = self::ALLOWED_TYPES;
  929. if (
  930. isset($config['allowedTypes']) === true
  931. && empty($config['allowedTypes']) === false
  932. && empty(array_diff($config['allowedTypes'], self::ALLOWED_TYPES))
  933. ) {
  934. $this->allowedTypes = $config['allowedTypes'];
  935. }
  936. }
  937. /**
  938. * {@inheritdoc}
  939. */
  940. public function isEmpty()
  941. {
  942. if ($this->id) {
  943. return false;
  944. }
  945. return true;
  946. }
  947. /**
  948. * @return string
  949. */
  950. public function getVideoType()
  951. {
  952. if (empty($this->type) === true) {
  953. $this->type = $this->getAllowedTypes()[0];
  954. }
  955. return $this->type;
  956. }
  957. /**
  958. * @return Asset\Video|null
  959. */
  960. public function getVideoAsset()
  961. {
  962. if ($this->getVideoType() === self::TYPE_ASSET) {
  963. return Asset\Video::getById($this->id);
  964. }
  965. return null;
  966. }
  967. /**
  968. * @return Asset\Image|null
  969. */
  970. public function getPosterAsset()
  971. {
  972. return Asset\Image::getById($this->poster);
  973. }
  974. /**
  975. * @param string|Asset\Video\Thumbnail\Config $config
  976. *
  977. * @return Asset\Image\Thumbnail|Asset\Video\ImageThumbnail|string
  978. *
  979. * TODO Pimcore 11: Change empty string return to null
  980. */
  981. public function getImageThumbnail($config)
  982. {
  983. if ($this->poster && ($poster = Asset\Image::getById($this->poster))) {
  984. return $poster->getThumbnail($config);
  985. }
  986. if ($this->getVideoAsset()) {
  987. return $this->getVideoAsset()->getImageThumbnail($config);
  988. }
  989. return '';
  990. }
  991. /**
  992. * @param string|Asset\Video\Thumbnail\Config $config
  993. *
  994. * @return array
  995. */
  996. public function getThumbnail($config)
  997. {
  998. if ($this->getVideoAsset()) {
  999. return $this->getVideoAsset()->getThumbnail($config);
  1000. }
  1001. return [];
  1002. }
  1003. /**
  1004. * {@inheritdoc}
  1005. */
  1006. public function rewriteIds($idMapping) /** : void */
  1007. {
  1008. if ($this->type == self::TYPE_ASSET && array_key_exists(self::TYPE_ASSET, $idMapping) && array_key_exists($this->getId(), $idMapping[self::TYPE_ASSET])) {
  1009. $this->setId($idMapping[self::TYPE_ASSET][$this->getId()]);
  1010. }
  1011. }
  1012. }