diff --git a/wire/core/Sanitizer.php b/wire/core/Sanitizer.php index c94c9946..4487b321 100644 --- a/wire/core/Sanitizer.php +++ b/wire/core/Sanitizer.php @@ -4706,59 +4706,129 @@ class Sanitizer extends Wire { */ /** - * Validate a file using FileValidator modules + * Validate and sanitize a file using FileValidator modules * - * Note that this is intended for validating file data, not file names. + * This is intended for validating file data, not file names. Depending on the FileValidator + * modules that are used, they may sanitize the file in order ot make it valid. * - * IMPORTANT: This method returns NULL if it can't find a validator for the file. This does - * not mean the file is invalid, just that it didn't have the tools to validate it. + * IMPORTANT: This method returns NULL if it can’t find a validator for the file. This does + * not mean the file is invalid, just that it didn't have the tools to validate it. If the + * getArray option is specified then it would return a blank array rather than null. + * + * **getArray option** (3.0.167+): + * When specifying true for the `getArray` option this method will return an associative array + * of validation results indexed by module name. The values for each module name will be either + * true (file validates as-is), 1 (file valid after it was sanitized), or false (file not valid + * and cannot be sanitized). A blank array is returned if no modules could perform the validation. + * + * **dryrun option** (3.0.167+): + * When specifying true for the `dryrun` option please note that no validation is performed and + * instead the method returns true or false as to whether or not the file can be validated. It + * only looks at the file extension, so the file need not exist. Meaning it’s also okay to specify + * filename like “test.jpg” without path, when using this option. If using the dryrun option with + * the `getArray` option then it will return an array of module names that would perform the + * validation for the given file type (or blank array if none). * * #pw-group-files * * @param string $filename Full path and filename to validate * @param array $options When available, provide array with any one or all of the following: - * - `page` (Page): Page object associated with $filename. - * - `field` (Field): Field object associated with $filename. - * - `pagefile` (Pagefile): Pagefile object associated with $filename. - * @return bool|null Returns TRUE if valid, FALSE if not, or NULL if no validator available for given file type. + * - `page` (Page): Page object associated with $filename. (default=null) + * - `field` (Field): Field object associated with $filename. (default=null) + * - `pagefile` (Pagefile): Pagefile object associated with $filename. (default=null) + * - `getArray` (bool): Return array of results rather than a boolean? (default=false) Added 3.0.167 + * - `dryrun` (bool|int): Specify true to only return if the file can be validated with this method, + * without actually performing any validation. (default=false). Added 3.0.167 + * @return bool|array|null Returns one of the following, depending on use of dryrun and getArray options: + * - Boolean true if valid, false if not. + * - NULL if no validator available for given file type or file does not exist. + * - If dryrun option is used, returns boolean (or array of strings if getArray option is true). + * - If getArray option is used, returns associative array of results or blank array if no validators. * */ public function validateFile($filename, array $options = array()) { + $defaults = array( 'page' => null, 'field' => null, 'pagefile' => null, + 'dryrun' => false, + 'getArray' => false, ); + $options = array_merge($defaults, $options); + $filename = (string) $filename; + $modules = $this->wire()->modules; $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - $validators = $this->wire('modules')->findByPrefix('FileValidator', false); + $validatorNames = array(); + $validatorResults = array(); $isValid = null; - foreach($validators as $validatorName) { - $info = $this->wire('modules')->getModuleInfoVerbose($validatorName); + $getArray = $options['getArray']; + $dryrun = $options['dryrun'] || !empty($options['dryRun']); + + if(!strlen($extension) || (!$dryrun && !is_file($filename))) { + return $getArray ? array() : null; + } + + // find modules that can validate extension + foreach($modules->findByPrefix('FileValidator', false) as $validatorName) { + $info = $modules->getModuleInfoVerbose($validatorName); if(empty($info) || empty($info['validates'])) continue; + foreach($info['validates'] as $ext) { - if($ext[0] == '/') { - if(!preg_match($ext, $extension)) continue; - } else if($ext !== $extension) { - continue; - } - $validator = $this->wire('modules')->get($validatorName); - if(!$validator) continue; - if(!empty($options['page'])) $validator->setPage($options['page']); - if(!empty($options['field'])) $validator->setField($options['field']); - if(!empty($options['pagefile'])) $validator->setPagefile($options['pagefile']); - $isValid = $validator->isValid($filename); - if(!$isValid) { - // move errors to Sanitizer class so they can be retrieved - foreach($validator->errors('clear array') as $error) { - $this->wire('log')->error($error); - $this->error($error); - } - break; + if($ext === $extension) { + $validatorNames[$validatorName] = $validatorName; + } else if($ext[0] === '/' && preg_match($ext, $extension)) { + $validatorNames[$validatorName] = $validatorName; + } else { + // module does not validate extension } } + + // when doing a dryrun we only need to know if at least one module can run + if($dryrun && !$getArray && count($validatorNames)) break; } - return $isValid; + + // if doing a dryrun then just return whether or not validation is possible + if($dryrun) return ($getArray ? $validatorNames : count($validatorNames) > 0); + + // if no validators can validate extension then early exit + if(empty($validatorNames)) return ($getArray ? array() : null); + + // execute modules that can validate extension and get results + foreach($validatorNames as $validatorName) { + /** @var FileValidatorModule $validator */ + $validator = $modules->get($validatorName); + if(!$validator) continue; // not likely + + if(!empty($options['page'])) $validator->setPage($options['page']); + if(!empty($options['field'])) $validator->setField($options['field']); + if(!empty($options['pagefile'])) $validator->setPagefile($options['pagefile']); + + $valid = $validator->isValid($filename); + $validatorResults[$validatorName] = $valid; // false, true or 1 + + if($valid) { + // true (bool): file is valid as-is + // 1 (int): file is valid as a result of sanitization + // in either case, continue on to the next applicable FileValidator module + continue; + } + + // at this point we’ve determined file is not valid + $isValid = false; + + // move errors to Sanitizer class so they can be retrieved + foreach($validator->errors('clear array') as $error) { + $this->wire()->log->error($error); + $this->error($error); + } + + // unless we are returning an array of results, we can stop now for invalid files + if(!$getArray) break; + } + + return $getArray ? $validatorResults : $isValid; } /**********************************************************************************************************************