Passed
Push — master ( 82f320...31cbf7 )
by Thomas
02:40 queued 10s
created

DropInvalidFilesTask::getHashPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 10
rs 10
1
<?php
2
3
namespace LeKoala\DevToolkit\Tasks;
4
5
use FilesystemIterator;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\Assets\File;
8
use RecursiveIteratorIterator;
9
use RecursiveDirectoryIterator;
10
use SilverStripe\Dev\BuildTask;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Core\Environment;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Versioned\Versioned;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Versioned\Versioned 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...
15
use LeKoala\DevToolkit\BuildTaskTools;
16
use SilverStripe\Assets\Flysystem\ProtectedAssetAdapter;
17
use SilverStripe\Assets\Folder;
18
19
/**
20
 * @author lekoala
21
 */
22
class DropInvalidFilesTask extends BuildTask
23
{
24
    use BuildTaskTools;
25
26
    protected $title = "Drop Invalid Files";
27
    protected $description = 'Drop file objects that are not linked to a proper asset (warning ! experimental)';
28
    private static $segment = 'DropInvalidFilesTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
29
30
    public function run($request)
31
    {
32
        $this->request = $request;
33
34
        $this->addOption("go", "Tick this to proceed", false);
35
        $this->addOption("remove_files", "Remove db files", false);
36
        $this->addOption("remove_local", "Remove local files", false);
37
38
        $options = $this->askOptions();
39
40
        $go = $options['go'];
41
        $remove_files = $options['remove_files'];
42
        $remove_local = $options['remove_local'];
43
44
        if (!$go) {
45
            echo ('Previewing what this task is about to do.');
46
        } else {
47
            echo ("Let's clean this up!");
48
        }
49
        echo ('<hr/>');
50
        if ($remove_files) {
51
            $this->removeFiles($request, $go);
52
        }
53
        if ($remove_local) {
54
            $this->removeLocalFiles($request, $go);
55
        }
56
    }
57
58
    protected function removeLocalFiles($request, $go = false)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

58
    protected function removeLocalFiles(/** @scrutinizer ignore-unused */ $request, $go = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
59
    {
60
        $iter = new RecursiveDirectoryIterator(ASSETS_PATH);
61
        $iter2 = new RecursiveIteratorIterator($iter);
62
63
        foreach ($iter2 as $file) {
64
            // Ignore roots and _
65
            $startsWithSlash = strpos($file->getName(), '_') === 0;
66
            $hasVariant = strpos($file->getName(), '__') !== false;
67
            if ($startsWithSlash || $hasVariant) {
68
                // $this->message("Ignore " . $file->getPath());
69
                continue;
70
            }
71
72
            // Check for empty dirs
73
            if ($file->isDir()) {
74
                // ignores .dot files
75
                $dirFiles = scandir($file->getPath());
76
                $empty = (count($dirFiles) - 2) === 0;
77
                if ($empty) {
78
                    $this->message($file->getPath() . " is empty");
79
                    if ($go) {
80
                        rmdir($file->getPath());
81
                    }
82
                }
83
                continue;
84
            }
85
86
            // Check for files not matching anything in the db
87
            $thisPath = str_replace(ASSETS_PATH, "", $file->getPath());
0 ignored issues
show
Unused Code introduced by
The assignment to $thisPath is dead and can be removed.
Loading history...
88
            // $this->message($thisPath);
89
            // $dbFile = File::get()->filter("FileFilename", $thisPath);
90
        }
91
    }
92
93
    protected function removeFiles($request, $go = false)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

93
    protected function removeFiles(/** @scrutinizer ignore-unused */ $request, $go = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
94
    {
95
        $conn = DB::get_conn();
96
        $schema = DB::get_schema();
97
        $dataObjectSchema = DataObject::getSchema();
0 ignored issues
show
Unused Code introduced by
The assignment to $dataObjectSchema is dead and can be removed.
Loading history...
98
        $tableList = $schema->tableList();
0 ignored issues
show
Unused Code introduced by
The assignment to $tableList is dead and can be removed.
Loading history...
99
100
        $files = File::get();
101
102
        if ($go) {
103
            $conn->transactionStart();
104
        }
105
106
        $i = 0;
107
108
        /** @var File $file  */
109
        foreach ($files as $file) {
110
            if ($file instanceof Folder) {
111
                continue;
112
            }
113
            $path = self::getFullPath($file);
114
            $hashPath = self::getHashPath($file);
115
            if (!trim($file->getRelativePath(), '/')) {
116
                $this->message("#{$file->ID}: path is empty");
117
                if ($go) {
118
                    // $file->delete();
119
                    self::deleteFile($file->ID);
120
                    $i++;
121
                }
122
            }
123
            if (!file_exists($path) && !file_exists($hashPath)) {
124
                $this->message("#{$file->ID}: $path does not exist");
125
                if ($go) {
126
                    // $file->delete();
127
                    self::deleteFile($file->ID);
128
                    $i++;
129
                }
130
            } else {
131
                $this->message("#{$file->ID}: $path is valid", "success");
132
            }
133
            if ($go && $i % 100 == 0) {
134
                $conn->transactionEnd();
135
                $conn->transactionStart();
136
            }
137
        }
138
139
        if ($go) {
140
            $conn->transactionEnd();
141
        }
142
    }
143
144
    /**
145
     * ORM is just too slow for this
146
     *
147
     * @param int $ID
148
     * @return void
149
     */
150
    public static function deleteFile($ID)
151
    {
152
        DB::prepared_query("DELETE FROM File WHERE ID = ?", [$ID]);
153
        DB::prepared_query("DELETE FROM File_Live WHERE ID = ?", [$ID]);
154
        DB::prepared_query("DELETE FROM File_Versions WHERE RecordID = ?", [$ID]);
155
        DB::prepared_query("DELETE FROM File_ViewerGroups WHERE FileID = ?", [$ID]);
156
        DB::prepared_query("DELETE FROM File_EditorGroups WHERE FileID = ?", [$ID]);
157
    }
158
159
    public static function getFullPath(File $file)
160
    {
161
        return ASSETS_PATH . '/' . $file->getRelativePath();
162
    }
163
164
    public static function getHashPath(File $file)
165
    {
166
        $path = $file->getRelativePath();
167
        $parts = explode('/', $path);
168
        $name = array_pop($parts);
169
        $folder = implode("/", $parts);
170
171
        $full = ASSETS_PATH . '/' . $folder . '/' . substr($file->getHash(), 0, 10) . '/' . $name;
172
        $full = str_replace('//', '/', $full);
173
        return $full;
174
    }
175
176
    public function getProtectedFullPath(File $file)
177
    {
178
        return self::getBaseProtectedPath() . '/' . $file->getRelativePath();
179
    }
180
181
    public static function getBaseProtectedPath()
182
    {
183
        // Use environment defined path or default location is under assets
184
        if ($path = Environment::getEnv('SS_PROTECTED_ASSETS_PATH')) {
185
            return $path;
186
        }
187
188
        // Default location
189
        return ASSETS_PATH . '/' . Config::inst()->get(ProtectedAssetAdapter::class, 'secure_folder');
190
    }
191
}
192