Passed
Push — master ( 7d4e8a...625645 )
by Nicolaas
05:21 queued 01:31
created

updateCMSFields()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 95
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 1 Features 0
Metric Value
cc 5
eloc 58
c 10
b 1
f 0
nc 12
nop 1
dl 0
loc 95
rs 8.6052

How to fix   Long Method   

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
namespace Sunnysideup\SimpleTemplateCaching\Extensions;
4
5
use Exception;
6
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use SilverStripe\CMS\Model\SiteTree;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Forms\CheckboxField;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\GridField\GridField;
14
use SilverStripe\Forms\GridField\GridFieldConfig_RecordViewer;
15
use SilverStripe\Forms\NumericField;
16
use SilverStripe\Forms\ReadonlyField;
17
use SilverStripe\Core\Extension;
18
use SilverStripe\Forms\HeaderField;
19
use SilverStripe\ORM\DB;
20
use SilverStripe\ORM\FieldType\DBDatetime;
21
use SilverStripe\SiteConfig\SiteConfig;
22
use Sunnysideup\SimpleTemplateCaching\Model\ObjectsUpdated;
23
24
/**
25
 * Class \Sunnysideup\SimpleTemplateCaching\Extensions\SimpleTemplateCachingSiteConfigExtension.
26
 *
27
 * @property SiteConfig|SimpleTemplateCachingSiteConfigExtension $owner
28
 * @property bool $HasCaching
29
 * @property int $PublicCacheDurationInSeconds
30
 * @property bool $RecordCacheUpdates
31
 * @property string $CacheKeyLastEdited
32
 * @property string $ClassNameLastEdited
33
 */
34
class SimpleTemplateCachingSiteConfigExtension extends Extension
35
{
36
    private const MAX_OBJECTS_UPDATED = 1000;
37
38
    private static string $image_cache_directive = '
39
<IfModule mod_headers.c>
40
  <FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|avif)$">
41
    Header set Cache-Control "public, max-age=86400"
42
  </FilesMatch>
43
</IfModule>
44
    ';
45
46
    private static string $css_and_js_cache_directive = '
47
<IfModule mod_headers.c>
48
  <FilesMatch "\.(js|css)$">
49
    Header set Cache-Control "public, max-age=86400"
50
  </FilesMatch>
51
</IfModule>
52
    ';
53
54
    private static $db = [
55
        'HasCaching' => 'Boolean(1)',
56
        'HasPartialCaching' => 'Boolean(1)',
57
        'HasResourceCaching' => 'Boolean(1)',
58
        'PublicCacheDurationInSeconds' => 'Int',
59
        'RecordCacheUpdates' => 'Boolean(0)',
60
        'CacheKeyLastEdited' => 'DBDatetime',
61
        'ClassNameLastEdited' => 'Varchar(200)',
62
    ];
63
64
    public function updateCMSFields(FieldList $fields)
