Passed
Push — master ( e1aaee...213e90 )
by Thomas
04:24
created

DevBuildExtension::beforeCallActionHandler()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 38
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 25
c 2
b 0
f 0
nc 16
nop 0
dl 0
loc 38
rs 8.8977
1
<?php
2
3
namespace LeKoala\DevToolkit\Extensions;
4
5
use Exception;
6
use SilverStripe\ORM\DB;
7
use RecursiveIteratorIterator;
8
use RecursiveDirectoryIterator;
9
use SilverStripe\Core\ClassInfo;
10
use SilverStripe\Core\Extension;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Control\HTTPRequest;
14
use LeKoala\DevToolkit\Helpers\DevUtils;
15
use LeKoala\DevToolkit\Helpers\FileHelper;
16
use LeKoala\DevToolkit\Helpers\SubsiteHelper;
17
18
/**
19
 * Allow the following functions before dev build
20
 * - renameColumns
21
 * - truncateSiteTree
22
 *
23
 * Allow the following functions after dev build:
24
 * - generateQueryTraits
25
 * - clearCache
26
 * - clearEmptyFolders
27
 * - provisionLocales
28
 *
29
 * Preserve current subsite
30
 *
31
 * @property \SilverStripe\Dev\DevBuildController $owner
32
 */
