Passed
Push — master ( 1d0e04...652cf6 )
by Thomas
03:15
created

DevBuildExtension::getRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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