@amazeelabs/silverback-preview-link 1.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/drupal/silverback_preview_link/CHANGELOG.md +195 -0
- package/drupal/silverback_preview_link/README.md +30 -0
- package/drupal/silverback_preview_link/composer.json +13 -0
- package/drupal/silverback_preview_link/config/install/silverback_preview_link.settings.yml +3 -0
- package/drupal/silverback_preview_link/config/schema/silverback_preview_link.schema.yml +14 -0
- package/drupal/silverback_preview_link/css/modal.css +17 -0
- package/drupal/silverback_preview_link/js/copy.js +39 -0
- package/drupal/silverback_preview_link/silverback_preview_link.info.yml +12 -0
- package/drupal/silverback_preview_link/silverback_preview_link.install +6 -0
- package/drupal/silverback_preview_link/silverback_preview_link.libraries.yml +9 -0
- package/drupal/silverback_preview_link/silverback_preview_link.links.menu.yml +5 -0
- package/drupal/silverback_preview_link/silverback_preview_link.module +79 -0
- package/drupal/silverback_preview_link/silverback_preview_link.permissions.yml +9 -0
- package/drupal/silverback_preview_link/silverback_preview_link.routing.yml +40 -0
- package/drupal/silverback_preview_link/silverback_preview_link.services.yml +31 -0
- package/drupal/silverback_preview_link/src/Access/PreviewEnabledAccessCheck.php +80 -0
- package/drupal/silverback_preview_link/src/Access/PreviewLinkAccessCheck.php +49 -0
- package/drupal/silverback_preview_link/src/Authentication/Provider/PreviewToken.php +118 -0
- package/drupal/silverback_preview_link/src/Controller/PreviewController.php +83 -0
- package/drupal/silverback_preview_link/src/Entity/SilverbackPreviewLink.php +224 -0
- package/drupal/silverback_preview_link/src/Entity/SilverbackPreviewLinkInterface.php +110 -0
- package/drupal/silverback_preview_link/src/Form/PreviewLinkForm.php +320 -0
- package/drupal/silverback_preview_link/src/Form/SettingsForm.php +204 -0
- package/drupal/silverback_preview_link/src/Plugin/EntityReferenceSelection/SilverbackPreviewLinkSelection.php +25 -0
- package/drupal/silverback_preview_link/src/Plugin/Field/FieldWidget/PreviewLinkEntitiesWidget.php +103 -0
- package/drupal/silverback_preview_link/src/Plugin/Validation/Constraint/SilverbackPreviewLinkEntitiesUniqueConstraint.php +26 -0
- package/drupal/silverback_preview_link/src/Plugin/Validation/Constraint/SilverbackPreviewLinkEntitiesUniqueConstraintValidator.php +44 -0
- package/drupal/silverback_preview_link/src/PreviewLinkExpiry.php +55 -0
- package/drupal/silverback_preview_link/src/PreviewLinkHooks.php +61 -0
- package/drupal/silverback_preview_link/src/PreviewLinkHost.php +70 -0
- package/drupal/silverback_preview_link/src/PreviewLinkHostInterface.php +49 -0
- package/drupal/silverback_preview_link/src/PreviewLinkStorage.php +99 -0
- package/drupal/silverback_preview_link/src/PreviewLinkStorageInterface.php +19 -0
- package/drupal/silverback_preview_link/src/PreviewLinkUtility.php +27 -0
- package/drupal/silverback_preview_link/src/QRCodeWithLogo.php +84 -0
- package/drupal/silverback_preview_link/src/QRMarkupSVGWithLogo.php +51 -0
- package/drupal/silverback_preview_link/src/Routing/PreviewLinkRouteProvider.php +61 -0
- package/drupal/silverback_preview_link/src/images/amazee-labs_logo-square-green.svg +13 -0
- package/drupal/silverback_preview_link/templates/preview-link.html.twig +39 -0
- package/package.json +13 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link\Plugin\Validation\Constraint;
|
|
6
|
+
|
|
7
|
+
use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceFieldItemList;
|
|
8
|
+
use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem;
|
|
9
|
+
use Symfony\Component\Validator\Constraint;
|
|
10
|
+
use Symfony\Component\Validator\ConstraintValidator;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates the SilverbackPreviewLinkEntitiesUniqueConstraint constraint.
|
|
14
|
+
*/
|
|
15
|
+
class SilverbackPreviewLinkEntitiesUniqueConstraintValidator extends ConstraintValidator {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* {@inheritdoc}
|
|
19
|
+
*/
|
|
20
|
+
public function validate($value, Constraint $constraint): void {
|
|
21
|
+
assert($constraint instanceof SilverbackPreviewLinkEntitiesUniqueConstraint);
|
|
22
|
+
assert($value instanceof DynamicEntityReferenceFieldItemList);
|
|
23
|
+
|
|
24
|
+
$entities = [];
|
|
25
|
+
foreach ($value as $delta => $item) {
|
|
26
|
+
assert($item instanceof DynamicEntityReferenceItem);
|
|
27
|
+
$entity = $item->entity;
|
|
28
|
+
$hash = $item->target_type . '|' . $item->target_id;
|
|
29
|
+
$duplicateDelta = array_search($hash, $entities, TRUE);
|
|
30
|
+
if ($duplicateDelta !== FALSE) {
|
|
31
|
+
$this->context
|
|
32
|
+
->buildViolation($constraint->multipleReferences)
|
|
33
|
+
->setParameter('%entity_type', $entity !== NULL ? $entity->getEntityType()->getSingularLabel() : $item->target_type)
|
|
34
|
+
->setParameter('%other_delta', (string) ($duplicateDelta + 1))
|
|
35
|
+
->atPath((string) $delta)
|
|
36
|
+
->addViolation();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
$entities[$delta] = $hash;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Core\Config\ConfigFactoryInterface;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculates link expiry time.
|
|
11
|
+
*/
|
|
12
|
+
class PreviewLinkExpiry {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default expiry time in seconds. This amounts to 7 days.
|
|
16
|
+
*
|
|
17
|
+
* @var int
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_EXPIRY_SECONDS = 86400;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Statically cache the lifetime.
|
|
23
|
+
*
|
|
24
|
+
* @var int|null
|
|
25
|
+
*/
|
|
26
|
+
protected ?int $lifetime = NULL;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* LinkExpiry constructor.
|
|
30
|
+
*
|
|
31
|
+
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
|
|
32
|
+
* The configuration factory.
|
|
33
|
+
*/
|
|
34
|
+
public function __construct(
|
|
35
|
+
protected ConfigFactoryInterface $configFactory,
|
|
36
|
+
) {
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculates default lifetime of a preview link.
|
|
41
|
+
*
|
|
42
|
+
* @return int
|
|
43
|
+
* Lifetime in seconds.
|
|
44
|
+
*
|
|
45
|
+
* @phpstan-return int<0, max>
|
|
46
|
+
*/
|
|
47
|
+
public function getLifetime(): int {
|
|
48
|
+
if (!isset($this->lifetime)) {
|
|
49
|
+
$config = $this->configFactory->get('silverback_preview_link.settings');
|
|
50
|
+
$this->lifetime = (int) ($config->get('expiry_seconds') ?: self::DEFAULT_EXPIRY_SECONDS);
|
|
51
|
+
}
|
|
52
|
+
return max(0, $this->lifetime);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Component\Datetime\TimeInterface;
|
|
8
|
+
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
|
9
|
+
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Drupal hooks.
|
|
13
|
+
*/
|
|
14
|
+
class PreviewLinkHooks implements ContainerInjectionInterface {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* PreviewLinkHooks constructor.
|
|
18
|
+
*/
|
|
19
|
+
final public function __construct(
|
|
20
|
+
protected PreviewLinkStorageInterface $previewLinkStorage,
|
|
21
|
+
protected TimeInterface $time,
|
|
22
|
+
) {
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* {@inheritdoc}
|
|
27
|
+
*/
|
|
28
|
+
public static function create(ContainerInterface $container) {
|
|
29
|
+
/** @var \Drupal\silverback_preview_link\PreviewLinkStorageInterface $storage */
|
|
30
|
+
$storage = $container->get('entity_type.manager')->getStorage('silverback_preview_link');
|
|
31
|
+
return new static(
|
|
32
|
+
$storage,
|
|
33
|
+
$container->get('datetime.time'),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Implements hook_cron().
|
|
39
|
+
*
|
|
40
|
+
* Cleans up expired Preview Links.
|
|
41
|
+
*
|
|
42
|
+
* @see \preview_link_cron()
|
|
43
|
+
*/
|
|
44
|
+
public function cron(): void {
|
|
45
|
+
$ids = $this->previewLinkStorage->getQuery()
|
|
46
|
+
->accessCheck(FALSE)
|
|
47
|
+
->condition('expiry', $this->time->getRequestTime(), '<')
|
|
48
|
+
->execute();
|
|
49
|
+
|
|
50
|
+
// If there are no expired links then nothing to do.
|
|
51
|
+
if (count($ids) === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$previewLinks = $this->previewLinkStorage->loadMultiple($ids);
|
|
56
|
+
// Simply delete the preview links. A new one will be regenerated at a later
|
|
57
|
+
// date as required.
|
|
58
|
+
$this->previewLinkStorage->delete($previewLinks);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Component\Datetime\TimeInterface;
|
|
8
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
9
|
+
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service for relationships between preview links and entities they unlock.
|
|
13
|
+
*/
|
|
14
|
+
class PreviewLinkHost implements PreviewLinkHostInterface {
|
|
15
|
+
|
|
16
|
+
protected PreviewLinkStorageInterface $previewLinkStorage;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* PreviewLinkHost constructor.
|
|
20
|
+
*/
|
|
21
|
+
final public function __construct(
|
|
22
|
+
EntityTypeManagerInterface $entityTypeManager,
|
|
23
|
+
protected TimeInterface $time,
|
|
24
|
+
) {
|
|
25
|
+
/** @var \Drupal\silverback_preview_link\PreviewLinkStorageInterface $storage */
|
|
26
|
+
$storage = $entityTypeManager->getStorage('silverback_preview_link');
|
|
27
|
+
$this->previewLinkStorage = $storage;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* {@inheritdoc}
|
|
32
|
+
*/
|
|
33
|
+
public function getPreviewLinks(EntityInterface $entity): array {
|
|
34
|
+
$ids = $this->previewLinkStorage->getQuery()
|
|
35
|
+
->accessCheck()
|
|
36
|
+
->condition('entities.target_type', $entity->getEntityTypeId())
|
|
37
|
+
->condition('entities.target_id', $entity->id())
|
|
38
|
+
->execute();
|
|
39
|
+
return $this->previewLinkStorage->loadMultiple($ids);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* {@inheritdoc}
|
|
44
|
+
*/
|
|
45
|
+
public function isToken(EntityInterface $entity, array $tokens): bool {
|
|
46
|
+
$count = $this->previewLinkStorage->getQuery()
|
|
47
|
+
->accessCheck()
|
|
48
|
+
->condition('entities.target_type', $entity->getEntityTypeId())
|
|
49
|
+
->condition('entities.target_id', $entity->id())
|
|
50
|
+
->condition('token', $tokens, 'IN')
|
|
51
|
+
->count()
|
|
52
|
+
->execute();
|
|
53
|
+
return $count > 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* {@inheritdoc}
|
|
58
|
+
*/
|
|
59
|
+
public function hasPreviewLinks(EntityInterface $entity): bool {
|
|
60
|
+
$count = $this->previewLinkStorage->getQuery()
|
|
61
|
+
->accessCheck()
|
|
62
|
+
->condition('entities.target_type', $entity->getEntityTypeId())
|
|
63
|
+
->condition('entities.target_id', $entity->id())
|
|
64
|
+
->condition('expiry', $this->time->getRequestTime(), '>')
|
|
65
|
+
->count()
|
|
66
|
+
->execute();
|
|
67
|
+
return $count > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Interface for relationships between preview links and entities they unlock.
|
|
11
|
+
*/
|
|
12
|
+
interface PreviewLinkHostInterface {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get preview links for an entity.
|
|
16
|
+
*
|
|
17
|
+
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
18
|
+
* An entity.
|
|
19
|
+
*
|
|
20
|
+
* @return \Drupal\silverback_preview_link\Entity\SilverbackPreviewLinkInterface[]
|
|
21
|
+
* Preview links associated with an entity.
|
|
22
|
+
*/
|
|
23
|
+
public function getPreviewLinks(EntityInterface $entity): array;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines if a token unlocks an entity.
|
|
27
|
+
*
|
|
28
|
+
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
29
|
+
* An entity.
|
|
30
|
+
* @param string[] $tokens
|
|
31
|
+
* An array of Preview Link tokens.
|
|
32
|
+
*
|
|
33
|
+
* @return bool
|
|
34
|
+
* Whether if at least one provided token grants access to the entity.
|
|
35
|
+
*/
|
|
36
|
+
public function isToken(EntityInterface $entity, array $tokens): bool;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Determines if an entity has any active preview links.
|
|
40
|
+
*
|
|
41
|
+
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
42
|
+
* An entity.
|
|
43
|
+
*
|
|
44
|
+
* @return bool
|
|
45
|
+
* Whether the entity has any associated preview links.
|
|
46
|
+
*/
|
|
47
|
+
public function hasPreviewLinks(EntityInterface $entity): bool;
|
|
48
|
+
|
|
49
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Component\Datetime\TimeInterface;
|
|
8
|
+
use Drupal\Component\Uuid\UuidInterface;
|
|
9
|
+
use Drupal\Core\Cache\CacheBackendInterface;
|
|
10
|
+
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
|
|
11
|
+
use Drupal\Core\Database\Connection;
|
|
12
|
+
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
|
13
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
14
|
+
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
|
15
|
+
use Drupal\Core\Entity\EntityTypeInterface;
|
|
16
|
+
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
17
|
+
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
|
18
|
+
use Drupal\Core\Language\LanguageManagerInterface;
|
|
19
|
+
use Drupal\silverback_preview_link\Entity\SilverbackPreviewLinkInterface;
|
|
20
|
+
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Preview Link entity storage.
|
|
24
|
+
*/
|
|
25
|
+
final class PreviewLinkStorage extends SqlContentEntityStorage implements PreviewLinkStorageInterface {
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Constructs a new PreviewLinkStorage.
|
|
29
|
+
*/
|
|
30
|
+
public function __construct(
|
|
31
|
+
EntityTypeInterface $entity_type,
|
|
32
|
+
Connection $database,
|
|
33
|
+
EntityFieldManagerInterface $entity_field_manager,
|
|
34
|
+
CacheBackendInterface $cache,
|
|
35
|
+
LanguageManagerInterface $language_manager,
|
|
36
|
+
MemoryCacheInterface $memory_cache,
|
|
37
|
+
EntityTypeBundleInfoInterface $entity_type_bundle_info,
|
|
38
|
+
EntityTypeManagerInterface $entity_type_manager,
|
|
39
|
+
UuidInterface $uuid,
|
|
40
|
+
protected TimeInterface $time,
|
|
41
|
+
) {
|
|
42
|
+
parent::__construct($entity_type, $database, $entity_field_manager, $cache, $language_manager, $memory_cache, $entity_type_bundle_info, $entity_type_manager);
|
|
43
|
+
$this->uuidService = $uuid;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* {@inheritdoc}
|
|
48
|
+
*/
|
|
49
|
+
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type): self {
|
|
50
|
+
return new static(
|
|
51
|
+
$entity_type,
|
|
52
|
+
$container->get('database'),
|
|
53
|
+
$container->get('entity_field.manager'),
|
|
54
|
+
$container->get('cache.entity'),
|
|
55
|
+
$container->get('language_manager'),
|
|
56
|
+
$container->get('entity.memory_cache'),
|
|
57
|
+
$container->get('entity_type.bundle.info'),
|
|
58
|
+
$container->get('entity_type.manager'),
|
|
59
|
+
$container->get('uuid'),
|
|
60
|
+
$container->get('datetime.time'),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* {@inheritdoc}
|
|
66
|
+
*/
|
|
67
|
+
public function create(array $values = []) {
|
|
68
|
+
return parent::create($values + [
|
|
69
|
+
'token' => $this->generateUniqueToken(),
|
|
70
|
+
'generated_timestamp' => $this->time->getRequestTime(),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* {@inheritdoc}
|
|
76
|
+
*/
|
|
77
|
+
public function save(EntityInterface $entity) {
|
|
78
|
+
assert($entity instanceof SilverbackPreviewLinkInterface);
|
|
79
|
+
if ($entity->regenerateToken()) {
|
|
80
|
+
$entity->setToken($this->generateUniqueToken());
|
|
81
|
+
}
|
|
82
|
+
return parent::save($entity);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Gets the unique token for the link.
|
|
87
|
+
*
|
|
88
|
+
* This token is unique every time we generate a link, there is nothing
|
|
89
|
+
* from the original entity involved in the token so it does not need to be
|
|
90
|
+
* cryptographically secure, only sufficiently random which UUID is.
|
|
91
|
+
*
|
|
92
|
+
* @return string
|
|
93
|
+
* A unique identifier for this preview link.
|
|
94
|
+
*/
|
|
95
|
+
protected function generateUniqueToken(): string {
|
|
96
|
+
return $this->uuidService->generate();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
|
|
8
|
+
use Drupal\silverback_preview_link\Entity\SilverbackPreviewLinkInterface;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interface for Preview Link entities.
|
|
12
|
+
*
|
|
13
|
+
* @method \Drupal\silverback_preview_link\Entity\SilverbackPreviewLinkInterface[] loadMultiple(array $ids = NULL)
|
|
14
|
+
* @method \Drupal\silverback_preview_link\Entity\SilverbackPreviewLinkInterface create(array $values = [])
|
|
15
|
+
* @method int save(SilverbackPreviewLinkInterface $entity)
|
|
16
|
+
*/
|
|
17
|
+
interface PreviewLinkStorageInterface extends SqlEntityStorageInterface {
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link;
|
|
6
|
+
|
|
7
|
+
use Drupal\Core\Entity\EntityTypeInterface;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Preview link utility.
|
|
11
|
+
*/
|
|
12
|
+
class PreviewLinkUtility {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Determines if an entity type is supported by Preview Link.
|
|
16
|
+
*
|
|
17
|
+
* @param \Drupal\Core\Entity\EntityTypeInterface $entityType
|
|
18
|
+
* An entity type.
|
|
19
|
+
*
|
|
20
|
+
* @return bool
|
|
21
|
+
* Whether an entity type is supported by Preview Link.
|
|
22
|
+
*/
|
|
23
|
+
public static function isEntityTypeSupported(EntityTypeInterface $entityType): bool {
|
|
24
|
+
return $entityType->isRevisionable() && $entityType->hasLinkTemplate('canonical');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_preview_link;
|
|
4
|
+
|
|
5
|
+
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation\Version;
|
|
6
|
+
use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
|
|
7
|
+
use chillerlan\QRCode\Data\QRMatrix;
|
|
8
|
+
use chillerlan\QRCode\Common\EccLevel;
|
|
9
|
+
use Symfony\Component\HttpFoundation\Response;
|
|
10
|
+
use function file_exists, gzencode, header, is_readable, max, min;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates and renders a QR Code with embedded SVG logo.
|
|
14
|
+
*/
|
|
15
|
+
class QRCodeWithLogo {
|
|
16
|
+
|
|
17
|
+
private $config = [
|
|
18
|
+
'svgLogo' => __DIR__ . '/images/amazee-labs_logo-square-green.svg',
|
|
19
|
+
'svgLogoScale' => 1,
|
|
20
|
+
'svgLogoCssClass' => 'dark',
|
|
21
|
+
'version' => QRCode::VERSION_AUTO,
|
|
22
|
+
'outputType' => QRCode::OUTPUT_CUSTOM,
|
|
23
|
+
'outputInterface' => QRMarkupSVGWithLogo::class,
|
|
24
|
+
'imageBase64' => FALSE,
|
|
25
|
+
// ECC level H is necessary when using logos.
|
|
26
|
+
'eccLevel' => EccLevel::H,
|
|
27
|
+
'addQuietzone' => TRUE,
|
|
28
|
+
// If set to TRUE, the light modules won't be rendered.
|
|
29
|
+
'imageTransparent' => FALSE,
|
|
30
|
+
// Empty the default value to remove the fill* attributes from the <path> elements
|
|
31
|
+
'markupDark' => '',
|
|
32
|
+
'markupLight' => '',
|
|
33
|
+
'drawCircularModules' => TRUE,
|
|
34
|
+
'circleRadius' => 0.45,
|
|
35
|
+
'svgConnectPaths' => TRUE,
|
|
36
|
+
'keepAsSquare' => [
|
|
37
|
+
QRMatrix::M_FINDER | QRMatrix::IS_DARK,
|
|
38
|
+
QRMatrix::M_FINDER_DOT,
|
|
39
|
+
QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK,
|
|
40
|
+
],
|
|
41
|
+
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
|
|
42
|
+
'svgDefs' => '
|
|
43
|
+
<linearGradient id="gradient" x1="100%" y2="100%">
|
|
44
|
+
<stop stop-color="#951b81" offset="0"/>
|
|
45
|
+
<stop stop-color="#00a29a" offset="0.8"/>
|
|
46
|
+
</linearGradient>
|
|
47
|
+
<style><![CDATA[
|
|
48
|
+
.dark{fill: url(#gradient);}
|
|
49
|
+
.light{fill: #fff;}
|
|
50
|
+
]]></style>',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
private function getOptions(): QROptions {
|
|
54
|
+
// Augment the QROptions class.
|
|
55
|
+
return new class ($this->config) extends QROptions {
|
|
56
|
+
|
|
57
|
+
protected string $svgLogo;
|
|
58
|
+
|
|
59
|
+
// Logo scale in % of QR Code size, clamped to 5%-15%.
|
|
60
|
+
protected float $svgLogoScale = 0.20;
|
|
61
|
+
|
|
62
|
+
// CSS class for the logo (defined in $svgDefs).
|
|
63
|
+
protected string $svgLogoCssClass = '';
|
|
64
|
+
|
|
65
|
+
protected function set_svgLogo(string $svgLogo): void {
|
|
66
|
+
if (!file_exists($svgLogo) || !is_readable($svgLogo)) {
|
|
67
|
+
throw new QRCodeException('invalid svg logo');
|
|
68
|
+
}
|
|
69
|
+
$this->svgLogo = $svgLogo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clamp logo scale.
|
|
73
|
+
protected function set_svgLogoScale(float $svgLogoScale): void {
|
|
74
|
+
$this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale)) / 2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public function getQRCode(string $data) {
|
|
81
|
+
return (new QRCode($this->getOptions()))->render($data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_preview_link;
|
|
4
|
+
|
|
5
|
+
use chillerlan\QRCode\Output\QRMarkupSVG;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Output interface for QRCode::OUTPUT_CUSTOM.
|
|
9
|
+
*/
|
|
10
|
+
class QRMarkupSVGWithLogo extends QRMarkupSVG {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* {@inheritdoc}
|
|
14
|
+
*/
|
|
15
|
+
protected function paths(): string {
|
|
16
|
+
$size = (int) ceil($this->moduleCount * $this->options->svgLogoScale);
|
|
17
|
+
// Calling QRMatrix::setLogoSpace() manually,
|
|
18
|
+
// so QROptions::$addLogoSpace has no effect.
|
|
19
|
+
$this->matrix->setLogoSpace($size, $size);
|
|
20
|
+
$svg = parent::paths();
|
|
21
|
+
$svg .= $this->getLogo();
|
|
22
|
+
return $svg;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* {@inheritdoc}
|
|
27
|
+
*/
|
|
28
|
+
protected function path(string $path, int $M_TYPE): string {
|
|
29
|
+
// Omit the "fill" and "opacity" attributes on the path element.
|
|
30
|
+
return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns a <g> element that contains the SVG logo and positions
|
|
35
|
+
* it properly within the QR Code.
|
|
36
|
+
*
|
|
37
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
|
38
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
|
|
39
|
+
*/
|
|
40
|
+
protected function getLogo(): string {
|
|
41
|
+
return sprintf(
|
|
42
|
+
'%5$s<g transform="translate(%1$s %1$s) scale(%2$s)" class="%3$s">%5$s%4$s%5$s</g>',
|
|
43
|
+
(($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2),
|
|
44
|
+
$this->options->svgLogoScale,
|
|
45
|
+
$this->options->svgLogoCssClass,
|
|
46
|
+
file_get_contents($this->options->svgLogo),
|
|
47
|
+
$this->options->eol
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types = 1);
|
|
4
|
+
|
|
5
|
+
namespace Drupal\silverback_preview_link\Routing;
|
|
6
|
+
|
|
7
|
+
use Drupal\Core\Entity\EntityTypeInterface;
|
|
8
|
+
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
|
9
|
+
use Symfony\Component\Routing\Route;
|
|
10
|
+
use Symfony\Component\Routing\RouteCollection;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Preview Link route provider.
|
|
14
|
+
*/
|
|
15
|
+
class PreviewLinkRouteProvider implements EntityRouteProviderInterface {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* {@inheritdoc}
|
|
19
|
+
*/
|
|
20
|
+
public function getRoutes(EntityTypeInterface $entity_type) {
|
|
21
|
+
$collection = new RouteCollection();
|
|
22
|
+
|
|
23
|
+
$generateRoute = $this->getGeneratePreviewLinkRoute($entity_type);
|
|
24
|
+
if ($generateRoute !== NULL) {
|
|
25
|
+
$entity_type_id = $entity_type->id();
|
|
26
|
+
$collection->add("entity.{$entity_type_id}.silverback_preview_link_generate", $generateRoute);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return $collection;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gets the route for generating and viewing preview links for this entity.
|
|
34
|
+
*
|
|
35
|
+
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
|
36
|
+
* The entity type.
|
|
37
|
+
*
|
|
38
|
+
* @return \Symfony\Component\Routing\Route|null
|
|
39
|
+
* The generated route, if available.
|
|
40
|
+
*/
|
|
41
|
+
protected function getGeneratePreviewLinkRoute(EntityTypeInterface $entity_type): ?Route {
|
|
42
|
+
$entity_type_id = $entity_type->id();
|
|
43
|
+
$route = new Route($entity_type->getLinkTemplate('preview-link-generate'));
|
|
44
|
+
|
|
45
|
+
$route
|
|
46
|
+
->setDefaults([
|
|
47
|
+
'_entity_form' => "silverback_preview_link.silverback_preview_link",
|
|
48
|
+
'_title' => 'Share preview',
|
|
49
|
+
])
|
|
50
|
+
->setRequirement('_permission', 'generate silverback preview links')
|
|
51
|
+
->setRequirement('_access_preview_enabled', 'TRUE')
|
|
52
|
+
->setOption('silverback_preview_link.entity_type_id', $entity_type_id)
|
|
53
|
+
->setOption('_admin_route', TRUE)
|
|
54
|
+
->setOption('parameters', [
|
|
55
|
+
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
return $route;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg width="100%" height="100%" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
2
|
+
<g transform="matrix(1,0,0,1,0.11328,0.418902)">
|
|
3
|
+
<g transform="matrix(0.023858,0,0,0.023858,-7.11186,-4.05593)">
|
|
4
|
+
<path d="M661.8,407L661.8,390.8L614.5,390.8L614.5,312.2L596.9,312.2L596.9,407L661.8,407Z" style="fill:rgb(0,162,154);fill-rule:nonzero;"/>
|
|
5
|
+
</g>
|
|
6
|
+
<g transform="matrix(0.023858,0,0,0.023858,-7.11186,-4.05593)">
|
|
7
|
+
<path d="M519.3,416C492.8,416 476.7,401 476,400.3C472.4,396.9 472.3,391.2 475.7,387.6C479.1,384 484.7,383.9 488.3,387.3C488.9,387.8 500.3,398.1 519.3,398.1C528.9,398.1 533.3,396 538.9,393.3C545.4,390.2 552.8,386.6 567.1,386C572.1,385.8 576.2,389.7 576.4,394.6C576.6,399.5 572.7,403.7 567.8,403.9C557.3,404.3 552.4,406.7 546.7,409.4C540.4,412.5 533.1,416 519.3,416" style="fill:rgb(0,162,154);fill-rule:nonzero;"/>
|
|
8
|
+
</g>
|
|
9
|
+
<g transform="matrix(0.023858,0,0,0.023858,-7.11186,-4.05593)">
|
|
10
|
+
<path d="M372.9,390L429.8,390L401.4,339.5L372.9,390ZM458.8,407L344,407L401.4,305.1L458.8,407Z" style="fill:rgb(0,162,154);fill-rule:nonzero;"/>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<div class="preview-link">
|
|
2
|
+
<h2 class="preview-link__title hidden">{{ title }}</h2>
|
|
3
|
+
{% if preview_url is not empty and not preview_link_has_expired %}
|
|
4
|
+
<div class="preview-link__copy js-form-item form-item js-form-type-textfield form-type--textfield">
|
|
5
|
+
<input id="preview-link__copy--text"
|
|
6
|
+
disabled
|
|
7
|
+
name="preview-link__copy--text"
|
|
8
|
+
type="text"
|
|
9
|
+
value="{{ preview_url }}"
|
|
10
|
+
size="28"
|
|
11
|
+
class="form-text required form-element form-element--type-text form-element--api-textfield"
|
|
12
|
+
>
|
|
13
|
+
<button class="button button--primary">{{ 'Copy' }}</button>
|
|
14
|
+
</div>
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% if preview_link_has_expired and display_gif %}
|
|
17
|
+
<iframe src="https://giphy.com/embed/CZGcUfnAy3ayJw2eZX" width="480" height="398" style="" frameBorder="0" class="giphy-embed" allowFullScreen></iframe>
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% if preview_url is not empty and not preview_link_has_expired %}
|
|
20
|
+
<div class="preview-link__qr">
|
|
21
|
+
<p>{{ 'Scan the QR Code to open the preview on another device.' }}</p>
|
|
22
|
+
<div class="preview-link__qr--wrapper" style="display: flex; align-items: center; justify-content: center;">
|
|
23
|
+
{% if preview_qr_code_url %}
|
|
24
|
+
<img src="{{ preview_qr_code_url }}" alt="{{ preview_url }}" width="300" height="300" />
|
|
25
|
+
{% elseif preview_qr_code_fallback is not empty %}
|
|
26
|
+
<img src="{{ preview_qr_code_fallback }}" alt="{{ preview_url }}" width="250" height="250" />
|
|
27
|
+
{% else %}
|
|
28
|
+
<p>{{ '❌ Error while generating the QR Code.' | t }}</p>
|
|
29
|
+
{% endif %}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
{% endif %}
|
|
33
|
+
{% if expiry_description is not empty %}
|
|
34
|
+
<p>{{ expiry_description }}</p>
|
|
35
|
+
{% endif %}
|
|
36
|
+
{% if actions_description is not empty %}
|
|
37
|
+
<p>{{ actions_description }}</p>
|
|
38
|
+
{% endif %}
|
|
39
|
+
</div>
|