From 681bb09ad7739f50a44654531f1a6dc58e616fd1 Mon Sep 17 00:00:00 2001 From: josue2591 Date: Fri, 12 Sep 2025 16:09:36 -0600 Subject: [PATCH 1/3] feat(EMULSIF-531): favicon support added --- .../emulsify_tools_favicon.settings.yml | 3 + .../schema/emulsify_tools_favicon.schema.yml | 20 ++ emulsify_tools.info.yml | 3 + emulsify_tools.links.action.yml | 7 + emulsify_tools.links.menu.yml | 6 + emulsify_tools.module | 75 ++++++ emulsify_tools.services.yml | 3 + src/Entity/Favicon.php | 237 ++++++++++++++++++ src/Entity/FaviconInterface.php | 34 +++ src/FaviconHtmlRouteProvider.php | 39 +++ src/FaviconListBuilder.php | 82 ++++++ src/FaviconManager.php | 104 ++++++++ src/FaviconManagerInterface.php | 39 +++ src/Form/FaviconDeleteForm.php | 39 +++ src/Form/FaviconForm.php | 106 ++++++++ src/Form/FaviconSettingsForm.php | 57 +++++ 16 files changed, 854 insertions(+) create mode 100644 config/install/emulsify_tools_favicon.settings.yml create mode 100644 config/schema/emulsify_tools_favicon.schema.yml create mode 100644 emulsify_tools.links.action.yml create mode 100644 emulsify_tools.links.menu.yml create mode 100644 emulsify_tools.module create mode 100644 src/Entity/Favicon.php create mode 100644 src/Entity/FaviconInterface.php create mode 100644 src/FaviconHtmlRouteProvider.php create mode 100644 src/FaviconListBuilder.php create mode 100644 src/FaviconManager.php create mode 100644 src/FaviconManagerInterface.php create mode 100644 src/Form/FaviconDeleteForm.php create mode 100644 src/Form/FaviconForm.php create mode 100644 src/Form/FaviconSettingsForm.php diff --git a/config/install/emulsify_tools_favicon.settings.yml b/config/install/emulsify_tools_favicon.settings.yml new file mode 100644 index 0000000..1a2d49b --- /dev/null +++ b/config/install/emulsify_tools_favicon.settings.yml @@ -0,0 +1,3 @@ +themes: [] + + diff --git a/config/schema/emulsify_tools_favicon.schema.yml b/config/schema/emulsify_tools_favicon.schema.yml new file mode 100644 index 0000000..a39770e --- /dev/null +++ b/config/schema/emulsify_tools_favicon.schema.yml @@ -0,0 +1,20 @@ +emulsify_tools_favicon.emulsify_tools_favicon.*: + type: config_entity + label: 'Favicon config' + mapping: + id: + type: string + label: 'ID' + label: + type: label + label: 'Label' + uuid: + type: string + tags: + type: sequence + label: 'Tags' + archive: + type: sequence + label: 'Archive' + + diff --git a/emulsify_tools.info.yml b/emulsify_tools.info.yml index 7a51d70..5071d2a 100644 --- a/emulsify_tools.info.yml +++ b/emulsify_tools.info.yml @@ -4,6 +4,9 @@ description: Toolset of useful Twig extensions and a subtheme generation command core_version_requirement: ^10 || ^11 package: Emulsify +dependencies: + - drupal:file + # Information added by Drupal.org packaging script on 2023-02-16 version: '1.0.0' project: 'emulsify_tools' diff --git a/emulsify_tools.links.action.yml b/emulsify_tools.links.action.yml new file mode 100644 index 0000000..9a9310f --- /dev/null +++ b/emulsify_tools.links.action.yml @@ -0,0 +1,7 @@ +entity.emulsify_tools_favicon.add_form: + route_name: 'entity.emulsify_tools_favicon.add_form' + title: 'Add Favicon Package' + appears_on: + - entity.emulsify_tools_favicon.collection + + diff --git a/emulsify_tools.links.menu.yml b/emulsify_tools.links.menu.yml new file mode 100644 index 0000000..edafc4b --- /dev/null +++ b/emulsify_tools.links.menu.yml @@ -0,0 +1,6 @@ +entity.emulsify_tools_favicon.collection: + title: 'Favicons' + route_name: entity.emulsify_tools_favicon.collection + description: 'List Favicon packages' + parent: system.admin_structure + weight: 99 diff --git a/emulsify_tools.module b/emulsify_tools.module new file mode 100644 index 0000000..9ff454a --- /dev/null +++ b/emulsify_tools.module @@ -0,0 +1,75 @@ +' . t('About') . ''; + $output .= '

' . t('Emulsify Tools provides helpful developer utilities, including favicon management from realfavicongenerator.net.') . '