65
    {
66
        $owner = $this->getOwner();
67
        $name = '[none]';
68
        if (class_exists((string) $owner->ClassNameLastEdited)) {
69
            $name = Injector::inst()->get($owner->ClassNameLastEdited)->i18n_singular_name();
70
        }
71
72
        // page caching
73
        $fields->addFieldsToTab(
74
            'Root.Caching',
75
            [
76
                HeaderField::create('FullPageCachingHeader', 'Full Page Caching'),
77
                CheckboxField::create('HasCaching', 'Allow caching of entire pages?')
78
                    ->setDescription(
79
                        'You will also need to set up the cache time below for it to be enabled.
80
                        You can set a default time below, but you can also set the time for individual pages.'
81
                    ),
82
            ]
83
        );
84
        if ($owner->HasCaching) {
85
            $fields->addFieldsToTab(
86
                'Root.Caching',
87
                [
88
                    NumericField::create('PublicCacheDurationInSeconds', 'Cache time for ALL pages')
89
                        ->setDescription(
90
                            'USE WITH CARE - This will apply caching to ALL pages on the site.
91
                            Time is in seconds (e.g. 600 = 10 minutes).
92
                            Cache time on individual pages will override this value set here.
93
                            The total number of pages on the site with an individual caching time is: ' . Page::get()->filter('PublicCacheDurationInSeconds:GreaterThan', 0)->count()
94
                        ),
95
                ]
96
            );
97
        }
98
99
        //partial caching
100
        $fields->addFieldsToTab(
101
            'Root.Caching',
102
            [
103
                HeaderField::create('PartialCachingHeader', 'Partial Caching'),
104
                CheckboxField::create('HasPartialCaching', 'Allow partial template caching?')
105
                    ->setDescription(
106
                        'This should usually be turned on unless you want to make sure no templates are cached in any part at all.'
107
                    ),
108
            ]
109
        );
110
        if ($owner->HasPartialCaching) {
111
            $fields->addFieldsToTab(
112
                'Root.Caching',
113
                [
114
                    CheckboxField::create('RecordCacheUpdates', 'Keep a record of what is being changed?')
115
                        ->setDescription(
116
                            'To work out when the cache is being cleared,
117
                            you can keep a record of the last ' . self::MAX_OBJECTS_UPDATED . ' records changed.
118
                            Only turn this on temporarily for tuning purposes.'
119
                        ),
120
                ]
121
            );
122
            if ($this->getOwner()->RecordCacheUpdates) {
123
                $fields->addFieldsToTab(
124
                    'Root.Caching',
125
                    [
126
127
                        ReadonlyField::create('CacheKeyLastEditedNice', 'Last database change', $owner->dbObject('CacheKeyLastEdited')->ago())
128
                            ->setDescription(
129
                                'The frontend template cache will be invalidated every time this value changes.
130
                                                The value changes every time anything is changed in the database.'
131
                            ),
132
                        ReadonlyField::create('ClassNameLastEditedNice', 'Last record updated', $name)
133
                            ->setDescription('The last record to invalidate the cache.'),
134
135
                        GridField::create(
136
                            'ObjectsUpdated',
137
                            'Last ' . self::MAX_OBJECTS_UPDATED . ' records updated',
138
                            ObjectsUpdated::get()->limit(self::MAX_OBJECTS_UPDATED),
139
                            GridFieldConfig_RecordViewer::create()
140
                        )
141
                            ->setDescription(
142
                                '
143
                                This is a list of the last ' . self::MAX_OBJECTS_UPDATED . ' records updated.
144
                                It is used to track changes to the database.
145
                                It includes: ' . ObjectsUpdated::classes_edited()
146
                            ),
147
                    ]
148
                );
149
            }
150
        }
151
        // resource caching
152
        $fields->addFieldsToTab(
153
            'Root.Caching',
154
            [
155
                HeaderField::create('ResourceCachingHeader', 'Resource Caching'),
156
                CheckboxField::create('HasResourceCaching', 'Allow caching of resources (e.g. images, styles, etc.). ')
157
                    ->setDescription(
158
                        'This will add cache control headers to your .htaccess file for images, styles, and scripts.
159
                        This will help with performance, but once cached, a cache can not be cleared without changing the file name.'
160
                    ),
161
            ]
162
        );
163
    }
164
165
    public static function site_cache_key(): string
166
    {
167
        $obj = SiteConfig::current_site_config();
168
        if ($obj->HasPartialCaching) {
169
            return 'ts_' . strtotime((string) $obj->CacheKeyLastEdited);
170
        }
171
172
        return 'ts_' . time();
173
    }
174
175
    public static function update_cache_key(?string $className = '')
