├── .editorconfig ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codeception.dist.yml ├── composer.json ├── src ├── File.php ├── FileError.php ├── NoFilesException.php ├── Providers │ └── FuelServiceProvider.php └── Upload.php └── tests ├── _bootstrap.php ├── _support └── UnitHelper.php ├── unit.suite.yml └── unit ├── UnitTester.php └── _bootstrap.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | 11 | [*.yml] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/_log/ 2 | tests/_output/ 3 | vendor/ 4 | codeception.yml 5 | composer.lock 6 | composer.phar 7 | index.php 8 | 9 | ### IDE ### 10 | .buildpath 11 | .project 12 | .settings 13 | nbproject/ 14 | .idea/ 15 | *.tmproj 16 | *.sublime-project 17 | *.sublime-workspace 18 | 19 | ### Cache, temp, etc ### 20 | .DS_Store 21 | cache/ 22 | *~ 23 | *.bak 24 | Thumbs.db 25 | desktop.ini 26 | 27 | tests/_output/* 28 | 29 | tests/_output/* -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - php 3 | filter: 4 | paths: [src/*] 5 | tools: 6 | external_code_coverage: 7 | timeout: 600 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer install --no-interaction 11 | 12 | script: 13 | - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then vendor/bin/codecept run unit --coverage-xml; fi; 14 | - if [[ "$TRAVIS_PHP_VERSION" == "hhvm" ]]; then vendor/bin/codecept run unit; fi; 15 | 16 | after_script: 17 | - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi 18 | - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php ocular.phar code-coverage:upload --format=php-clover tests/_output/coverage.xml; fi; 19 | 20 | 21 | notifications: 22 | irc: "irc.freenode.org#fuelphp-status" 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | ## 2.0.0 - 2015-01-01 5 | 6 | ### Added 7 | 8 | - Inital release 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 - 2015 Fuel Development Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuel Upload 2 | 3 | [![Build Status](https://img.shields.io/travis/fuelphp/upload.svg?style=flat-square)](https://travis-ci.org/fuelphp/upload) 4 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/fuelphp/upload.svg?style=flat-square)](https://scrutinizer-ci.com/g/fuelphp/upload) 5 | [![Quality Score](https://img.shields.io/scrutinizer/g/fuelphp/upload.svg?style=flat-square)](https://scrutinizer-ci.com/g/fuelphp/upload) 6 | [![HHVM Status](https://img.shields.io/hhvm/fuelphp/upload.svg?style=flat-square)](http://hhvm.h4cc.de/package/fuelphp/upload) 7 | 8 | 9 | **Process uploaded files.** 10 | 11 | 12 | ## Contributing 13 | 14 | Thank you for considering contribution to FuelPHP framework. Please see [CONTRIBUTING](https://github.com/fuelphp/fuelphp/blob/master/CONTRIBUTING.md) for details. 15 | 16 | 17 | ## License 18 | 19 | The MIT License (MIT). Please see [License File](LICENSE) for more information. 20 | -------------------------------------------------------------------------------- /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | helpers: tests/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | colors: false 10 | memory_limit: 1024M 11 | coverage: 12 | enabled: true 13 | include: 14 | - src/* 15 | exclude: 16 | - src/Providers/* 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuelphp/upload", 3 | "description": "Process uploaded files", 4 | "keywords": ["Upload", "File uploads"], 5 | "homepage": "https://github.com/fuelphp/upload", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "FuelPHP Development Team", 10 | "email": "team@fuelphp.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3", 15 | "ext-fileinfo": "*" 16 | }, 17 | "require-dev": { 18 | "codeception/codeception": "~2.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Fuel\\Upload\\": "src/" 23 | } 24 | }, 25 | "extra": { 26 | "branch-alias": { 27 | "dev-master": "2.0-dev" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/File.php: -------------------------------------------------------------------------------- 1 | null, 55 | 'moveCallback' => null, 56 | // validation settings 57 | 'max_size' => 0, 58 | 'max_length' => 0, 59 | 'ext_whitelist' => array(), 60 | 'ext_blacklist' => array(), 61 | 'type_whitelist' => array(), 62 | 'type_blacklist' => array(), 63 | 'mime_whitelist' => array(), 64 | 'mime_blacklist' => array(), 65 | // file settings 66 | 'prefix' => '', 67 | 'suffix' => '', 68 | 'extension' => '', 69 | 'randomize' => false, 70 | 'normalize' => false, 71 | 'normalize_separator' => '_', 72 | 'change_case' => false, 73 | // save-to-disk settings 74 | 'path' => '', 75 | 'create_path' => true, 76 | 'path_chmod' => 0777, 77 | 'file_chmod' => 0666, 78 | 'auto_rename' => true, 79 | 'new_name' => false, 80 | 'overwrite' => false, 81 | ); 82 | 83 | /** 84 | * @var boolean 85 | */ 86 | protected $isValidated = false; 87 | 88 | /** 89 | * @var boolean 90 | */ 91 | protected $isValid = false; 92 | 93 | /** 94 | * @var array 95 | */ 96 | protected $callbacks = array(); 97 | 98 | /** 99 | * @param array $file 100 | * @param array|null $callbacks 101 | */ 102 | public function __construct(array $file, &$callbacks = array()) 103 | { 104 | // store the file data for this file 105 | $this->container = $file; 106 | 107 | // the file callbacks reference 108 | $this->callbacks =& $callbacks; 109 | } 110 | 111 | /** 112 | * Magic getter, gives read access to all elements in the file container 113 | * 114 | * @param string $name 115 | * 116 | * @return mixed 117 | */ 118 | public function __get($name) 119 | { 120 | $name = strtolower($name); 121 | return isset($this->container[$name]) ? $this->container[$name] : null; 122 | } 123 | 124 | /** 125 | * Magic setter, gives write access to all elements in the file container 126 | * 127 | * @param string $name 128 | * @param mixed $value 129 | */ 130 | public function __set($name, $value) 131 | { 132 | $name = strtolower($name); 133 | array_key_exists($name, $this->container) and $this->container[$name] = $value; 134 | } 135 | 136 | /** 137 | * Returns the validation state of this object 138 | * 139 | * @return boolean 140 | */ 141 | public function isValidated() 142 | { 143 | return $this->isValidated; 144 | } 145 | 146 | /** 147 | * Returns the state of this object 148 | * 149 | * @return boolean 150 | */ 151 | public function isValid() 152 | { 153 | return $this->isValid; 154 | } 155 | 156 | /** 157 | * Returns the error objects collected for this file upload 158 | * 159 | * @return FileError[] 160 | */ 161 | public function getErrors() 162 | { 163 | return $this->isValidated ? $this->errors : array(); 164 | } 165 | 166 | /** 167 | * Sets the configuration for this file 168 | * 169 | * @param string|array $item 170 | * @param mixed $value 171 | */ 172 | public function setConfig($item, $value = null) 173 | { 174 | // unify the parameters 175 | is_array($item) or $item = array($item => $value); 176 | 177 | // update the configuration 178 | foreach ($item as $name => $value) 179 | { 180 | array_key_exists($name, $this->config) and $this->config[$name] = $value; 181 | } 182 | } 183 | 184 | /** 185 | * Runs validation on the uploaded file, based on the config being loaded 186 | * 187 | * @return boolean 188 | */ 189 | public function validate() 190 | { 191 | // reset the error container and status 192 | $this->errors = array(); 193 | $this->isValid = true; 194 | 195 | // validation starts, call the pre-validation callback 196 | $this->runCallbacks('before_validation'); 197 | 198 | // was the upload of the file a success? 199 | if ($this->container['error'] == 0) 200 | { 201 | // add some filename details (pathinfo can't be trusted with utf-8 filenames!) 202 | $this->container['extension'] = ltrim(strrchr(ltrim($this->container['name'], '.'), '.'),'.'); 203 | if (empty($this->container['extension'])) 204 | { 205 | $this->container['basename'] = $this->container['name']; 206 | } 207 | else 208 | { 209 | $this->container['basename'] = substr($this->container['name'], 0, strlen($this->container['name'])-(strlen($this->container['extension'])+1)); 210 | } 211 | 212 | // does this upload exceed the maximum size? 213 | if ( ! empty($this->config['max_size']) and is_numeric($this->config['max_size']) and $this->container['size'] > $this->config['max_size']) 214 | { 215 | $this->addError(static::UPLOAD_ERR_MAX_SIZE); 216 | } 217 | 218 | // add mimetype information 219 | try 220 | { 221 | $handle = finfo_open(FILEINFO_MIME_TYPE); 222 | $this->container['mimetype'] = finfo_file($handle, $this->container['tmp_name']); 223 | finfo_close($handle); 224 | } 225 | // this will only work if PHP errors are converted into ErrorException (like when you use FuelPHP) 226 | catch (\ErrorException $e) 227 | { 228 | $this->container['mimetype'] = false; 229 | $this->addError(UPLOAD_ERR_NO_FILE); 230 | } 231 | 232 | // make sure it contains something valid 233 | if (empty($this->container['mimetype']) or strpos($this->container['mimetype'], '/') === false) 234 | { 235 | $this->container['mimetype'] = 'application/octet-stream'; 236 | } 237 | 238 | // split the mimetype info so we can run some tests 239 | preg_match('|^(.*)/(.*)|', $this->container['mimetype'], $mimeinfo); 240 | 241 | // check the file extension black- and whitelists 242 | if (in_array(strtolower($this->container['extension']), (array) $this->config['ext_blacklist'])) 243 | { 244 | $this->addError(static::UPLOAD_ERR_EXT_BLACKLISTED); 245 | } 246 | elseif ( ! empty($this->config['ext_whitelist']) and ! in_array(strtolower($this->container['extension']), (array) $this->config['ext_whitelist'])) 247 | { 248 | $this->addError(static::UPLOAD_ERR_EXT_NOT_WHITELISTED); 249 | } 250 | 251 | // check the file type black- and whitelists 252 | if (in_array($mimeinfo[1], (array) $this->config['type_blacklist'])) 253 | { 254 | $this->addError(static::UPLOAD_ERR_TYPE_BLACKLISTED); 255 | } 256 | if ( ! empty($this->config['type_whitelist']) and ! in_array($mimeinfo[1], (array) $this->config['type_whitelist'])) 257 | { 258 | $this->addError(static::UPLOAD_ERR_TYPE_NOT_WHITELISTED); 259 | } 260 | 261 | // check the file mimetype black- and whitelists 262 | if (in_array($this->container['mimetype'], (array) $this->config['mime_blacklist'])) 263 | { 264 | $this->addError(static::UPLOAD_ERR_MIME_BLACKLISTED); 265 | } 266 | elseif ( ! empty($this->config['mime_whitelist']) and ! in_array($this->container['mimetype'], (array) $this->config['mime_whitelist'])) 267 | { 268 | $this->addError(static::UPLOAD_ERR_MIME_NOT_WHITELISTED); 269 | } 270 | 271 | // validation finished, call the post-validation callback 272 | $this->runCallbacks('after_validation'); 273 | } 274 | else 275 | { 276 | // upload was already a failure, store the corresponding error 277 | $this->addError($this->container['error']); 278 | } 279 | 280 | // set the flag to indicate we ran the validation 281 | $this->isValidated = true; 282 | 283 | // return the validation state 284 | return $this->isValid; 285 | } 286 | 287 | /** 288 | * Saves the uploaded file 289 | * 290 | * @return boolean 291 | * 292 | * @throws \DomainException if destination path specified does not exist 293 | */ 294 | public function save() 295 | { 296 | $tempfileCreated = false; 297 | 298 | // we can only save files marked as valid 299 | if ($this->isValid) 300 | { 301 | // make sure we have a valid path 302 | if (empty($this->container['path'])) 303 | { 304 | $this->container['path'] = rtrim($this->config['path'], DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; 305 | } 306 | 307 | // if the path does not exist 308 | if ( ! is_dir($this->container['path'])) 309 | { 310 | // do we need to create it? 311 | if ((bool) $this->config['create_path']) 312 | { 313 | @mkdir($this->container['path'], $this->config['path_chmod'], true); 314 | 315 | if ( ! is_dir($this->container['path'])) 316 | { 317 | $this->addError(static::UPLOAD_ERR_MKDIR_FAILED); 318 | } 319 | } 320 | else 321 | { 322 | $this->addError(static::UPLOAD_ERR_NO_PATH); 323 | } 324 | } 325 | 326 | // start processing the uploaded file 327 | if ($this->isValid) 328 | { 329 | $this->container['path'] = realpath($this->container['path']).DIRECTORY_SEPARATOR; 330 | 331 | // was a new name for the file given? 332 | if ( ! is_string($this->container['filename']) or $this->container['filename'] === '') 333 | { 334 | // do we need to generate a random filename? 335 | if ( (bool) $this->config['randomize']) 336 | { 337 | $this->container['filename'] = md5(serialize($this->container)); 338 | } 339 | 340 | // do we need to normalize the filename? 341 | else 342 | { 343 | $this->container['filename'] = $this->container['basename']; 344 | (bool) $this->config['normalize'] and $this->normalize(); 345 | } 346 | } 347 | 348 | // was a hardcoded new name specified in the config? 349 | if (array_key_exists('new_name', $this->config) and $this->config['new_name'] !== false) 350 | { 351 | $new_name = pathinfo($this->config['new_name']); 352 | empty($new_name['filename']) or $this->container['filename'] = $new_name['filename']; 353 | empty($new_name['extension']) or $this->container['extension'] = $new_name['extension']; 354 | } 355 | 356 | // array with all filename components 357 | $filename = array( 358 | $this->config['prefix'], 359 | $this->container['filename'], 360 | $this->config['suffix'], 361 | '', 362 | '.', 363 | empty($this->config['extension']) ? $this->container['extension'] : $this->config['extension'] 364 | ); 365 | 366 | // remove the dot if no extension is present 367 | empty($filename[5]) and $filename[4] = ''; 368 | 369 | // need to modify case? 370 | switch($this->config['change_case']) 371 | { 372 | case 'upper': 373 | $filename = array_map(function($var) { return strtoupper($var); }, $filename); 374 | break; 375 | 376 | case 'lower': 377 | $filename = array_map(function($var) { return strtolower($var); }, $filename); 378 | break; 379 | 380 | default: 381 | break; 382 | } 383 | 384 | // if we're saving the file locally 385 | if ( ! $this->config['moveCallback']) 386 | { 387 | // check if the file already exists 388 | if (file_exists($this->container['path'].implode('', $filename))) 389 | { 390 | // generate a unique filename if needed 391 | if ( (bool) $this->config['auto_rename']) 392 | { 393 | $counter = 0; 394 | do 395 | { 396 | $filename[3] = '_'.++$counter; 397 | } 398 | while (file_exists($this->container['path'].implode('', $filename))); 399 | 400 | // claim this generated filename before someone else does 401 | touch($this->container['path'].implode('', $filename)); 402 | $tempfileCreated = true; 403 | } 404 | else 405 | { 406 | // if we can't overwrite, we've got to bail out now 407 | if ( ! (bool) $this->config['overwrite']) 408 | { 409 | $this->addError(static::UPLOAD_ERR_DUPLICATE_FILE); 410 | } 411 | } 412 | } 413 | } 414 | 415 | // no need to store it as an array anymore 416 | $this->container['filename'] = implode('', $filename); 417 | 418 | // does the filename exceed the maximum length? 419 | if ( ! empty($this->config['max_length']) and strlen($this->container['filename']) > $this->config['max_length']) 420 | { 421 | $this->addError(static::UPLOAD_ERR_MAX_FILENAME_LENGTH); 422 | } 423 | 424 | // if the file is still valid, run the before save callbacks 425 | if ($this->isValid) 426 | { 427 | // validation starts, call the pre-save callbacks 428 | $this->runCallbacks('before_save'); 429 | 430 | // recheck the path, it might have been altered by a callback 431 | if ($this->isValid and ! is_dir($this->container['path']) and (bool) $this->config['create_path']) 432 | { 433 | @mkdir($this->container['path'], $this->config['path_chmod'], true); 434 | 435 | if ( ! is_dir($this->container['path'])) 436 | { 437 | $this->addError(static::UPLOAD_ERR_MKDIR_FAILED); 438 | } 439 | } 440 | 441 | // if the file is still valid, move it 442 | if ($this->isValid) 443 | { 444 | // check if file should be moved to an ftp server 445 | if ($this->config['moveCallback']) 446 | { 447 | $moved = call_user_func($this->config['moveCallback'], $this->container['tmp_name'], $this->container['path'].$this->container['filename']); 448 | 449 | if ( ! $moved) 450 | { 451 | $this->addError(static::UPLOAD_ERR_EXTERNAL_MOVE_FAILED); 452 | } 453 | } 454 | else 455 | { 456 | if( ! @move_uploaded_file($this->container['tmp_name'], $this->container['path'].$this->container['filename'])) 457 | { 458 | $this->addError(static::UPLOAD_ERR_MOVE_FAILED); 459 | } 460 | else 461 | { 462 | @chmod($this->container['path'].$this->container['filename'], $this->config['file_chmod']); 463 | } 464 | } 465 | } 466 | } 467 | } 468 | 469 | // call the post-save callbacks if the file was succefully saved 470 | if ($this->isValid) 471 | { 472 | $this->runCallbacks('after_save'); 473 | } 474 | 475 | // if there was an error and we've created a temp file, make sure to remove it 476 | elseif ($tempfileCreated) 477 | { 478 | unlink($this->container['path'].$this->container['filename']); 479 | } 480 | } 481 | 482 | // return the status of this operation 483 | return $this->isValid; 484 | } 485 | 486 | /** 487 | * Runs callbacks of he defined type 488 | * 489 | * @param callable $type 490 | */ 491 | protected function runCallbacks($type) 492 | { 493 | // make sure we have callbacks of this type 494 | if (array_key_exists($type, $this->callbacks)) 495 | { 496 | // run the defined callbacks 497 | foreach ($this->callbacks[$type] as $callback) 498 | { 499 | // check if the callback is valid 500 | if (is_callable($callback)) 501 | { 502 | // call the defined callback 503 | $result = call_user_func_array($callback, array(&$this)); 504 | 505 | // and process the results. we need FileError instances only 506 | foreach ((array) $result as $entry) 507 | { 508 | if (is_object($entry) and $entry instanceOf FileError) 509 | { 510 | $this->errors[] = $entry; 511 | } 512 | } 513 | 514 | // update the status of this validation 515 | $this->isValid = empty($this->errors); 516 | } 517 | } 518 | } 519 | } 520 | 521 | /** 522 | * Converts a filename into a normalized name. only outputs 7 bit ASCII characters. 523 | */ 524 | protected function normalize() 525 | { 526 | // Decode all entities to their simpler forms 527 | $this->container['filename'] = html_entity_decode($this->container['filename'], ENT_QUOTES, 'UTF-8'); 528 | 529 | // Remove all quotes 530 | $this->container['filename'] = preg_replace("#[\"\']#", '', $this->container['filename']); 531 | 532 | // Strip unwanted characters 533 | $this->container['filename'] = preg_replace("#[^a-z0-9]#i", $this->config['normalize_separator'], $this->container['filename']); 534 | $this->container['filename'] = preg_replace("#[/_|+ -]+#u", $this->config['normalize_separator'], $this->container['filename']); 535 | $this->container['filename'] = trim($this->container['filename'], $this->config['normalize_separator']); 536 | } 537 | 538 | /** 539 | * Adds a new error object to the list 540 | * 541 | * @param integer $error 542 | */ 543 | protected function addError($error) 544 | { 545 | $this->errors[] = new FileError($error, $this->config['langCallback']); 546 | $this->isValid = false; 547 | } 548 | 549 | //------------------------------------------------------------------------------------------------------------------ 550 | 551 | /** 552 | * Countable methods 553 | */ 554 | #[\ReturnTypeWillChange] 555 | public function count()/*: int*/ 556 | { 557 | return count($this->container); 558 | } 559 | 560 | /** 561 | * ArrayAccess methods 562 | */ 563 | #[\ReturnTypeWillChange] 564 | public function offsetExists(/*mixed */$offset)/*: bool*/ 565 | { 566 | return isset($this->container[$offset]); 567 | } 568 | 569 | #[\ReturnTypeWillChange] 570 | public function offsetGet(/*mixed */$offset)/*: mixed*/ 571 | { 572 | return $this->container[$offset]; 573 | } 574 | 575 | #[\ReturnTypeWillChange] 576 | public function offsetSet(/*mixed */$offset, /*mixed */$value)/*: void*/ 577 | { 578 | $this->container[$offset] = $value; 579 | } 580 | 581 | #[\ReturnTypeWillChange] 582 | public function offsetUnset(/*mixed */$offset)/*: void*/ 583 | { 584 | throw new \OutOfBoundsException('You can not unset a data element of an Upload File instance'); 585 | } 586 | 587 | /** 588 | * Iterator methods 589 | */ 590 | #[\ReturnTypeWillChange] 591 | public function rewind()/*: void*/ 592 | { 593 | return reset($this->container); 594 | } 595 | 596 | #[\ReturnTypeWillChange] 597 | public function current()/*: mixed*/ 598 | { 599 | return current($this->container); 600 | } 601 | 602 | #[\ReturnTypeWillChange] 603 | public function key()/*: mixed*/ 604 | { 605 | return key($this->container); 606 | } 607 | 608 | #[\ReturnTypeWillChange] 609 | public function next()/*: void*/ 610 | { 611 | return next($this->container); 612 | } 613 | 614 | #[\ReturnTypeWillChange] 615 | public function valid()/*: bool*/ 616 | { 617 | return key($this->container) !== null; 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /src/FileError.php: -------------------------------------------------------------------------------- 1 | 'The file uploaded with success', 23 | 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', 24 | 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', 25 | 3 => 'The uploaded file was only partially uploaded', 26 | 4 => 'No file was uploaded', 27 | 6 => 'Configured temporary upload folder is missing', 28 | 7 => 'Failed to write uploaded file to disk', 29 | 8 => 'Upload blocked by an installed PHP extension', 30 | 101 => 'The uploaded file exceeds the defined maximum size', 31 | 102 => 'Upload of files with this extension is not allowed', 32 | 103 => 'Upload of files with this extension is not allowed', 33 | 104 => 'Upload of files of this file type is not allowed', 34 | 105 => 'Upload of files of this file type is not allowed', 35 | 106 => 'Upload of files of this mime type is not allowed', 36 | 107 => 'Upload of files of this mime type is not allowed', 37 | 108 => 'The uploaded file name exceeds the defined maximum length', 38 | 109 => 'Unable to move the uploaded file to it\'s final destination', 39 | 110 => 'A file with the name of the uploaded file already exists', 40 | 111 => 'Unable to create the file\'s destination directory', 41 | 112 => 'Unable to upload the file to the destination using FTP', 42 | 113 => 'The configured destination path does not exist', 43 | ); 44 | 45 | /** 46 | * @var integer 47 | */ 48 | protected $error = 0; 49 | 50 | /** 51 | * @var string 52 | */ 53 | protected $message = ''; 54 | 55 | /** 56 | * @param integer $error 57 | * @param callable|null $langCallback 58 | */ 59 | public function __construct($error, $langCallback = null) 60 | { 61 | $this->error = $error; 62 | 63 | if (is_callable($langCallback)) 64 | { 65 | $this->message = call_user_func($langCallback, $error); 66 | } 67 | 68 | if (empty($this->message)) 69 | { 70 | $this->message = isset($this->messages[$error]) ? $this->messages[$error] : 'Unknown error message number: '.$error; 71 | } 72 | } 73 | 74 | /** 75 | * Returns the error code 76 | * 77 | * @return integer 78 | */ 79 | public function getError() 80 | { 81 | return $this->error; 82 | } 83 | 84 | /** 85 | * Returns the error message 86 | * 87 | * @return string 88 | */ 89 | public function getMessage() 90 | { 91 | return $this->message; 92 | } 93 | 94 | /** 95 | * __toString magic method, will output the stored error message 96 | * 97 | * @return string 98 | */ 99 | public function __toString() 100 | { 101 | return $this->getMessage(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/NoFilesException.php: -------------------------------------------------------------------------------- 1 | register('upload', function (array $config = null) 33 | { 34 | return new Upload($config); 35 | }); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Upload.php: -------------------------------------------------------------------------------- 1 | false, 34 | 'langCallback' => null, 35 | 'moveCallback' => null, 36 | // validation settings 37 | 'max_size' => 0, 38 | 'max_length' => 0, 39 | 'ext_whitelist' => array(), 40 | 'ext_blacklist' => array(), 41 | 'type_whitelist' => array(), 42 | 'type_blacklist' => array(), 43 | 'mime_whitelist' => array(), 44 | 'mime_blacklist' => array(), 45 | // file settings 46 | 'prefix' => '', 47 | 'suffix' => '', 48 | 'extension' => '', 49 | 'randomize' => false, 50 | 'normalize' => false, 51 | 'normalize_separator' => '_', 52 | 'change_case' => false, 53 | // save-to-disk settings 54 | 'path' => '', 55 | 'create_path' => true, 56 | 'path_chmod' => 0777, 57 | 'file_chmod' => 0666, 58 | 'auto_rename' => true, 59 | 'new_name' => false, 60 | 'overwrite' => false, 61 | ); 62 | 63 | /** 64 | * @var array 65 | */ 66 | protected $callbacks = array( 67 | 'before_validation' => array(), 68 | 'after_validation' => array(), 69 | 'before_save' => array(), 70 | 'after_save' => array(), 71 | ); 72 | 73 | /** 74 | * @param array|null $config 75 | * 76 | * @throws NoFilesException if no uploaded files were found (did specify "enctype"?) 77 | */ 78 | public function __construct($config = null) 79 | { 80 | // input validation 81 | if ( ! is_array($config) and ! is_null($config)) 82 | { 83 | trigger_error('Uncaught TypeError: '.__METHOD__ .'(): Argument #1 ($config) must be of type ?array, '.gettype($config).' given, called in '.__FILE__.' on line '.__LINE__, E_USER_WARNING); 84 | } 85 | 86 | // override defaults if needed 87 | if (is_array($config)) 88 | { 89 | foreach ($config as $key => $value) 90 | { 91 | array_key_exists($key, $this->defaults) and $this->defaults[$key] = $value; 92 | } 93 | } 94 | 95 | // we can't do anything without any files uploaded 96 | if (empty($_FILES)) 97 | { 98 | throw new NoFilesException('No uploaded files were found. Did you specify "enctype" in your <form> tag?'); 99 | } 100 | 101 | // if auto-process was active, run validation on all file objects 102 | if ($this->defaults['auto_process']) 103 | { 104 | // process all data in the $_FILES array 105 | $this->processFiles(); 106 | 107 | // and validate it 108 | $this->validate(); 109 | } 110 | } 111 | 112 | /** 113 | * Runs save on all loaded file objects 114 | * 115 | * @param integer|string|array $selection 116 | */ 117 | public function save($selection = null) 118 | { 119 | // prepare the selection 120 | if (func_num_args()) 121 | { 122 | if (is_array($selection)) 123 | { 124 | $filter = array(); 125 | 126 | foreach ($this->container as $file) 127 | { 128 | $match = true; 129 | foreach($selection as $item => $value) 130 | { 131 | if ($value != $file->{$item}) 132 | { 133 | $match = false; 134 | break; 135 | } 136 | } 137 | 138 | $match and $filter[] = $file; 139 | } 140 | 141 | $selection = $filter; 142 | } 143 | else 144 | { 145 | $selection = array($this[$selection]); 146 | } 147 | } 148 | else 149 | { 150 | $selection = $this->container; 151 | } 152 | 153 | // loop through all selected files 154 | foreach ($selection as $file) 155 | { 156 | $file->save(); 157 | } 158 | } 159 | 160 | /** 161 | * Runs validation on all selected file objects 162 | * 163 | * @param integer|string|array $selection 164 | */ 165 | public function validate($selection = null) 166 | { 167 | // prepare the selection 168 | if (func_num_args()) 169 | { 170 | if (is_array($selection)) 171 | { 172 | $filter = array(); 173 | 174 | foreach ($this->container as $file) 175 | { 176 | $match = true; 177 | foreach($selection as $item => $value) 178 | { 179 | if ($value != $file->{$item}) 180 | { 181 | $match = false; 182 | break; 183 | } 184 | } 185 | 186 | $match and $filter[] = $file; 187 | } 188 | 189 | $selection = $filter; 190 | } 191 | else 192 | { 193 | $selection = array($this[$selection]); 194 | } 195 | } 196 | else 197 | { 198 | $selection = $this->container; 199 | } 200 | 201 | // loop through all selected files 202 | foreach ($selection as $file) 203 | { 204 | $file->validate(); 205 | } 206 | } 207 | 208 | /** 209 | * Returns a consolidated status of all uploaded files 210 | * 211 | * @return boolean 212 | */ 213 | public function isValid() 214 | { 215 | // loop through all files 216 | foreach ($this->container as $file) 217 | { 218 | // return false at the first non-valid file 219 | if ( ! $file->isValid()) 220 | { 221 | return false; 222 | } 223 | } 224 | 225 | // only return true if there are uploaded files, and they are all valid 226 | return empty($this->container) ? false : true; 227 | } 228 | 229 | /** 230 | * Returns the list of uploaded files 231 | * 232 | * @param integer|string $index 233 | * 234 | * @return File[] 235 | */ 236 | public function getAllFiles($index = null) 237 | { 238 | // return the selection 239 | if ($selection = (func_num_args() and ! is_null($index)) ? $this[$index] : $this->container) 240 | { 241 | // make sure selection is an array 242 | is_array($selection) or $selection = array($selection); 243 | } 244 | else 245 | { 246 | $selection = array(); 247 | } 248 | 249 | return $selection; 250 | } 251 | 252 | /** 253 | * Returns the list of uploaded files that valid 254 | * 255 | * @param integer|string $index 256 | * 257 | * @return File[] 258 | */ 259 | public function getValidFiles($index = null) 260 | { 261 | // prepare the selection 262 | if (is_numeric($index)) 263 | { 264 | $selection = $this->container; 265 | } 266 | else 267 | { 268 | $selection = (func_num_args() and ! is_null($index)) ? $this[$index] : $this->container; 269 | } 270 | 271 | // storage for the results 272 | $results = array(); 273 | 274 | if ($selection) 275 | { 276 | // make sure selection is an array 277 | is_array($selection) or $selection = array($selection); 278 | 279 | // loop through all files 280 | foreach ($selection as $file) 281 | { 282 | // store only files that are valid 283 | $file->isValid() and $results[] = $file; 284 | } 285 | } 286 | 287 | // return the results 288 | if (is_numeric($index)) 289 | { 290 | // a specific valid file was requested 291 | return isset($results[$index]) ? array($results[$index]) : array(); 292 | } 293 | else 294 | { 295 | return $results; 296 | } 297 | } 298 | 299 | /** 300 | * Returns the list of uploaded files that invalid 301 | * 302 | * @param integer|string $index 303 | * 304 | * @return File[] 305 | */ 306 | public function getInvalidFiles($index = null) 307 | { 308 | // prepare the selection 309 | if (is_numeric($index)) 310 | { 311 | $selection = $this->container; 312 | } 313 | else 314 | { 315 | $selection = (func_num_args() and ! is_null($index)) ? $this[$index] : $this->container; 316 | } 317 | 318 | // storage for the results 319 | $results = array(); 320 | 321 | if ($selection) 322 | { 323 | // make sure selection is an array 324 | is_array($selection) or $selection = array($selection); 325 | 326 | // loop through all files 327 | foreach ($selection as $file) 328 | { 329 | // store only files that are invalid 330 | $file->isValid() or $results[] = $file; 331 | } 332 | } 333 | 334 | // return the results 335 | if (is_numeric($index)) 336 | { 337 | // a specific valid file was requested 338 | return isset($results[$index]) ? array($results[$index]) : array(); 339 | } 340 | else 341 | { 342 | return $results; 343 | } 344 | } 345 | 346 | /** 347 | * Registers a callback for a given event 348 | * 349 | * @param string $event 350 | * @param mixed $callback 351 | * 352 | * @throws \InvalidArgumentException if not valid event or not callable second parameter 353 | */ 354 | public function register($event, $callback) 355 | { 356 | // check if this is a valid event type 357 | if ( ! isset($this->callbacks[$event])) 358 | { 359 | throw new \InvalidArgumentException($event.' is not a valid event'); 360 | } 361 | 362 | // check if the callback is acually callable 363 | if ( ! is_callable($callback)) 364 | { 365 | throw new \InvalidArgumentException('Callback passed is not callable'); 366 | } 367 | 368 | // store it 369 | $this->callbacks[$event][] = $callback; 370 | } 371 | 372 | /** 373 | * Sets the configuration for this file 374 | * 375 | * @param string|array $item 376 | * @param mixed $value 377 | */ 378 | public function setConfig($item, $value = null) 379 | { 380 | // unify the parameters 381 | is_array($item) or $item = array($item => $value); 382 | 383 | // update the configuration 384 | foreach ($item as $name => $value) 385 | { 386 | // is this a valid config item? then update the defaults 387 | array_key_exists($name, $this->defaults) and $this->defaults[$name] = $value; 388 | } 389 | 390 | // and push it to all file objects in the containers 391 | foreach ($this->container as $file) 392 | { 393 | $file->setConfig($item); 394 | } 395 | } 396 | 397 | /** 398 | * Processes the data in the $_FILES array, unify it, and create File objects for them 399 | * 400 | * @param mixed $selection 401 | */ 402 | public function processFiles($selection = null) 403 | { 404 | // input validation 405 | if ( ! is_array($selection) and ! is_null($selection)) 406 | { 407 | trigger_error('Uncaught TypeError: '.__METHOD__ .'(): Argument #1 ($selection) must be of type ?array, '.gettype($selection).' given, called in '.__FILE__.' on line '.__LINE__, E_USER_WARNING); 408 | } 409 | 410 | // normalize the multidimensional fields in the $_FILES array 411 | foreach($_FILES as $name => $file) 412 | { 413 | // was it defined as an array? 414 | if (is_array($file['name'])) 415 | { 416 | $data = $this->unifyFile($name, $file); 417 | 418 | foreach ($data as $entry) 419 | { 420 | if ($selection === null or in_array($entry['element'], $selection)) 421 | { 422 | $this->addFile($entry); 423 | } 424 | } 425 | } 426 | else 427 | { 428 | // normal form element, just create a File object for this uploaded file 429 | if ($selection === null or in_array($name, $selection)) 430 | { 431 | $this->addFile(array_merge(array('element' => $name, 'filename' => null), $file)); 432 | } 433 | } 434 | } 435 | } 436 | 437 | /** 438 | * Converts the silly different $_FILE structures to a flattened array 439 | * 440 | * @param string $name 441 | * @param array $file 442 | * 443 | * @return array 444 | */ 445 | protected function unifyFile($name, $file) 446 | { 447 | // storage for results 448 | $data = array(); 449 | 450 | // loop over the file array 451 | foreach ($file['name'] as $key => $value) 452 | { 453 | // we're not an the end of the element name nesting yet 454 | if (is_array($value)) 455 | { 456 | // recurse with the array data we have at this point 457 | $data = array_merge( 458 | $data, 459 | $this->unifyFile($name.'.'.$key, 460 | array( 461 | 'filename' => null, 462 | 'name' => $file['name'][$key], 463 | 'type' => $file['type'][$key], 464 | 'tmp_name' => $file['tmp_name'][$key], 465 | 'error' => $file['error'][$key], 466 | 'size' => $file['size'][$key], 467 | ) 468 | ) 469 | ); 470 | } 471 | else 472 | { 473 | $data[] = array( 474 | 'filename' => null, 475 | 'element' => $name.'.'.$key, 476 | 'name' => $file['name'][$key], 477 | 'type' => $file['type'][$key], 478 | 'tmp_name' => $file['tmp_name'][$key], 479 | 'error' => $file['error'][$key], 480 | 'size' => $file['size'][$key], 481 | ); 482 | } 483 | } 484 | 485 | return $data; 486 | } 487 | 488 | /** 489 | * Adds a new uploaded file structure to the container 490 | * 491 | * @param array $entry 492 | */ 493 | protected function addFile(array $entry) 494 | { 495 | // add the new file object to the container 496 | $this->container[] = new File($entry, $this->callbacks); 497 | 498 | // and load it with a default config 499 | end($this->container)->setConfig($this->defaults); 500 | } 501 | 502 | //------------------------------------------------------------------------------------------------------------------ 503 | 504 | /** 505 | * Countable methods 506 | */ 507 | #[\ReturnTypeWillChange] 508 | public function count()/*: int*/ 509 | { 510 | return count($this->container); 511 | } 512 | 513 | /** 514 | * ArrayAccess methods 515 | */ 516 | #[\ReturnTypeWillChange] 517 | public function offsetExists(/*mixed */$offset)/*: bool*/ 518 | { 519 | return isset($this->container[$offset]); 520 | } 521 | 522 | #[\ReturnTypeWillChange] 523 | public function offsetGet(/*mixed */$offset)/*: mixed*/ 524 | { 525 | // if the requested key is alphanumeric, do a search on element name 526 | if (is_string($offset)) 527 | { 528 | // if it's in form notation, convert it to dot notation 529 | $offset = str_replace(array('][', '[', ']'), array('.', '.', ''), $offset); 530 | 531 | // see if we can find this element or elements 532 | $found = array(); 533 | foreach($this->container as $key => $file) 534 | { 535 | if (strpos($file->element, $offset) === 0) 536 | { 537 | $found[] = $this->container[$key]; 538 | } 539 | } 540 | 541 | if ( ! empty($found)) 542 | { 543 | return $found; 544 | } 545 | } 546 | 547 | // else check on numeric offset 548 | elseif (isset($this->container[$offset])) 549 | { 550 | return $this->container[$offset]; 551 | } 552 | 553 | // not found 554 | return null; 555 | } 556 | 557 | #[\ReturnTypeWillChange] 558 | public function offsetSet(/*mixed */$offset, /*mixed */$value)/*: void*/ 559 | { 560 | throw new \OutOfBoundsException('An Upload Files instance is read-only, its contents can not be altered'); 561 | } 562 | 563 | #[\ReturnTypeWillChange] 564 | public function offsetUnset(/*mixed */$offset)/*: void*/ 565 | { 566 | throw new \OutOfBoundsException('An Upload Files instance is read-only, its contents can not be altered'); 567 | } 568 | 569 | /** 570 | * Iterator methods 571 | */ 572 | #[\ReturnTypeWillChange] 573 | public function rewind()/*: void*/ 574 | { 575 | $this->index = 0; 576 | } 577 | 578 | #[\ReturnTypeWillChange] 579 | public function current()/*: mixed*/ 580 | { 581 | return $this->container[$this->index]; 582 | } 583 | 584 | #[\ReturnTypeWillChange] 585 | public function key()/*: mixed*/ 586 | { 587 | return $this->index; 588 | } 589 | 590 | #[\ReturnTypeWillChange] 591 | public function next()/*: void*/ 592 | { 593 | ++$this->index; 594 | } 595 | 596 | #[\ReturnTypeWillChange] 597 | public function valid()/*: bool*/ 598 | { 599 | return isset($this->container[$this->index]); 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | scenario->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); 43 | } 44 | 45 | 46 | /** 47 | * [!] Method is generated. Documentation taken from corresponding module. 48 | * 49 | * Checks that two variables are not equal 50 | * 51 | * @param $expected 52 | * @param $actual 53 | * @param string $message 54 | * @see \Codeception\Module\Asserts::assertNotEquals() 55 | */ 56 | public function assertNotEquals($expected, $actual, $message = null) { 57 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); 58 | } 59 | 60 | 61 | /** 62 | * [!] Method is generated. Documentation taken from corresponding module. 63 | * 64 | * Checks that expected is greater than actual 65 | * 66 | * @param $expected 67 | * @param $actual 68 | * @param string $message 69 | * @see \Codeception\Module\Asserts::assertGreaterThan() 70 | */ 71 | public function assertGreaterThan($expected, $actual, $message = null) { 72 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThan', func_get_args())); 73 | } 74 | 75 | 76 | /** 77 | * [!] Method is generated. Documentation taken from corresponding module. 78 | * 79 | * @deprecated 80 | * @see \Codeception\Module\Asserts::assertGreaterThen() 81 | */ 82 | public function assertGreaterThen($expected, $actual, $message = null) { 83 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThen', func_get_args())); 84 | } 85 | 86 | 87 | /** 88 | * [!] Method is generated. Documentation taken from corresponding module. 89 | * 90 | * Checks that expected is greater or equal than actual 91 | * 92 | * @param $expected 93 | * @param $actual 94 | * @param string $message 95 | * @see \Codeception\Module\Asserts::assertGreaterThanOrEqual() 96 | */ 97 | public function assertGreaterThanOrEqual($expected, $actual, $message = null) { 98 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', func_get_args())); 99 | } 100 | 101 | 102 | /** 103 | * [!] Method is generated. Documentation taken from corresponding module. 104 | * 105 | * @deprecated 106 | * @see \Codeception\Module\Asserts::assertGreaterThenOrEqual() 107 | */ 108 | public function assertGreaterThenOrEqual($expected, $actual, $message = null) { 109 | return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThenOrEqual', func_get_args())); 110 | } 111 | 112 | 113 | /** 114 | * [!] Method is generated. Documentation taken from corresponding module. 115 | * 116 | * Checks that expected is less than actual 117 | * 118 | * @param $expected 119 | * @param $actual 120 | * @param string $message 121 | * @see \Codeception\Module\Asserts::assertLessThan() 122 | */ 123 | public function assertLessThan($expected, $actual, $message = null) { 124 | return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThan', func_get_args())); 125 | } 126 | 127 | 128 | /** 129 | * [!] Method is generated. Documentation taken from corresponding module. 130 | * 131 | * Checks that expected is less or equal than actual 132 | * 133 | * @param $expected 134 | * @param $actual 135 | * @param string $message 136 | * @see \Codeception\Module\Asserts::assertLessThanOrEqual() 137 | */ 138 | public function assertLessThanOrEqual($expected, $actual, $message = null) { 139 | return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', func_get_args())); 140 | } 141 | 142 | 143 | /** 144 | * [!] Method is generated. Documentation taken from corresponding module. 145 | * 146 | * Checks that haystack contains needle 147 | * 148 | * @param $needle 149 | * @param $haystack 150 | * @param string $message 151 | * @see \Codeception\Module\Asserts::assertContains() 152 | */ 153 | public function assertContains($needle, $haystack, $message = null) { 154 | return $this->scenario->runStep(new \Codeception\Step\Action('assertContains', func_get_args())); 155 | } 156 | 157 | 158 | /** 159 | * [!] Method is generated. Documentation taken from corresponding module. 160 | * 161 | * Checks that haystack doesn't contain needle. 162 | * 163 | * @param $needle 164 | * @param $haystack 165 | * @param string $message 166 | * @see \Codeception\Module\Asserts::assertNotContains() 167 | */ 168 | public function assertNotContains($needle, $haystack, $message = null) { 169 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args())); 170 | } 171 | 172 | 173 | /** 174 | * [!] Method is generated. Documentation taken from corresponding module. 175 | * 176 | * Checks that variable is empty. 177 | * 178 | * @param $actual 179 | * @param string $message 180 | * @see \Codeception\Module\Asserts::assertEmpty() 181 | */ 182 | public function assertEmpty($actual, $message = null) { 183 | return $this->scenario->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args())); 184 | } 185 | 186 | 187 | /** 188 | * [!] Method is generated. Documentation taken from corresponding module. 189 | * 190 | * Checks that variable is not empty. 191 | * 192 | * @param $actual 193 | * @param string $message 194 | * @see \Codeception\Module\Asserts::assertNotEmpty() 195 | */ 196 | public function assertNotEmpty($actual, $message = null) { 197 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args())); 198 | } 199 | 200 | 201 | /** 202 | * [!] Method is generated. Documentation taken from corresponding module. 203 | * 204 | * Checks that variable is NULL 205 | * 206 | * @param $actual 207 | * @param string $message 208 | * @see \Codeception\Module\Asserts::assertNull() 209 | */ 210 | public function assertNull($actual, $message = null) { 211 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNull', func_get_args())); 212 | } 213 | 214 | 215 | /** 216 | * [!] Method is generated. Documentation taken from corresponding module. 217 | * 218 | * Checks that variable is not NULL 219 | * 220 | * @param $actual 221 | * @param string $message 222 | * @see \Codeception\Module\Asserts::assertNotNull() 223 | */ 224 | public function assertNotNull($actual, $message = null) { 225 | return $this->scenario->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args())); 226 | } 227 | 228 | 229 | /** 230 | * [!] Method is generated. Documentation taken from corresponding module. 231 | * 232 | * Checks that condition is positive. 233 | * 234 | * @param $condition 235 | * @param string $message 236 | * @see \Codeception\Module\Asserts::assertTrue() 237 | */ 238 | public function assertTrue($condition, $message = null) { 239 | return $this->scenario->runStep(new \Codeception\Step\Action('assertTrue', func_get_args())); 240 | } 241 | 242 | 243 | /** 244 | * [!] Method is generated. Documentation taken from corresponding module. 245 | * 246 | * Checks that condition is negative. 247 | * 248 | * @param $condition 249 | * @param string $message 250 | * @see \Codeception\Module\Asserts::assertFalse() 251 | */ 252 | public function assertFalse($condition, $message = null) { 253 | return $this->scenario->runStep(new \Codeception\Step\Action('assertFalse', func_get_args())); 254 | } 255 | 256 | 257 | /** 258 | * [!] Method is generated. Documentation taken from corresponding module. 259 | * 260 | * Fails the test with message. 261 | * 262 | * @param $message 263 | * @see \Codeception\Module\Asserts::fail() 264 | */ 265 | public function fail($message) { 266 | return $this->scenario->runStep(new \Codeception\Step\Action('fail', func_get_args())); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /tests/unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 |