diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 021d7417a..164bc3665 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,7 +11,7 @@ Thank you for your interest in contributing to the OctoberCMS project. We apprec ## Reporting a Security Vulnerability -Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities. +Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities. Please do not report security vulnerabilities on GitHub. ## Reporting an issue with OctoberCMS @@ -21,6 +21,8 @@ We work hard to process bugs that are reported, to assist with this please ensur - **Summary**: Make sure your summary reflects what the problem is and where it is. Provide as much detail as possible, the more information we have to work with the more likely it is that your problem can be solved. +- **Installed build and plugins**: Please provide the build number of October CMS that is exhibiting the fault, and the version numbers of any installed and active plugins on your installation. You may retrieve this information by logging in to the Backend and navigating to *Settings* and then *Updates & Plugins*. + - **Reproduce steps**: Clearly mention the steps to reproduce the bug. - **Expected behavior**: Describe how OctoberCMS should behave on above mentioned steps. @@ -61,7 +63,7 @@ We do our best to attend to all reported issues. If you have an important issue >**NOTE:** Please don't use GitHub issues for suggesting a new feature. If you have a feature idea, the best place to suggest it is the [OctoberCMS website forum](https://octobercms.com/forum/chan/feature-requests). -Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [IRC](https://octobercms.com/chat) or [Slack](https://octobercms.slack.com). +Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [Discord](https://discord.gg/gEKgwSZ). #### GitHub feature requests diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index 65a0f4646..af05451d1 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,25 +6,18 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index 3de3f6a97..ff209d0b5 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,23 +9,16 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks - run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml deleted file mode 100644 index 9d0068a0a..000000000 --- a/.github/workflows/frontend-tests.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - - develop - pull_request: - -jobs: - frontendTests: - runs-on: ubuntu-latest - name: JavaScript - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install Node - uses: actions/setup-node@v1 - with: - node-version: 8 - - name: Install Node dependencies - run: npm install - - name: Run tests - run: npm run test diff --git a/.github/workflows/matchers/phpcs-matcher.json b/.github/workflows/matchers/phpcs-matcher.json new file mode 100644 index 000000000..5c80b26d2 --- /dev/null +++ b/.github/workflows/matchers/phpcs-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] + } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 072b795da..a27246d97 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,37 +8,56 @@ on: pull_request: jobs: + frontendTests: + runs-on: ubuntu-latest + name: JavaScript + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install Node + uses: actions/setup-node@v1 + with: + node-version: 8 + - name: Install Node dependencies + run: npm install + - name: Run tests + run: npm run test phpUnitTests: runs-on: ubuntu-latest strategy: max-parallel: 6 matrix: - phpVersions: ['7.1', '7.2', '7.3', '7.4'] + phpVersions: ['7.2', '7.3', '7.4'] fail-fast: false - name: PHP ${{ matrix.phpVersions }} + name: Unit Tests / PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@master + uses: shivammathur/setup-php@v1 with: php-version: ${{ matrix.phpVersions }} - extension-csv: mbstring, intl, gd, xml, sqlite + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - - name: Reset October modules and library + - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules run: | git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload - - name: Run post-update Composer scripts - run: | - php artisan october:util set build - php artisan package:discover + composer dumpautoload - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . - ./vendor/bin/phpunit + ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php diff --git a/.gitignore b/.gitignore index 4cd08cf9a..63a1b61d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,32 @@ -/bootstrap/compiled.php +# Composer ignores /vendor composer.phar -.DS_Store -.idea +composer.lock + +# Framework ignores .env .env.*.php .env.php +selenium.php +/bootstrap/compiled.php +.phpunit.result.cache + +# Hosting ignores php_errors.log nginx-error.log nginx-access.log nginx-ssl.access.log nginx-ssl.error.log -php-errors.log sftp-config.json .ftpconfig -selenium.php -composer.lock -package-lock.json -/node_modules + +# Editor ignores +nbproject +.idea +.vscode _ide_helper.php -# for netbeans -nbproject +# Other ignores +.DS_Store +package-lock.json +/node_modules diff --git a/artisan b/artisan index 961e94d0b..df630d0d6 100644 --- a/artisan +++ b/artisan @@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); \ No newline at end of file +exit($status); diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index b980622d7..6533c54ca 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -35,20 +35,3 @@ require $helperPath; */ require __DIR__.'/../vendor/autoload.php'; - -/* -|-------------------------------------------------------------------------- -| Include The Compiled Class File -|-------------------------------------------------------------------------- -| -| To dramatically increase your application's performance, you may use a -| compiled class file which contains all of the classes commonly used -| by a request. The Artisan "optimize" is used to create this file. -| -*/ - -$compiledPath = __DIR__.'/../storage/framework/compiled.php'; - -if (file_exists($compiledPath)) { - require $compiledPath; -} diff --git a/composer.json b/composer.json index eb4f8fe37..9542fc5a1 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "october/october", - "description": "OctoberCMS", + "description": "October CMS", "homepage": "https://octobercms.com", "type": "project", "keywords": ["october", "cms", "octobercms", "laravel"], @@ -24,37 +24,34 @@ } ], "support": { + "paid": "https://octobercms.com/premium-support", "issues": "https://github.com/octobercms/october/issues", "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", - "irc": "irc://irc.freenode.net/october", "source": "https://github.com/octobercms/october" }, "require": { - "php": ">=7.0.8", - "ext-mbstring": "*", - "ext-openssl": "*", - "october/rain": "~1.0", - "october/system": "~1.0", - "october/backend": "~1.0", - "october/cms": "~1.0", - "laravel/framework": "~5.5.40", + "php": ">=7.2", + "october/rain": "dev-develop as 1.0", + "october/system": "dev-develop", + "october/backend": "dev-develop", + "october/cms": "dev-develop", + "laravel/framework": "~6.0", "wikimedia/composer-merge-plugin": "1.4.1" }, "require-dev": { - "fzaninotto/faker": "~1.7", - "phpunit/phpunit": "~6.5", - "phpunit/phpunit-selenium": "~1.2", - "meyfa/phpunit-assert-gd": "1.1.0", + "phpunit/phpunit": "^8.0|^9.0", + "fzaninotto/faker": "~1.9", "squizlabs/php_codesniffer": "3.*", - "php-parallel-lint/php-parallel-lint": "^1.0" + "php-parallel-lint/php-parallel-lint": "^1.0", + "meyfa/phpunit-assert-gd": "^2.0.0", + "dms/phpunit-arraysubset-asserts": "^0.1.0" }, "autoload-dev": { "classmap": [ "tests/concerns/InteractsWithAuthentication.php", "tests/fixtures/backend/models/UserFixture.php", "tests/TestCase.php", - "tests/UiTestCase.php", "tests/PluginTestCase.php" ] }, @@ -66,12 +63,21 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" + ], + "test": [ + "phpunit --stop-on-failure" + ], + "lint": [ + "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." + ], + "sniff": [ + "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { "preferred-install": "dist", "platform": { - "php": "7.0.8" + "php": "7.2" } }, "minimum-stability": "dev", diff --git a/config/app.php b/config/app.php index 23aad8473..56e4959ab 100644 --- a/config/app.php +++ b/config/app.php @@ -111,21 +111,6 @@ return [ 'cipher' => 'AES-256-CBC', - /* - |-------------------------------------------------------------------------- - | Logging Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure the log settings for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Settings: "single", "daily", "syslog", "errorlog" - | - */ - - 'log' => 'single', - /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -144,6 +129,26 @@ return [ 'System\ServiceProvider', ]), + /* + |-------------------------------------------------------------------------- + | Load automatically discovered packages + |-------------------------------------------------------------------------- + | + | By default, October CMS disables the loading of discovered packages + | through Laravel's package discovery service, in order to allow packages + | used by plugins to be disabled if the plugin itself is disabled. + | + | Set this to `true` to enable automatic loading of these packages. This + | will result in packages being loaded, even if the plugin using them is + | disabled. This is NOT RECOMMENDED. + | + | Please note that packages defined in `app.providers` will still be loaded + | even if discovery is disabled. + | + */ + + 'loadDiscoveredPackages' => false, + /* |-------------------------------------------------------------------------- | Class Aliases diff --git a/config/database.php b/config/database.php index 70f1420c6..a06bc7586 100644 --- a/config/database.php +++ b/config/database.php @@ -116,6 +116,7 @@ return [ 'redis' => [ + 'client' => 'predis', 'cluster' => false, 'default' => [ diff --git a/config/develop.php b/config/develop.php index cd4aee7d7..2c1cbc642 100644 --- a/config/develop.php +++ b/config/develop.php @@ -20,5 +20,27 @@ return [ */ 'decompileBackendAssets' => false, + + /* + |-------------------------------------------------------------------------- + | Allow deep-level symlinks + |-------------------------------------------------------------------------- + | + | October CMS, by default, will allow symlinks within the first level of + | subdirectories. When this feature is enabled, the system will allow + | symlinks to be used at any directory level. This can be useful for + | symlinking individual plugins or themes. + | + | Please note that this has a negative effect on performance. This feature + | abides by "cms.restrictBaseDir" - if enabled, symlinks cannot point to + | resources outside of the root folder. + | + | true - allow symlinks at any level + | + | false - only allow symlinks at the first level of subdirectories (default) + | + */ + + 'allowDeepSymlinks' => false, ]; diff --git a/config/filesystems.php b/config/filesystems.php index 4d843013c..36d9a0dfa 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -11,7 +11,7 @@ return [ | by the framework. A "local" driver, as well as a variety of cloud | based drivers are available for your choosing. Just store away! | - | Supported: "local", "s3", "rackspace" + | Supported: "local", "ftp", "sftp", "s3", "rackspace" | */ diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 000000000..842577087 --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 000000000..900d48123 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,91 @@ + env('LOG_CHANNEL', 'single'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['daily'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/system.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/system.log'), + 'level' => 'debug', + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'October CMS Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => 'debug', + 'handler' => \Monolog\Handler\SyslogUdpHandler::class, + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => \Monolog\Handler\StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php index 7c2332d2f..4c471d568 100644 --- a/config/mail.php +++ b/config/mail.php @@ -12,7 +12,7 @@ return [ | your application here. By default, Laravel is setup for SMTP mail. | | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | "postmark", "sparkpost", "log", "array" | */ diff --git a/config/services.php b/config/services.php index c2d453065..d53643b03 100644 --- a/config/services.php +++ b/config/services.php @@ -24,6 +24,10 @@ return [ 'secret' => '', ], + 'postmark' => [ + 'token' => '', + ], + 'ses' => [ 'key' => '', 'secret' => '', diff --git a/index.php b/index.php index ba43df3ec..9c4d23a06 100644 --- a/index.php +++ b/index.php @@ -37,7 +37,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make('Illuminate\Contracts\Http\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index b0a5026f8..4c3825dd0 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -80,6 +80,7 @@ class ServiceProvider extends ModuleServiceProvider $combiner->registerBundle('~/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less'); $combiner->registerBundle('~/modules/backend/formwidgets/permissioneditor/assets/less/permissioneditor.less'); $combiner->registerBundle('~/modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less'); + $combiner->registerBundle('~/modules/backend/formwidgets/sensitive/assets/less/sensitive.less'); /* * Rich Editor is protected by DRM @@ -199,6 +200,7 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist'); $manager->registerFormWidget('Backend\FormWidgets\MediaFinder', 'mediafinder'); $manager->registerFormWidget('Backend\FormWidgets\NestedForm', 'nestedform'); + $manager->registerFormWidget('Backend\FormWidgets\Sensitive', 'sensitive'); }); } diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index c146050b6..b0278ccdc 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -11,7 +11,7 @@ use Backend\Behaviors\ImportExportController\TranscodeFilter; use Illuminate\Database\Eloquent\MassAssignmentException; use League\Csv\Reader as CsvReader; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; use Exception; @@ -624,9 +624,7 @@ class ImportExportController extends ControllerBehavior $csv->setDelimiter($options['delimiter']); $csv->setEnclosure($options['enclosure']); $csv->setEscape($options['escape']); - - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers @@ -662,9 +660,6 @@ class ImportExportController extends ControllerBehavior $record[] = $value; } - // Temporary until upgrading to league/csv >= 9.1.0 - $record = $formatter($record); - $csv->insertOne($record); } diff --git a/modules/backend/classes/FormField.php b/modules/backend/classes/FormField.php index f2b6efc68..d9782a267 100644 --- a/modules/backend/classes/FormField.php +++ b/modules/backend/classes/FormField.php @@ -124,7 +124,7 @@ class FormField /** * @var string Specifies a comment to accompany the field */ - public $comment; + public $comment = ''; /** * @var string Specifies the comment position. @@ -139,7 +139,7 @@ class FormField /** * @var string Specifies a message to display when there is no value supplied (placeholder). */ - public $placeholder; + public $placeholder = ''; /** * @var array Contains a list of attributes specified in the field configuration. diff --git a/modules/backend/composer.json b/modules/backend/composer.json index 15cfd2a3f..5d4231af2 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php index 7be5f9885..78c561441 100644 --- a/modules/backend/database/seeds/DatabaseSeeder.php +++ b/modules/backend/database/seeds/DatabaseSeeder.php @@ -12,8 +12,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + Eloquent::unguarded(function () { + $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + }); } } diff --git a/modules/backend/formwidgets/Sensitive.php b/modules/backend/formwidgets/Sensitive.php new file mode 100644 index 000000000..a28a8d60b --- /dev/null +++ b/modules/backend/formwidgets/Sensitive.php @@ -0,0 +1,117 @@ +fillFromConfig([ + 'readOnly', + 'disabled', + 'allowCopy', + 'hiddenPlaceholder', + 'hideOnTabChange', + ]); + + if ($this->formField->disabled || $this->formField->readOnly) { + $this->previewMode = true; + } + } + + /** + * @inheritDoc + */ + public function render() + { + $this->prepareVars(); + + return $this->makePartial('sensitive'); + } + + /** + * Prepares the view data for the widget partial. + */ + public function prepareVars() + { + $this->vars['readOnly'] = $this->readOnly; + $this->vars['disabled'] = $this->disabled; + $this->vars['hasValue'] = !empty($this->getLoadValue()); + $this->vars['allowCopy'] = $this->allowCopy; + $this->vars['hiddenPlaceholder'] = $this->hiddenPlaceholder; + $this->vars['hideOnTabChange'] = $this->hideOnTabChange; + } + + /** + * Reveals the value of a hidden, unmodified sensitive field. + * + * @return array + */ + public function onShowValue() + { + return [ + 'value' => $this->getLoadValue() + ]; + } + + /** + * @inheritDoc + */ + public function getSaveValue($value) + { + if ($value === $this->hiddenPlaceholder) { + $value = $this->getLoadValue(); + } + + return $value; + } + + /** + * @inheritDoc + */ + protected function loadAssets() + { + $this->addCss('css/sensitive.css', 'core'); + $this->addJs('js/sensitive.js', 'core'); + } +} diff --git a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css new file mode 100644 index 000000000..c8ac9378a --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css @@ -0,0 +1,2 @@ +div[data-control="sensitive"] a[data-toggle], +div[data-control="sensitive"] a[data-copy] {box-shadow:none;border:1px solid #d1d6d9;border-left:0} \ No newline at end of file diff --git a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js new file mode 100644 index 000000000..69251304c --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js @@ -0,0 +1,192 @@ +/* + * Sensitive field widget plugin. + * + * Data attributes: + * - data-control="sensitive" - enables the plugin on an element + * + * JavaScript API: + * $('div#someElement').sensitive({...}) + */ ++function ($) { "use strict"; + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var Sensitive = function(element, options) { + this.$el = $(element) + this.options = options + this.clean = Boolean(this.$el.data('clean')) + this.hidden = true + + this.$input = this.$el.find('[data-input]').first() + this.$toggle = this.$el.find('[data-toggle]').first() + this.$icon = this.$el.find('[data-icon]').first() + this.$loader = this.$el.find('[data-loader]').first() + this.$copy = this.$el.find('[data-copy]').first() + + $.oc.foundation.controlUtils.markDisposable(element) + Base.call(this) + this.init() + } + + Sensitive.DEFAULTS = { + readOnly: false, + disabled: false, + eventHandler: null, + hideOnTabChange: false, + } + + Sensitive.prototype = Object.create(BaseProto) + Sensitive.prototype.constructor = Sensitive + + Sensitive.prototype.init = function() { + this.$input.on('keydown', this.proxy(this.onInput)) + this.$toggle.on('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + // Watch for tab change or minimise + document.addEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.on('click', this.proxy(this.onCopy)) + } + } + + Sensitive.prototype.dispose = function () { + this.$input.off('keydown', this.proxy(this.onInput)) + this.$toggle.off('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + document.removeEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.off('click', this.proxy(this.onCopy)) + } + + this.$input = this.$toggle = this.$icon = this.$loader = null + this.$el = null + + BaseProto.dispose.call(this) + } + + Sensitive.prototype.onInput = function() { + if (this.clean) { + this.clean = false + this.$input.val('') + } + + return true + } + + Sensitive.prototype.onToggle = function() { + if (this.$input.val() !== '' && this.clean) { + this.reveal() + } else { + this.toggleVisibility() + } + + return true + } + + Sensitive.prototype.onTabChange = function() { + if (document.hidden && !this.hidden) { + this.toggleVisibility() + } + } + + Sensitive.prototype.onCopy = function() { + var that = this, + deferred = $.Deferred(), + isHidden = this.hidden + + deferred.then(function () { + if (that.hidden) { + that.toggleVisibility() + } + + that.$input.focus() + that.$input.select() + + try { + document.execCommand('copy') + } catch (err) { + } + + that.$input.blur() + if (isHidden) { + that.toggleVisibility() + } + }) + + if (this.$input.val() !== '' && this.clean) { + this.reveal(deferred) + } else { + deferred.resolve() + } + } + + Sensitive.prototype.toggleVisibility = function() { + if (this.hidden) { + this.$input.attr('type', 'text') + } else { + this.$input.attr('type', 'password') + } + + this.$icon.toggleClass('icon-eye icon-eye-slash') + + this.hidden = !this.hidden + } + + Sensitive.prototype.reveal = function(deferred) { + var that = this + this.$icon.css({ + visibility: 'hidden' + }) + this.$loader.removeClass('hide') + + this.$input.request(this.options.eventHandler, { + success: function (data) { + that.$input.val(data.value) + that.clean = false + + that.$icon.css({ + visibility: 'visible' + }) + that.$loader.addClass('hide') + + that.toggleVisibility() + + if (deferred) { + deferred.resolve() + } + } + }) + } + + var old = $.fn.sensitive + + $.fn.sensitive = function (option) { + var args = Array.prototype.slice.call(arguments, 1), result + this.each(function () { + var $this = $(this) + var data = $this.data('oc.sensitive') + var options = $.extend({}, Sensitive.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.sensitive', (data = new Sensitive(this, options))) + if (typeof option == 'string') result = data[option].apply(data, args) + if (typeof result != 'undefined') return false + }) + + return result ? result : this + } + + $.fn.sensitive.noConflict = function () { + $.fn.sensitive = old + return this + } + + $(document).render(function () { + $('[data-control="sensitive"]').sensitive() + }); + +}(window.jQuery); diff --git a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less new file mode 100644 index 000000000..5717658f2 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less @@ -0,0 +1,10 @@ +@import "../../../../assets/less/core/boot.less"; + +div[data-control="sensitive"] { + a[data-toggle], + a[data-copy] { + box-shadow: none; + border: 1px solid @input-group-addon-border-color; + border-left: 0; + } +} diff --git a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm new file mode 100644 index 000000000..b913070d7 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm @@ -0,0 +1,41 @@ +
data-hide-on-tab-change="true" +> +
+
+ previewMode): ?>disabled="disabled" + autocomplete="off" + data-input + /> + + + + + + + + +
+
+ +
+
+
diff --git a/modules/backend/models/ExportModel.php b/modules/backend/models/ExportModel.php index 1a03a813b..e6a767e80 100644 --- a/modules/backend/models/ExportModel.php +++ b/modules/backend/models/ExportModel.php @@ -5,7 +5,7 @@ use Lang; use Model; use Response; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; @@ -112,8 +112,7 @@ abstract class ExportModel extends Model $csv->setEscape($options['escape']); } - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers @@ -128,10 +127,6 @@ abstract class ExportModel extends Model */ foreach ($results as $result) { $data = $this->matchDataToColumns($result, $columns); - - // Temporary until upgrading to league/csv >= 9.1.0 - $data = $formatter($data); - $csv->insertOne($data); } diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index 12f937a16..09c1bf426 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -5,6 +5,7 @@ use Str; use Lang; use Model; use League\Csv\Reader as CsvReader; +use League\Csv\Statement as CsvStatement; /** * Model used for importing data @@ -108,11 +109,6 @@ abstract class ImportModel extends Model */ $reader = CsvReader::createFromPath($filePath, 'r'); - // Filter out empty rows - $reader->addFilter(function (array $row) { - return count($row) > 1 || reset($row) !== null; - }); - if ($options['delimiter'] !== null) { $reader->setDelimiter($options['delimiter']); } @@ -125,15 +121,11 @@ abstract class ImportModel extends Model $reader->setEscape($options['escape']); } - if ($options['firstRowTitles']) { - $reader->setOffset(1); - } - if ( $options['encoding'] !== null && - $reader->isActiveStreamFilter() + $reader->supportsStreamFilter() ) { - $reader->appendStreamFilter(sprintf( + $reader->addStreamFilter(sprintf( '%s%s:%s', TranscodeFilter::FILTER_NAME, strtolower($options['encoding']), @@ -141,8 +133,19 @@ abstract class ImportModel extends Model )); } + // Create reader statement + $stmt = (new CsvStatement) + ->where(function (array $row) { + // Filter out empty rows + return count($row) > 1 || reset($row) !== null; + }); + + if ($options['firstRowTitles']) { + $stmt = $stmt->offset(1); + } + $result = []; - $contents = $reader->fetch(); + $contents = $stmt->process($reader); foreach ($contents as $row) { $result[] = $this->processImportRow($row, $matches); } diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 650a1378e..46259792b 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -27,8 +27,8 @@ class User extends UserBase public $rules = [ 'email' => 'required|between:6,255|email|unique:backend_users', 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|between:4,255|confirmed', - 'password_confirmation' => 'required_with:password|between:4,255' + 'password' => 'required:create|min:4|confirmed', + 'password_confirmation' => 'required_with:password|min:4' ]; /** diff --git a/modules/backend/routes.php b/modules/backend/routes.php index 81904802f..0268f7efc 100644 --- a/modules/backend/routes.php +++ b/modules/backend/routes.php @@ -25,7 +25,7 @@ App::before(function ($request) { 'middleware' => ['web'], 'prefix' => Config::get('cms.backendUri', 'backend') ], function () { - Route::any('{slug}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); + Route::any('{slug?}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); }) ; diff --git a/modules/cms/classes/Asset.php b/modules/cms/classes/Asset.php index 12b79ea35..0bfc2eba8 100644 --- a/modules/cms/classes/Asset.php +++ b/modules/cms/classes/Asset.php @@ -287,25 +287,14 @@ class Asset extends Extendable $directory = $this->theme->getPath() . '/' . $this->dirName . '/'; $filePath = $directory . $fileName; - $path = realpath($filePath); - - /** - * If the path doesn't exist yet, then create it temporarily - * in order to run realpath() resolution on it to verify the - * final destination and then remove the temporary file. - */ - if (!$path) { - touch($filePath); - $path = realpath($filePath); - unlink($filePath); - } + $resolvedPath = resolve_path($filePath); // Limit paths to those under the theme's assets directory - if (!starts_with($path, $directory)) { + if (!starts_with($resolvedPath, $directory)) { return false; } - return $path; + return $resolvedPath; } /** diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index 5705515bc..fca40ff67 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -316,7 +316,8 @@ class CmsCompoundObject extends CmsObject self::$objectComponentPropertyMap = $objectComponentMap; - Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10)); + Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt); if (array_key_exists($componentName, $objectComponentMap[$objectCode])) { return $objectComponentMap[$objectCode][$componentName]; diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index 798a065a0..714650150 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -227,7 +227,16 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->fileName; } - return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; + $directory = $this->theme->getPath() . '/' . $this->getObjectTypeDirName() . '/'; + $filePath = $directory . $fileName; + $resolvedPath = resolve_path($filePath); + + // Limit paths to those under the corresponding theme directory + if (!starts_with($resolvedPath, $directory)) { + return false; + } + + return $resolvedPath; } /** diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index ffc7afc73..2105789ac 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -1,5 +1,6 @@ filter(function ($object) use ($property, $value, $strict) { + if (empty($value) || !is_string($value)) { + throw new ApplicationException('You must provide a string value to compare with when executing a "where" ' + . 'query for CMS object collections.'); + } + if (!isset($strict) || !is_bool($strict)) { + $strict = true; + } + + return $this->filter(function ($object) use ($property, $value, $strict) { if (!array_key_exists($property, $object->settings)) { return false; } diff --git a/modules/cms/classes/CodeParser.php b/modules/cms/classes/CodeParser.php index dd6687637..b747a20e5 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -224,7 +224,8 @@ class CodeParser $cached = $this->getCachedInfo() ?: []; $cached[$this->filePath] = $cacheItem; - Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440); + $expiresAt = now()->addMinutes(1440); + Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt); self::$cache[$this->filePath] = $result; } diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 0c033fd81..4f75656f1 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -127,10 +127,11 @@ class Router : $fileName; $key = $this->getUrlListCacheKey(); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); Cache::put( $key, base64_encode(serialize($urlList)), - Config::get('cms.urlCacheTtl', 1) + $expiresAt ); } } @@ -251,7 +252,8 @@ class Router $this->urlMap = $map; if ($cacheable) { - Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1)); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); + Cache::put($key, base64_encode(serialize($map)), $expiresAt); } return false; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index a5306c897..6ce06a1c5 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -158,7 +158,8 @@ class Theme if ($checkDatabase && App::hasDatabase()) { try { try { - $dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () { + $expiresAt = now()->addMinutes(1440); + $dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () { return Parameter::applyKey(self::ACTIVE_KEY)->value('value'); }); } diff --git a/modules/cms/composer.json b/modules/cms/composer.json index 49e2d944b..9e4cfa70e 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/cms/routes.php b/modules/cms/routes.php index 76f35c4b3..1aaf5ec5d 100644 --- a/modules/cms/routes.php +++ b/modules/cms/routes.php @@ -22,7 +22,7 @@ App::before(function ($request) { * The CMS module intercepts all URLs that were not * handled by the back-end modules. */ - Route::any('{slug}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); + Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); /** * @event cms.route diff --git a/modules/cms/traits/UrlMaker.php b/modules/cms/traits/UrlMaker.php index 210922cfa..ebe0c050f 100644 --- a/modules/cms/traits/UrlMaker.php +++ b/modules/cms/traits/UrlMaker.php @@ -190,7 +190,8 @@ trait UrlMaker 'mtime' => @File::lastModified($filePath) ]; - Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440)); + Cache::put($key, serialize($cached), $expiresAt); return static::$urlPageName = $baseFileName; } diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php index 242eb23a5..f974bb8e2 100644 --- a/modules/cms/twig/DebugExtension.php +++ b/modules/cms/twig/DebugExtension.php @@ -8,7 +8,7 @@ use Cms\Classes\Controller; use Cms\Classes\ComponentBase; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; -use Illuminate\Support\Debug\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Cloner\VarCloner; use October\Rain\Database\Model; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 9fc858f39..eb5b6c613 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -94,6 +94,7 @@ class ServiceProvider extends ModuleServiceProvider } } + Paginator::useBootstrapThree(); Paginator::defaultSimpleView('system::pagination.simple-default'); /* diff --git a/modules/system/aliases.php b/modules/system/aliases.php index bcb2c78b2..d6364cd13 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,7 +16,6 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -30,7 +29,6 @@ return [ 'Storage' => Illuminate\Support\Facades\Storage::class, 'Url' => Illuminate\Support\Facades\URL::class, // Preferred 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, /* @@ -42,6 +40,7 @@ return [ 'Config' => October\Rain\Support\Facades\Config::class, 'Seeder' => October\Rain\Database\Updates\Seeder::class, 'Flash' => October\Rain\Support\Facades\Flash::class, + 'Input' => October\Rain\Support\Facades\Input::class, 'Form' => October\Rain\Support\Facades\Form::class, 'Html' => October\Rain\Support\Facades\Html::class, 'Http' => October\Rain\Support\Facades\Http::class, @@ -52,6 +51,7 @@ return [ 'Twig' => October\Rain\Support\Facades\Twig::class, 'DbDongle' => October\Rain\Support\Facades\DbDongle::class, 'Schema' => October\Rain\Support\Facades\Schema::class, + 'Validator' => October\Rain\Support\Facades\Validator::class, 'Cms' => Cms\Facades\Cms::class, 'Backend' => Backend\Facades\Backend::class, 'BackendMenu' => Backend\Facades\BackendMenu::class, @@ -60,4 +60,12 @@ return [ 'SystemException' => October\Rain\Exception\SystemException::class, 'ApplicationException' => October\Rain\Exception\ApplicationException::class, 'ValidationException' => October\Rain\Exception\ValidationException::class, + + /* + * Fallback aliases + */ + // Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility. + 'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class, + // Illuminate's HtmlDumper was "dumped" in Laravel 6 - we'll route this to Symfony's HtmlDumper as Laravel have done. + 'Illuminate\Support\Debug\HtmlDumper' => Symfony\Component\VarDumper\Dumper\HtmlDumper::class, ]; diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php index 50a03c292..17ec7c2bd 100644 --- a/modules/system/classes/CombineAssets.php +++ b/modules/system/classes/CombineAssets.php @@ -10,11 +10,11 @@ use Route; use Config; use Request; use Response; -use Assetic\Asset\FileAsset; -use Assetic\Asset\AssetCache; -use Assetic\Asset\AssetCollection; -use Assetic\Factory\AssetFactory; -use October\Rain\Parse\Assetic\FilesystemCache; +use October\Rain\Assetic\Asset\FileAsset; +use October\Rain\Assetic\Asset\AssetCache; +use October\Rain\Assetic\Asset\AssetCollection; +use October\Rain\Assetic\Cache\FilesystemCache; +use October\Rain\Assetic\Factory\AssetFactory; use System\Helpers\Cache as CacheHelper; use ApplicationException; use DateTime; @@ -126,22 +126,22 @@ class CombineAssets /* * Register JavaScript filters */ - $this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter); /* * Register CSS filters */ - $this->registerFilter('css', new \Assetic\Filter\CssImportFilter); - $this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter); - $this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler); - $this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler); + $this->registerFilter('css', new \October\Rain\Assetic\Filter\CssImportFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\CssRewriteFilter); + $this->registerFilter('less', new \October\Rain\Assetic\Filter\LessCompiler); + $this->registerFilter('scss', new \October\Rain\Assetic\Filter\ScssCompiler); /* * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \Assetic\Filter\JSMinFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify); } /* diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 50a6cc8f9..40808854e 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -134,10 +134,11 @@ class MediaLibrary $folderContents = $this->scanFolderContents($fullFolderPath); $cached[$fullFolderPath] = $folderContents; + $expiresAt = now()->addMinutes(Config::get('cms.storage.media.ttl', 10)); Cache::put( $this->cacheKey, base64_encode(serialize($cached)), - Config::get('cms.storage.media.ttl', 10) + $expiresAt ); } diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 7e4779de1..1b9c3618f 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -29,11 +29,6 @@ class UpdateManager { use \October\Rain\Support\Traits\Singleton; - /** - * @var array The notes for the current operation. - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -345,13 +340,13 @@ class UpdateManager /* * Rollback modules */ + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } + while (true) { $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); - foreach ($this->migrator->getNotes() as $note) { - $this->note($note); - } - if (count($rolledBack) == 0) { break; } @@ -403,13 +398,13 @@ class UpdateManager */ public function migrateModule($module) { - $this->migrator->run(base_path() . '/modules/' . strtolower($module) . '/database/migrations'); + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } $this->note($module); - foreach ($this->migrator->getNotes() as $note) { - $this->note(' - ' . $note); - } + $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); return $this; } @@ -518,13 +513,9 @@ class UpdateManager $this->note($name); - $this->versionManager->resetNotes()->setNotesOutput($this->notesOutput); + $this->versionManager->setNotesOutput($this->notesOutput); - if ($this->versionManager->updatePlugin($plugin) !== false) { - foreach ($this->versionManager->getNotes() as $note) { - $this->note($note); - } - } + $this->versionManager->updatePlugin($plugin); return $this; } @@ -713,7 +704,8 @@ class UpdateManager } $data = $this->requestServerData($type . '/popular'); - Cache::put($cacheKey, base64_encode(serialize($data)), 60); + $expiresAt = now()->addMinutes(60); + Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt); foreach ($data as $product) { $code = array_get($product, 'code', -1); @@ -802,35 +794,11 @@ class UpdateManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); - } else { - $this->notes[] = $message; } return $this; } - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; - - return $this; - } - /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index 77127cf12..11d425d9a 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -29,12 +29,6 @@ class VersionManager const HISTORY_TYPE_COMMENT = 'comment'; const HISTORY_TYPE_SCRIPT = 'script'; - /** - * The notes for the current operation. - * @var array - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -426,6 +420,7 @@ class VersionManager * Execute the database PHP script */ $updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script; + $this->updater->packDown($updateFile); Db::table('system_plugin_history') @@ -508,35 +503,11 @@ class VersionManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); - } else { - $this->notes[] = $message; } return $this; } - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; - - return $this; - } - /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output @@ -550,8 +521,7 @@ class VersionManager } /** - * @param $details - * + * Extract script and comments from version details * @return array */ protected function extractScriptsAndComments($details): array @@ -566,7 +536,8 @@ class VersionManager $scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) { return preg_match($fileNamePattern, $detail); })); - } else { + } + else { $comments = (array)$details; $scripts = []; } diff --git a/modules/system/composer.json b/modules/system/composer.json index 5bebae7be..81d59a4dc 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index cc735c2c4..43f2079bf 100644 --- a/modules/system/console/OctoberEnv.php +++ b/modules/system/console/OctoberEnv.php @@ -369,7 +369,7 @@ class OctoberEnv extends Command 'SESSION_DRIVER' => 'driver', ], 'queue' => [ - 'QUEUE_DRIVER' => 'default', + 'QUEUE_CONNECTION' => 'default', ], 'mail' => [ 'MAIL_DRIVER' => 'driver', diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index b826fe305..200dcd221 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -17,7 +17,6 @@ use Symfony\Component\Console\Input\InputOption; */ class OctoberUpdate extends Command { - /** * The console command name. */ diff --git a/modules/system/controllers/Settings.php b/modules/system/controllers/Settings.php index 7ba29dea7..719dff20e 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -2,6 +2,8 @@ use Lang; use Flash; +use Config; +use Request; use Backend; use BackendMenu; use System\Classes\SettingsManager; @@ -139,6 +141,22 @@ class Settings extends Controller return $this->formWidget->render($options); } + /** + * Returns the form widget used by this behavior. + * + * @return \Backend\Widgets\Form + */ + public function formGetWidget() + { + if (is_null($this->formWidget)) { + $item = $this->findSettingItem(); + $model = $this->createModel($item); + $this->initWidgets($model); + } + + return $this->formWidget; + } + /** * Prepare the widgets used by this action * Model $model @@ -169,10 +187,22 @@ class Settings extends Controller } /** - * Locates a setting item for a module or plugin + * Locates a setting item for a module or plugin. + * + * If none of the parameters are provided, they will be auto-guessed from the URL. + * + * @param string|null $author + * @param string|null $plugin + * @param string|null $code + * + * @return array */ - protected function findSettingItem($author, $plugin, $code) + protected function findSettingItem($author = null, $plugin = null, $code = null) { + if (is_null($author) || is_null($plugin)) { + [$author, $plugin, $code] = $this->guessSettingItem(); + } + $manager = SettingsManager::instance(); $moduleOwner = $author; @@ -187,4 +217,23 @@ class Settings extends Controller return $item; } + + /** + * Guesses the requested setting item from the current URL segments provided by the Request object. + * + * @return array + */ + protected function guessSettingItem() + { + $segments = Request::segments(); + + if (!empty(Config::get('cms.backendUri', 'backend'))) { + array_splice($segments, 0, 4); + } else { + array_splice($segments, 0, 3); + } + + // Ensure there's at least 3 segments + return array_pad($segments, 3, null); + } } diff --git a/modules/system/database/seeds/DatabaseSeeder.php b/modules/system/database/seeds/DatabaseSeeder.php index f2f924b13..7dec41339 100644 --- a/modules/system/database/seeds/DatabaseSeeder.php +++ b/modules/system/database/seeds/DatabaseSeeder.php @@ -13,8 +13,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + Eloquent::unguarded(function () { + $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + }); } } diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index edc036dd0..ad6561c9e 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -32,6 +32,7 @@ return [ 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', @@ -39,9 +40,22 @@ return [ 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', @@ -50,6 +64,18 @@ return [ 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', @@ -65,6 +91,7 @@ return [ 'array' => 'The :attribute must have at least :min items.', ], 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', @@ -82,11 +109,13 @@ return [ 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- diff --git a/modules/system/models/mailsetting/fields.yaml b/modules/system/models/mailsetting/fields.yaml index c2457ec38..d49851cba 100644 --- a/modules/system/models/mailsetting/fields.yaml +++ b/modules/system/models/mailsetting/fields.yaml @@ -79,6 +79,7 @@ tabs: smtp_password: label: system::lang.mail.smtp_password tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -107,6 +108,7 @@ tabs: label: system::lang.mail.mailgun_secret commentAbove: system::lang.mail.mailgun_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -116,6 +118,7 @@ tabs: label: system::lang.mail.mandrill_secret commentAbove: system::lang.mail.mandrill_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -135,6 +138,7 @@ tabs: label: system::lang.mail.ses_secret commentAbove: system::lang.mail.ses_secret_comment tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -154,6 +158,7 @@ tabs: sparkpost_secret: label: system::lang.mail.sparkpost_secret commentAbove: system::lang.mail.sparkpost_secret_comment + type: sensitive tab: system::lang.mail.general trigger: action: show diff --git a/modules/system/providers.php b/modules/system/providers.php index 7b63edb43..96951f747 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -15,9 +15,7 @@ return [ Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, @@ -36,5 +34,7 @@ return [ October\Rain\Flash\FlashServiceProvider::class, October\Rain\Mail\MailServiceProvider::class, October\Rain\Argon\ArgonServiceProvider::class, + October\Rain\Redis\RedisServiceProvider::class, + October\Rain\Validation\ValidationServiceProvider::class, ]; diff --git a/phpunit.xml b/phpunit.xml index 08cd19d58..de01655c0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" > diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 6618b17e0..1d4a66f00 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -28,7 +28,7 @@ abstract class PluginTestCase extends TestCase $app['cache']->setDefaultDriver('array'); $app->setLocale('en'); - $app->singleton('auth', function ($app) { + $app->singleton('backend.auth', function ($app) { $app['auth.loaded'] = true; return AuthManager::instance(); @@ -67,7 +67,7 @@ abstract class PluginTestCase extends TestCase * Perform test case set up. * @return void */ - public function setUp() + public function setUp() : void { /* * Force reload of October singletons @@ -105,7 +105,7 @@ abstract class PluginTestCase extends TestCase * Flush event listeners and collect garbage. * @return void */ - public function tearDown() + public function tearDown() : void { $this->flushModelEventListeners(); parent::tearDown(); diff --git a/tests/README.md b/tests/README.md index a9e7b78b5..0e985d79b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Plugin unit tests can be performed by running `phpunit` in the base plugin directory. +Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`. ### Creating plugin tests @@ -58,7 +58,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci class BaseTestCase extends PluginTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); @@ -72,7 +72,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci $pluginManager->bootAll(true); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); @@ -96,39 +96,10 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests -Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. +Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation. ### Functional tests -Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: +Functional tests can be performed by installing the [RainLab Dusk](https://octobercms.com/plugin/rainlab-dusk) in your October CMS installation. The RainLab Dusk plugin is powered by Laravel Dusk, a comprehensive testing suite for the Laravel framework that is designed to test interactions with a fully operational October CMS instance through a virtual browser. -- Active theme is `demo` -- Language preference is `en` - -#### Selenium set up - -1. Download latest Java SE from http://java.sun.com/ and install -1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). -1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. -1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. - -#### Selenium configuration - -Create a new file `selenium.php` in the root directory, add the following content: - - markTestSkipped('Selenium skipped'); - } - - if (defined('TEST_SELENIUM_HOST')) { - $this->setHost(TEST_SELENIUM_HOST); - } - if (defined('TEST_SELENIUM_PORT')) { - $this->setPort(TEST_SELENIUM_PORT); - } - if (defined('TEST_SELENIUM_BROWSER')) { - $this->setBrowser(TEST_SELENIUM_BROWSER); - } - $this->setBrowserUrl(TEST_SELENIUM_URL); - } - - // - // OctoberCMS Helpers - // - - protected function signInToBackend() - { - $this->open('backend'); - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } - - // - // Selenium helpers - // - - protected function waitForElementPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if ($this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } - - protected function waitForElementNotPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if (!$this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e7e53fd1a..5ed3e1296 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,14 +19,3 @@ $loader->addDirectories([ 'modules', 'plugins' ]); - -/* - * Monkey patch PHPUnit\Framework\MockObject\Generator to avoid - * "Function ReflectionType::__toString() is deprecated" warnings - */ -$generatorPatchPath = __DIR__ . '/resources/patches/php-generator-7.php'; -$generatorSourcePath = __DIR__ . '/../vendor/phpunit/phpunit-mock-objects/src/Generator.php'; - -if (file_exists($generatorSourcePath)) { - file_put_contents($generatorSourcePath, file_get_contents($generatorPatchPath)); -} diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/concerns/InteractsWithAuthentication.php index a7950f230..5f5c3c8c7 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/concerns/InteractsWithAuthentication.php @@ -29,7 +29,7 @@ trait InteractsWithAuthentication */ public function be(UserContract $user, $driver = null) { - $this->app['auth']->setUser($user); + $this->app['backend.auth']->setUser($user); } /** @@ -66,7 +66,7 @@ trait InteractsWithAuthentication */ protected function isAuthenticated($guard = null) { - return $this->app->make('auth')->guard($guard)->check(); + return $this->app->make('backend.auth')->guard($guard)->check(); } /** @@ -78,7 +78,7 @@ trait InteractsWithAuthentication */ public function assertAuthenticatedAs($user, $guard = null) { - $expected = $this->app->make('auth')->guard($guard)->user(); + $expected = $this->app->make('backend.auth')->guard($guard)->user(); $this->assertNotNull($expected, 'The current user is not authenticated.'); @@ -140,7 +140,7 @@ trait InteractsWithAuthentication */ protected function hasCredentials(array $credentials, $guard = null) { - $provider = $this->app->make('auth')->guard($guard)->getProvider(); + $provider = $this->app->make('backend.auth')->guard($guard)->getProvider(); $user = $provider->retrieveByCredentials($credentials); diff --git a/tests/fixtures/plugins/database/tester/models/Author.php b/tests/fixtures/plugins/database/tester/models/Author.php index 05e9a039f..2d9656fee 100644 --- a/tests/fixtures/plugins/database/tester/models/Author.php +++ b/tests/fixtures/plugins/database/tester/models/Author.php @@ -20,6 +20,7 @@ class Author extends Model */ public $belongsTo = [ 'user' => ['Database\Tester\Models\User', 'delete' => true], + 'country' => ['Database\Tester\Models\Country'], 'user_soft' => ['Database\Tester\Models\SoftDeleteUser', 'key' => 'user_id', 'softDelete' => true], ]; diff --git a/tests/fixtures/plugins/database/tester/models/Country.php b/tests/fixtures/plugins/database/tester/models/Country.php new file mode 100644 index 000000000..e7bb583c5 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/models/Country.php @@ -0,0 +1,35 @@ + [ + 'Database\Tester\Models\User', + ], + ]; + + public $hasManyThrough = [ + 'posts' => [ + 'Database\Tester\Models\Post', + 'through' => 'Database\Tester\Models\Author', + ] + ]; +} + +class SoftDeleteCountry extends Country +{ + use \October\Rain\Database\Traits\SoftDelete; +} diff --git a/tests/fixtures/plugins/database/tester/models/User.php b/tests/fixtures/plugins/database/tester/models/User.php index 867992c2c..130120e9c 100644 --- a/tests/fixtures/plugins/database/tester/models/User.php +++ b/tests/fixtures/plugins/database/tester/models/User.php @@ -17,6 +17,19 @@ class User extends Model /** * @var array Relations */ + public $hasOne = [ + 'author' => [ + 'Database\Tester\Models\Author', + ] + ]; + + public $hasOneThrough = [ + 'phone' => [ + 'Database\Tester\Models\Phone', + 'through' => 'Database\Tester\Models\Author', + ], + ]; + public $attachOne = [ 'avatar' => 'System\Models\File' ]; diff --git a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php index 6466e4e13..d6277e525 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php @@ -11,6 +11,7 @@ class CreateAuthorsTable extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->integer('user_id')->unsigned()->index()->nullable(); + $table->integer('country_id')->unsigned()->index()->nullable(); $table->string('name')->nullable(); $table->string('email')->nullable(); $table->softDeletes(); diff --git a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php new file mode 100644 index 000000000..7fc85ae54 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php @@ -0,0 +1,23 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('database_tester_countries'); + } +} diff --git a/tests/fixtures/plugins/database/tester/updates/version.yaml b/tests/fixtures/plugins/database/tester/updates/version.yaml index 613fc5337..590bb7892 100644 --- a/tests/fixtures/plugins/database/tester/updates/version.yaml +++ b/tests/fixtures/plugins/database/tester/updates/version.yaml @@ -9,3 +9,4 @@ - create_users_table.php - create_event_log_table.php - create_meta_table.php + - create_countries_table.php diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index e69de29bb..b8bbe4e13 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -0,0 +1 @@ +console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index e69de29bb..54e00dea3 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -0,0 +1 @@ +console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js new file mode 100644 index 000000000..bc27e099c --- /dev/null +++ b/tests/fixtures/themes/test/assets/js/subdir/script1.js @@ -0,0 +1 @@ +console.log('subdir/script1.js'); diff --git a/tests/functional/backend/AuthTest.php b/tests/functional/backend/AuthTest.php deleted file mode 100644 index 4c1fe925d..000000000 --- a/tests/functional/backend/AuthTest.php +++ /dev/null @@ -1,90 +0,0 @@ -open('backend'); - - $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("name=login")); - $this->assertTrue($this->isElementPresent("name=password")); - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Login"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - /* - * Sign in - */ - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Dashboard | October CMS'); - $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->verifyText('css='.$cssLogoutLink, "Sign out"); - - /* - * Log out - */ - $this->click('css='.$cssLogoutLink); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } - - public function testPasswordReset() - { - $this->open('backend'); - - try { - $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->click('link=exact:Forgot your password?'); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Restore"); - $this->assertTrue($this->isElementPresent("link=Cancel")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->type("name=login", TEST_SELENIUM_USER); - sleep(1); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); - $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } -} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php deleted file mode 100644 index 5ff2a0a9e..000000000 --- a/tests/functional/cms/TemplateTest.php +++ /dev/null @@ -1,143 +0,0 @@ -signInToBackend(); - $this->open('cms'); - $this->waitForPageToLoad("30000"); - - // Fix the sidebar - $this->click("xpath=(//a[@class='fix-button'])[1]"); - - /* - * Page - */ - - // Create a new page - $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=settings[title]"); - - // Populate page details - $this->type('name=settings[title]', 'Functional Test Page'); - $this->type('name=settings[url]', '/xxx/functional/test/page'); - $this->type('name=fileName', 'xxx_functional_test_page'); - - // Save the new page - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->waitForElementPresent("name=settings[title]"); - sleep(1); - - // Delete the page - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this page?'); - // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); - $this->waitForElementNotPresent("name=settings[title]"); - - /* - * Partial - */ - - // Click partials menu item - $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); - - // Create a new partial - $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate partial details - $this->type('name=fileName', 'xxx_functional_test_partial'); - $this->type('name=settings[description]', 'Test partial'); - - // Save the new partial - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the partial - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this partial?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Layout - */ - - // Click layouts menu item - $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); - - // Create a new layout - $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate layout details - $this->type('name=fileName', 'xxx_functional_test_layout'); - $this->type('name=settings[description]', 'Test layout'); - - // Save the new layout - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the layout - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this layout?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Content - */ - - // Click contents menu item - $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); - - // Create a new content - $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate content details - $this->type('name=fileName', 'xxx_functional_test_content.txt'); - - // Save the new content - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the content - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this content file?'); - $this->waitForElementNotPresent("name=fileName"); - } -} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml deleted file mode 100644 index c8043149c..000000000 --- a/tests/functional/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ./ - - - \ No newline at end of file diff --git a/tests/resources/patches/php-generator-7.php b/tests/resources/patches/php-generator-7.php deleted file mode 100644 index 677680d69..000000000 --- a/tests/resources/patches/php-generator-7.php +++ /dev/null @@ -1,1185 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - * Patched with: https://github.com/sebastianbergmann/phpunit/pull/3765/files - */ -namespace PHPUnit\Framework\MockObject; - -use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; -use Doctrine\Instantiator\Instantiator; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\Exception; -use PHPUnit\Util\InvalidArgumentHelper; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; -use SoapClient; -use Text_Template; -use Traversable; - -/** - * Mock Object Code Generator - */ -class Generator -{ - /** - * @var array - */ - private static $cache = []; - - /** - * @var Text_Template[] - */ - private static $templates = []; - - /** - * @var array - */ - private $blacklistedMethodNames = [ - '__CLASS__' => true, - '__DIR__' => true, - '__FILE__' => true, - '__FUNCTION__' => true, - '__LINE__' => true, - '__METHOD__' => true, - '__NAMESPACE__' => true, - '__TRAIT__' => true, - '__clone' => true, - '__halt_compiler' => true, - ]; - - /** - * Returns a mock object for the specified class. - * - * @param string|string[] $type - * @param array $methods - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * @param bool $allowMockingUnknownTypes - * - * @return MockObject - * - * @throws Exception - * @throws RuntimeException - * @throws \PHPUnit\Framework\Exception - * @throws \ReflectionException - */ - public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true) - { - if (!\is_array($type) && !\is_string($type)) { - throw InvalidArgumentHelper::factory(1, 'array or string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(4, 'string'); - } - - if (!\is_array($methods) && null !== $methods) { - throw InvalidArgumentHelper::factory(2, 'array', $methods); - } - - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - - if (\is_array($type)) { - $type = \array_unique( - \array_map( - function ($type) { - if ($type === 'Traversable' || - $type === '\\Traversable' || - $type === '\\Iterator') { - return 'Iterator'; - } - - return $type; - }, - $type - ) - ); - } - - if (!$allowMockingUnknownTypes) { - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\class_exists($_type, $callAutoload) && - !\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $_type - ) - ); - } - } - } else { - if (!\class_exists($type, $callAutoload) && - !\interface_exists($type, $callAutoload) - ) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $type - ) - ); - } - } - } - - if (null !== $methods) { - foreach ($methods as $method) { - if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock method with invalid name "%s"', - $method - ) - ); - } - } - - if ($methods !== \array_unique($methods)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', - \implode(', ', $methods), - \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods)))) - ) - ); - } - } - - if ($mockClassName !== '' && \class_exists($mockClassName, false)) { - $reflect = new ReflectionClass($mockClassName); - - if (!$reflect->implementsInterface(MockObject::class)) { - throw new RuntimeException( - \sprintf( - 'Class "%s" already exists.', - $mockClassName - ) - ); - } - } - - if ($callOriginalConstructor === false && $callOriginalMethods === true) { - throw new RuntimeException( - 'Proxying to original methods requires invoking the original constructor' - ); - } - - $mock = $this->generate( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - return $this->getObject( - $mock['code'], - $mock['mockClassName'], - $type, - $callOriginalConstructor, - $callAutoload, - $arguments, - $callOriginalMethods, - $proxyTarget - ); - } - - /** - * @param string $code - * @param string $className - * @param array|string $type - * @param bool $callOriginalConstructor - * @param bool $callAutoload - * @param array $arguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - */ - private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null) - { - $this->evalClass($code, $className); - - if ($callOriginalConstructor && - \is_string($type) && - !\interface_exists($type, $callAutoload)) { - if (\count($arguments) === 0) { - $object = new $className; - } else { - $class = new ReflectionClass($className); - $object = $class->newInstanceArgs($arguments); - } - } else { - try { - $instantiator = new Instantiator; - $object = $instantiator->instantiate($className); - } catch (InstantiatorException $exception) { - throw new RuntimeException($exception->getMessage()); - } - } - - if ($callOriginalMethods) { - if (!\is_object($proxyTarget)) { - if (\count($arguments) === 0) { - $proxyTarget = new $type; - } else { - $class = new ReflectionClass($type); - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - - $object->__phpunit_setOriginalObject($proxyTarget); - } - - return $object; - } - - /** - * @param string $code - * @param string $className - */ - private function evalClass($code, $className) - { - if (!\class_exists($className, false)) { - eval($code); - } - } - - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods to mock can be specified with - * the last parameter - * - * @param string $originalClassName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($originalClassName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (\class_exists($originalClassName, $callAutoload) || - \interface_exists($originalClassName, $callAutoload)) { - $reflector = new ReflectionClass($originalClassName); - $methods = $mockedMethods; - - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !\in_array($method->getName(), $methods)) { - $methods[] = $method->getName(); - } - } - - if (empty($methods)) { - $methods = null; - } - - return $this->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments - ); - } - - throw new RuntimeException( - \sprintf('Class "%s" does not exist.', $originalClassName) - ); - } - - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @param string $traitName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - '', - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => 'abstract ', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - $this->evalClass( - $classTemplate->render(), - $className['className'] - ); - - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - } - - /** - * Returns an object for the specified trait. - * - * @param string $traitName - * @param array $arguments - * @param string $traitClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * - * @return object - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($traitClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - $traitClassName, - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => '', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - return $this->getObject($classTemplate->render(), $className['className']); - } - - /** - * @param array|string $type - * @param array $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \ReflectionException - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) - { - if (\is_array($type)) { - \sort($type); - } - - if ($mockClassName === '') { - $key = \md5( - \is_array($type) ? \implode('_', $type) : $type . - \serialize($methods) . - \serialize($callOriginalClone) . - \serialize($cloneArguments) . - \serialize($callOriginalMethods) - ); - - if (isset(self::$cache[$key])) { - return self::$cache[$key]; - } - } - - $mock = $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - if (isset($key)) { - self::$cache[$key] = $mock; - } - - return $mock; - } - - /** - * @param string $wsdlFile - * @param string $className - * @param array $methods - * @param array $options - * - * @return string - * - * @throws RuntimeException - */ - public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = []) - { - if (!\extension_loaded('soap')) { - throw new RuntimeException( - 'The SOAP extension is required to generate a mock object from WSDL.' - ); - } - - $options = \array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); - $client = new SoapClient($wsdlFile, $options); - $_methods = \array_unique($client->__getFunctions()); - unset($client); - - \sort($_methods); - - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - - foreach ($_methods as $method) { - $nameStart = \strpos($method, ' ') + 1; - $nameEnd = \strpos($method, '('); - $name = \substr($method, $nameStart, $nameEnd - $nameStart); - - if (empty($methods) || \in_array($name, $methods)) { - $args = \explode( - ',', - \substr( - $method, - $nameEnd + 1, - \strpos($method, ')') - $nameEnd - 1 - ) - ); - - foreach (\range(0, \count($args) - 1) as $i) { - $args[$i] = \substr($args[$i], \strpos($args[$i], '$')); - } - - $methodTemplate->setVar( - [ - 'method_name' => $name, - 'arguments' => \implode(', ', $args) - ] - ); - - $methodsBuffer .= $methodTemplate->render(); - } - } - - $optionsBuffer = 'array('; - - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - - $optionsBuffer .= ')'; - - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - - if (\strpos($className, '\\') !== false) { - $parts = \explode('\\', $className); - $className = \array_pop($parts); - $namespace = 'namespace ' . \implode('\\', $parts) . ';' . "\n\n"; - } - - $classTemplate->setVar( - [ - 'namespace' => $namespace, - 'class_name' => $className, - 'wsdl' => $wsdlFile, - 'options' => $optionsBuffer, - 'methods' => $methodsBuffer - ] - ); - - return $classTemplate->render(); - } - - /** - * @param array|string $type - * @param array|null $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \InvalidArgumentException - * @throws \ReflectionException - * @throws RuntimeException - */ - private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) - { - $methodReflections = []; - $classTemplate = $this->getTemplate('mocked_class.tpl'); - - $additionalInterfaces = []; - $cloneTemplate = ''; - $isClass = false; - $isInterface = false; - $isMultipleInterfaces = false; - - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Interface "%s" does not exist.', - $_type - ) - ); - } - - $isMultipleInterfaces = true; - - $additionalInterfaces[] = $_type; - $typeClass = new ReflectionClass($this->generateClassName( - $_type, - $mockClassName, - 'Mock_' - )['fullClassName'] - ); - - foreach ($this->getClassMethods($_type) as $method) { - if (\in_array($method, $methods)) { - throw new RuntimeException( - \sprintf( - 'Duplicate method "%s" not allowed.', - $method - ) - ); - } - - $methodReflections[$method] = $typeClass->getMethod($method); - $methods[] = $method; - } - } - } - - $mockClassName = $this->generateClassName( - $type, - $mockClassName, - 'Mock_' - ); - - if (\class_exists($mockClassName['fullClassName'], $callAutoload)) { - $isClass = true; - } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) { - $isInterface = true; - } - - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; - - if (!empty($mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $mockClassName['namespaceName'] . - " {\n\n" . $prologue . "}\n\n" . - "namespace {\n\n"; - - $epilogue = "\n\n}"; - } - - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } else { - $class = new ReflectionClass($mockClassName['fullClassName']); - - if ($class->isFinal()) { - throw new RuntimeException( - \sprintf( - 'Class "%s" is declared "final" and cannot be mocked.', - $mockClassName['fullClassName'] - ) - ); - } - - if ($class->hasMethod('__clone')) { - $cloneMethod = $class->getMethod('__clone'); - - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $cloneTemplate = $this->getTemplate('unmocked_clone.tpl'); - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - - if (\is_object($cloneTemplate)) { - $cloneTemplate = $cloneTemplate->render(); - } - - if (\is_array($methods) && empty($methods) && - ($isClass || $isInterface)) { - $methods = $this->getClassMethods($mockClassName['fullClassName']); - } - - if (!\is_array($methods)) { - $methods = []; - } - - $mockedMethods = ''; - $configurable = []; - - foreach ($methods as $methodName) { - if ($methodName !== '__construct' && $methodName !== '__clone') { - $configurable[] = \strtolower($methodName); - } - } - - if (isset($class)) { - // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && - !$class->implementsInterface(Iterator::class) && - !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - $methods = \array_merge($methods, $this->getClassMethods(Iterator::class)); - } - - foreach ($methods as $methodName) { - try { - $method = $class->getMethod($methodName); - - if ($this->canMockMethod($method)) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $method, - $cloneArguments, - $callOriginalMethods - ); - } - } catch (ReflectionException $e) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - } elseif ($isMultipleInterfaces) { - foreach ($methods as $methodName) { - if ($this->canMockMethod($methodReflections[$methodName])) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $methodReflections[$methodName], - $cloneArguments, - $callOriginalMethods - ); - } - } - } else { - foreach ($methods as $methodName) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - - $method = ''; - - if (!\in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) { - $methodTemplate = $this->getTemplate('mocked_class_method.tpl'); - - $method = $methodTemplate->render(); - } - - $classTemplate->setVar( - [ - 'prologue' => $prologue ?? '', - 'epilogue' => $epilogue ?? '', - 'class_declaration' => $this->generateMockClassDeclaration( - $mockClassName, - $isInterface, - $additionalInterfaces - ), - 'clone' => $cloneTemplate, - 'mock_class_name' => $mockClassName['className'], - 'mocked_methods' => $mockedMethods, - 'method' => $method, - 'configurable' => '[' . \implode(', ', \array_map(function ($m) { - return '\'' . $m . '\''; - }, $configurable)) . ']' - ] - ); - - return [ - 'code' => $classTemplate->render(), - 'mockClassName' => $mockClassName['className'] - ]; - } - - /** - * @param array|string $type - * @param string $className - * @param string $prefix - * - * @return array - */ - private function generateClassName($type, $className, $prefix) - { - if (\is_array($type)) { - $type = \implode('_', $type); - } - - if ($type[0] === '\\') { - $type = \substr($type, 1); - } - - $classNameParts = \explode('\\', $type); - - if (\count($classNameParts) > 1) { - $type = \array_pop($classNameParts); - $namespaceName = \implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; - } else { - $namespaceName = ''; - $fullClassName = $type; - } - - if ($className === '') { - do { - $className = $prefix . $type . '_' . - \substr(\md5(\mt_rand()), 0, 8); - } while (\class_exists($className, false)); - } - - return [ - 'className' => $className, - 'originalClassName' => $type, - 'fullClassName' => $fullClassName, - 'namespaceName' => $namespaceName - ]; - } - - /** - * @param array $mockClassName - * @param bool $isInterface - * @param array $additionalInterfaces - * - * @return string - */ - private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = []) - { - $buffer = 'class '; - - $additionalInterfaces[] = MockObject::class; - $interfaces = \implode(', ', $additionalInterfaces); - - if ($isInterface) { - $buffer .= \sprintf( - '%s implements %s', - $mockClassName['className'], - $interfaces - ); - - if (!\in_array($mockClassName['originalClassName'], $additionalInterfaces)) { - $buffer .= ', '; - - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - - $buffer .= $mockClassName['originalClassName']; - } - } else { - $buffer .= \sprintf( - '%s extends %s%s implements %s', - $mockClassName['className'], - !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', - $mockClassName['originalClassName'], - $interfaces - ); - } - - return $buffer; - } - - /** - * @param ReflectionMethod $method - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return string - * - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods) - { - if ($method->isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - - if ($method->isStatic()) { - $modifier .= ' static'; - } - - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - - if ($method->hasReturnType()) { - $returnType = $method->getReturnType()->getName(); - } else { - $returnType = ''; - } - - if (\preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) { - $deprecation = \trim(\preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = false; - } - - return $this->generateMockedMethodDefinition( - $method->getDeclaringClass()->getName(), - $method->getName(), - $cloneArguments, - $modifier, - $this->getMethodParameters($method), - $this->getMethodParameters($method, true), - $returnType, - $reference, - $callOriginalMethods, - $method->isStatic(), - $deprecation, - $method->hasReturnType() && PHP_VERSION_ID >= 70100 && $method->getReturnType()->allowsNull() - ); - } - - /** - * @param string $className - * @param string $methodName - * @param bool $cloneArguments - * @param string $modifier - * @param string $argumentsForDeclaration - * @param string $argumentsForCall - * @param string $returnType - * @param string $reference - * @param bool $callOriginalMethods - * @param bool $static - * @param bool|string $deprecation - * @param bool $allowsReturnNull - * - * @return string - * - * @throws \InvalidArgumentException - */ - private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false) - { - if ($static) { - $templateFile = 'mocked_static_method.tpl'; - } else { - if ($returnType === 'void') { - $templateFile = \sprintf( - '%s_method_void.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } else { - $templateFile = \sprintf( - '%s_method.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } - } - - // Mocked interfaces returning 'self' must explicitly declare the - // interface name as the return type. See - // https://bugs.php.net/bug.php?id=70722 - if ($returnType === 'self') { - $returnType = $className; - } - - if (false !== $deprecation) { - $deprecation = "The $className::$methodName method is deprecated ($deprecation)."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - - $deprecationTemplate->setVar( - [ - 'deprecation' => \var_export($deprecation, true), - ] - ); - - $deprecation = $deprecationTemplate->render(); - } - - $template = $this->getTemplate($templateFile); - - $template->setVar( - [ - 'arguments_decl' => $argumentsForDeclaration, - 'arguments_call' => $argumentsForCall, - 'return_delim' => $returnType ? ': ' : '', - 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType, - 'arguments_count' => !empty($argumentsForCall) ? \substr_count($argumentsForCall, ',') + 1 : 0, - 'class_name' => $className, - 'method_name' => $methodName, - 'modifier' => $modifier, - 'reference' => $reference, - 'clone_arguments' => $cloneArguments ? 'true' : 'false', - 'deprecation' => $deprecation - ] - ); - - return $template->render(); - } - - /** - * @param ReflectionMethod $method - * - * @return bool - * - * @throws \ReflectionException - */ - private function canMockMethod(ReflectionMethod $method) - { - return !($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())); - } - - /** - * Returns whether a method name is blacklisted - * - * @param string $name - * - * @return bool - */ - private function isMethodNameBlacklisted($name) - { - return isset($this->blacklistedMethodNames[$name]); - } - - /** - * Returns the parameters of a function or method. - * - * @param ReflectionMethod $method - * @param bool $forCall - * - * @return string - * - * @throws RuntimeException - */ - private function getMethodParameters(ReflectionMethod $method, $forCall = false) - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - if ($parameter->isVariadic()) { - if ($forCall) { - continue; - } - - $name = '...' . $name; - } - - $nullable = ''; - $default = ''; - $reference = ''; - $typeDeclaration = ''; - - if (!$forCall) { - if (PHP_VERSION_ID >= 70100 && $parameter->hasType() && $parameter->allowsNull()) { - $nullable = '?'; - } - - if ($parameter->hasType() && $parameter->getType()->getName() !== 'self') { - $typeDeclaration = $parameter->getType()->getName() . ' '; - } elseif ($parameter->isArray()) { - $typeDeclaration = 'array '; - } elseif ($parameter->isCallable()) { - $typeDeclaration = 'callable '; - } else { - try { - $class = $parameter->getClass(); - } catch (ReflectionException $e) { - throw new RuntimeException( - \sprintf( - 'Cannot mock %s::%s() because a class or ' . - 'interface used in the signature is not loaded', - $method->getDeclaringClass()->getName(), - $method->getName() - ), - 0, - $e - ); - } - - if ($class !== null) { - $typeDeclaration = $class->getName() . ' '; - } - } - - if (!$parameter->isVariadic()) { - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValueConstantName(); - - if ($value === null) { - $value = \var_export($parameter->getDefaultValue(), true); - } elseif (!\defined($value)) { - $rootValue = \preg_replace('/^.*\\\\/', '', $value); - $value = \defined($rootValue) ? $rootValue : $value; - } - - $default = ' = ' . $value; - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - } - } - - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - - $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; - } - - return \implode(', ', $parameters); - } - - /** - * @param string $className - * - * @return array - * - * @throws \ReflectionException - */ - public function getClassMethods($className) - { - $class = new ReflectionClass($className); - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } - - /** - * @param string $template - * - * @return Text_Template - * - * @throws \InvalidArgumentException - */ - private function getTemplate($template) - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - self::$templates[$filename] = new Text_Template($filename); - } - - return self::$templates[$filename]; - } -} diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index 6bb9f9684..65dd5e735 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -4,7 +4,7 @@ use October\Rain\Exception\SystemException; class AuthManagerTest extends TestCase { - public function setUp() + public function setUp(): void { $this->createApplication(); @@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase ]); } - public function tearDown() + public function tearDown(): void { AuthManager::forgetInstance(); } diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index 04ca2f183..f63645198 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -59,18 +59,18 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('posts', $items); $this->assertArrayHasKey('categories', $items); - $this->assertInternalType('object', $items['posts']); + $this->assertIsObject($items['posts']); $this->assertObjectHasAttribute('code', $items['posts']); $this->assertObjectHasAttribute('owner', $items['posts']); $this->assertEquals('posts', $items['posts']->code); $this->assertEquals('October.Tester', $items['posts']->owner); $this->assertObjectHasAttribute('permissions', $items['posts']); - $this->assertInternalType('array', $items['posts']->permissions); + $this->assertIsArray($items['posts']->permissions); $this->assertCount(1, $items['posts']->permissions); $this->assertObjectHasAttribute('order', $items['posts']); @@ -92,7 +92,7 @@ class NavigationManagerTest extends TestCase $items = $manager->listMainMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('OCTOBER.TESTER.PRINT', $items); $item = $items['OCTOBER.TESTER.PRINT']; @@ -143,10 +143,10 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('foo', $items); - $this->assertInternalType('object', $items['foo']); + $this->assertIsObject($items['foo']); $this->assertObjectHasAttribute('code', $items['foo']); $this->assertObjectHasAttribute('owner', $items['foo']); $this->assertObjectHasAttribute('order', $items['foo']); @@ -156,7 +156,7 @@ class NavigationManagerTest extends TestCase $this->assertEquals('October.Tester', $items['foo']->owner); $this->assertObjectHasAttribute('permissions', $items['foo']); - $this->assertInternalType('array', $items['foo']->permissions); + $this->assertIsArray($items['foo']->permissions); $this->assertCount(2, $items['foo']->permissions); $this->assertContains('october.tester.access_foo', $items['foo']->permissions); $this->assertContains('october.tester.access_bar', $items['foo']->permissions); diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index b4ee7c0c6..176690b78 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -11,8 +11,8 @@ class BackendHelperTest extends TestCase $assets = $backendHelper->decompileAsset('tests/fixtures/backend/assets/compilation.js'); $this->assertCount(2, $assets); - $this->assertContains('file1.js', $assets[0]); - $this->assertContains('file2.js', $assets[1]); + $this->assertStringContainsString('file1.js', $assets[0]); + $this->assertStringContainsString('file2.js', $assets[1]); } public function testDecompileMissingFile() diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 1369a34d1..9a290b509 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -28,7 +28,7 @@ class WidgetMakerTest extends TestCase * * @return void */ - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/AssetTest.php b/tests/unit/cms/classes/AssetTest.php new file mode 100644 index 000000000..13519a161 --- /dev/null +++ b/tests/unit/cms/classes/AssetTest.php @@ -0,0 +1,119 @@ +assertStringContainsString( + 'console.log(\'script1.js\');', + Asset::load($theme, 'js/script1.js')->content + ); + + // Valid direct subdirectory path + $this->assertStringContainsString( + 'console.log(\'subdir/script1.js\');', + Asset::load($theme, 'js/subdir/script1.js')->content + ); + + // Valid relative path + $this->assertStringContainsString( + 'console.log(\'script2.js\');', + Asset::load($theme, 'js/subdir/../script2.js')->content + ); + + // Invalid theme path + $this->assertNull( + Asset::load($theme, 'js/invalid.js') + ); + + // Check that we cannot break out of assets directory + $this->assertNull( + Asset::load($theme, '../../../../js/helpers/fakeDom.js') + ); + $this->assertNull( + Asset::load($theme, '../content/html-content.htm') + ); + + // Check that we cannot load directories directly + $this->assertNull( + Asset::load($theme, 'js/subdir') + ); + + // Check that we definitely cannot load external PHP files + $this->assertNull( + Asset::load($theme, '../../../../../config/database.php') + ); + } + + public function testGetPath() + { + // Test some pathing fringe cases + + $theme = Theme::load('test'); + $assetClass = new Asset($theme); + $themeDir = $theme->getPath(); + + // Direct paths + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('js/script1.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('/js/script1.js') + ); + + // Direct path to a directory + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir') + ); + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir/') + ); + + // Relative paths + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('./js/script2.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('/js/subdir/../script2.js') + ); + + // Missing file, but valid directory (allows for new files) + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('/js/missing.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('js/missing.js') + ); + + // Missing file and missing directory (new directories are created as needed) + $this->assertEquals( + $themeDir . '/assets/js/missing/missing.js', + $assetClass->getFilePath('/js/missing/missing.js') + ); + + // Ensure we cannot get paths outside of the assets directory + $this->assertFalse( + $assetClass->getFilePath('../../../../js/helpers/fakeDom.js') + ); + $this->assertFalse( + $assetClass->getFilePath('../content/html-content.htm') + ); + $this->assertFalse( + $assetClass->getFilePath('../../../../../config/database.php') + ); + } +} diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index c66d31ee5..ec82153f9 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -30,7 +30,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject class CmsCompoundObjectTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); Model::clearBootedModels(); @@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase $theme = Theme::load('test'); $obj = TestCmsCompoundObject::load($theme, 'compound.htm'); - $this->assertContains("\$controller->data['something'] = 'some value'", $obj->code); + $this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('section', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -69,7 +69,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'component.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive', $obj->settings['components']); $this->assertArrayHasKey('posts-per-page', $obj->settings['components']['testArchive']); $this->assertEquals(10, $obj->settings['components']['testArchive']['posts-per-page']); @@ -82,7 +82,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'components.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive firstAlias', $obj->settings['components']); $this->assertArrayHasKey('October\Tester\Components\Post secondAlias', $obj->settings['components']); @@ -108,7 +108,7 @@ class CmsCompoundObjectTest extends TestCase $properties = $obj->getComponentProperties('October\Tester\Components\Post'); $emptyProperties = $obj->getComponentProperties('October\Tester\Components\Archive'); $notExistingProperties = $obj->getComponentProperties('This\Is\Not\Component'); - $this->assertInternalType('array', $properties); + $this->assertIsArray($properties); $this->assertArrayHasKey('show-featured', $properties); $this->assertTrue((bool)$properties['show-featured']); $this->assertEquals('true', $properties['show-featured']); @@ -148,18 +148,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -173,18 +173,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); } @@ -280,14 +280,14 @@ class CmsCompoundObjectTest extends TestCase $obj = TestParsedCmsCompoundObject::load($theme, 'viewbag.htm'); $this->assertNull($obj->code); $this->assertEquals('

Chop Suey!

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('viewBag', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['viewBag']); + $this->assertIsArray($obj->settings['components']['viewBag']); $this->assertArrayHasKey('title', $obj->settings['components']['viewBag']); $this->assertEquals('Toxicity', $obj->settings['components']['viewBag']['title']); diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index 978745ae1..dc5172add 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Model; class CmsObjectQueryTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 95d4f50d9..2c65761b4 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -148,12 +148,11 @@ class CmsObjectTest extends TestCase $this->assertNull($obj->something); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNameSymbol() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -164,12 +163,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNamePath() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -180,12 +178,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileSlash() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -196,12 +193,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage The File Name field is required - */ public function testFillEmptyFileName() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('The File Name field is required'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -266,11 +262,12 @@ class CmsObjectTest extends TestCase /** * @depends testRename - * @expectedException \October\Rain\Exception\ApplicationException - * @expectedExceptionMessage already exists */ public function testRenameToExistingFile() { + $this->expectException(\October\Rain\Exception\ApplicationException::class); + $this->expectExceptionMessageMatches('/already\sexists/'); + $theme = Theme::load('apitest'); $srcFilePath = $theme->getPath().'/testobjects/anotherobj.htm'; diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 43eb34a84..67d8f30e9 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -10,7 +10,7 @@ use Cms\Classes\Controller; class CodeParserTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setup(); @@ -41,7 +41,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -78,7 +78,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -91,7 +91,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -101,7 +101,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -110,13 +110,14 @@ class CodeParserTest extends TestCase */ $this->assertTrue(@touch($layout->getFilePath())); + clearstatcache(); $layout = Layout::load($theme, 'php-parser-test.htm'); $this->assertNotEmpty($layout); $parser = new CodeParser($layout); $property->setValue($parser, []); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('parser', $info['source']); $this->assertFileExists($info['filePath']); } @@ -131,7 +132,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -157,7 +158,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -191,7 +192,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -220,7 +221,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -255,7 +256,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -284,7 +285,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index a12255580..152367b8d 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -9,7 +9,7 @@ use Cms\Classes\ComponentManager; class ComponentManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 02b2abc34..1496055a3 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -6,7 +6,7 @@ use October\Rain\Halcyon\Model; class ControllerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -69,7 +69,7 @@ class ControllerTest extends TestCase $response = $controller->run('/some-page-that-doesnt-exist'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

Page not found

', $content); } @@ -83,16 +83,15 @@ class ControllerTest extends TestCase $response = $controller->run('/'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

My Webpage

', trim($content)); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage is not found - */ public function testLayoutNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-layout'); @@ -146,12 +145,11 @@ class ControllerTest extends TestCase $this->assertEquals("
LAYOUT CONTENT

This page is a subdirectory

", $response); } - /** - * @expectedException \Twig\Error\RuntimeError - * @expectedExceptionMessage is not found - */ public function testPartialNotFound() { + $this->expectException(\Twig\Error\RuntimeError::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-partial')->getContent(); @@ -193,12 +191,11 @@ class ControllerTest extends TestCase return $requestMock; } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage AJAX handler 'onNoHandler' was not found. - */ public function testAjaxHandlerNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('AJAX handler \'onNoHandler\' was not found.'); + Request::swap($this->configAjaxRequestMock('onNoHandler', '')); $theme = Theme::load('test'); @@ -206,12 +203,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid AJAX handler name: delete. - */ public function testAjaxInvalidHandlerName() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid AJAX handler name: delete.'); + Request::swap($this->configAjaxRequestMock('delete')); $theme = Theme::load('test'); @@ -219,12 +215,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid partial name: p:artial. - */ public function testAjaxInvalidPartial() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid partial name: p:artial.'); + Request::swap($this->configAjaxRequestMock('onTest', 'p:artial')); $theme = Theme::load('test'); @@ -232,12 +227,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage The partial 'partial' is not found. - */ public function testAjaxPartialNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('The partial \'partial\' is not found.'); + Request::swap($this->configAjaxRequestMock('onTest', 'partial')); $theme = Theme::load('test'); @@ -255,7 +249,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -272,7 +266,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -289,7 +283,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(2, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -303,7 +297,7 @@ class ControllerTest extends TestCase $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-component')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -331,7 +325,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-components')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('firstAlias', $page->components); $this->assertArrayHasKey('secondAlias', $page->components); @@ -363,19 +357,18 @@ ESC; $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); $this->assertEquals('page', $content['ajax-result']); } - /** - * @expectedException October\Rain\Exception\SystemException - * @expectedExceptionMessage is not registered for the component - */ public function testComponentClassNotFound() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessageMatches('/is\snot\sregistered\sfor\sthe\scomponent/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-component-class')->getContent(); @@ -395,7 +388,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -421,7 +414,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class-alias')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('someAlias', $page->components); $component = $page->components['someAlias']; diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index 9bbcd3247..f4eeda27a 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -7,7 +7,7 @@ class RouterTest extends TestCase { protected static $theme = null; - public function setUp() + public function setUp() : void { parent::setUp(); @@ -43,7 +43,7 @@ class RouterTest extends TestCase $this->assertFalse($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); /* @@ -52,7 +52,7 @@ class RouterTest extends TestCase $value = $method->invoke($router); $this->assertTrue($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); } diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index a45087ede..07a15f2db 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; class ThemeTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -44,7 +44,7 @@ class ThemeTest extends TestCase $pageCollection = $theme->listPages(); $pages = array_values($pageCollection->all()); - $this->assertInternalType('array', $pages); + $this->assertIsArray($pages); $expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages'); $this->assertCount($expectedPageNum, $pages); @@ -63,12 +63,11 @@ class ThemeTest extends TestCase $this->assertEquals('test', $activeTheme->getDirName()); } - /** - * @expectedException \October\Rain\Exception\SystemException - * @expectedExceptionMessage The active theme is not set. - */ public function testNoActiveTheme() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessage('The active theme is not set.'); + Config::set('cms.activeTheme', null); Theme::getActiveTheme(); } diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 79c03c443..9c316c032 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; class AttachManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index 52cb62fdf..d50debdb6 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e151d2304..e850b7752 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 6d87dbd3a..8fca6ff06 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index a8f354a65..e36460255 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Models\DeferredBinding; class DeferredBindingTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 64559dbc1..9a1def095 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class HasManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -63,8 +63,6 @@ class HasManyModelTest extends PluginTestCase public function testGetRelationValue() { - $this->markTestSkipped('Marked as \'skipped\' for further investigation'); - Model::unguard(); $author = Author::create(['name' => 'Stevie']); $post1 = Post::create(['title' => "First post", 'author_id' => $author->id]); diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php new file mode 100644 index 000000000..85ac947f2 --- /dev/null +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -0,0 +1,54 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $country = Country::create(['name' => 'Australia']); + $author1 = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $author2 = Author::create(['name' => 'Louie', 'email' => 'louie@email.tld']); + $post1 = Post::create(['title' => "First post", 'description' => "Yay!!"]); + $post2 = Post::create(['title' => "Second post", 'description' => "Woohoo!!"]); + $post3 = Post::create(['title' => "Third post", 'description' => "Yipiee!!"]); + $post4 = Post::make(['title' => "Fourth post", 'description' => "Hooray!!"]); + Model::reguard(); + + // Set data + $author1->country = $country; + $author2->country = $country; + + $author1->posts = new Collection([$post1, $post2]); + $author2->posts = new Collection([$post3, $post4]); + + $author1->save(); + $author2->save(); + + $country = Country::with([ + 'posts' + ])->find($country->id); + + $this->assertEquals([ + $post1->id, + $post2->id, + $post3->id, + $post4->id + ], $country->posts->pluck('id')->toArray()); + } +} diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index 7a5e30157..e6bd0f45e 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Phone; class HasOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php new file mode 100644 index 000000000..91be83b60 --- /dev/null +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -0,0 +1,39 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $phone = Phone::create(['number' => '08 1234 5678']); + $author = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $user = User::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + Model::reguard(); + + // Set data + $author->phone = $phone; + $author->user = $user; + $author->save(); + + $user = User::with([ + 'phone' + ])->find($user->id); + + $this->assertEquals($phone->id, $user->phone->id); + } +} diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 492b39505..16bf07c1d 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; class ModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -23,12 +23,11 @@ class ModelTest extends PluginTestCase $this->assertEquals(1, $post->id); } - /** - * @expectedException \Illuminate\Database\Eloquent\MassAssignmentException - * @expectedExceptionMessage title - */ public function testGuardedAttribute() { + $this->expectException(\Illuminate\Database\Eloquent\MassAssignmentException::class); + $this->expectExceptionMessageMatches('/title/'); + Post::create(['title' => 'Hi!', 'slug' => 'authenticity']); } } diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index e9b77aced..a7ba3041c 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class MorphManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index 4051e0b0c..e8c6f60fd 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\Meta; class MorphOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index 962f2d658..b0eb37ff7 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\EventLog; class MorphToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index b2f0ae019..49d46764c 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategoryNested; class NestedTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 0484409d4..9d6d61114 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\NullablePost; class NullableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index afee0c93d..c8fa19a54 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\RevisionablePost; class RevisionableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index fac6a48f3..c22b11d16 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategorySimple; class SimpleTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -165,12 +165,11 @@ class SimpleTreeModelTest extends PluginTestCase ], $array); } - /** - * @expectedException \Exception - * @expectedExceptionMessage Column mismatch in listsNested method - */ public function testListsNestedUnknownColumn() { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Column mismatch in listsNested method'); + CategorySimple::listsNested('custom_name', 'id'); } diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index f1f7aac71..22cad1837 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\SluggablePost; class SluggableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index 23a0ef436..fc5212433 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -10,7 +10,7 @@ use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; class SoftDeleteModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index ce1f4e382..f5ddb55b7 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\ValidationPost; class ValidationModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -13,11 +13,10 @@ class ValidationModelTest extends PluginTestCase $this->runPluginRefreshCommand('Database.Tester'); } - /** - * @expectedException October\Rain\Database\ModelException - */ public function testUniqueTableValidation() { + $this->expectException(\October\Rain\Database\ModelException::class); + $post = ValidationPost::create([ 'title' => 'This is a new post', 'slug' => 'post-1', diff --git a/tests/unit/system/AliasesTest.php b/tests/unit/system/AliasesTest.php new file mode 100644 index 000000000..8d389cdcc --- /dev/null +++ b/tests/unit/system/AliasesTest.php @@ -0,0 +1,22 @@ +assertTrue(class_exists('Illuminate\Support\Facades\Input')); + $this->assertInstanceOf( + \October\Rain\Support\Facades\Input::class, + new \Illuminate\Support\Facades\Input() + ); + } + + public function testHtmlDumperAlias() + { + $this->assertTrue(class_exists('Illuminate\Support\Debug\HtmlDumper')); + $this->assertInstanceOf( + \Symfony\Component\VarDumper\Dumper\HtmlDumper::class, + new \Illuminate\Support\Debug\HtmlDumper() + ); + } +} diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 43410cc77..14dc6f54b 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Datasource\FileDatasource; class CmsThemeTemplateFixture extends Model { - protected $fillable = ['*']; + protected $guarded = []; public $timestamps = false; @@ -30,7 +30,7 @@ class AutoDatasourceTest extends PluginTestCase */ public $datasource; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -73,7 +73,7 @@ class AutoDatasourceTest extends PluginTestCase ]); } - public function tearDown() + public function tearDown(): void { foreach ($this->fixtures as $fixture) { $fixture->delete(); diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index c9fed4a04..5c5d313bd 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -5,7 +5,7 @@ use System\Classes\CombineAssets; class CombineAssetsTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -24,10 +24,10 @@ class CombineAssetsTest extends TestCase * Supported file extensions should exist */ $jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions'); - $this->assertInternalType('array', $jsExt); + $this->assertIsArray($jsExt); $cssExt = self::getProtectedProperty($combiner, 'cssExtensions'); - $this->assertInternalType('array', $cssExt); + $this->assertIsArray($cssExt); /* * Check service methods diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index 48a82ba3e..f4ee00a32 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\MarkupManager; class MarkupManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 35e561594..54c4a6a3e 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -4,7 +4,7 @@ use System\Classes\MediaLibrary; class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine { - protected function tearDown() + protected function tearDown() : void { $this->removeMedia(); parent::tearDown(); @@ -66,7 +66,7 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine public function testValidPathsOnValidatePath($path) { $result = MediaLibrary::validatePath($path); - $this->assertInternalType('string', $result); + $this->assertIsString($result); } public function testListFolderContents() diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index 4b20f8509..c963f2dd6 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -5,7 +5,7 @@ class PluginManagerTest extends TestCase { public $manager; - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index b9934d3e5..ba163d18c 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\VersionManager; class VersionManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -100,8 +100,8 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]); - $this->assertInternalType('array', $comments); - $this->assertInternalType('array', $scripts); + $this->assertIsArray($comments); + $this->assertIsArray($scripts); $this->assertEquals($expectedComments, $comments); $this->assertEquals($expectedScripts, $scripts); diff --git a/tests/unit/system/console/OctoberEnvTest.php b/tests/unit/system/console/OctoberEnvTest.php index 6170cc739..184c3e68d 100644 --- a/tests/unit/system/console/OctoberEnvTest.php +++ b/tests/unit/system/console/OctoberEnvTest.php @@ -13,7 +13,7 @@ class OctoberEnvTest extends TestCase /** @var string Stores the original config path from the app container */ public static $origConfigPath; - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -30,59 +30,32 @@ class OctoberEnvTest extends TestCase // Check environment file $envFile = file_get_contents(base_path('.env')); - // Forward compatible assertions - // @TODO: Use only `assertStringContainsString` after L6 upgrade - - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('APP_DEBUG=true', $envFile); - $this->assertStringContainsString('APP_URL=https://localhost', $envFile); - $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); - $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); - $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); - $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertStringContainsString('DB_PORT=3306', $envFile); - } else { - $this->assertContains('APP_DEBUG=true', $envFile); - $this->assertContains('APP_URL=https://localhost', $envFile); - $this->assertContains('DB_CONNECTION=mysql', $envFile); - $this->assertContains('DB_DATABASE="data#base"', $envFile); - $this->assertContains('DB_USERNAME="teal\'c"', $envFile); - $this->assertContains('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertContains('DB_PORT=3306', $envFile); - } + $this->assertStringContainsString('APP_DEBUG=true', $envFile); + $this->assertStringContainsString('APP_URL=https://localhost', $envFile); + $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); + $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); + $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); + $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); + $this->assertStringContainsString('DB_PORT=3306', $envFile); // Check app.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/app.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } else { - $this->assertContains('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertContains('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } + $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); + $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); // Check database.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/database.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } else { - $this->assertContains('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertContains('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertContains('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertContains('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } + $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); + $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); + // Both the following configurations had values in the original config, they should be stripped out once + // the .env file is generated. + $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); + $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); } - protected function tearDown() + protected function tearDown(): void { $this->tearDownConfigFixtures(); $this->restoreEnvFile(); diff --git a/tests/unit/system/traits/AssetMakerTest.php b/tests/unit/system/traits/AssetMakerTest.php index 352d66107..606df9562 100644 --- a/tests/unit/system/traits/AssetMakerTest.php +++ b/tests/unit/system/traits/AssetMakerTest.php @@ -10,7 +10,7 @@ class AssetMakerTest extends TestCase { private $stub; - public function setUp() + public function setUp() : void { $this->createApplication(); $this->stub = new AssetMakerStub();