Merge branch 'develop' into wip/image-resizing

This commit is contained in:
Luke Towers 2020-08-09 04:26:48 -06:00
commit b4dd25534e
119 changed files with 1385 additions and 2107 deletions

View File

@ -11,7 +11,7 @@ Thank you for your interest in contributing to the OctoberCMS project. We apprec
## Reporting a Security Vulnerability
Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities.
Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities. Please do not report security vulnerabilities on GitHub.
## Reporting an issue with OctoberCMS
@ -21,6 +21,8 @@ We work hard to process bugs that are reported, to assist with this please ensur
- **Summary**: Make sure your summary reflects what the problem is and where it is. Provide as much detail as possible, the more information we have to work with the more likely it is that your problem can be solved.
- **Installed build and plugins**: Please provide the build number of October CMS that is exhibiting the fault, and the version numbers of any installed and active plugins on your installation. You may retrieve this information by logging in to the Backend and navigating to *Settings* and then *Updates & Plugins*.
- **Reproduce steps**: Clearly mention the steps to reproduce the bug.
- **Expected behavior**: Describe how OctoberCMS should behave on above mentioned steps.
@ -61,7 +63,7 @@ We do our best to attend to all reported issues. If you have an important issue
>**NOTE:** Please don't use GitHub issues for suggesting a new feature. If you have a feature idea, the best place to suggest it is the [OctoberCMS website forum](https://octobercms.com/forum/chan/feature-requests).
Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [IRC](https://octobercms.com/chat) or [Slack](https://octobercms.slack.com).
Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [Discord](https://discord.gg/gEKgwSZ).
#### GitHub feature requests

View File

@ -6,25 +6,18 @@ on:
jobs:
codeQuality:
runs-on: ubuntu-latest
name: PHP
name: PHPCS
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
- name: Install PHP and PHP Code Sniffer
uses: shivammathur/setup-php@v1
with:
php-version: 7.2
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest
- name: Reset October modules and library
run: |
git reset --hard HEAD
rm -rf ./vendor/october/rain
wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip
unzip ./vendor/october/develop.zip -d ./vendor/october
mv ./vendor/october/library-develop ./vendor/october/rain
composer dump-autoload
php-version: '7.3'
tools: phpcs
- name: Setup problem matcher for PHPCS
run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json"
- name: Run code quality checks
run: |
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch
./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD)
phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD)

View File

@ -9,23 +9,16 @@ on:
jobs:
codeQuality:
runs-on: ubuntu-latest
name: PHP
name: PHPCS
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
- name: Install PHP and PHP Code Sniffer
uses: shivammathur/setup-php@v1
with:
php-version: 7.2
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest
- name: Reset October modules and library
run: |
git reset --hard HEAD
rm -rf ./vendor/october/rain
wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip
unzip ./vendor/october/develop.zip -d ./vendor/october
mv ./vendor/october/library-develop ./vendor/october/rain
composer dump-autoload
php-version: '7.3'
tools: phpcs
- name: Setup problem matcher for PHPCS
run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json"
- name: Run code quality checks
run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }})
run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }})

View File

@ -1,24 +0,0 @@
name: Tests
on:
push:
branches:
- master
- develop
pull_request:
jobs:
frontendTests:
runs-on: ubuntu-latest
name: JavaScript
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 8
- name: Install Node dependencies
run: npm install
- name: Run tests
run: npm run test

View File

@ -0,0 +1,23 @@
{
"problemMatcher": [
{
"owner": "phpcs",
"severity": "error",
"pattern": [
{
"regexp": "^<file name=\"(.*)\">$",
"file": 1
},
{
"regexp": "<error line=\"(\\d*)\" column=\"(\\d*)\" severity=\"(error|warning)\" message=\"(.*)\" source=\"(.*)(\"\\/>+)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}

View File

@ -8,37 +8,56 @@ on:
pull_request:
jobs:
frontendTests:
runs-on: ubuntu-latest
name: JavaScript
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 8
- name: Install Node dependencies
run: npm install
- name: Run tests
run: npm run test
phpUnitTests:
runs-on: ubuntu-latest
strategy:
max-parallel: 6
matrix:
phpVersions: ['7.1', '7.2', '7.3', '7.4']
phpVersions: ['7.2', '7.3', '7.4']
fail-fast: false
name: PHP ${{ matrix.phpVersions }}
name: Unit Tests / PHP ${{ matrix.phpVersions }}
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
uses: shivammathur/setup-php@v1
with:
php-version: ${{ matrix.phpVersions }}
extension-csv: mbstring, intl, gd, xml, sqlite
extensions: mbstring, intl, gd, xml, sqlite
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Set Composer cache
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache Composer dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest --no-scripts
- name: Reset October modules and library
- name: Run post-update Composer scripts
run: php artisan package:discover
- name: Reset October modules
run: |
git reset --hard HEAD
rm -rf ./vendor/october/rain
wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip
unzip ./vendor/october/develop.zip -d ./vendor/october
mv ./vendor/october/library-develop ./vendor/october/rain
composer dump-autoload
- name: Run post-update Composer scripts
run: |
php artisan october:util set build
php artisan package:discover
composer dumpautoload
- name: Run Linting and Tests
run: |
./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php .
./vendor/bin/phpunit
./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php

28
.gitignore vendored
View File

@ -1,24 +1,32 @@
/bootstrap/compiled.php
# Composer ignores
/vendor
composer.phar
.DS_Store
.idea
composer.lock
# Framework ignores
.env
.env.*.php
.env.php
selenium.php
/bootstrap/compiled.php
.phpunit.result.cache
# Hosting ignores
php_errors.log
nginx-error.log
nginx-access.log
nginx-ssl.access.log
nginx-ssl.error.log
php-errors.log
sftp-config.json
.ftpconfig
selenium.php
composer.lock
package-lock.json
/node_modules
# Editor ignores
nbproject
.idea
.vscode
_ide_helper.php
# for netbeans
nbproject
# Other ignores
.DS_Store
package-lock.json
/node_modules

View File

@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php';
|
*/
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
@ -48,4 +48,4 @@ $status = $kernel->handle(
$kernel->terminate($input, $status);
exit($status);
exit($status);

View File

@ -35,20 +35,3 @@ require $helperPath;
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Include The Compiled Class File
|--------------------------------------------------------------------------
|
| To dramatically increase your application's performance, you may use a
| compiled class file which contains all of the classes commonly used
| by a request. The Artisan "optimize" is used to create this file.
|
*/
$compiledPath = __DIR__.'/../storage/framework/compiled.php';
if (file_exists($compiledPath)) {
require $compiledPath;
}

View File

@ -1,6 +1,6 @@
{
"name": "october/october",
"description": "OctoberCMS",
"description": "October CMS",
"homepage": "https://octobercms.com",
"type": "project",
"keywords": ["october", "cms", "octobercms", "laravel"],
@ -24,37 +24,34 @@
}
],
"support": {
"paid": "https://octobercms.com/premium-support",
"issues": "https://github.com/octobercms/october/issues",
"forum": "https://octobercms.com/forum/",
"docs": "https://octobercms.com/docs/",
"irc": "irc://irc.freenode.net/october",
"source": "https://github.com/octobercms/october"
},
"require": {
"php": ">=7.0.8",
"ext-mbstring": "*",
"ext-openssl": "*",
"october/rain": "~1.0",
"october/system": "~1.0",
"october/backend": "~1.0",
"october/cms": "~1.0",
"laravel/framework": "~5.5.40",
"php": ">=7.2",
"october/rain": "dev-develop as 1.0",
"october/system": "dev-develop",
"october/backend": "dev-develop",
"october/cms": "dev-develop",
"laravel/framework": "~6.0",
"wikimedia/composer-merge-plugin": "1.4.1"
},
"require-dev": {
"fzaninotto/faker": "~1.7",
"phpunit/phpunit": "~6.5",
"phpunit/phpunit-selenium": "~1.2",
"meyfa/phpunit-assert-gd": "1.1.0",
"phpunit/phpunit": "^8.0|^9.0",
"fzaninotto/faker": "~1.9",
"squizlabs/php_codesniffer": "3.*",
"php-parallel-lint/php-parallel-lint": "^1.0"
"php-parallel-lint/php-parallel-lint": "^1.0",
"meyfa/phpunit-assert-gd": "^2.0.0",
"dms/phpunit-arraysubset-asserts": "^0.1.0"
},
"autoload-dev": {
"classmap": [
"tests/concerns/InteractsWithAuthentication.php",
"tests/fixtures/backend/models/UserFixture.php",
"tests/TestCase.php",
"tests/UiTestCase.php",
"tests/PluginTestCase.php"
]
},
@ -66,12 +63,21 @@
"post-update-cmd": [
"php artisan october:util set build",
"php artisan package:discover"
],
"test": [
"phpunit --stop-on-failure"
],
"lint": [
"parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ."
],
"sniff": [
"phpcs --colors -nq --report=\"full\" --extensions=\"php\""
]
},
"config": {
"preferred-install": "dist",
"platform": {
"php": "7.0.8"
"php": "7.2"
}
},
"minimum-stability": "dev",

View File

@ -111,21 +111,6 @@ return [
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
|
| Here you may configure the log settings for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Settings: "single", "daily", "syslog", "errorlog"
|
*/
'log' => 'single',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
@ -144,6 +129,26 @@ return [
'System\ServiceProvider',
]),
/*
|--------------------------------------------------------------------------
| Load automatically discovered packages
|--------------------------------------------------------------------------
|
| By default, October CMS disables the loading of discovered packages
| through Laravel's package discovery service, in order to allow packages
| used by plugins to be disabled if the plugin itself is disabled.
|
| Set this to `true` to enable automatic loading of these packages. This
| will result in packages being loaded, even if the plugin using them is
| disabled. This is NOT RECOMMENDED.
|
| Please note that packages defined in `app.providers` will still be loaded
| even if discovery is disabled.
|
*/
'loadDiscoveredPackages' => false,
/*
|--------------------------------------------------------------------------
| Class Aliases

View File

@ -116,6 +116,7 @@ return [
'redis' => [
'client' => 'predis',
'cluster' => false,
'default' => [

View File

@ -20,5 +20,27 @@ return [
*/
'decompileBackendAssets' => false,
/*
|--------------------------------------------------------------------------
| Allow deep-level symlinks
|--------------------------------------------------------------------------
|
| October CMS, by default, will allow symlinks within the first level of
| subdirectories. When this feature is enabled, the system will allow
| symlinks to be used at any directory level. This can be useful for
| symlinking individual plugins or themes.
|
| Please note that this has a negative effect on performance. This feature
| abides by "cms.restrictBaseDir" - if enabled, symlinks cannot point to
| resources outside of the root folder.
|
| true - allow symlinks at any level
|
| false - only allow symlinks at the first level of subdirectories (default)
|
*/
'allowDeepSymlinks' => false,
];

View File

@ -11,7 +11,7 @@ return [
| by the framework. A "local" driver, as well as a variety of cloud
| based drivers are available for your choosing. Just store away!
|
| Supported: "local", "s3", "rackspace"
| Supported: "local", "ftp", "sftp", "s3", "rackspace"
|
*/

52
config/hashing.php Normal file
View File

@ -0,0 +1,52 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => '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,
],
];

91
config/logging.php Normal file
View File

@ -0,0 +1,91 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => 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',
],
],
];

