Passed
Push — master ( 74673c...6b5a1a )
by Nicolaas
02:36
created

SortOutFolders::setDebug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Sunnysideup\PerfectCmsImages\Api;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Dev\BuildTask;
9
use SilverStripe\ORM\DB;
10
11
use SilverStripe\Assets\Image;
12
use SilverStripe\Assets\Folder;
13
14
use Sunnysideup\PerfectCmsImages\Api\PerfectCMSImages;
15
16
use SilverStripe\Core\Config\Config;
17
use SilverStripe\Core\Config\Configurable;
18
19
use SilverStripe\Core\ClassInfo;
20
21
use SilverStripe\Core\Injector\Injector;
22
23
24
/**
25
 * the assumption we make here is that a particular group of images (e.g. Page.Image) live
26
 * live in a particular folder.
27
 */
28
class SortOutFolders
29
{
30
31
    use Configurable;
32
33
    /**
34
     * @var Folder
35
     */
36
    protected $unusedImagesFolder = null;
37
38
    /**
39
     *
40
     * @var bool
41
     */
42
    protected $debug = false;
43
44
    /**
45
     *
46
     * @var bool
47
     */
48
    protected $verbose = false;
49
50
    private static $unused_images_folder_name = 'unusedimages';
51
52
    public function setVerbose(?bool $b = true)
53
    {
54
        $this->verbose = $b;
55
        return $this;
56
    }
57
58
    public function setDebug(?bool $b = true)
59
    {
60
        $this->debug = $b;
61
        return $this;
62
    }
63
64
65
    /**
66
     * @param string $unusedFolderName
67
     * @param array $data
68
     * Create test jobs for the purposes of testing.
69
     * The array must contains arrays with
70
     * - folder
71
     * - used_by
72
     * used_by is an array that has ClassNames and Relations
73
     * (has_one / has_many / many_many relations)
74
     * e.g. Page.Image, MyDataObject.MyImages
75
     *
76
     * @param HTTPRequest $request
77
     */
78
    public function run(string $unusedFolderName, array $data) // phpcs:ignore
79
    {
80
        $unusedImagesFolder = Folder::find_or_make($unusedFolderName);
0 ignored issues
show
Unused Code introduced by
The assignment to $unusedImagesFolder is dead and can be removed.
Loading history...
81
82
        $folderArray = $this->getFolderArray($data);
83
        if ($this->verbose) {
84
            DB::alteration_message('==== List of folders ====');
85
            print_r($folderArray);
86
        }
87
88
        $listOfImageIds = $this->getListOfImages($folderArray);
89
        if ($this->verbose) {
90
            DB::alteration_message('==== List of Image IDs ====');
91
            print_r($listOfImageIds);
92
        }
93
94
        // remove
95
        foreach($listOfImageIds as $folderName => $listOfIds) {
96
            $this->removeUnusedFiles($folderName, $listOfIds);
97
        }
98
    }
99
100
101
    protected function getFolderArray(array $data) :array
102
    {
103
104
        // check folders
105
        $folderArray = [];
106
        foreach($data as $dataInner) {
107
            $folder = $dataInner['folder'] ?? '';
108
            if($folder) {
109
                $folderArray[$folder] = [];
110
                $classes = $dataInner['used_by'] ?? [];
111
                if(is_array($classes) && ! empty($classes)) {
112
                    foreach($classes as $classAndMethodList) {
113
                        $folderArray[$folder][$classAndMethodList] = $classAndMethodList;
114
                    }
115
                } else {
116
                    user_error('Bad definition for: '.print_r($dataInner, 1));
0 ignored issues
show
Bug introduced by
Are you sure print_r($dataInner, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

116
                    user_error('Bad definition for: './** @scrutinizer ignore-type */ print_r($dataInner, 1));
Loading history...
117
                }
118
            }
119
        }
120
        return $folderArray;
121
    }
122
123
    protected function getListOfImages(array $folderArray) : array
124
    {
125
        $listOfImageIds = [];
126
        foreach($folderArray as $folderName => $classAndMethodList) {
127
128
            // find all images that should be there...
129
            $listOfIds = [];
130
            foreach($classAndMethodList as $classAndMethod) {
131
                list($className, $method) = explode('.', $classAndMethod);
132
                $fieldDetails = $this->getFieldDetails($className, $method);
133
                if(empty($field)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field seems to never exist and therefore empty should always be true.
Loading history...
134
                    user_error('Could not find relation: '.$className.'.'.$method);
135
                }
136
                if($fieldDetails['dataType'] === 'has_one') {
137
                    $list = $className::get()->columnUnique($method.'ID');
138
                } else {
139
                    $dataClassName = $fieldDetails['dataClassName'];
140
                    $list = $dataClassName::get()->relation($method)->columnUnique('ID');
141
                }
142
                $listOfImageIds = array_merge(
143
                    $listOfImageIds,
144
                    $list
145
                );
146
            }
147
            $listOfImageIds[$folderName] = $listOfIds;
148
        }
149
        return $listOfImageIds;
150
    }
151
152
    protected function removeUnusedFiles(string $folderName, array $listOfImageIds)
0 ignored issues
show
Unused Code introduced by
The parameter $listOfImageIds 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

152
    protected function removeUnusedFiles(string $folderName, /** @scrutinizer ignore-unused */ array $listOfImageIds)

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...
153
    {
154
        $unusedFolderName = $this->unusedImagesFolder->Name;
155
        $folder = Folder::find_or_make($folderName);
156
        $where = " ParentID = " . $folder->ID. ' AND File.ID NOT IN('.implode('.$listOfImageIds.').')';
0 ignored issues
show
Bug introduced by
'.$listOfImageIds.' of type string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

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

156
        $where = " ParentID = " . $folder->ID. ' AND File.ID NOT IN('.implode(/** @scrutinizer ignore-type */ '.$listOfImageIds.').')';
Loading history...
157
        $unused = Image::get()->where($where);
158
        if ($unused->exists()) {
159
            foreach ($unused as $file) {
160
                $oldName = $file->getFullPath();
161
                if($this->verbose) {
162
                    DB::alteration_message('DEBUG ONLY '.$file->getFileName().' to '.$unusedFolderName);
163
                }
164
                if($this->debug) {
165
                    echo 'skipping as we are in debug mode';
166
                } else {
167
                    $file->ParentID = $this->unusedImagesFolder->ID;
168
                    $file->write();
169
                    $file->doPublish();
170
                    $newName = str_replace($folder->Name, $unusedFolderName, $oldName);
171
                    $oldNameFull = Controller::join_links(ASSETS_PATH, $oldName);
172
                    $newNameFull = Controller::join_links(ASSETS_PATH, $newName);
173
                    if (file_exists($oldNameFull) && $newNameFull !== $oldNameFull) {
174
                        if(file_exists($newNameFull)) {
175
                            unlink($newNameFull);
176
                        }
177
                        rename($oldNameFull, $newNameFull);
178
                    }
179
                }
180
            }
181
        }
182
    }
183
184
    protected static $my_cache = [];
185
186
    protected function getFieldDetails(string $originClassName, string $originMethod) : array
187
    {
188
        $key = $originClassName.'_'.$originMethod;
189
        if(! isset(self::$my_cache[$key])) {
190
            $types = ['has_one', 'has_many', 'many_many'];
191
            $classNames = ClassInfo::ancestry($originClassName, true);
192
            foreach ($classNames as $className) {
193
                $obj = Injector::inst()->get($className);
0 ignored issues
show
Unused Code introduced by
The assignment to $obj is dead and can be removed.
Loading history...
194
                foreach ($types as $type) {
195
                    $rels = Config::inst()->get($className, $type, Config::UNINHERITED);
196
                    if (is_array($rels) && ! empty($rels)) {
197
                        foreach ($rels as $relName => $relType) {
198
                            if (Image::class === $relType && $relName === $originatingFieldName) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $originatingFieldName seems to be never defined.
Loading history...
199
                                self::$my_cache[$key] = [
200
                                    'dataClassName' => $className,
201
                                    'dataType' => $type,
202
                                ];
203
                            }
204
                        }
205
                    }
206
                }
207
            }
208
        }
209
        return self::$my_cache[$key];
210
    }
211
212
213
}
214