Completed
Pull Request — 5.6 (#2830)
by Jeroen
14:14
created

src/Kunstmaan/SeoBundle/Twig/SeoTwigExtension.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kunstmaan\SeoBundle\Twig;
4
5
use Doctrine\ORM\EntityManager;
6
use Kunstmaan\AdminBundle\Entity\AbstractEntity;
7
use Kunstmaan\NodeBundle\Entity\AbstractPage;
8
use Kunstmaan\SeoBundle\Entity\Seo;
9
use Psr\Cache\CacheItemPoolInterface;
10
use Twig\Environment;
11
use Twig\Extension\AbstractExtension;
12
use Twig\TwigFunction;
13
14
/**
15
 * Twig extensions for Seo
16
 *
17
 * @final since 5.4
18
 */
19
class SeoTwigExtension extends AbstractExtension
20
{
21
    /**
22
     * @var EntityManager
23
     */
24
    protected $em;
25
26
    /**
27
     * Website title defined in your parameters
28
     *
29
     * @var string
30
     */
31
    private $websiteTitle;
32
33
    /**
34
     * Saves querying the db multiple times, if you happen to use any of the defined
35
     * functions more than once in your templates
36
     *
37
     * @var array
38
     */
39
    private $seoCache = [];
40
41
    /**
42
     * @var CacheItemPoolInterface
43
     */
44
    private $requestCache;
45
46
    public function __construct(EntityManager $em)
47
    {
48
        $this->em = $em;
49
    }
50
51
    /**
52
     * Returns a list of functions to add to the existing list.
53
     *
54
     * @return array An array of functions
55
     */
56
    public function getFunctions()
57
    {
58
        return [
59
            new TwigFunction('render_seo_metadata_for', [$this, 'renderSeoMetadataFor'], ['is_safe' => ['html'], 'needs_environment' => true]),
60
            new TwigFunction('get_seo_for', [$this, 'getSeoFor']),
61
            new TwigFunction('get_title_for', [$this, 'getTitleFor']),
62
            new TwigFunction('get_title_for_page_or_default', [$this, 'getTitleForPageOrDefault']),
63
            new TwigFunction('get_absolute_url', [$this, 'getAbsoluteUrl']),
64
            new TwigFunction('get_image_dimensions', [$this, 'getImageDimensions']),
65
        ];
66
    }
67
68
    /**
69
     * Validates the $url value as URL (according to » http://www.faqs.org/rfcs/rfc2396), optionally with required components.
70
     * It will just return the url if it's valid. If it starts with '/', the $host will be prepended.
71
     *
72
     * @param string $url
73
     * @param string $host
0 ignored issues
show
Should the type for parameter $host not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
74
     *
75
     * @return string
0 ignored issues
show
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
76
     */
77
    public function getAbsoluteUrl($url, $host = null)
78
    {
79
        $validUrl = filter_var($url, FILTER_VALIDATE_URL);
80
        $host = rtrim($host, '/');
81
82
        if (!$validUrl === false) {
83
            // The url is valid
84
            return $url;
85
        }
86
87
        // Prepend with $host if $url starts with "/"
88
        if (strpos($url, '/') === 0) {
89
            return $url = $host . $url;
0 ignored issues
show
$url is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
90
        }
91
92
        return false;
93
    }
94
95
    /**
96
     * @return Seo
97
     */
98
    public function getSeoFor(AbstractPage $entity)
99
    {
100
        $key = md5(\get_class($entity) . $entity->getId());
101
102
        if (!\array_key_exists($key, $this->seoCache)) {
103
            $seo = $this->em->getRepository(Seo::class)->findOrCreateFor($entity);
104
            $this->seoCache[$key] = $seo;
105
        }
106
107
        return $this->seoCache[$key];
108
    }
109
110
    /**
111
     * The first value that is not null or empty will be returned.
112
     *
113
     * @param AbstractPage $entity the entity for which you want the page title
114
     *
115
     * @return string The page title. Will look in the SEO meta first, then the NodeTranslation, then the page.
116
     */
117
    public function getTitleFor(AbstractPage $entity)
118
    {
119
        $arr = [];
120
121
        $arr[] = $this->getSeoTitle($entity);
122
123
        $arr[] = $entity->getTitle();
124
125
        return $this->getPreferredValue($arr);
126
    }
127
128
    /**
129
     * @param AbstractPage $entity
130
     * @param string|null  $default if given we'll return this text if no SEO title was found
131
     *
132
     * @return string
133
     */
134
    public function getTitleForPageOrDefault(AbstractPage $entity = null, $default = null)
135
    {
136
        if (\is_null($entity)) {
137
            return $default;
138
        }
139
140
        $arr = [];
141
142
        $arr[] = $this->getSeoTitle($entity);
143
144
        $arr[] = $default;
145
146
        $arr[] = $entity->getTitle();
147
148
        return $this->getPreferredValue($arr);
149
    }
150
151
    /**
152
     * @param AbstractEntity $entity      The entity
153
     * @param mixed          $currentNode The current node
154
     * @param string         $template    The template
155
     *
156
     * @return string
157
     */
158
    public function renderSeoMetadataFor(Environment $environment, AbstractEntity $entity, $currentNode = null, $template = '@KunstmaanSeo/SeoTwigExtension/metadata.html.twig')
159
    {
160
        $seo = $this->getSeoFor($entity);
161
        $template = $environment->load($template);
162
163
        return $template->render(
164
            [
165
                'seo' => $seo,
166
                'entity' => $entity,
167
                'currentNode' => $currentNode,
168
            ]
169
        );
170
    }
171
172
    /**
173
     * @return string
174
     */
175
    protected function getPreferredValue(array $values)
176
    {
177
        foreach ($values as $v) {
178
            if (!\is_null($v) && !empty($v)) {
179
                return $v;
180
            }
181
        }
182
183
        return '';
184
    }
185
186
    /**
187
     * @param AbstractPage $entity
188
     *
189
     * @return string|null
190
     */
191
    private function getSeoTitle(AbstractPage $entity = null)
192
    {
193
        if (\is_null($entity)) {
194
            return null;
195
        }
196
197
        $seo = $this->getSeoFor($entity);
198
        if (!\is_null($seo)) {
199
            $title = $seo->getMetaTitle();
200
            if (!empty($title)) {
201
                return str_replace('%websitetitle%', $this->getWebsiteTitle(), $title);
202
            }
203
        }
204
205
        return null;
206
    }
207
208
    /**
209
     * Gets the Website title defined in your parameters.
210
     *
211
     * @return string
212
     */
213
    public function getWebsiteTitle()
214
    {
215
        return $this->websiteTitle;
216
    }
217
218
    /**
219
     * Sets the Website title defined in your parameters.
220
     *
221
     * @param string $websiteTitle the website title
222
     *
223
     * @return self
224
     */
225
    public function setWebsiteTitle($websiteTitle)
226
    {
227
        $this->websiteTitle = $websiteTitle;
228
229
        return $this;
230
    }
231
232
    /**
233
     * @param $src
234
     *
235
     * @return array
236
     */
237
    public function getImageDimensions($src)
238
    {
239
        list($width, $height) = $this->getImageSize($src);
240
241
        return ['width' => $width, 'height' => $height];
242
    }
243
244
    public function setRequestCache(CacheItemPoolInterface $cacheService)
245
    {
246
        $this->requestCache = $cacheService;
247
    }
248
249
    /**
250
     * @return CacheItemPoolInterface
251
     */
252
    public function getRequestCache()
253
    {
254
        return $this->requestCache;
255
    }
256
257
    private function getImageSize($src)
258
    {
259
        try {
260
            $cache = $this->getRequestCache();
261
            if (null === $cache) {
262
                return getimagesize($src);
263
            }
264
265
            $cachedImageSizes = $cache->getItem(md5($src));
266
            if (!$cachedImageSizes->isHit()) {
267
                $sizes = getimagesize($src);
268
269
                $cachedImageSizes->set($sizes);
270
                $cache->save($cachedImageSizes);
271
            }
272
273
            return $cachedImageSizes->get();
274
        } catch (\Exception $e) {
275
            return [null, null];
276
        }
277
    }
278
}
279