Issues (257)

src/services/FrontendTemplates.php (1 issue)

1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2017 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\services;
13
14
use Craft;
15
use craft\base\Component;
16
use craft\errors\SiteNotFoundException;
17
use craft\events\RegisterUrlRulesEvent;
18
use craft\web\UrlManager;
19
use nystudio107\seomatic\helpers\UrlHelper;
20
use nystudio107\seomatic\models\EditableTemplate;
21
use nystudio107\seomatic\models\FrontendTemplateContainer;
22
use nystudio107\seomatic\Seomatic;
23
use yii\base\Event;
24
use yii\caching\TagDependency;
25
26
/**
27
 * @author    nystudio107
28
 * @package   Seomatic
29
 * @since     3.0.0
30
 */
31
class FrontendTemplates extends Component
32
{
33
    // Constants
34
    // =========================================================================
35
36
    public const FRONTENDTEMPLATES_CONTAINER = Seomatic::SEOMATIC_HANDLE . EditableTemplate::TEMPLATE_TYPE;
37
38
    public const HUMANS_TXT_HANDLE = 'humans';
39
    public const ROBOTS_TXT_HANDLE = 'robots';
40
    public const ADS_TXT_HANDLE = 'ads';
41
    public const SECURITY_TXT_HANDLE = 'security';
42
43
    public const GLOBAL_FRONTENDTEMPLATE_CACHE_TAG = 'seomatic_frontendtemplate';
44
    public const FRONTENDTEMPLATE_CACHE_TAG = 'seomatic_frontendtemplate_';
45
46
    public const CACHE_KEY = 'seomatic_frontendtemplate_';
47
48
    public const IGNORE_DB_ATTRIBUTES = [
49
        'id',
50
        'dateCreated',
51
        'dateUpdated',
52
        'uid',
53
    ];
54
55
    // Public Properties
56
    // =========================================================================
57
58
    /**
59
     * @var FrontendTemplateContainer|null
60
     */
61
    public $frontendTemplateContainer;
62
63
    // Public Methods
64
    // =========================================================================
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function init(): void
70
    {
71
        parent::init();
72
    }
73
74
    /**
75
     * Load the frontend template containers
76
     *
77
     * @param int|null $siteId
78
     */
79
    public function loadFrontendTemplateContainers(int $siteId = null)
80
    {
81
        $sites = Craft::$app->getSites();
82
        if ($siteId === null) {
83
            $siteId = $sites->getCurrentSite()->id ?? 1;
84
        }
85
        $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId, false);
86
        if ($metaBundle === null) {
87
            return;
88
        }
89
        // Don't register any frontend templates if this site has no Base URL or a sub-directory as part of the URL
90
        $shouldRegister = false;
91
        try {
92
            $baseUrl = $sites->getCurrentSite()->getBaseUrl(true);
93
        } catch (SiteNotFoundException $e) {
94
            $baseUrl = null;
95
        }
96
        if ($baseUrl !== null && !UrlHelper::urlHasSubDir($baseUrl)) {
97
            $shouldRegister = true;
98
        }
99
        // See if the path for this request is the domain root, and the request has a file extension
100
        $request = Craft::$app->getRequest();
101
        if ($request->isConsoleRequest) {
102
            return;
103
        }
104
        $fullPath = $request->getFullPath();
105
        if ((strpos($fullPath, '/') === false) && (strpos($fullPath, '.') !== false)) {
106
            $shouldRegister = true;
107
        }
108
        // If this is a headless request, register them
109
        if (Seomatic::$headlessRequest) {
110
            $shouldRegister = true;
111
        }
112
        // Register the frontend template only if we pass the various tests
113
        if ($shouldRegister) {
114
            $this->frontendTemplateContainer = $metaBundle->frontendTemplatesContainer;
0 ignored issues
show
Documentation Bug introduced by
It seems like $metaBundle->frontendTemplatesContainer can also be of type array. However, the property $frontendTemplateContainer is declared as type null|nystudio107\seomati...ontendTemplateContainer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
115
            // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
116
            Event::on(
117
                UrlManager::class,
118
                UrlManager::EVENT_REGISTER_SITE_URL_RULES,
119
                function(RegisterUrlRulesEvent $event) {
120
                    Craft::debug(
121
                        'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
122
                        __METHOD__
123
                    );
124
                    // Register our sitemap routes
125
                    $event->rules = array_merge(
126
                        $event->rules,
127
                        $this->frontendTemplateRouteRules()
128
                    );
129
                }
130
            );
131
        }
132
    }