176
    {
177
        // important - avoid endless loop!
178
        if (SiteConfig::get()->exists()) {
179
            $howOldIsIt = DB::query('SELECT Created FROM SiteConfig LIMIT 1')->value();
180
            if ($howOldIsIt && strtotime((string) $howOldIsIt) > strtotime('-5 minutes')) {
181
                return;
182
            }
183
        } else {
184
            return;
185
        }
186
        try {
187
            $obj = SiteConfig::current_site_config();
188
            if ($obj->HasPartialCaching) {
189
                DB::query('
190
                    UPDATE "SiteConfig"
191
                    SET
192
                        "CacheKeyLastEdited" = \'' . DBDatetime::now()->Rfc2822() . '\',
193
                        "ClassNameLastEdited" = \'' . addslashes((string) $className) . '\'
194
                    WHERE ID = ' . $obj->ID . '
195
                    LIMIT 1
196
                ;');
197
                if ($obj->RecordCacheUpdates) {
198
                    $recordId = Injector::inst()
199
                        ->create(ObjectsUpdated::class, ['ClassNameLastEdited' => $className])
200
                        ->write();
201
                    DB::query('DELETE FROM "ObjectsUpdated" WHERE "ID" < ' . (int) ($recordId - self::MAX_OBJECTS_UPDATED));
202
                }
203
            } else {
204
                DB::query('TRUNCATE "ObjectsUpdated";');
205
            }
206
        } catch (Exception $e) {
207
            DB::query('
208
                UPDATE "SiteConfig"
209
                SET
210
                    "CacheKeyLastEdited" = \'' . DBDatetime::now()->Rfc2822() . '\',
211
                    "ClassNameLastEdited" = \'ERROR\'
212
                WHERE ID = ' . $obj->ID . '
213
                LIMIT 1
214
        ;');
215
        }
216
    }
217
218
    public function requireDefaultRecords()
219
    {
220
        $currentSiteConfig = SiteConfig::current_site_config();
221
        if ((int) SiteConfig::get()->count() > 100) {
222
            if ($currentSiteConfig) {
0 ignored issues
show
introduced by
$currentSiteConfig is of type SilverStripe\SiteConfig\SiteConfig, thus it always evaluated to true.
Loading history...
223
                DB::alteration_message('Deleting all SiteConfig records except for the current one.', 'deleted');
224
                DB::query('DELETE FROM "SiteConfig" WHERE ID <> ' . $currentSiteConfig->ID);
225
            }
226
        }
227
        foreach (
228
            [
229
                'IMAGE_CACHE_DIRECTIVE' => $currentSiteConfig->config()->get('image_cache_directive'),
230
                'CSS_JS_CACHE_DIRECTIVE' => $currentSiteConfig->config()->get('css_and_js_cache_directive'),
231
            ] as $key => $value
232
        ) {
233
            if (! $currentSiteConfig->HasResourceCaching) {
234
                $value = '';
235
            }
236
            $this->updateHtaccess($key, $value);
237
        }
238
    }
239
240
    protected function updateHtaccess(string $code, string $toAdd)
241
    {
242
        $htaccessPath = Controller::join_links(Director::publicFolder(), '.htaccess');
243
        $htaccessContent = file_get_contents($htaccessPath);
244
        $originalContent = $htaccessContent;
245
246
        // Define start and end comments
247
        $startComment = PHP_EOL . "# auto add start " . $code . PHP_EOL;
248
        $endComment = PHP_EOL . "# auto add end " . $code . PHP_EOL;
249
250
        // Full content to replace or add
251
        $toAddFull = $startComment . $toAdd . $endComment;
252
253
        // Check if the section already exists
254
        $pattern = "/" . preg_quote($startComment, '/') . ".*?" . preg_quote($endComment, '/') . "/s";
255
        if (preg_match($pattern, $htaccessContent)) {
256
            // Replace existing content between the start and end comments
257
            $htaccessContent = preg_replace($pattern, $toAddFull, $htaccessContent);
258
        } else {
259
            // Prepend the new content if not found
260
            $htaccessContent = $toAddFull . $htaccessContent;
261
        }
262
        if ($originalContent !== $htaccessContent) {
263
            // Save the updated .htaccess file
264
            DB::alteration_message('Updating .htaccess file with ' . $code . ' cache directive', 'created');
265
            file_put_contents($htaccessPath, $htaccessContent);
266
        }
267
    }
268
}
269