33
class DevBuildExtension extends Extension
34
{
35
    /**
36
     * @var \SilverStripe\Subsites\Model\Subsite|null
0 ignored issues
show
Bug introduced by
The type SilverStripe\Subsites\Model\Subsite 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...
37
     */
38
    protected $currentSubsite;
39
40
    /**
41
     * @return \SilverStripe\Dev\DevBuildController
42
     */
43
    public function getExtensionOwner()
44
    {
45
        return $this->owner;
46
    }
47
48
    /**
49
     * @return HTTPRequest
50
     */
51
    public function getRequest()
52
    {
53
        return $this->getExtensionOwner()->getRequest();
54
    }
55
56
    /**
57
     * @return void
58
     */
59
    public function beforeCallActionHandler()
60
    {
61
        $this->currentSubsite = SubsiteHelper::currentSubsiteID();
0 ignored issues
show
Documentation Bug introduced by
It seems like LeKoala\DevToolkit\Helpe...per::currentSubsiteID() of type integer is incompatible with the declared type SilverStripe\Subsites\Model\Subsite|null of property $currentSubsite.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
62
63
        $annotate = $this->getRequest()->getVar('annotate');
64
        if ($annotate) {
65
            \SilverLeague\IDEAnnotator\DataObjectAnnotator::config()->enabled = true;
0 ignored issues
show
Bug introduced by
The type SilverLeague\IDEAnnotator\DataObjectAnnotator 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...
66
        } else {
67
            \SilverLeague\IDEAnnotator\DataObjectAnnotator::config()->enabled = false;
68
        }
69
70
        $renameColumns = $this->getRequest()->getVar('fixTableCase');
71
        if ($renameColumns) {
72
            $this->displayMessage("<div class='build'><p><b>Fixing tables case</b></p><ul>\n\n");
73
            $this->fixTableCase();
74
            $this->displayMessage("</ul>\n<p><b>Tables fixed!</b></p></div>");
75
        }
76
77
        $renameColumns = $this->getRequest()->getVar('renameColumns');
78
        if ($renameColumns) {
79
            $this->displayMessage("<div class='build'><p><b>Renaming columns</b></p><ul>\n\n");
80
            $this->renameColumns();
81
            $this->displayMessage("</ul>\n<p><b>Columns renamed!</b></p></div>");
82
        }
83
84
        $truncateSiteTree = $this->getRequest()->getVar('truncateSiteTree');
85
        if ($truncateSiteTree) {
86
            $this->displayMessage("<div class='build'><p><b>Truncating SiteTree</b></p><ul>\n\n");
87
            $this->truncateSiteTree();
88
            $this->displayMessage("</ul>\n<p><b>SiteTree truncated!</b></p></div>");
89
        }
90
91
        // Reverse the logic, don't populate by default
92
        DevUtils::updatePropCb($this->getRequest(), 'getVars', function ($arr) {
93
            if ($this->getRequest()->getVar('populate')) {
94
                $arr['dont_populate'] = false;
95
            }
96
            return $arr;
97
        });
98
    }
99
100
    protected function fixTableCase(): void
101
    {
102
        if (!Director::isDev()) {
103
            throw new Exception("Only available in dev mode");
104
        }
105
106
        $conn = DB::get_conn();
107
        $dbName = $conn->getSelectedDatabase();
108
109
        $tablesSql = "SELECT table_name FROM information_schema.tables WHERE table_schema = '$dbName';";
110
111
        $result = DB::query($tablesSql);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
112
113
        //TODO: check list of tables name and match any lowercased one to the right one from the db schema
114
    }
115
116
    protected function truncateSiteTree(): void
117
    {
118
        if (!Director::isDev()) {
119
            throw new Exception("Only available in dev mode");
120
        }
121
122
        $sql = <<<SQL
123
        TRUNCATE TABLE ErrorPage;
124
        TRUNCATE TABLE ErrorPage_Live;
125
        TRUNCATE TABLE ErrorPage_Versions;
126
        TRUNCATE TABLE SiteTree;
127
        TRUNCATE TABLE SiteTree_CrossSubsiteLinkTracking;
128
        TRUNCATE TABLE SiteTree_EditorGroups;
129
        TRUNCATE TABLE SiteTree_ImageTracking;
130
        TRUNCATE TABLE SiteTree_LinkTracking;
131
        TRUNCATE TABLE SiteTree_Live;
132
        TRUNCATE TABLE SiteTree_Versions;
133
        TRUNCATE TABLE SiteTree_ViewerGroups;
134
SQL;
135
        DB::query($sql);
136
        $this->displayMessage($sql);
137
    }
138
139
    /**
140
     * Loop on all DataObjects and look for rename_columns property
141
     *
142
     * It will rename old columns from old_value => new_value
143
     */
144
    protected function renameColumns(): void
145
    {
146
        $classes = $this->getDataObjects();
147
148
        foreach ($classes as $class) {
149
            if (!property_exists($class, 'rename_columns')) {
150
                continue;
151
            }
152
153
            $fields = $class::$rename_columns;
154
155
            $schema = DataObject::getSchema();
156
            $tableName = $schema->baseDataTable($class);
157
158
            $dbSchema = DB::get_schema();
159
            foreach ($fields as $oldName => $newName) {
160
                if ($dbSchema->hasField($tableName, $oldName)) {
161
                    if ($dbSchema->hasField($tableName, $newName)) {
162
                        $this->displayMessage("<li>$oldName still exists in $tableName. Data will be migrated to $newName and old column $oldName will be dropped.</li>");
163
                        // Migrate data
164
                        DB::query("UPDATE $tableName SET $newName = $oldName WHERE $newName IS NULL");
165
                        // Remove column
166
                        DB::query("ALTER TABLE $tableName DROP COLUMN $oldName");
167
                    } else {
168
                        $this->displayMessage("<li>Renaming $oldName to $newName in $tableName</li>");
169
                        $dbSchema->renameField($tableName, $oldName, $newName);
170
                    }
171
                } else {
172
                    $this->displayMessage("<li>$oldName does not exist anymore in $tableName</li>");
173
                }
174
175
                // Look for fluent
176
                $fluentTable = $tableName . '_Localised';
177
                if ($dbSchema->hasTable($fluentTable)) {
178
                    if ($dbSchema->hasField($fluentTable, $oldName)) {
179
                        if ($dbSchema->hasField($fluentTable, $newName)) {
180
                            $this->displayMessage("<li>$oldName still exists in $fluentTable. Data will be migrated to $newName and old column $oldName will be dropped.</li>");
181
                            // Migrate data
182
                            DB::query("UPDATE $fluentTable SET $newName = $oldName WHERE $newName IS NULL");
183
                            // Remove column
184
                            DB::query("ALTER TABLE $fluentTable DROP COLUMN $oldName");
185
                        } else {
186
                            $this->displayMessage("<li>Renaming $oldName to $newName in $fluentTable</li>");
187
                            $dbSchema->renameField($fluentTable, $oldName, $newName);
188
                        }
189
                    } else {
190
                        $this->displayMessage("<li>$oldName does not exist anymore in $fluentTable</li>");
191
                    }
192
                }
193
            }
194
        }
195
    }
196
197
    public function afterCallActionHandler(): void
198
    {
199
        // Other helpers
200
        $clearCache = $this->owner->getRequest()->getVar('clearCache');
201
        $clearEmptyFolders = $this->owner->getRequest()->getVar('clearEmptyFolders');
202
203
        $this->displayMessage("<div class='build'>");
204
        if ($clearCache) {
205
            $this->clearCache();
206
        }
207
        if ($clearEmptyFolders) {
208
            $this->clearEmptyFolders();
209
        }
210
        $this->displayMessage("</div>");
211
212
        // Restore subsite
213
        if ($this->currentSubsite) {
214
            SubsiteHelper::changeSubsite($this->currentSubsite);
215
        }
216
217
        $provisionLocales = $this->owner->getRequest()->getVar('provisionLocales');
218
        if ($provisionLocales) {
219
            $this->displayMessage("<div class='build'><p><b>Provisioning locales</b></p><ul>\n\n");
220
            try {
221
                \LeKoala\Multilingual\LangHelper::provisionLocales();
0 ignored issues
show
Bug introduced by
The type LeKoala\Multilingual\LangHelper 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...
222
                $this->displayMessage("</ul>\n<p><b>Locales provisioned!</b></p></div>");
223
            } catch (Exception $ex) {
224
                $this->displayMessage($ex->getMessage() . '<br/>');
225
            }
226
        }
227
    }
228
229
    protected function clearCache(): void
230
    {
231
        $this->displayMessage("<strong>Clearing cache folder</strong>");
232
        $folder = Director::baseFolder() . '/silverstripe-cache';
233
        if (!is_dir($folder)) {
234
            $this->displayMessage("silverstripe-cache does not exist in base folder\n");
235
            return;
236
        }
237
        FileHelper::rmDir($folder);
238
        mkdir($folder, 0755);
239
        $this->displayMessage("Cleared silverstripe-cache folder\n");
240
    }
241
242
    protected function clearEmptyFolders(): void
243
    {
244
        $this->displayMessage("<strong>Clearing empty folders in assets</strong>");
245
        $folder = Director::publicFolder() . '/assets';
246
        if (!is_dir($folder)) {
247
            $this->displayMessage("assets folder does not exist in public folder\n");
248
            return;
249
        }
250
251
        $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder), RecursiveIteratorIterator::SELF_FIRST);
252
        foreach ($objects as $name => $object) {
253
            if ($object->isDir()) {
254
                $path = $object->getPath();
255
                if (!is_readable($path)) {
256
                    $this->displayMessage("$path is not readable\n");
257
                    continue;
258
                }
259
                if (!FileHelper::dirContainsChildren($path)) {
260
                    rmdir($path);
261
                    $this->displayMessage("Removed $path\n");
262
                }
263
            }
264
        }
265
    }
266
267
    /**
268
     * @return array<string>
269
     */
270
    protected function getDataObjects()
271
    {
272
        $classes = ClassInfo::subclassesFor(DataObject::class);
273
        array_shift($classes); // remove dataobject
274
        return $classes;
275
    }
276
277
    /**
278
     * @param string $message
279
     */
280
    protected function displayMessage($message): void
281
    {
282
        echo Director::is_cli() ? strip_tags($message) : nl2br($message);
283
    }
284
}
285