View File

@ -12,7 +12,7 @@ return [
| your application here. By default, Laravel is setup for SMTP mail.
|
| Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses",
| "sparkpost", "log", "array"
| "postmark", "sparkpost", "log", "array"
|
*/

View File

@ -24,6 +24,10 @@ return [
'secret' => '',
],
'postmark' => [
'token' => '',
],
'ses' => [
'key' => '',
'secret' => '',

View File

@ -37,7 +37,7 @@ $app = require_once __DIR__.'/bootstrap/app.php';
|
*/
$kernel = $app->make('Illuminate\Contracts\Http\Kernel');
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()

View File

@ -80,6 +80,7 @@ class ServiceProvider extends ModuleServiceProvider
$combiner->registerBundle('~/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less');
$combiner->registerBundle('~/modules/backend/formwidgets/permissioneditor/assets/less/permissioneditor.less');
$combiner->registerBundle('~/modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less');
$combiner->registerBundle('~/modules/backend/formwidgets/sensitive/assets/less/sensitive.less');
/*
* Rich Editor is protected by DRM
@ -199,6 +200,7 @@ class ServiceProvider extends ModuleServiceProvider
$manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist');
$manager->registerFormWidget('Backend\FormWidgets\MediaFinder', 'mediafinder');
$manager->registerFormWidget('Backend\FormWidgets\NestedForm', 'nestedform');
$manager->registerFormWidget('Backend\FormWidgets\Sensitive', 'sensitive');
});
}

View File

@ -11,7 +11,7 @@ use Backend\Behaviors\ImportExportController\TranscodeFilter;
use Illuminate\Database\Eloquent\MassAssignmentException;
use League\Csv\Reader as CsvReader;
use League\Csv\Writer as CsvWriter;
use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula;
use League\Csv\EscapeFormula as CsvEscapeFormula;
use ApplicationException;
use SplTempFileObject;
use Exception;
@ -624,9 +624,7 @@ class ImportExportController extends ControllerBehavior
$csv->setDelimiter($options['delimiter']);
$csv->setEnclosure($options['enclosure']);
$csv->setEscape($options['escape']);
// Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter))
$formatter = new CsvEscapeFormula();
$csv->addFormatter(new CsvEscapeFormula());
/*
* Add headers
@ -662,9 +660,6 @@ class ImportExportController extends ControllerBehavior
$record[] = $value;
}
// Temporary until upgrading to league/csv >= 9.1.0
$record = $formatter($record);
$csv->insertOne($record);
}

View File

@ -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.

View File

@ -8,11 +8,13 @@
"authors": [
{
"name": "Alexey Bobkov",
"email": "aleksey.bobkov@gmail.com"
"email": "aleksey.bobkov@gmail.com",
"role": "Co-founder"
},
{
"name": "Samuel Georges",
"email": "daftspunky@gmail.com"
"email": "daftspunky@gmail.com",
"role": "Co-founder"
},
{
"name": "Luke Towers",
@ -22,9 +24,10 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.2",
"composer/installers": "~1.0",
"october/rain": "~1.0"
"october/rain": "~1.0",
"laravel/framework": "~6.0"
},
"autoload": {
"psr-4": {

View File

@ -12,8 +12,8 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
Eloquent::unguard();
$this->call('Backend\Database\Seeds\SeedSetupAdmin');
Eloquent::unguarded(function () {
$this->call('Backend\Database\Seeds\SeedSetupAdmin');
});
}
}

View File

@ -0,0 +1,117 @@
<?php namespace Backend\FormWidgets;
use Backend\Classes\FormWidgetBase;
/**
* Sensitive widget.
*
* Renders a password field that can be optionally made visible
*
* @package october\backend
*/
class Sensitive extends FormWidgetBase
{
/**
* @var bool If true, the sensitive field cannot be edited, but can be toggled.
*/
public $readOnly = false;
/**
* @var bool If true, the sensitive field is disabled.
*/
public $disabled = false;
/**
* @var bool If true, a button will be available to copy the value.
*/
public $allowCopy = false;
/**
* @var string The string that will be used as a placeholder for an unrevealed sensitive value.
*/
public $hiddenPlaceholder = '__hidden__';
/**
* @var bool If true, the sensitive input will be hidden if the user changes to another tab in their browser.
*/
public $hideOnTabChange = true;
/**
* @inheritDoc
*/
protected $defaultAlias = 'sensitive';
/**
* @inheritDoc
*/
public function init()
{
$this->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');
}
}

View File

@ -0,0 +1,2 @@
div[data-control="sensitive"] a[data-toggle],
div[data-control="sensitive"] a[data-copy] {box-shadow:none;border:1px solid #d1d6d9;border-left:0}

View File

@ -0,0 +1,192 @@
/*
* Sensitive field widget plugin.
*
* Data attributes:
* - data-control="sensitive" - enables the plugin on an element
*
* JavaScript API:
* $('div#someElement').sensitive({...})
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var Sensitive = function(element, options) {
this.$el = $(element)
this.options = options
this.clean = Boolean(this.$el.data('clean'))
this.hidden = true
this.$input = this.$el.find('[data-input]').first()
this.$toggle = this.$el.find('[data-toggle]').first()
this.$icon = this.$el.find('[data-icon]').first()
this.$loader = this.$el.find('[data-loader]').first()
this.$copy = this.$el.find('[data-copy]').first()
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
}
Sensitive.DEFAULTS = {
readOnly: false,
disabled: false,
eventHandler: null,
hideOnTabChange: false,
}
Sensitive.prototype = Object.create(BaseProto)
Sensitive.prototype.constructor = Sensitive
Sensitive.prototype.init = function() {
this.$input.on('keydown', this.proxy(this.onInput))
this.$toggle.on('click', this.proxy(this.onToggle))
if (this.options.hideOnTabChange) {
// Watch for tab change or minimise
document.addEventListener('visibilitychange', this.proxy(this.onTabChange))
}
if (this.$copy.length) {
this.$copy.on('click', this.proxy(this.onCopy))
}
}
Sensitive.prototype.dispose = function () {
this.$input.off('keydown', this.proxy(this.onInput))
this.$toggle.off('click', this.proxy(this.onToggle))
if (this.options.hideOnTabChange) {
document.removeEventListener('visibilitychange', this.proxy(this.onTabChange))
}
if (this.$copy.length) {
this.$copy.off('click', this.proxy(this.onCopy))
}
this.$input = this.$toggle = this.$icon = this.$loader = null
this.$el = null
BaseProto.dispose.call(this)
}
Sensitive.prototype.onInput = function() {
if (this.clean) {
this.clean = false
this.$input.val('')
}
return true
}
Sensitive.prototype.onToggle = function() {
if (this.$input.val() !== '' && this.clean) {
this.reveal()
} else {
this.toggleVisibility()
}
return true
}
Sensitive.prototype.onTabChange = function() {
if (document.hidden && !this.hidden) {
this.toggleVisibility()
}
}
Sensitive.prototype.onCopy = function() {
var that = this,
deferred = $.Deferred(),
isHidden = this.hidden
deferred.then(function () {
if (that.hidden) {
that.toggleVisibility()
}
that.$input.focus()
that.$input.select()
try {
document.execCommand('copy')
} catch (err) {
}
that.$input.blur()
if (isHidden) {
that.toggleVisibility()
}
})
if (this.$input.val() !== '' && this.clean) {
this.reveal(deferred)
} else {
deferred.resolve()
}
}
Sensitive.prototype.toggleVisibility = function() {
if (this.hidden) {
this.$input.attr('type', 'text')
} else {
this.$input.attr('type', 'password')
}
this.$icon.toggleClass('icon-eye icon-eye-slash')
this.hidden = !this.hidden
}
Sensitive.prototype.reveal = function(deferred) {
var that = this
this.$icon.css({
visibility: 'hidden'
})
this.$loader.removeClass('hide')
this.$input.request(this.options.eventHandler, {
success: function (data) {
that.$input.val(data.value)
that.clean = false
that.$icon.css({
visibility: 'visible'
})
that.$loader.addClass('hide')
that.toggleVisibility()
if (deferred) {
deferred.resolve()
}
}
})
}
var old = $.fn.sensitive
$.fn.sensitive = function (option) {
var args = Array.prototype.slice.call(arguments, 1), result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.sensitive')
var options = $.extend({}, Sensitive.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.sensitive', (data = new Sensitive(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.sensitive.noConflict = function () {
$.fn.sensitive = old
return this
}
$(document).render(function () {
$('[data-control="sensitive"]').sensitive()
});
}(window.jQuery);

View File

@ -0,0 +1,10 @@
@import "../../../../assets/less/core/boot.less";
div[data-control="sensitive"] {
a[data-toggle],
a[data-copy] {
box-shadow: none;
border: 1px solid @input-group-addon-border-color;
border-left: 0;
}
}

View File

@ -0,0 +1,41 @@
<div
data-control="sensitive"
data-clean="true"
data-event-handler="<?= $this->getEventHandler('onShowValue') ?>"
<?php if ($hideOnTabChange): ?>data-hide-on-tab-change="true"<?php endif ?>
>
<div class="loading-indicator-container size-form-field">
<div class="input-group">
<input
type="password"
name="<?= $this->getFieldName() ?>"
id="<?= $this->getId() ?>"
value="<?= ($hasValue) ? $hiddenPlaceholder : '' ?>"
placeholder="<?= e(trans($this->formField->placeholder)) ?>"
class="form-control"
<?php if ($this->previewMode): ?>disabled="disabled"<?php endif ?>
autocomplete="off"
data-input
/>
<?php if ($allowCopy): ?>
<a
href="javascript:;"
class="input-group-addon btn btn-secondary"
data-copy
>
<i class="icon-copy"></i>
</a>
<?php endif ?>
<a
href="javascript:;"
class="input-group-addon btn btn-secondary"
data-toggle
>
<i class="icon-eye" data-icon></i>
</a>
</div>
<div class="loading-indicator hide" data-loader>
<span class="p-a"></span>
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@ use Lang;
use Model;
use Response;
use League\Csv\Writer as CsvWriter;
use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula;
use League\Csv\EscapeFormula as CsvEscapeFormula;
use ApplicationException;
use SplTempFileObject;
@ -112,8 +112,7 @@ abstract class ExportModel extends Model
$csv->setEscape($options['escape']);
}
// Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter))
$formatter = new CsvEscapeFormula();
$csv->addFormatter(new CsvEscapeFormula());
/*
* Add headers
@ -128,10 +127,6 @@ abstract class ExportModel extends Model
*/
foreach ($results as $result) {
$data = $this->matchDataToColumns($result, $columns);
// Temporary until upgrading to league/csv >= 9.1.0
$data = $formatter($data);
$csv->insertOne($data);
}

View File

@ -5,6 +5,7 @@ use Str;
use Lang;
use Model;
use League\Csv\Reader as CsvReader;
use League\Csv\Statement as CsvStatement;
/**
* Model used for importing data
@ -108,11 +109,6 @@ abstract class ImportModel extends Model
*/
$reader = CsvReader::createFromPath($filePath, 'r');
// Filter out empty rows
$reader->addFilter(function (array $row) {
return count($row) > 1 || reset($row) !== null;
});
if ($options['delimiter'] !== null) {
$reader->setDelimiter($options['delimiter']);
}
@ -125,15 +121,11 @@ abstract class ImportModel extends Model
$reader->setEscape($options['escape']);
}
if ($options['firstRowTitles']) {
$reader->setOffset(1);
}
if (
$options['encoding'] !== null &&
$reader->isActiveStreamFilter()
$reader->supportsStreamFilter()
) {
$reader->appendStreamFilter(sprintf(
$reader->addStreamFilter(sprintf(
'%s%s:%s',
TranscodeFilter::FILTER_NAME,
strtolower($options['encoding']),
@ -141,8 +133,19 @@ abstract class ImportModel extends Model
));
}
// Create reader statement
$stmt = (new CsvStatement)
->where(function (array $row) {
// Filter out empty rows
return count($row) > 1 || reset($row) !== null;
});
if ($options['firstRowTitles']) {
$stmt = $stmt->offset(1);
}
$result = [];
$contents = $reader->fetch();
$contents = $stmt->process($reader);
foreach ($contents as $row) {
$result[] = $this->processImportRow($row, $matches);
}

View File

@ -27,8 +27,8 @@ class User extends UserBase
public $rules = [
'email' => 'required|between:6,255|email|unique:backend_users',
'login' => 'required|between:2,255|unique:backend_users',
'password' => 'required:create|between:4,255|confirmed',
'password_confirmation' => 'required_with:password|between:4,255'
'password' => 'required:create|min:4|confirmed',
'password_confirmation' => 'required_with:password|min:4'
];
/**

View File

@ -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', '(.*)?');
})
;

View File

@ -287,25 +287,14 @@ class Asset extends Extendable
$directory = $this->theme->getPath() . '/' . $this->dirName . '/';
$filePath = $directory . $fileName;
$path = realpath($filePath);
/**
* If the path doesn't exist yet, then create it temporarily
* in order to run realpath() resolution on it to verify the
* final destination and then remove the temporary file.
*/
if (!$path) {
touch($filePath);
$path = realpath($filePath);
unlink($filePath);
}
$resolvedPath = resolve_path($filePath);
// Limit paths to those under the theme's assets directory
if (!starts_with($path, $directory)) {
if (!starts_with($resolvedPath, $directory)) {
return false;
}
return $path;
return $resolvedPath;
}
/**

View File

@ -316,7 +316,8 @@ class CmsCompoundObject extends CmsObject
self::$objectComponentPropertyMap = $objectComponentMap;
Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10));
$expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10));
Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt);
if (array_key_exists($componentName, $objectComponentMap[$objectCode])) {
return $objectComponentMap[$objectCode][$componentName];

View File

@ -227,7 +227,16 @@ class CmsObject extends HalcyonModel implements CmsObjectContract
$fileName = $this->fileName;
}
return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName;
$directory = $this->theme->getPath() . '/' . $this->getObjectTypeDirName() . '/';
$filePath = $directory . $fileName;
$resolvedPath = resolve_path($filePath);
// Limit paths to those under the corresponding theme directory
if (!starts_with($resolvedPath, $directory)) {
return false;
}
return $resolvedPath;
}
/**

View File

@ -1,5 +1,6 @@
<?php namespace Cms\Classes;
use ApplicationException;
use October\Rain\Support\Collection as CollectionBase;
/**
@ -37,15 +38,32 @@ class CmsObjectCollection extends CollectionBase
/**
* Returns objects whose properties match the supplied value.
* @param string $property
* @param string $value
* @param bool $strict
*
* Note that this deviates from Laravel 6's Illuminate\Support\Traits\EnumeratesValues::where() method signature,
* which uses ($key, $operator = null, $value = null) as parameters and that this class extends.
*
* To ensure backwards compatibility with our current Halcyon functionality, this method retains the original
* parameters and functions the same way as before, with handling for the $value and $strict parameters to ensure
* they match the previously expected formats. This means that you cannot use operators for "where" queries on
* CMS object collections.
*
* @param string $property
* @param string $value
* @param bool $strict
* @return static
*/
public function where($property, $value, $strict = true)
public function where($property, $value = null, $strict = null)
{
return $this->filter(function ($object) use ($property, $value, $strict) {
if (empty($value) || !is_string($value)) {
throw new ApplicationException('You must provide a string value to compare with when executing a "where" '
. 'query for CMS object collections.');
}
if (!isset($strict) || !is_bool($strict)) {
$strict = true;
}
return $this->filter(function ($object) use ($property, $value, $strict) {
if (!array_key_exists($property, $object->settings)) {
return false;
}

View File

@ -224,7 +224,8 @@ class CodeParser
$cached = $this->getCachedInfo() ?: [];
$cached[$this->filePath] = $cacheItem;
Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440);
$expiresAt = now()->addMinutes(1440);
Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt);
self::$cache[$this->filePath] = $result;
}

View File

@ -127,10 +127,11 @@ class Router
: $fileName;
$key = $this->getUrlListCacheKey();
$expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1));
Cache::put(
$key,
base64_encode(serialize($urlList)),
Config::get('cms.urlCacheTtl', 1)
$expiresAt
);
}
}
@ -251,7 +252,8 @@ class Router
$this->urlMap = $map;
if ($cacheable) {
Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1));
$expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1));
Cache::put($key, base64_encode(serialize($map)), $expiresAt);
}
return false;

