diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index af05451d1..65a0f4646 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,18 +6,25 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHPCS + name: PHP steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP and PHP Code Sniffer - uses: shivammathur/setup-php@v1 + - name: Install PHP + uses: shivammathur/setup-php@master with: - php-version: '7.3' - tools: phpcs - - name: Setup problem matcher for PHPCS - run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" + 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 - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + ./vendor/bin/phpcs --colors -nq --report="full" --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 ff209d0b5..3de3f6a97 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,16 +9,23 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHPCS + name: PHP steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP and PHP Code Sniffer - uses: shivammathur/setup-php@v1 + - name: Install PHP + uses: shivammathur/setup-php@master with: - php-version: '7.3' - tools: phpcs - - name: Setup problem matcher for PHPCS - run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" + 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 - name: Run code quality checks - run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: ./vendor/bin/phpcs --colors -nq --report="full" --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 new file mode 100644 index 000000000..9d0068a0a --- /dev/null +++ b/.github/workflows/frontend-tests.yaml @@ -0,0 +1,24 @@ +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 deleted file mode 100644 index 5c80b26d2..000000000 --- a/.github/workflows/matchers/phpcs-matcher.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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 a27246d97..072b795da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,56 +8,37 @@ 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.2', '7.3', '7.4'] + phpVersions: ['7.1', '7.2', '7.3', '7.4'] fail-fast: false - name: Unit Tests / PHP ${{ matrix.phpVersions }} + name: PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@v1 + uses: shivammathur/setup-php@master with: php-version: ${{ matrix.phpVersions }} - 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- + extension-csv: mbstring, intl, gd, xml, sqlite - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - - name: Run post-update Composer scripts - run: php artisan package:discover - - name: Reset October modules + - name: Reset October modules and library run: | git reset --hard HEAD - composer dumpautoload + 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 - 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 --prepend ./vendor/october/rain/src/Support/helpers.php + ./vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 63a1b61d7..4cd08cf9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,24 @@ -# Composer ignores +/bootstrap/compiled.php /vendor composer.phar -composer.lock - -# Framework ignores +.DS_Store +.idea .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 - -# Editor ignores -nbproject -.idea -.vscode -_ide_helper.php - -# Other ignores -.DS_Store +selenium.php +composer.lock package-lock.json /node_modules +_ide_helper.php + +# for netbeans +nbproject diff --git a/artisan b/artisan index df630d0d6..961e94d0b 100644 --- a/artisan +++ b/artisan @@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); +$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); +exit($status); \ No newline at end of file diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index 6533c54ca..b980622d7 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -35,3 +35,20 @@ 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 9542fc5a1..eb4f8fe37 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "october/october", - "description": "October CMS", + "description": "OctoberCMS", "homepage": "https://octobercms.com", "type": "project", "keywords": ["october", "cms", "octobercms", "laravel"], @@ -24,34 +24,37 @@ } ], "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.2", - "october/rain": "dev-develop as 1.0", - "october/system": "dev-develop", - "october/backend": "dev-develop", - "october/cms": "dev-develop", - "laravel/framework": "~6.0", + "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", "wikimedia/composer-merge-plugin": "1.4.1" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.0", - "fzaninotto/faker": "~1.9", + "fzaninotto/faker": "~1.7", + "phpunit/phpunit": "~6.5", + "phpunit/phpunit-selenium": "~1.2", + "meyfa/phpunit-assert-gd": "1.1.0", "squizlabs/php_codesniffer": "3.*", - "php-parallel-lint/php-parallel-lint": "^1.0", - "meyfa/phpunit-assert-gd": "^2.0.0", - "dms/phpunit-arraysubset-asserts": "^0.1.0" + "php-parallel-lint/php-parallel-lint": "^1.0" }, "autoload-dev": { "classmap": [ "tests/concerns/InteractsWithAuthentication.php", "tests/fixtures/backend/models/UserFixture.php", "tests/TestCase.php", + "tests/UiTestCase.php", "tests/PluginTestCase.php" ] }, @@ -63,21 +66,12 @@ "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.2" + "php": "7.0.8" } }, "minimum-stability": "dev", diff --git a/config/app.php b/config/app.php index 56e4959ab..23aad8473 100644 --- a/config/app.php +++ b/config/app.php @@ -111,6 +111,21 @@ 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 @@ -129,26 +144,6 @@ 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 a06bc7586..70f1420c6 100644 --- a/config/database.php +++ b/config/database.php @@ -116,7 +116,6 @@ return [ 'redis' => [ - 'client' => 'predis', 'cluster' => false, 'default' => [ diff --git a/config/develop.php b/config/develop.php index 2c1cbc642..cd4aee7d7 100644 --- a/config/develop.php +++ b/config/develop.php @@ -20,27 +20,5 @@ 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 36d9a0dfa..4d843013c 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", "ftp", "sftp", "s3", "rackspace" + | Supported: "local", "s3", "rackspace" | */ diff --git a/config/hashing.php b/config/hashing.php deleted file mode 100644 index 842577087..000000000 --- a/config/hashing.php +++ /dev/null @@ -1,52 +0,0 @@ - '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 deleted file mode 100644 index 900d48123..000000000 --- a/config/logging.php +++ /dev/null @@ -1,91 +0,0 @@ - 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 4c471d568..7c2332d2f 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", - | "postmark", "sparkpost", "log", "array" + | "sparkpost", "log", "array" | */ diff --git a/config/services.php b/config/services.php index d53643b03..c2d453065 100644 --- a/config/services.php +++ b/config/services.php @@ -24,10 +24,6 @@ return [ 'secret' => '', ], - 'postmark' => [ - 'token' => '', - ], - 'ses' => [ 'key' => '', 'secret' => '', diff --git a/index.php b/index.php index 9c4d23a06..ba43df3ec 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::class); +$kernel = $app->make('Illuminate\Contracts\Http\Kernel'); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 4c3825dd0..b0a5026f8 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -80,7 +80,6 @@ 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 @@ -200,7 +199,6 @@ 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 b0278ccdc..c146050b6 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 League\Csv\EscapeFormula as CsvEscapeFormula; +use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; use Exception; @@ -624,7 +624,9 @@ class ImportExportController extends ControllerBehavior $csv->setDelimiter($options['delimiter']); $csv->setEnclosure($options['enclosure']); $csv->setEscape($options['escape']); - $csv->addFormatter(new CsvEscapeFormula()); + + // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) + $formatter = new CsvEscapeFormula(); /* * Add headers @@ -660,6 +662,9 @@ 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 d9782a267..f2b6efc68 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 5d4231af2..15cfd2a3f 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php index 78c561441..7be5f9885 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::unguarded(function () { - $this->call('Backend\Database\Seeds\SeedSetupAdmin'); - }); + Eloquent::unguard(); + + $this->call('Backend\Database\Seeds\SeedSetupAdmin'); } } diff --git a/modules/backend/formwidgets/Sensitive.php b/modules/backend/formwidgets/Sensitive.php deleted file mode 100644 index a28a8d60b..000000000 --- a/modules/backend/formwidgets/Sensitive.php +++ /dev/null @@ -1,117 +0,0 @@ -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 deleted file mode 100644 index c8ac9378a..000000000 --- a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index 69251304c..000000000 --- a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 deleted file mode 100644 index 5717658f2..000000000 --- a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less +++ /dev/null @@ -1,10 +0,0 @@ -@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 deleted file mode 100644 index b913070d7..000000000 --- a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm +++ /dev/null @@ -1,41 +0,0 @@ -
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 e6a767e80..1a03a813b 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 League\Csv\EscapeFormula as CsvEscapeFormula; +use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; @@ -112,7 +112,8 @@ abstract class ExportModel extends Model $csv->setEscape($options['escape']); } - $csv->addFormatter(new CsvEscapeFormula()); + // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) + $formatter = new CsvEscapeFormula(); /* * Add headers @@ -127,6 +128,10 @@ 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 09c1bf426..12f937a16 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -5,7 +5,6 @@ use Str; use Lang; use Model; use League\Csv\Reader as CsvReader; -use League\Csv\Statement as CsvStatement; /** * Model used for importing data @@ -109,6 +108,11 @@ 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']); } @@ -121,11 +125,15 @@ abstract class ImportModel extends Model $reader->setEscape($options['escape']); } + if ($options['firstRowTitles']) { + $reader->setOffset(1); + } + if ( $options['encoding'] !== null && - $reader->supportsStreamFilter() + $reader->isActiveStreamFilter() ) { - $reader->addStreamFilter(sprintf( + $reader->appendStreamFilter(sprintf( '%s%s:%s', TranscodeFilter::FILTER_NAME, strtolower($options['encoding']), @@ -133,19 +141,8 @@ 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 = $stmt->process($reader); + $contents = $reader->fetch(); foreach ($contents as $row) { $result[] = $this->processImportRow($row, $matches); } diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 46259792b..650a1378e 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|min:4|confirmed', - 'password_confirmation' => 'required_with:password|min:4' + 'password' => 'required:create|between:4,255|confirmed', + 'password_confirmation' => 'required_with:password|between:4,255' ]; /** diff --git a/modules/backend/routes.php b/modules/backend/routes.php index 0268f7efc..81904802f 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 0bfc2eba8..12b79ea35 100644 --- a/modules/cms/classes/Asset.php +++ b/modules/cms/classes/Asset.php @@ -287,14 +287,25 @@ class Asset extends Extendable $directory = $this->theme->getPath() . '/' . $this->dirName . '/'; $filePath = $directory . $fileName; - $resolvedPath = resolve_path($filePath); + $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); + } // Limit paths to those under the theme's assets directory - if (!starts_with($resolvedPath, $directory)) { + if (!starts_with($path, $directory)) { return false; } - return $resolvedPath; + return $path; } /** diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index fca40ff67..5705515bc 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -316,8 +316,7 @@ class CmsCompoundObject extends CmsObject self::$objectComponentPropertyMap = $objectComponentMap; - $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10)); - Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt); + Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10)); 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 714650150..798a065a0 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -227,16 +227,7 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->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; + return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; } /** diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index 2105789ac..ffc7afc73 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -1,6 +1,5 @@ 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 b747a20e5..dd6687637 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -224,8 +224,7 @@ class CodeParser $cached = $this->getCachedInfo() ?: []; $cached[$this->filePath] = $cacheItem; - $expiresAt = now()->addMinutes(1440); - Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt); + Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440); self::$cache[$this->filePath] = $result; } diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 4f75656f1..0c033fd81 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -127,11 +127,10 @@ class Router : $fileName; $key = $this->getUrlListCacheKey(); - $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); Cache::put( $key, base64_encode(serialize($urlList)), - $expiresAt + Config::get('cms.urlCacheTtl', 1) ); } } @@ -252,8 +251,7 @@ class Router $this->urlMap = $map; if ($cacheable) { - $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); - Cache::put($key, base64_encode(serialize($map)), $expiresAt); + Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1)); } return false; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index 6ce06a1c5..a5306c897 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -158,8 +158,7 @@ class Theme if ($checkDatabase && App::hasDatabase()) { try { try { - $expiresAt = now()->addMinutes(1440); - $dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () { + $dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () { return Parameter::applyKey(self::ACTIVE_KEY)->value('value'); }); } diff --git a/modules/cms/composer.json b/modules/cms/composer.json index 9e4cfa70e..49e2d944b 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/cms/routes.php b/modules/cms/routes.php index 1aaf5ec5d..76f35c4b3 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 ebe0c050f..210922cfa 100644 --- a/modules/cms/traits/UrlMaker.php +++ b/modules/cms/traits/UrlMaker.php @@ -190,8 +190,7 @@ trait UrlMaker 'mtime' => @File::lastModified($filePath) ]; - $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440)); - Cache::put($key, serialize($cached), $expiresAt); + Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440)); return static::$urlPageName = $baseFileName; } diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php index f974bb8e2..242eb23a5 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 Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Illuminate\Support\Debug\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 eb5b6c613..9fc858f39 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -94,7 +94,6 @@ 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 d6364cd13..bcb2c78b2 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,6 +16,7 @@ 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, @@ -29,6 +30,7 @@ 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, /* @@ -40,7 +42,6 @@ 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, @@ -51,7 +52,6 @@ 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,12 +60,4 @@ 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 17ec7c2bd..50a03c292 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 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 Assetic\Asset\FileAsset; +use Assetic\Asset\AssetCache; +use Assetic\Asset\AssetCollection; +use Assetic\Factory\AssetFactory; +use October\Rain\Parse\Assetic\FilesystemCache; 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\Assetic\Filter\JavascriptImporter); + $this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter); /* * Register CSS filters */ - $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); + $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); /* * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify); + $this->registerFilter('js', new \Assetic\Filter\JSMinFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); } /* diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 40808854e..50a6cc8f9 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -134,11 +134,10 @@ 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)), - $expiresAt + Config::get('cms.storage.media.ttl', 10) ); } diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 1b9c3618f..7e4779de1 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -29,6 +29,11 @@ class UpdateManager { use \October\Rain\Support\Traits\Singleton; + /** + * @var array The notes for the current operation. + */ + protected $notes = []; + /** * @var \Illuminate\Console\OutputStyle */ @@ -340,13 +345,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; } @@ -398,13 +403,13 @@ class UpdateManager */ public function migrateModule($module) { - if (isset($this->notesOutput)) { - $this->migrator->setOutput($this->notesOutput); - } + $this->migrator->run(base_path() . '/modules/' . strtolower($module) . '/database/migrations'); $this->note($module); - $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); + foreach ($this->migrator->getNotes() as $note) { + $this->note(' - ' . $note); + } return $this; } @@ -513,9 +518,13 @@ class UpdateManager $this->note($name); - $this->versionManager->setNotesOutput($this->notesOutput); + $this->versionManager->resetNotes()->setNotesOutput($this->notesOutput); - $this->versionManager->updatePlugin($plugin); + if ($this->versionManager->updatePlugin($plugin) !== false) { + foreach ($this->versionManager->getNotes() as $note) { + $this->note($note); + } + } return $this; } @@ -704,8 +713,7 @@ class UpdateManager } $data = $this->requestServerData($type . '/popular'); - $expiresAt = now()->addMinutes(60); - Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt); + Cache::put($cacheKey, base64_encode(serialize($data)), 60); foreach ($data as $product) { $code = array_get($product, 'code', -1); @@ -794,11 +802,35 @@ 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 11d425d9a..77127cf12 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -29,6 +29,12 @@ 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 */ @@ -420,7 +426,6 @@ class VersionManager * Execute the database PHP script */ $updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script; - $this->updater->packDown($updateFile); Db::table('system_plugin_history') @@ -503,11 +508,35 @@ 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 @@ -521,7 +550,8 @@ class VersionManager } /** - * Extract script and comments from version details + * @param $details + * * @return array */ protected function extractScriptsAndComments($details): array @@ -536,8 +566,7 @@ 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 81d59a4dc..5bebae7be 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index 43f2079bf..cc735c2c4 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_CONNECTION' => 'default', + 'QUEUE_DRIVER' => 'default', ], 'mail' => [ 'MAIL_DRIVER' => 'driver', diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index 200dcd221..b826fe305 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -17,6 +17,7 @@ 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 719dff20e..7ba29dea7 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -2,8 +2,6 @@ use Lang; use Flash; -use Config; -use Request; use Backend; use BackendMenu; use System\Classes\SettingsManager; @@ -141,22 +139,6 @@ 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 @@ -187,22 +169,10 @@ class Settings extends Controller } /** - * 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 + * Locates a setting item for a module or plugin */ - protected function findSettingItem($author = null, $plugin = null, $code = null) + protected function findSettingItem($author, $plugin, $code) { - if (is_null($author) || is_null($plugin)) { - [$author, $plugin, $code] = $this->guessSettingItem(); - } - $manager = SettingsManager::instance(); $moduleOwner = $author; @@ -217,23 +187,4 @@ 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 7dec41339..f2f924b13 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::unguarded(function () { - $this->call('System\Database\Seeds\SeedSetupMailLayouts'); - }); + Eloquent::unguard(); + + $this->call('System\Database\Seeds\SeedSetupMailLayouts'); } } diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index ad6561c9e..edc036dd0 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -32,7 +32,6 @@ 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.', @@ -40,22 +39,9 @@ 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.', @@ -64,18 +50,6 @@ 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.', @@ -91,7 +65,6 @@ 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.', @@ -109,13 +82,11 @@ 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 d49851cba..c2457ec38 100644 --- a/modules/system/models/mailsetting/fields.yaml +++ b/modules/system/models/mailsetting/fields.yaml @@ -79,7 +79,6 @@ tabs: smtp_password: label: system::lang.mail.smtp_password tab: system::lang.mail.general - type: sensitive span: right trigger: action: show @@ -108,7 +107,6 @@ 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 @@ -118,7 +116,6 @@ 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 @@ -138,7 +135,6 @@ 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 @@ -158,7 +154,6 @@ 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 96951f747..7b63edb43 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -15,7 +15,9 @@ 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, @@ -34,7 +36,5 @@ 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 de01655c0..08cd19d58 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,6 +8,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" + syntaxCheck="false" > diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 1d4a66f00..6618b17e0 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('backend.auth', function ($app) { + $app->singleton('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() : void + public function setUp() { /* * 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() : void + public function tearDown() { $this->flushModelEventListeners(); parent::tearDown(); diff --git a/tests/README.md b/tests/README.md index 0e985d79b..a9e7b78b5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`. +Plugin unit tests can be performed by running `phpunit` in the base plugin directory. ### 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(): void + public function setUp() { 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(): void + public function tearDown() { parent::tearDown(); @@ -96,10 +96,39 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests -Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation. +Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. ### Functional tests -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. +Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: -For information on installing and setting up your October CMS install to run functional tests, please review the [README](https://github.com/rainlab/dusk-plugin/blob/master/README.md) for the plugin. +- 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 5ed3e1296..e7e53fd1a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,3 +19,14 @@ $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 5f5c3c8c7..a7950f230 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['backend.auth']->setUser($user); + $this->app['auth']->setUser($user); } /** @@ -66,7 +66,7 @@ trait InteractsWithAuthentication */ protected function isAuthenticated($guard = null) { - return $this->app->make('backend.auth')->guard($guard)->check(); + return $this->app->make('auth')->guard($guard)->check(); } /** @@ -78,7 +78,7 @@ trait InteractsWithAuthentication */ public function assertAuthenticatedAs($user, $guard = null) { - $expected = $this->app->make('backend.auth')->guard($guard)->user(); + $expected = $this->app->make('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('backend.auth')->guard($guard)->getProvider(); + $provider = $this->app->make('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 2d9656fee..05e9a039f 100644 --- a/tests/fixtures/plugins/database/tester/models/Author.php +++ b/tests/fixtures/plugins/database/tester/models/Author.php @@ -20,7 +20,6 @@ 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 deleted file mode 100644 index e7bb583c5..000000000 --- a/tests/fixtures/plugins/database/tester/models/Country.php +++ /dev/null @@ -1,35 +0,0 @@ - [ - '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 130120e9c..867992c2c 100644 --- a/tests/fixtures/plugins/database/tester/models/User.php +++ b/tests/fixtures/plugins/database/tester/models/User.php @@ -17,19 +17,6 @@ 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 d6277e525..6466e4e13 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php @@ -11,7 +11,6 @@ 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 deleted file mode 100644 index 7fc85ae54..000000000 --- a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php +++ /dev/null @@ -1,23 +0,0 @@ -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 590bb7892..613fc5337 100644 --- a/tests/fixtures/plugins/database/tester/updates/version.yaml +++ b/tests/fixtures/plugins/database/tester/updates/version.yaml @@ -9,4 +9,3 @@ - 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 b8bbe4e13..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -1 +0,0 @@ -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 54e00dea3..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -1 +0,0 @@ -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 deleted file mode 100644 index bc27e099c..000000000 --- a/tests/fixtures/themes/test/assets/js/subdir/script1.js +++ /dev/null @@ -1 +0,0 @@ -console.log('subdir/script1.js'); diff --git a/tests/functional/backend/AuthTest.php b/tests/functional/backend/AuthTest.php new file mode 100644 index 000000000..4c1fe925d --- /dev/null +++ b/tests/functional/backend/AuthTest.php @@ -0,0 +1,90 @@ +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 new file mode 100644 index 000000000..5ff2a0a9e --- /dev/null +++ b/tests/functional/cms/TemplateTest.php @@ -0,0 +1,143 @@ +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 new file mode 100644 index 000000000..c8043149c --- /dev/null +++ b/tests/functional/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./ + + + \ No newline at end of file diff --git a/tests/resources/patches/php-generator-7.php b/tests/resources/patches/php-generator-7.php new file mode 100644 index 000000000..677680d69 --- /dev/null +++ b/tests/resources/patches/php-generator-7.php @@ -0,0 +1,1185 @@ + + * + * 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 65dd5e735..6bb9f9684 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(): void + public function setUp() { $this->createApplication(); @@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase ]); } - public function tearDown(): void + public function tearDown() { AuthManager::forgetInstance(); } diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index f63645198..04ca2f183 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->assertIsArray($items); + $this->assertInternalType('array', $items); $this->assertArrayHasKey('posts', $items); $this->assertArrayHasKey('categories', $items); - $this->assertIsObject($items['posts']); + $this->assertInternalType('object', $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->assertIsArray($items['posts']->permissions); + $this->assertInternalType('array', $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->assertIsArray($items); + $this->assertInternalType('array', $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->assertIsArray($items); + $this->assertInternalType('array', $items); $this->assertArrayHasKey('foo', $items); - $this->assertIsObject($items['foo']); + $this->assertInternalType('object', $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->assertIsArray($items['foo']->permissions); + $this->assertInternalType('array', $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 176690b78..b4ee7c0c6 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->assertStringContainsString('file1.js', $assets[0]); - $this->assertStringContainsString('file2.js', $assets[1]); + $this->assertContains('file1.js', $assets[0]); + $this->assertContains('file2.js', $assets[1]); } public function testDecompileMissingFile() diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 9a290b509..1369a34d1 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/AssetTest.php b/tests/unit/cms/classes/AssetTest.php deleted file mode 100644 index 13519a161..000000000 --- a/tests/unit/cms/classes/AssetTest.php +++ /dev/null @@ -1,119 +0,0 @@ -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 ec82153f9..c66d31ee5 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() : void + public function setUp() { parent::setUp(); Model::clearBootedModels(); @@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase $theme = Theme::load('test'); $obj = TestCmsCompoundObject::load($theme, 'compound.htm'); - $this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code); + $this->assertContains("\$controller->data['something'] = 'some value'", $obj->code); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertIsArray($obj->settings); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']); + $this->assertInternalType('array', $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->assertIsArray($properties); + $this->assertInternalType('array', $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->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $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->assertIsArray($obj->settings); + $this->assertInternalType('array', $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->assertIsArray($obj->settings['components']['viewBag']); + $this->assertInternalType('array', $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 dc5172add..978745ae1 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 2c65761b4..95d4f50d9 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -148,11 +148,12 @@ 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'; @@ -163,11 +164,12 @@ 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'; @@ -178,11 +180,12 @@ 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'; @@ -193,11 +196,12 @@ 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'; @@ -262,12 +266,11 @@ 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 67d8f30e9..43eb34a84 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() : void + public function setUp() { parent::setup(); @@ -41,7 +41,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $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->assertIsArray($info); + $this->assertInternalType('array', $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->assertIsArray($info); + $this->assertInternalType('array', $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->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -110,14 +110,13 @@ 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->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('parser', $info['source']); $this->assertFileExists($info['filePath']); } @@ -132,7 +131,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -158,7 +157,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -192,7 +191,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -221,7 +220,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -256,7 +255,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -285,7 +284,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $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 152367b8d..a12255580 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 1496055a3..02b2abc34 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() : void + public function setUp() { 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->assertIsString($content); + $this->assertInternalType('string', $content); $this->assertEquals('

