Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/addtoprojects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
3 changes: 3 additions & 0 deletions config/install/emulsify_tools_favicon.settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
themes: []


20 changes: 20 additions & 0 deletions config/schema/emulsify_tools_favicon.schema.yml
Original file line number Diff line number Diff line change
@@ -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'


3 changes: 3 additions & 0 deletions emulsify_tools.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.2'
project: 'emulsify_tools'
Expand Down
7 changes: 7 additions & 0 deletions emulsify_tools.links.action.yml
Original file line number Diff line number Diff line change
@@ -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


6 changes: 6 additions & 0 deletions emulsify_tools.links.menu.yml
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions emulsify_tools.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

use Drupal\Core\Routing\RouteMatchInterface;

/**
* Implements hook_help().
*/
function emulsify_tools_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.emulsify_tools':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Emulsify Tools provides helpful developer utilities, including favicon management from realfavicongenerator.net.') . '</p>';
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);
}
3 changes: 3 additions & 0 deletions emulsify_tools.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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']
237 changes: 237 additions & 0 deletions src/Entity/Favicon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<?php

namespace Drupal\emulsify_tools\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\File\FileSystemInterface;

/**
* Defines the Favicon entity.
*
* @ConfigEntityType(
* id = "emulsify_tools_favicon",
* label = @Translation("Favicon"),
* handlers = {
* "list_builder" = "Drupal\emulsify_tools\FaviconListBuilder",
* "form" = {
* "add" = "Drupal\emulsify_tools\Form\FaviconForm",
* "edit" = "Drupal\emulsify_tools\Form\FaviconForm",
* "delete" = "Drupal\emulsify_tools\Form\FaviconDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\emulsify_tools\FaviconHtmlRouteProvider",
* },
* },
* config_prefix = "emulsify_tools_favicon",
* admin_permission = "administer site configuration",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* config_export = {
* "id",
* "label",
* "tags",
* "archive",
* },
* links = {
* "canonical" = "/admin/structure/emulsify-tools-favicon/{emulsify_tools_favicon}",
* "add-form" = "/admin/structure/emulsify-tools-favicon/add",
* "edit-form" = "/admin/structure/emulsify-tools-favicon/{emulsify_tools_favicon}/edit",
* "delete-form" = "/admin/structure/emulsify-tools-favicon/{emulsify_tools_favicon}/delete",
* "collection" = "/admin/structure/emulsify-tools-favicon"
* }
* )
*/
class Favicon extends ConfigEntityBase implements FaviconInterface {

protected $id;
protected $label;
protected $manifest = [];
protected $directory = 'public://favicon';

public function setTagsAsString($string) {
$tags = array_filter(explode(PHP_EOL, $string));
foreach ($tags as $pos => $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);
}

}


Loading