View File

@ -158,7 +158,8 @@ class Theme
if ($checkDatabase && App::hasDatabase()) {
try {
try {
$dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () {
$expiresAt = now()->addMinutes(1440);
$dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () {
return Parameter::applyKey(self::ACTIVE_KEY)->value('value');
});
}

View File

@ -8,11 +8,13 @@
"authors": [
{
"name": "Alexey Bobkov",
"email": "aleksey.bobkov@gmail.com"
"email": "aleksey.bobkov@gmail.com",
"role": "Co-founder"
},
{
"name": "Samuel Georges",
"email": "daftspunky@gmail.com"
"email": "daftspunky@gmail.com",
"role": "Co-founder"
},
{
"name": "Luke Towers",
@ -22,9 +24,10 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.2",
"composer/installers": "~1.0",
"october/rain": "~1.0"
"october/rain": "~1.0",
"laravel/framework": "~6.0"
},
"autoload": {
"psr-4": {

View File

@ -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

View File

@ -190,7 +190,8 @@ trait UrlMaker
'mtime' => @File::lastModified($filePath)
];
Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440));
$expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440));
Cache::put($key, serialize($cached), $expiresAt);
return static::$urlPageName = $baseFileName;
}

View File

@ -8,7 +8,7 @@ use Cms\Classes\Controller;
use Cms\Classes\ComponentBase;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Debug\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use October\Rain\Database\Model;