Page not found

', $content); } @@ -83,15 +83,16 @@ class ControllerTest extends TestCase $response = $controller->run('/'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertIsString($content); + $this->assertInternalType('string', $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'); @@ -145,11 +146,12 @@ 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(); @@ -191,11 +193,12 @@ 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'); @@ -203,11 +206,12 @@ 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'); @@ -215,11 +219,12 @@ 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'); @@ -227,11 +232,12 @@ 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'); @@ -249,7 +255,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -266,7 +272,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -283,7 +289,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(2, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -297,7 +303,7 @@ class ControllerTest extends TestCase $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-component')->getContent(); - $page = self::getProtectedProperty($controller, 'page'); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -325,7 +331,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-components')->getContent(); - $page = self::getProtectedProperty($controller, 'page'); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('firstAlias', $page->components); $this->assertArrayHasKey('secondAlias', $page->components); @@ -357,18 +363,19 @@ ESC; $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $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(); @@ -388,7 +395,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class')->getContent(); - $page = $controller->getPage(); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -414,7 +421,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class-alias')->getContent(); - $page = $controller->getPage(); + $page = $this->readAttribute($controller, 'page'); $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 f4eeda27a..9bbcd3247 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() : void + public function setUp() { parent::setUp(); @@ -43,7 +43,7 @@ class RouterTest extends TestCase $this->assertFalse($value); $map = $property->getValue($router); - $this->assertIsArray($map); + $this->assertInternalType('array', $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->assertIsArray($map); + $this->assertInternalType('array', $map); $this->assertGreaterThanOrEqual(4, count($map)); } diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 07a15f2db..a45087ede 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() : void + public function setUp() { parent::setUp(); @@ -44,7 +44,7 @@ class ThemeTest extends TestCase $pageCollection = $theme->listPages(); $pages = array_values($pageCollection->all()); - $this->assertIsArray($pages); + $this->assertInternalType('array', $pages); $expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages'); $this->assertCount($expectedPageNum, $pages); @@ -63,11 +63,12 @@ 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 9c316c032..79c03c443 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index d50debdb6..52cb62fdf 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e850b7752..e151d2304 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 8fca6ff06..6d87dbd3a 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index e36460255..a8f354a65 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 9a1def095..64559dbc1 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() : void + public function setUp() { parent::setUp(); @@ -63,6 +63,8 @@ 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 deleted file mode 100644 index 85ac947f2..000000000 --- a/tests/unit/plugins/database/HasManyThroughModelTest.php +++ /dev/null @@ -1,54 +0,0 @@ -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 e6bd0f45e..7a5e30157 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php deleted file mode 100644 index 91be83b60..000000000 --- a/tests/unit/plugins/database/HasOneThroughModelTest.php +++ /dev/null @@ -1,39 +0,0 @@ -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 16bf07c1d..492b39505 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() : void + public function setUp() { parent::setUp(); @@ -23,11 +23,12 @@ 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 a7ba3041c..e9b77aced 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index e8c6f60fd..4051e0b0c 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index b0eb37ff7..962f2d658 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index 49d46764c..b2f0ae019 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 9d6d61114..0484409d4 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index c8fa19a54..afee0c93d 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index c22b11d16..fac6a48f3 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() : void + public function setUp() { parent::setUp(); @@ -165,11 +165,12 @@ 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 22cad1837..f1f7aac71 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index fc5212433..23a0ef436 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index f5ddb55b7..ce1f4e382 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() : void + public function setUp() { parent::setUp(); @@ -13,10 +13,11 @@ 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 deleted file mode 100644 index 8d389cdcc..000000000 --- a/tests/unit/system/AliasesTest.php +++ /dev/null @@ -1,22 +0,0 @@ -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 14dc6f54b..43410cc77 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 $guarded = []; + protected $fillable = ['*']; public $timestamps = false; @@ -30,7 +30,7 @@ class AutoDatasourceTest extends PluginTestCase */ public $datasource; - public function setUp(): void + public function setUp() { parent::setUp(); @@ -73,7 +73,7 @@ class AutoDatasourceTest extends PluginTestCase ]); } - public function tearDown(): void + public function tearDown() { foreach ($this->fixtures as $fixture) { $fixture->delete(); diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 5c5d313bd..c9fed4a04 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() : void + public function setUp() { parent::setUp(); @@ -24,10 +24,10 @@ class CombineAssetsTest extends TestCase * Supported file extensions should exist */ $jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions'); - $this->assertIsArray($jsExt); + $this->assertInternalType('array', $jsExt); $cssExt = self::getProtectedProperty($combiner, 'cssExtensions'); - $this->assertIsArray($cssExt); + $this->assertInternalType('array', $cssExt); /* * Check service methods diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index f4ee00a32..48a82ba3e 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 54c4a6a3e..35e561594 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() : void + protected function tearDown() { $this->removeMedia(); parent::tearDown(); @@ -66,7 +66,7 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine public function testValidPathsOnValidatePath($path) { $result = MediaLibrary::validatePath($path); - $this->assertIsString($result); + $this->assertInternalType('string', $result); } public function testListFolderContents() diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index c963f2dd6..4b20f8509 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() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index ba163d18c..b9934d3e5 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() : void + public function setUp() { parent::setUp(); @@ -100,8 +100,8 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]); - $this->assertIsArray($comments); - $this->assertIsArray($scripts); + $this->assertInternalType('array', $comments); + $this->assertInternalType('array', $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 184c3e68d..6170cc739 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(): void + protected function setUp() { parent::setUp(); @@ -30,32 +30,59 @@ class OctoberEnvTest extends TestCase // Check environment file $envFile = file_get_contents(base_path('.env')); - $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); + // 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); + } // Check app.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/app.php')); - $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); + 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); + } // Check database.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/database.php')); - $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); + 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); + } } - protected function tearDown(): void + protected function tearDown() { $this->tearDownConfigFixtures(); $this->restoreEnvFile(); diff --git a/tests/unit/system/traits/AssetMakerTest.php b/tests/unit/system/traits/AssetMakerTest.php index 606df9562..352d66107 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() : void + public function setUp() { $this->createApplication(); $this->stub = new AssetMakerStub();