Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

Filesystem::sync()   C

Complexity

Conditions 12
Paths 32

Size

Total Lines 58
Code Lines 27

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 58
rs 6.5331
cc 12
eloc 27
nc 32
nop 2

How to fix   Long Method    Complexity   

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
 * A collection of static methods for manipulating the filesystem.
4
 *
5
 * @package framework
6
 * @subpackage filesystem
7
 */
8
class Filesystem extends Object {
9
10
	/**
11
	 * @config
12
	 * @var integer Integer
13
	 */
14
	private static $file_create_mask = 02775;
15
16
	/**
17
	 * @config
18
	 * @var integer Integer
19
	 */
20
	private static $folder_create_mask = 02775;
21
22
	/**
23
	 * @var int
24
	 */
25
	protected static $cache_folderModTime;
26
27
	/**
28
	 * @config
29
	 *
30
	 * Array of file / folder regex expressions to exclude from the
31
	 * {@link Filesystem::sync()}
32
	 *
33
	 * @var array
34
	 */
35
	private static $sync_blacklisted_patterns = array(
36
		"/^\./",
37
		"/^_combinedfiles$/i",
38
		"/^_resampled$/i",
39
		"/^web.config/i",
40
		"/^Thumbs(.)/"
41
	);
42
43
	/**
44
	 * Create a folder on the filesystem, recursively.
45
	 * Uses {@link Filesystem::$folder_create_mask} to set filesystem permissions.
46
	 * Use {@link Folder::findOrMake()} to create a {@link Folder} database
47
	 * record automatically.
48
	 *
49
	 * @param String $folder Absolute folder path
50
	 */
51
	public static function makeFolder($folder) {
52
		if(!file_exists($base = dirname($folder))) self::makeFolder($base);
53
		if(!file_exists($folder)) mkdir($folder, Config::inst()->get('Filesystem', 'folder_create_mask'));
54
	}
55
56
	/**
57
	 * Remove a directory and all subdirectories and files.
58
	 *
59
	 * @param String $folder Absolute folder path
60
	 * @param Boolean $contentsOnly If this is true then the contents of the folder will be removed but not the
61
	 *                              folder itself
62
	 */
63
	public static function removeFolder($folder, $contentsOnly = false) {
64
65
		// remove a file encountered by a recursive call.
66
		if(is_file($folder) || is_link($folder)) {
67
			unlink($folder);
68
		} else {
69
			$dir = opendir($folder);
70
			while($file = readdir($dir)) {
71
				if(($file == '.' || $file == '..')) continue;
72
				else {
73
					self::removeFolder($folder . '/' . $file);
74
				}
75
			}
76
			closedir($dir);
77
			if(!$contentsOnly) rmdir($folder);
78
		}
79
	}
80
81
	/**
82
	 * Remove a directory, but only if it is empty.
83
	 *
84
	 * @param string $folder Absolute folder path
85
	 * @param boolean $recursive Remove contained empty folders before attempting to remove this one
86
	 * @return boolean True on success, false on failure.
87
	 */
88
	public static function remove_folder_if_empty($folder, $recursive = true) {
89
		if (!is_readable($folder)) return false;
90
		$handle = opendir($folder);
91
		while (false !== ($entry = readdir($handle))) {
92
			if ($entry != "." && $entry != "..") {
93
				// if an empty folder is detected, remove that one first and move on
94
				if($recursive && is_dir($entry) && self::remove_folder_if_empty($entry)) continue;
95
				// if a file was encountered, or a subdirectory was not empty, return false.
96
				return false;
97
			}
98
		}
99
		// if we are still here, the folder is empty.
100
		rmdir($folder);
101
		return true;
102
	}
103
104
	/**
105
	 * Cleanup function to reset all the Filename fields.  Visit File/fixfiles to call.
106
	 */
107
	public function fixfiles() {
108
		if(!Permission::check('ADMIN')) return Security::permissionFailure($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Filesystem>, but the function expects a object<Controller>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
109
110
		$files = DataObject::get("File");
111
		foreach($files as $file) {
112
			$file->updateFilesystem();
113
			echo "<li>", $file->Filename;
114
			$file->write();
115
		}
116
		echo "<p>Done!";
117
	}
118
119
	/**
120
	 * Return the most recent modification time of anything in the folder.
121
	 *
122
	 * @param $folder The folder, relative to the site root
123
	 * @param $extensionList An option array of file extensions to limit the search to
124
	 * @return String Same as filemtime() format.
125
	 */
126
	public static function folderModTime($folder, $extensionList = null, $recursiveCall = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $recursiveCall is not used and could be removed.

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

Loading history...
127
		//$cacheID = $folder . ',' . implode(',', $extensionList);
128
		//if(!$recursiveCall && self::$cache_folderModTime[$cacheID]) return self::$cache_folderModTime[$cacheID];
129
130
		$modTime = 0;
131
		if(!Filesystem::isAbsolute($folder)) $folder = Director::baseFolder() . '/' . $folder;
132
133
		$items = scandir($folder);
134
		foreach($items as $item) {
135
			if($item[0] != '.') {
136
				// Recurse into folders
137
				if(is_dir("$folder/$item")) {
138
					$modTime = max($modTime, self::folderModTime("$folder/$item", $extensionList, true));
139
140
				// Check files
141
				} else {
142
					if($extensionList) $extension = strtolower(substr($item,strrpos($item,'.')+1));
143
					if(!$extensionList || in_array($extension, $extensionList)) {
0 ignored issues
show
Bug introduced by
The variable $extension does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
144
						$modTime = max($modTime, filemtime("$folder/$item"));
145
					}
146
				}
147
			}
148
		}
149
150
		//if(!$recursiveCall) self::$cache_folderModTime[$cacheID] = $modTime;
151
		return $modTime;
152
	}
153
154
	/**
155
	 * Returns true if the given filename is an absolute file reference.
156
	 * Works on Linux and Windows.
157
	 *
158
	 * @param String $filename Absolute or relative filename, with or without path.
159
	 * @return Boolean
160
	 */
161
	public static function isAbsolute($filename) {
162
		if($_ENV['OS'] == "Windows_NT" || $_SERVER['WINDIR']) return $filename[1] == ':' && $filename[2] == '/';
163
		else return $filename[0] == '/';
164
	}
165
166
	/**
167
	 * This function ensures the file table is correct with the files in the assets folder.
168
	 *
169
	 * If a Folder record ID is given, all of that folder's children will be synchronised.
170
	 * If the given Folder ID isn't found, or not specified at all, then everything will
171
	 * be synchronised from the root folder (singleton Folder).
172
	 *
173
	 * See {@link File->updateFilesystem()} to sync properties of a single database record
174
	 * back to the equivalent filesystem record.
175
	 *
176
	 * @param int $folderID Folder ID to sync along with all it's children
177
	 * @param Boolean $syncLinkTracking Determines if the link tracking data should also
178
	 *        be updated via {@link SiteTree->syncLinkTracking()}. Setting this to FALSE
179
	 *        means that broken links inside page content are not noticed, at least until the next
180
	 *        call to {@link SiteTree->write()} on this page.
181
	 * @return string Localized status message
182
	 */
183
	public static function sync($folderID = null, $syncLinkTracking = true) {
184
		$folder = DataObject::get_by_id('Folder', (int) $folderID);
185
		if(!($folder && $folder->exists())) $folder = singleton('Folder');
186
187
		$results = $folder->syncChildren();
188
		$finished = false;
189
		while(!$finished) {
190
			$orphans = DB::query('SELECT "ChildFile"."ID" FROM "File" AS "ChildFile"
191
				LEFT JOIN "File" AS "ParentFile" ON "ChildFile"."ParentID" = "ParentFile"."ID"
192
				WHERE "ParentFile"."ID" IS NULL AND "ChildFile"."ParentID" > 0');
193
			$finished = true;
194
			if($orphans) foreach($orphans as $orphan) {
195
				$finished = false;
196
				// Delete the database record but leave the filesystem alone
197
				$file = DataObject::get_by_id("File", $orphan['ID']);
198
				$file->deleteDatabaseOnly();
199
				unset($file);
200
			}
201
		}
202
203
		// Update the image tracking of all pages
204
		if($syncLinkTracking) {
205
			if(class_exists('SiteTree')) {
206
207
				// if subsites exist, go through each subsite and sync each subsite's pages.
208
				// disabling the filter doesn't work reliably, because writing pages that share
209
				// the same URLSegment between subsites will break, e.g. "home" between two
210
				// sites will modify one of them to "home-2", thinking it's a duplicate. The
211
				// check before a write is done in SiteTree::validURLSegment()
212
				if(class_exists('Subsite')) {
213
					// loop through each subsite ID, changing the subsite, then query it's pages
214
					foreach(Subsite::get()->getIDList() as $id) {
215
						Subsite::changeSubsite($id);
216
						foreach(SiteTree::get() as $page) {
217
							// syncLinkTracking is called by SiteTree::onBeforeWrite().
218
							// Call it without affecting the page version, as this is an internal change.
219
							$page->writeWithoutVersion();
220
						}
221
					}
222
223
					// change back to the main site so the foreach below works
224
					Subsite::changeSubsite(0);
225
				}
226
227
				foreach(SiteTree::get() as $page) {
228
					// syncLinkTracking is called by SiteTree::onBeforeWrite().
229
					// Call it without affecting the page version, as this is an internal change.
230
					$page->writeWithoutVersion();
231
				}
232
			}
233
		}
234
235
		return _t(
236
			'Filesystem.SYNCRESULTS',
237
			'Sync complete: {createdcount} items created, {deletedcount} items deleted',
238
			array('createdcount' => (int)$results['added'], 'deletedcount' => (int)$results['deleted'])
0 ignored issues
show
Documentation introduced by
array('createdcount' => ...t) $results['deleted']) is of type array<string,integer,{"c...letedcount":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
239
		);
240
	}
241
242
}
243
244