'; + return $output; + + default: + } +} + +/** + * Implements hook_page_attachments_alter(). + */ +function emulsify_tools_page_attachments_alter(array &$attachments) { + $theme = \Drupal::theme()->getActiveTheme()->getName(); + $faviconManager = \Drupal::service('emulsify_tools.favicon_manager'); + + if ($tags = $faviconManager->getTags($theme)) { + // Remove default favicon from html_head_link. + if (!empty($attachments['#attached']['html_head_link'])) { + foreach ($attachments['#attached']['html_head_link'] as $i => $item) { + if (!empty($item) && is_array($item)) { + foreach ($item as $ii => $iitem) { + if (isset($iitem['rel']) && in_array($iitem['rel'], ['shortcut icon', 'icon'])) { + unset($attachments['#attached']['html_head_link'][$i][$ii]); + } + } + if (empty($attachments['#attached']['html_head_link'][$i])) { + unset($attachments['#attached']['html_head_link'][$i]); + } + } + } + if (empty($attachments['#attached']['html_head_link'])) { + unset($attachments['#attached']['html_head_link']); + } + } + // Attach favicon tags. + $attachments['#attached']['html_head'][] = [ + [ + '#type' => 'markup', + '#markup' => $tags, + '#allowed_tags' => ['link', 'meta'], + '#cache' => [ + 'tags' => $faviconManager->getCacheTags(), + ], + ], + 'emulsify_tools_favicon', + ]; + } +} + +/** + * Load favicon by theme. + * + * @param string $theme_id + * The theme id. + * + * @return \Drupal\emulsify_tools\Entity\Favicon|null + * The Favicon entity. + */ +function emulsify_tools_favicon_load_by_theme($theme_id = NULL) { + if (empty($active_theme)) { + $active_theme = \Drupal::theme()->getActiveTheme()->getName(); + } + return \Drupal::service('emulsify_tools.favicon_manager')->loadFavicon($active_theme); +} diff --git a/emulsify_tools.services.yml b/emulsify_tools.services.yml index 238d8dc..43428aa 100644 --- a/emulsify_tools.services.yml +++ b/emulsify_tools.services.yml @@ -16,3 +16,6 @@ services: - { name: drush.command } emulsify_tools.subtheme_generator: class: Drupal\emulsify_tools\SubThemeGenerator + emulsify_tools.favicon_manager: + class: Drupal\emulsify_tools\FaviconManager + arguments: ['@entity_type.manager', '@config.factory', '@cache.data'] diff --git a/src/Entity/Favicon.php b/src/Entity/Favicon.php new file mode 100644 index 0000000..59eecc8 --- /dev/null +++ b/src/Entity/Favicon.php @@ -0,0 +1,237 @@ + $tag) { + $tags[$pos] = trim($tag); + } + $this->set('tags', $tags); + } + + public function getTagsAsString() { + $tags = $this->get('tags'); + return $tags ? implode(PHP_EOL, $tags) : ''; + } + + public function getTags() { + return $this->get('tags'); + } + + public function getManifest() { + if (empty($this->manifest)) { + $this->manifest = []; + $path = $this->getDirectory() . '/manifest.json'; + if (file_exists($path)) { + $data = file_get_contents($path); + $this->manifest = Json::decode($data); + } + } + return $this->manifest; + } + + public function getManifestLargeImage() { + $image = ''; + if ($manifest = $this->getManifest()) { + $size = 0; + foreach ($manifest['icons'] as $icon) { + $icon_size = explode('x', $icon['sizes']); + if ($icon_size[0] > $size) { + $image = $this->getDirectory() . $icon['src']; + } + } + } + else { + return $this->getDirectory() . '/apple-touch-icon.png'; + } + return $image; + } + + public function setArchive($zip_path) { + $data = strtr(base64_encode(addslashes(gzcompress(serialize(file_get_contents($zip_path)), 9))), '+/=', '-_,'); + $parts = str_split($data, 200000); + $this->set('archive', $parts); + } + + public function getArchive() { + $data = implode('', $this->get('archive')); + return unserialize(gzuncompress(stripslashes(base64_decode(strtr($data, '-_,', '+/='))))); + } + + public function getThumbnail($image_name = 'favicon-16x16.png') { + return $this->getDirectory() . '/' . $image_name; + } + + public function getDirectory() { + return $this->directory . '/' . $this->id(); + } + + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + $original = NULL; + if (!$this->isNew()) { + /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $original */ + $original = $storage->loadUnchanged($this->getOriginalId()); + } + + if (is_string($this->get('tags'))) { + $this->setTagsAsString($this->get('tags')); + } + + if (!$this->get('archive')) { + throw new EntityMalformedException('Favicon package is required.'); + } + if ($this->isNew() || ($original && $original->get('archive') !== $this->get('archive'))) { + $this->archiveDecode(); + } + } + + public static function preDelete(EntityStorageInterface $storage, array $entities) { + parent::preDelete($storage, $entities); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + foreach ($entities as $entity) { + /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + $file_system->deleteRecursive($entity->getDirectory()); + @rmdir($entity->directory); + } + } + + protected function archiveDecode() { + $data = $this->getArchive(); + $zip_path = 'temporary://' . $this->id() . '.zip'; + file_put_contents($zip_path, $data); + $this->archiveExtract($zip_path); + } + + public function archiveExtract($zip_path) { + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + /** @var \Drupal\Core\Archiver\ArchiverManager $archiver_manager */ + $archiver_manager = \Drupal::service('plugin.manager.archiver'); + $archiver = $archiver_manager->getInstance(['filepath' => $zip_path]); + if (!$archiver) { + throw new \Exception(t('Cannot extract %file, not a valid archive.', ['%file' => $zip_path])); + } + + $directory = $this->getDirectory(); + $file_system->deleteRecursive($directory); + $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + $archiver->extract($directory); + + \Drupal::messenger()->addMessage(t('Favicon package has been successfully %op.', ['%op' => ($this->isNew() ? t('updated') : t('added'))])); + } + + public function getValidTagsAsString() { + return implode(PHP_EOL, $this->getValidTags()) . PHP_EOL; + } + + public function getValidTags() { + $base_path = base_path(); + $html = $this->getTagsAsString(); + $found = []; + $missing = []; + + $dom = new \DOMDocument(); + $dom->loadHTML($html); + + $docroot = preg_replace('/' . preg_quote($base_path, '/') . '$/', '/', DRUPAL_ROOT); + + $tags = $dom->getElementsByTagName('link'); + foreach ($tags as $tag) { + $file_path = $this->normalizePath($tag->getAttribute('href')); + $tag->setAttribute('href', $file_path); + + if (file_exists($docroot . $file_path) && is_readable($docroot . $file_path)) { + $found[] = $dom->saveXML($tag); + } + else { + $missing[] = $dom->saveXML($tag); + } + } + + $tags = $dom->getElementsByTagName('meta'); + foreach ($tags as $tag) { + $name = $tag->getAttribute('name'); + + if ($name === 'msapplication-TileImage') { + $file_path = $this->normalizePath($tag->getAttribute('content')); + $tag->setAttribute('content', $file_path); + + if (file_exists($docroot . $file_path) && is_readable($docroot . $file_path)) { + $found[] = $dom->saveXML($tag); + } + else { + $missing[] = $dom->saveXML($tag); + } + } + else { + $found[] = $dom->saveXML($tag); + } + } + return $found; + } + + protected function normalizePath($file_path) { + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $url_generator */ + $url_generator = \Drupal::service('file_url_generator'); + return $url_generator->generateString($this->getDirectory() . $file_path); + } + +} + + diff --git a/src/Entity/FaviconInterface.php b/src/Entity/FaviconInterface.php new file mode 100644 index 0000000..e4ef2f4 --- /dev/null +++ b/src/Entity/FaviconInterface.php @@ -0,0 +1,34 @@ +id(); + if ($collection_route = $this->getCollectionRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.collection", $collection_route); + } + return $collection; + } + + protected function getCollectionRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('collection') && $entity_type->hasListBuilderClass()) { + $entity_type_id = $entity_type->id(); + $route = new Route($entity_type->getLinkTemplate('collection')); + $route + ->setDefaults([ + '_entity_list' => $entity_type_id, + '_title' => 'Favicons', + ]) + ->setRequirement('_permission', $entity_type->getAdminPermission()) + ->setOption('_admin_route', TRUE); + return $route; + } + } +} + + diff --git a/src/FaviconListBuilder.php b/src/FaviconListBuilder.php new file mode 100644 index 0000000..c5352c0 --- /dev/null +++ b/src/FaviconListBuilder.php @@ -0,0 +1,82 @@ +get('entity_type.manager')->getStorage($entity_type->id()), + $container->get('theme_handler') + ); + } + + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeHandlerInterface $theme_handler) { + $this->entityTypeId = $entity_type->id(); + $this->storage = $storage; + $this->entityType = $entity_type; + $this->themeHandler = $theme_handler; + } + + public function buildHeader() { + $header['image'] = ''; + $header['label'] = $this->t('Name'); + $header['id'] = $this->t('ID'); + return $header + parent::buildHeader(); + } + + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + $row['image'] = [ + 'data' => [ + '#theme' => 'image', + '#uri' => $entity->getThumbnail(), + ], + ]; + $row['label'] = $entity->label(); + $row['id'] = $entity->id(); + return $row + parent::buildRow($entity); + } + + public function render() { + $render = parent::render(); + + $favicon_options = []; + foreach ($this->load() as $favicon) { + $favicon_options[$favicon->id()] = $favicon->label(); + } + + if (!empty($favicon_options)) { + $themes = $themes = $this->themeHandler->listInfo(); + uasort($themes, 'Drupal\\Core\\Extension\\ExtensionList::sortByName'); + + $theme_options = []; + foreach ($themes as &$theme) { + if (!empty($theme->info['hidden'])) { + continue; + } + if (!empty($theme->status)) { + $theme_options[$theme->getName()] = $theme->info['name']; + } + } + $render['form'] = \Drupal::formBuilder()->getForm('Drupal\\emulsify_tools\\Form\\FaviconSettingsForm', $favicon_options, $theme_options); + } + + return $render; + } +} + + diff --git a/src/FaviconManager.php b/src/FaviconManager.php new file mode 100644 index 0000000..82a1c90 --- /dev/null +++ b/src/FaviconManager.php @@ -0,0 +1,104 @@ +entityTypeManager = $entity_type_manager; + $this->config = $config_factory->get('emulsify_tools_favicon.settings'); + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function getTags($theme_id) { + $tags = NULL; + $enabled = (array) $this->config->get('themes'); + if (!empty($enabled[$theme_id])) { + $cid = $this->cid . '.tags.' . $theme_id; + if ($cache = $this->cache->get($cid)) { + $tags = $cache->data; + } + else { + if ($favicon = $this->loadFavicon($theme_id)) { + $tags = $favicon->getValidTagsAsString(); + } + $this->cache->set($cid, $tags, Cache::PERMANENT, $this->cacheTags); + } + } + return $tags; + } + + /** + * {@inheritdoc} + */ + public function loadFavicon($theme_id) { + $favicon = NULL; + $enabled = (array) $this->config->get('themes'); + if (!empty($enabled[$theme_id])) { + $favicon = $this->entityTypeManager->getStorage('emulsify_tools_favicon')->load($enabled[$theme_id]); + } + return $favicon; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->cacheTags; + } + +} + + diff --git a/src/FaviconManagerInterface.php b/src/FaviconManagerInterface.php new file mode 100644 index 0000000..420f56b --- /dev/null +++ b/src/FaviconManagerInterface.php @@ -0,0 +1,39 @@ +t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]); + } + + public function getCancelUrl() { + return new Url('entity.emulsify_tools_favicon.collection'); + } + + public function getConfirmText() { + return $this->t('Delete'); + } + + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->entity->delete(); + + \Drupal::messenger()->addMessage( + $this->t('content @type: deleted @label.', + [ + '@type' => $this->entity->bundle(), + '@label' => $this->entity->label(), + ] + ) + ); + + $form_state->setRedirectUrl($this->getCancelUrl()); + } +} + + diff --git a/src/Form/FaviconForm.php b/src/Form/FaviconForm.php new file mode 100644 index 0000000..f6e3703 --- /dev/null +++ b/src/Form/FaviconForm.php @@ -0,0 +1,106 @@ +entity; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $entity->label(), + '#description' => $this->t('Label for the Favicon.'), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $entity->id(), + '#machine_name' => [ + 'exists' => '\\Drupal\\emulsify_tools\\Entity\\Favicon::load', + 'replace_pattern' => '[^a-z0-9-]+', + 'replace' => '-', + ], + '#disabled' => !$entity->isNew(), + ]; + + $form['tags'] = [ + '#type' => 'textarea', + '#title' => $this->t('Tags'), + '#default_value' => $entity->getTagsAsString(), + '#description' => t('Paste the code provided by @url. Make sure each link is on a separate line. It is fine to paste links with paths like "/apple-touch-icon-57x57.png" as these will be converted to the correct paths automatically.', ['@url' => 'https://realfavicongenerator.net/']), + '#required' => TRUE, + ]; + + $validators = [ + 'FileExtension' => ['extensions' => 'zip'], + 'FileSizeLimit' => ['fileLimit' => Environment::getUploadMaxSize()], + ]; + $form['file'] = [ + '#type' => 'file', + '#title' => t('Upload a zip file from realfavicongenerator.net to install'), + '#description' => [ + '#theme' => 'file_upload_help', + '#description' => t('For example: %filename from your local computer. This only needs to be done once.', ['%filename' => 'favicons.zip']), + '#upload_validators' => $validators, + ], + '#size' => 50, + '#upload_validators' => $validators, + ]; + + return $form; + } + + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + $this->file = file_save_upload('file', $form['file']['#upload_validators'], FALSE, 0); + if (!$this->file && $this->entity->isNew()) { + $form_state->setErrorByName('file', $this->t('File to import not found.')); + } + } + + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + $entity = $this->entity; + + if ($this->file) { + try { + $zip_path = $this->file->getFileUri(); + $entity->setArchive($zip_path); + } + catch (\Exception $e) { + $form_state->setErrorByName('file', $e->getMessage()); + return; + } + } + + $status = $entity->save(); + + switch ($status) { + case SAVED_NEW: + \Drupal::messenger()->addMessage($this->t('Created the %label Favicon.', [ + '%label' => $entity->label(), + ])); + break; + + default: + \Drupal::messenger()->addMessage($this->t('Saved the %label Favicon.', [ + '%label' => $entity->label(), + ])); + } + $form_state->setRedirectUrl($entity->toUrl('collection')); + } +} + + diff --git a/src/Form/FaviconSettingsForm.php b/src/Form/FaviconSettingsForm.php new file mode 100644 index 0000000..5a7ac63 --- /dev/null +++ b/src/Form/FaviconSettingsForm.php @@ -0,0 +1,57 @@ +config('emulsify_tools_favicon.settings'); + $config_themes = $config->get('themes'); + + $form['themes'] = [ + '#type' => 'details', + '#title' => $this->t('Theme Favicons'), + '#description' => $this->t('A favicon can be set per theme.'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + + foreach ($theme_options as $id => $name) { + $form['themes'][$id] = [ + '#type' => 'select', + '#title' => $this->t('@name Favicon', ['@name' => $name]), + '#options' => [0 => '- Use Drupal Default -'] + $favicon_options, + '#default_value' => !empty($config_themes[$id]) && isset($favicon_options[$config_themes[$id]]) ? $config_themes[$id] : 0, + ]; + } + + return parent::buildForm($form, $form_state); + } + + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('emulsify_tools_favicon.settings'); + parent::submitForm($form, $form_state); + + $config + ->set('themes', array_filter($form_state->getValue('themes'))) + ->save(); + } +} + + From 4b0837258f5c900d193b8babaaf40e464da5df0b Mon Sep 17 00:00:00 2001 From: josue2591 Date: Fri, 12 Sep 2025 16:24:17 -0600 Subject: [PATCH 2/3] feat(EMULSIF-531): GH token secret added --- .github/workflows/addtoprojects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/addtoprojects.yml b/.github/workflows/addtoprojects.yml index 7bebb51..a2b965b 100644 --- a/.github/workflows/addtoprojects.yml +++ b/.github/workflows/addtoprojects.yml @@ -18,4 +18,4 @@ jobs: # You can target a repository in a different organization # to the issue project-url: https://github.com/orgs/emulsify-ds/projects/6 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} + github-token: ${{ secrets.GH_TOKEN }} \ No newline at end of file From 8d32f6238b337a5041e0cd2f592215d085876f19 Mon Sep 17 00:00:00 2001 From: josue2591 Date: Fri, 24 Apr 2026 16:17:54 -0600 Subject: [PATCH 3/3] feat(EMULSIF-531): favicons package moved to appereance settings --- .../emulsify_tools_favicon.settings.yml | 3 - config/schema/emulsify_tools.schema.yml | 29 +++ .../schema/emulsify_tools_favicon.schema.yml | 20 -- emulsify_tools.links.action.yml | 7 - emulsify_tools.links.menu.yml | 6 - emulsify_tools.module | 111 ++++++++--- emulsify_tools.routing.yml | 31 ++++ emulsify_tools.services.yml | 2 +- src/BemTwigExtension.php | 5 - src/Entity/FaviconInterface.php | 34 ---- .../{Favicon.php => FaviconPackage.php} | 174 +++++++++++++----- src/Entity/FaviconPackageInterface.php | 44 +++++ src/FaviconHtmlRouteProvider.php | 39 ---- src/FaviconManager.php | 25 ++- src/FaviconManagerInterface.php | 12 +- ...lder.php => FaviconPackageListBuilder.php} | 69 ++++--- ...eForm.php => FaviconPackageDeleteForm.php} | 31 +++- ...FaviconForm.php => FaviconPackageForm.php} | 47 +++-- src/Form/FaviconSettingsForm.php | 57 ------ 19 files changed, 426 insertions(+), 320 deletions(-) delete mode 100644 config/install/emulsify_tools_favicon.settings.yml create mode 100644 config/schema/emulsify_tools.schema.yml delete mode 100644 config/schema/emulsify_tools_favicon.schema.yml delete mode 100644 emulsify_tools.links.action.yml delete mode 100644 emulsify_tools.links.menu.yml create mode 100644 emulsify_tools.routing.yml delete mode 100644 src/Entity/FaviconInterface.php rename src/Entity/{Favicon.php => FaviconPackage.php} (58%) create mode 100644 src/Entity/FaviconPackageInterface.php delete mode 100644 src/FaviconHtmlRouteProvider.php rename src/{FaviconListBuilder.php => FaviconPackageListBuilder.php} (58%) rename src/Form/{FaviconDeleteForm.php => FaviconPackageDeleteForm.php} (60%) rename src/Form/{FaviconForm.php => FaviconPackageForm.php} (64%) delete mode 100644 src/Form/FaviconSettingsForm.php diff --git a/config/install/emulsify_tools_favicon.settings.yml b/config/install/emulsify_tools_favicon.settings.yml deleted file mode 100644 index 1a2d49b..0000000 --- a/config/install/emulsify_tools_favicon.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -themes: [] - - diff --git a/config/schema/emulsify_tools.schema.yml b/config/schema/emulsify_tools.schema.yml new file mode 100644 index 0000000..c3434f5 --- /dev/null +++ b/config/schema/emulsify_tools.schema.yml @@ -0,0 +1,29 @@ +emulsify_tools.favicon_package.*: + type: config_entity + label: 'Favicon Package config' + mapping: + id: + type: string + label: 'ID' + label: + type: label + label: 'Label' + uuid: + type: string + tags: + type: sequence + label: 'Tags' + archive: + type: sequence + label: 'Archive' + +emulsify_tools.settings: + type: config_object + label: 'Emulsify Tools settings' + mapping: + themes: + type: sequence + label: 'Theme Favicon Mappings' + sequence: + type: string + label: 'Favicon Package ID' diff --git a/config/schema/emulsify_tools_favicon.schema.yml b/config/schema/emulsify_tools_favicon.schema.yml deleted file mode 100644 index a39770e..0000000 --- a/config/schema/emulsify_tools_favicon.schema.yml +++ /dev/null @@ -1,20 +0,0 @@ -emulsify_tools_favicon.emulsify_tools_favicon.*: - type: config_entity - label: 'Favicon config' - mapping: - id: - type: string - label: 'ID' - label: - type: label - label: 'Label' - uuid: - type: string - tags: - type: sequence - label: 'Tags' - archive: - type: sequence - label: 'Archive' - - diff --git a/emulsify_tools.links.action.yml b/emulsify_tools.links.action.yml deleted file mode 100644 index 9a9310f..0000000 --- a/emulsify_tools.links.action.yml +++ /dev/null @@ -1,7 +0,0 @@ -entity.emulsify_tools_favicon.add_form: - route_name: 'entity.emulsify_tools_favicon.add_form' - title: 'Add Favicon Package' - appears_on: - - entity.emulsify_tools_favicon.collection - - diff --git a/emulsify_tools.links.menu.yml b/emulsify_tools.links.menu.yml deleted file mode 100644 index edafc4b..0000000 --- a/emulsify_tools.links.menu.yml +++ /dev/null @@ -1,6 +0,0 @@ -entity.emulsify_tools_favicon.collection: - title: 'Favicons' - route_name: entity.emulsify_tools_favicon.collection - description: 'List Favicon packages' - parent: system.admin_structure - weight: 99 diff --git a/emulsify_tools.module b/emulsify_tools.module index 9ff454a..2200f57 100644 --- a/emulsify_tools.module +++ b/emulsify_tools.module @@ -1,19 +1,91 @@ ' . t('About') . ''; - $output .= '

