Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
created

src/Kunstmaan/SeoBundle/Twig/SeoTwigExtension.php (5 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 Twig_Extension;
10
11
/**
12
 * Twig extensions for Seo
13
 */
14
class SeoTwigExtension extends Twig_Extension
15
{
16
    /**
17
     * @var EntityManager
18
     */
19
    protected $em;
20
21
    /**
22
     * Website title defined in your parameters
23
     *
24
     * @var string
25
     */
26
    private $websiteTitle;
27
28
    /**
29
     * Saves querying the db multiple times, if you happen to use any of the defined
30
     * functions more than once in your templates
31
     *
32
     * @var array
33
     */
34
    private $seoCache = [];
35
36
    /**
37
     * @param EntityManager $em
38
     */
39
    public function __construct(EntityManager $em)
0 ignored issues
show
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
40
    {
41
        $this->em = $em;
42
    }
43
44
    /**
45
     * Returns a list of functions to add to the existing list.
46
     *
47
     * @return array An array of functions
48
     */
49
    public function getFunctions()
50
    {
51
        return array(
52
            new \Twig_SimpleFunction('render_seo_metadata_for', array($this, 'renderSeoMetadataFor'), array('is_safe' => array('html'), 'needs_environment' => true)),
53
            new \Twig_SimpleFunction('get_seo_for', array($this, 'getSeoFor')),
54
            new \Twig_SimpleFunction('get_title_for', array($this, 'getTitleFor')),
55
            new \Twig_SimpleFunction('get_title_for_page_or_default', array($this, 'getTitleForPageOrDefault')),
56
            new \Twig_SimpleFunction('get_absolute_url', array($this, 'getAbsoluteUrl')),
57
            new \Twig_SimpleFunction('get_image_dimensions', array($this, 'getImageDimensions')),
58
        );
59
    }
60
61
    /**
62
     * Validates the $url value as URL (according to » http://www.faqs.org/rfcs/rfc2396), optionally with required components.
63
     * It will just return the url if it's valid. If it starts with '/', the $host will be prepended.
64
     *
65
     * @param string $url
66
     * @param string $host
67
     *
68
     * @return string
69
     */
70
    public function getAbsoluteUrl($url, $host = null)
71
    {
72
        $validUrl = filter_var($url, FILTER_VALIDATE_URL);
73
        $host = rtrim($host, '/');
74
75
        if (!$validUrl === false) {
76
            // The url is valid
77
            return $url;
78
        } else {
79
            // Prepend with $host if $url starts with "/"
80
            if ($url[0] == '/') {
81
                return $url = $host.$url;
82
            }
83
84
            return false;
85
        }
86
    }
87
88
    /**
89
     * @param AbstractPage $entity
90
     *
91
     * @return Seo
92
     */
93
    public function getSeoFor(AbstractPage $entity)
94
    {
95
        $key = md5(get_class($entity).$entity->getId());
96
97
        if (!array_key_exists($key, $this->seoCache)) {
98
            $seo = $this->em->getRepository('KunstmaanSeoBundle:Seo')->findOrCreateFor($entity);
99
            $this->seoCache[$key] = $seo;
100
        }
101
102
        return $this->seoCache[$key];
103
    }
104
105
    /**
106
     * The first value that is not null or empty will be returned.
107
     *
108
     * @param AbstractPage $entity the entity for which you want the page title
109
     *
110
     * @return string The page title. Will look in the SEO meta first, then the NodeTranslation, then the page.
111
     */
112
    public function getTitleFor(AbstractPage $entity)
113
    {
114
        $arr = array();
115
116
        $arr[] = $this->getSeoTitle($entity);
117
118
        $arr[] = $entity->getTitle();
119
120
        return $this->getPreferredValue($arr);
121
    }
122
123
    /**
124
     * @param AbstractPage $entity
0 ignored issues
show
Should the type for parameter $entity not be null|AbstractPage?

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...
125
     * @param null|string  $default if given we'll return this text if no SEO title was found
126
     *
127
     * @return string
0 ignored issues
show
Should the return type not be null|string?

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...
128
     */
129
    public function getTitleForPageOrDefault(AbstractPage $entity = null, $default = null)
130
    {
131
        if (is_null($entity)) {
132
            return $default;
133
        }
134
135
        $arr = array();
136
137
        $arr[] = $this->getSeoTitle($entity);
138
139
        $arr[] = $default;
140
141
        $arr[] = $entity->getTitle();
142
143
        return $this->getPreferredValue($arr);
144
    }
145
146
    /**
147
     * @param \Twig_Environment $environment
148
     * @param AbstractEntity    $entity      The entity
149
     * @param mixed             $currentNode The current node
150
     * @param string            $template    The template
151
     *
152
     * @return string
153
     */
154
    public function renderSeoMetadataFor(\Twig_Environment $environment, AbstractEntity $entity, $currentNode = null, $template = 'KunstmaanSeoBundle:SeoTwigExtension:metadata.html.twig')
155
    {
156
        $seo = $this->getSeoFor($entity);
0 ignored issues
show
$entity of type object<Kunstmaan\AdminBu...\Entity\AbstractEntity> is not a sub-type of object<Kunstmaan\NodeBundle\Entity\AbstractPage>. It seems like you assume a child class of the class Kunstmaan\AdminBundle\Entity\AbstractEntity to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
157
        $template = $environment->loadTemplate($template);
158
159
        return $template->render(
160
            array(
161
                'seo' => $seo,
162
                'entity' => $entity,
163
                'currentNode' => $currentNode,
164
            )
165
        );
166
    }
167
168
    /**
169
     * @param array $values
170
     *
171
     * @return string
172
     */
173
    protected function getPreferredValue(array $values)
174
    {
175
        foreach ($values as $v) {
176
            if (!is_null($v) && !empty($v)) {
177
                return $v;
178
            }
179
        }
180
181
        return '';
182
    }
183
184
    /**
185
     * @param AbstractPage $entity
0 ignored issues
show
Should the type for parameter $entity not be null|AbstractPage?

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...
186
     *
187
     * @return null|string
188
     */
189
    private function getSeoTitle(AbstractPage $entity = null)
190
    {
191
        if (is_null($entity)) {
192
            return null;
193
        }
194
195
        $seo = $this->getSeoFor($entity);
196
        if (!is_null($seo)) {
197
            $title = $seo->getMetaTitle();
198
            if (!empty($title)) {
199
                return str_replace('%websitetitle%', $this->getWebsiteTitle(), $title);
200
            }
201
        }
202
203
        return null;
204
    }
205
206
    /**
207
     * Gets the Website title defined in your parameters.
208
     *
209
     * @return string
210
     */
211
    public function getWebsiteTitle()
212
    {
213
        return $this->websiteTitle;
214
    }
215
216
    /**
217
     * Sets the Website title defined in your parameters.
218
     *
219
     * @param string $websiteTitle the website title
220
     *
221
     * @return self
222
     */
223
    public function setWebsiteTitle($websiteTitle)
224
    {
225
        $this->websiteTitle = $websiteTitle;
226
227
        return $this;
228
    }
229
230
    /**
231
     * @param $src
232
     *
233
     * @return array|null
234
     */
235
    public function getImageDimensions($src)
236
    {
237
        try {
238
            list($width, $height) = getimagesize($src);
239
        } catch (\Exception $e) {
240
            return null;
241
        }
242
243
        return array('width' => $width, 'height' => $height);
244
    }
245
}
246