Skip to content

Commit afb4627

Browse files
committed
TASK: Initial commit
1 parent a93516a commit afb4627

File tree

9 files changed

+715
-0
lines changed

9 files changed

+715
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
namespace Yeebase\Fusion\ContentCacheDebug\Aspect;
3+
4+
/**
5+
* This file is part of the Yeebase.Fusion.ContentCacheDebug package.
6+
*
7+
* (c) 2018 yeebase media GmbH
8+
*
9+
* This package is Open Source Software. For the full copyright and license
10+
* information, please view the LICENSE file which was distributed with this
11+
* source code.
12+
*/
13+
14+
use Neos\Cache\CacheAwareInterface;
15+
use Neos\Flow\Annotations as Flow;
16+
use Neos\Flow\Aop\JoinPointInterface;
17+
use Neos\Fusion\Core\Cache\ContentCache;
18+
use Neos\Fusion\FusionObjects\AbstractFusionObject;
19+
use Neos\Utility\ObjectAccess;
20+
21+
/**
22+
* @Flow\Scope("singleton")
23+
* @Flow\Aspect
24+
*/
25+
class ContentCacheSegmentAspect
26+
{
27+
const MODE_CACHED = 'cached';
28+
const MODE_UNCACHED = 'uncached';
29+
const MODE_DYNAMIC = 'dynamic';
30+
31+
/**
32+
* @var array
33+
*/
34+
protected $interceptedCacheEntryValues = [];
35+
36+
/**
37+
* @var string
38+
*/
39+
protected $cacheSegmentTail;
40+
41+
/**
42+
* @var AbstractFusionObject
43+
*/
44+
protected $interceptedFusionObject;
45+
46+
/**
47+
* @param ContentCache $contentCache
48+
*/
49+
public function injectContentCache(ContentCache $contentCache)
50+
{
51+
$randomCacheMarker = ObjectAccess::getProperty($contentCache, 'randomCacheMarker', true);
52+
$this->cacheSegmentTail = ContentCache::CACHE_SEGMENT_END_TOKEN . $randomCacheMarker;
53+
}
54+
55+
/**
56+
* @Flow\Pointcut("setting(Yeebase.Fusion.ContentCacheDebug.enabled) && evaluate(current.securityContext.initialized == true, current.securityContext.roles contains 'Yeebase.Fusion.ContentCacheDebug:Debugger')")
57+
*/
58+
public function debuggingActive()
59+
{
60+
}
61+
62+
/**
63+
* @Flow\Around("method(Neos\Fusion\Core\Cache\ContentCache->createCacheSegment()) && Yeebase\Fusion\ContentCacheDebug\Aspect\ContentCacheSegmentAspect->debuggingActive")
64+
* @param JoinPointInterface $joinPoint
65+
* @return string
66+
*/
67+
public function wrapCachedSegment(JoinPointInterface $joinPoint): string
68+
{
69+
$segment = $joinPoint->getAdviceChain()->proceed($joinPoint);
70+
71+
return $this->renderCacheInfoIntoSegment($segment, [
72+
'mode' => static::MODE_CACHED,
73+
'fusionPath' => $joinPoint->getMethodArgument('fusionPath'),
74+
'entryIdentifier' => $this->interceptedCacheEntryValues,
75+
'entryTags' => $joinPoint->getMethodArgument('tags'),
76+
'lifetime' => $joinPoint->getMethodArgument('lifetime')
77+
]);
78+
}
79+
80+
/**
81+
* @Flow\Around("method(Neos\Fusion\Core\Cache\ContentCache->createUncachedSegment()) && Yeebase\Fusion\ContentCacheDebug\Aspect\ContentCacheSegmentAspect->debuggingActive")
82+
* @param JoinPointInterface $joinPoint
83+
* @return string
84+
*/
85+
public function wrapUncachedSegment(JoinPointInterface $joinPoint): string
86+
{
87+
$segment = $joinPoint->getAdviceChain()->proceed($joinPoint);
88+
89+
return $this->renderCacheInfoIntoSegment($segment, [
90+
'mode' => static::MODE_UNCACHED,
91+
'fusionPath' => $joinPoint->getMethodArgument('fusionPath'),
92+
'contextVariables' => array_keys($joinPoint->getMethodArgument('contextVariables'))
93+
]);
94+
}
95+
96+
97+
/**
98+
* @Flow\Around("method(Neos\Fusion\Core\Cache\ContentCache->createDynamicCachedSegment()) && Yeebase\Fusion\ContentCacheDebug\Aspect\ContentCacheSegmentAspect->debuggingActive")
99+
* @param JoinPointInterface $joinPoint
100+
* @return string
101+
*/
102+
public function wrapDynamicSegment(JoinPointInterface $joinPoint): string
103+
{
104+
$segment = $joinPoint->getAdviceChain()->proceed($joinPoint);
105+
106+
return $this->renderCacheInfoIntoSegment($segment, [
107+
'mode' => static::MODE_DYNAMIC,
108+
'fusionPath' => $joinPoint->getMethodArgument('fusionPath'),
109+
'entryIdentifier' => $this->interceptedCacheEntryValues,
110+
'entryTags' => $joinPoint->getMethodArgument('tags'),
111+
'lifetime' => $joinPoint->getMethodArgument('lifetime'),
112+
'contextVariables' => array_keys($joinPoint->getMethodArgument('contextVariables')),
113+
'entryDiscriminator' => $joinPoint->getMethodArgument('cacheDiscriminator')
114+
]);
115+
}
116+
117+
/**
118+
* @Flow\Around("method(Neos\Fusion\Core\Cache\ContentCache->renderContentCacheEntryIdentifier()) && Yeebase\Fusion\ContentCacheDebug\Aspect\ContentCacheSegmentAspect->debuggingActive")
119+
* @param JoinPointInterface $joinPoint
120+
* @return string
121+
*/
122+
public function interceptContentCacheEntryIdentifier(JoinPointInterface $joinPoint): string
123+
{
124+
125+
$cacheIdentifierValues = $joinPoint->getMethodArgument('cacheIdentifierValues');
126+
$this->interceptedCacheEntryValues = [];
127+
128+
foreach ($cacheIdentifierValues as $key => $value) {
129+
if ($value instanceof CacheAwareInterface) {
130+
$this->interceptedCacheEntryValues[$key] = $value->getCacheEntryIdentifier();
131+
} else if (is_string($value) || is_bool($value) || is_integer($value)) {
132+
$this->interceptedCacheEntryValues[$key] = $value;
133+
}
134+
}
135+
136+
$result = $joinPoint->getAdviceChain()->proceed($joinPoint);
137+
$this->interceptedCacheEntryValues['=>'] = $result;
138+
return $result;
139+
}
140+
141+
/**
142+
* @Flow\Before("method(Neos\Fusion\Core\Cache\RuntimeContentCache->postProcess()) && Yeebase\Fusion\ContentCacheDebug\Aspect\ContentCacheSegmentAspect->debuggingActive")
143+
* @param JoinPointInterface $joinPoint
144+
*/
145+
public function interceptFusionObject(JoinPointInterface $joinPoint)
146+
{
147+
$this->interceptedFusionObject = $joinPoint->getMethodArgument('fusionObject');
148+
}
149+
150+
/**
151+
* @param string $segment
152+
* @param array $info
153+
* @return string
154+
*/
155+
protected function renderCacheInfoIntoSegment(string $segment, array $info): string
156+
{
157+
$injectPosition = 2;
158+
$info = array_slice($info, 0, $injectPosition, true)
159+
+ ['fusionObject' => ObjectAccess::getProperty($this->interceptedFusionObject, 'fusionObjectName', true)]
160+
+ array_slice($info, $injectPosition, count($info) - $injectPosition, true);
161+
162+
$info['created'] = (new \DateTime())->format('d.m.Y H:i:s');
163+
164+
$segmentHead = substr($segment, 0, strlen($segment) - strlen($this->cacheSegmentTail));
165+
$segmentEnd = $this->cacheSegmentTail;
166+
167+
// Ensure we don't place comments outside of the html tag
168+
$htmlEndPosition = strpos($segmentHead, '</html>');
169+
if ($htmlEndPosition !== false) {
170+
$segmentEnd = substr($segmentHead, $htmlEndPosition) . $segmentEnd;
171+
$segmentHead = substr($segmentHead, 0, $htmlEndPosition);
172+
}
173+
174+
return $segmentHead . '<!--__CONTENT_CACHE_DEBUG__ ' . json_encode($info) . ' -->' . $segmentEnd;
175+
}
176+
}