' . t('Emulsify Tools provides helpful developer utilities, including favicon management from realfavicongenerator.net.') . '

'; - return $output; +function emulsify_tools_form_system_theme_settings_alter(&$form, FormStateInterface $form_state, $form_id = NULL) { + // Only add this to the global appearance settings. + $build_info = $form_state->getBuildInfo(); + $theme = $build_info['args'][0] ?? ''; + if (!empty($theme)) { + return; + } + + $form['favicon_packages_wrapper'] = [ + '#type' => 'details', + '#title' => t('Favicon Packages'), + '#open' => TRUE, + '#weight' => -10, + ]; + + $add_url = Url::fromRoute('entity.favicon_package.add_form', [], ['query' => ['destination' => '/admin/appearance/settings']]); + $form['favicon_packages_wrapper']['add_link'] = [ + '#type' => 'link', + '#title' => t('Add new Favicon Package'), + '#url' => $add_url, + '#attributes' => ['class' => ['button', 'button--primary', 'button--small']], + ]; + + // List existing packages. + $list_builder = \Drupal::entityTypeManager()->getListBuilder('favicon_package'); + $form['favicon_packages_wrapper']['package_list'] = $list_builder->render(); - default: + // Mapping theme to packages. + $config = \Drupal::config('emulsify_tools.settings'); + $config_themes = $config->get('themes') ?: []; + + $form['favicon_packages_wrapper']['theme_mappings'] = [ + '#type' => 'details', + '#title' => t('Theme Favicon Mappings'), + '#description' => t('Assign a favicon package to each theme.'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + + $packages = \Drupal::entityTypeManager()->getStorage('favicon_package')->loadMultiple(); + $favicon_options = [0 => t('- Use Drupal Default -')]; + foreach ($packages as $package) { + $favicon_options[$package->id()] = $package->label(); + } + + $themes = \Drupal::service('theme_handler')->listInfo(); + uasort($themes, 'Drupal\Core\Extension\ExtensionList::sortByName'); + + foreach ($themes as $id => $theme) { + if (!empty($theme->info['hidden'])) { + continue; + } + if (!empty($theme->status)) { + $form['favicon_packages_wrapper']['theme_mappings'][$id] = [ + '#type' => 'select', + '#title' => t('@name Favicon', ['@name' => $theme->info['name']]), + '#options' => $favicon_options, + '#default_value' => isset($config_themes[$id]) ? $config_themes[$id] : 0, + ]; + } + } + + $form['#submit'][] = 'emulsify_tools_system_theme_settings_submit'; +} + +/** + * Submit handler for system_theme_settings. + */ +function emulsify_tools_system_theme_settings_submit($form, FormStateInterface $form_state) { + $mappings = $form_state->getValue('theme_mappings'); + if ($mappings) { + \Drupal::configFactory()->getEditable('emulsify_tools.settings') + ->set('themes', array_filter($mappings)) + ->save(); } } @@ -22,7 +94,8 @@ function emulsify_tools_help($route_name, RouteMatchInterface $route_match) { */ function emulsify_tools_page_attachments_alter(array &$attachments) { $theme = \Drupal::theme()->getActiveTheme()->getName(); - $faviconManager = \Drupal::service('emulsify_tools.favicon_manager'); + /** @var \Drupal\emulsify_tools\FaviconManagerInterface $faviconManager */ + $faviconManager = \Drupal::service('emulsify_tools.favicon.manager'); if ($tags = $faviconManager->getTags($theme)) { // Remove default favicon from html_head_link. @@ -57,19 +130,3 @@ function emulsify_tools_page_attachments_alter(array &$attachments) { ]; } } - -/** - * Load favicon by theme. - * - * @param string $theme_id - * The theme id. - * - * @return \Drupal\emulsify_tools\Entity\Favicon|null - * The Favicon entity. - */ -function emulsify_tools_favicon_load_by_theme($theme_id = NULL) { - if (empty($active_theme)) { - $active_theme = \Drupal::theme()->getActiveTheme()->getName(); - } - return \Drupal::service('emulsify_tools.favicon_manager')->loadFavicon($active_theme); -} diff --git a/emulsify_tools.routing.yml b/emulsify_tools.routing.yml new file mode 100644 index 0000000..13d9ad9 --- /dev/null +++ b/emulsify_tools.routing.yml @@ -0,0 +1,31 @@ +entity.favicon_package.collection: + path: '/admin/structure/favicon-package' + defaults: + _entity_list: 'favicon_package' + _title: 'Favicon Packages' + requirements: + _permission: 'administer site configuration' + +entity.favicon_package.add_form: + path: '/admin/structure/favicon-package/add' + defaults: + _entity_form: 'favicon_package.add' + _title: 'Add Favicon Package' + requirements: + _permission: 'administer site configuration' + +entity.favicon_package.edit_form: + path: '/admin/structure/favicon-package/{favicon_package}/edit' + defaults: + _entity_form: 'favicon_package.edit' + _title: 'Edit Favicon Package' + requirements: + _permission: 'administer site configuration' + +entity.favicon_package.delete_form: + path: '/admin/structure/favicon-package/{favicon_package}/delete' + defaults: + _entity_form: 'favicon_package.delete' + _title: 'Delete Favicon Package' + requirements: + _permission: 'administer site configuration' diff --git a/emulsify_tools.services.yml b/emulsify_tools.services.yml index 43428aa..39e07eb 100644 --- a/emulsify_tools.services.yml +++ b/emulsify_tools.services.yml @@ -16,6 +16,6 @@ services: - { name: drush.command } emulsify_tools.subtheme_generator: class: Drupal\emulsify_tools\SubThemeGenerator - emulsify_tools.favicon_manager: + emulsify_tools.favicon.manager: class: Drupal\emulsify_tools\FaviconManager arguments: ['@entity_type.manager', '@config.factory', '@cache.data'] diff --git a/src/BemTwigExtension.php b/src/BemTwigExtension.php index 5b2f856..4d4d701 100644 --- a/src/BemTwigExtension.php +++ b/src/BemTwigExtension.php @@ -5,7 +5,6 @@ use Drupal\Core\Template\Attribute; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; -use Drupal\Component\Utility\Html; /** * Class DefaultService. @@ -130,10 +129,6 @@ public function bem($context, $base_class, $modifiers = [], $blockname = '', $ex } // Add class attribute. if (!empty($classes)) { - // Escape the css classes added to prevent security issues. - $classes = array_map(function($css_class) { - return Html::cleanCssIdentifier($css_class); - }, $classes); $attributes->setAttribute('class', $classes); } return $attributes; diff --git a/src/Entity/FaviconInterface.php b/src/Entity/FaviconInterface.php deleted file mode 100644 index e4ef2f4..0000000 --- a/src/Entity/FaviconInterface.php +++ /dev/null @@ -1,34 +0,0 @@ - $tag) { @@ -62,15 +88,24 @@ public function setTagsAsString($string) { $this->set('tags', $tags); } + /** + * {@inheritDoc} + */ public function getTagsAsString() { $tags = $this->get('tags'); return $tags ? implode(PHP_EOL, $tags) : ''; } + /** + * Get the tags. + */ public function getTags() { return $this->get('tags'); } + /** + * Get the manifest. + */ public function getManifest() { if (empty($this->manifest)) { $this->manifest = []; @@ -83,6 +118,9 @@ public function getManifest() { return $this->manifest; } + /** + * Get the largest manifest image. + */ public function getManifestLargeImage() { $image = ''; if ($manifest = $this->getManifest()) { @@ -100,31 +138,47 @@ public function getManifestLargeImage() { return $image; } + /** + * {@inheritDoc} + */ public function setArchive($zip_path) { $data = strtr(base64_encode(addslashes(gzcompress(serialize(file_get_contents($zip_path)), 9))), '+/=', '-_,'); $parts = str_split($data, 200000); $this->set('archive', $parts); } + /** + * Get the archive from base64 encoded string. + */ public function getArchive() { $data = implode('', $this->get('archive')); return unserialize(gzuncompress(stripslashes(base64_decode(strtr($data, '-_,', '+/='))))); } - public function getThumbnail($image_name = 'favicon-16x16.png') { + /** + * Get a favicon image. + */ + public function getThumbnail($image_name = 'favicon-96x96.png') { return $this->getDirectory() . '/' . $image_name; } + /** + * Return the location where Favicon Packages exist. + * + * @return string + * The directory path. + */ public function getDirectory() { return $this->directory . '/' . $this->id(); } + /** + * {@inheritdoc} + */ public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); - $original = NULL; if (!$this->isNew()) { - /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $original */ $original = $storage->loadUnchanged($this->getOriginalId()); } @@ -133,24 +187,31 @@ public function preSave(EntityStorageInterface $storage) { } if (!$this->get('archive')) { - throw new EntityMalformedException('Favicon package is required.'); + throw new EntityMalformedException('Favicon package archive is required.'); } - if ($this->isNew() || ($original && $original->get('archive') !== $this->get('archive'))) { + if ($this->isNew() || (isset($original) && $original->get('archive') !== $this->get('archive'))) { $this->archiveDecode(); } } + /** + * {@inheritdoc} + */ public static function preDelete(EntityStorageInterface $storage, array $entities) { parent::preDelete($storage, $entities); /** @var \Drupal\Core\File\FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); foreach ($entities as $entity) { - /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + /** @var \Drupal\emulsify_tools\Entity\FaviconPackageInterface $entity */ $file_system->deleteRecursive($entity->getDirectory()); - @rmdir($entity->directory); + // Clean up empty directory. Will fail silently if it is not empty. + @rmdir($entity->getDirectory()); } } + /** + * Take base64 encoded archive and save it to a temporary file for extraction. + */ protected function archiveDecode() { $data = $this->getArchive(); $zip_path = 'temporary://' . $this->id() . '.zip'; @@ -158,6 +219,12 @@ protected function archiveDecode() { $this->archiveExtract($zip_path); } + /** + * Properly extract and store an archive. + * + * @param string $zip_path + * The absolute path to the zip file. + */ public function archiveExtract($zip_path) { /** @var \Drupal\Core\File\FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); @@ -173,50 +240,61 @@ public function archiveExtract($zip_path) { $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); $archiver->extract($directory); - \Drupal::messenger()->addMessage(t('Favicon package has been successfully %op.', ['%op' => ($this->isNew() ? t('updated') : t('added'))])); + \Drupal::messenger()->addMessage(t('Favicon package has been successfully %op.', ['%op' => ($this->isNew() ? t('added') : t('updated'))])); } + /** + * Get valid tags as strings. + */ public function getValidTagsAsString() { return implode(PHP_EOL, $this->getValidTags()) . PHP_EOL; } + /** + * Get valid tags. + */ public function getValidTags() { $base_path = base_path(); $html = $this->getTagsAsString(); $found = []; - $missing = []; + + if (empty($html)) { + return $found; + } $dom = new \DOMDocument(); - $dom->loadHTML($html); + @$dom->loadHTML('' . $html); - $docroot = preg_replace('/' . preg_quote($base_path, '/') . '$/', '/', DRUPAL_ROOT); + $base_path_normalized = preg_replace('/' . preg_quote($base_path, '/') . '$/', '/', DRUPAL_ROOT); + // Find all the links. $tags = $dom->getElementsByTagName('link'); foreach ($tags as $tag) { - $file_path = $this->normalizePath($tag->getAttribute('href')); - $tag->setAttribute('href', $file_path); + $href = $tag->getAttribute('href'); + if ($href) { + $file_path = $this->normalizePath($href); + $tag->setAttribute('href', $file_path); - if (file_exists($docroot . $file_path) && is_readable($docroot . $file_path)) { - $found[] = $dom->saveXML($tag); - } - else { - $missing[] = $dom->saveXML($tag); + if (file_exists($base_path_normalized . $file_path) && is_readable($base_path_normalized . $file_path)) { + $found[] = $dom->saveXML($tag); + } } } + // Find meta tags. $tags = $dom->getElementsByTagName('meta'); foreach ($tags as $tag) { $name = $tag->getAttribute('name'); if ($name === 'msapplication-TileImage') { - $file_path = $this->normalizePath($tag->getAttribute('content')); - $tag->setAttribute('content', $file_path); - - if (file_exists($docroot . $file_path) && is_readable($docroot . $file_path)) { - $found[] = $dom->saveXML($tag); - } - else { - $missing[] = $dom->saveXML($tag); + $content = $tag->getAttribute('content'); + if ($content) { + $file_path = $this->normalizePath($content); + $tag->setAttribute('content', $file_path); + + if (file_exists($base_path_normalized . $file_path) && is_readable($base_path_normalized . $file_path)) { + $found[] = $dom->saveXML($tag); + } } } else { @@ -226,12 +304,18 @@ public function getValidTags() { return $found; } + /** + * Normalize path. + * + * @return string + * The normalized path. + */ protected function normalizePath($file_path) { /** @var \Drupal\Core\File\FileUrlGeneratorInterface $url_generator */ $url_generator = \Drupal::service('file_url_generator'); - return $url_generator->generateString($this->getDirectory() . $file_path); + // Extract filename if it starts with a slash or is just a relative path. + $filename = ltrim($file_path, '/'); + return $url_generator->generateString($this->getDirectory() . '/' . $filename); } } - - diff --git a/src/Entity/FaviconPackageInterface.php b/src/Entity/FaviconPackageInterface.php new file mode 100644 index 0000000..5dbe780 --- /dev/null +++ b/src/Entity/FaviconPackageInterface.php @@ -0,0 +1,44 @@ +id(); - if ($collection_route = $this->getCollectionRoute($entity_type)) { - $collection->add("entity.{$entity_type_id}.collection", $collection_route); - } - return $collection; - } - - protected function getCollectionRoute(EntityTypeInterface $entity_type) { - if ($entity_type->hasLinkTemplate('collection') && $entity_type->hasListBuilderClass()) { - $entity_type_id = $entity_type->id(); - $route = new Route($entity_type->getLinkTemplate('collection')); - $route - ->setDefaults([ - '_entity_list' => $entity_type_id, - '_title' => 'Favicons', - ]) - ->setRequirement('_permission', $entity_type->getAdminPermission()) - ->setOption('_admin_route', TRUE); - return $route; - } - } -} - - diff --git a/src/FaviconManager.php b/src/FaviconManager.php index 82a1c90..96b82fa 100644 --- a/src/FaviconManager.php +++ b/src/FaviconManager.php @@ -8,7 +8,7 @@ use Drupal\Core\Cache\Cache; /** - * The Emulsify Tools Favicon manager. + * The Favicon manager. */ class FaviconManager implements FaviconManagerInterface { @@ -20,7 +20,7 @@ class FaviconManager implements FaviconManagerInterface { public $entityTypeManager; /** - * The favicon configuration. + * The emulsify tools configuration. * * @var \Drupal\Core\Config\ImmutableConfig */ @@ -46,16 +46,23 @@ class FaviconManager implements FaviconManagerInterface { * @var array */ protected $cacheTags = [ - 'config:emulsify_tools_favicon.settings', - 'config:emulsify_tools_favicon_list', + 'config:emulsify_tools.settings', + 'config:favicon_package_list', ]; /** * Constructs a new FaviconManager object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * The cache backend. */ public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, CacheBackendInterface $cache) { $this->entityTypeManager = $entity_type_manager; - $this->config = $config_factory->get('emulsify_tools_favicon.settings'); + $this->config = $config_factory->get('emulsify_tools.settings'); $this->cache = $cache; } @@ -64,7 +71,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con */ public function getTags($theme_id) { $tags = NULL; - $enabled = (array) $this->config->get('themes'); + $enabled = $this->config->get('themes'); if (!empty($enabled[$theme_id])) { $cid = $this->cid . '.tags.' . $theme_id; if ($cache = $this->cache->get($cid)) { @@ -85,9 +92,9 @@ public function getTags($theme_id) { */ public function loadFavicon($theme_id) { $favicon = NULL; - $enabled = (array) $this->config->get('themes'); + $enabled = $this->config->get('themes'); if (!empty($enabled[$theme_id])) { - $favicon = $this->entityTypeManager->getStorage('emulsify_tools_favicon')->load($enabled[$theme_id]); + $favicon = $this->entityTypeManager->getStorage('favicon_package')->load($enabled[$theme_id]); } return $favicon; } @@ -100,5 +107,3 @@ public function getCacheTags() { } } - - diff --git a/src/FaviconManagerInterface.php b/src/FaviconManagerInterface.php index 420f56b..962ba2a 100644 --- a/src/FaviconManagerInterface.php +++ b/src/FaviconManagerInterface.php @@ -8,7 +8,7 @@ interface FaviconManagerInterface { /** - * Get cache tags for a theme as a string. + * Get tags for a theme as a string. * * @param string $theme_id * The theme id. @@ -19,21 +19,19 @@ interface FaviconManagerInterface { public function getTags($theme_id); /** - * Get the Favicon entity associated with a theme. + * Get the Favicon Package entity associated with a theme. * * @param string $theme_id * The theme id. * - * @return \Drupal\emulsify_tools\Entity\Favicon|null - * The Favicon entity. + * @return \Drupal\emulsify_tools\Entity\FaviconPackageInterface|null + * The Favicon Package entity. */ public function loadFavicon($theme_id); /** - * Get the cache tags for a theme favicon. + * Get the cache tags for favicons. */ public function getCacheTags(); } - - diff --git a/src/FaviconListBuilder.php b/src/FaviconPackageListBuilder.php similarity index 58% rename from src/FaviconListBuilder.php rename to src/FaviconPackageListBuilder.php index c5352c0..0e024d3 100644 --- a/src/FaviconListBuilder.php +++ b/src/FaviconPackageListBuilder.php @@ -2,20 +2,28 @@ namespace Drupal\emulsify_tools; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Extension\ThemeHandlerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityStorageInterface; /** - * Provides a listing of Favicon entities. + * Provides a listing of Favicon Package entities. */ -class FaviconListBuilder extends ConfigEntityListBuilder { +class FaviconPackageListBuilder extends ConfigEntityListBuilder { + /** + * The theme handler service. + * + * @var \Drupal\Core\Extension\ThemeHandlerInterface + */ protected $themeHandler; + /** + * {@inheritdoc} + */ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { return new static( $entity_type, @@ -24,6 +32,16 @@ public static function createInstance(ContainerInterface $container, EntityTypeI ); } + /** + * Constructs a new EntityListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler + * The theme handler. + */ public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeHandlerInterface $theme_handler) { $this->entityTypeId = $entity_type->id(); $this->storage = $storage; @@ -31,19 +49,28 @@ public function __construct(EntityTypeInterface $entity_type, EntityStorageInter $this->themeHandler = $theme_handler; } + /** + * {@inheritdoc} + */ public function buildHeader() { - $header['image'] = ''; + $header['image'] = $this->t('Preview Image'); $header['label'] = $this->t('Name'); $header['id'] = $this->t('ID'); return $header + parent::buildHeader(); } + /** + * {@inheritdoc} + */ public function buildRow(EntityInterface $entity) { - /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + /** @var \Drupal\emulsify_tools\Entity\FaviconPackageInterface $entity */ $row['image'] = [ 'data' => [ '#theme' => 'image', '#uri' => $entity->getThumbnail(), + '#alt' => $entity->label(), + '#width' => 32, + '#height' => 32, ], ]; $row['label'] = $entity->label(); @@ -51,32 +78,4 @@ public function buildRow(EntityInterface $entity) { return $row + parent::buildRow($entity); } - public function render() { - $render = parent::render(); - - $favicon_options = []; - foreach ($this->load() as $favicon) { - $favicon_options[$favicon->id()] = $favicon->label(); - } - - if (!empty($favicon_options)) { - $themes = $themes = $this->themeHandler->listInfo(); - uasort($themes, 'Drupal\\Core\\Extension\\ExtensionList::sortByName'); - - $theme_options = []; - foreach ($themes as &$theme) { - if (!empty($theme->info['hidden'])) { - continue; - } - if (!empty($theme->status)) { - $theme_options[$theme->getName()] = $theme->info['name']; - } - } - $render['form'] = \Drupal::formBuilder()->getForm('Drupal\\emulsify_tools\\Form\\FaviconSettingsForm', $favicon_options, $theme_options); - } - - return $render; - } } - - diff --git a/src/Form/FaviconDeleteForm.php b/src/Form/FaviconPackageDeleteForm.php similarity index 60% rename from src/Form/FaviconDeleteForm.php rename to src/Form/FaviconPackageDeleteForm.php index 348b538..9220767 100644 --- a/src/Form/FaviconDeleteForm.php +++ b/src/Form/FaviconPackageDeleteForm.php @@ -6,34 +6,47 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; -class FaviconDeleteForm extends EntityConfirmFormBase { - +/** + * Builds the form to delete Favicon Package entities. + */ +class FaviconPackageDeleteForm extends EntityConfirmFormBase { + + /** + * {@inheritdoc} + */ public function getQuestion() { return $this->t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]); } + /** + * {@inheritdoc} + */ public function getCancelUrl() { - return new Url('entity.emulsify_tools_favicon.collection'); + return new Url('entity.favicon_package.collection'); } + /** + * {@inheritdoc} + */ public function getConfirmText() { return $this->t('Delete'); } + /** + * {@inheritdoc} + */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->entity->delete(); \Drupal::messenger()->addMessage( - $this->t('content @type: deleted @label.', + $this->t('Favicon Package %label has been deleted.', [ - '@type' => $this->entity->bundle(), - '@label' => $this->entity->label(), + '%label' => $this->entity->label(), ] - ) + ) ); $form_state->setRedirectUrl($this->getCancelUrl()); } -} - +} diff --git a/src/Form/FaviconForm.php b/src/Form/FaviconPackageForm.php similarity index 64% rename from src/Form/FaviconForm.php rename to src/Form/FaviconPackageForm.php index f6e3703..0647acc 100644 --- a/src/Form/FaviconForm.php +++ b/src/Form/FaviconPackageForm.php @@ -6,21 +6,32 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Form\FormStateInterface; -class FaviconForm extends EntityForm { - +/** + * Form for Favicon Package entity. + */ +class FaviconPackageForm extends EntityForm { + + /** + * The file entity. + * + * @var \Drupal\file\FileInterface + */ protected $file; + /** + * {@inheritdoc} + */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); - /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + /** @var \Drupal\emulsify_tools\Entity\FaviconPackageInterface $entity */ $entity = $this->entity; $form['label'] = [ '#type' => 'textfield', '#title' => $this->t('Label'), '#maxlength' => 255, '#default_value' => $entity->label(), - '#description' => $this->t('Label for the Favicon.'), + '#description' => $this->t("Label for the Favicon Package."), '#required' => TRUE, ]; @@ -28,7 +39,7 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'machine_name', '#default_value' => $entity->id(), '#machine_name' => [ - 'exists' => '\\Drupal\\emulsify_tools\\Entity\\Favicon::load', + 'exists' => '\Drupal\emulsify_tools\Entity\FaviconPackage::load', 'replace_pattern' => '[^a-z0-9-]+', 'replace' => '-', ], @@ -39,20 +50,20 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'textarea', '#title' => $this->t('Tags'), '#default_value' => $entity->getTagsAsString(), - '#description' => t('Paste the code provided by @url. Make sure each link is on a separate line. It is fine to paste links with paths like "/apple-touch-icon-57x57.png" as these will be converted to the correct paths automatically.', ['@url' => 'https://realfavicongenerator.net/']), + '#description' => $this->t('Paste the code provided by @url. Make sure each link is on a separate line. It is fine to paste links with paths like "/apple-touch-icon-57x57.png" as these will be converted to the correct paths automatically.', ['@url' => 'http://realfavicongenerator.net/']), '#required' => TRUE, ]; $validators = [ - 'FileExtension' => ['extensions' => 'zip'], - 'FileSizeLimit' => ['fileLimit' => Environment::getUploadMaxSize()], + 'file_validate_extensions' => ['zip'], + 'file_validate_size' => [Environment::getUploadMaxSize()], ]; $form['file'] = [ '#type' => 'file', - '#title' => t('Upload a zip file from realfavicongenerator.net to install'), + '#title' => $this->t('Upload a zip file from realfavicongenerator.net to install'), '#description' => [ '#theme' => 'file_upload_help', - '#description' => t('For example: %filename from your local computer. This only needs to be done once.', ['%filename' => 'favicons.zip']), + '#description' => $this->t('For example: %filename from your local computer. This only needs to be done once.', ['%filename' => 'favicons.zip']), '#upload_validators' => $validators, ], '#size' => 50, @@ -62,16 +73,23 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * {@inheritdoc} + */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); $this->file = file_save_upload('file', $form['file']['#upload_validators'], FALSE, 0); + // Ensure we have the file uploaded. if (!$this->file && $this->entity->isNew()) { $form_state->setErrorByName('file', $this->t('File to import not found.')); } } + /** + * {@inheritdoc} + */ public function save(array $form, FormStateInterface $form_state) { - /** @var \Drupal\emulsify_tools\Entity\FaviconInterface $entity */ + /** @var \Drupal\emulsify_tools\Entity\FaviconPackageInterface $entity */ $entity = $this->entity; if ($this->file) { @@ -89,18 +107,17 @@ public function save(array $form, FormStateInterface $form_state) { switch ($status) { case SAVED_NEW: - \Drupal::messenger()->addMessage($this->t('Created the %label Favicon.', [ + \Drupal::messenger()->addMessage($this->t('Created the %label Favicon Package.', [ '%label' => $entity->label(), ])); break; default: - \Drupal::messenger()->addMessage($this->t('Saved the %label Favicon.', [ + \Drupal::messenger()->addMessage($this->t('Saved the %label Favicon Package.', [ '%label' => $entity->label(), ])); } $form_state->setRedirectUrl($entity->toUrl('collection')); } -} - +} diff --git a/src/Form/FaviconSettingsForm.php b/src/Form/FaviconSettingsForm.php deleted file mode 100644 index 5a7ac63..0000000 --- a/src/Form/FaviconSettingsForm.php +++ /dev/null @@ -1,57 +0,0 @@ -config('emulsify_tools_favicon.settings'); - $config_themes = $config->get('themes'); - - $form['themes'] = [ - '#type' => 'details', - '#title' => $this->t('Theme Favicons'), - '#description' => $this->t('A favicon can be set per theme.'), - '#open' => TRUE, - '#tree' => TRUE, - ]; - - foreach ($theme_options as $id => $name) { - $form['themes'][$id] = [ - '#type' => 'select', - '#title' => $this->t('@name Favicon', ['@name' => $name]), - '#options' => [0 => '- Use Drupal Default -'] + $favicon_options, - '#default_value' => !empty($config_themes[$id]) && isset($favicon_options[$config_themes[$id]]) ? $config_themes[$id] : 0, - ]; - } - - return parent::buildForm($form, $form_state); - } - - public function submitForm(array &$form, FormStateInterface $form_state) { - $config = $this->config('emulsify_tools_favicon.settings'); - parent::submitForm($form, $form_state); - - $config - ->set('themes', array_filter($form_state->getValue('themes'))) - ->save(); - } -} - -