View File

@ -94,6 +94,7 @@ class ServiceProvider extends ModuleServiceProvider
}
}
Paginator::useBootstrapThree();
Paginator::defaultSimpleView('system::pagination.simple-default');
/*

View File

@ -16,7 +16,6 @@ return [
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
@ -30,7 +29,6 @@ return [
'Storage' => Illuminate\Support\Facades\Storage::class,
'Url' => Illuminate\Support\Facades\URL::class, // Preferred
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
/*
@ -42,6 +40,7 @@ return [
'Config' => October\Rain\Support\Facades\Config::class,
'Seeder' => October\Rain\Database\Updates\Seeder::class,
'Flash' => October\Rain\Support\Facades\Flash::class,
'Input' => October\Rain\Support\Facades\Input::class,
'Form' => October\Rain\Support\Facades\Form::class,
'Html' => October\Rain\Support\Facades\Html::class,
'Http' => October\Rain\Support\Facades\Http::class,
@ -52,6 +51,7 @@ return [
'Twig' => October\Rain\Support\Facades\Twig::class,
'DbDongle' => October\Rain\Support\Facades\DbDongle::class,
'Schema' => October\Rain\Support\Facades\Schema::class,
'Validator' => October\Rain\Support\Facades\Validator::class,
'Cms' => Cms\Facades\Cms::class,
'Backend' => Backend\Facades\Backend::class,
'BackendMenu' => Backend\Facades\BackendMenu::class,
@ -60,4 +60,12 @@ return [
'SystemException' => October\Rain\Exception\SystemException::class,
'ApplicationException' => October\Rain\Exception\ApplicationException::class,
'ValidationException' => October\Rain\Exception\ValidationException::class,
/*
* Fallback aliases
*/
// Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility.
'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class,
// Illuminate's HtmlDumper was "dumped" in Laravel 6 - we'll route this to Symfony's HtmlDumper as Laravel have done.
'Illuminate\Support\Debug\HtmlDumper' => Symfony\Component\VarDumper\Dumper\HtmlDumper::class,
];

View File

@ -10,11 +10,11 @@ use Route;
use Config;
use Request;
use Response;
use Assetic\Asset\FileAsset;
use Assetic\Asset\AssetCache;
use Assetic\Asset\AssetCollection;
use Assetic\Factory\AssetFactory;
use October\Rain\Parse\Assetic\FilesystemCache;
use October\Rain\Assetic\Asset\FileAsset;
use October\Rain\Assetic\Asset\AssetCache;
use October\Rain\Assetic\Asset\AssetCollection;
use October\Rain\Assetic\Cache\FilesystemCache;
use October\Rain\Assetic\Factory\AssetFactory;
use System\Helpers\Cache as CacheHelper;
use ApplicationException;
use DateTime;
@ -126,22 +126,22 @@ class CombineAssets
/*
* Register JavaScript filters
*/
$this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter);
$this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter);
/*
* Register CSS filters
*/
$this->registerFilter('css', new \Assetic\Filter\CssImportFilter);
$this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter);
$this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler);
$this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler);
$this->registerFilter('css', new \October\Rain\Assetic\Filter\CssImportFilter);
$this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\CssRewriteFilter);
$this->registerFilter('less', new \October\Rain\Assetic\Filter\LessCompiler);
$this->registerFilter('scss', new \October\Rain\Assetic\Filter\ScssCompiler);
/*
* Minification filters
*/
if ($this->useMinify) {
$this->registerFilter('js', new \Assetic\Filter\JSMinFilter);
$this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify);
$this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter);
$this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify);
}
/*

View File

@ -134,10 +134,11 @@ class MediaLibrary
$folderContents = $this->scanFolderContents($fullFolderPath);
$cached[$fullFolderPath] = $folderContents;
$expiresAt = now()->addMinutes(Config::get('cms.storage.media.ttl', 10));
Cache::put(
$this->cacheKey,
base64_encode(serialize($cached)),
Config::get('cms.storage.media.ttl', 10)
$expiresAt
);
}

View File

@ -29,11 +29,6 @@ class UpdateManager
{
use \October\Rain\Support\Traits\Singleton;
/**
* @var array The notes for the current operation.
*/
protected $notes = [];
/**
* @var \Illuminate\Console\OutputStyle
*/
@ -345,13 +340,13 @@ class UpdateManager
/*
* Rollback modules
*/
if (isset($this->notesOutput)) {
$this->migrator->setOutput($this->notesOutput);
}
while (true) {
$rolledBack = $this->migrator->rollback($paths, ['pretend' => false]);
foreach ($this->migrator->getNotes() as $note) {
$this->note($note);
}
if (count($rolledBack) == 0) {
break;
}
@ -403,13 +398,13 @@ class UpdateManager
*/
public function migrateModule($module)
{
$this->migrator->run(base_path() . '/modules/' . strtolower($module) . '/database/migrations');
if (isset($this->notesOutput)) {
$this->migrator->setOutput($this->notesOutput);
}
$this->note($module);
foreach ($this->migrator->getNotes() as $note) {
$this->note(' - ' . $note);
}
$this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations');
return $this;
}
@ -518,13 +513,9 @@ class UpdateManager
$this->note($name);
$this->versionManager->resetNotes()->setNotesOutput($this->notesOutput);
$this->versionManager->setNotesOutput($this->notesOutput);
if ($this->versionManager->updatePlugin($plugin) !== false) {
foreach ($this->versionManager->getNotes() as $note) {
$this->note($note);
}
}
$this->versionManager->updatePlugin($plugin);
return $this;
}
@ -713,7 +704,8 @@ class UpdateManager
}
$data = $this->requestServerData($type . '/popular');
Cache::put($cacheKey, base64_encode(serialize($data)), 60);
$expiresAt = now()->addMinutes(60);
Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt);
foreach ($data as $product) {
$code = array_get($product, 'code', -1);
@ -802,35 +794,11 @@ class UpdateManager
{
if ($this->notesOutput !== null) {
$this->notesOutput->writeln($message);
} else {
$this->notes[] = $message;
}
return $this;
}
/**
* Get the notes for the last operation.
* @return array
*/
public function getNotes()
{
return $this->notes;
}
/**
* Resets the notes store.
* @return self
*/
public function resetNotes()
{
$this->notesOutput = null;
$this->notes = [];
return $this;
}
/**
* Sets an output stream for writing notes.
* @param Illuminate\Console\Command $output

View File

@ -29,12 +29,6 @@ class VersionManager
const HISTORY_TYPE_COMMENT = 'comment';
const HISTORY_TYPE_SCRIPT = 'script';
/**
* The notes for the current operation.
* @var array
*/
protected $notes = [];
/**
* @var \Illuminate\Console\OutputStyle
*/
@ -426,6 +420,7 @@ class VersionManager
* Execute the database PHP script
*/
$updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script;
$this->updater->packDown($updateFile);
Db::table('system_plugin_history')
@ -508,35 +503,11 @@ class VersionManager
{
if ($this->notesOutput !== null) {
$this->notesOutput->writeln($message);
} else {
$this->notes[] = $message;
}
return $this;
}
/**
* Get the notes for the last operation.
* @return array
*/
public function getNotes()
{
return $this->notes;
}
/**
* Resets the notes store.
* @return self
*/
public function resetNotes()
{
$this->notesOutput = null;
$this->notes = [];
return $this;
}
/**
* Sets an output stream for writing notes.
* @param Illuminate\Console\Command $output
@ -550,8 +521,7 @@ class VersionManager
}
/**
* @param $details
*
* Extract script and comments from version details
* @return array
*/
protected function extractScriptsAndComments($details): array
@ -566,7 +536,8 @@ class VersionManager
$scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) {
return preg_match($fileNamePattern, $detail);
}));
} else {
}
else {
$comments = (array)$details;
$scripts = [];
}