Configuration/Policy.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
roles:
3+
'Yeebase.Fusion.ContentCacheDebug:Debugger':
4+
abstract: TRUE

Configuration/Settings.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Yeebase:
2+
Fusion:
3+
ContentCacheDebug:
4+
enabled: false
5+
Neos:
6+
Neos:
7+
fusion:
8+
autoInclude:
9+
'Yeebase.Fusion.ContentCacheDebug': false

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Yeebase.Fusion.ContentCacheDebug
2+
3+
The Yeebase.Fusion.ContentCacheDebug package is a helper package to visualize your cache configuration. Once the package is active, administrators can view a cache configuration overlay on the website to see exactly which parts of the website are cached like
4+
5+
6+
## Installation & configuration
7+
8+
Install the package via composer
9+
```
10+
composer require yeebase/fusion-contentcachedebug
11+
```
12+
13+
The debug mode is disabled by default. To enable it add this to your Settings.yaml
14+
15+
```yaml
16+
Yeebase:
17+
Fusion:
18+
ContentCacheDebug:
19+
enabled: true
20+
```
21+
22+
Once the package is activated it will render some meta data into the html output if the current user inherits the role `Yeebase.Fusion.ContentCacheDebug:Debugger`. So make sure that
23+
this role is applied as well.
24+
25+
## Usage
26+
To enable the cache visualization open your browsers developer console and execute
27+
`__enable_content_cache_debug__()`. This will add three new buttons.
28+
29+
flaslight: toggle visualization
30+
clipboard: displays a list of used cached entries in a hierarchical order
31+
X: disble debug mode
32+
33+
![Demo](demo.gif)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
prototype(Neos.Neos:Page) {
2+
head.contentCacheDebugStyle = Neos.Fusion:Tag {
3+
tagName = 'link'
4+
attributes {
5+
rel = 'stylesheet'
6+
href = Neos.Fusion:ResourceUri {
7+
path = 'resource://Yeebase.Fusion.ContentCacheDebug/Public/Style/main.css'
8+
}
9+
}
10+
11+
@if.notInBackend = ${!documentNode.context.inBackend}
12+
@if.isActive = ${Configuration.setting('Yeebase.Fusion.ContentCacheDebug.enabled')}
13+
@if.onlyAdmins = ${Security.hasRole('Yeebase.Fusion.ContentCacheDebug:Debugger')}
14+
}
15+
16+
contentCacheDebugScript = Neos.Fusion:Tag {
17+
tagName = 'script'
18+
attributes {
19+
src = Neos.Fusion:ResourceUri {
20+
path = 'resource://Yeebase.Fusion.ContentCacheDebug/Public/Script/main.js'
21+
}
22+
}
23+
24+
@if.notInBackend = ${!documentNode.context.inBackend}
25+
@if.isActive = ${Configuration.setting('Yeebase.Fusion.ContentCacheDebug.enabled')}
26+
@if.onlyAdmins = ${Security.hasRole('Yeebase.Fusion.ContentCacheDebug:Debugger')}
27+
@position = 'before closingBodyTag'
28+
}
29+
}

0 commit comments

Comments
 (0)