diff --git a/framework/core/CHANGELOG.md b/framework/core/CHANGELOG.md
new file mode 100644
index 000000000..cd46cf93d
--- /dev/null
+++ b/framework/core/CHANGELOG.md
@@ -0,0 +1,44 @@
+# Change Log
+All notable changes to Flarum and its bundled extensions will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased][unreleased]
+*nothing yet*
+
+## [0.1.0-beta.2] - 2015-09-15
+### Added
+- Check prerequisites (PHP version, extensions, etc.) before installation (#364)
+- Enforce maximum title and post length through validation (#53, #338)
+- Ctrl+Enter submits posts (#276)
+- Syntax highlighting for code blocks (#248)
+- All links open in new window, receive rel=nofollow attribute (#247)
+- Default build script for extensions (#438)
+- Input validation in installer
+
+### Changed
+- Ask for admin password confirmation in installer (#405)
+- Increased some text contrasts for accessibility (#390)
+
+### Fixed
+- Discussion list did not work with non-empty database prefix (#269, #380)
+- Non-admins could not reset their password (#229)
+- Requests ending with a slash resulted in a 404 (#334)
+- In rare cases, posts did not load correctly (#295)
+- Avatars did not show up when installed in a subfolder (#291)
+- Installer crashed when views directory was not writable (#376)
+- Table prefix could not be set in web installer (#269)
+- Enabling an extension disabled all other extensions (#402)
+- Invalid custom CSS could crash the application (#400)
+- First posts could not be restored or deleted
+- Several design bugs
+- Set cookies to be HTTP-only
+- Tags: Sometimes, tags could not be dragged for reordering in the admin panel (#341)
+- Suspend: Use correct column name in when migrating database
+- Lock: Check for correct permission when displaying lock control
+- Likes: Allow liking permissions to be configured
+
+## 0.1.0-beta - 2015-08-27
+First Version
+
+[unreleased]: https://github.com/flarum/core/compare/v0.1.0-beta.2...HEAD
+[0.1.0-beta.2]: https://github.com/flarum/core/compare/v0.1.0-beta...v0.1.0-beta.2
diff --git a/framework/core/composer.json b/framework/core/composer.json
index 1f52e0b2f..f2e18d97d 100644
--- a/framework/core/composer.json
+++ b/framework/core/composer.json
@@ -26,13 +26,14 @@
"tobscure/json-api": "^0.1.1",
"oyejorge/less.php": "~1.5",
"intervention/image": "^2.3.0",
- "s9e/text-formatter": "^0.1.0",
+ "s9e/text-formatter": "^0.3.2",
"psr/http-message": "^1.0",
"zendframework/zend-diactoros": "^1.1",
"nikic/fast-route": "^0.6",
"dflydev/fig-cookies": "^1.0",
"symfony/console": "^2.7",
- "symfony/yaml": "^2.7"
+ "symfony/yaml": "^2.7",
+ "doctrine/dbal": "^2.5"
},
"require-dev": {
"squizlabs/php_codesniffer": "2.*",
diff --git a/framework/core/composer.lock b/framework/core/composer.lock
index bb43b0637..9606be396 100644
--- a/framework/core/composer.lock
+++ b/framework/core/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "9b1418962fa1c90ff76716ca0356562b",
+ "hash": "e60ca4174e0cfb22de050c0f15539735",
"packages": [
{
"name": "danielstjules/stringy",
@@ -114,6 +114,354 @@
],
"time": "2015-06-03 18:02:30"
},
+ {
+ "name": "doctrine/annotations",
+ "version": "v1.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/annotations.git",
+ "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
+ "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "1.*",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "doctrine/cache": "1.*",
+ "phpunit/phpunit": "4.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Annotations\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Docblock Annotations Parser",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "parser"
+ ],
+ "time": "2015-08-31 12:32:49"
+ },
+ {
+ "name": "doctrine/cache",
+ "version": "v1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/cache.git",
+ "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/cache/zipball/8c434000f420ade76a07c64cbe08ca47e5c101ca",
+ "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "conflict": {
+ "doctrine/common": ">2.2,<2.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=3.7",
+ "predis/predis": "~1.0",
+ "satooshi/php-coveralls": "~0.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Cache\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Caching library offering an object-oriented API for many cache backends",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "cache",
+ "caching"
+ ],
+ "time": "2015-08-31 12:36:41"
+ },
+ {
+ "name": "doctrine/collections",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/collections.git",
+ "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/6c1e4eef75f310ea1b3e30945e9f06e652128b8a",
+ "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Collections\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Collections Abstraction library",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "array",
+ "collections",
+ "iterator"
+ ],
+ "time": "2015-04-14 22:21:58"
+ },
+ {
+ "name": "doctrine/common",
+ "version": "v2.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/common.git",
+ "reference": "0009b8f0d4a917aabc971fb089eba80e872f83f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/common/zipball/0009b8f0d4a917aabc971fb089eba80e872f83f9",
+ "reference": "0009b8f0d4a917aabc971fb089eba80e872f83f9",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "1.*",
+ "doctrine/cache": "1.*",
+ "doctrine/collections": "1.*",
+ "doctrine/inflector": "1.*",
+ "doctrine/lexer": "1.*",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~3.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Common Library for Doctrine projects",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "collections",
+ "eventmanager",
+ "persistence",
+ "spl"
+ ],
+ "time": "2015-08-31 13:00:22"
+ },
+ {
+ "name": "doctrine/dbal",
+ "version": "v2.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/dbal.git",
+ "reference": "628c2256b646ae2417d44e063bce8aec5199d48d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/628c2256b646ae2417d44e063bce8aec5199d48d",
+ "reference": "628c2256b646ae2417d44e063bce8aec5199d48d",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/common": ">=2.4,<2.6-dev",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*",
+ "symfony/console": "2.*"
+ },
+ "suggest": {
+ "symfony/console": "For helpful console commands such as SQL execution and import of files."
+ },
+ "bin": [
+ "bin/doctrine-dbal"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\DBAL\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ }
+ ],
+ "description": "Database Abstraction Layer",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "database",
+ "dbal",
+ "persistence",
+ "queryobject"
+ ],
+ "time": "2015-01-12 21:52:47"
+ },
{
"name": "doctrine/inflector",
"version": "v1.0.1",
@@ -182,17 +530,71 @@
"time": "2014-12-20 21:24:13"
},
{
- "name": "guzzlehttp/psr7",
- "version": "1.1.0",
+ "name": "doctrine/lexer",
+ "version": "v1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/psr7.git",
- "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd"
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/af0e1758de355eb113917ad79c3c0e3604bce4bd",
- "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
+ "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Lexer\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "lexer",
+ "parser"
+ ],
+ "time": "2014-09-09 13:34:57"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
+ "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"shasum": ""
},
"require": {
@@ -216,7 +618,7 @@
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
- "src/functions.php"
+ "src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -237,20 +639,20 @@
"stream",
"uri"
],
- "time": "2015-06-24 19:55:15"
+ "time": "2015-08-15 19:32:36"
},
{
"name": "illuminate/bus",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/bus.git",
- "reference": "e04397b4dcbe36997439ce1654dcb2a187a68a4e"
+ "reference": "f06735223936552d56733a6e7657040029dd7a7d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/bus/zipball/e04397b4dcbe36997439ce1654dcb2a187a68a4e",
- "reference": "e04397b4dcbe36997439ce1654dcb2a187a68a4e",
+ "url": "https://api.github.com/repos/illuminate/bus/zipball/f06735223936552d56733a6e7657040029dd7a7d",
+ "reference": "f06735223936552d56733a6e7657040029dd7a7d",
"shasum": ""
},
"require": {
@@ -282,20 +684,20 @@
],
"description": "The Illuminate Bus package.",
"homepage": "http://laravel.com",
- "time": "2015-07-03 11:51:37"
+ "time": "2015-08-25 00:21:39"
},
{
"name": "illuminate/cache",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/cache.git",
- "reference": "a15a873448c24b9657a91cfd97f65539eb040771"
+ "reference": "dcd7551151aa752324562ad220e4fe4ee0687116"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/cache/zipball/a15a873448c24b9657a91cfd97f65539eb040771",
- "reference": "a15a873448c24b9657a91cfd97f65539eb040771",
+ "url": "https://api.github.com/repos/illuminate/cache/zipball/dcd7551151aa752324562ad220e4fe4ee0687116",
+ "reference": "dcd7551151aa752324562ad220e4fe4ee0687116",
"shasum": ""
},
"require": {
@@ -332,11 +734,11 @@
],
"description": "The Illuminate Cache package.",
"homepage": "http://laravel.com",
- "time": "2015-06-30 21:20:09"
+ "time": "2015-09-03 11:44:03"
},
{
"name": "illuminate/config",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/config.git",
@@ -381,16 +783,16 @@
},
{
"name": "illuminate/container",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
- "reference": "248cd26c38d1e29eac74d32d9fff158b2232a3c6"
+ "reference": "66621248395705cc64bc1ce9262b99b4bfa606f2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/container/zipball/248cd26c38d1e29eac74d32d9fff158b2232a3c6",
- "reference": "248cd26c38d1e29eac74d32d9fff158b2232a3c6",
+ "url": "https://api.github.com/repos/illuminate/container/zipball/66621248395705cc64bc1ce9262b99b4bfa606f2",
+ "reference": "66621248395705cc64bc1ce9262b99b4bfa606f2",
"shasum": ""
},
"require": {
@@ -420,20 +822,20 @@
],
"description": "The Illuminate Container package.",
"homepage": "http://laravel.com",
- "time": "2015-06-13 00:40:15"
+ "time": "2015-08-26 23:00:38"
},
{
"name": "illuminate/contracts",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
- "reference": "140dd18acb6be297fbd9870f8dda9d9d0a17c460"
+ "reference": "610aea16e3d91dfd85db4cf43703403a83b7cba2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/contracts/zipball/140dd18acb6be297fbd9870f8dda9d9d0a17c460",
- "reference": "140dd18acb6be297fbd9870f8dda9d9d0a17c460",
+ "url": "https://api.github.com/repos/illuminate/contracts/zipball/610aea16e3d91dfd85db4cf43703403a83b7cba2",
+ "reference": "610aea16e3d91dfd85db4cf43703403a83b7cba2",
"shasum": ""
},
"require": {
@@ -462,20 +864,20 @@
],
"description": "The Illuminate Contracts package.",
"homepage": "http://laravel.com",
- "time": "2015-07-14 01:36:14"
+ "time": "2015-08-31 12:59:22"
},
{
"name": "illuminate/database",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/database.git",
- "reference": "ad33fd28df334f248b97cd7ffd747b78635ec0c4"
+ "reference": "7655e39c507776c9b9226a8f5619ed9933d3396e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/database/zipball/ad33fd28df334f248b97cd7ffd747b78635ec0c4",
- "reference": "ad33fd28df334f248b97cd7ffd747b78635ec0c4",
+ "url": "https://api.github.com/repos/illuminate/database/zipball/7655e39c507776c9b9226a8f5619ed9933d3396e",
+ "reference": "7655e39c507776c9b9226a8f5619ed9933d3396e",
"shasum": ""
},
"require": {
@@ -521,20 +923,20 @@
"orm",
"sql"
],
- "time": "2015-07-20 16:50:34"
+ "time": "2015-09-03 14:53:32"
},
{
"name": "illuminate/events",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/events.git",
- "reference": "e10687896023649477a3f8617f3f4abfb39043ac"
+ "reference": "851c2351401145d81674991fe8491d560e620ec6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/events/zipball/e10687896023649477a3f8617f3f4abfb39043ac",
- "reference": "e10687896023649477a3f8617f3f4abfb39043ac",
+ "url": "https://api.github.com/repos/illuminate/events/zipball/851c2351401145d81674991fe8491d560e620ec6",
+ "reference": "851c2351401145d81674991fe8491d560e620ec6",
"shasum": ""
},
"require": {
@@ -566,20 +968,20 @@
],
"description": "The Illuminate Events package.",
"homepage": "http://laravel.com",
- "time": "2015-06-29 08:51:25"
+ "time": "2015-08-08 13:06:30"
},
{
"name": "illuminate/filesystem",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/filesystem.git",
- "reference": "74b0f13734a9dcd4a4e0be2a0324cfe76d825483"
+ "reference": "b93043ad8efe4a2ccd0bb202d8493255bb865720"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/filesystem/zipball/74b0f13734a9dcd4a4e0be2a0324cfe76d825483",
- "reference": "74b0f13734a9dcd4a4e0be2a0324cfe76d825483",
+ "url": "https://api.github.com/repos/illuminate/filesystem/zipball/b93043ad8efe4a2ccd0bb202d8493255bb865720",
+ "reference": "b93043ad8efe4a2ccd0bb202d8493255bb865720",
"shasum": ""
},
"require": {
@@ -616,11 +1018,11 @@
],
"description": "The Illuminate Filesystem package.",
"homepage": "http://laravel.com",
- "time": "2015-07-11 20:41:11"
+ "time": "2015-08-24 08:44:48"
},
{
"name": "illuminate/hashing",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/hashing.git",
@@ -664,16 +1066,16 @@
},
{
"name": "illuminate/mail",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/mail.git",
- "reference": "de000dc1d2b74b7d8e746a9be2af470f90b4655b"
+ "reference": "467d6b7d4b8edbd8acb94248c4b294108773760d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/mail/zipball/de000dc1d2b74b7d8e746a9be2af470f90b4655b",
- "reference": "de000dc1d2b74b7d8e746a9be2af470f90b4655b",
+ "url": "https://api.github.com/repos/illuminate/mail/zipball/467d6b7d4b8edbd8acb94248c4b294108773760d",
+ "reference": "467d6b7d4b8edbd8acb94248c4b294108773760d",
"shasum": ""
},
"require": {
@@ -711,11 +1113,11 @@
],
"description": "The Illuminate Mail package.",
"homepage": "http://laravel.com",
- "time": "2015-07-11 20:41:11"
+ "time": "2015-08-08 22:00:02"
},
{
"name": "illuminate/pipeline",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/pipeline.git",
@@ -759,16 +1161,16 @@
},
{
"name": "illuminate/support",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
- "reference": "2ba8a22a554aee9a412baa2e16e4387262d6d57c"
+ "reference": "c5389968d48517b3b51cfd8cd4abd72f0cc1575b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/support/zipball/2ba8a22a554aee9a412baa2e16e4387262d6d57c",
- "reference": "2ba8a22a554aee9a412baa2e16e4387262d6d57c",
+ "url": "https://api.github.com/repos/illuminate/support/zipball/c5389968d48517b3b51cfd8cd4abd72f0cc1575b",
+ "reference": "c5389968d48517b3b51cfd8cd4abd72f0cc1575b",
"shasum": ""
},
"require": {
@@ -808,20 +1210,20 @@
],
"description": "The Illuminate Support package.",
"homepage": "http://laravel.com",
- "time": "2015-07-20 16:31:36"
+ "time": "2015-09-03 15:47:41"
},
{
"name": "illuminate/validation",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/validation.git",
- "reference": "23848aae24e6513aacd92c154befb26e00ebb858"
+ "reference": "6192667064fd75ff70efb83c6f5366a9edeac275"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/validation/zipball/23848aae24e6513aacd92c154befb26e00ebb858",
- "reference": "23848aae24e6513aacd92c154befb26e00ebb858",
+ "url": "https://api.github.com/repos/illuminate/validation/zipball/6192667064fd75ff70efb83c6f5366a9edeac275",
+ "reference": "6192667064fd75ff70efb83c6f5366a9edeac275",
"shasum": ""
},
"require": {
@@ -858,20 +1260,20 @@
],
"description": "The Illuminate Validation package.",
"homepage": "http://laravel.com",
- "time": "2015-07-18 02:49:20"
+ "time": "2015-08-17 22:03:13"
},
{
"name": "illuminate/view",
- "version": "v5.1.8",
+ "version": "v5.1.16",
"source": {
"type": "git",
"url": "https://github.com/illuminate/view.git",
- "reference": "c5f6d9517ac956937adb44b081c44cb9ebca0ea6"
+ "reference": "82c5bc657554360a7325ace0761c24f26de9e771"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/view/zipball/c5f6d9517ac956937adb44b081c44cb9ebca0ea6",
- "reference": "c5f6d9517ac956937adb44b081c44cb9ebca0ea6",
+ "url": "https://api.github.com/repos/illuminate/view/zipball/82c5bc657554360a7325ace0761c24f26de9e771",
+ "reference": "82c5bc657554360a7325ace0761c24f26de9e771",
"shasum": ""
},
"require": {
@@ -905,20 +1307,20 @@
],
"description": "The Illuminate View package.",
"homepage": "http://laravel.com",
- "time": "2015-07-17 15:21:21"
+ "time": "2015-09-04 12:45:07"
},
{
"name": "intervention/image",
- "version": "2.3.1",
+ "version": "2.3.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
- "reference": "156f9d6f8a186c68b92f0c50084718f02dae1b5f"
+ "reference": "1124ff3c6298f0dcf9edf9156623904d7a5c3428"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Intervention/image/zipball/156f9d6f8a186c68b92f0c50084718f02dae1b5f",
- "reference": "156f9d6f8a186c68b92f0c50084718f02dae1b5f",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/1124ff3c6298f0dcf9edf9156623904d7a5c3428",
+ "reference": "1124ff3c6298f0dcf9edf9156623904d7a5c3428",
"shasum": ""
},
"require": {
@@ -967,7 +1369,88 @@
"thumbnail",
"watermark"
],
- "time": "2015-07-10 15:03:58"
+ "time": "2015-08-16 15:31:59"
+ },
+ {
+ "name": "league/flysystem",
+ "version": "1.0.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "7323424a9d39c24e597ed3f2144419dfbb52e086"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7323424a9d39c24e597ed3f2144419dfbb52e086",
+ "reference": "7323424a9d39c24e597ed3f2144419dfbb52e086",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "ext-fileinfo": "*",
+ "mockery/mockery": "~0.9",
+ "phpspec/phpspec": "^2.2",
+ "phpspec/prophecy-phpunit": "~1.0",
+ "phpunit/phpunit": "~4.1"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "league/flysystem-copy": "Allows you to use Copy.com storage",
+ "league/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ],
+ "time": "2015-09-05 12:06:41"
},
{
"name": "nesbot/carbon",
@@ -1207,16 +1690,16 @@
},
{
"name": "s9e/text-formatter",
- "version": "0.1.0",
+ "version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/s9e/TextFormatter.git",
- "reference": "ec60dea24268512c040016bbd54ff01f0d1c0443"
+ "reference": "4db4d250735125274c8cfd1ac41ef76eae7d15e8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/ec60dea24268512c040016bbd54ff01f0d1c0443",
- "reference": "ec60dea24268512c040016bbd54ff01f0d1c0443",
+ "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4db4d250735125274c8cfd1ac41ef76eae7d15e8",
+ "reference": "4db4d250735125274c8cfd1ac41ef76eae7d15e8",
"shasum": ""
},
"require": {
@@ -1262,7 +1745,7 @@
"parser",
"shortcodes"
],
- "time": "2015-08-12 12:44:18"
+ "time": "2015-09-06 18:52:07"
},
{
"name": "swiftmailer/swiftmailer",
@@ -1318,17 +1801,74 @@
"time": "2015-06-06 14:19:39"
},
{
- "name": "symfony/finder",
- "version": "v2.7.3",
+ "name": "symfony/console",
+ "version": "v2.7.4",
"source": {
"type": "git",
- "url": "https://github.com/symfony/Finder.git",
- "reference": "ae0f363277485094edc04c9f3cbe595b183b78e4"
+ "url": "git@github.com:symfony/Console.git",
+ "reference": "f1f7376766394f409b9b33a57a5675a52f8aad93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Finder/zipball/ae0f363277485094edc04c9f3cbe595b183b78e4",
- "reference": "ae0f363277485094edc04c9f3cbe595b183b78e4",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/f1f7376766394f409b9b33a57a5675a52f8aad93",
+ "reference": "f1f7376766394f409b9b33a57a5675a52f8aad93",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.1",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/process": "~2.1"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-09-03 11:40:38"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v2.7.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Finder.git",
+ "reference": "fff4b0c362640a0ab7355e2647b3d461608e9065"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Finder/zipball/fff4b0c362640a0ab7355e2647b3d461608e9065",
+ "reference": "fff4b0c362640a0ab7355e2647b3d461608e9065",
"shasum": ""
},
"require": {
@@ -1364,20 +1904,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2015-07-09 16:07:40"
+ "time": "2015-08-26 17:56:37"
},
{
"name": "symfony/http-foundation",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpFoundation.git",
- "reference": "863af6898081b34c65d42100c370b9f3c51b70ca"
+ "reference": "7253c2041652353e71560bbd300d6256d170ddaf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/863af6898081b34c65d42100c370b9f3c51b70ca",
- "reference": "863af6898081b34c65d42100c370b9f3c51b70ca",
+ "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/7253c2041652353e71560bbd300d6256d170ddaf",
+ "reference": "7253c2041652353e71560bbd300d6256d170ddaf",
"shasum": ""
},
"require": {
@@ -1417,20 +1957,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2015-07-22 10:11:00"
+ "time": "2015-08-27 06:45:45"
},
{
"name": "symfony/translation",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Translation.git",
- "reference": "c8dc34cc936152c609cdd722af317e4239d10dd6"
+ "reference": "485877661835e188cd78345c6d4eef1290d17571"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Translation/zipball/c8dc34cc936152c609cdd722af317e4239d10dd6",
- "reference": "c8dc34cc936152c609cdd722af317e4239d10dd6",
+ "url": "https://api.github.com/repos/symfony/Translation/zipball/485877661835e188cd78345c6d4eef1290d17571",
+ "reference": "485877661835e188cd78345c6d4eef1290d17571",
"shasum": ""
},
"require": {
@@ -1442,7 +1982,7 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.7",
- "symfony/intl": "~2.3",
+ "symfony/intl": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.2"
},
@@ -1478,20 +2018,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2015-07-09 16:07:40"
+ "time": "2015-09-06 08:36:38"
},
{
"name": "symfony/yaml",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
- "reference": "71340e996171474a53f3d29111d046be4ad8a0ff"
+ "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff",
- "reference": "71340e996171474a53f3d29111d046be4ad8a0ff",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/2dc7b06c065df96cc686c66da2705e5e18aef661",
+ "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661",
"shasum": ""
},
"require": {
@@ -1527,7 +2067,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2015-07-28 14:07:07"
+ "time": "2015-08-24 07:13:45"
},
{
"name": "tobscure/json-api",
@@ -1770,16 +2310,16 @@
},
{
"name": "phpspec/phpspec",
- "version": "2.2.1",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/phpspec.git",
- "reference": "e9a40577323e67f1de2e214abf32976a0352d8f8"
+ "reference": "36635a903bdeb54899d7407bc95610501fd98559"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/phpspec/zipball/e9a40577323e67f1de2e214abf32976a0352d8f8",
- "reference": "e9a40577323e67f1de2e214abf32976a0352d8f8",
+ "url": "https://api.github.com/repos/phpspec/phpspec/zipball/36635a903bdeb54899d7407bc95610501fd98559",
+ "reference": "36635a903bdeb54899d7407bc95610501fd98559",
"shasum": ""
},
"require": {
@@ -1791,15 +2331,14 @@
"symfony/console": "~2.3",
"symfony/event-dispatcher": "~2.1",
"symfony/finder": "~2.1",
- "symfony/process": "~2.1",
+ "symfony/process": "^2.6",
"symfony/yaml": "~2.1"
},
"require-dev": {
"behat/behat": "^3.0.11",
"bossa/phpspec2-expect": "~1.0",
"phpunit/phpunit": "~4.4",
- "symfony/filesystem": "~2.1",
- "symfony/process": "~2.1"
+ "symfony/filesystem": "~2.1"
},
"suggest": {
"phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters"
@@ -1844,20 +2383,20 @@
"testing",
"tests"
],
- "time": "2015-05-30 15:21:40"
+ "time": "2015-09-07 07:07:37"
},
{
"name": "phpspec/prophecy",
- "version": "v1.4.1",
+ "version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
+ "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
- "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+ "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
"shasum": ""
},
"require": {
@@ -1904,7 +2443,7 @@
"spy",
"stub"
],
- "time": "2015-04-27 22:15:08"
+ "time": "2015-08-13 10:07:40"
},
{
"name": "sebastian/comparator",
@@ -2143,16 +2682,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "2.3.3",
+ "version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "c1a26c729508f73560c1a4f767f60b8ab6b4a666"
+ "reference": "11a2545c44a5915f883e2e5ec12e14ed345e3ab2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/c1a26c729508f73560c1a4f767f60b8ab6b4a666",
- "reference": "c1a26c729508f73560c1a4f767f60b8ab6b4a666",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/11a2545c44a5915f883e2e5ec12e14ed345e3ab2",
+ "reference": "11a2545c44a5915f883e2e5ec12e14ed345e3ab2",
"shasum": ""
},
"require": {
@@ -2213,77 +2752,20 @@
"phpcs",
"standards"
],
- "time": "2015-06-24 03:16:23"
- },
- {
- "name": "symfony/console",
- "version": "v2.7.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/Console.git",
- "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e",
- "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.9"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/event-dispatcher": "~2.1",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.1"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/process": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.7-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Console Component",
- "homepage": "https://symfony.com",
- "time": "2015-07-28 15:18:12"
+ "time": "2015-09-09 00:18:50"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3"
+ "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
- "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/b58c916f1db03a611b72dd702564f30ad8fe83fa",
+ "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa",
"shasum": ""
},
"require": {
@@ -2328,20 +2810,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-06-18 19:21:56"
+ "time": "2015-08-24 07:13:45"
},
{
"name": "symfony/process",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
- "reference": "48aeb0e48600321c272955132d7606ab0a49adb3"
+ "reference": "f7b3f73f70a7f8f49a1c838dc3debbf054732d8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Process/zipball/48aeb0e48600321c272955132d7606ab0a49adb3",
- "reference": "48aeb0e48600321c272955132d7606ab0a49adb3",
+ "url": "https://api.github.com/repos/symfony/Process/zipball/f7b3f73f70a7f8f49a1c838dc3debbf054732d8e",
+ "reference": "f7b3f73f70a7f8f49a1c838dc3debbf054732d8e",
"shasum": ""
},
"require": {
@@ -2377,7 +2859,7 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2015-07-01 11:25:50"
+ "time": "2015-08-27 06:45:45"
}
],
"aliases": [],
diff --git a/framework/core/js/admin/src/components/AppearancePage.js b/framework/core/js/admin/src/components/AppearancePage.js
index 67b1884e0..2b6fd436c 100644
--- a/framework/core/js/admin/src/components/AppearancePage.js
+++ b/framework/core/js/admin/src/components/AppearancePage.js
@@ -44,6 +44,7 @@ export default class AppearancePage extends Component {
{Button.component({
className: 'Button Button--primary',
+ type: 'submit',
children: 'Save Changes',
loading: this.loading
})}
diff --git a/framework/core/js/admin/src/components/EditCustomCssModal.js b/framework/core/js/admin/src/components/EditCustomCssModal.js
index 1619a66ec..651a4f183 100644
--- a/framework/core/js/admin/src/components/EditCustomCssModal.js
+++ b/framework/core/js/admin/src/components/EditCustomCssModal.js
@@ -30,6 +30,7 @@ export default class EditCustomCssModal extends Modal {
{Button.component({
className: 'Button Button--primary',
+ type: 'submit',
children: 'Save Changes',
loading: this.loading
})}
diff --git a/framework/core/js/forum/src/ForumApp.js b/framework/core/js/forum/src/ForumApp.js
index 9e7edd397..2abeea524 100644
--- a/framework/core/js/forum/src/ForumApp.js
+++ b/framework/core/js/forum/src/ForumApp.js
@@ -4,6 +4,7 @@ import Search from 'flarum/components/Search';
import Composer from 'flarum/components/Composer';
import ReplyComposer from 'flarum/components/ReplyComposer';
import DiscussionPage from 'flarum/components/DiscussionPage';
+import SignUpModal from 'flarum/components/SignUpModal';
export default class ForumApp extends App {
constructor(...args) {
@@ -76,4 +77,27 @@ export default class ForumApp extends App {
return this.current instanceof DiscussionPage &&
this.current.discussion === discussion;
}
+
+ /**
+ * Callback for when an external authenticator (social login) action has
+ * completed.
+ *
+ * If the payload indicates that the user has been logged in, then the page
+ * will be reloaded. Otherwise, a SignUpModal will be opened, prefilled
+ * with the provided details.
+ *
+ * @param {Object} payload A dictionary of props to pass into the sign up
+ * modal. A truthy `authenticated` prop indicates that the user has logged
+ * in, and thus the page is reloaded.
+ * @public
+ */
+ authenticationComplete(payload) {
+ if (payload.authenticated) {
+ window.location.reload();
+ } else {
+ const modal = new SignUpModal(payload);
+ this.modal.show(modal);
+ modal.$('[name=password]').focus();
+ }
+ }
}
diff --git a/framework/core/js/forum/src/components/Activity.js b/framework/core/js/forum/src/components/Activity.js
deleted file mode 100644
index ea5821bdb..000000000
--- a/framework/core/js/forum/src/components/Activity.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import Component from 'flarum/Component';
-import humanTime from 'flarum/helpers/humanTime';
-import avatar from 'flarum/helpers/avatar';
-
-/**
- * The `Activity` component represents a piece of activity of a user's activity
- * feed. Subclasses should implement the `description` and `content` methods.
- *
- * ### Props
- *
- * - `activity`
- *
- * @abstract
- */
-export default class Activity extends Component {
- view() {
- const activity = this.props.activity;
-
- return (
-
- {avatar(this.user(), {className: 'Activity-avatar'})}
-
-
- {this.description()}
- {humanTime(this.time())}
-
-
- {this.content()}
-
- );
- }
-
- /**
- * Get the user whose avatar should be displayed.
- *
- * @return {User}
- * @abstract
- */
- user() {
- }
-
- /**
- * Get the time of the activity.
- *
- * @return {Date}
- * @abstract
- */
- time() {
- }
-
- /**
- * Get the description of the activity.
- *
- * @return {VirtualElement}
- */
- description() {
- }
-
- /**
- * Get the content to show below the activity description.
- *
- * @return {VirtualElement}
- */
- content() {
- }
-}
diff --git a/framework/core/js/forum/src/components/LogInButton.js b/framework/core/js/forum/src/components/LogInButton.js
new file mode 100644
index 000000000..3de450589
--- /dev/null
+++ b/framework/core/js/forum/src/components/LogInButton.js
@@ -0,0 +1,30 @@
+import Button from 'flarum/components/Button';
+
+/**
+ * The `LogInButton` component displays a social login button which will open
+ * a popup window containing the specified path.
+ *
+ * ### Props
+ *
+* - `path`
+ */
+export default class LogInButton extends Button {
+ static initProps(props) {
+ props.className = (props.className || '') + ' LogInButton';
+
+ props.onclick = function() {
+ const width = 620;
+ const height = 400;
+ const $window = $(window);
+
+ window.open(app.forum.attribute('baseUrl') + props.path, 'logInPopup',
+ `width=${width},` +
+ `height=${height},` +
+ `top=${$window.height() / 2 - height / 2},` +
+ `left=${$window.width() / 2 - width / 2},` +
+ 'status=no,scrollbars=no,resizable=no');
+ };
+
+ super.initProps(props);
+ }
+}
diff --git a/framework/core/js/forum/src/components/LogInButtons.js b/framework/core/js/forum/src/components/LogInButtons.js
new file mode 100644
index 000000000..ef2c853e9
--- /dev/null
+++ b/framework/core/js/forum/src/components/LogInButtons.js
@@ -0,0 +1,25 @@
+import Component from 'flarum/Component';
+import ItemList from 'flarum/utils/ItemList';
+
+/**
+ * The `LogInButtons` component displays a collection of social login buttons.
+ */
+export default class LogInButtons extends Component {
+ view() {
+ return (
+
+ {this.items().toArray()}
+
+ );
+ }
+
+ /**
+ * Build a list of LogInButton components.
+ *
+ * @return {ItemList}
+ * @public
+ */
+ items() {
+ return new ItemList();
+ }
+}
diff --git a/framework/core/js/forum/src/components/LogInModal.js b/framework/core/js/forum/src/components/LogInModal.js
index ad839a863..ad2ac1769 100644
--- a/framework/core/js/forum/src/components/LogInModal.js
+++ b/framework/core/js/forum/src/components/LogInModal.js
@@ -3,6 +3,7 @@ import ForgotPasswordModal from 'flarum/components/ForgotPasswordModal';
import SignUpModal from 'flarum/components/SignUpModal';
import Alert from 'flarum/components/Alert';
import Button from 'flarum/components/Button';
+import LogInButtons from 'flarum/components/LogInButtons';
/**
* The `LogInModal` component displays a modal dialog with a login form.
@@ -42,6 +43,8 @@ export default class LogInModal extends Modal {
content() {
return [
+
+
{app.trans('core.forgot_password_link')}
+
{app.forum.attribute('allowSignUp') ? (
{app.trans('core.before_sign_up_link')}{' '}
@@ -84,6 +88,8 @@ export default class LogInModal extends Modal {
/**
* Open the forgot password modal, prefilling it with an email if the user has
* entered one.
+ *
+ * @public
*/
forgotPassword() {
const email = this.email();
@@ -95,6 +101,8 @@ export default class LogInModal extends Modal {
/**
* Open the sign up modal, prefilling it with an email/username/password if
* the user has entered one.
+ *
+ * @public
*/
signUp() {
const props = {password: this.password()};
diff --git a/framework/core/js/forum/src/components/SignUpModal.js b/framework/core/js/forum/src/components/SignUpModal.js
index 0cbdb231e..3d6ffb1df 100644
--- a/framework/core/js/forum/src/components/SignUpModal.js
+++ b/framework/core/js/forum/src/components/SignUpModal.js
@@ -2,6 +2,7 @@ import Modal from 'flarum/components/Modal';
import LogInModal from 'flarum/components/LogInModal';
import avatar from 'flarum/helpers/avatar';
import Button from 'flarum/components/Button';
+import LogInButtons from 'flarum/components/LogInButtons';
/**
* The `SignUpModal` component displays a modal dialog with a singup form.
@@ -11,6 +12,7 @@ import Button from 'flarum/components/Button';
* - `username`
* - `email`
* - `password`
+ * - `token` An email token to sign up with.
*/
export default class SignUpModal extends Modal {
constructor(...args) {
@@ -65,7 +67,9 @@ export default class SignUpModal extends Modal {
}
body() {
- const body = [(
+ const body = [
+ this.props.token ? '' : ,
+
- )];
+ ];
if (this.welcomeUser) {
const user = this.welcomeUser;
@@ -115,20 +121,12 @@ export default class SignUpModal extends Modal {
{avatar(user)}
{app.trans('core.welcome_user', {user})}
- {!user.isActivated() ? [
-
{app.trans('core.confirmation_email_sent', {email: {user.email()}})}
,
-
-
- {app.trans('core.go_to', {location: emailProviderName})}
-
-
- ] : (
-
-
-
- )}
+
{app.trans('core.confirmation_email_sent', {email: {user.email()}})}
,
+
+
+ {app.trans('core.go_to', {location: emailProviderName})}
+
+
@@ -150,6 +148,8 @@ export default class SignUpModal extends Modal {
/**
* Open the log in modal, prefilling it with an email/username/password if
* the user has entered one.
+ *
+ * @public
*/
logIn() {
const props = {
@@ -161,7 +161,7 @@ export default class SignUpModal extends Modal {
}
onready() {
- if (this.props.username) {
+ if (this.props.username && !this.props.token) {
this.$('[name=email]').select();
} else {
super.onready();
@@ -175,24 +175,50 @@ export default class SignUpModal extends Modal {
const data = this.submitData();
- app.store.createRecord('users').save(data).then(
- user => {
- this.welcomeUser = user;
- this.loading = false;
- m.redraw();
+ app.request({
+ url: app.forum.attribute('baseUrl') + '/register',
+ method: 'POST',
+ data
+ }).then(
+ payload => {
+ const user = app.store.pushPayload(payload);
+
+ // If the user's new account has been activated, then we can assume
+ // that they have been logged in too. Thus, we will reload the page.
+ // Otherwise, we will show a message asking them to check their email.
+ if (user.isActivated()) {
+ window.location.reload();
+ } else {
+ this.welcomeUser = user;
+ this.loading = false;
+ m.redraw();
+ }
},
response => {
this.loading = false;
- this.handleErrors(response.errors);
+ this.handleErrors(response);
}
);
}
+ /**
+ * Get the data that should be submitted in the sign-up request.
+ *
+ * @return {Object}
+ * @public
+ */
submitData() {
- return {
+ const data = {
username: this.username(),
- email: this.email(),
- password: this.password()
+ email: this.email()
};
+
+ if (this.props.token) {
+ data.token = this.props.token;
+ } else {
+ data.password = this.password();
+ }
+
+ return data;
}
}
diff --git a/framework/core/js/lib/Session.js b/framework/core/js/lib/Session.js
index 28b7ec40b..a6c121b49 100644
--- a/framework/core/js/lib/Session.js
+++ b/framework/core/js/lib/Session.js
@@ -16,6 +16,7 @@ export default class Session {
* The token that was used for authentication.
*
* @type {String|null}
+ * @public
*/
this.token = token;
}
@@ -26,6 +27,7 @@ export default class Session {
* @param {String} identification The username/email.
* @param {String} password
* @return {Promise}
+ * @public
*/
login(identification, password) {
return app.request({
@@ -38,6 +40,8 @@ export default class Session {
/**
* Log the user out.
+ *
+ * @public
*/
logout() {
window.location = app.forum.attribute('baseUrl') + '/logout?token=' + this.token;
@@ -48,8 +52,11 @@ export default class Session {
* XMLHttpRequest object.
*
* @param {XMLHttpRequest} xhr
+ * @public
*/
authorize(xhr) {
- xhr.setRequestHeader('Authorization', 'Token ' + this.token);
+ if (this.token) {
+ xhr.setRequestHeader('Authorization', 'Token ' + this.token);
+ }
}
}
diff --git a/framework/core/js/lib/components/Button.js b/framework/core/js/lib/components/Button.js
index a2ae2d7de..eed25c372 100644
--- a/framework/core/js/lib/components/Button.js
+++ b/framework/core/js/lib/components/Button.js
@@ -25,7 +25,8 @@ export default class Button extends Component {
delete attrs.children;
- attrs.className = (attrs.className || '');
+ attrs.className = attrs.className || '';
+ attrs.type = attrs.type || 'button';
const iconName = extract(attrs, 'icon');
if (iconName) attrs.className += ' hasIcon';
diff --git a/framework/core/js/lib/components/Modal.js b/framework/core/js/lib/components/Modal.js
index 38a42c280..1d5b81493 100644
--- a/framework/core/js/lib/components/Modal.js
+++ b/framework/core/js/lib/components/Modal.js
@@ -129,7 +129,7 @@ export default class Modal extends Component {
m.redraw();
if (errors) {
- this.$('form [name=' + errors[0].path + ']').select();
+ this.$('form [name=' + errors[0].source.pointer.replace('/data/attributes/', '') + ']').select();
} else {
this.$('form :input:first').select();
}
diff --git a/framework/core/less/forum/DiscussionListItem.less b/framework/core/less/forum/DiscussionListItem.less
index da5228a29..22b775d66 100644
--- a/framework/core/less/forum/DiscussionListItem.less
+++ b/framework/core/less/forum/DiscussionListItem.less
@@ -45,7 +45,7 @@
text-overflow: ellipsis;
.read & {
- color: mix(@heading-color, @body-bg, 60%);
+ color: mix(@heading-color, @body-bg, 55%);
}
.unread & {
font-weight: 600;
@@ -59,6 +59,12 @@
> li {
display: inline;
+ opacity: 0.7;
+ .transition(opacity 0.2s);
+
+ .DiscussionListItem:hover &, .DiscussionListItem.active & {
+ opacity: 1;
+ }
}
.username {
font-weight: bold;
diff --git a/framework/core/less/forum/LogInButton.less b/framework/core/less/forum/LogInButton.less
new file mode 100644
index 000000000..f0fd6c1e9
--- /dev/null
+++ b/framework/core/less/forum/LogInButton.less
@@ -0,0 +1,11 @@
+.LogInButton {
+ &:extend(.Button--block);
+}
+.LogInButtons {
+ width: 200px;
+ margin: 0 auto 20px;
+
+ .LogInButton {
+ margin-bottom: 5px;
+ }
+}
diff --git a/framework/core/less/forum/Post.less b/framework/core/less/forum/Post.less
index 60c4f7328..fa84e43fe 100644
--- a/framework/core/less/forum/Post.less
+++ b/framework/core/less/forum/Post.less
@@ -139,7 +139,7 @@
font-size: 100%;
font-weight: bold;
}
- img {
+ img, iframe {
max-width: 100%;
}
}
diff --git a/framework/core/less/forum/UserCard.less b/framework/core/less/forum/UserCard.less
index fe10abb26..731ea28c2 100644
--- a/framework/core/less/forum/UserCard.less
+++ b/framework/core/less/forum/UserCard.less
@@ -126,6 +126,9 @@
& .icon {
font-size: 14px;
}
+ &.online .fa-circle {
+ color: @online-user-circle-color;
+ }
&.online .icon {
font-size: 12px;
}
diff --git a/framework/core/less/forum/app.less b/framework/core/less/forum/app.less
index 40bac2b0a..d9b4a3122 100644
--- a/framework/core/less/forum/app.less
+++ b/framework/core/less/forum/app.less
@@ -10,6 +10,7 @@
@import "EditUserModal.less";
@import "Hero.less";
@import "IndexPage.less";
+@import "LogInButton.less";
@import "LogInModal.less";
@import "NotificationGrid.less";
@import "NotificationList.less";
diff --git a/framework/core/less/lib/FormControl.less b/framework/core/less/lib/FormControl.less
index 66c09f2ac..73c562c2e 100755
--- a/framework/core/less/lib/FormControl.less
+++ b/framework/core/less/lib/FormControl.less
@@ -27,8 +27,7 @@
&[disabled],
&[readonly],
fieldset[disabled] & {
- // background-color: @input-bg-disabled;
- opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655
+ opacity: 0.5;
}
&[disabled],
diff --git a/framework/core/less/lib/variables.less b/framework/core/less/lib/variables.less
index bbca8b077..3b39e1357 100755
--- a/framework/core/less/lib/variables.less
+++ b/framework/core/less/lib/variables.less
@@ -27,7 +27,7 @@
@text-color: #111;
@link-color: saturate(@primary-color, 10%);
@heading-color: @text-color;
- @muted-color: hsl(@secondary-hue, min(25%, @secondary-sat), 60%);
+ @muted-color: hsl(@secondary-hue, min(25%, @secondary-sat), 55%);
@muted-more-color: #aaa;
@shadow-color: rgba(0, 0, 0, 0.35);
@@ -130,3 +130,5 @@
@tooltip-bg: rgba(0, 0, 0, 0.9);
@tooltip-color: #fff;
+
+@online-user-circle-color: #7FBA00;
diff --git a/framework/core/locale/en.yml b/framework/core/locale/en.yml
index e68aac89a..c40cdc9e7 100644
--- a/framework/core/locale/en.yml
+++ b/framework/core/locale/en.yml
@@ -28,7 +28,6 @@ core:
discussion_started: "Started {ago} by {username}"
discussion_title: Discussion Title
discussions: Discussions
- dismiss: Dismiss
edit: Edit
editing_post: "Post #{number} in {discussion}"
email: Email
diff --git a/framework/core/migrations/2015_02_24_000000_create_email_tokens_table.php b/framework/core/migrations/2015_02_24_000000_create_email_tokens_table.php
index e14bff848..a199c3e03 100644
--- a/framework/core/migrations/2015_02_24_000000_create_email_tokens_table.php
+++ b/framework/core/migrations/2015_02_24_000000_create_email_tokens_table.php
@@ -23,8 +23,8 @@ class CreateEmailTokensTable extends Migration
{
$this->schema->create('email_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
- $table->integer('user_id')->unsigned();
$table->string('email', 150);
+ $table->integer('user_id')->unsigned();
$table->timestamp('created_at');
});
}
diff --git a/framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php b/framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php
new file mode 100644
index 000000000..5e8ced5e1
--- /dev/null
+++ b/framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Flarum\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+
+class MakeEmailTokensUserIdColumnNullable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ $this->schema->table('email_tokens', function (Blueprint $table) {
+ $table->integer('user_id')->unsigned()->nullable()->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ $this->schema->table('email_tokens', function (Blueprint $table) {
+ $table->integer('user_id')->unsigned()->change();
+ });
+ }
+}
diff --git a/framework/core/src/Admin/Middleware/LoginWithCookieAndCheckAdmin.php b/framework/core/src/Admin/Middleware/LoginWithCookieAndCheckAdmin.php
index b1b0d6d72..04169ca3f 100644
--- a/framework/core/src/Admin/Middleware/LoginWithCookieAndCheckAdmin.php
+++ b/framework/core/src/Admin/Middleware/LoginWithCookieAndCheckAdmin.php
@@ -15,36 +15,32 @@ use Illuminate\Contracts\Container\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Stratigility\MiddlewareInterface;
+use Flarum\Forum\Middleware\LoginWithCookie;
+use Flarum\Core\Exceptions\PermissionDeniedException;
-class LoginWithCookieAndCheckAdmin implements MiddlewareInterface
+class LoginWithCookieAndCheckAdmin extends LoginWithCookie
{
- /**
- * @var Container
- */
- protected $app;
-
- /**
- * @param Container $app
- */
- public function __construct(Container $app)
- {
- $this->app = $app;
- }
-
/**
* {@inheritdoc}
*/
public function __invoke(Request $request, Response $response, callable $out = null)
{
- if (($token = array_get($request->getCookieParams(), 'flarum_remember')) &&
- ($accessToken = AccessToken::valid($token)) &&
- $accessToken->user->isAdmin()
- ) {
- $this->app->instance('flarum.actor', $accessToken->user);
- } else {
- die('Access Denied');
+ if (! $this->logIn($request)) {
+ throw new PermissionDeniedException;
}
return $out ? $out($request, $response) : $response;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getToken(Request $request)
+ {
+ $token = parent::getToken($request);
+
+ if ($token && $token->user && $token->user->isAdmin()) {
+ return $token;
+ }
+ }
}
diff --git a/framework/core/src/Api/AccessToken.php b/framework/core/src/Api/AccessToken.php
index fdad6f4ec..9a6efa56e 100644
--- a/framework/core/src/Api/AccessToken.php
+++ b/framework/core/src/Api/AccessToken.php
@@ -12,6 +12,7 @@ namespace Flarum\Api;
use Flarum\Core\Model;
use DateTime;
+use Exception;
/**
* @todo document database columns with @property
@@ -55,14 +56,13 @@ class AccessToken extends Model
}
/**
- * Get the given token only if it is valid.
+ * Check that the token has not expired.
*
- * @param string $token
- * @return static|null
+ * @return bool
*/
- public static function valid($token)
+ public function isValid()
{
- return static::where('id', $token)->where('expires_at', '>', new DateTime)->first();
+ return $this->expires_at > new DateTime;
}
/**
diff --git a/framework/core/src/Api/Actions/DeleteAction.php b/framework/core/src/Api/Actions/DeleteAction.php
index f460063c7..2d1863695 100644
--- a/framework/core/src/Api/Actions/DeleteAction.php
+++ b/framework/core/src/Api/Actions/DeleteAction.php
@@ -13,7 +13,7 @@ namespace Flarum\Api\Actions;
use Flarum\Api\Request;
use Zend\Diactoros\Response\EmptyResponse;
-abstract class DeleteAction extends JsonApiAction
+abstract class DeleteAction implements Action
{
/**
* Delegate deletion of the resource, and return a 204 No Content
@@ -22,7 +22,7 @@ abstract class DeleteAction extends JsonApiAction
* @param \Flarum\Api\Request $request
* @return \Psr\Http\Message\ResponseInterface
*/
- public function respond(Request $request)
+ public function handle(Request $request)
{
$this->delete($request);
diff --git a/framework/core/src/Api/Actions/Extensions/UpdateAction.php b/framework/core/src/Api/Actions/Extensions/UpdateAction.php
index 3a44dea15..4f2e633b5 100644
--- a/framework/core/src/Api/Actions/Extensions/UpdateAction.php
+++ b/framework/core/src/Api/Actions/Extensions/UpdateAction.php
@@ -10,13 +10,13 @@
namespace Flarum\Api\Actions\Extensions;
-use Flarum\Api\Actions\JsonApiAction;
+use Flarum\Api\Actions\Action;
use Flarum\Api\Request;
use Illuminate\Contracts\Bus\Dispatcher;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Support\ExtensionManager;
-class UpdateAction extends JsonApiAction
+class UpdateAction implements Action
{
protected $extensions;
@@ -25,7 +25,7 @@ class UpdateAction extends JsonApiAction
$this->extensions = $extensions;
}
- protected function respond(Request $request)
+ public function handle(Request $request)
{
if (! $request->actor->isAdmin()) {
throw new PermissionDeniedException;
diff --git a/framework/core/src/Api/Actions/ForgotAction.php b/framework/core/src/Api/Actions/ForgotAction.php
index 95e56f49b..fa4918248 100644
--- a/framework/core/src/Api/Actions/ForgotAction.php
+++ b/framework/core/src/Api/Actions/ForgotAction.php
@@ -16,7 +16,7 @@ use Flarum\Core\Users\Commands\RequestPasswordReset;
use Illuminate\Contracts\Bus\Dispatcher;
use Zend\Diactoros\Response\EmptyResponse;
-class ForgotAction extends JsonApiAction
+class ForgotAction implements Action
{
protected $users;
@@ -34,7 +34,7 @@ class ForgotAction extends JsonApiAction
* @param \Flarum\Api\Request $request
* @return \Psr\Http\Message\ResponseInterface
*/
- public function respond(Request $request)
+ public function handle(Request $request)
{
$email = $request->get('email');
diff --git a/framework/core/src/Api/Actions/JsonApiAction.php b/framework/core/src/Api/Actions/JsonApiAction.php
deleted file mode 100644
index 09e54dcf4..000000000
--- a/framework/core/src/Api/Actions/JsonApiAction.php
+++ /dev/null
@@ -1,63 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Api\Actions;
-
-use Flarum\Api\Request;
-use Illuminate\Contracts\Validation\ValidationException;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
-use Flarum\Core\Exceptions\ValidationFailureException;
-use Flarum\Core\Exceptions\PermissionDeniedException;
-use Zend\Diactoros\Response\JsonResponse;
-
-abstract class JsonApiAction implements Action
-{
- /**
- * Handle an API request and return an API response, handling any relevant
- * (API-related) exceptions that are thrown.
- *
- * @param Request $request
- * @return \Psr\Http\Message\ResponseInterface
- */
- public function handle(Request $request)
- {
- // TODO: This is gross. Move this error handling code to middleware?
- try {
- return $this->respond($request);
- } catch (ValidationException $e) {
- $errors = [];
- foreach ($e->errors()->toArray() as $field => $messages) {
- $errors[] = [
- 'detail' => implode("\n", $messages),
- 'path' => $field
- ];
- }
- return new JsonResponse(['errors' => $errors], 422);
- } catch (\Flarum\Core\Exceptions\ValidationException $e) {
- $errors = [];
- foreach ($e->getMessages() as $path => $detail) {
- $errors[] = compact('path', 'detail');
- }
- return new JsonResponse(['errors' => $errors], 422);
- } catch (PermissionDeniedException $e) {
- return new JsonResponse(null, 401);
- } catch (ModelNotFoundException $e) {
- return new JsonResponse(null, 404);
- }
- }
-
- /**
- * Handle an API request and return an API response.
- *
- * @param Request $request
- * @return \Psr\Http\Message\ResponseInterface
- */
- abstract protected function respond(Request $request);
-}
diff --git a/framework/core/src/Api/Actions/SerializeAction.php b/framework/core/src/Api/Actions/SerializeAction.php
index b60b98c45..16398282b 100644
--- a/framework/core/src/Api/Actions/SerializeAction.php
+++ b/framework/core/src/Api/Actions/SerializeAction.php
@@ -19,7 +19,7 @@ use Tobscure\JsonApi\Document;
use Tobscure\JsonApi\SerializerInterface;
use Zend\Diactoros\Response\JsonResponse;
-abstract class SerializeAction extends JsonApiAction
+abstract class SerializeAction implements Action
{
/**
* The name of the serializer class to output results with.
@@ -77,7 +77,7 @@ abstract class SerializeAction extends JsonApiAction
* @param Request $request
* @return JsonResponse
*/
- public function respond(Request $request)
+ public function handle(Request $request)
{
$request = $this->buildJsonApiRequest($request);
$document = new Document();
diff --git a/framework/core/src/Api/Actions/TokenAction.php b/framework/core/src/Api/Actions/TokenAction.php
index 4dfeff869..5d4da1ec6 100644
--- a/framework/core/src/Api/Actions/TokenAction.php
+++ b/framework/core/src/Api/Actions/TokenAction.php
@@ -18,7 +18,7 @@ use Flarum\Events\UserEmailChangeWasRequested;
use Illuminate\Contracts\Bus\Dispatcher;
use Zend\Diactoros\Response\JsonResponse;
-class TokenAction extends JsonApiAction
+class TokenAction implements Action
{
protected $users;
@@ -37,7 +37,7 @@ class TokenAction extends JsonApiAction
* @return \Psr\Http\Message\ResponseInterface
* @throws PermissionDeniedException
*/
- public function respond(Request $request)
+ public function handle(Request $request)
{
$identification = $request->get('identification');
$password = $request->get('password');
diff --git a/framework/core/src/Api/Actions/Users/CreateAction.php b/framework/core/src/Api/Actions/Users/CreateAction.php
index d88117f2f..92b85730e 100644
--- a/framework/core/src/Api/Actions/Users/CreateAction.php
+++ b/framework/core/src/Api/Actions/Users/CreateAction.php
@@ -73,8 +73,12 @@ class CreateAction extends BaseCreateAction
*/
protected function create(JsonApiRequest $request)
{
- return $this->bus->dispatch(
+ $user = $this->bus->dispatch(
new RegisterUser($request->actor, $request->get('data'))
);
+
+ $request->actor = $user;
+
+ return $user;
}
}
diff --git a/framework/core/src/Api/Client.php b/framework/core/src/Api/Client.php
index 197e5e46a..6f05dc2ff 100644
--- a/framework/core/src/Api/Client.php
+++ b/framework/core/src/Api/Client.php
@@ -12,6 +12,8 @@ namespace Flarum\Api;
use Flarum\Core\Users\User;
use Illuminate\Contracts\Container\Container;
+use Exception;
+use Flarum\Api\Middleware\JsonApiErrors;
class Client
{
@@ -38,10 +40,16 @@ class Client
*/
public function send(User $actor, $actionClass, array $input = [])
{
- /** @var \Flarum\Api\Actions\JsonApiAction $action */
+ /** @var \Flarum\Api\Actions\Action $action */
$action = $this->container->make($actionClass);
- $response = $action->handle(new Request($input, $actor));
+ try {
+ $response = $action->handle(new Request($input, $actor));
+ } catch (Exception $e) {
+ $middleware = new JsonApiErrors();
+
+ $response = $middleware->handle($e);
+ }
return new Response($response);
}
diff --git a/framework/core/src/Api/Middleware/JsonApiErrors.php b/framework/core/src/Api/Middleware/JsonApiErrors.php
index 0b76a70a1..bf55f2b99 100644
--- a/framework/core/src/Api/Middleware/JsonApiErrors.php
+++ b/framework/core/src/Api/Middleware/JsonApiErrors.php
@@ -10,35 +10,65 @@
namespace Flarum\Api\Middleware;
+use Flarum\Core\Exceptions\JsonApiSerializable;
+use Illuminate\Contracts\Validation\ValidationException;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Stratigility\ErrorMiddlewareInterface;
+use Flarum\Core;
+use Exception;
class JsonApiErrors implements ErrorMiddlewareInterface
{
/**
* {@inheritdoc}
*/
- public function __invoke($error, Request $request, Response $response, callable $out = null)
+ public function __invoke($e, Request $request, Response $response, callable $out = null)
{
- $errorObject = [
- 'title' => $error->getMessage(),
- ];
+ return $this->handle($e);
+ }
- $status = 500;
+ public function handle(Exception $e)
+ {
+ if ($e instanceof JsonApiSerializable) {
+ $status = $e->getStatusCode();
- // If it seems to be a valid HTTP status code, we pass on the
- // exception's status.
- $errorCode = $error->getCode();
- if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) {
- $status = $errorCode;
+ $errors = $e->getErrors();
+ } else if ($e instanceof ValidationException) {
+ $status = 422;
+
+ $errors = $e->errors()->toArray();
+ $errors = array_map(function ($field, $messages) {
+ return [
+ 'detail' => implode("\n", $messages),
+ 'source' => ['pointer' => '/data/attributes/' . $field],
+ ];
+ }, array_keys($errors), $errors);
+ } else if ($e instanceof ModelNotFoundException) {
+ $status = 404;
+
+ $errors = [];
+ } else {
+ $status = 500;
+
+ $error = [
+ 'code' => $status,
+ 'title' => 'Internal Server Error'
+ ];
+
+ if (Core::inDebugMode()) {
+ $error['detail'] = (string) $e;
+ }
+
+ $errors = [$error];
}
// JSON API errors must be collected in an array under the
// "errors" key in the top level of the document
$data = [
- 'errors' => [$errorObject]
+ 'errors' => $errors,
];
return new JsonResponse($data, $status);
diff --git a/framework/core/src/Api/Middleware/LoginWithHeader.php b/framework/core/src/Api/Middleware/LoginWithHeader.php
index 1a66456af..c5873bb40 100644
--- a/framework/core/src/Api/Middleware/LoginWithHeader.php
+++ b/framework/core/src/Api/Middleware/LoginWithHeader.php
@@ -50,7 +50,7 @@ class LoginWithHeader implements MiddlewareInterface
if (isset($parts[0]) && starts_with($parts[0], $this->prefix)) {
$token = substr($parts[0], strlen($this->prefix));
- if ($accessToken = AccessToken::valid($token)) {
+ if (($accessToken = AccessToken::find($token)) && $accessToken->isValid()) {
$this->app->instance('flarum.actor', $user = $accessToken->user);
$user->updateLastSeen()->save();
diff --git a/framework/core/src/Core/Application.php b/framework/core/src/Core/Application.php
index b3de34329..aa4972478 100644
--- a/framework/core/src/Core/Application.php
+++ b/framework/core/src/Core/Application.php
@@ -27,7 +27,7 @@ class Application extends Container implements ApplicationContract
*
* @var string
*/
- const VERSION = '0.1.0-beta';
+ const VERSION = '0.1.0-beta.2';
/**
* The base path for the Laravel installation.
diff --git a/framework/core/src/Core/Exceptions/InvalidConfirmationTokenException.php b/framework/core/src/Core/Exceptions/InvalidConfirmationTokenException.php
index 22ebed2b8..14c22bd56 100644
--- a/framework/core/src/Core/Exceptions/InvalidConfirmationTokenException.php
+++ b/framework/core/src/Core/Exceptions/InvalidConfirmationTokenException.php
@@ -12,6 +12,21 @@ namespace Flarum\Core\Exceptions;
use Exception;
-class InvalidConfirmationTokenException extends Exception
+class InvalidConfirmationTokenException extends Exception implements JsonApiSerializable
{
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatusCode()
+ {
+ return 403;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getErrors()
+ {
+ return ['code' => 'invalid_confirmation_token'];
+ }
}
diff --git a/framework/core/src/Core/Exceptions/JsonApiSerializable.php b/framework/core/src/Core/Exceptions/JsonApiSerializable.php
new file mode 100644
index 000000000..4ff671d22
--- /dev/null
+++ b/framework/core/src/Core/Exceptions/JsonApiSerializable.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Core\Exceptions;
+
+interface JsonApiSerializable
+{
+ /**
+ * Return the HTTP status code to be used for this exception.
+ *
+ * @return int
+ */
+ public function getStatusCode();
+
+ /**
+ * Return an array of errors, formatted as JSON-API error objects.
+ *
+ * @see http://jsonapi.org/format/#error-objects
+ * @return array
+ */
+ public function getErrors();
+}
diff --git a/framework/core/src/Core/Exceptions/PermissionDeniedException.php b/framework/core/src/Core/Exceptions/PermissionDeniedException.php
index 14b7c1d66..9cffa8eb3 100644
--- a/framework/core/src/Core/Exceptions/PermissionDeniedException.php
+++ b/framework/core/src/Core/Exceptions/PermissionDeniedException.php
@@ -12,6 +12,26 @@ namespace Flarum\Core\Exceptions;
use Exception;
-class PermissionDeniedException extends Exception
+class PermissionDeniedException extends Exception implements JsonApiSerializable
{
+ /**
+ * Return the HTTP status code to be used for this exception.
+ *
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return 401;
+ }
+
+ /**
+ * Return an array of errors, formatted as JSON-API error objects.
+ *
+ * @see http://jsonapi.org/format/#error-objects
+ * @return array
+ */
+ public function getErrors()
+ {
+ return [];
+ }
}
diff --git a/framework/core/src/Core/Exceptions/ValidationException.php b/framework/core/src/Core/Exceptions/ValidationException.php
index 56ff781a9..445ee0599 100644
--- a/framework/core/src/Core/Exceptions/ValidationException.php
+++ b/framework/core/src/Core/Exceptions/ValidationException.php
@@ -12,17 +12,39 @@ namespace Flarum\Core\Exceptions;
use Exception;
-class ValidationException extends Exception
+class ValidationException extends Exception implements JsonApiSerializable
{
protected $messages;
public function __construct(array $messages)
{
$this->messages = $messages;
+
+ parent::__construct(implode("\n", $messages));
}
public function getMessages()
{
return $this->messages;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatusCode()
+ {
+ return 422;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getErrors()
+ {
+ return array_map(function ($path, $detail) {
+ $source = ['pointer' => '/data/attributes/' . $path];
+
+ return compact('source', 'detail');
+ }, array_keys($this->messages), $this->messages);
+ }
}
diff --git a/framework/core/src/Core/Model.php b/framework/core/src/Core/Model.php
index d1bdf255f..aa1f3ebb4 100755
--- a/framework/core/src/Core/Model.php
+++ b/framework/core/src/Core/Model.php
@@ -10,14 +10,8 @@
namespace Flarum\Core;
-use Flarum\Core\Exceptions\PermissionDeniedException;
-use Flarum\Core\Users\User;
-use Flarum\Events\ModelAllow;
use Flarum\Events\ModelDates;
use Flarum\Events\ModelRelationship;
-use Flarum\Events\ScopeModelVisibility;
-use Illuminate\Contracts\Validation\Factory;
-use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\Relation;
use LogicException;
diff --git a/framework/core/src/Core/Users/Commands/ConfirmEmailHandler.php b/framework/core/src/Core/Users/Commands/ConfirmEmailHandler.php
index 71f766915..9825f8926 100644
--- a/framework/core/src/Core/Users/Commands/ConfirmEmailHandler.php
+++ b/framework/core/src/Core/Users/Commands/ConfirmEmailHandler.php
@@ -13,7 +13,6 @@ namespace Flarum\Core\Users\Commands;
use Flarum\Core\Users\UserRepository;
use Flarum\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
-use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
use Flarum\Core\Users\EmailToken;
use DateTime;
@@ -36,16 +35,14 @@ class ConfirmEmailHandler
/**
* @param ConfirmEmail $command
- * @return \Flarum\Core\Users\User
+ *
* @throws InvalidConfirmationTokenException
+ *
+ * @return \Flarum\Core\Users\User
*/
public function handle(ConfirmEmail $command)
{
- $token = EmailToken::find($command->token);
-
- if (! $token || $token->created_at < new DateTime('-1 day')) {
- throw new InvalidConfirmationTokenException;
- }
+ $token = EmailToken::validOrFail($command->token);
$user = $token->user;
$user->changeEmail($token->email);
diff --git a/framework/core/src/Core/Users/Commands/RegisterUserHandler.php b/framework/core/src/Core/Users/Commands/RegisterUserHandler.php
index af8dbeeb2..132a400e0 100644
--- a/framework/core/src/Core/Users/Commands/RegisterUserHandler.php
+++ b/framework/core/src/Core/Users/Commands/RegisterUserHandler.php
@@ -11,17 +11,25 @@
namespace Flarum\Core\Users\Commands;
use Flarum\Core\Users\User;
+use Flarum\Core\Users\EmailToken;
use Flarum\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Core\Exceptions\PermissionDeniedException;
+use DateTime;
class RegisterUserHandler
{
use DispatchesEvents;
+ /**
+ * @var SettingsRepository
+ */
protected $settings;
+ /**
+ * @param SettingsRepository $settings
+ */
public function __construct(SettingsRepository $settings)
{
$this->settings = $settings;
@@ -29,26 +37,57 @@ class RegisterUserHandler
/**
* @param RegisterUser $command
+ *
+ * @throws PermissionDeniedException if signup is closed and the actor is
+ * not an administrator.
+ * @throws \Flarum\Core\Exceptions\InvalidConfirmationTokenException if an
+ * email confirmation token is provided but is invalid.
+ *
* @return User
*/
public function handle(RegisterUser $command)
{
- if (! $this->settings->get('allow_sign_up')) {
- throw new PermissionDeniedException;
- }
-
$actor = $command->actor;
$data = $command->data;
+ if (! $this->settings->get('allow_sign_up') && ! $actor->isAdmin()) {
+ throw new PermissionDeniedException;
+ }
+
+ // If a valid email confirmation token was provided as an attribute,
+ // then we can create a random password for this user and consider their
+ // email address confirmed.
+ if (isset($data['attributes']['token'])) {
+ $token = EmailToken::whereNull('user_id')->validOrFail($data['attributes']['token']);
+
+ $email = $token->email;
+ $password = array_get($data, 'attributes.password', str_random(20));
+ } else {
+ $email = array_get($data, 'attributes.email');
+ $password = array_get($data, 'attributes.password');
+ }
+
+ // Create the user's new account. If their email was set via token, then
+ // we can activate their account from the get-go, and they won't need
+ // to confirm their email address.
$user = User::register(
array_get($data, 'attributes.username'),
- array_get($data, 'attributes.email'),
- array_get($data, 'attributes.password')
+ $email,
+ $password
);
+ if (isset($token)) {
+ $user->activate();
+ }
+
event(new UserWillBeSaved($user, $actor, $data));
$user->save();
+
+ if (isset($token)) {
+ $token->delete();
+ }
+
$this->dispatchEventsFor($user);
return $user;
diff --git a/framework/core/src/Core/Users/EmailToken.php b/framework/core/src/Core/Users/EmailToken.php
index 92acd1bc9..eb61b0cd0 100644
--- a/framework/core/src/Core/Users/EmailToken.php
+++ b/framework/core/src/Core/Users/EmailToken.php
@@ -11,6 +11,8 @@
namespace Flarum\Core\Users;
use Flarum\Core\Model;
+use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
+use DateTime;
/**
* @todo document database columns with @property
@@ -37,11 +39,12 @@ class EmailToken extends Model
/**
* Generate an email token for the specified user.
*
- * @param int $userId
* @param string $email
+ * @param int $userId
+ *
* @return static
*/
- public static function generate($userId, $email)
+ public static function generate($email, $userId = null)
{
$token = new static;
@@ -62,4 +65,25 @@ class EmailToken extends Model
{
return $this->belongsTo('Flarum\Core\Users\User');
}
+
+ /**
+ * Find the token with the given ID, and assert that it has not expired.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param string $id
+ *
+ * @throws InvalidConfirmationTokenException
+ *
+ * @return static
+ */
+ public function scopeValidOrFail($query, $id)
+ {
+ $token = $query->find($id);
+
+ if (! $token || $token->created_at < new DateTime('-1 day')) {
+ throw new InvalidConfirmationTokenException;
+ }
+
+ return $token;
+ }
}
diff --git a/framework/core/src/Core/Users/Listeners/EmailConfirmationMailer.php b/framework/core/src/Core/Users/Listeners/EmailConfirmationMailer.php
index 497285e3a..7ca9eb384 100755
--- a/framework/core/src/Core/Users/Listeners/EmailConfirmationMailer.php
+++ b/framework/core/src/Core/Users/Listeners/EmailConfirmationMailer.php
@@ -57,6 +57,11 @@ class EmailConfirmationMailer
public function whenUserWasRegistered(UserWasRegistered $event)
{
$user = $event->user;
+
+ if ($user->is_activated) {
+ return;
+ }
+
$data = $this->getEmailData($user, $user->email);
$this->mailer->send(['text' => 'flarum::emails.activateAccount'], $data, function (Message $message) use ($user) {
@@ -82,11 +87,12 @@ class EmailConfirmationMailer
/**
* @param User $user
* @param string $email
+ *
* @return EmailToken
*/
protected function generateToken(User $user, $email)
{
- $token = EmailToken::generate($user->id, $email);
+ $token = EmailToken::generate($email, $user->id);
$token->save();
return $token;
@@ -97,6 +103,7 @@ class EmailConfirmationMailer
*
* @param User $user
* @param string $email
+ *
* @return array
*/
protected function getEmailData(User $user, $email)
diff --git a/framework/core/src/Forum/Actions/ClientAction.php b/framework/core/src/Forum/Actions/ClientAction.php
index bf358d1a3..49224baf6 100644
--- a/framework/core/src/Forum/Actions/ClientAction.php
+++ b/framework/core/src/Forum/Actions/ClientAction.php
@@ -56,7 +56,6 @@ class ClientAction extends BaseClientAction
'core.discussion_started',
'core.discussion_title',
'core.discussions',
- 'core.dismiss',
'core.edit',
'core.editing_post',
'core.email',
diff --git a/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php b/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php
new file mode 100644
index 000000000..0ed0337ef
--- /dev/null
+++ b/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Forum\Actions;
+
+use Flarum\Core\Users\User;
+use Zend\Diactoros\Response\HtmlResponse;
+use Flarum\Api\Commands\GenerateAccessToken;
+use Flarum\Core\Users\EmailToken;
+
+trait ExternalAuthenticatorTrait
+{
+ use WritesRememberCookie;
+
+ /**
+ * @var \Illuminate\Contracts\Bus\Dispatcher
+ */
+ protected $bus;
+
+ /**
+ * Respond with JavaScript to tell the Flarum app that the user has been
+ * authenticated, or with information about their sign up status.
+ *
+ * @param string $email The email of the user's account.
+ * @param string $username A suggested username for the user's account.
+ * @return HtmlResponse
+ */
+ protected function authenticated($email, $username)
+ {
+ $user = User::where('email', $email)->first();
+
+ // If a user with this email address doesn't already exist, then we will
+ // generate a unique confirmation token for this email address and add
+ // it to the response, along with the email address and a suggested
+ // username. Otherwise, we will log in the existing user by generating
+ // an access token.
+ if (! $user) {
+ $token = EmailToken::generate($email);
+ $token->save();
+
+ $payload = compact('email', 'username');
+
+ $payload['token'] = $token->id;
+ } else {
+ $accessToken = $this->bus->dispatch(new GenerateAccessToken($user->id));
+
+ $payload = ['authenticated' => true];
+ }
+
+ $content = sprintf('', json_encode($payload));
+
+ $response = new HtmlResponse($content);
+
+ if (isset($accessToken)) {
+ $response = $this->withRememberCookie($response, $accessToken->id);
+ }
+
+ return $response;
+ }
+}
diff --git a/framework/core/src/Forum/Actions/LoginAction.php b/framework/core/src/Forum/Actions/LoginAction.php
index 847c0c5a0..d089df799 100644
--- a/framework/core/src/Forum/Actions/LoginAction.php
+++ b/framework/core/src/Forum/Actions/LoginAction.php
@@ -47,7 +47,7 @@ class LoginAction extends Action
/**
* @param Request $request
* @param array $routeParams
- * @return \Psr\Http\Message\ResponseInterface|EmptyResponse
+ * @return JsonResponse|EmptyResponse
*/
public function handle(Request $request, array $routeParams = [])
{
diff --git a/framework/core/src/Forum/Actions/RegisterAction.php b/framework/core/src/Forum/Actions/RegisterAction.php
new file mode 100644
index 000000000..db9d74e9e
--- /dev/null
+++ b/framework/core/src/Forum/Actions/RegisterAction.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Flarum\Forum\Actions;
+
+use Flarum\Api\Client;
+use Flarum\Api\AccessToken;
+use Flarum\Events\UserLoggedIn;
+use Flarum\Support\Action;
+use Flarum\Api\Commands\GenerateAccessToken;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Zend\Diactoros\Response\EmptyResponse;
+use Zend\Diactoros\Response\JsonResponse;
+use Illuminate\Contracts\Bus\Dispatcher;
+use DateTime;
+
+class RegisterAction extends Action
+{
+ use WritesRememberCookie;
+
+ /**
+ * @var Dispatcher
+ */
+ protected $bus;
+
+ /**
+ * @var Client
+ */
+ protected $apiClient;
+
+ /**
+ * @param Dispatcher $bus
+ * @param Client $apiClient
+ */
+ public function __construct(Dispatcher $bus, Client $apiClient)
+ {
+ $this->bus = $bus;
+ $this->apiClient = $apiClient;
+ }
+
+ /**
+ * @param Request $request
+ * @param array $routeParams
+ *
+ * @return JsonResponse
+ */
+ public function handle(Request $request, array $routeParams = [])
+ {
+ $params = ['data' => ['attributes' => $request->getAttributes()]];
+
+ $apiResponse = $this->apiClient->send(app('flarum.actor'), 'Flarum\Api\Actions\Users\CreateAction', $params);
+
+ $body = $apiResponse->getBody();
+ $statusCode = $apiResponse->getStatusCode();
+
+ $response = new JsonResponse($body, $statusCode);
+
+ if (! empty($body->data->attributes->isActivated)) {
+ $token = $this->bus->dispatch(new GenerateAccessToken($body->data->id));
+
+ // Extend the token's expiry to 2 weeks so that we can set a
+ // remember cookie
+ AccessToken::where('id', $token->id)->update(['expires_at' => new DateTime('+2 weeks')]);
+
+ return $this->withRememberCookie(
+ $response,
+ $token->id
+ );
+ }
+
+ return $response;
+ }
+}
diff --git a/framework/core/src/Forum/Actions/WritesRememberCookie.php b/framework/core/src/Forum/Actions/WritesRememberCookie.php
index a52872ed4..24e61d80f 100644
--- a/framework/core/src/Forum/Actions/WritesRememberCookie.php
+++ b/framework/core/src/Forum/Actions/WritesRememberCookie.php
@@ -24,6 +24,7 @@ trait WritesRememberCookie
SetCookie::create('flarum_remember', $token)
->withMaxAge(14 * 24 * 60 * 60)
->withPath('/')
+ ->withHttpOnly(true)
);
}
@@ -35,6 +36,7 @@ trait WritesRememberCookie
SetCookie::create('flarum_remember')
->withMaxAge(-2628000)
->withPath('/')
+ ->withHttpOnly(true)
);
}
}
diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php
index af6d98acf..0edd323e6 100644
--- a/framework/core/src/Forum/ForumServiceProvider.php
+++ b/framework/core/src/Forum/ForumServiceProvider.php
@@ -102,6 +102,12 @@ class ForumServiceProvider extends ServiceProvider
$this->action('Flarum\Forum\Actions\LoginAction')
);
+ $routes->post(
+ '/register',
+ 'flarum.forum.register',
+ $this->action('Flarum\Forum\Actions\RegisterAction')
+ );
+
$routes->get(
'/confirm/{token}',
'flarum.forum.confirmEmail',
diff --git a/framework/core/src/Forum/Middleware/LoginWithCookie.php b/framework/core/src/Forum/Middleware/LoginWithCookie.php
index 95e5e9605..78ce75879 100644
--- a/framework/core/src/Forum/Middleware/LoginWithCookie.php
+++ b/framework/core/src/Forum/Middleware/LoginWithCookie.php
@@ -36,14 +36,46 @@ class LoginWithCookie implements MiddlewareInterface
*/
public function __invoke(Request $request, Response $response, callable $out = null)
{
- if (($token = array_get($request->getCookieParams(), 'flarum_remember')) &&
- ($accessToken = AccessToken::valid($token))
- ) {
- $this->app->instance('flarum.actor', $user = $accessToken->user);
-
- $user->updateLastSeen()->save();
- }
+ $this->logIn($request);
return $out ? $out($request, $response) : $response;
}
+
+ /**
+ * Set the application's actor instance according to the request token.
+ *
+ * @param Request $request
+ * @return bool
+ */
+ protected function logIn(Request $request)
+ {
+ if ($token = $this->getToken($request)) {
+ if (! $token->isValid()) {
+ // TODO: https://github.com/flarum/core/issues/253
+ } elseif ($token->user) {
+ $this->app->instance('flarum.actor', $user = $token->user);
+
+ $user->updateLastSeen()->save();
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the access token referred to by the request cookie.
+ *
+ * @param Request $request
+ * @return AccessToken|null
+ */
+ protected function getToken(Request $request)
+ {
+ $token = array_get($request->getCookieParams(), 'flarum_remember');
+
+ if ($token) {
+ return AccessToken::find($token);
+ }
+ }
}
diff --git a/framework/core/src/Install/Console/InstallCommand.php b/framework/core/src/Install/Console/InstallCommand.php
index 626b41bf6..b4c15cdd0 100644
--- a/framework/core/src/Install/Console/InstallCommand.php
+++ b/framework/core/src/Install/Console/InstallCommand.php
@@ -11,12 +11,14 @@
namespace Flarum\Install\Console;
use Flarum\Console\Command;
+use Flarum\Core\Exceptions\ValidationException;
use Flarum\Core\Model;
use Flarum\Core\Users\User;
use Flarum\Core\Groups\Group;
use Flarum\Core\Groups\Permission;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Arr;
+use Illuminate\Validation\Factory;
use PDO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -100,6 +102,43 @@ class InstallCommand extends Command
protected function install()
{
try {
+ $this->dbConfig = $this->dataSource->getDatabaseConfiguration();
+
+ $validation = $this->getValidator()->make(
+ $this->dbConfig,
+ [
+ 'driver' => 'required|in:mysql',
+ 'host' => 'required',
+ 'database' => 'required|alpha_dash',
+ 'username' => 'required|alpha_dash',
+ 'prefix' => 'alpha_dash|max:10'
+ ]
+ );
+
+ if ($validation->fails()) {
+ throw new Exception(implode("\n", call_user_func_array('array_merge', $validation->getMessageBag()->toArray())));
+ }
+
+ $this->baseUrl = $this->dataSource->getBaseUrl();
+ $this->settings = $this->dataSource->getSettings();
+ $this->adminUser = $admin = $this->dataSource->getAdminUser();
+
+ if (strlen($admin['password']) < 8) {
+ throw new Exception('Password must be at least 8 characters.');
+ }
+
+ if ($admin['password'] !== $admin['password_confirmation']) {
+ throw new Exception('The password did not match its confirmation.');
+ }
+
+ if (! filter_var($admin['email'], FILTER_VALIDATE_EMAIL)) {
+ throw new Exception('You must enter a valid email.');
+ }
+
+ if (! $admin['username'] || preg_match('/[^a-z0-9_-]/i', $admin['username'])) {
+ throw new Exception('Username can only contain letters, numbers, underscores, and dashes.');
+ }
+
$this->storeConfiguration();
$this->runMigrations();
@@ -127,7 +166,7 @@ class InstallCommand extends Command
protected function storeConfiguration()
{
- $dbConfig = $this->dataSource->getDatabaseConfiguration();
+ $dbConfig = $this->dbConfig;
$config = [
'debug' => true,
@@ -142,7 +181,7 @@ class InstallCommand extends Command
'prefix' => $dbConfig['prefix'],
'strict' => false
],
- 'url' => $this->dataSource->getBaseUrl(),
+ 'url' => $this->baseUrl,
'paths' => [
'api' => 'api',
'admin' => 'admin',
@@ -186,12 +225,11 @@ class InstallCommand extends Command
protected function writeSettings()
{
- $data = $this->dataSource->getSettings();
$settings = $this->application->make('Flarum\Core\Settings\SettingsRepository');
$this->info('Writing default settings');
- foreach ($data as $k => $v) {
+ foreach ($this->settings as $k => $v) {
$settings->set($k, $v);
}
}
@@ -246,7 +284,7 @@ class InstallCommand extends Command
protected function createAdminUser()
{
- $admin = $this->dataSource->getAdminUser();
+ $admin = $this->adminUser;
if ($admin['password'] !== $admin['password_confirmation']) {
throw new Exception('The password did not match its confirmation.');
@@ -254,11 +292,13 @@ class InstallCommand extends Command
$this->info('Creating admin user '.$admin['username']);
- User::unguard();
+ $user = User::register(
+ $admin['username'],
+ $admin['email'],
+ $admin['password']
+ );
- $user = new User(Arr::except($admin, 'password_confirmation'));
$user->is_activated = 1;
- $user->join_time = time();
$user->save();
$user->groups()->sync([1]);
@@ -300,6 +340,14 @@ class InstallCommand extends Command
return $this->application->make('Flarum\Install\Prerequisites\Prerequisite');
}
+ /**
+ * @return \Illuminate\Contracts\Validation\Factory
+ */
+ protected function getValidator()
+ {
+ return new Factory($this->application->make('Symfony\Component\Translation\TranslatorInterface'));
+ }
+
protected function showErrors($errors)
{
foreach ($errors as $error) {
diff --git a/framework/core/views/install/app.php b/framework/core/views/install/app.php
index 0f57b6146..e6facc153 100644
--- a/framework/core/views/install/app.php
+++ b/framework/core/views/install/app.php
@@ -162,7 +162,7 @@