133
134
    /**
135
     * @return array
136
     */
137
    public function frontendTemplateRouteRules(): array
138
    {
139
        $rules = [];
140
        foreach ($this->frontendTemplateContainer->data as $frontendTemplate) {
141
            if ($frontendTemplate->include) {
142
                $rules = array_merge(
143
                    $rules,
144
                    $frontendTemplate->routeRules()
145
                );
146
            }
147
        }
148
149
        return $rules;
150
    }
151
152
    /**
153
     * @param string $template
154
     * @param array $params
155
     *
156
     * @return string
157
     */
158
    public function renderTemplate(string $template, array $params = []): string
159
    {
160
        // Make sure the cache is on a per-site basis
161
        $siteId = 1;
162
        try {
163
            $currentSite = Craft::$app->getSites()->getCurrentSite();
164
        } catch (SiteNotFoundException $e) {
165
            $currentSite = null;
166
        }
167
        if ($currentSite) {
168
            $siteId = $currentSite->id;
169
        }
170
        $dependency = new TagDependency([
171
            'tags' => [
172
                self::GLOBAL_FRONTENDTEMPLATE_CACHE_TAG,
173
                self::FRONTENDTEMPLATE_CACHE_TAG . $template,
174
                self::FRONTENDTEMPLATE_CACHE_TAG . $template . $siteId,
175
            ],
176
        ]);
177
        $cache = Craft::$app->getCache();
178
        $html = $cache->getOrSet(
179
            self::CACHE_KEY . $template . $siteId,
180
            function() use ($template, $params) {
181
                Craft::info(
182
                    'Frontend template cache miss: ' . $template,
183
                    __METHOD__
184
                );
185
                $html = '';
186
                if (!empty($this->frontendTemplateContainer->data[$template])) {
187
                    /** @var EditableTemplate $frontendTemplate */
188
                    $frontendTemplate = $this->frontendTemplateContainer->data[$template];
189
                    // Special-case for the Robots.text template, to upgrade it
190
                    if ($template === FrontendTemplates::ROBOTS_TXT_HANDLE) {
191
                        $frontendTemplate->templateString = str_replace(
192
                            'Sitemap: {{ seomatic.helper.sitemapIndexForSiteId() }}',
193
                            '{{ seomatic.helper.sitemapIndex() }}',
194
                            $frontendTemplate->templateString
195
                        );
196
                    }
197
                    $html = $frontendTemplate->render($params);
198
                }
199
200
                return $html;
201
            },
202
            Seomatic::$cacheDuration,
203
            $dependency
204
        );
205
206
        return $html;
207
    }
208
209
    /**
210
     * Return a EditableTemplate object by $key from container $type
211
     *
212
     * @param string $key
213
     * @param string $type
214
     *
215
     * @return null|EditableTemplate
216
     */
217
    public function getFrontendTemplateByKey(string $key, string $type = '')
218
    {
219
        $frontendTemplate = null;
220
        if (!empty($this->frontendTemplateContainer) && !empty($this->frontendTemplateContainer->data)) {
221
            foreach ($this->frontendTemplateContainer->data as $frontendTemplate) {
222
                if ($key === $frontendTemplate->handle) {
223
                    return $frontendTemplate;
224
                }
225
            }
226
        }
227
228
        return $frontendTemplate;
229
    }
230
231
    /**
232
     * Invalidate all of the frontend template caches
233
     */
234
    public function invalidateCaches()
235
    {
236
        $cache = Craft::$app->getCache();
237
        TagDependency::invalidate($cache, self::GLOBAL_FRONTENDTEMPLATE_CACHE_TAG);
238
        Craft::info(
239
            'All frontend template caches cleared',
240
            __METHOD__
241
        );
242
    }
243
244
    /**
245
     * Invalidate a frontend template cache
246
     *
247
     * @param string $template
248
     */
249
    public function invalidateFrontendTemplateCache(string $template)
250
    {
251
        $cache = Craft::$app->getCache();
252
        TagDependency::invalidate($cache, self::FRONTENDTEMPLATE_CACHE_TAG . $template);
253
        Craft::info(
254
            'Frontend template cache cleared: ' . $template,
255
            __METHOD__
256
        );
257
    }
258
259
    // Protected Methods
260
    // =========================================================================
261
}
262