View File

@ -8,11 +8,13 @@
"authors": [
{
"name": "Alexey Bobkov",
"email": "aleksey.bobkov@gmail.com"
"email": "aleksey.bobkov@gmail.com",
"role": "Co-founder"
},
{
"name": "Samuel Georges",
"email": "daftspunky@gmail.com"
"email": "daftspunky@gmail.com",
"role": "Co-founder"
},
{
"name": "Luke Towers",
@ -22,9 +24,10 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.2",
"composer/installers": "~1.0",
"october/rain": "~1.0"
"october/rain": "~1.0",
"laravel/framework": "~6.0"
},
"autoload": {
"psr-4": {

View File

@ -369,7 +369,7 @@ class OctoberEnv extends Command
'SESSION_DRIVER' => 'driver',
],
'queue' => [
'QUEUE_DRIVER' => 'default',
'QUEUE_CONNECTION' => 'default',
],
'mail' => [
'MAIL_DRIVER' => 'driver',

View File

@ -17,7 +17,6 @@ use Symfony\Component\Console\Input\InputOption;
*/
class OctoberUpdate extends Command
{
/**
* The console command name.
*/

View File

@ -2,6 +2,8 @@
use Lang;
use Flash;
use Config;
use Request;
use Backend;
use BackendMenu;
use System\Classes\SettingsManager;
@ -139,6 +141,22 @@ class Settings extends Controller
return $this->formWidget->render($options);
}
/**
* Returns the form widget used by this behavior.
*
* @return \Backend\Widgets\Form
*/
public function formGetWidget()
{
if (is_null($this->formWidget)) {
$item = $this->findSettingItem();
$model = $this->createModel($item);
$this->initWidgets($model);
}
return $this->formWidget;
}
/**
* Prepare the widgets used by this action
* Model $model
@ -169,10 +187,22 @@ class Settings extends Controller
}
/**
* Locates a setting item for a module or plugin
* Locates a setting item for a module or plugin.
*
* If none of the parameters are provided, they will be auto-guessed from the URL.
*
* @param string|null $author
* @param string|null $plugin
* @param string|null $code
*
* @return array
*/
protected function findSettingItem($author, $plugin, $code)
protected function findSettingItem($author = null, $plugin = null, $code = null)
{
if (is_null($author) || is_null($plugin)) {
[$author, $plugin, $code] = $this->guessSettingItem();
}
$manager = SettingsManager::instance();
$moduleOwner = $author;
@ -187,4 +217,23 @@ class Settings extends Controller
return $item;
}
/**
* Guesses the requested setting item from the current URL segments provided by the Request object.
*
* @return array
*/
protected function guessSettingItem()
{
$segments = Request::segments();
if (!empty(Config::get('cms.backendUri', 'backend'))) {
array_splice($segments, 0, 4);
} else {
array_splice($segments, 0, 3);
}
// Ensure there's at least 3 segments
return array_pad($segments, 3, null);
}
}

View File

@ -13,8 +13,8 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
Eloquent::unguard();
$this->call('System\Database\Seeds\SeedSetupMailLayouts');
Eloquent::unguarded(function () {
$this->call('System\Database\Seeds\SeedSetupMailLayouts');
});
}
}

View File

