AudioFieldPluginBase.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <?php
  2. namespace Drupal\audiofield;
  3. use Drupal\Core\Link;
  4. use Drupal\Core\Url;
  5. use Drupal\file\FileInterface;
  6. use Drupal\Core\Field\FieldItemListInterface;
  7. use Drupal\Component\Utility\UrlHelper;
  8. use Drupal\Component\Utility\Random;
  9. use Drupal\Component\Plugin\PluginBase;
  10. /**
  11. * Base class for audiofield plugins. Includes global functions.
  12. */
  13. abstract class AudioFieldPluginBase extends PluginBase {
  14. /**
  15. * Renders the player.
  16. *
  17. * @param \Drupal\Core\Field\FieldItemListInterface $items
  18. * The uploaded item list.
  19. * @param string $langcode
  20. * The language code.
  21. * @param array $settings
  22. * An array of additional render settings.
  23. *
  24. * @return array
  25. * Returns the rendered array.
  26. */
  27. abstract public function renderPlayer(FieldItemListInterface $items, $langcode, array $settings);
  28. /**
  29. * Gets the plugin_id of the plugin instance.
  30. *
  31. * @return string
  32. * The plugin_id of the plugin instance.
  33. */
  34. public function getPluginId() {
  35. return $this->pluginDefinition['id'];
  36. }
  37. /**
  38. * Gets the title of the plugin instance.
  39. *
  40. * @return string
  41. * The title of the plugin instance.
  42. */
  43. public function getPluginTitle() {
  44. return $this->pluginDefinition['title'];
  45. }
  46. /**
  47. * Gets the name of the main library of the plugin instance.
  48. *
  49. * @return string
  50. * The name of the main library of the plugin instance.
  51. */
  52. public function getPluginLibraryName() {
  53. return $this->pluginDefinition['libraryName'];
  54. }
  55. /**
  56. * Gets the main library instance of this plugin.
  57. *
  58. * @return array
  59. * The definition of the main library for this plugin.
  60. */
  61. public function getPluginLibrary() {
  62. // Get the main library for this plugin.
  63. return \Drupal::service('library.discovery')->getLibraryByName('audiofield', 'audiofield.' . $this->getPluginLibraryName());
  64. }
  65. /**
  66. * Parses library to get version number of installed library.
  67. *
  68. * @return string
  69. * The version number of the currently installed library.
  70. */
  71. public function getPluginLibraryVersion() {
  72. // Default to 1. This is implemented in the plugins.
  73. return '1';
  74. }
  75. /**
  76. * Gets the location of this plugin's library installation.
  77. *
  78. * @return string
  79. * The library path of the plugin instance.
  80. */
  81. public function getPluginLibraryPath() {
  82. // Get the main library for this plugin.
  83. $library = $this->getPluginLibrary();
  84. return preg_filter('%(^libraries/[^//]+).*%', '/$1', $library['js'][0]['data']);
  85. }
  86. /**
  87. * Gets the remote download source from the plugin's main library.
  88. *
  89. * @return string
  90. * The remote download source of the plugin instance.
  91. */
  92. public function getPluginRemoteSource() {
  93. // Get the main library for this plugin.
  94. $library = $this->getPluginLibrary();
  95. return $library['remote'];
  96. }
  97. /**
  98. * Gets the definition of the plugin implementation.
  99. *
  100. * @return array
  101. * The plugin definition, as returned by the discovery object used by the
  102. * plugin manager.
  103. */
  104. public function getPluginDefinition() {
  105. return t('@title: @description. Plugin library can be found at %librarySource.', [
  106. '@title' => $this->getPluginTitle(),
  107. '@description' => $this->pluginDefinition['description'],
  108. '%librarySource' => $this->getPluginRemoteSource(),
  109. ]);
  110. }
  111. /**
  112. * Checks to see if this audio plugin has been properly installed.
  113. *
  114. * @param bool $log_error
  115. * Flag to indicate whether or not alert should be logged/shown.
  116. *
  117. * @return bool
  118. * Returns a boolean indicating install state.
  119. */
  120. public function checkInstalled($log_error = TRUE) {
  121. // Get the main library for this plugin.
  122. $library = $this->getPluginLibrary();
  123. // Check if the library is installed.
  124. if (file_exists(DRUPAL_ROOT . '/' . $library['js'][0]['data'])) {
  125. // Check to make sure the installed version is up to date.
  126. $this->checkVersion($log_error);
  127. return TRUE;
  128. }
  129. return FALSE;
  130. }
  131. /**
  132. * Checks to see if this audio plugin version is up to date.
  133. *
  134. * @param bool $log_error
  135. * Flag to indicate whether or not alert should be logged/shown.
  136. *
  137. * @return bool
  138. * Returns a boolean indicating if version is up to date.
  139. */
  140. public function checkVersion($log_error = TRUE) {
  141. // Get the main library for this plugin.
  142. $library = $this->getPluginLibrary();
  143. // Check to make sure the installed version is up to date.
  144. if (version_compare($this->getPluginLibraryVersion(), $library['version'], '<')) {
  145. // Log the warning if necessary.
  146. if ($log_error) {
  147. $message_data = [
  148. '@plugin' => $this->getPluginTitle(),
  149. '@version' => $this->getPluginLibraryVersion(),
  150. '@newversion' => $library['version'],
  151. '@download-link' => Link::fromTextAndUrl($this->getPluginRemoteSource(), Url::fromUri($this->getPluginRemoteSource()))->toString(),
  152. '%command' => 'drush audiofield-update',
  153. '@status_report' => Link::createFromRoute('status report', 'system.status')->toString(),
  154. ];
  155. \Drupal::logger('audiofield')->warning('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data);
  156. drupal_set_message(t('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data), 'warning');
  157. }
  158. return FALSE;
  159. }
  160. return TRUE;
  161. }
  162. /**
  163. * Shows library installation errors for in-use libraries.
  164. */
  165. public function showInstallError() {
  166. $message_data = [
  167. '@library_name' => $this->getPluginLibraryName(),
  168. '@status_report' => Link::createFromRoute('status report', 'system.status')->toString(),
  169. ];
  170. \Drupal::logger('audiofield')->error('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data);
  171. drupal_set_message(t('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data), 'error');
  172. }
  173. /**
  174. * Validate that a file entity will work with this player.
  175. *
  176. * @param \Drupal\file\FileInterface $file
  177. * A file entity.
  178. *
  179. * @return bool
  180. * Indicates if the file is valid for this player or not.
  181. */
  182. private function validateFileAgainstPlayer(FileInterface $file) {
  183. // Validate the file extension agains the list of valid extensions.
  184. $errors = file_validate_extensions($file, implode(' ', $this->pluginDefinition["fileTypes"]));
  185. if (count($errors) > 0) {
  186. $message_data = [
  187. '%filename' => $file->getFilename(),
  188. '@player' => $this->getPluginLibraryName(),
  189. '%extensions' => implode(', ', $this->pluginDefinition["fileTypes"]),
  190. ];
  191. \Drupal::logger('audiofield')->error('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data);
  192. drupal_set_message(t('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data), 'error');
  193. return FALSE;
  194. }
  195. return TRUE;
  196. }
  197. /**
  198. * Validate that a link entity will work with this player.
  199. *
  200. * @param \Drupal\Core\Url $link
  201. * Url to the link.
  202. *
  203. * @return bool
  204. * Indicates if the link is valid for this player or not.
  205. */
  206. private function validateLinkAgainstPlayer(Url $link) {
  207. // Check for a valid link and a valid extension.
  208. $extension = pathinfo($link->toString(), PATHINFO_EXTENSION);
  209. if (!UrlHelper::isValid($link->toString(), FALSE) ||empty($extension)) {
  210. // We are currently not validating file types for links.
  211. $message_data = [
  212. '%link' => $link->toString(),
  213. ];
  214. \Drupal::logger('audiofield')->error('Error playing file: invalid link: %link', $message_data);
  215. drupal_set_message(t('Error playing file: invalid link: %link', $message_data), 'error');
  216. return FALSE;
  217. }
  218. return TRUE;
  219. }
  220. /**
  221. * Get the class type for an entity.
  222. *
  223. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  224. * Item for which we are determining class type.
  225. *
  226. * @return string
  227. * The class type for the item.
  228. */
  229. protected function getClassType($item) {
  230. // Handle File entity.
  231. if (get_class($item) == 'Drupal\file\Plugin\Field\FieldType\FileItem') {
  232. return 'FileItem';
  233. }
  234. // Handle Link entity.
  235. elseif (get_class($item) == 'Drupal\link\Plugin\Field\FieldType\LinkItem') {
  236. return 'LinkItem';
  237. }
  238. return NULL;
  239. }
  240. /**
  241. * Validate that this entity will work with this player.
  242. *
  243. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  244. * Item which we are validating works with the player.
  245. *
  246. * @return bool
  247. * Indicates if the entity is valid for this player or not.
  248. */
  249. protected function validateEntityAgainstPlayer($item) {
  250. // Handle File entity.
  251. if ($this->getClassType($item) == 'FileItem') {
  252. // Load the associated file.
  253. $file = $this->loadFileFromItem($item);
  254. // Validate that this file will work with this player.
  255. return $this->validateFileAgainstPlayer($file);
  256. }
  257. // Handle Link entity.
  258. elseif ($this->getClassType($item) == 'LinkItem') {
  259. // Get the source URL for this item.
  260. $source_url = $this->getAudioSource($item);
  261. // Validate that this link will work with this player.
  262. return $this->validateLinkAgainstPlayer($source_url);
  263. }
  264. return FALSE;
  265. }
  266. /**
  267. * Load a file from an audio file entity.
  268. *
  269. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  270. * Item for which we are loading the file entity.
  271. *
  272. * @return \Drupal\file\FileInterface
  273. * FileInterface from the item.
  274. */
  275. private function loadFileFromItem($item) {
  276. // Load the associated file.
  277. return file_load($item->get('target_id')->getCastedValue());
  278. }
  279. /**
  280. * Get a unique ID for an item.
  281. *
  282. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  283. * Item for which we are generating a unique ID.
  284. *
  285. * @return string
  286. * Unique ID for the item.
  287. */
  288. private function getUniqueId($item) {
  289. // Used to generate unique container.
  290. $random_generator = new Random();
  291. // Handle File entity.
  292. if ($this->getClassType($item) == 'FileItem') {
  293. // Load the associated file.
  294. $file = $this->loadFileFromItem($item);
  295. // Craft a unique ID.
  296. return 'file_' . $file->get('fid')->getValue()[0]['value'] . '_' . $random_generator->name(16, TRUE);
  297. }
  298. // Handle Link entity.
  299. elseif ($this->getClassType($item) == 'LinkItem') {
  300. // Craft a unique ID.
  301. return 'item_' . $random_generator->name(16, TRUE);
  302. }
  303. return $random_generator->name(16, TRUE);
  304. }
  305. /**
  306. * Get the filetype for an item.
  307. *
  308. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  309. * Item for which we are determining filetype.
  310. *
  311. * @return string
  312. * Filetype for the item.
  313. */
  314. private function getFiletype($item) {
  315. // Handle File entity.
  316. if ($this->getClassType($item) == 'FileItem') {
  317. // Load the associated file.
  318. $file = $this->loadFileFromItem($item);
  319. return pathinfo($file->getFilename(), PATHINFO_EXTENSION);
  320. }
  321. // Handle Link entity.
  322. elseif ($this->getClassType($item) == 'LinkItem') {
  323. return pathinfo($this->getAudioSource($item)->toString(), PATHINFO_EXTENSION);
  324. }
  325. return '';
  326. }
  327. /**
  328. * Get source URL from an audiofield entity.
  329. *
  330. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  331. * Item for which we are determining source.
  332. *
  333. * @return string
  334. * The source URL of an entity.
  335. */
  336. private function getAudioSource($item) {
  337. $source_url = '';
  338. if ($this->getClassType($item) == 'FileItem') {
  339. // Load the associated file.
  340. $file = $this->loadFileFromItem($item);
  341. // Get the file URL.
  342. $source_url = Url::fromUri(file_create_url($file->getFileUri()));
  343. }
  344. // Handle Link entity.
  345. elseif ($this->getClassType($item) == 'LinkItem') {
  346. // Get the file URL.
  347. $source_url = $item->getUrl();
  348. }
  349. return $source_url;
  350. }
  351. /**
  352. * Get a title description from an audiofield entity.
  353. *
  354. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  355. * Item for which a title is being generated.
  356. *
  357. * @return string
  358. * The description of an entity.
  359. */
  360. private function getAudioDescription($item) {
  361. $entity_description = '';
  362. if ($this->getClassType($item) == 'FileItem') {
  363. // Get the file description - use the filename if it doesn't exist.
  364. $entity_description = $item->get('description')->getString();
  365. if (empty($entity_description)) {
  366. // Load the associated file.
  367. $file = $this->loadFileFromItem($item);
  368. $entity_description = $file->getFilename();
  369. }
  370. }
  371. // Handle Link entity.
  372. elseif ($this->getClassType($item) == 'LinkItem') {
  373. // Get the file description - use the filename if it doesn't exist.
  374. $entity_description = $item->get('title')->getString();
  375. if (empty($entity_description)) {
  376. $entity_description = $item->getUrl()->toString();
  377. }
  378. }
  379. return $entity_description;
  380. }
  381. /**
  382. * Get required rendering information from an entity.
  383. *
  384. * @param \Drupal\file\Plugin\Field\FieldType\FileItem|\Drupal\link\Plugin\Field\FieldType\LinkItem $item
  385. * Item for which we are getting render information.
  386. *
  387. * @return object
  388. * Render information for an item.
  389. */
  390. public function getAudioRenderInfo($item) {
  391. return (object) [
  392. 'description' => $this->getAudioDescription($item),
  393. 'url' => $this->getAudioSource($item),
  394. 'id' => $this->getUniqueId($item),
  395. 'filetype' => $this->getFiletype($item),
  396. ];
  397. }
  398. /**
  399. * Used to format file entities for use in the twig themes.
  400. *
  401. * @param object $items
  402. * A list of items for which we need to generate render information.
  403. * @param int $limit
  404. * An upper limit for the number of files to return. 0 indicates unlimited.
  405. *
  406. * @return array
  407. * A render array containing files in the proper format for rendering.
  408. */
  409. public function getItemRenderList($items, $limit = 0) {
  410. $template_files = [];
  411. foreach ($items as $item) {
  412. // If this entity has passed validation, we render it.
  413. if ($this->validateEntityAgainstPlayer($item)) {
  414. // Get render information for this item.
  415. $renderInfo = $this->getAudioRenderInfo($item);
  416. // Add the file to the render array.
  417. $template_files[] = $renderInfo;
  418. // Return list if we have hit the limit.
  419. if ($limit > 0 && count($template_files) == $limit) {
  420. return $template_files;
  421. }
  422. }
  423. }
  424. return $template_files;
  425. }
  426. /**
  427. * Used to render list of downloads as an item list.
  428. *
  429. * @param object $items
  430. * A list of items for which we need to generate download links..
  431. * @param array $settings
  432. * An array of additional render settings.
  433. *
  434. * @return array
  435. * A render array containing download links.
  436. */
  437. public function createDownloadList($items, array $settings) {
  438. $download_links = [];
  439. // Check if download links are turned on.
  440. if ($settings['download_link']) {
  441. // Loop over each item.
  442. foreach ($items as $item) {
  443. // Get the source URL for this item.
  444. $source_url = $this->getAudioSource($item);
  445. // Get the entity description for this item.
  446. $entity_description = $this->getAudioDescription($item);
  447. // Add the link.
  448. $download_links[] = [
  449. '#markup' => Link::fromTextAndUrl($entity_description, $source_url)->toString(),
  450. '#wrapper_attributes' => [
  451. 'class' => [
  452. 'audiofield-download-link',
  453. ],
  454. ],
  455. ];
  456. }
  457. }
  458. // Render links if we have them.
  459. $download_render_array = [];
  460. if (count($download_links) > 0) {
  461. $download_render_array = [
  462. '#theme' => 'item_list',
  463. '#list_type' => 'ul',
  464. '#title' => t('Download files:'),
  465. '#wrapper_attributes' => [
  466. 'class' => [
  467. 'audiofield-downloads',
  468. ],
  469. ],
  470. '#attributes' => [],
  471. '#empty' => '',
  472. '#items' => $download_links,
  473. ];
  474. }
  475. return [
  476. '#theme' => 'audiofield_download_links',
  477. '#links' => $download_render_array,
  478. ];
  479. }
  480. }