SitemapTemplate::render()   F
last analyzed

Complexity

Conditions 23
Paths 339

Size

Total Lines 134
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 88
dl 0
loc 134
rs 1.5958
c 0
b 0
f 0
cc 23
nc 339
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\models;
13
14
use Craft;
15
use craft\queue\QueueInterface;
16
use nystudio107\seomatic\base\FrontendTemplate;
17
use nystudio107\seomatic\base\SitemapInterface;
18
use nystudio107\seomatic\helpers\SiteHelper;
19
use nystudio107\seomatic\helpers\Sitemap;
20
use nystudio107\seomatic\jobs\GenerateSitemap;
21
use nystudio107\seomatic\Seomatic;
22
use yii\caching\TagDependency;
23
use yii\web\NotFoundHttpException;
24
25
/**
26
 * @author    nystudio107
27
 * @package   Seomatic
28
 * @since     3.0.0
29
 */
30
class SitemapTemplate extends FrontendTemplate implements SitemapInterface
31
{
32
    // Constants
33
    // =========================================================================
34
35
    public const TEMPLATE_TYPE = 'SitemapTemplate';
36
37
    public const CACHE_KEY = 'seomatic_sitemap_';
38
39
    public const QUEUE_JOB_CACHE_KEY = 'seomatic_sitemap_queue_job_';
40
41
    public const SITEMAP_CACHE_TAG = 'seomatic_sitemap_';
42
43
    public const FILE_TYPES = [
44
        'excel',
45
        'pdf',
46
        'illustrator',
47
        'powerpoint',
48
        'text',
49
        'word',
50
        'xml',
51
    ];
52
53
    // Static Methods
54
    // =========================================================================
55
56
    /**
57
     * @param array $config
58
     *
59
     * @return null|SitemapTemplate
60
     */
61
    public static function create(array $config = [])
62
    {
63
        $defaults = [
64
            'path' => 'sitemaps-<groupId:\d+>-<type:[\w\.*]+>-<handle:[\w\.*]+>-<siteId:\d+>-<file:[-\w\.*]+>',
65
            'template' => '',
66
            'controller' => 'sitemap',
67
            'action' => 'sitemap',
68
        ];
69
        $config = array_merge($config, $defaults);
70
71
        return new SitemapTemplate($config);
72
    }
73
74
    // Public Properties
75
    // =========================================================================
76
77
    // Public Methods
78
    // =========================================================================
79
80
    /**
81
     * @inheritdoc
82
     */
83
    public function rules(): array
84
    {
85
        $rules = parent::rules();
86
        $rules = array_merge($rules, [
87
        ]);
88
89
        return $rules;
90
    }
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function fields(): array
96
    {
97
        return parent::fields();
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103
    public function render(array $params = []): string
104
    {
105
        $groupId = $params['groupId'];
106
        $type = $params['type'];
107
        $handle = $params['handle'];
108
        $siteId = $params['siteId'];
109
        // If $throwException === false it means we're trying to regenerate the sitemap due to an invalidation
110
        // rather than a request for the actual sitemap, so don't try to run the queue immediately
111
        $throwException = $params['throwException'] ?? true;
112
        // Only regenerate the sitemap via queue job if it's via an invalidation of the sitemap cache
113
        $immediately = $params['immediately'] ?? $throwException;
114
        $request = Craft::$app->getRequest();
115
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle($type, $handle, $siteId);
116
        // If it doesn't exist, throw a 404
117
        if ($metaBundle === null) {
118
            if ($request->isCpRequest || $request->isConsoleRequest) {
119
                return '';
120
            }
121
            if ($throwException) {
122
                throw new NotFoundHttpException(Craft::t('seomatic', 'Page not found.'));
123
            }
124
            return '';
125
        }
126
        // Check to see if robots is `none` or `no index`
127
        $robotsEnabled = true;
128
        if (!empty($metaBundle->metaGlobalVars->robots)) {
129
            $robotsEnabled = $metaBundle->metaGlobalVars->robots !== 'none' &&
130
                $metaBundle->metaGlobalVars->robots !== 'noindex';
131
        }
132
        $sitemapUrls = $metaBundle->metaSitemapVars->sitemapUrls;
133
        if (Seomatic::$plugin->sitemaps->anyEntryTypeHasSitemapUrls($metaBundle)) {
134
            $robotsEnabled = true;
135
            $sitemapUrls = true;
136
        }
137
        if ($sitemapUrls && !SiteHelper::siteEnabledWithUrls($siteId)) {
138
            $sitemapUrls = false;
139
        }
140
        // If it's disabled, just throw a 404
141
        if (!$sitemapUrls || !$robotsEnabled) {
142
            if ($request->isCpRequest || $request->isConsoleRequest) {
143
                return '';
144
            }
145
            if ($throwException) {
146
                throw new NotFoundHttpException(Craft::t('seomatic', 'Page not found.'));
147
            }
148
        }
149
150
        $cache = Craft::$app->getCache();
151
        $uniqueKey = $groupId . $type . $handle . $siteId;
152
        $cacheKey = self::CACHE_KEY . $uniqueKey;
153
        $queueJobCacheKey = self::QUEUE_JOB_CACHE_KEY . $uniqueKey;
154
        $result = $cache->get($cacheKey);
155
        // If the sitemap isn't cached, start a job to create it
156
        // Even if it is cached, if $throwException === false we should regenerate it, as it is part of
157
        // an invalidation
158
        if ($result === false || $throwException === false) {
159
            $queue = Craft::$app->getQueue();
160
            // If there's an existing queue job, release it so queue jobs don't stack
161
            $existingJobId = $cache->get($queueJobCacheKey);
162
            // Make sure the queue uses the Craft web interface
163
            if ($existingJobId && $queue instanceof QueueInterface) {
164
                $queue->release($existingJobId);
165
                $cache->delete($queueJobCacheKey);
166
            }
167
            // See if we should regenerate this sitemap immediately, or via queue job
168
            if ($immediately) {
169
                Sitemap::generateSitemap([
170
                    'groupId' => $groupId,
171
                    'type' => $type,
172
                    'handle' => $handle,
173
                    'siteId' => $siteId,
174
                    'queueJobCacheKey' => $queueJobCacheKey,
175
                ]);
176
            } else {
177
                // Push a new queue job
178
                $jobId = $queue->push(new GenerateSitemap([
179
                    'groupId' => $groupId,
180
                    'type' => $type,
181
                    'handle' => $handle,
182
                    'siteId' => $siteId,
183
                    'queueJobCacheKey' => $queueJobCacheKey,
184
                ]));
185
186
                // Stash the queue job id in the cache for future reference
187
                $cacheDuration = 3600;
188
                $dependency = new TagDependency([
189
                    'tags' => [
190
                        self::GLOBAL_SITEMAP_CACHE_TAG,
191
                        self::CACHE_KEY . $uniqueKey,
192
                    ],
193
                ]);
194
                $cache->set($queueJobCacheKey, $jobId, $cacheDuration, $dependency);
195
                Craft::debug(
196
                    Craft::t(
197
                        'seomatic',
198
                        'Started GenerateSitemap queue job id: {jobId} with cache key {cacheKey}',
199
                        [
200
                            'jobId' => $jobId,
201
                            'cacheKey' => $cacheKey,
202
                        ]
203
                    ),
204
                    __METHOD__
205
                );
206
            }
207
208
            // Try it again now
209
            $result = $cache->get($cacheKey);
210
            if ($result !== false) {
211
                return $result;
212
            }
213
            // Return a 503 Service Unavailable an a Retry-After so bots will try back later
214
            $lines = [];
215
            $response = Craft::$app->getResponse();
216
            if (!$request->isConsoleRequest && $throwException) {
217
                $response->setStatusCode(503);
218
                $response->headers->add('Retry-After', '60');
219
                $response->headers->add('Cache-Control', 'no-cache, no-store');
220
                // Return an empty XML document
221
                $lines[] = '<?xml version="1.0" encoding="UTF-8"?>';
222
                $lines[] = '<?xml-stylesheet type="text/xsl" href="sitemap-empty.xsl"?>';
223
                $lines[] = '<!-- ' . Craft::t('seomatic', 'This sitemap has not been generated yet.') . ' -->';
224
                $lines[] = '<!-- ' . Craft::t('seomatic', 'If you are seeing this in local dev or an') . ' -->';
225
                $lines[] = '<!-- ' . Craft::t('seomatic', 'environment with `devMode` on, caches only') . ' -->';
226
                $lines[] = '<!-- ' . Craft::t('seomatic', 'last for 30 seconds in local dev, so it is') . ' -->';
227
                $lines[] = '<!-- ' . Craft::t('seomatic', 'normal for the sitemap to not be cached.') . ' -->';
228
                $lines[] = '<urlset>';
229
                $lines[] = '</urlset>';
230
            }
231
            $lines = implode("\r\n", $lines);
232
233
            return $lines;
234
        }
235
236
        return $result;
237
    }
238
239
    /**
240
     * Invalidate a sitemap cache
241
     *
242
     * @param string $handle
243
     * @param int $siteId
244
     */
245
    public function invalidateCache(string $handle, int $siteId)
246
    {
247
        $cache = Craft::$app->getCache();
248
        TagDependency::invalidate($cache, self::SITEMAP_CACHE_TAG . $handle . $siteId);
249
        Craft::info(
250
            'Sitemap cache cleared: ' . $handle,
251
            __METHOD__
252
        );
253
    }
254
}
255