@ -32,6 +32,7 @@ return [
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
@ -39,9 +40,22 @@ return [
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'numeric' => 'The :attribute must be greater than :value.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'string' => 'The :attribute must be greater than :value characters.',
'array' => 'The :attribute must have more than :value items.',
],
'gte' => [
'numeric' => 'The :attribute must be greater than or equal :value.',
'file' => 'The :attribute must be greater than or equal :value kilobytes.',
'string' => 'The :attribute must be greater than or equal :value characters.',
'array' => 'The :attribute must have :value items or more.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
@ -50,6 +64,18 @@ return [
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'numeric' => 'The :attribute must be less than :value.',
'file' => 'The :attribute must be less than :value kilobytes.',
'string' => 'The :attribute must be less than :value characters.',
'array' => 'The :attribute must have less than :value items.',
],
'lte' => [
'numeric' => 'The :attribute must be less than or equal :value.',
'file' => 'The :attribute must be less than or equal :value kilobytes.',
'string' => 'The :attribute must be less than or equal :value characters.',
'array' => 'The :attribute must not have more than :value items.',
],
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
@ -65,6 +91,7 @@ return [
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
@ -82,11 +109,13 @@ return [
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'starts_with' => 'The :attribute must start with one of the following: :values.',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------

View File

@ -79,6 +79,7 @@ tabs:
smtp_password:
label: system::lang.mail.smtp_password
tab: system::lang.mail.general
type: sensitive
span: right
trigger:
action: show
@ -107,6 +108,7 @@ tabs:
label: system::lang.mail.mailgun_secret
commentAbove: system::lang.mail.mailgun_secret_comment
tab: system::lang.mail.general
type: sensitive
trigger:
action: show
field: send_mode
@ -116,6 +118,7 @@ tabs:
label: system::lang.mail.mandrill_secret
commentAbove: system::lang.mail.mandrill_secret_comment
tab: system::lang.mail.general
type: sensitive
trigger:
action: show
field: send_mode
@ -135,6 +138,7 @@ tabs:
label: system::lang.mail.ses_secret
commentAbove: system::lang.mail.ses_secret_comment
tab: system::lang.mail.general
type: sensitive
span: right
trigger:
action: show
@ -154,6 +158,7 @@ tabs:
sparkpost_secret:
label: system::lang.mail.sparkpost_secret
commentAbove: system::lang.mail.sparkpost_secret_comment
type: sensitive
tab: system::lang.mail.general
trigger:
action: show

View File

@ -15,9 +15,7 @@ return [
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
Laravel\Tinker\TinkerServiceProvider::class,
@ -36,5 +34,7 @@ return [
October\Rain\Flash\FlashServiceProvider::class,
October\Rain\Mail\MailServiceProvider::class,
October\Rain\Argon\ArgonServiceProvider::class,
October\Rain\Redis\RedisServiceProvider::class,
October\Rain\Validation\ValidationServiceProvider::class,
];

View File

@ -8,7 +8,6 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="October CMS Test Suite">

View File

@ -28,7 +28,7 @@ abstract class PluginTestCase extends TestCase
$app['cache']->setDefaultDriver('array');
$app->setLocale('en');
$app->singleton('auth', function ($app) {
$app->singleton('backend.auth', function ($app) {
$app['auth.loaded'] = true;
return AuthManager::instance();
@ -67,7 +67,7 @@ abstract class PluginTestCase extends TestCase
* Perform test case set up.
* @return void
*/
public function setUp()
public function setUp() : void
{
/*
* Force reload of October singletons
@ -105,7 +105,7 @@ abstract class PluginTestCase extends TestCase
* Flush event listeners and collect garbage.
* @return void
*/
public function tearDown()
public function tearDown() : void
{
$this->flushModelEventListeners();
parent::tearDown();

View File

@ -1,6 +1,6 @@
# Plugin testing
Plugin unit tests can be performed by running `phpunit` in the base plugin directory.
Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`.
### Creating plugin tests
@ -58,7 +58,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci
class BaseTestCase extends PluginTestCase
{
public function setUp()
public function setUp(): void
{
parent::setUp();
@ -72,7 +72,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci
$pluginManager->bootAll(true);
}
public function tearDown()
public function tearDown(): void
{
parent::tearDown();
@ -96,39 +96,10 @@ To perform unit testing on the core October files, you should download a develop
### Unit tests
Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`.
Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation.
### Functional tests
Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met:
Functional tests can be performed by installing the [RainLab Dusk](https://octobercms.com/plugin/rainlab-dusk) in your October CMS installation. The RainLab Dusk plugin is powered by Laravel Dusk, a comprehensive testing suite for the Laravel framework that is designed to test interactions with a fully operational October CMS instance through a virtual browser.
- Active theme is `demo`
- Language preference is `en`
#### Selenium set up
1. Download latest Java SE from http://java.sun.com/ and install
1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/).
1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance.
1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`.
#### Selenium configuration
Create a new file `selenium.php` in the root directory, add the following content:
<?php
// Selenium server details
define('TEST_SELENIUM_HOST', '127.0.0.1');
define('TEST_SELENIUM_PORT', 4444);
define('TEST_SELENIUM_BROWSER', '*firefox');
// Back-end URL
define('TEST_SELENIUM_URL', 'http://localhost/backend/');
// Active Theme
define('TEST_SELENIUM_THEME', 'demo');
// Back-end credentials
define('TEST_SELENIUM_USER', 'admin');
define('TEST_SELENIUM_PASS', 'admin');
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.

View File

@ -1,111 +0,0 @@
<?php
class UiTestCase extends PHPUnit_Extensions_SeleniumTestCase
{
protected function setUp()
{
/*
* Look for selenium configuration
*/
if (file_exists($seleniumEnv = __DIR__.'/../selenium.php')) {
require_once $seleniumEnv;
}
/*
* Configure selenium
*/
if (!defined('TEST_SELENIUM_URL')) {
return $this->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;
}
}
}

View File

@ -19,14 +19,3 @@ $loader->addDirectories([
'modules',
'plugins'
]);
/*
* Monkey patch PHPUnit\Framework\MockObject\Generator to avoid
* "Function ReflectionType::__toString() is deprecated" warnings
*/
$generatorPatchPath = __DIR__ . '/resources/patches/php-generator-7.php';
$generatorSourcePath = __DIR__ . '/../vendor/phpunit/phpunit-mock-objects/src/Generator.php';
if (file_exists($generatorSourcePath)) {
file_put_contents($generatorSourcePath, file_get_contents($generatorPatchPath));
}

View File

@ -29,7 +29,7 @@ trait InteractsWithAuthentication
*/
public function be(UserContract $user, $driver = null)
{
$this->app['auth']->setUser($user);
$this->app['backend.auth']->setUser($user);
}
/**
@ -66,7 +66,7 @@ trait InteractsWithAuthentication
*/
protected function isAuthenticated($guard = null)
{
return $this->app->make('auth')->guard($guard)->check();
return $this->app->make('backend.auth')->guard($guard)->check();
}
/**
@ -78,7 +78,7 @@ trait InteractsWithAuthentication
*/
public function assertAuthenticatedAs($user, $guard = null)
{
$expected = $this->app->make('auth')->guard($guard)->user();
$expected = $this->app->make('backend.auth')->guard($guard)->user();
$this->assertNotNull($expected, 'The current user is not authenticated.');
@ -140,7 +140,7 @@ trait InteractsWithAuthentication
*/
protected function hasCredentials(array $credentials, $guard = null)
{
$provider = $this->app->make('auth')->guard($guard)->getProvider();
$provider = $this->app->make('backend.auth')->guard($guard)->getProvider();
$user = $provider->retrieveByCredentials($credentials);

View File

@ -20,6 +20,7 @@ class Author extends Model
*/
public $belongsTo = [
'user' => ['Database\Tester\Models\User', 'delete' => true],
'country' => ['Database\Tester\Models\Country'],
'user_soft' => ['Database\Tester\Models\SoftDeleteUser', 'key' => 'user_id', 'softDelete' => true],
];

View File

@ -0,0 +1,35 @@
<?php namespace Database\Tester\Models;
use Model;
class Country extends Model
{
/**
* @var string The database table used by the model.
*/
public $table = 'database_tester_countries';
/**
* @var array Guarded fields
*/
protected $guarded = [];
public $hasMany = [
'users' => [
'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;
}

View File

@ -17,6 +17,19 @@ class User extends Model
/**
* @var array Relations
*/
public $hasOne = [
'author' => [
'Database\Tester\Models\Author',
]
];
public $hasOneThrough = [
'phone' => [
'Database\Tester\Models\Phone',
'through' => 'Database\Tester\Models\Author',
],
];
public $attachOne = [
'avatar' => 'System\Models\File'
];

View File

@ -11,6 +11,7 @@ class CreateAuthorsTable extends Migration
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('user_id')->unsigned()->index()->nullable();
$table->integer('country_id')->unsigned()->index()->nullable();
$table->string('name')->nullable();
$table->string('email')->nullable();
$table->softDeletes();

View File

@ -0,0 +1,23 @@
<?php namespace Database\Tester\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class CreateCountriesTable extends Migration
{
public function up()
{
Schema::create('database_tester_countries', function ($table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name')->nullable();
$table->softDeletes();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('database_tester_countries');
}
}

View File

@ -9,3 +9,4 @@
- create_users_table.php
- create_event_log_table.php
- create_meta_table.php
- create_countries_table.php

View File

@ -0,0 +1 @@
console.log('script1.js');

View File

@ -0,0 +1 @@
console.log('script2.js');

View File

@ -0,0 +1 @@
console.log('subdir/script1.js');

View File

@ -1,90 +0,0 @@
<?php
class AuthTest extends UiTestCase
{
public function testSignInAndOut()
{
$this->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());
}
}
}

View File

@ -1,143 +0,0 @@
<?php
class TemplateTest extends UiTestCase
{
public function testOpenTemplates()
{
$this->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");
}
}

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="true"
syntaxCheck="false"
>
<testsuites>
<testsuite name="October Test Suite">
<directory>./</directory>
</testsuite>
</testsuites>
</phpunit>

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ use October\Rain\Exception\SystemException;
class AuthManagerTest extends TestCase
{
public function setUp()
public function setUp(): void
{
$this->createApplication();
@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase
]);
}
public function tearDown()
public function tearDown(): void
{
AuthManager::forgetInstance();
}

View File

@ -59,18 +59,18 @@ class NavigationManagerTest extends TestCase
$manager->setContext('October.Tester', 'blog');
$items = $manager->listSideMenuItems();
$this->assertInternalType('array', $items);
$this->assertIsArray($items);
$this->assertArrayHasKey('posts', $items);
$this->assertArrayHasKey('categories', $items);
$this->assertInternalType('object', $items['posts']);
$this->assertIsObject($items['posts']);
$this->assertObjectHasAttribute('code', $items['posts']);
$this->assertObjectHasAttribute('owner', $items['posts']);
$this->assertEquals('posts', $items['posts']->code);
$this->assertEquals('October.Tester', $items['posts']->owner);
$this->assertObjectHasAttribute('permissions', $items['posts']);
$this->assertInternalType('array', $items['posts']->permissions);
$this->assertIsArray($items['posts']->permissions);
$this->assertCount(1, $items['posts']->permissions);
$this->assertObjectHasAttribute('order', $items['posts']);
@ -92,7 +92,7 @@ class NavigationManagerTest extends TestCase
$items = $manager->listMainMenuItems();
$this->assertInternalType('array', $items);
$this->assertIsArray($items);
$this->assertArrayHasKey('OCTOBER.TESTER.PRINT', $items);
$item = $items['OCTOBER.TESTER.PRINT'];
@ -143,10 +143,10 @@ class NavigationManagerTest extends TestCase
$manager->setContext('October.Tester', 'blog');
$items = $manager->listSideMenuItems();
$this->assertInternalType('array', $items);
$this->assertIsArray($items);
$this->assertArrayHasKey('foo', $items);
$this->assertInternalType('object', $items['foo']);
$this->assertIsObject($items['foo']);
$this->assertObjectHasAttribute('code', $items['foo']);
$this->assertObjectHasAttribute('owner', $items['foo']);
$this->assertObjectHasAttribute('order', $items['foo']);
@ -156,7 +156,7 @@ class NavigationManagerTest extends TestCase
$this->assertEquals('October.Tester', $items['foo']->owner);
$this->assertObjectHasAttribute('permissions', $items['foo']);
$this->assertInternalType('array', $items['foo']->permissions);
$this->assertIsArray($items['foo']->permissions);
$this->assertCount(2, $items['foo']->permissions);
$this->assertContains('october.tester.access_foo', $items['foo']->permissions);
$this->assertContains('october.tester.access_bar', $items['foo']->permissions);

View File

@ -11,8 +11,8 @@ class BackendHelperTest extends TestCase
$assets = $backendHelper->decompileAsset('tests/fixtures/backend/assets/compilation.js');
$this->assertCount(2, $assets);
$this->assertContains('file1.js', $assets[0]);
$this->assertContains('file2.js', $assets[1]);
$this->assertStringContainsString('file1.js', $assets[0]);
$this->assertStringContainsString('file2.js', $assets[1]);
}
public function testDecompileMissingFile()

View File

@ -28,7 +28,7 @@ class WidgetMakerTest extends TestCase
*
* @return void
*/
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -0,0 +1,119 @@
<?php
use Cms\Classes\Theme;
use Cms\Classes\Asset;
class AssetTest extends TestCase
{
public function testLoad()
{
$theme = Theme::load('test');
// Valid direct path
$this->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')
);
}
}

View File

@ -30,7 +30,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject
class CmsCompoundObjectTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();
Model::clearBootedModels();
@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase
$theme = Theme::load('test');
$obj = TestCmsCompoundObject::load($theme, 'compound.htm');
$this->assertContains("\$controller->data['something'] = 'some value'", $obj->code);
$this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code);
$this->assertEquals('<p>This is a paragraph</p>', $obj->markup);
$this->assertInternalType('array', $obj->settings);
$this->assertIsArray($obj->settings);
$this->assertArrayHasKey('var', $obj->settings);
$this->assertEquals('value', $obj->settings['var']);
$this->assertArrayHasKey('components', $obj->settings);
$this->assertArrayHasKey('section', $obj->settings['components']);
$this->assertInternalType('array', $obj->settings['components']['section']);
$this->assertIsArray($obj->settings['components']['section']);
$this->assertArrayHasKey('version', $obj->settings['components']['section']);
$this->assertEquals(10, $obj->settings['components']['section']['version']);
@ -69,7 +69,7 @@ class CmsCompoundObjectTest extends TestCase
$obj = TestCmsCompoundObject::load($theme, 'component.htm');
$this->assertArrayHasKey('components', $obj->settings);
$this->assertInternalType('array', $obj->settings['components']);
$this->assertIsArray($obj->settings['components']);
$this->assertArrayHasKey('testArchive', $obj->settings['components']);
$this->assertArrayHasKey('posts-per-page', $obj->settings['components']['testArchive']);
$this->assertEquals(10, $obj->settings['components']['testArchive']['posts-per-page']);
@ -82,7 +82,7 @@ class CmsCompoundObjectTest extends TestCase
$obj = TestCmsCompoundObject::load($theme, 'components.htm');
$this->assertArrayHasKey('components', $obj->settings);
$this->assertInternalType('array', $obj->settings['components']);
$this->assertIsArray($obj->settings['components']);
$this->assertArrayHasKey('testArchive firstAlias', $obj->settings['components']);
$this->assertArrayHasKey('October\Tester\Components\Post secondAlias', $obj->settings['components']);
@ -108,7 +108,7 @@ class CmsCompoundObjectTest extends TestCase
$properties = $obj->getComponentProperties('October\Tester\Components\Post');
$emptyProperties = $obj->getComponentProperties('October\Tester\Components\Archive');
$notExistingProperties = $obj->getComponentProperties('This\Is\Not\Component');
$this->assertInternalType('array', $properties);
$this->assertIsArray($properties);
$this->assertArrayHasKey('show-featured', $properties);
$this->assertTrue((bool)$properties['show-featured']);
$this->assertEquals('true', $properties['show-featured']);
@ -148,18 +148,18 @@ class CmsCompoundObjectTest extends TestCase
$this->assertEquals($testContent, $obj->getContent());
$this->assertEquals('testcompound.htm', $obj->getFileName());
$this->assertEquals('<p>This is a paragraph</p>', $obj->markup);
$this->assertInternalType('array', $obj->settings);
$this->assertIsArray($obj->settings);
$this->assertArrayHasKey('var', $obj->settings);
$this->assertEquals('value', $obj->settings['var']);
$this->assertArrayHasKey('components', $obj->settings);
$this->assertInternalType('array', $obj->settings['components']['section']);
$this->assertIsArray($obj->settings['components']['section']);
$this->assertArrayHasKey('version', $obj->settings['components']['section']);
$this->assertEquals(10, $obj->settings['components']['section']['version']);
$this->assertEquals('value', $obj->var);
$this->assertInternalType('array', $obj->settings['components']['section']);
$this->assertIsArray($obj->settings['components']['section']);
$this->assertArrayHasKey('version', $obj->settings['components']['section']);
$this->assertEquals(10, $obj->settings['components']['section']['version']);
@ -173,18 +173,18 @@ class CmsCompoundObjectTest extends TestCase
$this->assertEquals($testContent, $obj->getContent());
$this->assertEquals('testcompound.htm', $obj->getFileName());
$this->assertEquals('<p>This is a paragraph</p>', $obj->markup);
$this->assertInternalType('array', $obj->settings);
$this->assertIsArray($obj->settings);
$this->assertArrayHasKey('var', $obj->settings);
$this->assertEquals('value', $obj->settings['var']);
$this->assertArrayHasKey('components', $obj->settings);
$this->assertInternalType('array', $obj->settings['components']['section']);
$this->assertIsArray($obj->settings['components']['section']);
$this->assertArrayHasKey('version', $obj->settings['components']['section']);
$this->assertEquals(10, $obj->settings['components']['section']['version']);
$this->assertEquals('value', $obj->var);
$this->assertInternalType('array', $obj->settings['components']['section']);
$this->assertIsArray($obj->settings['components']['section']);
$this->assertArrayHasKey('version', $obj->settings['components']['section']);
$this->assertEquals(10, $obj->settings['components']['section']['version']);
}
@ -280,14 +280,14 @@ class CmsCompoundObjectTest extends TestCase
$obj = TestParsedCmsCompoundObject::load($theme, 'viewbag.htm');
$this->assertNull($obj->code);
$this->assertEquals('<p>Chop Suey!</p>', $obj->markup);
$this->assertInternalType('array', $obj->settings);
$this->assertIsArray($obj->settings);
$this->assertArrayHasKey('var', $obj->settings);
$this->assertEquals('value', $obj->settings['var']);
$this->assertArrayHasKey('components', $obj->settings);
$this->assertArrayHasKey('viewBag', $obj->settings['components']);
$this->assertInternalType('array', $obj->settings['components']['viewBag']);
$this->assertIsArray($obj->settings['components']['viewBag']);
$this->assertArrayHasKey('title', $obj->settings['components']['viewBag']);
$this->assertEquals('Toxicity', $obj->settings['components']['viewBag']['title']);

View File

@ -7,7 +7,7 @@ use October\Rain\Halcyon\Model;
class CmsObjectQueryTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -148,12 +148,11 @@ class CmsObjectTest extends TestCase
$this->assertNull($obj->something);
}
/**
* @expectedException \October\Rain\Exception\ValidationException
* @expectedExceptionMessage Invalid file name
*/
public function testFillInvalidFileNameSymbol()
{
$this->expectException(\October\Rain\Exception\ValidationException::class);
$this->expectExceptionMessage('Invalid file name');
$theme = Theme::load('apitest');
$testContents = 'mytestcontent';
@ -164,12 +163,11 @@ class CmsObjectTest extends TestCase
$obj->save();
}
/**
* @expectedException \October\Rain\Exception\ValidationException
* @expectedExceptionMessage Invalid file name
*/
public function testFillInvalidFileNamePath()
{
$this->expectException(\October\Rain\Exception\ValidationException::class);
$this->expectExceptionMessage('Invalid file name');
$theme = Theme::load('apitest');
$testContents = 'mytestcontent';
@ -180,12 +178,11 @@ class CmsObjectTest extends TestCase
$obj->save();
}
/**
* @expectedException \October\Rain\Exception\ValidationException
* @expectedExceptionMessage Invalid file name
*/
public function testFillInvalidFileSlash()
{
$this->expectException(\October\Rain\Exception\ValidationException::class);
$this->expectExceptionMessage('Invalid file name');
$theme = Theme::load('apitest');
$testContents = 'mytestcontent';
@ -196,12 +193,11 @@ class CmsObjectTest extends TestCase
$obj->save();
}
/**
* @expectedException \October\Rain\Exception\ValidationException
* @expectedExceptionMessage The File Name field is required
*/
public function testFillEmptyFileName()
{
$this->expectException(\October\Rain\Exception\ValidationException::class);
$this->expectExceptionMessage('The File Name field is required');
$theme = Theme::load('apitest');
$testContents = 'mytestcontent';
@ -266,11 +262,12 @@ class CmsObjectTest extends TestCase
/**
* @depends testRename
* @expectedException \October\Rain\Exception\ApplicationException
* @expectedExceptionMessage already exists
*/
public function testRenameToExistingFile()
{
$this->expectException(\October\Rain\Exception\ApplicationException::class);
$this->expectExceptionMessageMatches('/already\sexists/');
$theme = Theme::load('apitest');
$srcFilePath = $theme->getPath().'/testobjects/anotherobj.htm';

View File

@ -10,7 +10,7 @@ use Cms\Classes\Controller;
class CodeParserTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setup();
@ -41,7 +41,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($layout);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -78,7 +78,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($layout);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertEquals('request-cache', $info['source']);
$this->assertFileExists($info['filePath']);
@ -91,7 +91,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($layout);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertEquals('cache', $info['source']);
$this->assertFileExists($info['filePath']);
@ -101,7 +101,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($layout);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertEquals('request-cache', $info['source']);
$this->assertFileExists($info['filePath']);
@ -110,13 +110,14 @@ class CodeParserTest extends TestCase
*/
$this->assertTrue(@touch($layout->getFilePath()));
clearstatcache();
$layout = Layout::load($theme, 'php-parser-test.htm');
$this->assertNotEmpty($layout);
$parser = new CodeParser($layout);
$property->setValue($parser, []);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertEquals('parser', $info['source']);
$this->assertFileExists($info['filePath']);
}
@ -131,7 +132,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($layout);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -157,7 +158,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($page);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -191,7 +192,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($page);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -220,7 +221,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($page);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -255,7 +256,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($page);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);
@ -284,7 +285,7 @@ class CodeParserTest extends TestCase
$parser = new CodeParser($page);
$info = $parser->parse();
$this->assertInternalType('array', $info);
$this->assertIsArray($info);
$this->assertArrayHasKey('filePath', $info);
$this->assertArrayHasKey('className', $info);
$this->assertArrayHasKey('source', $info);

View File

@ -9,7 +9,7 @@ use Cms\Classes\ComponentManager;
class ComponentManagerTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -6,7 +6,7 @@ use October\Rain\Halcyon\Model;
class ControllerTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();
@ -69,7 +69,7 @@ class ControllerTest extends TestCase
$response = $controller->run('/some-page-that-doesnt-exist');
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getContent();
$this->assertInternalType('string', $content);
$this->assertIsString($content);
$this->assertEquals('<p>Page not found</p>', $content);
}
@ -83,16 +83,15 @@ class ControllerTest extends TestCase
$response = $controller->run('/');
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getContent();
$this->assertInternalType('string', $content);
$this->assertIsString($content);
$this->assertEquals('<h1>My Webpage</h1>', trim($content));
}
/**
* @expectedException Cms\Classes\CmsException
* @expectedExceptionMessage is not found
*/
public function testLayoutNotFound()
{
$this->expectException(\Cms\Classes\CmsException::class);
$this->expectExceptionMessageMatches('/is\snot\sfound/');
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/no-layout');
@ -146,12 +145,11 @@ class ControllerTest extends TestCase
$this->assertEquals("<div>LAYOUT CONTENT <h1>This page is a subdirectory</h1></div>", $response);
}
/**
* @expectedException \Twig\Error\RuntimeError
* @expectedExceptionMessage is not found
*/
public function testPartialNotFound()
{
$this->expectException(\Twig\Error\RuntimeError::class);
$this->expectExceptionMessageMatches('/is\snot\sfound/');
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/no-partial')->getContent();
@ -193,12 +191,11 @@ class ControllerTest extends TestCase
return $requestMock;
}
/**
* @expectedException Cms\Classes\CmsException
* @expectedExceptionMessage AJAX handler 'onNoHandler' was not found.
*/
public function testAjaxHandlerNotFound()
{
$this->expectException(\Cms\Classes\CmsException::class);
$this->expectExceptionMessage('AJAX handler \'onNoHandler\' was not found.');
Request::swap($this->configAjaxRequestMock('onNoHandler', ''));
$theme = Theme::load('test');
@ -206,12 +203,11 @@ class ControllerTest extends TestCase
$controller->run('/ajax-test');
}
/**
* @expectedException Cms\Classes\CmsException
* @expectedExceptionMessage Invalid AJAX handler name: delete.
*/
public function testAjaxInvalidHandlerName()
{
$this->expectException(\Cms\Classes\CmsException::class);
$this->expectExceptionMessage('Invalid AJAX handler name: delete.');
Request::swap($this->configAjaxRequestMock('delete'));
$theme = Theme::load('test');
@ -219,12 +215,11 @@ class ControllerTest extends TestCase
$controller->run('/ajax-test');
}
/**
* @expectedException Cms\Classes\CmsException
* @expectedExceptionMessage Invalid partial name: p:artial.
*/
public function testAjaxInvalidPartial()
{
$this->expectException(\Cms\Classes\CmsException::class);
$this->expectExceptionMessage('Invalid partial name: p:artial.');
Request::swap($this->configAjaxRequestMock('onTest', 'p:artial'));
$theme = Theme::load('test');
@ -232,12 +227,11 @@ class ControllerTest extends TestCase
$controller->run('/ajax-test');
}
/**
* @expectedException Cms\Classes\CmsException
* @expectedExceptionMessage The partial 'partial' is not found.
*/
public function testAjaxPartialNotFound()
{
$this->expectException(\Cms\Classes\CmsException::class);
$this->expectExceptionMessage('The partial \'partial\' is not found.');
Request::swap($this->configAjaxRequestMock('onTest', 'partial'));
$theme = Theme::load('test');
@ -255,7 +249,7 @@ class ControllerTest extends TestCase
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getOriginalContent();
$this->assertInternalType('array', $content);
$this->assertIsArray($content);
$this->assertEquals(200, $response->getStatusCode());
$this->assertCount(1, $content);
$this->assertArrayHasKey('ajax-result', $content);
@ -272,7 +266,7 @@ class ControllerTest extends TestCase
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getOriginalContent();
$this->assertInternalType('array', $content);
$this->assertIsArray($content);
$this->assertEquals(200, $response->getStatusCode());
$this->assertCount(1, $content);
$this->assertArrayHasKey('ajax-result', $content);
@ -289,7 +283,7 @@ class ControllerTest extends TestCase
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getOriginalContent();
$this->assertInternalType('array', $content);
$this->assertIsArray($content);
$this->assertEquals(200, $response->getStatusCode());
$this->assertCount(2, $content);
$this->assertArrayHasKey('ajax-result', $content);
@ -303,7 +297,7 @@ class ControllerTest extends TestCase
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-component')->getContent();
$page = $this->readAttribute($controller, 'page');
$page = self::getProtectedProperty($controller, 'page');
$this->assertArrayHasKey('testArchive', $page->components);
$component = $page->components['testArchive'];
@ -331,7 +325,7 @@ ESC;
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-components')->getContent();
$page = $this->readAttribute($controller, 'page');
$page = self::getProtectedProperty($controller, 'page');
$this->assertArrayHasKey('firstAlias', $page->components);
$this->assertArrayHasKey('secondAlias', $page->components);
@ -363,19 +357,18 @@ ESC;
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
$content = $response->getOriginalContent();
$this->assertInternalType('array', $content);
$this->assertIsArray($content);
$this->assertEquals(200, $response->getStatusCode());
$this->assertCount(1, $content);
$this->assertArrayHasKey('ajax-result', $content);
$this->assertEquals('page', $content['ajax-result']);
}
/**
* @expectedException October\Rain\Exception\SystemException
* @expectedExceptionMessage is not registered for the component
*/
public function testComponentClassNotFound()
{
$this->expectException(\October\Rain\Exception\SystemException::class);
$this->expectExceptionMessageMatches('/is\snot\sregistered\sfor\sthe\scomponent/');
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/no-component-class')->getContent();
@ -395,7 +388,7 @@ ESC;
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-soft-component-class')->getContent();
$page = $this->readAttribute($controller, 'page');
$page = $controller->getPage();
$this->assertArrayHasKey('testArchive', $page->components);
$component = $page->components['testArchive'];
@ -421,7 +414,7 @@ ESC;
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-soft-component-class-alias')->getContent();
$page = $this->readAttribute($controller, 'page');
$page = $controller->getPage();
$this->assertArrayHasKey('someAlias', $page->components);
$component = $page->components['someAlias'];

View File

@ -7,7 +7,7 @@ class RouterTest extends TestCase
{
protected static $theme = null;
public function setUp()
public function setUp() : void
{
parent::setUp();
@ -43,7 +43,7 @@ class RouterTest extends TestCase
$this->assertFalse($value);
$map = $property->getValue($router);
$this->assertInternalType('array', $map);
$this->assertIsArray($map);
$this->assertGreaterThanOrEqual(4, count($map));
/*
@ -52,7 +52,7 @@ class RouterTest extends TestCase
$value = $method->invoke($router);
$this->assertTrue($value);
$map = $property->getValue($router);
$this->assertInternalType('array', $map);
$this->assertIsArray($map);
$this->assertGreaterThanOrEqual(4, count($map));
}

View File

@ -4,7 +4,7 @@ use Cms\Classes\Theme;
class ThemeTest extends TestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();
@ -44,7 +44,7 @@ class ThemeTest extends TestCase
$pageCollection = $theme->listPages();
$pages = array_values($pageCollection->all());
$this->assertInternalType('array', $pages);
$this->assertIsArray($pages);
$expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages');
$this->assertCount($expectedPageNum, $pages);
@ -63,12 +63,11 @@ class ThemeTest extends TestCase
$this->assertEquals('test', $activeTheme->getDirName());
}
/**
* @expectedException \October\Rain\Exception\SystemException
* @expectedExceptionMessage The active theme is not set.
*/
public function testNoActiveTheme()
{
$this->expectException(\October\Rain\Exception\SystemException::class);
$this->expectExceptionMessage('The active theme is not set.');
Config::set('cms.activeTheme', null);
Theme::getActiveTheme();
}

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\User;
class AttachManyModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachOneModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\Author;
class BelongsToManyModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\Author;
class BelongsToModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -6,7 +6,7 @@ use October\Rain\Database\Models\DeferredBinding;
class DeferredBindingTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -6,7 +6,7 @@ use October\Rain\Database\Collection;
class HasManyModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();
@ -63,8 +63,6 @@ class HasManyModelTest extends PluginTestCase
public function testGetRelationValue()
{
$this->markTestSkipped('Marked as \'skipped\' for further investigation');
Model::unguard();
$author = Author::create(['name' => 'Stevie']);
$post1 = Post::create(['title' => "First post", 'author_id' => $author->id]);

View File

@ -0,0 +1,54 @@
<?php
use Database\Tester\Models\Author;
use Database\Tester\Models\Country;
use Database\Tester\Models\Post;
use October\Rain\Database\Collection;
class HasManyThroughModelTest extends PluginTestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path().'/tests/fixtures/plugins/database/tester/models/Post.php';
include_once base_path().'/tests/fixtures/plugins/database/tester/models/Author.php';
include_once base_path().'/tests/fixtures/plugins/database/tester/models/Country.php';
$this->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());
}
}

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\Phone;
class HasOneModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();

View File

@ -0,0 +1,39 @@
<?php
use Database\Tester\Models\Author;
use Database\Tester\Models\Phone;
use Database\Tester\Models\User;
class HasOneThroughModelTest extends PluginTestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path().'/tests/fixtures/plugins/database/tester/models/User.php';
include_once base_path().'/tests/fixtures/plugins/database/tester/models/Author.php';
include_once base_path().'/tests/fixtures/plugins/database/tester/models/Phone.php';
$this->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);
}
}

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Post;
class ModelTest extends PluginTestCase
{
public function setUp()
public function setUp() : void
{
parent::setUp();
@ -23,12 +23,11 @@ class ModelTest extends PluginTestCase
$this->assertEquals(1, $post->id);
}
/**
* @expectedException \Illuminate\Database\Eloquent\MassAssignmentException
* @expectedExceptionMessage title
*/
public function testGuardedAttribute()
{
$this->expectException(\Illuminate\Database\Eloquent\MassAssignmentException::class);
$this->expectExceptionMessageMatches('/title/');
Post::create(['title' => 'Hi!', 'slug' => 'authenticity']);
}
}

Some files were not shown because too many files have changed in this diff Show More