diff --git a/composer.json b/composer.json
index d5f562cc2..9869b2336 100644
--- a/composer.json
+++ b/composer.json
@@ -1,35 +1,32 @@
-{
- "name": "flarum/core",
- "description": "",
- "authors": [
- {
- "name": "Toby Zerner",
- "email": "toby@flarum.org"
- }
- ],
- "require": {
- "php": ">=5.4.0",
- "illuminate/support": "4.2.*",
- "laracasts/commander": "1.1.*",
- "tobscure/json-api": "dev-master",
- "tobscure/permissible": "dev-master",
- "misd/linkify": "1.1.*"
- },
- "require-dev": {
- "fzaninotto/faker": "1.4.0",
- "codeception/codeception": "~2.0.0",
- "codeception/mockery-module": "*",
- "laracasts/testdummy": "~2.0"
- },
- "autoload": {
- "classmap": [
- "src/migrations"
- ],
- "psr-4": {
- "Flarum\\Core\\": "src/Flarum/Core",
- "Flarum\\Api\\": "src/Flarum/Api",
- "Flarum\\Web\\": "src/Flarum/Web"
- }
- },
- "minimum-stability": "dev"
-}
+{
+ "name": "flarum/core",
+ "description": "",
+ "authors": [
+ {
+ "name": "Toby Zerner",
+ "email": "toby@flarum.org"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "illuminate/support": "5.0.*",
+ "tobscure/json-api": "dev-master",
+ "tobscure/permissible": "dev-master",
+ "misd/linkify": "1.1.*"
+ },
+ "require-dev": {
+ "fzaninotto/faker": "1.4.0",
+ "codeception/codeception": "~2.0.0",
+ "codeception/mockery-module": "*",
+ "laracasts/testdummy": "~2.0"
+ },
+ "autoload": {
+ "classmap": [
+ "seeds"
+ ],
+ "psr-4": {
+ "Flarum\\": "src/"
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/composer.lock b/composer.lock
index c720fbf12..d4b36f262 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,35 +4,158 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "f965b67cdaace68ab3e159d2c058c449",
+ "hash": "eacf297f994d4976c0c3a9d9ded71bf6",
"packages": [
+ {
+ "name": "danielstjules/stringy",
+ "version": "1.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/danielstjules/Stringy.git",
+ "reference": "3cf18e9e424a6dedc38b7eb7ef580edb0929461b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/3cf18e9e424a6dedc38b7eb7ef580edb0929461b",
+ "reference": "3cf18e9e424a6dedc38b7eb7ef580edb0929461b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Stringy\\": "src/"
+ },
+ "files": [
+ "src/Create.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel St. Jules",
+ "email": "danielst.jules@gmail.com",
+ "homepage": "http://www.danielstjules.com"
+ }
+ ],
+ "description": "A string manipulation library with multibyte support",
+ "homepage": "https://github.com/danielstjules/Stringy",
+ "keywords": [
+ "UTF",
+ "helpers",
+ "manipulation",
+ "methods",
+ "multibyte",
+ "string",
+ "utf-8",
+ "utility",
+ "utils"
+ ],
+ "time": "2015-02-10 06:19:18"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "e5eaf8c7ded0877195b5d2848491e17b1c0a6c4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/e5eaf8c7ded0877195b5d2848491e17b1c0a6c4d",
+ "reference": "e5eaf8c7ded0877195b5d2848491e17b1c0a6c4d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Inflector\\": "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 String Manipulations with regard to casing and singular/plural rules.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string"
+ ],
+ "time": "2015-01-01 18:34:57"
+ },
{
"name": "illuminate/container",
- "version": "4.2.x-dev",
- "target-dir": "Illuminate/Container",
+ "version": "5.0.x-dev",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
- "reference": "8db091c1b4e503ef8dcd4586d5c63e3997bc4e89"
+ "reference": "7ffdad0a2b2c600445deb57f5f7e93092e44ca2a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/container/zipball/8db091c1b4e503ef8dcd4586d5c63e3997bc4e89",
- "reference": "8db091c1b4e503ef8dcd4586d5c63e3997bc4e89",
+ "url": "https://api.github.com/repos/illuminate/container/zipball/7ffdad0a2b2c600445deb57f5f7e93092e44ca2a",
+ "reference": "7ffdad0a2b2c600445deb57f5f7e93092e44ca2a",
"shasum": ""
},
"require": {
+ "illuminate/contracts": "5.0.*",
"php": ">=5.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.2-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Illuminate\\Container": ""
+ "psr-4": {
+ "Illuminate\\Container\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -45,45 +168,86 @@
"email": "taylorotwell@gmail.com"
}
],
- "time": "2014-12-17 20:39:51"
+ "description": "The Illuminate Container package.",
+ "time": "2015-02-11 16:00:31"
+ },
+ {
+ "name": "illuminate/contracts",
+ "version": "5.0.x-dev",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/contracts.git",
+ "reference": "78f1dba092d5fcb6d3a19537662abe31c4d128fd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/contracts/zipball/78f1dba092d5fcb6d3a19537662abe31c4d128fd",
+ "reference": "78f1dba092d5fcb6d3a19537662abe31c4d128fd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Contracts\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "description": "The Illuminate Contracts package.",
+ "time": "2015-01-30 16:27:08"
},
{
"name": "illuminate/database",
- "version": "4.2.x-dev",
- "target-dir": "Illuminate/Database",
+ "version": "5.0.x-dev",
"source": {
"type": "git",
"url": "https://github.com/illuminate/database.git",
- "reference": "eabb5ad0db896339f821ef088ca2fd0d6972e137"
+ "reference": "0c86cd20e7b0fb0bd0979bbddc7946239c58ad66"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/database/zipball/eabb5ad0db896339f821ef088ca2fd0d6972e137",
- "reference": "eabb5ad0db896339f821ef088ca2fd0d6972e137",
+ "url": "https://api.github.com/repos/illuminate/database/zipball/0c86cd20e7b0fb0bd0979bbddc7946239c58ad66",
+ "reference": "0c86cd20e7b0fb0bd0979bbddc7946239c58ad66",
"shasum": ""
},
"require": {
- "illuminate/container": "4.2.*",
- "illuminate/events": "4.2.*",
- "illuminate/support": "4.2.*",
+ "illuminate/container": "5.0.*",
+ "illuminate/contracts": "5.0.*",
+ "illuminate/support": "5.0.*",
"nesbot/carbon": "~1.0",
"php": ">=5.4.0"
},
- "require-dev": {
- "illuminate/cache": "4.2.*",
- "illuminate/console": "4.2.*",
- "illuminate/filesystem": "4.2.*",
- "illuminate/pagination": "4.2.*"
+ "suggest": {
+ "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).",
+ "illuminate/console": "Required to use the database commands (5.0.*).",
+ "illuminate/events": "Required to use the observers with Eloquent (5.0.*).",
+ "illuminate/filesystem": "Required to use the migrations (5.0.*)."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.2-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Illuminate\\Database": ""
+ "psr-4": {
+ "Illuminate\\Database\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -96,91 +260,51 @@
"email": "taylorotwell@gmail.com"
}
],
+ "description": "The Illuminate Database package.",
"keywords": [
"database",
"laravel",
"orm",
"sql"
],
- "time": "2015-01-27 20:51:43"
- },
- {
- "name": "illuminate/events",
- "version": "4.2.x-dev",
- "target-dir": "Illuminate/Events",
- "source": {
- "type": "git",
- "url": "https://github.com/illuminate/events.git",
- "reference": "a8471d3f6c3e87c50e25e18f13908fffaef159df"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/illuminate/events/zipball/a8471d3f6c3e87c50e25e18f13908fffaef159df",
- "reference": "a8471d3f6c3e87c50e25e18f13908fffaef159df",
- "shasum": ""
- },
- "require": {
- "illuminate/container": "4.2.*",
- "illuminate/support": "4.2.*",
- "php": ">=5.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Illuminate\\Events": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylorotwell@gmail.com"
- }
- ],
- "time": "2014-10-02 19:49:50"
+ "time": "2015-02-18 02:07:31"
},
{
"name": "illuminate/support",
- "version": "4.2.x-dev",
- "target-dir": "Illuminate/Support",
+ "version": "5.0.x-dev",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
- "reference": "db61f3f6d507ce417ca993e1f93585db7efd8b12"
+ "reference": "405a2241fefa49cfc39b7fba8cc54f69fce2f101"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/illuminate/support/zipball/db61f3f6d507ce417ca993e1f93585db7efd8b12",
- "reference": "db61f3f6d507ce417ca993e1f93585db7efd8b12",
+ "url": "https://api.github.com/repos/illuminate/support/zipball/405a2241fefa49cfc39b7fba8cc54f69fce2f101",
+ "reference": "405a2241fefa49cfc39b7fba8cc54f69fce2f101",
"shasum": ""
},
"require": {
+ "danielstjules/stringy": "~1.8",
+ "doctrine/inflector": "~1.0",
+ "ext-mbstring": "*",
+ "illuminate/contracts": "5.0.*",
"php": ">=5.4.0"
},
- "require-dev": {
- "jeremeamia/superclosure": "~1.0.1",
- "patchwork/utf8": "~1.1"
+ "suggest": {
+ "jeremeamia/superclosure": "Required to be able to serialize closures (~2.0)."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.2-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Illuminate\\Support": ""
+ "psr-4": {
+ "Illuminate\\Support\\": ""
},
"files": [
- "Illuminate/Support/helpers.php"
+ "helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -193,44 +317,8 @@
"email": "taylorotwell@gmail.com"
}
],
- "time": "2015-01-27 20:51:43"
- },
- {
- "name": "laracasts/commander",
- "version": "1.1.2",
- "source": {
- "type": "git",
- "url": "https://github.com/laracasts/Commander.git",
- "reference": "89b47ceb08e26d6beae35781010522e7110aded3"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laracasts/Commander/zipball/89b47ceb08e26d6beae35781010522e7110aded3",
- "reference": "89b47ceb08e26d6beae35781010522e7110aded3",
- "shasum": ""
- },
- "require": {
- "illuminate/support": "~4.0",
- "php": ">=5.4.0"
- },
- "type": "library",
- "autoload": {
- "psr-0": {
- "Laracasts\\Commander": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jeffrey Way",
- "email": "jeffrey@laracasts.com"
- }
- ],
- "description": "Commands and domain events in Laravel",
- "time": "2014-07-03 13:05:27"
+ "description": "The Illuminate Support package.",
+ "time": "2015-02-11 11:08:03"
},
{
"name": "misd/linkify",
@@ -371,16 +459,16 @@
"source": {
"type": "git",
"url": "https://github.com/tobscure/permissible.git",
- "reference": "223a62784672981a45170d2192c9786971b3abee"
+ "reference": "35d92ad11e66bafc94666cae3224d0ca08f44a6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/tobscure/permissible/zipball/223a62784672981a45170d2192c9786971b3abee",
- "reference": "223a62784672981a45170d2192c9786971b3abee",
+ "url": "https://api.github.com/repos/tobscure/permissible/zipball/35d92ad11e66bafc94666cae3224d0ca08f44a6a",
+ "reference": "35d92ad11e66bafc94666cae3224d0ca08f44a6a",
"shasum": ""
},
"require": {
- "illuminate/database": "4.2.*",
+ "illuminate/database": "5.0.*",
"php": ">=5.4.0"
},
"require-dev": {
@@ -400,7 +488,7 @@
}
],
"description": "Powerful, flexible, relational permissions using Eloquent.",
- "time": "2014-07-26 05:00:03"
+ "time": "2015-02-18 23:38:49"
}
],
"packages-dev": [
@@ -410,12 +498,12 @@
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
- "reference": "1416a5ed429615664ba406a37de141a7cba2982b"
+ "reference": "4e267293bb8070e03fb358f27e74271c2d10acbe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/Codeception/zipball/1416a5ed429615664ba406a37de141a7cba2982b",
- "reference": "1416a5ed429615664ba406a37de141a7cba2982b",
+ "url": "https://api.github.com/repos/Codeception/Codeception/zipball/4e267293bb8070e03fb358f27e74271c2d10acbe",
+ "reference": "4e267293bb8070e03fb358f27e74271c2d10acbe",
"shasum": ""
},
"require": {
@@ -482,7 +570,7 @@
"functional testing",
"unit testing"
],
- "time": "2015-02-07 01:27:55"
+ "time": "2015-02-16 11:49:51"
},
{
"name": "codeception/mockery-module",
@@ -727,12 +815,12 @@
"source": {
"type": "git",
"url": "https://github.com/guzzle/RingPHP.git",
- "reference": "0b13b4f0d595c4e886ae8eecdfe27e049ba38375"
+ "reference": "a1da305d1c5c0175abf7bc912cb065e906de9500"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/0b13b4f0d595c4e886ae8eecdfe27e049ba38375",
- "reference": "0b13b4f0d595c4e886ae8eecdfe27e049ba38375",
+ "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/a1da305d1c5c0175abf7bc912cb065e906de9500",
+ "reference": "a1da305d1c5c0175abf7bc912cb065e906de9500",
"shasum": ""
},
"require": {
@@ -770,7 +858,7 @@
}
],
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
- "time": "2015-01-20 22:24:03"
+ "time": "2015-02-08 23:29:08"
},
{
"name": "guzzlehttp/streams",
@@ -875,12 +963,12 @@
"source": {
"type": "git",
"url": "https://github.com/padraic/mockery.git",
- "reference": "0bf3f7346cb49a24587a1a6292e7f20fcd80af0a"
+ "reference": "a450aa28455781c7a72e50794b0f9d954cf8decb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/padraic/mockery/zipball/0bf3f7346cb49a24587a1a6292e7f20fcd80af0a",
- "reference": "0bf3f7346cb49a24587a1a6292e7f20fcd80af0a",
+ "url": "https://api.github.com/repos/padraic/mockery/zipball/a450aa28455781c7a72e50794b0f9d954cf8decb",
+ "reference": "a450aa28455781c7a72e50794b0f9d954cf8decb",
"shasum": ""
},
"require": {
@@ -889,8 +977,7 @@
},
"require-dev": {
"hamcrest/hamcrest-php": "~1.1",
- "phpunit/phpunit": "~4.0",
- "satooshi/php-coveralls": "~0.7@dev"
+ "phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
@@ -933,7 +1020,7 @@
"test double",
"testing"
],
- "time": "2015-02-05 10:30:00"
+ "time": "2015-02-18 18:54:04"
},
{
"name": "phpunit/php-code-coverage",
@@ -1185,12 +1272,12 @@
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a"
+ "reference": "5fbd35c52d9ad2a12683a11a138e5c05b31da95a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2e8580deebb7d1ac92ac878595e6bffe01069c2a",
- "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5fbd35c52d9ad2a12683a11a138e5c05b31da95a",
+ "reference": "5fbd35c52d9ad2a12683a11a138e5c05b31da95a",
"shasum": ""
},
"require": {
@@ -1249,7 +1336,7 @@
"testing",
"xunit"
],
- "time": "2015-01-27 16:06:15"
+ "time": "2015-02-09 06:34:32"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -1728,12 +1815,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/BrowserKit.git",
- "reference": "432c0593d5367b1bb8aa893cb2272172934ad371"
+ "reference": "7b8c4f5e8839122a931f45978c1330749ef9e83a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/432c0593d5367b1bb8aa893cb2272172934ad371",
- "reference": "432c0593d5367b1bb8aa893cb2272172934ad371",
+ "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/7b8c4f5e8839122a931f45978c1330749ef9e83a",
+ "reference": "7b8c4f5e8839122a931f45978c1330749ef9e83a",
"shasum": ""
},
"require": {
@@ -1774,7 +1861,7 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "http://symfony.com",
- "time": "2015-01-09 06:51:41"
+ "time": "2015-02-11 07:17:51"
},
{
"name": "symfony/console",
@@ -1783,12 +1870,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "e30a2778069b48bd1e560e4a2815d15c9ab2f563"
+ "reference": "289113e60aad754d35b50347f66ecd3a4d96d03c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/e30a2778069b48bd1e560e4a2815d15c9ab2f563",
- "reference": "e30a2778069b48bd1e560e4a2815d15c9ab2f563",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/289113e60aad754d35b50347f66ecd3a4d96d03c",
+ "reference": "289113e60aad754d35b50347f66ecd3a4d96d03c",
"shasum": ""
},
"require": {
@@ -1831,7 +1918,7 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
- "time": "2015-02-05 07:11:58"
+ "time": "2015-02-12 11:03:31"
},
{
"name": "symfony/css-selector",
@@ -1840,12 +1927,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/CssSelector.git",
- "reference": "1f8617c67bef17192d28c0f2dda3a04b632629bb"
+ "reference": "e37b4cd8a5ea27d2bd4775a4980f32d39d5daf31"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/CssSelector/zipball/1f8617c67bef17192d28c0f2dda3a04b632629bb",
- "reference": "1f8617c67bef17192d28c0f2dda3a04b632629bb",
+ "url": "https://api.github.com/repos/symfony/CssSelector/zipball/e37b4cd8a5ea27d2bd4775a4980f32d39d5daf31",
+ "reference": "e37b4cd8a5ea27d2bd4775a4980f32d39d5daf31",
"shasum": ""
},
"require": {
@@ -1882,7 +1969,7 @@
],
"description": "Symfony CssSelector Component",
"homepage": "http://symfony.com",
- "time": "2015-01-09 06:51:41"
+ "time": "2015-02-11 07:17:51"
},
{
"name": "symfony/dom-crawler",
@@ -1891,12 +1978,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/DomCrawler.git",
- "reference": "49d1657a690eaa6e8fd4f92d828b89bed05dd55d"
+ "reference": "e1355f67898ca94fe18425c3f2d2d4e905ea894f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/49d1657a690eaa6e8fd4f92d828b89bed05dd55d",
- "reference": "49d1657a690eaa6e8fd4f92d828b89bed05dd55d",
+ "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/e1355f67898ca94fe18425c3f2d2d4e905ea894f",
+ "reference": "e1355f67898ca94fe18425c3f2d2d4e905ea894f",
"shasum": ""
},
"require": {
@@ -1935,7 +2022,7 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "http://symfony.com",
- "time": "2015-02-05 06:58:17"
+ "time": "2015-02-11 07:17:51"
},
{
"name": "symfony/event-dispatcher",
@@ -1944,12 +2031,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "e9298668dce8dd219d1ee2290c7f313954f1f984"
+ "reference": "2dc33aff298b20ad099ac456034aebc5055f02fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/e9298668dce8dd219d1ee2290c7f313954f1f984",
- "reference": "e9298668dce8dd219d1ee2290c7f313954f1f984",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/2dc33aff298b20ad099ac456034aebc5055f02fb",
+ "reference": "2dc33aff298b20ad099ac456034aebc5055f02fb",
"shasum": ""
},
"require": {
@@ -1993,7 +2080,7 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
- "time": "2015-02-05 06:58:17"
+ "time": "2015-02-11 07:17:51"
},
{
"name": "symfony/finder",
@@ -2002,12 +2089,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
- "reference": "0c737de96a94d14a51738d285ad426a102baac0e"
+ "reference": "509d125782f31ebbd0bb3cd9824f1ccc1e5d5a18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Finder/zipball/0c737de96a94d14a51738d285ad426a102baac0e",
- "reference": "0c737de96a94d14a51738d285ad426a102baac0e",
+ "url": "https://api.github.com/repos/symfony/Finder/zipball/509d125782f31ebbd0bb3cd9824f1ccc1e5d5a18",
+ "reference": "509d125782f31ebbd0bb3cd9824f1ccc1e5d5a18",
"shasum": ""
},
"require": {
@@ -2040,7 +2127,7 @@
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
- "time": "2015-01-09 06:51:41"
+ "time": "2015-02-11 07:17:51"
},
{
"name": "symfony/yaml",
@@ -2049,12 +2136,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
- "reference": "02ba3dc638c5d3f0ab3b47ddb74f98c11dcc0c60"
+ "reference": "43aac3461a5679f486f7c96e4d7e780a59fec997"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Yaml/zipball/02ba3dc638c5d3f0ab3b47ddb74f98c11dcc0c60",
- "reference": "02ba3dc638c5d3f0ab3b47ddb74f98c11dcc0c60",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/43aac3461a5679f486f7c96e4d7e780a59fec997",
+ "reference": "43aac3461a5679f486f7c96e4d7e780a59fec997",
"shasum": ""
},
"require": {
@@ -2087,7 +2174,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
- "time": "2015-01-25 04:39:35"
+ "time": "2015-02-11 07:17:51"
}
],
"aliases": [],
diff --git a/ember/.ember-cli b/ember/.ember-cli
index ee64cfed2..69595d2a5 100644
--- a/ember/.ember-cli
+++ b/ember/.ember-cli
@@ -1,9 +1,3 @@
{
- /**
- Ember CLI sends analytics information by default. The data is completely
- anonymous, but there are times when you might want to disable this behavior.
-
- Setting `disableAnalytics` to true will prevent any data from being sent.
- */
- "disableAnalytics": false
+ "output-path": "../public"
}
diff --git a/ember/app/authenticators/flarum.js b/ember/app/authenticators/flarum.js
index e24f5f56c..edec017f1 100644
--- a/ember/app/authenticators/flarum.js
+++ b/ember/app/authenticators/flarum.js
@@ -3,35 +3,26 @@ import config from '../config/environment';
export default Base.extend({
- restore: function(data) {
- var container = this.container;
- return new Ember.RSVP.Promise(function(resolve, reject) {
- Ember.run.next(function() {
- container.lookup('store:main').find('user', data.userId).then(function(user) {
- resolve( { token: data.token, userId: data.userId, user: user } );
- });
- });
- });
- },
-
authenticate: function(credentials) {
var container = this.container;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
- url: config.apiURL+'/auth/login',
+ url: config.baseURL+'login',
type: 'POST',
data: { identification: credentials.identification, password: credentials.password }
}).then(function(response) {
- container.lookup('store:main').find('user', response.userId).then(function(user) {
- resolve({ token: response.token, userId: response.userId, user: user });
- });
+ container.lookup('store:main').find('user', response.userId).then(function(user) {
+ resolve({ token: response.token, userId: response.userId, user: user });
+ });
}, function(xhr, status, error) {
- reject(xhr.responseJSON.errors);
+ reject(xhr.responseJSON.errors);
});
});
},
- // invalidate: function(data) {
- // return new Ember.RSVP.Promise();
- // }
-});
\ No newline at end of file
+ invalidate: function(data) {
+ return new Ember.RSVP.Promise(function() {
+ window.location = config.baseURL+'logout';
+ });
+ }
+});
diff --git a/ember/app/authorizers/flarum.js b/ember/app/authorizers/flarum.js
index 49346cad5..8cd3d01ec 100644
--- a/ember/app/authorizers/flarum.js
+++ b/ember/app/authorizers/flarum.js
@@ -1,11 +1,10 @@
import Base from 'simple-auth/authorizers/base';
export default Base.extend({
-
- authorize: function(jqXHR, requestOptions) {
- var token = this.get('session.token');
- if (this.get('session.isAuthenticated') && !Ember.isEmpty(token)) {
- jqXHR.setRequestHeader('Authorization', 'Token ' + token);
- }
- }
-});
\ No newline at end of file
+ authorize: function(jqXHR, requestOptions) {
+ var token = this.get('session.token');
+ if (this.get('session.isAuthenticated') && !Ember.isEmpty(token)) {
+ jqXHR.setRequestHeader('Authorization', 'Token ' + token);
+ }
+ }
+});
diff --git a/ember/app/controllers/alerts.js b/ember/app/controllers/alerts.js
index cea222d20..1ce9f27f4 100644
--- a/ember/app/controllers/alerts.js
+++ b/ember/app/controllers/alerts.js
@@ -1,17 +1,17 @@
import Ember from 'ember';
export default Ember.Controller.extend({
- alerts: [],
+ alerts: [],
- actions: {
- alert: function(message) {
- this.get('alerts').pushObject(message);
- },
- dismissAlert: function(message) {
- this.get('alerts').removeObject(message);
- },
- clearAlerts: function() {
- this.get('alerts').clear();
- }
- }
+ actions: {
+ alert: function(message) {
+ this.get('alerts').pushObject(message);
+ },
+ dismissAlert: function(message) {
+ this.get('alerts').removeObject(message);
+ },
+ clearAlerts: function() {
+ this.get('alerts').clear();
+ }
+ }
});
diff --git a/ember/app/controllers/index/index.js b/ember/app/controllers/index/index.js
index 48f20c5b1..303377ecd 100644
--- a/ember/app/controllers/index/index.js
+++ b/ember/app/controllers/index/index.js
@@ -54,6 +54,8 @@ export default Ember.Controller.extend({
}
}
+ // var results = Ember.RSVP.resolve(FLARUM_DATA.discussions);
+
return this.store.find('discussion', params).then(function(discussions) {
var results = Ember.A();
discussions.forEach(function(discussion) {
diff --git a/ember/app/controllers/login.js b/ember/app/controllers/login.js
index 92cc45567..c63616c21 100644
--- a/ember/app/controllers/login.js
+++ b/ember/app/controllers/login.js
@@ -4,31 +4,31 @@ import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-con
import ModalController from 'flarum/mixins/modal-controller';
export default Ember.Controller.extend(ModalController, AuthenticationControllerMixin, {
- authenticator: 'authenticator:flarum',
- loading: false,
+ authenticator: 'authenticator:flarum',
+ loading: false,
- actions: {
- authenticate: function() {
+ actions: {
+ authenticate: function() {
var data = this.getProperties('identification', 'password');
var controller = this;
this.set('error', null);
this.set('loading', true);
return this._super(data).then(function() {
- controller.send("sessionChanged");
- controller.send("closeModal");
+ controller.send("sessionChanged");
+ controller.send("closeModal");
}, function(errors) {
- switch(errors[0].code) {
- case 'invalidLogin':
- controller.set('error', 'Your login details are incorrect.');
- break;
+ switch(errors[0].code) {
+ case 'invalidLogin':
+ controller.set('error', 'Your login details are incorrect.');
+ break;
- default:
- controller.set('error', 'Something went wrong. (Error code: '+errors[0].code+')');
- }
- controller.trigger('refocus');
+ default:
+ controller.set('error', 'Something went wrong. (Error code: '+errors[0].code+')');
+ }
+ controller.trigger('refocus');
}).finally(function() {
- controller.set('loading', false);
+ controller.set('loading', false);
});
}
- }
+ }
});
diff --git a/ember/app/controllers/signup.js b/ember/app/controllers/signup.js
index 1b1f3d17d..035816e09 100644
--- a/ember/app/controllers/signup.js
+++ b/ember/app/controllers/signup.js
@@ -3,6 +3,19 @@ import Ember from 'ember';
import ModalController from 'flarum/mixins/modal-controller';
export default Ember.Controller.extend(ModalController, {
+ emailProviderName: Ember.computed('welcomeUser.email', function() {
+ if (!this.get('welcomeUser.email')) { return; }
+ return this.get('welcomeUser.email').split('@')[1];
+ }),
+
+ emailProviderUrl: Ember.computed('emailProviderName', function() {
+ return 'http://'+this.get('emailProviderName');
+ }),
+
+ welcomeStyle: Ember.computed('welcomeUser.color', function() {
+ return 'background:'+this.get('welcomeUser.color');
+ }),
+
actions: {
submit: function() {
var data = this.getProperties('username', 'email', 'password');
@@ -12,15 +25,9 @@ export default Ember.Controller.extend(ModalController, {
var user = this.store.createRecord('user', data);
- return user.save().then(function() {
- controller.get('session').authenticate('authenticator:flarum', {
- identification: data.email,
- password: data.password
- }).then(function() {
- controller.send('closeModal');
- controller.send('sessionChanged');
- controller.set('loading', false);
- });
+ return user.save().then(function(user) {
+ controller.set('welcomeUser', user);
+ controller.set('loading', false);
}, function(reason) {
controller.set('loading', false);
});
diff --git a/ember/app/initializers/authentication.js b/ember/app/initializers/authentication.js
index 40a2547f2..8c431f6fa 100644
--- a/ember/app/initializers/authentication.js
+++ b/ember/app/initializers/authentication.js
@@ -1,9 +1,11 @@
import FlarumAuthorizer from 'flarum/authorizers/flarum';
+import Config from 'flarum/config/environment';
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container) {
container.register('authorizer:flarum', FlarumAuthorizer);
+ Config['simple-auth'] = {authorizer: 'authorizer:flarum'};
}
};
diff --git a/ember/app/initializers/preload-data.js b/ember/app/initializers/preload-data.js
new file mode 100644
index 000000000..793b16609
--- /dev/null
+++ b/ember/app/initializers/preload-data.js
@@ -0,0 +1,20 @@
+import Ember from 'ember';
+
+export default {
+ name: 'preload-data',
+ after: 'store',
+ initialize: function(container) {
+ var store = container.lookup('store:main');
+ if (!Ember.isEmpty(FLARUM_DATA)) {
+ store.pushPayload(FLARUM_DATA);
+ }
+ if (!Ember.isEmpty(FLARUM_SESSION)) {
+ FLARUM_SESSION.user = store.getById('user', FLARUM_SESSION.userId);
+ container.lookup('simple-auth-session:main').setProperties({
+ isAuthenticated: true,
+ authenticator: 'authenticator:flarum',
+ content: FLARUM_SESSION
+ });
+ }
+ }
+};
diff --git a/ember/app/routes/application.js b/ember/app/routes/application.js
index 810187b67..dab0ecef4 100644
--- a/ember/app/routes/application.js
+++ b/ember/app/routes/application.js
@@ -1,7 +1,15 @@
import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
+import AlertMessage from 'flarum/components/ui/alert-message';
+
export default Ember.Route.extend(ApplicationRouteMixin, {
+ activate: function() {
+ if (!Ember.isEmpty(FLARUM_ALERT)) {
+ this.controllerFor('alerts').send('alert', AlertMessage.create(FLARUM_ALERT));
+ }
+ },
+
actions: {
login: function() {
this.controllerFor('login').set('error', null);
@@ -9,7 +17,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, {
},
signup: function() {
- this.controllerFor('signup').set('error', null);
+ this.controllerFor('signup').set('error', null).set('welcomeUser', null);
this.send('showModal', 'signup');
},
@@ -33,7 +41,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, {
},
sessionChanged: function() {
- this.refresh();
+ this.refresh();
}
}
});
diff --git a/ember/app/styles/app.less b/ember/app/styles/app.less
index 6d8d88787..482053cbc 100644
--- a/ember/app/styles/app.less
+++ b/ember/app/styles/app.less
@@ -1,7 +1,7 @@
// This files is where our LESS journey begins.
// We begin by importing our own configuration variables, which are used all
-// throughout the stylesheets. These pertain to
+// throughout the stylesheets. These pertain to
// @import "config-default.less";
@import "config.less";
@@ -25,4 +25,5 @@
@import "@{flarum-base}index.less";
@import "@{flarum-base}discussion.less";
-@import "@{flarum-base}login.less";
\ No newline at end of file
+@import "@{flarum-base}login.less";
+@import "@{flarum-base}signup.less";
diff --git a/ember/app/styles/flarum/signup.less b/ember/app/styles/flarum/signup.less
new file mode 100644
index 000000000..86355bb77
--- /dev/null
+++ b/ember/app/styles/flarum/signup.less
@@ -0,0 +1,23 @@
+.signup-welcome {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: @border-radius-base - 1px;
+ padding: 60px;
+ text-align: center;
+ color: #fff;
+
+ & .avatar {
+ .avatar-size(64px);
+ .box-shadow(0 0 0 4px #fff);
+ }
+ & h3, & p {
+ margin-bottom: 20px;
+ }
+ & .btn-default {
+ background: fade(#000, 10%);
+ color: #fff;
+ }
+}
diff --git a/ember/app/templates/signup.hbs b/ember/app/templates/signup.hbs
index a9ed28266..d91dd885d 100644
--- a/ember/app/templates/signup.hbs
+++ b/ember/app/templates/signup.hbs
@@ -26,3 +26,15 @@
{{ui/loading-indicator classNameBindings=":modal-loading loading:active"}}
+
+{{#if welcomeUser}}
+
+ {{user-avatar welcomeUser}}
+
Welcome, {{welcomeUser.username}}!
+
+ {{#unless welcomeUser.isConfirmed}}
+
We've sent a confirmation email to {{welcomeUser.email}}. If it doesn't arrive soon, check your spam folder.
+
Go to {{emailProviderName}}
+ {{/unless}}
+
+{{/if}}
diff --git a/ember/app/views/signup.js b/ember/app/views/signup.js
index bffccd9c3..ea8379203 100644
--- a/ember/app/views/signup.js
+++ b/ember/app/views/signup.js
@@ -7,5 +7,13 @@ export default Ember.View.extend(ModalView, {
templateName: 'signup',
didInsertElement: function() {
- }
+ },
+
+ welcomeUserDidChange: Ember.observer('welcomeUser', function() {
+ if (this.get('welcomeUser')) {
+ Ember.run.scheduleOnce('afterRender', this, function() {
+ this.$('.signup-welcome').addClass('in');
+ });
+ }
+ })
});
diff --git a/src/migrations/2014_01_14_231357_create_sessions_table.php b/migrations/2015_02_24_000000_create_access_tokens_table.php
similarity index 54%
rename from src/migrations/2014_01_14_231357_create_sessions_table.php
rename to migrations/2015_02_24_000000_create_access_tokens_table.php
index e383784ad..9d123d0f2 100644
--- a/src/migrations/2014_01_14_231357_create_sessions_table.php
+++ b/migrations/2015_02_24_000000_create_access_tokens_table.php
@@ -3,7 +3,7 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
-class CreateSessionsTable extends Migration {
+class CreateAccessTokensTable extends Migration {
/**
* Run the migrations.
@@ -12,11 +12,10 @@ class CreateSessionsTable extends Migration {
*/
public function up()
{
- Schema::create('sessions', function(Blueprint $table)
+ Schema::create('access_tokens', function(Blueprint $table)
{
- $table->string('id')->unique();
- $table->binary('payload');
- $table->integer('last_activity');
+ $table->string('id');
+ $table->integer('user_id')->unsigned();
});
}
@@ -27,7 +26,7 @@ class CreateSessionsTable extends Migration {
*/
public function down()
{
- Schema::drop('sessions');
+ Schema::drop('access_tokens');
}
}
diff --git a/src/migrations/2014_01_19_232631_create_activity_table.php b/migrations/2015_02_24_000000_create_activity_table.php
similarity index 100%
rename from src/migrations/2014_01_19_232631_create_activity_table.php
rename to migrations/2015_02_24_000000_create_activity_table.php
diff --git a/src/migrations/2014_01_14_231259_create_config_table.php b/migrations/2015_02_24_000000_create_config_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231259_create_config_table.php
rename to migrations/2015_02_24_000000_create_config_table.php
diff --git a/src/migrations/2014_01_14_231321_create_discussions_table.php b/migrations/2015_02_24_000000_create_discussions_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231321_create_discussions_table.php
rename to migrations/2015_02_24_000000_create_discussions_table.php
diff --git a/src/migrations/2014_01_14_231334_create_groups_table.php b/migrations/2015_02_24_000000_create_groups_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231334_create_groups_table.php
rename to migrations/2015_02_24_000000_create_groups_table.php
diff --git a/src/migrations/2014_01_14_231343_create_permissions_table.php b/migrations/2015_02_24_000000_create_permissions_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231343_create_permissions_table.php
rename to migrations/2015_02_24_000000_create_permissions_table.php
diff --git a/src/migrations/2014_01_14_231350_create_posts_table.php b/migrations/2015_02_24_000000_create_posts_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231350_create_posts_table.php
rename to migrations/2015_02_24_000000_create_posts_table.php
diff --git a/src/migrations/2014_01_14_231455_create_users_discussions_table.php b/migrations/2015_02_24_000000_create_users_discussions_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231455_create_users_discussions_table.php
rename to migrations/2015_02_24_000000_create_users_discussions_table.php
diff --git a/src/migrations/2014_01_14_231503_create_users_groups_table.php b/migrations/2015_02_24_000000_create_users_groups_table.php
similarity index 100%
rename from src/migrations/2014_01_14_231503_create_users_groups_table.php
rename to migrations/2015_02_24_000000_create_users_groups_table.php
diff --git a/src/migrations/2014_01_14_231404_create_users_table.php b/migrations/2015_02_24_000000_create_users_table.php
similarity index 94%
rename from src/migrations/2014_01_14_231404_create_users_table.php
rename to migrations/2015_02_24_000000_create_users_table.php
index 0efe6efef..58d4e7f3d 100644
--- a/src/migrations/2014_01_14_231404_create_users_table.php
+++ b/migrations/2015_02_24_000000_create_users_table.php
@@ -19,8 +19,8 @@ class CreateUsersTable extends Migration {
$table->string('email');
$table->boolean('is_confirmed')->default(0);
$table->string('confirmation_token')->nullable();
+ $table->boolean('is_activated')->default(0);
$table->string('password');
- $table->string('token');
$table->dateTime('join_time')->nullable();
$table->dateTime('last_seen_time')->nullable();
$table->dateTime('read_time')->nullable();
diff --git a/src/Api/Actions/ApiParams.php b/src/Api/Actions/ApiParams.php
new file mode 100644
index 000000000..22b09fc01
--- /dev/null
+++ b/src/Api/Actions/ApiParams.php
@@ -0,0 +1,89 @@
+params = $params;
+ }
+
+ public function get($key, $default = null)
+ {
+ return array_get($this->params, $key, $default);
+ }
+
+ public function range($key, $default = null, $min = null, $max = null)
+ {
+ $value = (int) $this->get($key, $default);
+
+ if (! is_null($min)) {
+ $value = max($value, $min);
+ }
+ if (! is_null($max)) {
+ $value = min($value, $max);
+ }
+ return $value;
+ }
+
+ public function included($available)
+ {
+ $requested = explode(',', $this->get('include'));
+ return array_intersect((array) $available, $requested);
+ }
+
+ // public function explodeIds($ids)
+ // {
+ // return array_unique(array_map('intval', array_filter(explode(',', $ids))));
+ // }
+
+ public function in($key, $options)
+ {
+ $value = $this->get($key);
+
+ if (array_key_exists($key, $options)) {
+ return $options[$key];
+ }
+ if (! in_array($value, $options)) {
+ $value = reset($options);
+ }
+
+ return $value;
+ }
+
+ public function sort($options)
+ {
+ $criteria = (string) $this->get('sort', '');
+ $order = null;
+
+ if ($criteria && $criteria[0] == '-') {
+ $order = 'desc';
+ $criteria = substr($criteria, 1);
+ }
+
+ if (! in_array($criteria, $options)) {
+ $criteria = reset($options);
+ }
+
+ if ($criteria && ! $order) {
+ $order = 'asc';
+ }
+
+ return [
+ 'field' => $criteria,
+ 'order' => $order,
+ 'string' => ($order == 'desc' ? '-' : '').$criteria
+ ];
+ }
+
+ public function start()
+ {
+ return $this->range('start', 0, 0);
+ }
+
+ public function count($default, $max = 100)
+ {
+ return $this->range('count', $default, 1, $max);
+ }
+}
diff --git a/src/Api/Actions/BaseAction.php b/src/Api/Actions/BaseAction.php
new file mode 100644
index 000000000..c4d41d56b
--- /dev/null
+++ b/src/Api/Actions/BaseAction.php
@@ -0,0 +1,130 @@
+actor = $actor;
+ $this->bus = $bus;
+ }
+
+ public function handle(Request $request, $routeParams = [])
+ {
+ $this->registerErrorHandlers(); // @todo convert to middleware and add to route group?
+
+ $params = array_merge($request->all(), $routeParams);
+
+ return $this->call($params);
+ }
+
+ public function call($params = [])
+ {
+ $params = new ApiParams($params);
+
+ return $this->run($params);
+ }
+
+ public function hydrate($object, $params)
+ {
+ foreach ($params as $k => $v) {
+ $object->$k = $v;
+ }
+ }
+
+ protected function dispatch($command, $params)
+ {
+ $this->event(new CommandWillBeDispatched($command, $params));
+ return $this->bus->dispatch($command);
+ }
+
+ protected function event($event)
+ {
+ event($event);
+ }
+
+ public function document()
+ {
+ return new Document;
+ }
+
+ protected function buildUrl($route, $params = [], $input = [])
+ {
+ $url = route('flarum.api.'.$route, $params);
+ $queryString = $input ? '?'.http_build_query($input) : '';
+
+ return $url.$queryString;
+ }
+
+ protected function respondWithoutContent($statusCode = 204, $headers = [])
+ {
+ return Response::make('', $statusCode, $headers);
+ }
+
+ protected function respondWithArray($array, $statusCode = 200, $headers = [])
+ {
+ return Response::json($array, $statusCode, $headers);
+ }
+
+ protected function respondWithDocument($document, $statusCode = 200, $headers = [])
+ {
+ $headers['Content-Type'] = 'application/vnd.api+json';
+
+ $this->event(new WillRespondWithDocument($document, $statusCode, $headers));
+
+ return $this->respondWithArray($document->toArray(), $statusCode, $headers);
+ }
+
+ protected function registerErrorHandlers()
+ {
+ // if (! Config::get('app.debug')) {
+ // App::error(function ($exception, $code) {
+ // return $this->respondWithError('ApplicationError', $code);
+ // });
+ // }
+
+ // App::error(function (ModelNotFoundException $exception) {
+ // return $this->respondWithError('ResourceNotFound', 404);
+ // });
+
+ // App::error(function (ValidationFailureException $exception) {
+ // $errors = [];
+ // foreach ($exception->getErrors()->getMessages() as $field => $messages) {
+ // $errors[] = [
+ // 'code' => 'ValidationFailure',
+ // 'detail' => implode("\n", $messages),
+ // 'path' => $field
+ // ];
+ // }
+ // return $this->respondWithErrors($errors, 422);
+ // });
+ }
+
+ protected function respondWithErrors($errors, $httpCode = 500)
+ {
+ return Response::json(['errors' => $errors], $httpCode);
+ }
+
+ protected function respondWithError($error, $httpCode = 500, $detail = null)
+ {
+ $error = ['code' => $error];
+
+ if ($detail) {
+ $error['detail'] = $detail;
+ }
+
+ return $this->respondWithErrors([$error], $httpCode);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Discussions/Create.php b/src/Api/Actions/Discussions/CreateAction.php
similarity index 52%
rename from src/Flarum/Api/Actions/Discussions/Create.php
rename to src/Api/Actions/Discussions/CreateAction.php
index 716f1d433..fe91ec51a 100644
--- a/src/Flarum/Api/Actions/Discussions/Create.php
+++ b/src/Api/Actions/Discussions/CreateAction.php
@@ -1,45 +1,41 @@
input('discussions.title');
- $content = $this->input('discussions.content');
- $user = User::current();
- $command = new StartDiscussionCommand($title, $content, $user);
+ $title = $params->get('discussions.title');
+ $content = $params->get('discussions.content');
+ $user = $this->actor->getUser();
- Event::fire('Flarum.Api.Actions.Discussions.Create.WillExecuteCommand', [$command, $this->document]);
-
- $discussion = $this->commandBus->execute($command);
+ $command = new StartDiscussionCommand($title, $content, $user, app('flarum.forum'));
+ $discussion = $this->dispatch($command, $params);
// After creating the discussion, we assume that the user has seen all
// of the posts in the discussion; thus, we will mark the discussion
// as read if they are logged in.
if ($user->exists) {
$command = new ReadDiscussionCommand($discussion->id, $user, 1);
- $this->commandBus->execute($command);
+ $this->dispatch($command, $params);
}
$serializer = new DiscussionSerializer(['posts']);
- $this->document->setPrimaryElement($serializer->resource($discussion));
+ $document = $this->document()->setPrimaryElement($serializer->resource($discussion));
- return $this->respondWithDocument();
+ return $this->respondWithDocument($document);
}
}
diff --git a/src/Api/Actions/Discussions/DeleteAction.php b/src/Api/Actions/Discussions/DeleteAction.php
new file mode 100644
index 000000000..78f8a7fab
--- /dev/null
+++ b/src/Api/Actions/Discussions/DeleteAction.php
@@ -0,0 +1,23 @@
+get('id');
+
+ $command = new DeleteDiscussionCommand($discussionId, $this->actor->getUser());
+ $this->dispatch($command, $params);
+
+ return $this->respondWithoutContent();
+ }
+}
diff --git a/src/Api/Actions/Discussions/IndexAction.php b/src/Api/Actions/Discussions/IndexAction.php
new file mode 100644
index 000000000..b6881a0d8
--- /dev/null
+++ b/src/Api/Actions/Discussions/IndexAction.php
@@ -0,0 +1,81 @@
+actor = $actor;
+ $this->searcher = $searcher;
+ }
+
+ /**
+ * Show a list of discussions.
+ *
+ * @todo custom rate limit for this function? determined by if $key was valid?
+ * @return Response
+ */
+ protected function run(ApiParams $params)
+ {
+ $query = $params->get('q');
+ $start = $params->start();
+ $include = $params->included(['startPost', 'lastPost', 'relevantPosts']);
+ $count = $params->count(20, 50);
+ $sort = $params->sort(['', 'lastPost', 'replies', 'created']);
+
+ $relations = array_merge(['startUser', 'lastUser'], $include);
+
+ // Set up the discussion finder with our search criteria, and get the
+ // requested range of results with the necessary relations loaded.
+ $criteria = new DiscussionSearchCriteria($this->actor->getUser(), $query, $sort['field'], $sort['order']);
+ $load = array_merge($relations, ['state']);
+
+ $results = $this->searcher->search($criteria, $count, $start, $load);
+
+ $document = $this->document();
+
+ if (($total = $results->getTotal()) !== null) {
+ $document->addMeta('total', $total);
+ }
+
+ // If there are more results, then we need to construct a URL to the
+ // next results page and add that to the metadata. We do this by
+ // compacting all of the valid query parameters which have been
+ // specified.
+ if ($results->areMoreResults()) {
+ $start += $count;
+ $include = implode(',', $include);
+ $sort = $sort['string'];
+ $input = array_filter(compact('query', 'sort', 'start', 'count', 'include'));
+ $moreUrl = $this->buildUrl('discussions.index', [], $input);
+ } else {
+ $moreUrl = '';
+ }
+ $document->addMeta('moreUrl', $moreUrl);
+
+ // Finally, we can set up the discussion serializer and use it to create
+ // a collection of discussion results.
+ $serializer = new DiscussionSerializer($relations);
+ $document->setPrimaryElement($serializer->collection($results->getDiscussions()));
+
+ return $this->respondWithDocument($document);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Discussions/Show.php b/src/Api/Actions/Discussions/ShowAction.php
similarity index 52%
rename from src/Flarum/Api/Actions/Discussions/Show.php
rename to src/Api/Actions/Discussions/ShowAction.php
index 1c0d98e0b..635fab3c2 100644
--- a/src/Flarum/Api/Actions/Discussions/Show.php
+++ b/src/Api/Actions/Discussions/ShowAction.php
@@ -1,15 +1,24 @@
actor = $actor;
+ $this->discussions = $discussions;
$this->posts = $posts;
}
@@ -32,15 +43,15 @@ class Show extends Base
*
* @return Response
*/
- protected function run()
+ protected function run(ApiParams $params)
{
- $include = $this->included(['startPost', 'lastPost', 'posts']);
+ $include = $params->included(['startPost', 'lastPost', 'posts']);
- $discussion = Discussion::whereCanView()->findOrFail($this->param('id'));
+ $discussion = $this->discussions->findOrFail($params->get('id'), $this->actor->getUser());
if (in_array('posts', $include)) {
$relations = ['user', 'user.groups', 'editUser', 'hideUser'];
- $discussion->posts = $this->getPostsForDiscussion($this->posts, $discussion->id, $relations);
+ $discussion->posts = $this->getPostsForDiscussion($params, $discussion->id)->load($relations);
$include = array_merge($include, array_map(function ($relation) {
return 'posts.'.$relation;
@@ -52,8 +63,8 @@ class Show extends Base
// relations, we will specify that we want the 'posts' relation to be
// linked so that a list of post IDs will show up in the response.
$serializer = new DiscussionSerializer($include, ['posts']);
- $this->document->setPrimaryElement($serializer->resource($discussion));
+ $document = $this->document()->setPrimaryElement($serializer->resource($discussion));
- return $this->respondWithDocument();
+ return $this->respondWithDocument($document);
}
}
diff --git a/src/Api/Actions/Discussions/UpdateAction.php b/src/Api/Actions/Discussions/UpdateAction.php
new file mode 100644
index 000000000..6f7b5f67c
--- /dev/null
+++ b/src/Api/Actions/Discussions/UpdateAction.php
@@ -0,0 +1,54 @@
+get('id');
+ $user = $this->actor->getUser();
+
+ // First, we will run the EditDiscussionCommand. This will update the
+ // discussion's direct properties; by default, this is just the title.
+ // As usual, however, we will fire an event to allow plugins to update
+ // additional properties.
+ if ($data = array_except($params->get('discussions'), ['readNumber'])) {
+ $command = new EditDiscussionCommand($discussionId, $user);
+ $this->hydrate($command, $params->get('discussions'));
+ $discussion = $this->dispatch($command, $params);
+ }
+
+ // Next, if a read number was specified in the request, we will run the
+ // ReadDiscussionCommand.
+ //
+ // @todo Currently, if the user doesn't have permission to edit a
+ // discussion, they're unable to update their readNumber because a
+ // PermissionsDeniedException is thrown by the
+ // EditDiscussionCommand above. So this needs to be extracted into
+ // its own endpoint.
+ if ($readNumber = $params->get('discussions.readNumber')) {
+ $command = new ReadDiscussionCommand($discussionId, $user, $readNumber);
+ $this->dispatch($command, $params);
+ }
+
+ // Presumably, the discussion was updated successfully. (One of the command
+ // handlers would have thrown an exception if not.) We set this
+ // discussion as our document's primary element.
+ $serializer = new DiscussionSerializer(['addedPosts', 'addedPosts.user']);
+ $document = $this->document()->setPrimaryElement($serializer->resource($discussion));
+
+ return $this->respondWithDocument($document);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Groups/Index.php b/src/Api/Actions/Groups/IndexAction.php
similarity index 100%
rename from src/Flarum/Api/Actions/Groups/Index.php
rename to src/Api/Actions/Groups/IndexAction.php
diff --git a/src/Flarum/Api/Actions/Posts/Create.php b/src/Api/Actions/Posts/CreateAction.php
similarity index 62%
rename from src/Flarum/Api/Actions/Posts/Create.php
rename to src/Api/Actions/Posts/CreateAction.php
index 81a63951d..96db97aff 100644
--- a/src/Flarum/Api/Actions/Posts/Create.php
+++ b/src/Api/Actions/Posts/CreateAction.php
@@ -1,50 +1,47 @@
actor->getUser();
// We've received a request to post a reply. By default, the only
// required attributes of a post is the ID of the discussion to post in,
// the post content, and the author's user account. Let's set up a
// command with this information. We also fire an event to allow plugins
// to add data to the command.
- $discussionId = $this->input('posts.links.discussion');
- $content = $this->input('posts.content');
+ $discussionId = $params->get('posts.links.discussion');
+ $content = $params->get('posts.content');
+
$command = new PostReplyCommand($discussionId, $content, $user);
-
- Event::fire('Flarum.Api.Actions.Posts.Create.WillExecuteCommand', [$command]);
-
- $post = $this->commandBus->execute($command);
+ $post = $this->dispatch($command, $params);
// After replying, we assume that the user has seen all of the posts
// in the discussion; thus, we will mark the discussion as read if
// they are logged in.
if ($user->exists) {
$command = new ReadDiscussionCommand($discussionId, $user, $post->number);
- $this->commandBus->execute($command);
+ $this->dispatch($command, $params);
}
// Presumably, the post was created successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new PostSerializer;
- $this->document->setPrimaryElement($serializer->resource($post));
+ $document = $this->document()->setPrimaryElement($serializer->resource($post));
- return $this->respondWithDocument(201);
+ return $this->respondWithDocument($document, 201);
}
}
diff --git a/src/Api/Actions/Posts/DeleteAction.php b/src/Api/Actions/Posts/DeleteAction.php
new file mode 100644
index 000000000..66c806b90
--- /dev/null
+++ b/src/Api/Actions/Posts/DeleteAction.php
@@ -0,0 +1,23 @@
+get('id');
+
+ $command = new DeletePostCommand($postId, $this->actor->getUser());
+ $this->dispatch($command, $params);
+
+ return $this->respondWithoutContent();
+ }
+}
diff --git a/src/Api/Actions/Posts/GetsPostsForDiscussion.php b/src/Api/Actions/Posts/GetsPostsForDiscussion.php
new file mode 100644
index 000000000..f1f847420
--- /dev/null
+++ b/src/Api/Actions/Posts/GetsPostsForDiscussion.php
@@ -0,0 +1,31 @@
+sort(['time']);
+ $count = $params->count(20, 50);
+ $user = $this->actor->getUser();
+
+ if (($near = $params->get('near')) > 1) {
+ $start = $this->posts->getIndexForNumber($discussionId, $near, $user);
+ $start = max(0, $start - $count / 2);
+ } else {
+ $start = 0;
+ }
+
+ return $this->posts->findByDiscussion(
+ $discussionId,
+ $user,
+ $sort['field'],
+ $sort['order'] ?: 'asc',
+ $count,
+ $start
+ );
+ }
+}
diff --git a/src/Flarum/Api/Actions/Posts/Index.php b/src/Api/Actions/Posts/IndexAction.php
similarity index 52%
rename from src/Flarum/Api/Actions/Posts/Index.php
rename to src/Api/Actions/Posts/IndexAction.php
index 7bd1af190..11f8685ba 100644
--- a/src/Flarum/Api/Actions/Posts/Index.php
+++ b/src/Api/Actions/Posts/IndexAction.php
@@ -1,29 +1,31 @@
actor = $actor;
$this->posts = $posts;
}
@@ -32,16 +34,17 @@ class Index extends Base
*
* @return Response
*/
- protected function run()
+ protected function run(ApiParams $params)
{
- $postIds = (array) $this->input('ids');
+ $postIds = (array) $params->get('ids');
$include = ['user', 'user.groups', 'editUser', 'hideUser'];
+ $user = $this->actor->getUser();
if (count($postIds)) {
- $posts = $this->posts->findMany($postIds, $include);
+ $posts = $this->posts->findByIds($postIds, $user);
} else {
- $discussionId = $this->input('discussions');
- $posts = $this->getPostsForDiscussion($this->posts, $discussionId, $include);
+ $discussionId = $params->get('discussions');
+ $posts = $this->getPostsForDiscussion($params, $discussionId, $user);
}
if (! count($posts)) {
@@ -52,8 +55,8 @@ class Index extends Base
// a post resource or collection, depending on how many posts were
// requested.
$serializer = new PostSerializer($include);
- $this->document->setPrimaryElement($serializer->collection($posts));
+ $document = $this->document()->setPrimaryElement($serializer->collection($posts->load($include)));
- return $this->respondWithDocument();
+ return $this->respondWithDocument($document);
}
}
diff --git a/src/Api/Actions/Posts/ShowAction.php b/src/Api/Actions/Posts/ShowAction.php
new file mode 100644
index 000000000..b3110e6d1
--- /dev/null
+++ b/src/Api/Actions/Posts/ShowAction.php
@@ -0,0 +1,42 @@
+actor = $actor;
+ $this->posts = $posts;
+ }
+
+ /**
+ * Show a single post by ID.
+ *
+ * @return Response
+ */
+ protected function run(ApiParams $params)
+ {
+ $id = $params->get('id');
+ $posts = $this->posts->findOrFail($id, $this->actor->getUser());
+
+ $include = $params->included(['discussion', 'replyTo']);
+ $relations = array_merge(['user', 'editUser', 'hideUser'], $include);
+ $posts->load($relations);
+
+ // Finally, we can set up the post serializer and use it to create
+ // a post resource or collection, depending on how many posts were
+ // requested.
+ $serializer = new PostSerializer($relations);
+ $document = $this->document()->setPrimaryElement($serializer->resource($posts->first()));
+
+ return $this->respondWithDocument($document);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Posts/Update.php b/src/Api/Actions/Posts/UpdateAction.php
similarity index 53%
rename from src/Flarum/Api/Actions/Posts/Update.php
rename to src/Api/Actions/Posts/UpdateAction.php
index cdf0a3399..bced8498a 100644
--- a/src/Flarum/Api/Actions/Posts/Update.php
+++ b/src/Api/Actions/Posts/UpdateAction.php
@@ -1,39 +1,34 @@
param('id');
+ $postId = $params->get('id');
// EditPost is a single command because we don't want to allow partial
// updates (i.e. if we were to run one command and then another, if the
// second one failed, the first one would still have succeeded.)
- $command = new EditPostCommand($postId, User::current());
- $this->fillCommandWithInput($command, 'posts');
-
- Event::fire('Flarum.Api.Actions.Posts.Update.WillExecuteCommand', [$command]);
-
- $post = $this->commandBus->execute($command);
+ $command = new EditPostCommand($postId, $this->actor->getUser());
+ $this->hydrate($command, $params->get('posts'));
+ $post = $this->dispatch($command, $params);
// Presumably, the post was updated successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new PostSerializer;
- $this->document->setPrimaryElement($serializer->resource($post));
+ $document = $this->document()->setPrimaryElement($serializer->resource($post));
- return $this->respondWithDocument();
+ return $this->respondWithDocument($document);
}
}
diff --git a/src/Api/Actions/TokenAction.php b/src/Api/Actions/TokenAction.php
new file mode 100644
index 000000000..8108c9d34
--- /dev/null
+++ b/src/Api/Actions/TokenAction.php
@@ -0,0 +1,43 @@
+users = $users;
+ $this->bus = $bus;
+ }
+
+ /**
+ * Log in and return a token.
+ *
+ * @return Response
+ */
+ public function run(ApiParams $params)
+ {
+ $identification = $params->get('identification');
+ $password = $params->get('password');
+
+ $user = $this->users->findByIdentification($identification);
+
+ if (! $user || ! $user->checkPassword($password)) {
+ return $this->respondWithError('invalidLogin', 401);
+ }
+
+ $command = new GenerateAccessTokenCommand($user->id);
+ $token = $this->dispatch($command, $params);
+
+ return new JsonResponse([
+ 'token' => $token->id,
+ 'userId' => $user->id
+ ]);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Users/Create.php b/src/Api/Actions/Users/CreateAction.php
similarity index 50%
rename from src/Flarum/Api/Actions/Users/Create.php
rename to src/Api/Actions/Users/CreateAction.php
index 7890bd844..f2c035a7b 100644
--- a/src/Flarum/Api/Actions/Users/Create.php
+++ b/src/Api/Actions/Users/CreateAction.php
@@ -1,39 +1,36 @@
input('users.username');
- $email = $this->input('users.email');
- $password = $this->input('users.password');
- $command = new RegisterUserCommand($username, $email, $password, User::current());
+ $username = $params->get('users.username');
+ $email = $params->get('users.email');
+ $password = $params->get('users.password');
- Event::fire('Flarum.Api.Actions.Users.Create.WillExecuteCommand', [$command]);
-
- $user = $this->commandBus->execute($command);
+ $command = new RegisterUserCommand($username, $email, $password, $this->actor->getUser());
+ $this->dispatch($command, $params);
// Presumably, the user was created successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new UserSerializer;
- $this->document->setPrimaryElement($serializer->resource($user));
+ $document = $this->document()->setPrimaryElement($serializer->resource($user));
- return $this->respondWithDocument(201);
+ return $this->respondWithDocument($document, 201);
}
}
diff --git a/src/Api/Actions/Users/DeleteAction.php b/src/Api/Actions/Users/DeleteAction.php
new file mode 100644
index 000000000..4f9d2a528
--- /dev/null
+++ b/src/Api/Actions/Users/DeleteAction.php
@@ -0,0 +1,23 @@
+get('id');
+
+ $command = new DeleteUserCommand($userId, $this->actor->getUser());
+ $this->dispatch($command, $params);
+
+ return $this->respondWithoutContent();
+ }
+}
diff --git a/src/Flarum/Api/Actions/Users/Index.php b/src/Api/Actions/Users/IndexAction.php
similarity index 98%
rename from src/Flarum/Api/Actions/Users/Index.php
rename to src/Api/Actions/Users/IndexAction.php
index 57fd4eea7..67d07f7f1 100644
--- a/src/Flarum/Api/Actions/Users/Index.php
+++ b/src/Api/Actions/Users/IndexAction.php
@@ -5,11 +5,11 @@ use Flarum\Core\Users\UserFinder;
use Flarum\Api\Actions\Base;
use Flarum\Api\Serializers\UserSerializer;
-class Index extends Base
+class IndexAction extends BaseAction
{
/**
* The user finder.
- *
+ *
* @var UserFinder
*/
protected $finder;
diff --git a/src/Api/Actions/Users/ShowAction.php b/src/Api/Actions/Users/ShowAction.php
new file mode 100644
index 000000000..e4a0d79b5
--- /dev/null
+++ b/src/Api/Actions/Users/ShowAction.php
@@ -0,0 +1,38 @@
+actor = $actor;
+ $this->users = $users;
+ }
+
+ /**
+ * Show a single user.
+ *
+ * @return Response
+ */
+ public function run(ApiParams $params)
+ {
+ $user = $this->users->findOrFail($params->get('id'), $this->actor->getUser());
+
+ // Set up the user serializer, which we will use to create the
+ // document's primary resource. We will specify that we want the
+ // 'groups' relation to be included by default.
+ $serializer = new UserSerializer(['groups']);
+ $document = $this->document()->setPrimaryElement($serializer->resource($user));
+
+ return $this->respondWithDocument($document);
+ }
+}
diff --git a/src/Flarum/Api/Actions/Users/Update.php b/src/Api/Actions/Users/UpdateAction.php
similarity index 54%
rename from src/Flarum/Api/Actions/Users/Update.php
rename to src/Api/Actions/Users/UpdateAction.php
index ed15e0a3c..e058b4ab3 100644
--- a/src/Flarum/Api/Actions/Users/Update.php
+++ b/src/Api/Actions/Users/UpdateAction.php
@@ -1,13 +1,11 @@
param('id');
+ $userId = $params->get('id');
// EditUser is a single command because we don't want to allow partial
// updates (i.e. if we were to run one command and then another, if the
// second one failed, the first one would still have succeeded.)
- $command = new EditUserCommand($userId, User::current());
- $this->fillCommandWithInput($command, 'users');
-
- Event::fire('Flarum.Api.Actions.Users.Update.WillExecuteCommand', [$command]);
-
- $user = $this->commandBus->execute($command);
+ $command = new EditUserCommand($userId, $this->actor->getUser());
+ $this->hydrate($command, $params->get('users'));
+ $this->dispatch($command);
// Presumably, the user was updated successfully. (The command handler
// would have thrown an exception if not.) We set this user as our
// document's primary element.
$serializer = new UserSerializer;
- $this->document->setPrimaryElement($serializer->resource($user));
+ $document = $this->document()->setPrimaryElement($serializer->resource($user));
- return $this->respondWithDocument();
+ return $this->respondWithDocument($document);
}
}
diff --git a/src/Api/ApiServiceProvider.php b/src/Api/ApiServiceProvider.php
new file mode 100644
index 000000000..0bbe49eca
--- /dev/null
+++ b/src/Api/ApiServiceProvider.php
@@ -0,0 +1,29 @@
+app['Flarum\Core\Support\Actor']);
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->singleton('Flarum\Core\Support\Actor');
+ }
+}
diff --git a/src/Api/Events/CommandWillBeDispatched.php b/src/Api/Events/CommandWillBeDispatched.php
new file mode 100644
index 000000000..0b1d12fc2
--- /dev/null
+++ b/src/Api/Events/CommandWillBeDispatched.php
@@ -0,0 +1,14 @@
+command = $command;
+ $this->params = $params;
+ }
+}
diff --git a/src/Api/Events/SerializeAttributes.php b/src/Api/Events/SerializeAttributes.php
new file mode 100644
index 000000000..0c44efc11
--- /dev/null
+++ b/src/Api/Events/SerializeAttributes.php
@@ -0,0 +1,17 @@
+serializer = $serializer;
+ $this->model = $model;
+ $this->attributes = $attributes;
+ }
+}
diff --git a/src/Api/Events/SerializeRelationship.php b/src/Api/Events/SerializeRelationship.php
new file mode 100644
index 000000000..48826796c
--- /dev/null
+++ b/src/Api/Events/SerializeRelationship.php
@@ -0,0 +1,23 @@
+serializer = $serializer;
+ $this->model = $model;
+ $this->type = $type;
+ $this->name = $name;
+ $this->relations = $relations;
+ }
+}
diff --git a/src/Api/Events/WillRespondWithDocument.php b/src/Api/Events/WillRespondWithDocument.php
new file mode 100644
index 000000000..16adf1d51
--- /dev/null
+++ b/src/Api/Events/WillRespondWithDocument.php
@@ -0,0 +1,17 @@
+document = $document;
+ $this->statusCode = $statusCode;
+ $this->headers = $headers;
+ }
+}
diff --git a/src/Api/Middleware/LoginWithHeaderMiddleware.php b/src/Api/Middleware/LoginWithHeaderMiddleware.php
new file mode 100644
index 000000000..e354da4ed
--- /dev/null
+++ b/src/Api/Middleware/LoginWithHeaderMiddleware.php
@@ -0,0 +1,29 @@
+actor = $actor;
+ }
+
+ public function handle($request, Closure $next)
+ {
+ $header = $request->headers->get('authorization');
+ if (starts_with($header, $this->prefix) &&
+ ($token = substr($header, strlen($this->prefix))) &&
+ ($accessToken = AccessToken::where('id', $token)->first())) {
+ $this->actor->setUser($accessToken->user);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Flarum/Api/Serializers/ActivitySerializer.php b/src/Api/Serializers/ActivitySerializer.php
similarity index 100%
rename from src/Flarum/Api/Serializers/ActivitySerializer.php
rename to src/Api/Serializers/ActivitySerializer.php
diff --git a/src/Api/Serializers/BaseSerializer.php b/src/Api/Serializers/BaseSerializer.php
new file mode 100644
index 000000000..5f0224f3a
--- /dev/null
+++ b/src/Api/Serializers/BaseSerializer.php
@@ -0,0 +1,62 @@
+state;
+ $user = static::$actor->getUser();
+ $state = $discussion->stateFor($user);
$attributes += [
'commentsCount' => (int) $discussion->comments_count,
'startTime' => $discussion->start_time->toRFC3339String(),
'lastTime' => $discussion->last_time ? $discussion->last_time->toRFC3339String() : null,
'lastPostNumber' => $discussion->last_post_number,
- 'canReply' => $discussion->permission('reply'),
- 'canEdit' => $discussion->permission('edit'),
- 'canDelete' => $discussion->permission('delete'),
+ 'canReply' => $discussion->can($user, 'reply'),
+ 'canEdit' => $discussion->can($user, 'edit'),
+ 'canDelete' => $discussion->can($user, 'delete'),
'readTime' => $state && $state->read_time ? $state->read_time->toRFC3339String() : null,
'readNumber' => $state ? (int) $state->read_number : 0
];
- return $this->attributesEvent($discussion, $attributes);
+ $this->attributesEvent($discussion, $attributes);
+
+ return $attributes;
}
/**
@@ -53,7 +50,9 @@ class DiscussionSerializer extends DiscussionBasicSerializer
*/
public function linkPosts(Discussion $discussion)
{
- return (new PostBasicSerializer)->collection($discussion->posts()->whereCanView()->orderBy('time', 'asc')->ids());
+ $user = static::$actor->getUser();
+
+ return (new PostBasicSerializer)->collection($discussion->posts()->whereCan($user, 'view')->orderBy('time', 'asc')->ids());
}
/**
diff --git a/src/Flarum/Api/Serializers/GroupSerializer.php b/src/Api/Serializers/GroupSerializer.php
similarity index 97%
rename from src/Flarum/Api/Serializers/GroupSerializer.php
rename to src/Api/Serializers/GroupSerializer.php
index d90ebc29a..3d77576a2 100644
--- a/src/Flarum/Api/Serializers/GroupSerializer.php
+++ b/src/Api/Serializers/GroupSerializer.php
@@ -1,6 +1,6 @@
getUser();
unset($attributes['content']);
diff --git a/src/Flarum/Api/Serializers/UserBasicSerializer.php b/src/Api/Serializers/UserBasicSerializer.php
similarity index 96%
rename from src/Flarum/Api/Serializers/UserBasicSerializer.php
rename to src/Api/Serializers/UserBasicSerializer.php
index f9cf4c96c..d047114f7 100644
--- a/src/Flarum/Api/Serializers/UserBasicSerializer.php
+++ b/src/Api/Serializers/UserBasicSerializer.php
@@ -1,6 +1,6 @@
getUser();
+ $canEdit = $user->can($actorUser, 'edit');
+
$attributes += [
'joinTime' => $user->join_time ? $user->join_time->toRFC3339String() : null,
'lastSeenTime' => $user->last_seen_time ? $user->last_seen_time->toRFC3339String() : null,
- 'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
'discussionsCount' => (int) $user->discussions_count,
'postsCount' => (int) $user->posts_count,
- 'canEdit' => $user->permission('edit'),
- 'canDelete' => $user->permission('delete'),
+ 'canEdit' => $canEdit,
+ 'canDelete' => $user->can($actorUser, 'delete'),
];
+ if ($canEdit) {
+ $attributes += [
+ 'isActivated' => $user->is_activated,
+ 'email' => $user->email,
+ 'isConfirmed' => $user->is_confirmed
+ ];
+ }
+
+ if ($user->id === $actorUser->id) {
+ $attributes += [
+ 'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
+ ];
+ }
+
return $this->attributesEvent($user, $attributes);
}
/**
* Get a collection containing a user's groups.
- *
+ *
* @param User $user
* @param array $relations
* @return Tobscure\JsonApi\Collection
diff --git a/src/routes.api.php b/src/Api/routes.php
similarity index 65%
rename from src/routes.api.php
rename to src/Api/routes.php
index d895be596..a7d3ed5be 100644
--- a/src/routes.api.php
+++ b/src/Api/routes.php
@@ -2,36 +2,18 @@
$action = function ($class) {
return function () use ($class) {
- $action = App::make($class);
- $request = app('request');
- $parameters = Route::current()->parameters();
+ $action = $this->app->make($class);
+ $request = $this->app['request']->instance();
+ $parameters = $this->app['router']->current()->parameters();
return $action->handle($request, $parameters);
};
};
-// @todo refactor into a unit-testable class
-Route::filter('attemptLogin', function($route, $request) {
- $prefix = 'Token ';
- if (starts_with($request->headers->get('authorization'), $prefix)) {
- $token = substr($request->headers->get('authorization'), strlen($prefix));
- if ($user = Flarum\Core\Users\User::where('token', $token)->first()) {
- Auth::setUser($user);
- }
- }
-});
+Route::group(['prefix' => 'api', 'middleware' => 'Flarum\Api\Middleware\LoginWithHeaderMiddleware'], function () use ($action) {
-Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($action) {
-
- /*
- |--------------------------------------------------------------------------
- | Auth
- |--------------------------------------------------------------------------
- */
-
- // Login
- Route::post('auth/login', [
- 'as' => 'flarum.api.auth.login',
- 'uses' => $action('Flarum\Api\Actions\Auth\Login')
+ Route::post('token', [
+ 'as' => 'flarum.api.token',
+ 'uses' => $action('Flarum\Api\Actions\TokenAction')
]);
/*
@@ -43,31 +25,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List users
Route::get('users', [
'as' => 'flarum.api.users.index',
- 'uses' => $action('Flarum\Api\Actions\Users\Index')
+ 'uses' => $action('Flarum\Api\Actions\Users\IndexAction')
]);
// Register a user
Route::post('users', [
'as' => 'flarum.api.users.create',
- 'uses' => $action('Flarum\Api\Actions\Users\Create')
+ 'uses' => $action('Flarum\Api\Actions\Users\CreateAction')
]);
// Get a single user
Route::get('users/{id}', [
'as' => 'flarum.api.users.show',
- 'uses' => $action('Flarum\Api\Actions\Users\Show')
+ 'uses' => $action('Flarum\Api\Actions\Users\ShowAction')
]);
// Edit a user
Route::put('users/{id}', [
'as' => 'flarum.api.users.update',
- 'uses' => $action('Flarum\Api\Actions\Users\Update')
+ 'uses' => $action('Flarum\Api\Actions\Users\UpdateAction')
]);
// Delete a user
Route::delete('users/{id}', [
'as' => 'flarum.api.users.delete',
- 'uses' => $action('Flarum\Api\Actions\Users\Delete')
+ 'uses' => $action('Flarum\Api\Actions\Users\DeleteAction')
]);
/*
@@ -79,13 +61,13 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List activity
Route::get('activity', [
'as' => 'flarum.api.activity.index',
- 'uses' => $action('Flarum\Api\Actions\Activity\Index')
+ 'uses' => $action('Flarum\Api\Actions\Activity\IndexAction')
]);
// List notifications for the current user
Route::get('notifications', [
'as' => 'flarum.api.notifications.index',
- 'uses' => $action('Flarum\Api\Actions\Notifications\Index')
+ 'uses' => $action('Flarum\Api\Actions\Notifications\IndexAction')
]);
/*
@@ -97,31 +79,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List discussions
Route::get('discussions', [
'as' => 'flarum.api.discussions.index',
- 'uses' => $action('Flarum\Api\Actions\Discussions\Index')
+ 'uses' => $action('Flarum\Api\Actions\Discussions\IndexAction')
]);
// Create a discussion
Route::post('discussions', [
'as' => 'flarum.api.discussions.create',
- 'uses' => $action('Flarum\Api\Actions\Discussions\Create')
+ 'uses' => $action('Flarum\Api\Actions\Discussions\CreateAction')
]);
// Show a single discussion
Route::get('discussions/{id}', [
'as' => 'flarum.api.discussions.show',
- 'uses' => $action('Flarum\Api\Actions\Discussions\Show')
+ 'uses' => $action('Flarum\Api\Actions\Discussions\ShowAction')
]);
// Edit a discussion
Route::put('discussions/{id}', [
'as' => 'flarum.api.discussions.update',
- 'uses' => $action('Flarum\Api\Actions\Discussions\Update')
+ 'uses' => $action('Flarum\Api\Actions\Discussions\UpdateAction')
]);
// Delete a discussion
Route::delete('discussions/{id}', [
'as' => 'flarum.api.discussions.delete',
- 'uses' => $action('Flarum\Api\Actions\Discussions\Delete')
+ 'uses' => $action('Flarum\Api\Actions\Discussions\DeleteAction')
]);
/*
@@ -133,32 +115,32 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List posts, usually for a discussion
Route::get('posts', [
'as' => 'flarum.api.posts.index',
- 'uses' => $action('Flarum\Api\Actions\Posts\Index')
+ 'uses' => $action('Flarum\Api\Actions\Posts\IndexAction')
]);
// Create a post
// @todo consider 'discussions/{id}/links/posts'?
Route::post('posts', [
'as' => 'flarum.api.posts.create',
- 'uses' => $action('Flarum\Api\Actions\Posts\Create')
+ 'uses' => $action('Flarum\Api\Actions\Posts\CreateAction')
]);
// Show a single or multiple posts by ID
Route::get('posts/{id}', [
'as' => 'flarum.api.posts.show',
- 'uses' => $action('Flarum\Api\Actions\Posts\Show')
+ 'uses' => $action('Flarum\Api\Actions\Posts\ShowAction')
]);
// Edit a post
Route::put('posts/{id}', [
'as' => 'flarum.api.posts.update',
- 'uses' => $action('Flarum\Api\Actions\Posts\Update')
+ 'uses' => $action('Flarum\Api\Actions\Posts\UpdateAction')
]);
// Delete a post
Route::delete('posts/{id}', [
'as' => 'flarum.api.posts.delete',
- 'uses' => $action('Flarum\Api\Actions\Posts\Delete')
+ 'uses' => $action('Flarum\Api\Actions\Posts\DeleteAction')
]);
/*
@@ -170,31 +152,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List groups
Route::get('groups', [
'as' => 'flarum.api.groups.index',
- 'uses' => $action('Flarum\Api\Actions\Groups\Index')
+ 'uses' => $action('Flarum\Api\Actions\Groups\IndexAction')
]);
// Create a group
Route::post('groups', [
'as' => 'flarum.api.groups.create',
- 'uses' => $action('Flarum\Api\Actions\Groups\Create')
+ 'uses' => $action('Flarum\Api\Actions\Groups\CreateAction')
]);
// Show a single group
Route::get('groups/{id}', [
'as' => 'flarum.api.groups.show',
- 'uses' => $action('Flarum\Api\Actions\Groups\Show')
+ 'uses' => $action('Flarum\Api\Actions\Groups\ShowAction')
]);
// Edit a group
Route::put('groups/{id}', [
'as' => 'flarum.api.groups.update',
- 'uses' => $action('Flarum\Api\Actions\Groups\Update')
+ 'uses' => $action('Flarum\Api\Actions\Groups\UpdateAction')
]);
// Delete a group
Route::delete('groups/{id}', [
'as' => 'flarum.api.groups.delete',
- 'uses' => $action('Flarum\Api\Actions\Groups\Delete')
+ 'uses' => $action('Flarum\Api\Actions\Groups\DeleteAction')
]);
});
diff --git a/src/Console/ConsoleServiceProvider.php b/src/Console/ConsoleServiceProvider.php
new file mode 100644
index 000000000..84693ebee
--- /dev/null
+++ b/src/Console/ConsoleServiceProvider.php
@@ -0,0 +1,22 @@
+commands('Flarum\Console\InstallCommand');
+ $this->commands('Flarum\Console\SeedCommand');
+ }
+
+ public function register()
+ {
+
+ }
+}
diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php
new file mode 100644
index 000000000..4fb4131fb
--- /dev/null
+++ b/src/Console/InstallCommand.php
@@ -0,0 +1,76 @@
+app = $app;
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function fire()
+ {
+ $path = str_replace($this->laravel['path.base'].'/', '', __DIR__.'/../../migrations');
+
+ $this->call('migrate', ['--path' => $path]);
+
+ $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\ConfigTableSeeder']);
+ $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\GroupsTableSeeder']);
+ $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\PermissionsTableSeeder']);
+ }
+
+ /**
+ * Get the console command arguments.
+ *
+ * @return array
+ */
+ protected function getArguments()
+ {
+ return [
+ // ['example', InputArgument::REQUIRED, 'An example argument.'],
+ ];
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions()
+ {
+ return [
+ // ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
+ ];
+ }
+
+}
diff --git a/src/Console/SeedCommand.php b/src/Console/SeedCommand.php
new file mode 100644
index 000000000..7f20887fa
--- /dev/null
+++ b/src/Console/SeedCommand.php
@@ -0,0 +1,71 @@
+app = $app;
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function fire()
+ {
+ $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\UsersTableSeeder']);
+ $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']);
+ }
+
+ /**
+ * Get the console command arguments.
+ *
+ * @return array
+ */
+ protected function getArguments()
+ {
+ return [
+ // ['example', InputArgument::REQUIRED, 'An example argument.'],
+ ];
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions()
+ {
+ return [
+ // ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
+ ];
+ }
+
+}
diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php b/src/Core/Commands/ConfirmEmailCommand.php
similarity index 81%
rename from src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php
rename to src/Core/Commands/ConfirmEmailCommand.php
index fc7d6547b..f1ffbf591 100644
--- a/src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php
+++ b/src/Core/Commands/ConfirmEmailCommand.php
@@ -1,4 +1,4 @@
-userId = $userId;
+ }
+}
diff --git a/src/Flarum/Core/Posts/Commands/PostReplyCommand.php b/src/Core/Commands/PostReplyCommand.php
similarity index 84%
rename from src/Flarum/Core/Posts/Commands/PostReplyCommand.php
rename to src/Core/Commands/PostReplyCommand.php
index 75e3d2d7e..7fa9e11f5 100644
--- a/src/Flarum/Core/Posts/Commands/PostReplyCommand.php
+++ b/src/Core/Commands/PostReplyCommand.php
@@ -1,7 +1,7 @@
-username = $username;
$this->email = $email;
$this->password = $password;
$this->user = $user;
+ $this->forum = $forum;
}
}
diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php b/src/Core/Commands/StartDiscussionCommand.php
similarity index 56%
rename from src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php
rename to src/Core/Commands/StartDiscussionCommand.php
index 5fbfa69b0..6d4e32b13 100644
--- a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php
+++ b/src/Core/Commands/StartDiscussionCommand.php
@@ -1,4 +1,4 @@
-title = $title;
$this->content = $content;
$this->user = $user;
+ $this->forum = $forum;
}
}
diff --git a/src/Core/CoreServiceProvider.php b/src/Core/CoreServiceProvider.php
new file mode 100644
index 000000000..dc39ba23a
--- /dev/null
+++ b/src/Core/CoreServiceProvider.php
@@ -0,0 +1,179 @@
+loadViewsFrom(__DIR__.'../../views', 'flarum');
+
+ $this->registerEventHandlers($events);
+ $this->registerPostTypes();
+ $this->registerPermissions();
+ $this->registerGambits();
+ $this->setupModels();
+
+ $bus->mapUsing(function ($command) {
+ return Bus::simpleMapping(
+ $command, 'Flarum\Core\Commands', 'Flarum\Core\Handlers\Commands'
+ );
+ });
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ // Register a singleton entity that represents this forum. This entity
+ // will be used to check for global forum permissions (like viewing the
+ // forum, registering, and starting discussions.)
+ $this->app->singleton('flarum.forum', 'Flarum\Core\Models\Forum');
+
+ // Register the extensions manager object. This manages a list of
+ // available extensions, and provides functionality to enable/disable
+ // them.
+ $this->app->singleton('flarum.extensions', 'Flarum\Core\Support\Extensions\Manager');
+
+ $this->app->bind('flarum.discussionFinder', 'Flarum\Core\Discussions\DiscussionFinder');
+
+ $this->app->singleton('flarum.formatter', function () {
+ $formatter = new FormatterManager($this->app);
+ $formatter->add('basic', 'Flarum\Core\Formatter\BasicFormatter');
+ return $formatter;
+ });
+
+ $this->app->bind(
+ 'Flarum\Core\Repositories\DiscussionRepositoryInterface',
+ 'Flarum\Core\Repositories\EloquentDiscussionRepository'
+ );
+ $this->app->bind(
+ 'Flarum\Core\Repositories\PostRepositoryInterface',
+ 'Flarum\Core\Repositories\EloquentPostRepository'
+ );
+ $this->app->bind(
+ 'Flarum\Core\Repositories\UserRepositoryInterface',
+ 'Flarum\Core\Repositories\EloquentUserRepository'
+ );
+ }
+
+ public function registerGambits()
+ {
+ $this->app->bind('Flarum\Core\Search\GambitManager', function () {
+ $gambits = new GambitManager($this->app);
+ $gambits->add('Flarum\Core\Search\Discussions\Gambits\AuthorGambit');
+ $gambits->add('Flarum\Core\Search\Discussions\Gambits\UnreadGambit');
+ $gambits->setFulltextGambit('Flarum\Core\Search\Discussions\Gambits\FulltextGambit');
+ return $gambits;
+ });
+ }
+
+ public function registerPostTypes()
+ {
+ Post::addType('comment', 'Flarum\Core\Models\CommentPost');
+ Post::addType('renamed', 'Flarum\Core\Models\RenamedPost');
+
+ CommentPost::setFormatter($this->app['flarum.formatter']);
+ }
+
+ public function registerEventHandlers($events)
+ {
+ $events->subscribe('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater');
+ $events->subscribe('Flarum\Core\Handlers\Events\UserMetadataUpdater');
+ $events->subscribe('Flarum\Core\Handlers\Events\RenamedPostCreator');
+ $events->subscribe('Flarum\Core\Handlers\Events\EmailConfirmationMailer');
+ }
+
+ public function setupModels()
+ {
+ Model::setForum($this->app['flarum.forum']);
+ Model::setValidator($this->app['validator']);
+
+ User::setHasher($this->app['hash']);
+ }
+
+ public function registerPermissions()
+ {
+ Forum::grantPermission(function ($grant, $user, $permission) {
+ return $user->hasPermission($permission, 'forum');
+ });
+
+ Post::grantPermission(function ($grant, $user, $permission) {
+ return $user->hasPermission($permission, 'post');
+ });
+
+ // Grant view access to a post only if the user can also view the
+ // discussion which the post is in. Also, the if the post is hidden,
+ // the user must have edit permissions too.
+ Post::grantPermission('view', function ($grant) {
+ $grant->whereCan('view', 'discussion');
+ });
+
+ Post::demandPermission('view', function ($demand) {
+ $demand->whereNull('hide_user_id')
+ ->orWhereCan('edit');
+ });
+
+ // Allow a user to edit their own post, unless it has been hidden by
+ // someone else.
+ Post::grantPermission('edit', function ($grant, $user) {
+ $grant->whereCan('editOwn')
+ ->where('user_id', $user->id);
+ });
+
+ Post::demandPermission('editOwn', function ($demand, $user) {
+ $demand->whereNull('hide_user_id');
+ if ($user) {
+ $demand->orWhere('hide_user_id', $user->id);
+ }
+ });
+
+ User::grantPermission(function ($grant, $user, $permission) {
+ return $user->hasPermission($permission, 'forum');
+ });
+
+ // Grant view access to a user if the user can view the forum.
+ User::grantPermission('view', function ($grant, $user) {
+ $grant->whereCan('view', 'forum');
+ });
+
+ // Allow a user to edit their own account.
+ User::grantPermission('edit', function ($grant, $user) {
+ $grant->where('id', $user->id);
+ });
+
+ Discussion::grantPermission(function ($grant, $user, $permission) {
+ return $user->hasPermission($permission, 'discussion');
+ });
+
+ // Grant view access to a discussion if the user can view the forum.
+ Discussion::grantPermission('view', function ($grant, $user) {
+ $grant->whereCan('view', 'forum');
+ });
+
+ // Allow a user to edit their own discussion.
+ Discussion::grantPermission('edit', function ($grant, $user) {
+ if ($user->hasPermission('editOwn', 'discussion')) {
+ $grant->where('start_user_id', $user->id);
+ }
+ });
+ }
+}
diff --git a/src/Core/Events/DiscussionStateWillBeSaved.php b/src/Core/Events/DiscussionStateWillBeSaved.php
new file mode 100644
index 000000000..1ec56107e
--- /dev/null
+++ b/src/Core/Events/DiscussionStateWillBeSaved.php
@@ -0,0 +1,16 @@
+state = $state;
+ $this->command = $command;
+ }
+}
diff --git a/src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php b/src/Core/Events/DiscussionWasDeleted.php
similarity index 65%
rename from src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php
rename to src/Core/Events/DiscussionWasDeleted.php
index 8efa6235b..271cc73d7 100644
--- a/src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php
+++ b/src/Core/Events/DiscussionWasDeleted.php
@@ -1,6 +1,6 @@
-discussion = $discussion;
+ $this->command = $command;
+ }
+}
diff --git a/src/Core/Events/DiscussionWillBeSaved.php b/src/Core/Events/DiscussionWillBeSaved.php
new file mode 100644
index 000000000..60b39fe17
--- /dev/null
+++ b/src/Core/Events/DiscussionWillBeSaved.php
@@ -0,0 +1,16 @@
+discussion = $discussion;
+ $this->command = $command;
+ }
+}
diff --git a/src/Flarum/Core/Posts/Events/PostWasDeleted.php b/src/Core/Events/PostWasDeleted.php
similarity index 65%
rename from src/Flarum/Core/Posts/Events/PostWasDeleted.php
rename to src/Core/Events/PostWasDeleted.php
index b57e680f1..c745f0980 100644
--- a/src/Flarum/Core/Posts/Events/PostWasDeleted.php
+++ b/src/Core/Events/PostWasDeleted.php
@@ -1,6 +1,6 @@
-post = $post;
+ $this->command = $command;
+ }
+}
diff --git a/src/Core/Events/PostWillBeSaved.php b/src/Core/Events/PostWillBeSaved.php
new file mode 100644
index 000000000..0b6b748eb
--- /dev/null
+++ b/src/Core/Events/PostWillBeSaved.php
@@ -0,0 +1,16 @@
+post = $post;
+ $this->command = $command;
+ }
+}
diff --git a/src/Flarum/Core/Users/Events/EmailWasChanged.php b/src/Core/Events/UserEmailWasChanged.php
similarity index 65%
rename from src/Flarum/Core/Users/Events/EmailWasChanged.php
rename to src/Core/Events/UserEmailWasChanged.php
index d018b8f93..5bc42943d 100644
--- a/src/Flarum/Core/Users/Events/EmailWasChanged.php
+++ b/src/Core/Events/UserEmailWasChanged.php
@@ -1,6 +1,6 @@
-user = $user;
+ $this->command = $command;
+ }
+}
diff --git a/src/Core/Events/UserWillBeSaved.php b/src/Core/Events/UserWillBeSaved.php
new file mode 100644
index 000000000..8a3bf7e9f
--- /dev/null
+++ b/src/Core/Events/UserWillBeSaved.php
@@ -0,0 +1,16 @@
+user = $user;
+ $this->command = $command;
+ }
+}
diff --git a/src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php b/src/Core/Exceptions/InvalidConfirmationTokenException.php
similarity index 62%
rename from src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php
rename to src/Core/Exceptions/InvalidConfirmationTokenException.php
index 3deb1dd59..b9232a5ae 100644
--- a/src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php
+++ b/src/Core/Exceptions/InvalidConfirmationTokenException.php
@@ -1,4 +1,4 @@
-errors = new MessageBag;
}
-
+
public function setErrors(MessageBag $errors)
{
$this->errors = $errors;
-
+
return $this;
}
-
+
public function getErrors()
{
return $this->errors;
}
-
+
public function setInput(array $input)
{
$this->input = $input;
-
+
return $this;
}
-
+
public function getInput()
{
return $this->input;
diff --git a/src/Core/Extensions/Extension.php b/src/Core/Extensions/Extension.php
new file mode 100755
index 000000000..08424b495
--- /dev/null
+++ b/src/Core/Extensions/Extension.php
@@ -0,0 +1,6 @@
+users = $users;
+ }
+
+ public function handle($command)
+ {
+ $user = $this->users->findOrFail($command->userId);
+
+ $user->assertConfirmationTokenValid($command->token);
+ $user->confirmEmail();
+
+ if (! $user->is_activated) {
+ $user->activate();
+ }
+
+ event(new UserWillBeSaved($user, $command));
+
+ $user->save();
+ $this->dispatchEventsFor($user);
+
+ return $user;
+ }
+}
diff --git a/src/Core/Handlers/Commands/DeleteDiscussionCommandHandler.php b/src/Core/Handlers/Commands/DeleteDiscussionCommandHandler.php
new file mode 100644
index 000000000..623ab7287
--- /dev/null
+++ b/src/Core/Handlers/Commands/DeleteDiscussionCommandHandler.php
@@ -0,0 +1,32 @@
+discussions = $discussions;
+ }
+
+ public function handle($command)
+ {
+ $user = $command->user;
+ $discussion = $this->discussions->findOrFail($command->discussionId, $user);
+
+ $discussion->assertCan($user, 'delete');
+
+ event(new DiscussionWillBeDeleted($discussion, $command));
+
+ $discussion->delete();
+ $this->dispatchEventsFor($discussion);
+
+ return $discussion;
+ }
+}
diff --git a/src/Core/Handlers/Commands/DeletePostCommandHandler.php b/src/Core/Handlers/Commands/DeletePostCommandHandler.php
new file mode 100644
index 000000000..6e4321a88
--- /dev/null
+++ b/src/Core/Handlers/Commands/DeletePostCommandHandler.php
@@ -0,0 +1,32 @@
+posts = $posts;
+ }
+
+ public function handle($command)
+ {
+ $user = $command->user;
+ $post = $this->posts->findOrFail($command->postId, $user);
+
+ $post->assertCan($user, 'delete');
+
+ event(new PostWillBeDeleted($post, $command));
+
+ $post->delete();
+ $this->dispatchEventsFor($post);
+
+ return $post;
+ }
+}
diff --git a/src/Core/Handlers/Commands/DeleteUserCommandHandler.php b/src/Core/Handlers/Commands/DeleteUserCommandHandler.php
new file mode 100644
index 000000000..e1b6e2d3c
--- /dev/null
+++ b/src/Core/Handlers/Commands/DeleteUserCommandHandler.php
@@ -0,0 +1,32 @@
+users = $users;
+ }
+
+ public function handle($command)
+ {
+ $user = $command->user;
+ $userToDelete = $this->users->findOrFail($command->userId, $user);
+
+ $userToDelete->assertCan($user, 'delete');
+
+ event(new UserWillBeDeleted($userToDelete, $command));
+
+ $userToDelete->delete();
+ $this->dispatchEventsFor($userToDelete);
+
+ return $userToDelete;
+ }
+}
diff --git a/src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php b/src/Core/Handlers/Commands/EditDiscussionCommandHandler.php
similarity index 54%
rename from src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php
rename to src/Core/Handlers/Commands/EditDiscussionCommandHandler.php
index cb92553ee..4f8ab6f1c 100644
--- a/src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php
+++ b/src/Core/Handlers/Commands/EditDiscussionCommandHandler.php
@@ -1,14 +1,12 @@
-title)) {
$discussion->rename($command->title, $user);
}
-
- Event::fire('Flarum.Core.Discussions.Commands.EditDiscussion.DiscussionWillBeSaved', [$discussion, $command]);
- $this->discussions->save($discussion);
+ event(new DiscussionWillBeSaved($discussion, $command));
+
+ $discussion->save();
$this->dispatchEventsFor($discussion);
return $discussion;
diff --git a/src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php b/src/Core/Handlers/Commands/EditPostCommandHandler.php
similarity index 62%
rename from src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php
rename to src/Core/Handlers/Commands/EditPostCommandHandler.php
index f7897c36c..477f741ca 100644
--- a/src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php
+++ b/src/Core/Handlers/Commands/EditPostCommandHandler.php
@@ -1,14 +1,12 @@
-restore($user);
}
- Event::fire('Flarum.Core.Posts.Commands.EditPost.PostWillBeSaved', [$post, $command]);
+ event(new PostWillBeSaved($post, $command));
- $this->posts->save($post);
+ $post->save();
$this->dispatchEventsFor($post);
return $post;
diff --git a/src/Core/Handlers/Commands/EditUserCommandHandler.php b/src/Core/Handlers/Commands/EditUserCommandHandler.php
new file mode 100644
index 000000000..709d0a6ec
--- /dev/null
+++ b/src/Core/Handlers/Commands/EditUserCommandHandler.php
@@ -0,0 +1,43 @@
+users = $users;
+ }
+
+ public function handle($command)
+ {
+ $user = $command->user;
+ $userToEdit = $this->users->findOrFail($command->userId, $user);
+
+ $userToEdit->assertCan($user, 'edit');
+
+ if (isset($command->username)) {
+ $userToEdit->rename($command->username);
+ }
+ if (isset($command->email)) {
+ $userToEdit->changeEmail($command->email);
+ }
+ if (isset($command->password)) {
+ $userToEdit->changePassword($command->password);
+ }
+ if (! empty($command->readTime)) {
+ $userToEdit->markAllAsRead();
+ }
+
+ event(new UserWillBeSaved($userToEdit, $command));
+
+ $userToEdit->save();
+ $this->dispatchEventsFor($userToEdit);
+
+ return $userToEdit;
+ }
+}
diff --git a/src/Core/Handlers/Commands/GenerateAccessTokenCommandHandler.php b/src/Core/Handlers/Commands/GenerateAccessTokenCommandHandler.php
new file mode 100644
index 000000000..15a74d8a5
--- /dev/null
+++ b/src/Core/Handlers/Commands/GenerateAccessTokenCommandHandler.php
@@ -0,0 +1,14 @@
+userId);
+ $token->save();
+
+ return $token;
+ }
+}
diff --git a/src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php b/src/Core/Handlers/Commands/PostReplyCommandHandler.php
similarity index 67%
rename from src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php
rename to src/Core/Handlers/Commands/PostReplyCommandHandler.php
index bedcb5eee..9ac223b7a 100644
--- a/src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php
+++ b/src/Core/Handlers/Commands/PostReplyCommandHandler.php
@@ -1,24 +1,19 @@
-discussions = $discussions;
- $this->posts = $posts;
}
public function handle($command)
@@ -43,9 +38,9 @@ class PostReplyCommandHandler implements CommandHandler
$user->id
);
- Event::fire('Flarum.Core.Posts.Commands.PostReply.PostWillBeSaved', [$post, $command]);
+ event(new PostWillBeSaved($post, $command));
- $this->posts->save($post);
+ $post->save();
$this->dispatchEventsFor($post);
return $post;
diff --git a/src/Core/Handlers/Commands/ReadDiscussionCommandHandler.php b/src/Core/Handlers/Commands/ReadDiscussionCommandHandler.php
new file mode 100644
index 000000000..2d9e6d376
--- /dev/null
+++ b/src/Core/Handlers/Commands/ReadDiscussionCommandHandler.php
@@ -0,0 +1,39 @@
+discussions = $discussions;
+ }
+
+ public function handle($command)
+ {
+ $user = $command->user;
+
+ if (! $user->exists) {
+ throw new PermissionDeniedException;
+ }
+
+ $discussion = $this->discussions->findOrFail($command->discussionId, $user);
+
+ $state = $discussion->stateFor($user);
+ $state->read($command->readNumber);
+
+ event(new DiscussionStateWillBeSaved($state, $command));
+
+ $state->save();
+ $this->dispatchEventsFor($state);
+
+ return $state;
+ }
+}
diff --git a/src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php b/src/Core/Handlers/Commands/RegisterUserCommandHandler.php
similarity index 51%
rename from src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php
rename to src/Core/Handlers/Commands/RegisterUserCommandHandler.php
index e2f50ceee..5838cef03 100644
--- a/src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php
+++ b/src/Core/Handlers/Commands/RegisterUserCommandHandler.php
@@ -1,25 +1,10 @@
-forum = $forum;
- $this->userRepo = $userRepo;
- }
+ use DispatchesEvents;
public function handle($command)
{
@@ -27,7 +12,7 @@ class RegisterUserCommandHandler implements CommandHandler
// case of a guest trying to register an account, this will depend on
// whether or not registration is open. If the user is an admin, though,
// it will be allowed.
- $this->forum->assertCan($command->user, 'register');
+ $command->forum->assertCan($command->user, 'register');
// Create a new User entity, persist it, and dispatch domain events.
// Before persistance, though, fire an event to give plugins an
@@ -38,9 +23,9 @@ class RegisterUserCommandHandler implements CommandHandler
$command->password
);
- Event::fire('Flarum.Core.Users.Commands.RegisterUser.UserWillBeSaved', [$user, $command]);
+ event(new UserWillBeSaved($user, $command));
- $this->userRepo->save($user);
+ $user->save();
$this->dispatchEventsFor($user);
return $user;
diff --git a/src/Core/Handlers/Commands/StartDiscussionCommandHandler.php b/src/Core/Handlers/Commands/StartDiscussionCommandHandler.php
new file mode 100644
index 000000000..6ca89b786
--- /dev/null
+++ b/src/Core/Handlers/Commands/StartDiscussionCommandHandler.php
@@ -0,0 +1,53 @@
+bus = $bus;
+ }
+
+ public function handle($command)
+ {
+ $command->forum->assertCan($command->user, 'startDiscussion');
+
+ // Create a new Discussion entity, persist it, and dispatch domain
+ // events. Before persistance, though, fire an event to give plugins
+ // an opportunity to alter the discussion entity based on data in the
+ // command they may have passed through in the controller.
+ $discussion = Discussion::start(
+ $command->title,
+ $command->user
+ );
+
+ event(new DiscussionWillBeSaved($discussion, $command));
+
+ $discussion->save();
+
+ // Now that the discussion has been created, we can add the first post.
+ // For now we will do this by running the PostReply command, but as this
+ // will trigger a domain event that is slightly semantically incorrect
+ // in this situation (PostWasPosted), we may need to reconsider someday.
+ $post = $this->bus->dispatch(
+ new PostReplyCommand($discussion->id, $command->content, $command->user)
+ );
+
+ // The discussion may have been updated by the PostReplyCommand; we need
+ // to refresh its data.
+ $discussion = $post->discussion;
+
+ $this->dispatchEventsFor($discussion);
+
+ return $discussion;
+ }
+}
diff --git a/src/Core/Handlers/Events/DiscussionMetadataUpdater.php b/src/Core/Handlers/Events/DiscussionMetadataUpdater.php
new file mode 100755
index 000000000..51c903aa6
--- /dev/null
+++ b/src/Core/Handlers/Events/DiscussionMetadataUpdater.php
@@ -0,0 +1,65 @@
+listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted');
+ $events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted');
+ $events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden');
+ $events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored');
+ }
+
+ public function whenPostWasPosted(PostWasPosted $event)
+ {
+ $discussion = $event->post->discussion;
+
+ $discussion->comments_count++;
+ $discussion->setLastPost($event->post);
+ $discussion->save();
+ }
+
+ public function whenPostWasDeleted(PostWasDeleted $event)
+ {
+ $this->removePost($event->post);
+ }
+
+ public function whenPostWasHidden(PostWasHidden $event)
+ {
+ $this->removePost($event->post);
+ }
+
+ public function whenPostWasRestored(PostWasRestored $event)
+ {
+ $discussion = $event->post->discussion;
+
+ $discussion->refreshCommentsCount();
+ $discussion->refreshLastPost();
+ $discussion->save();
+ }
+
+ protected function removePost(Post $post)
+ {
+ $discussion = $post->discussion;
+
+ $discussion->refreshCommentsCount();
+
+ if ($discussion->last_post_id == $post->id) {
+ $discussion->refreshLastPost();
+ }
+
+ $discussion->save();
+ }
+}
diff --git a/src/Core/Handlers/Events/EmailConfirmationMailer.php b/src/Core/Handlers/Events/EmailConfirmationMailer.php
new file mode 100755
index 000000000..e3cf8e613
--- /dev/null
+++ b/src/Core/Handlers/Events/EmailConfirmationMailer.php
@@ -0,0 +1,49 @@
+mailer = $mailer;
+ }
+
+ /**
+ * Register the listeners for the subscriber.
+ *
+ * @param Illuminate\Events\Dispatcher $events
+ * @return array
+ */
+ public function subscribe($events)
+ {
+ $events->listen('Flarum\Core\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered');
+ $events->listen('Flarum\Core\Events\EmailWasChanged', __CLASS__.'@whenEmailWasChanged');
+ }
+
+ public function whenUserWasRegistered(UserWasRegistered $event)
+ {
+ $user = $event->user;
+
+ $forumTitle = Config::get('flarum::forum_tite');
+
+ $data = [
+ 'username' => $user->username,
+ 'forumTitle' => $forumTitle,
+ 'url' => route('flarum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
+ ];
+
+ $this->mailer->send(['text' => 'flarum::emails.confirm'], $data, function ($message) use ($user) {
+ $message->to($user->email)->subject('['.$forumTitle.'] Email Address Confirmation');
+ });
+ }
+
+ public function whenEmailWasChanged(EmailWasChanged $event)
+ {
+
+ }
+}
diff --git a/src/Core/Handlers/Events/RenamedPostCreator.php b/src/Core/Handlers/Events/RenamedPostCreator.php
new file mode 100755
index 000000000..6ec7fab29
--- /dev/null
+++ b/src/Core/Handlers/Events/RenamedPostCreator.php
@@ -0,0 +1,32 @@
+listen('Flarum\Core\Events\DiscussionWasRenamed', __CLASS__.'@whenDiscussionWasRenamed');
+ }
+
+ public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
+ {
+ $post = RenamedPost::reply(
+ $event->discussion->id,
+ $event->user->id,
+ $event->oldTitle,
+ $event->discussion->title
+ );
+
+ $post->save();
+
+ $event->discussion->postWasAdded($post);
+ }
+}
diff --git a/src/Core/Handlers/Events/UserMetadataUpdater.php b/src/Core/Handlers/Events/UserMetadataUpdater.php
new file mode 100755
index 000000000..38fda8c0b
--- /dev/null
+++ b/src/Core/Handlers/Events/UserMetadataUpdater.php
@@ -0,0 +1,70 @@
+listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted');
+ $events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted');
+ $events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden');
+ $events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored');
+ $events->listen('Flarum\Core\Events\DiscussionWasStarted', __CLASS__.'@whenDiscussionWasStarted');
+ $events->listen('Flarum\Core\Events\DiscussionWasDeleted', __CLASS__.'@whenDiscussionWasDeleted');
+ }
+
+ public function whenPostWasPosted(PostWasPosted $event)
+ {
+ $this->updateRepliesCount($event->post->user, 1);
+ }
+
+ public function whenPostWasDeleted(PostWasDeleted $event)
+ {
+ $this->updateRepliesCount($event->post->user, -1);
+ }
+
+ public function whenPostWasHidden(PostWasHidden $event)
+ {
+ $this->updateRepliesCount($event->post->user, -1);
+ }
+
+ public function whenPostWasRestored(PostWasRestored $event)
+ {
+ $this->updateRepliesCount($event->post->user, 1);
+ }
+
+ public function whenDiscussionWasStarted(DiscussionWasStarted $event)
+ {
+ $this->updateDiscussionsCount($event->discussion->startUser, 1);
+ }
+
+ public function whenDiscussionWasDeleted(DiscussionWasDeleted $event)
+ {
+ $this->updateDiscussionsCount($event->discussion->startUser, -1);
+ }
+
+ protected function updateRepliesCount(User $user, $amount)
+ {
+ $user->posts_count += $amount;
+ $user->save();
+ }
+
+ protected function updateDiscussionsCount(User $user, $amount)
+ {
+ $user->discussions_count += $amount;
+ $user->save();
+ }
+}
diff --git a/src/Core/Models/AccessToken.php b/src/Core/Models/AccessToken.php
new file mode 100644
index 000000000..195f5ed84
--- /dev/null
+++ b/src/Core/Models/AccessToken.php
@@ -0,0 +1,44 @@
+id = str_random(40);
+ $token->user_id = $userId;
+
+ return $token;
+ }
+
+ /**
+ * Define the relationship with the owner of this access token.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User');
+ }
+}
diff --git a/src/Flarum/Core/Activity/Activity.php b/src/Core/Models/Activity.php
similarity index 88%
rename from src/Flarum/Core/Activity/Activity.php
rename to src/Core/Models/Activity.php
index b194b2a74..d053c81d5 100644
--- a/src/Flarum/Core/Activity/Activity.php
+++ b/src/Core/Models/Activity.php
@@ -15,7 +15,7 @@ class Activity extends Entity {
public function fromUser()
{
- return $this->belongsTo('Flarum\Core\Users\User', 'from_user_id');
+ return $this->belongsTo('Flarum\Core\Models\User', 'from_user_id');
}
public function permission($permission)
diff --git a/src/Core/Models/CommentPost.php b/src/Core/Models/CommentPost.php
new file mode 100755
index 000000000..40baf1f7a
--- /dev/null
+++ b/src/Core/Models/CommentPost.php
@@ -0,0 +1,162 @@
+number = ++$post->discussion->number_index;
+ $post->discussion->save();
+ });
+ }
+
+ /**
+ * Create a new instance in reply to a discussion.
+ *
+ * @param int $discussionId
+ * @param string $content
+ * @param int $userId
+ * @return static
+ */
+ public static function reply($discussionId, $content, $userId)
+ {
+ $post = new static;
+
+ $post->content = $content;
+ $post->content_html = static::formatContent($post->content);
+ $post->time = time();
+ $post->discussion_id = $discussionId;
+ $post->user_id = $userId;
+ $post->type = 'comment';
+
+ $post->raise(new PostWasPosted($post));
+
+ return $post;
+ }
+
+ /**
+ * Revise the post's content.
+ *
+ * @param string $content
+ * @param \Flarum\Core\Models\User $user
+ * @return $this
+ */
+ public function revise($content, $user)
+ {
+ if ($this->content !== $content) {
+ $this->content = $content;
+ $this->content_html = static::formatContent($this->content);
+
+ $this->edit_time = time();
+ $this->edit_user_id = $user->id;
+
+ $this->raise(new PostWasRevised($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Hide the post.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return $this
+ */
+ public function hide($user)
+ {
+ if (! $this->hide_time) {
+ $this->hide_time = time();
+ $this->hide_user_id = $user->id;
+
+ $this->raise(new PostWasHidden($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Restore the post.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return $this
+ */
+ public function restore($user)
+ {
+ if ($this->hide_time !== null) {
+ $this->hide_time = null;
+ $this->hide_user_id = null;
+
+ $this->raise(new PostWasRestored($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the content formatter as HTML.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function getContentHtmlAttribute($value)
+ {
+ if (! $value) {
+ $this->content_html = $value = static::formatContent($this->content);
+ $this->save();
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get text formatter instance.
+ *
+ * @return \Flarum\Core\Formatter\FormatterManager
+ */
+ public static function getFormatter()
+ {
+ return static::$formatter;
+ }
+
+ /**
+ * Set text formatter instance.
+ *
+ * @param \Flarum\Core\Formatter\FormatterManager $formatter
+ */
+ public static function setFormatter(FormatterManager $formatter)
+ {
+ static::$formatter = $formatter;
+ }
+
+ /**
+ * Format a string of post content using the set formatter.
+ *
+ * @param string $content
+ * @return string
+ */
+ protected static function formatContent($content)
+ {
+ return static::$formatter->format($content);
+ }
+}
diff --git a/src/Core/Models/Discussion.php b/src/Core/Models/Discussion.php
new file mode 100755
index 000000000..d78cdc2fd
--- /dev/null
+++ b/src/Core/Models/Discussion.php
@@ -0,0 +1,292 @@
+ 'required',
+ 'start_time' => 'required|date',
+ 'comments_count' => 'integer',
+ 'start_user_id' => 'integer',
+ 'start_post_id' => 'integer',
+ 'last_time' => 'date',
+ 'last_user_id' => 'integer',
+ 'last_post_id' => 'integer',
+ 'last_post_number' => 'integer'
+ ];
+
+ /**
+ * The table associated with the model.
+ *
+ * @var string
+ */
+ protected $table = 'discussions';
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['start_time', 'last_time'];
+
+ /**
+ * An array of posts that have been added during this request.
+ *
+ * @var \Flarum\Core\Models\Post[]
+ */
+ protected $addedPosts = [];
+
+ /**
+ * The user for which the state relationship should be loaded.
+ *
+ * @var \Flarum\Core\Models\User
+ */
+ protected static $stateUser;
+
+ /**
+ * Raise an event when a discussion is deleted.
+ *
+ * @return void
+ */
+ public static function boot()
+ {
+ parent::boot();
+
+ static::deleted(function ($discussion) {
+ $discussion->raise(new DiscussionWasDeleted($discussion));
+
+ $discussion->posts()->delete();
+ $discussion->readers()->detach();
+ });
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param string $title
+ * @param \Flarum\Core\Models\User $user
+ * @return static
+ */
+ public static function start($title, $user)
+ {
+ $discussion = new static;
+
+ $discussion->title = $title;
+ $discussion->start_time = time();
+ $discussion->start_user_id = $user->id;
+
+ $discussion->raise(new DiscussionWasStarted($discussion));
+
+ return $discussion;
+ }
+
+ /**
+ * Rename the discussion.
+ *
+ * @param string $title
+ * @param \Flarum\Core\Models\User $user
+ * @return $this
+ */
+ public function rename($title, $user)
+ {
+ if ($this->title !== $title) {
+ $oldTitle = $this->title;
+ $this->title = $title;
+
+ $this->raise(new DiscussionWasRenamed($this, $user, $oldTitle));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the discussion's last post details.
+ *
+ * @param \Flarum\Core\Models\Post $post
+ * @return $this
+ */
+ public function setLastPost(Post $post)
+ {
+ $this->last_time = $post->time;
+ $this->last_user_id = $post->user_id;
+ $this->last_post_id = $post->id;
+ $this->last_post_number = $post->number;
+
+ return $this;
+ }
+
+ /**
+ * Refresh a discussion's last post details.
+ *
+ * @return $this
+ */
+ public function refreshLastPost()
+ {
+ if ($lastPost = $this->comments()->orderBy('time', 'desc')->first()) {
+ $this->setLastPost($lastPost);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Refresh the discussion's comments count.
+ *
+ * @return $this
+ */
+ public function refreshCommentsCount()
+ {
+ $this->comments_count = $this->comments()->count();
+
+ return $this;
+ }
+
+ /**
+ * Get a list of the posts that have been added to this discussion during
+ * this request.
+ *
+ * @return \Flarum\Core\Models\Post[]
+ */
+ public function getAddedPosts()
+ {
+ return $this->addedPosts;
+ }
+
+ /**
+ * Specify that a post was added to this discussion during this request
+ * for later retrieval.
+ *
+ * @param \Flarum\Core\Models\Post $post
+ * @return void
+ */
+ public function postWasAdded(Post $post)
+ {
+ $this->addedPosts[] = $post;
+ }
+
+ /**
+ * Define the relationship with the discussion's posts.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function posts()
+ {
+ return $this->hasMany('Flarum\Core\Models\Post');
+ }
+
+ /**
+ * Define the relationship with the discussion's comments.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function comments()
+ {
+ return $this->posts()->where('type', 'comment')->whereNull('hide_time');
+ }
+
+ /**
+ * Define the relationship with the discussion's first post.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function startPost()
+ {
+ return $this->belongsTo('Flarum\Core\Models\Post', 'start_post_id');
+ }
+
+ /**
+ * Define the relationship with the discussion's author.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function startUser()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'start_user_id');
+ }
+
+ /**
+ * Define the relationship with the discussion's last post.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function lastPost()
+ {
+ return $this->belongsTo('Flarum\Core\Models\Post', 'last_post_id');
+ }
+
+ /**
+ * Define the relationship with the discussion's last post's author.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function lastUser()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'last_user_id');
+ }
+
+ /**
+ * Define the relationship with the discussion's readers.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function readers()
+ {
+ return $this->belongsToMany('Flarum\Core\Models\User', 'users_discussions');
+ }
+
+ /**
+ * Define the relationship with the discussion's state for a particular user.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return \Illuminate\Database\Eloquent\Relations\HasOne
+ */
+ public function state(User $user = null)
+ {
+ $user = $user ?: static::$stateUser;
+
+ return $this->hasOne('Flarum\Core\Models\DiscussionState')->where('user_id', $user->id);
+ }
+
+ /**
+ * Get the state model for a user, or instantiate a new one if it does not
+ * exist.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return \Flarum\Core\Models\DiscussionState
+ */
+ public function stateFor(User $user)
+ {
+ $state = $this->state($user)->first();
+
+ if (! $state) {
+ $state = new DiscussionState;
+ $state->discussion_id = $this->id;
+ $state->user_id = $user->id;
+ }
+
+ return $state;
+ }
+
+ /**
+ * Set the user for which the state relationship should be loaded.
+ *
+ * @param \Flarum\Core\Models\User $user
+ */
+ public static function setStateUser(User $user)
+ {
+ static::$stateUser = $user;
+ }
+}
diff --git a/src/Core/Models/DiscussionState.php b/src/Core/Models/DiscussionState.php
new file mode 100644
index 000000000..971396649
--- /dev/null
+++ b/src/Core/Models/DiscussionState.php
@@ -0,0 +1,73 @@
+ $this->read_number) {
+ $this->read_number = $number;
+ $this->read_time = time();
+
+ $this->raise(new DiscussionWasRead($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Define the relationship with the discussion that this state is for.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function discussion()
+ {
+ return $this->belongsTo('Flarum\Core\Models\Discussion', 'discussion_id');
+ }
+
+ /**
+ * Define the relationship with the user that this state is for.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'user_id');
+ }
+
+ /**
+ * Set the keys for a save update query.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ protected function setKeysForSaveQuery(\Illuminate\Database\Eloquent\Builder $query)
+ {
+ $query->where('discussion_id', $this->discussion_id)
+ ->where('user_id', $this->user_id);
+
+ return $query;
+ }
+}
diff --git a/src/Core/Models/Forum.php b/src/Core/Models/Forum.php
new file mode 100755
index 000000000..2d160b776
--- /dev/null
+++ b/src/Core/Models/Forum.php
@@ -0,0 +1,8 @@
+belongsToMany('Flarum\Core\Models\User', 'users_groups');
+ }
+}
diff --git a/src/Core/Models/Guest.php b/src/Core/Models/Guest.php
new file mode 100755
index 000000000..7dc91e052
--- /dev/null
+++ b/src/Core/Models/Guest.php
@@ -0,0 +1,30 @@
+attributes['groups'])) {
+ $this->attributes['groups'] = $this->relations['groups'] = Group::where('id', Group::GUEST_ID)->get();
+ }
+
+ return $this->attributes['groups'];
+ }
+
+ /**
+ * Check whether or not the user is a guest.
+ *
+ * @return boolean
+ */
+ public function guest()
+ {
+ return true;
+ }
+}
diff --git a/src/Core/Models/Model.php b/src/Core/Models/Model.php
new file mode 100755
index 000000000..5140902ce
--- /dev/null
+++ b/src/Core/Models/Model.php
@@ -0,0 +1,173 @@
+makeValidator()->passes();
+ }
+
+ /**
+ * Throw an exception if the model is not valid in its current state.
+ *
+ * @return void
+ *
+ * @throws \Flarum\Core\ValidationFailureException
+ */
+ public function assertValid()
+ {
+ if ($this->makeValidator()->fails()) {
+ throw (new ValidationFailureException)
+ ->setErrors($validation->errors())
+ ->setInput($validation->getData());
+ }
+ }
+
+ /**
+ * Make a new validator instance for this model.
+ *
+ * @return \Illuminate\Contracts\Validation\Validator
+ */
+ protected function makeValidator()
+ {
+ $rules = $this->expandUniqueRules(static::$rules);
+
+ return $this->validator->make($this->attributes, $rules, static::$messages);
+ }
+
+ /**
+ * Expand 'unique' rules in a set of validation rules into a fuller form
+ * that Laravel's validator can understand.
+ *
+ * @param array $rules
+ * @return array
+ */
+ protected function expandUniqueRules($rules)
+ {
+ foreach ($rules as $column => &$ruleset) {
+ if (is_string($ruleset)) {
+ $ruleset = explode('|', $ruleset);
+ }
+ foreach ($ruleset as &$rule) {
+ if (strpos($rule, 'unique') === 0) {
+ $parts = explode(':', $rule);
+ $key = $this->getKey() ?: 'NULL';
+ $rule = 'unique:'.$this->getTable().','.$column.','.$key.','.$this->getKeyName();
+ if (! empty($parts[1])) {
+ $wheres = explode(',', $parts[1]);
+ foreach ($wheres as &$where) {
+ $where .= ','.$this->$where;
+ }
+ $rule .= ','.implode(',', $wheres);
+ }
+ }
+ }
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Assert that the user has permission to view this model, throwing an
+ * exception if they don't.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return void
+ *
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
+ */
+ public function assertVisibleTo(User $user)
+ {
+ if (! $this->can($user, 'view')) {
+ throw new ModelNotFoundException;
+ }
+ }
+
+ /**
+ * Assert that the user has a certain permission for this model, throwing
+ * an exception if they don't.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @param string $permission
+ * @return void
+ *
+ * @throws \Flarum\Core\Exceptions\PermissionDeniedException
+ */
+ public function assertCan(User $user, $permission)
+ {
+ if (! $this->can($user, $permission)) {
+ throw new PermissionDeniedException;
+ }
+ }
+}
diff --git a/src/Core/Models/Permission.php b/src/Core/Models/Permission.php
new file mode 100644
index 000000000..22a876e04
--- /dev/null
+++ b/src/Core/Models/Permission.php
@@ -0,0 +1,5 @@
+ 'required|integer',
+ 'time' => 'required|date',
+ 'content' => 'required',
+ 'number' => 'integer',
+ 'user_id' => 'integer',
+ 'edit_time' => 'date',
+ 'edit_user_id' => 'integer',
+ 'hide_time' => 'date',
+ 'hide_user_id' => 'integer',
+ ];
+
+ /**
+ * The table associated with the model.
+ *
+ * @var string
+ */
+ protected $table = 'posts';
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['time', 'edit_time', 'hide_time'];
+
+ /**
+ * A map of post types, as specified in the `type` column, to their
+ * classes.
+ *
+ * @var array
+ */
+ protected static $types = [];
+
+ /**
+ * Raise an event when a post is deleted.
+ *
+ * @return void
+ */
+ public static function boot()
+ {
+ parent::boot();
+
+ static::deleted(function ($post) {
+ $post->raise(new PostWasDeleted($post));
+ });
+ }
+
+ /**
+ * Define the relationship with the post's discussion.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function discussion()
+ {
+ return $this->belongsTo('Flarum\Core\Models\Discussion', 'discussion_id');
+ }
+
+ /**
+ * Define the relationship with the post's author.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'user_id');
+ }
+
+ /**
+ * Define the relationship with the user who edited the post.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function editUser()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'edit_user_id');
+ }
+
+ /**
+ * Define the relationship with the user who hid the post.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function hideUser()
+ {
+ return $this->belongsTo('Flarum\Core\Models\User', 'hide_user_id');
+ }
+
+ /**
+ * Terminate the query and return an array of matching IDs.
+ * Example usage: `$ids = $discussion->posts()->ids()`
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return array
+ */
+ public function scopeIds($query)
+ {
+ return array_map('intval', $query->get(['id'])->fetch('id')->all());
+ }
+
+ /**
+ * Create a new model instance according to the post's type.
+ *
+ * @param array $attributes
+ * @return static|object
+ */
+ public function newFromBuilder($attributes = [], $connection = null)
+ {
+ if (!empty($attributes->type)) {
+ $type = $attributes->type;
+ if (isset(static::$types[$type])) {
+ $class = static::$types[$type];
+ if (class_exists($class)) {
+ $instance = new $class;
+ $instance->exists = true;
+ $instance->setRawAttributes((array) $attributes, true);
+ $instance->setConnection($connection ?: $this->connection);
+ return $instance;
+ }
+ }
+ }
+
+ return parent::newFromBuilder($attributes, $connection);
+ }
+
+ /**
+ * Register a post type and its model class.
+ *
+ * @param string $type
+ * @param string $class
+ * @return void
+ */
+ public static function addType($type, $class)
+ {
+ static::$types[$type] = $class;
+ }
+}
diff --git a/src/Flarum/Core/Posts/RenamedPost.php b/src/Core/Models/RenamedPost.php
similarity index 56%
rename from src/Flarum/Core/Posts/RenamedPost.php
rename to src/Core/Models/RenamedPost.php
index f42ecb33a..98d2133de 100755
--- a/src/Flarum/Core/Posts/RenamedPost.php
+++ b/src/Core/Models/RenamedPost.php
@@ -1,15 +1,16 @@
-attributes['content'] = json_encode($value);
diff --git a/src/Core/Models/User.php b/src/Core/Models/User.php
new file mode 100755
index 000000000..5fa7296c2
--- /dev/null
+++ b/src/Core/Models/User.php
@@ -0,0 +1,314 @@
+ 'required|username|unique',
+ 'email' => 'required|email|unique',
+ 'password' => 'required',
+ 'join_time' => 'date',
+ 'last_seen_time' => 'date',
+ 'discussions_count' => 'integer',
+ 'posts_count' => 'integer',
+ ];
+
+ /**
+ * The table associated with the model.
+ *
+ * @var string
+ */
+ protected $table = 'users';
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['join_time', 'last_seen_time', 'read_time'];
+
+ /**
+ * The hasher with which to hash passwords.
+ *
+ * @var \Illuminate\Contracts\Hashing\Hasher
+ */
+ protected static $hasher;
+
+ /**
+ * Raise an event when a post is deleted.
+ *
+ * @return void
+ */
+ public static function boot()
+ {
+ parent::boot();
+
+ static::deleted(function ($user) {
+ $user->raise(new UserWasDeleted($user));
+ });
+ }
+
+ /**
+ * Register a new user.
+ *
+ * @param string $username
+ * @param string $email
+ * @param string $password
+ * @return static
+ */
+ public static function register($username, $email, $password)
+ {
+ $user = new static;
+
+ $user->username = $username;
+ $user->email = $email;
+ $user->password = $password;
+ $user->join_time = time();
+
+ $user->refreshConfirmationToken();
+
+ $user->raise(new UserWasRegistered($user));
+
+ return $user;
+ }
+
+ /**
+ * Rename the user.
+ *
+ * @param string $username
+ * @return $this
+ */
+ public function rename($username)
+ {
+ if ($username !== $this->username) {
+ $this->username = $username;
+ $this->raise(new UserWasRenamed($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Change the user's email.
+ *
+ * @param string $email
+ * @return $this
+ */
+ public function changeEmail($email)
+ {
+ if ($email !== $this->email) {
+ $this->email = $email;
+ $this->raise(new EmailWasChanged($this));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Change the user's password.
+ *
+ * @param string $password
+ * @return $this
+ */
+ public function changePassword($password)
+ {
+ $this->password = $password ? static::$hasher->make($password) : null;
+ $this->raise(new PasswordWasChanged($this));
+
+ return $this;
+ }
+
+ /**
+ * Mark all discussions as read by setting the user's read_time.
+ *
+ * @return $this
+ */
+ public function markAllAsRead()
+ {
+ $this->read_time = time();
+
+ return $this;
+ }
+
+ /**
+ * Check if a given password matches the user's password.
+ *
+ * @param string $password
+ * @return boolean
+ */
+ public function checkPassword($password)
+ {
+ return static::$hasher->check($password, $this->password);
+ }
+
+ /**
+ * Activate the user's account.
+ *
+ * @return $this
+ */
+ public function activate()
+ {
+ $this->is_activated = true;
+ $this->groups()->sync([3]);
+
+ $this->raise(new UserWasActivated($this));
+
+ return $this;
+ }
+
+ /**
+ * Check if a given confirmation token is valid for this user.
+ *
+ * @param string $token
+ * @return boolean
+ */
+ public function assertConfirmationTokenValid($token)
+ {
+ if ($this->is_confirmed ||
+ ! $token ||
+ $this->confirmation_token !== $token) {
+ throw new InvalidConfirmationTokenException;
+ }
+ }
+
+ /**
+ * Generate a new confirmation token for the user.
+ *
+ * @return $this
+ */
+ public function refreshConfirmationToken()
+ {
+ $this->is_confirmed = false;
+ $this->confirmation_token = str_random(30);
+
+ return $this;
+ }
+
+ /**
+ * Confirm the user's email.
+ *
+ * @return $this
+ */
+ public function confirmEmail()
+ {
+ $this->is_confirmed = true;
+ $this->confirmation_token = null;
+
+ $this->raise(new EmailWasConfirmed($this));
+
+ return $this;
+ }
+
+ /**
+ * Get a list of the user's grantees according to their ID and groups.
+ *
+ * @return array
+ */
+ public function getGrantees()
+ {
+ $grantees = ['group.'.GROUP::GUEST_ID]; // guests
+ if ($this->id) {
+ $grantees[] = 'user.'.$this->id;
+ }
+ foreach ($this->groups as $group) {
+ $grantees[] = 'group.'.$group->id;
+ }
+
+ return $grantees;
+ }
+
+ /**
+ * Check whether the user has a certain permission based on their groups.
+ *
+ * @param string $permission
+ * @param string $entity
+ * @return boolean
+ */
+ public function hasPermission($permission, $entity)
+ {
+ if ($this->isAdmin()) {
+ return true;
+ }
+
+ $count = $this->permissions()->where('entity', $entity)->where('permission', $permission)->count();
+
+ return (bool) $count;
+ }
+
+ /**
+ * Check whether or not the user is an administrator.
+ *
+ * @return boolean
+ */
+ public function isAdmin()
+ {
+ return $this->groups->contains(Group::ADMINISTRATOR_ID);
+ }
+
+ /**
+ * Check whether or not the user is a guest.
+ *
+ * @return boolean
+ */
+ public function isGuest()
+ {
+ return false;
+ }
+
+ /**
+ * Define the relationship with the user's activity.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function activity()
+ {
+ return $this->hasMany('Flarum\Core\Models\Activity');
+ }
+
+ /**
+ * Define the relationship with the user's groups.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function groups()
+ {
+ return $this->belongsToMany('Flarum\Core\Models\Group', 'users_groups');
+ }
+
+ /**
+ * Define the relationship with the user's permissions.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function permissions()
+ {
+ return Permission::whereIn('grantee', $this->getGrantees());
+ }
+
+ /**
+ * Set the hasher with which to hash passwords.
+ *
+ * @param \Illuminate\Contracts\Hashing\Hasher $hasher
+ */
+ public static function setHasher(Hasher $hasher)
+ {
+ static::$hasher = $hasher;
+ }
+}
diff --git a/src/Core/Repositories/DiscussionRepositoryInterface.php b/src/Core/Repositories/DiscussionRepositoryInterface.php
new file mode 100644
index 000000000..6eb148c8b
--- /dev/null
+++ b/src/Core/Repositories/DiscussionRepositoryInterface.php
@@ -0,0 +1,33 @@
+scopeVisibleForUser($query, $user)->firstOrFail();
+ }
+
+ /**
+ * Get the IDs of discussions which a user has read completely.
+ *
+ * @param \Flarum\Core\Models\User $user
+ * @return array
+ */
+ public function getReadIds(User $user)
+ {
+ return Discussion::leftJoin('users_discussions', 'users_discussions.discussion_id', '=', 'discussions.id')
+ ->where('user_id', $user->id)
+ ->where('read_number', '<', 'last_post_number')
+ ->lists('id');
+ }
+
+ /**
+ * Scope a query to only include records that are visible to a user.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Flarum\Core\Models\User $user
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ protected function scopeVisibleForUser(Builder $query, User $user = null)
+ {
+ if ($user !== null) {
+ $query->whereCan($user, 'view');
+ }
+
+ return $query;
+ }
+}
diff --git a/src/Core/Repositories/EloquentPostRepository.php b/src/Core/Repositories/EloquentPostRepository.php
new file mode 100644
index 000000000..666c0c9a2
--- /dev/null
+++ b/src/Core/Repositories/EloquentPostRepository.php
@@ -0,0 +1,121 @@
+scopeVisibleForUser($query, $user)->firstOrFail();
+ }
+
+ /**
+ * Find posts in a discussion, optionally making sure they are visible to
+ * a certain user, and/or using other criteria.
+ *
+ * @param integer $discussionId
+ * @param \Flarum\Core\Models\User|null $user
+ * @param string $sort
+ * @param string $order
+ * @param integer $count
+ * @param integer $start
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public function findByDiscussion($discussionId, User $user = null, $sort = 'time', $order = 'asc', $count = null, $start = 0)
+ {
+ $query = Post::where('discussion_id', $discussionId)
+ ->orderBy($sort, $order)
+ ->skip($start)
+ ->take($count);
+
+ return $this->scopeVisibleForUser($query, $user)->get();
+ }
+
+ /**
+ * Find posts by their IDs, optionally making sure they are visible to a
+ * certain user.
+ *
+ * @param array $ids
+ * @param \Flarum\Core\Models\User|null $user
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public function findByIds(array $ids, User $user = null)
+ {
+ $query = Post::whereIn('id', (array) $ids);
+
+ return $this->scopeVisibleForUser($query, $user)->get();
+ }
+
+ /**
+ * Find posts by matching a string of words against their content,
+ * optionally making sure they are visible to a certain user.
+ *
+ * @param string $string
+ * @param \Flarum\Core\Models\User|null $user
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public function findByContent($string, User $user = null)
+ {
+ $query = Post::select('id', 'discussion_id')
+ ->where('content', 'like', '%'.$string.'%');
+ // ->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
+ // ->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
+
+ return $this->scopeVisibleForUser($query, $user)->get();
+ }
+
+ /**
+ * Get the position within a discussion where a post with a certain number
+ * is. If the post with that number does not exist, the index of the
+ * closest post to it will be returned.
+ *
+ * @param integer $discussionId
+ * @param integer $number
+ * @param \Flarum\Core\Models\User|null $user
+ * @return integer
+ */
+ public function getIndexForNumber($discussionId, $number, User $user = null)
+ {
+ $query = Post::where('discussion_id', $discussionId)
+ ->where('time', '<', function ($query) use ($discussionId, $number) {
+ $query->select('time')
+ ->from('posts')
+ ->where('discussion_id', $discussionId)
+ ->whereNotNull('number')
+ ->orderByRaw('ABS(CAST(number AS SIGNED) - ?)', [$number])
+ ->take(1);
+ });
+
+ return $this->scopeVisibleForUser($query, $user)->count();
+ }
+
+ /**
+ * Scope a query to only include records that are visible to a user.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Flarum\Core\Models\User $user
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ protected function scopeVisibleForUser(Builder $query, User $user = null)
+ {
+ if ($user !== null) {
+ $query->whereCan($user, 'view');
+ }
+
+ return $query;
+ }
+}
diff --git a/src/Core/Repositories/EloquentUserRepository.php b/src/Core/Repositories/EloquentUserRepository.php
new file mode 100644
index 000000000..c5cd1e0f3
--- /dev/null
+++ b/src/Core/Repositories/EloquentUserRepository.php
@@ -0,0 +1,67 @@
+scopeVisibleForUser($query, $user)->firstOrFail();
+ }
+
+ /**
+ * Find a user by an identification (username or email).
+ *
+ * @param string $identification
+ * @return \Flarum\Core\Models\User|null
+ */
+ public function findByIdentification($identification)
+ {
+ $field = filter_var($identification, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
+
+ return User::where($field, $identification)->first();
+ }
+
+ /**
+ * Get the ID of a user with the given username.
+ *
+ * @param string $username
+ * @param \Flarum\Core\Models\User $user
+ * @return integer|null
+ */
+ public function getIdForUsername($username, User $user = null)
+ {
+ $query = User::where('username', 'like', $username);
+
+ return $this->scopeVisibleForUser($query, $user)->pluck('id');
+ }
+
+ /**
+ * Scope a query to only include records that are visible to a user.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Flarum\Core\Models\User $user
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ protected function scopeVisibleForUser(Builder $query, User $user = null)
+ {
+ if ($user !== null) {
+ $query->whereCan($user, 'view');
+ }
+
+ return $query;
+ }
+}
diff --git a/src/Core/Repositories/PostRepositoryInterface.php b/src/Core/Repositories/PostRepositoryInterface.php
new file mode 100644
index 000000000..8796e82ec
--- /dev/null
+++ b/src/Core/Repositories/PostRepositoryInterface.php
@@ -0,0 +1,64 @@
+user = $user;
+ $this->query = $query;
+ $this->sort = $sort;
+ $this->order = $order;
+ }
+}
diff --git a/src/Core/Search/Discussions/DiscussionSearchResults.php b/src/Core/Search/Discussions/DiscussionSearchResults.php
new file mode 100644
index 000000000..b683b564e
--- /dev/null
+++ b/src/Core/Search/Discussions/DiscussionSearchResults.php
@@ -0,0 +1,32 @@
+discussions = $discussions;
+ $this->areMoreResults = $areMoreResults;
+ $this->total = $total;
+ }
+
+ public function getDiscussions()
+ {
+ return $this->discussions;
+ }
+
+ public function getTotal()
+ {
+ return $this->total;
+ }
+
+ public function areMoreResults()
+ {
+ return $this->areMoreResults;
+ }
+}
diff --git a/src/Core/Search/Discussions/DiscussionSearcher.php b/src/Core/Search/Discussions/DiscussionSearcher.php
new file mode 100644
index 000000000..7706576d3
--- /dev/null
+++ b/src/Core/Search/Discussions/DiscussionSearcher.php
@@ -0,0 +1,109 @@
+ ['last_time', 'desc'],
+ 'replies' => ['comments_count', 'desc'],
+ 'created' => ['start_time', 'desc']
+ ];
+
+ protected $defaultSort = 'lastPost';
+
+ protected $relevantPosts = [];
+
+ protected $gambits;
+
+ protected $discussions;
+
+ public function __construct(GambitManager $gambits, DiscussionRepositoryInterface $discussions, PostRepositoryInterface $posts)
+ {
+ $this->gambits = $gambits;
+ $this->discussions = $discussions;
+ $this->posts = $posts;
+ }
+
+ public function addRelevantPost($discussionId, $postId)
+ {
+ if (empty($this->relevantPosts[$discussionId])) {
+ $this->relevantPosts[$discussionId] = [];
+ }
+ $this->relevantPosts[$discussionId][] = $postId;
+ }
+
+ public function setDefaultSort($defaultSort)
+ {
+ $this->defaultSort = $defaultSort;
+ }
+
+ public function search(DiscussionSearchCriteria $criteria, $count = null, $start = 0, $load = [])
+ {
+ $this->user = $criteria->user;
+ $this->query = $this->discussions->query()->whereCan($criteria->user, 'view');
+
+ $this->gambits->apply($criteria->query, $this);
+
+ $total = $this->query->count();
+
+ $sort = $criteria->sort;
+ if (empty($sort)) {
+ $sort = $this->defaultSort;
+ }
+ // dd($sort);
+ if (is_array($sort)) {
+ foreach ($sort as $id) {
+ $this->query->orderByRaw('id != '.(int) $id);
+ }
+ } else {
+ list($column, $order) = $this->sortMap[$sort];
+ $this->query->orderBy($column, $criteria->order ?: $order);
+ }
+
+ if ($start > 0) {
+ $this->query->skip($start);
+ }
+ if ($count > 0) {
+ $this->query->take($count + 1);
+ }
+
+ $discussions = $this->query->get();
+
+ if ($count > 0 && $areMoreResults = $discussions->count() > $count) {
+ $discussions->pop();
+ }
+
+ if (in_array('relevantPosts', $load) && count($this->relevantPosts)) {
+ $load = array_diff($load, ['relevantPosts']);
+
+ $postIds = [];
+ foreach ($this->relevantPosts as $id => $posts) {
+ $postIds = array_merge($postIds, array_slice($posts, 0, 2));
+ }
+ $posts = $this->posts->findByIds($postIds, $this->user)->load('user');
+
+ foreach ($discussions as $discussion) {
+ $discussion->relevantPosts = $posts->filter(function ($post) use ($discussion) {
+ return $post->discussion_id == $discussion->id;
+ })
+ ->each(function ($post) {
+ $pos = strpos(strtolower($post->content), strtolower($this->fulltext));
+ // TODO: make clipping more intelligent (full words only)
+ $start = max(0, $pos - 50);
+ $post->content = ($start > 0 ? '...' : '').str_limit(substr($post->content, $start), 300);
+ });
+ }
+ }
+
+ Discussion::setStateUser($this->user);
+ $discussions->load($load);
+
+ return new DiscussionSearchResults($discussions, $areMoreResults, $total);
+ }
+}
diff --git a/src/Core/Search/Discussions/Gambits/AuthorGambit.php b/src/Core/Search/Discussions/Gambits/AuthorGambit.php
new file mode 100644
index 000000000..94134459a
--- /dev/null
+++ b/src/Core/Search/Discussions/Gambits/AuthorGambit.php
@@ -0,0 +1,30 @@
+users = $users;
+ }
+
+ public function conditions($matches, DiscussionSearcher $searcher)
+ {
+ $username = trim($matches[1], '"');
+
+ $id = $this->users->getIdForUsername($username);
+
+ $searcher->query->where('start_user_id', $id);
+ }
+}
diff --git a/src/Core/Search/Discussions/Gambits/FulltextGambit.php b/src/Core/Search/Discussions/Gambits/FulltextGambit.php
new file mode 100644
index 000000000..ce2ea5a1e
--- /dev/null
+++ b/src/Core/Search/Discussions/Gambits/FulltextGambit.php
@@ -0,0 +1,31 @@
+posts = $posts;
+ }
+
+ public function apply($string, DiscussionSearcher $searcher)
+ {
+ $posts = $this->posts->findByContent($string, $searcher->user);
+
+ $discussions = [];
+ foreach ($posts as $post) {
+ $discussions[] = $id = $post->discussion_id;
+ $searcher->addRelevantPost($id, $post->id);
+ }
+ $discussions = array_unique($discussions);
+
+ $searcher->query->whereIn('id', $discussions);
+
+ $searcher->setDefaultSort($discussions);
+ }
+}
diff --git a/src/Core/Search/Discussions/Gambits/UnreadGambit.php b/src/Core/Search/Discussions/Gambits/UnreadGambit.php
new file mode 100644
index 000000000..a18c13180
--- /dev/null
+++ b/src/Core/Search/Discussions/Gambits/UnreadGambit.php
@@ -0,0 +1,36 @@
+discussions = $discussions;
+ }
+
+ protected function conditions($matches, DiscussionSearcher $searcher)
+ {
+ $user = $searcher->user;
+
+ if ($user->exists) {
+ $readIds = $this->discussions->getReadIds($user);
+
+ if ($matches[1] === 'true') {
+ $searcher->query->whereNotIn('id', $readIds)->where('last_time', '>', $user->read_time ?: 0);
+ } else {
+ $searcher->query->whereIn('id', $readIds)->orWhere('last_time', '<=', $user->read_time ?: 0);
+ }
+ }
+ }
+}
diff --git a/src/Core/Search/GambitAbstract.php b/src/Core/Search/GambitAbstract.php
new file mode 100644
index 000000000..3dd4e67aa
--- /dev/null
+++ b/src/Core/Search/GambitAbstract.php
@@ -0,0 +1,23 @@
+match($bit)) {
+ $this->conditions($matches, $searcher);
+ return true;
+ }
+ }
+
+ public function match($bit)
+ {
+ if (preg_match('/^'.$this->pattern.'$/i', $bit, $matches)) {
+ return $matches;
+ }
+ }
+}
diff --git a/src/Core/Search/GambitInterface.php b/src/Core/Search/GambitInterface.php
new file mode 100644
index 000000000..0e6a5ab92
--- /dev/null
+++ b/src/Core/Search/GambitInterface.php
@@ -0,0 +1,6 @@
+container = $container;
+ }
+
+ public function add($gambit)
+ {
+ $this->gambits[] = $gambit;
+ }
+
+ public function apply($string, $searcher)
+ {
+ $string = $this->applyGambits($string, $searcher);
+
+ if ($string) {
+ $this->applyFulltext($string, $searcher);
+ }
+ }
+
+ public function setFulltextGambit($gambit)
+ {
+ $this->fulltextGambit = $gambit;
+ }
+
+ protected function bits($string)
+ {
+ return str_getcsv($string, ' ');
+ }
+
+ protected function applyGambits($string, $searcher)
+ {
+ $bits = $this->bits($string);
+
+ $gambits = array_map([$this->container, 'make'], $this->gambits);
+
+ foreach ($bits as $k => $bit) {
+ foreach ($gambits as $gambit) {
+ if ($gambit->apply($bit, $searcher)) {
+ unset($bits[$k]);
+ break;
+ }
+ }
+ }
+
+ return implode(' ', $bits);
+ }
+
+ protected function applyFulltext($string, $searcher)
+ {
+ if (! $this->fulltextGambit) {
+ return;
+ }
+
+ $gambit = $this->container->make($this->fulltextGambit);
+
+ $gambit->apply($string, $searcher);
+ }
+
+}
diff --git a/src/Flarum/Core/Support/Seeders/ConfigTableSeeder.php b/src/Core/Seeders/ConfigTableSeeder.php
similarity index 75%
rename from src/Flarum/Core/Support/Seeders/ConfigTableSeeder.php
rename to src/Core/Seeders/ConfigTableSeeder.php
index ea3d9eab1..5404d7690 100644
--- a/src/Flarum/Core/Support/Seeders/ConfigTableSeeder.php
+++ b/src/Core/Seeders/ConfigTableSeeder.php
@@ -1,4 +1,4 @@
- $group]);
+ }
+ }
+
+}
diff --git a/src/Core/Seeders/PermissionsTableSeeder.php b/src/Core/Seeders/PermissionsTableSeeder.php
new file mode 100644
index 000000000..9aedc08e1
--- /dev/null
+++ b/src/Core/Seeders/PermissionsTableSeeder.php
@@ -0,0 +1,47 @@
+ $permission[0],
+ 'entity' => $permission[1],
+ 'permission' => $permission[2]
+ ];
+ }
+ Permission::insert($permissions);
+ }
+
+}
diff --git a/src/Core/Seeders/UsersTableSeeder.php b/src/Core/Seeders/UsersTableSeeder.php
new file mode 100644
index 000000000..1b547dc82
--- /dev/null
+++ b/src/Core/Seeders/UsersTableSeeder.php
@@ -0,0 +1,42 @@
+ $faker->userName,
+ 'email' => $faker->safeEmail,
+ 'is_confirmed' => true,
+ 'is_activated' => true,
+ 'password' => 'password',
+ 'join_time' => $faker->dateTimeThisYear
+ ]);
+
+ // Assign the users to the 'Member' group, and possibly some others.
+ $user->groups()->attach(3);
+ if (rand(1, 50) == 1) {
+ $user->groups()->attach(4);
+ }
+ if (rand(1, 20) == 1) {
+ $user->groups()->attach(5);
+ }
+ if (rand(1, 20) == 1) {
+ $user->groups()->attach(1);
+ }
+ }
+ }
+}
diff --git a/src/Core/Support/Actor.php b/src/Core/Support/Actor.php
new file mode 100755
index 000000000..9c36d4a38
--- /dev/null
+++ b/src/Core/Support/Actor.php
@@ -0,0 +1,19 @@
+user ?: new Guest;
+ }
+
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+}
diff --git a/src/Core/Support/DispatchesEvents.php b/src/Core/Support/DispatchesEvents.php
new file mode 100644
index 000000000..7dcc353db
--- /dev/null
+++ b/src/Core/Support/DispatchesEvents.php
@@ -0,0 +1,16 @@
+releaseEvents() as $event) {
+ event($event);
+ }
+ }
+}
diff --git a/src/Core/Support/EventGenerator.php b/src/Core/Support/EventGenerator.php
new file mode 100644
index 000000000..bc46edaa3
--- /dev/null
+++ b/src/Core/Support/EventGenerator.php
@@ -0,0 +1,33 @@
+pendingEvents[] = $event;
+ }
+
+ /**
+ * Return and reset all pending events
+ *
+ * @return array
+ */
+ public function releaseEvents()
+ {
+ $events = $this->pendingEvents;
+
+ $this->pendingEvents = [];
+
+ return $events;
+ }
+}
diff --git a/src/Flarum/Api/Actions/Auth/Login.php b/src/Flarum/Api/Actions/Auth/Login.php
deleted file mode 100644
index d61030bdf..000000000
--- a/src/Flarum/Api/Actions/Auth/Login.php
+++ /dev/null
@@ -1,37 +0,0 @@
-input('identification');
- $password = $this->input('password');
- $field = filter_var($identification, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
- $credentials = [$field => $identification, 'password' => $password];
-
- if (! Auth::validate($credentials)) {
- return $this->respondWithError('invalidLogin', 401);
- }
-
- $user = Auth::getLastAttempted();
- $user->token = str_random(60);
- $user->save();
-
- return Response::json([
- 'token' => $user->token,
- 'userId' => $user->id
- ]);
- }
-}
diff --git a/src/Flarum/Api/Actions/Base.php b/src/Flarum/Api/Actions/Base.php
deleted file mode 100644
index 53a765e91..000000000
--- a/src/Flarum/Api/Actions/Base.php
+++ /dev/null
@@ -1,226 +0,0 @@
-commandBus = $commandBus;
- }
-
- abstract protected function run();
-
- public function handle($request, $parameters = [])
- {
- $this->registerErrorHandlers();
-
- $this->request = $request;
- $this->parameters = $parameters;
-
- $this->document = new Document;
- $this->document->addMeta('profile', '?');
-
- return $this->run();
- }
-
- public function setRequest($request)
- {
- $this->request = $request;
- }
-
- public function param($key, $default = null)
- {
- return array_get($this->parameters, $key, $default);
- }
-
- public function input($key, $default = null)
- {
- return $this->request->input($key, $default);
- }
-
- public function fillCommandWithInput($command, $inputKey = null)
- {
- $input = $inputKey ? $this->input($inputKey) : $this->request->input->all();
-
- foreach ($input as $k => $v) {
- $command->$k = $v;
- }
- }
-
- protected function inputRange($key, $default = null, $min = null, $max = null)
- {
- $value = (int) $this->input($key, $default);
-
- if (! is_null($min)) {
- $value = max($value, $min);
- }
- if (! is_null($max)) {
- $value = min($value, $max);
- }
- return $value;
- }
-
- protected function included($available)
- {
- $requested = explode(',', $this->input('include'));
- return array_intersect((array) $available, $requested);
- }
-
- protected function explodeIds($ids)
- {
- return array_unique(array_map('intval', array_filter(explode(',', $ids))));
- }
-
- protected function inputIn($key, $options)
- {
- $value = $this->input($key);
-
- if (array_key_exists($key, $options)) {
- return $options[$key];
- }
- if (! in_array($value, $options)) {
- $value = reset($options);
- }
-
- return $value;
- }
-
- protected function sort($options)
- {
- $criteria = (string) $this->input('sort', '');
- $order = null;
-
- if ($criteria && $criteria[0] == '-') {
- $order = 'desc';
- $criteria = substr($criteria, 1);
- }
-
- if (! in_array($criteria, $options)) {
- $criteria = reset($options);
- }
-
- if ($criteria && ! $order) {
- $order = 'asc';
- }
-
- return [
- 'by' => $criteria,
- 'order' => $order,
- 'string' => ($order == 'desc' ? '-' : '').$criteria
- ];
- }
-
- protected function start()
- {
- return $this->inputRange('start', 0, 0);
- }
-
- protected function count($default, $max = 100)
- {
- return $this->inputRange('count', $default, 1, $max);
- }
-
- protected function buildUrl($route, $params = [], $input = [])
- {
- $url = route('flarum.api.'.$route, $params);
- $queryString = $input ? '?'.http_build_query($input) : '';
-
- return $url.$queryString;
- }
-
- protected function respondWithoutContent($statusCode = 204, $headers = [])
- {
- return Response::make('', $statusCode, $headers);
- }
-
- protected function respondWithArray($array, $statusCode = 200, $headers = [])
- {
- // @todo remove this?
- $headers['Access-Control-Allow-Origin'] = '*';
-
- return Response::json($array, $statusCode, $headers);
- }
-
- protected function respondWithDocument($statusCode = 200, $headers = [])
- {
- // @todo remove this
- if (defined('LARAVEL_START')) {
- $this->document->addMeta('pageload', microtime(true) - LARAVEL_START);
- }
-
- Event::fire('flarum.api.willRespondWithDocument', [$this->document]);
-
- $headers['Content-Type'] = 'application/vnd.api+json';
-
- return $this->respondWithArray($this->document->toArray(), $statusCode, $headers);
- }
-
- // @todo fix this
- protected function call($name, $params, $method, $input)
- {
- Input::replace($input);
-
- $url = URL::action('\\Flarum\\Api\\Controllers\\'.$name, $params, false);
- $request = Request::create($url, $method);
- $json = Route::dispatch($request)->getContent();
-
- return json_decode($json, true);
- }
-
- protected function registerErrorHandlers()
- {
- if (! Config::get('app.debug')) {
- App::error(function ($exception, $code) {
- return $this->respondWithError('ApplicationError', $code);
- });
- }
-
- App::error(function (ModelNotFoundException $exception) {
- return $this->respondWithError('ResourceNotFound', 404);
- });
-
- App::error(function (ValidationFailureException $exception) {
- $errors = [];
- foreach ($exception->getErrors()->getMessages() as $field => $messages) {
- $errors[] = [
- 'code' => 'ValidationFailure',
- 'detail' => implode("\n", $messages),
- 'path' => $field
- ];
- }
- return $this->respondWithErrors($errors, 422);
- });
- }
-
- protected function respondWithErrors($errors, $httpCode = 500)
- {
- return Response::json(['errors' => $errors], $httpCode);
- }
-
- protected function respondWithError($error, $httpCode = 500, $detail = null)
- {
- $error = ['code' => $error];
-
- if ($detail) {
- $error['detail'] = $detail;
- }
-
- return $this->respondWithErrors([$error], $httpCode);
- }
-}
diff --git a/src/Flarum/Api/Actions/Discussions/Delete.php b/src/Flarum/Api/Actions/Discussions/Delete.php
deleted file mode 100644
index 5119e9a10..000000000
--- a/src/Flarum/Api/Actions/Discussions/Delete.php
+++ /dev/null
@@ -1,26 +0,0 @@
-param('id');
- $command = new DeleteDiscussionCommand($discussionId, User::current());
-
- Event::fire('Flarum.Api.Actions.Discussions.Delete.WillExecuteCommand', [$command]);
-
- $this->commandBus->execute($command);
-
- return $this->respondWithoutContent();
- }
-}
diff --git a/src/Flarum/Api/Actions/Discussions/Index.php b/src/Flarum/Api/Actions/Discussions/Index.php
deleted file mode 100644
index ca273b2d6..000000000
--- a/src/Flarum/Api/Actions/Discussions/Index.php
+++ /dev/null
@@ -1,84 +0,0 @@
-finder = $finder;
- }
-
- /**
- * Show a list of discussions.
- *
- * @todo custom rate limit for this function? determined by if $key was valid?
- * @return Response
- */
- protected function run()
- {
- $query = $this->input('q');
- $key = $this->input('key');
- $start = $this->start();
- $include = $this->included(['startPost', 'lastPost', 'relevantPosts']);
- $count = $this->count(20, 50);
- $sort = $this->sort(['', 'lastPost', 'replies', 'created']);
-
- $relations = array_merge(['startUser', 'lastUser'], $include);
-
- // Set up the discussion finder with our search criteria, and get the
- // requested range of results with the necessary relations loaded.
- $this->finder->setUser(User::current());
- $this->finder->setQuery($query);
- $this->finder->setSort($sort['by']);
- $this->finder->setOrder($sort['order']);
- $this->finder->setKey($key);
-
- $discussions = $this->finder->results($count, $start, array_merge($relations, ['state']));
-
- if (($total = $this->finder->getCount()) !== null) {
- $this->document->addMeta('total', $total);
- }
- if (($key = $this->finder->getKey()) !== null) {
- $this->document->addMeta('key', $key);
- }
-
- // If there are more results, then we need to construct a URL to the
- // next results page and add that to the metadata. We do this by
- // compacting all of the valid query parameters which have been
- // specified.
- if ($this->finder->areMoreResults()) {
- $start += $count;
- $include = implode(',', $include);
- $sort = $sort['string'];
- $input = array_filter(compact('query', 'key', 'sort', 'start', 'count', 'include'));
- $moreUrl = $this->buildUrl('discussions.index', [], $input);
- } else {
- $moreUrl = '';
- }
- $this->document->addMeta('moreUrl', $moreUrl);
-
- // Finally, we can set up the discussion serializer and use it to create
- // a collection of discussion results.
- $serializer = new DiscussionSerializer($relations);
- $this->document->setPrimaryElement($serializer->collection($discussions));
-
- return $this->respondWithDocument();
- }
-}
diff --git a/src/Flarum/Api/Actions/Discussions/Update.php b/src/Flarum/Api/Actions/Discussions/Update.php
deleted file mode 100644
index 02b7c6bc5..000000000
--- a/src/Flarum/Api/Actions/Discussions/Update.php
+++ /dev/null
@@ -1,52 +0,0 @@
-param('id');
- $readNumber = $this->input('discussions.readNumber');
- $user = User::current();
-
- // First, we will run the EditDiscussionCommand. This will update the
- // discussion's direct properties; by default, this is just the title.
- // As usual, however, we will fire an event to allow plugins to update
- // additional properties.
- $command = new EditDiscussionCommand($discussionId, $user);
- $this->fillCommandWithInput($command, 'discussions');
-
- Event::fire('Flarum.Api.Actions.Discussions.Update.WillExecuteCommand', [$command]);
-
- $discussion = $this->commandBus->execute($command);
-
- // Next, if a read number was specified in the request, we will run the
- // ReadDiscussionCommand. We won't bother firing an event for this one,
- // because it's pretty specific. (This may need to change in the future.)
- if ($readNumber) {
- $command = new ReadDiscussionCommand($discussionId, $user, $readNumber);
- $this->commandBus->execute($command);
- }
-
- // Presumably, the discussion was updated successfully. (One of the command
- // handlers would have thrown an exception if not.) We set this
- // discussion as our document's primary element.
- $serializer = new DiscussionSerializer(['addedPosts', 'addedPosts.user']);
- $this->document->setPrimaryElement($serializer->resource($discussion));
-
- return $this->respondWithDocument();
- }
-}
diff --git a/src/Flarum/Api/Actions/Posts/Delete.php b/src/Flarum/Api/Actions/Posts/Delete.php
deleted file mode 100644
index ee8cf2e6b..000000000
--- a/src/Flarum/Api/Actions/Posts/Delete.php
+++ /dev/null
@@ -1,26 +0,0 @@
-param('id');
- $command = new DeletePostCommand($postId, User::current());
-
- Event::fire('Flarum.Api.Actions.Posts.Delete.WillExecuteCommand', [$command]);
-
- $this->commandBus->execute($command);
-
- return $this->respondWithoutContent();
- }
-}
diff --git a/src/Flarum/Api/Actions/Posts/GetsPostsForDiscussion.php b/src/Flarum/Api/Actions/Posts/GetsPostsForDiscussion.php
deleted file mode 100644
index 809f2bc7d..000000000
--- a/src/Flarum/Api/Actions/Posts/GetsPostsForDiscussion.php
+++ /dev/null
@@ -1,19 +0,0 @@
-sort(['time']);
- $count = $this->count(20, 50);
-
- if (($near = $this->input('near')) > 1) {
- $start = $repository->getIndexForNumber($discussionId, $near);
- $start = max(0, $start - $count / 2);
- } else {
- $start = 0;
- }
-
- return $repository->findByDiscussion($discussionId, $relations, $sort['by'], $sort['order'] ?: 'asc', $count, $start);
- }
-}
diff --git a/src/Flarum/Api/Actions/Posts/Show.php b/src/Flarum/Api/Actions/Posts/Show.php
deleted file mode 100644
index b5eb05348..000000000
--- a/src/Flarum/Api/Actions/Posts/Show.php
+++ /dev/null
@@ -1,39 +0,0 @@
-explodeIds($this->param('id'));
- $posts = Post::whereCanView()->whereIn('id', $ids)->get();
-
- if (! count($posts)) {
- throw new ModelNotFoundException;
- }
-
- $include = $this->included(['discussion', 'replyTo']);
- $relations = array_merge(['user', 'editUser', 'hideUser'], $include);
- $posts->load($relations);
-
- // Finally, we can set up the post serializer and use it to create
- // a post resource or collection, depending on how many posts were
- // requested.
- $serializer = new PostSerializer($relations);
- $this->document->setPrimaryElement(
- count($ids) == 1 ? $serializer->resource($posts->first()) : $serializer->collection($posts)
- );
-
- return $this->respondWithDocument();
- }
-}
diff --git a/src/Flarum/Api/Actions/Users/Delete.php b/src/Flarum/Api/Actions/Users/Delete.php
deleted file mode 100644
index 2f58caa42..000000000
--- a/src/Flarum/Api/Actions/Users/Delete.php
+++ /dev/null
@@ -1,26 +0,0 @@
-param('id');
- $command = new DeleteUserCommand($userId, User::current());
-
- Event::fire('Flarum.Api.Actions.Users.Delete.WillExecuteCommand', [$command]);
-
- $this->commandBus->execute($command);
-
- return $this->respondWithoutContent();
- }
-}
diff --git a/src/Flarum/Api/Actions/Users/Show.php b/src/Flarum/Api/Actions/Users/Show.php
deleted file mode 100644
index 11da5c518..000000000
--- a/src/Flarum/Api/Actions/Users/Show.php
+++ /dev/null
@@ -1,26 +0,0 @@
-findOrFail($this->param('id'));
-
- // Set up the user serializer, which we will use to create the
- // document's primary resource. We will specify that we want the
- // 'groups' relation to be included by default.
- $serializer = new UserSerializer(['groups']);
- $this->document->setPrimaryElement($serializer->resource($user));
-
- return $this->respondWithDocument();
- }
-}
diff --git a/src/Flarum/Api/ApiServiceProvider.php b/src/Flarum/Api/ApiServiceProvider.php
deleted file mode 100644
index 35a102acc..000000000
--- a/src/Flarum/Api/ApiServiceProvider.php
+++ /dev/null
@@ -1,46 +0,0 @@
-package('flarum/api', 'flarum.api');
-
- include __DIR__.'/../../routes.api.php';
- }
-
- /**
- * Register the service provider.
- *
- * @return void
- */
- public function register()
- {
-
- }
-
- /**
- * Get the services provided by the provider.
- *
- * @return array
- */
- public function provides()
- {
- return array();
- }
-}
diff --git a/src/Flarum/Api/Serializers/BaseSerializer.php b/src/Flarum/Api/Serializers/BaseSerializer.php
deleted file mode 100644
index c8718fe39..000000000
--- a/src/Flarum/Api/Serializers/BaseSerializer.php
+++ /dev/null
@@ -1,102 +0,0 @@
- $user->email,
- ];
-
- Event::fire('flarum.api.serialize.user.admin', [&$serialized]);
-
- return $serialized;
- }
-
-}
diff --git a/src/Flarum/Api/Serializers/UserCurrentSerializer.php b/src/Flarum/Api/Serializers/UserCurrentSerializer.php
deleted file mode 100644
index a2e090062..000000000
--- a/src/Flarum/Api/Serializers/UserCurrentSerializer.php
+++ /dev/null
@@ -1,29 +0,0 @@
-id)
- {
- $serialized += [
- 'time_zone' => $user->time_zone,
- 'time_zone_offset' => with(new DateTimeZone($user->time_zone))->getOffset(new DateTime('now'))
- // other user preferences. probably mostly from external sources (e.g. flarum/web)
- ];
- }
-
- Event::fire('flarum.api.serialize.user.current', [&$serialized]);
-
- return $serialized;
- }
-
-}
diff --git a/src/Flarum/Core/CoreServiceProvider.php b/src/Flarum/Core/CoreServiceProvider.php
deleted file mode 100644
index 82952bd7b..000000000
--- a/src/Flarum/Core/CoreServiceProvider.php
+++ /dev/null
@@ -1,111 +0,0 @@
-package('flarum/core', 'flarum');
-
- $this->app->make('validator')->extend('username', 'Flarum\Core\Users\UsernameValidator@validate');
-
- $this->app['config']->set('auth.model', 'Flarum\Core\Users\User');
-
- Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater');
- Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater');
- Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\RenamedPostCreator');
- Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\EmailConfirmationMailer');
-
- Post::addType('comment', 'Flarum\Core\Posts\CommentPost');
- Post::addType('renamed', 'Flarum\Core\Posts\RenamedPost');
- }
-
- /**
- * Register the service provider.
- *
- * @return void
- */
- public function register()
- {
- // Start up the Laracasts Commander package. This is used as the basis
- // for the Commands & Domain Events architecture used to structure
- // Flarum's domain.
- $this->app->register('Laracasts\Commander\CommanderServiceProvider');
-
- // Register a singleton entity that represents this forum. This entity
- // will be used to check for global forum permissions (like viewing the
- // forum, registering, and starting discussions.)
- $this->app->singleton('flarum.forum', 'Flarum\Core\Forum');
-
- // Register the extensions manager object. This manages a list of
- // available extensions, and provides functionality to enable/disable
- // them.
- $this->app->singleton('flarum.extensions', 'Flarum\Core\Support\Extensions\Manager');
-
- // Register the permissions manager object. This reads the permissions
- // from the permissions repository and can determine whether or not a
- // user has explicitly been granted a certain permission.
- $this->app->singleton('flarum.permissions', 'Flarum\Core\Permissions\Manager');
-
-
-
- $this->app->bind('flarum.discussionFinder', 'Flarum\Core\Discussions\DiscussionFinder');
-
- $this->app->singleton('flarum.formatter', function () {
- $formatter = new FormatterManager($this->app);
- $formatter->add('basic', 'Flarum\Core\Formatter\BasicFormatter');
- return $formatter;
- });
-
-
-
- // $this->app->singleton(
- // 'Flarum\Core\Repositories\Contracts\DiscussionRepository',
- // function($app)
- // {
- // $discussion = new \Flarum\Core\Repositories\EloquentDiscussionRepository;
- // return new DiscussionCacheDecorator($discussion);
- // }
- // );
- // $this->app->singleton(
- // 'Flarum\Core\Repositories\Contracts\UserRepository',
- // 'Flarum\Core\Repositories\EloquentUserRepository'
- // );
- // $this->app->singleton(
- // 'Flarum\Core\Repositories\Contracts\PostRepository',
- // 'Flarum\Core\Repositories\EloquentPostRepository'
- // );
- // $this->app->singleton(
- // 'Flarum\Core\Repositories\Contracts\GroupRepository',
- // 'Flarum\Core\Repositories\EloquentGroupRepository'
- // );
- }
-
- /**
- * Get the services provided by the provider.
- *
- * @return array
- */
- public function provides()
- {
- return array();
- }
-}
diff --git a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php
deleted file mode 100644
index 6008c703e..000000000
--- a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php
+++ /dev/null
@@ -1,33 +0,0 @@
-discussions = $discussions;
- }
-
- public function handle($command)
- {
- $user = $command->user;
- $discussion = $this->discussions->findOrFail($command->discussionId, $user);
-
- $discussion->assertCan($user, 'delete');
-
- Event::fire('Flarum.Core.Discussions.Commands.DeleteDiscussion.DiscussionWillBeDeleted', [$discussion, $command]);
-
- $this->discussions->delete($discussion);
- $this->dispatchEventsFor($discussion);
-
- return $discussion;
- }
-}
diff --git a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php
deleted file mode 100644
index 3f0ccd0fc..000000000
--- a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php
+++ /dev/null
@@ -1,7 +0,0 @@
-discussions = $discussions;
- }
-
- public function handle($command)
- {
- $user = $command->user;
- $discussion = $this->discussions->findOrFail($command->discussionId, $user);
-
- $discussion->state = $this->discussions->getState($discussion, $user);
- $discussion->state->read($command->readNumber);
-
- Event::fire('Flarum.Core.Discussions.Commands.ReadDiscussion.StateWillBeSaved', [$discussion, $command]);
-
- $this->discussions->saveState($discussion->state);
- $this->dispatchEventsFor($discussion->state);
-
- return $discussion;
- }
-}
diff --git a/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php
deleted file mode 100644
index 46258ce3f..000000000
--- a/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php
+++ /dev/null
@@ -1,19 +0,0 @@
-user->exists) {
- throw new PermissionDeniedException;
- }
-
- parent::validate($command);
- }
-}
diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php
deleted file mode 100644
index bd549f6d9..000000000
--- a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php
+++ /dev/null
@@ -1,63 +0,0 @@
-forum = $forum;
- $this->discussionRepo = $discussionRepo;
- $this->commandBus = $commandBus;
- }
-
- public function handle($command)
- {
- $this->forum->assertCan($command->user, 'startDiscussion');
-
- // Create a new Discussion entity, persist it, and dispatch domain
- // events. Before persistance, though, fire an event to give plugins
- // an opportunity to alter the discussion entity based on data in the
- // command they may have passed through in the controller.
- $discussion = Discussion::start(
- $command->title,
- $command->user
- );
-
- Event::fire('Flarum.Core.Discussions.Commands.StartDiscussion.DiscussionWillBeSaved', [$discussion, $command]);
-
- $this->discussionRepo->save($discussion);
-
- // Now that the discussion has been created, we can add the first post.
- // For now we will do this by running the PostReply command, but as this
- // will trigger a domain event that is slightly semantically incorrect
- // in this situation (ReplyWasPosted), we may need to reconsider someday.
- $this->commandBus->execute(
- new PostReplyCommand($discussion->id, $command->content, $command->user)
- );
-
- // The discussion may have been updated by the PostReplyCommand; we need
- // to refresh its data.
- $discussion = $this->discussionRepo->find($discussion->id);
-
- $this->dispatchEventsFor($discussion);
-
- return $discussion;
- }
-}
diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php
deleted file mode 100644
index a00c68ae6..000000000
--- a/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php
+++ /dev/null
@@ -1,7 +0,0 @@
- 'required',
- 'start_time' => 'required|date',
- 'comments_count' => 'integer',
- 'start_user_id' => 'integer',
- 'start_post_id' => 'integer',
- 'last_time' => 'date',
- 'last_user_id' => 'integer',
- 'last_post_id' => 'integer',
- 'last_post_number' => 'integer'
- ];
-
- protected $addedPosts = [];
-
- public static function boot()
- {
- parent::boot();
-
- static::grant(function ($grant, $user, $permission) {
- return app('flarum.permissions')->granted($user, $permission, 'discussion');
- });
-
- // Grant view access to a discussion if the user can view the forum.
- static::grant('view', function ($grant, $user) {
- return app('flarum.forum')->can($user, 'view');
- });
-
- // Allow a user to edit their own discussion.
- static::grant('edit', function ($grant, $user) {
- if (app('flarum.permissions')->granted($user, 'editOwn', 'discussion')) {
- $grant->where('start_user_id', $user->id);
- }
- });
-
- static::deleted(function ($discussion) {
- $discussion->raise(new Events\DiscussionWasDeleted($discussion));
-
- $discussion->posts()->delete();
- $discussion->readers()->detach();
- });
- }
-
- public static function start($title, $user)
- {
- $discussion = new static;
-
- $discussion->title = $title;
- $discussion->start_time = time();
- $discussion->start_user_id = $user->id;
-
- $discussion->raise(new Events\DiscussionWasStarted($discussion));
-
- return $discussion;
- }
-
- public function setLastPost(Post $post)
- {
- $this->last_time = $post->time;
- $this->last_user_id = $post->user_id;
- $this->last_post_id = $post->id;
- $this->last_post_number = $post->number;
- }
-
- public function refreshLastPost()
- {
- if ($lastPost = $this->comments()->orderBy('time', 'desc')->first()) {
- $this->setLastPost($lastPost);
- }
- }
-
- public function getAddedPosts()
- {
- return $this->addedPosts;
- }
-
- public function postWasAdded(Post $post)
- {
- $this->addedPosts[] = $post;
- }
-
- public function refreshCommentsCount()
- {
- $this->comments_count = $this->comments()->count();
- }
-
- public function rename($title, $user)
- {
- if ($this->title === $title) {
- return;
- }
-
- $oldTitle = $this->title;
- $this->title = $title;
-
- $this->raise(new Events\DiscussionWasRenamed($this, $user, $oldTitle));
- }
-
- public function getDates()
- {
- return ['start_time', 'last_time'];
- }
-
- public function posts()
- {
- return $this->hasMany('Flarum\Core\Posts\Post');
- }
-
- public function comments()
- {
- return $this->posts()->where('type', 'comment')->whereNull('hide_time');
- }
-
- public function startPost()
- {
- return $this->belongsTo('Flarum\Core\Posts\Post', 'start_post_id');
- }
-
- public function startUser()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'start_user_id');
- }
-
- public function lastPost()
- {
- return $this->belongsTo('Flarum\Core\Posts\Post', 'last_post_id');
- }
-
- public function lastUser()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'last_user_id');
- }
-
- public function readers()
- {
- return $this->belongsToMany('Flarum\Core\Users\User', 'users_discussions');
- }
-
- public function state($userId = null)
- {
- if (is_null($userId)) {
- $userId = User::current()->id;
- }
- return $this->hasOne('Flarum\Core\Discussions\DiscussionState')->where('user_id', $userId);
- }
-
- public function stateFor($user)
- {
- $state = $this->state($user->id)->first();
-
- if (! $state) {
- $state = new DiscussionState;
- $state->discussion_id = $this->id;
- $state->user_id = $user->id;
- }
-
- return $state;
- }
-
- public function scopePermission($query, $permission, $user = null)
- {
- if (is_null($user)) {
- $user = User::current();
- }
- return $this->scopeWhereCan($query, $user, $permission);
- }
-
- public function scopeWhereCanView($query, $user = null)
- {
- return $this->scopePermission($query, 'view', $user);
- }
-
- public function permission($permission, $user = null)
- {
- if (is_null($user)) {
- $user = User::current();
- }
- return $this->can($user, $permission);
- }
-
- public function assertCan($user, $permission)
- {
- if (! $this->can($user, $permission)) {
- throw new PermissionDeniedException;
- }
- }
-}
diff --git a/src/Flarum/Core/Discussions/DiscussionFinder.php b/src/Flarum/Core/Discussions/DiscussionFinder.php
deleted file mode 100644
index a7d9cbd6d..000000000
--- a/src/Flarum/Core/Discussions/DiscussionFinder.php
+++ /dev/null
@@ -1,249 +0,0 @@
- ['last_time', 'desc'],
- 'replies' => ['comments_count', 'desc'],
- 'created' => ['start_time', 'desc']
- ];
-
- protected $order;
-
- protected $key;
-
- protected $count;
-
- protected $areMoreResults;
-
- protected $fulltext;
-
- public function getUser()
- {
- return $this->user;
- }
-
- public function setUser($user)
- {
- $this->user = $user;
- }
-
- public function getTokens()
- {
- return $this->tokens;
- }
-
- public function setTokens($tokens)
- {
- $this->tokens = $tokens;
- }
-
- public function setQuery($query)
- {
- $tokenizer = new Tokenizer($query);
- $this->setTokens($tokenizer->tokenize());
- }
-
- public function getSort()
- {
- return $this->sort;
- }
-
- public function setSort($sort)
- {
- $this->sort = $sort;
- }
-
- public function getOrder()
- {
- return $this->order;
- }
-
- public function setOrder($order)
- {
- $this->order = $order;
- }
-
- public function getKey()
- {
- return $this->key;
- }
-
- public function setKey($key)
- {
- $this->key = $key;
- }
-
- protected function getCacheKey()
- {
- return 'discussions.'.$this->key;
- }
-
- public function getCount()
- {
- return $this->count;
- }
-
- public function areMoreResults()
- {
- return $this->areMoreResults;
- }
-
- public function fulltext()
- {
- return $this->fulltext;
- }
-
- public function results($count = null, $start = 0, $load = [])
- {
- $relevantPosts = false;
-
- if (in_array('relevantPosts', $load)) {
- $load = array_diff($load, ['relevantPosts', 'relevantPosts.user']);
- $relevantPosts = true;
- }
-
- $ids = null;
- $query = Discussion::whereCan($this->user, 'view');
- $query->with($load);
-
- if ($this->key and Cache::has($key = $this->getCacheKey())) {
- $ids = Cache::get($key);
- } elseif (count($this->tokens)) {
- // foreach ($tokens as $type => $value)
- // {
- // switch ($type)
- // {
- // case 'flag:draft':
- // case 'flag:muted':
- // case 'flag:subscribed':
- // case 'flag:private':
- // // pre-process
- // $ids = $this->discussions->getDraftIdsForUser(Auth::user());
- // $ids = $this->discussions->getMutedIdsForUser(Auth::user());
- // $ids = $this->discussions->getSubscribedIdsForUser(Auth::user());
- // $ids = $this->discussions->getPrivateIdsForUser(Auth::user());
- // // $user->permissions['discussion']['view'] = [1,2,3]
- // break;
- // }
- // }
-
- // $search = $this->search->create();
- // $search->limitToIds($ids);
- // $search->setQuery($query);
- // $search->setSort($sort);
- // $search->setSortOrder($sortOrder);
- // $results = $search->results();
-
- // process flag:unread here?
-
- // parse the tokens.
- // run ID filters.
-
- // TESTING lol
- $this->fulltext = reset($this->tokens);
- $posts = Post::whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$this->fulltext])
- ->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$this->fulltext]);
-
- $posts = $posts->select('id', 'discussion_id');
-
- $posts = $posts->get();
-
- $ids = [];
- foreach ($posts as $post) {
- if (empty($ids[$post->discussion_id])) {
- $ids[$post->discussion_id] = [];
- }
- $ids[$post->discussion_id][] = $post->id;
- }
-
- if ($this->fulltext and ! $this->sort) {
- $this->sort = 'relevance';
- }
-
- if (! is_null($ids)) {
- $this->key = str_random();
- }
-
- // run other tokens
- // $discussions->where('');
- }
-
- if (! is_null($ids)) {
- Cache::put($this->getCacheKey(), $ids, 10); // recache
- $this->count = count($ids);
-
- if (! $ids) {
- return [];
- }
- $query->whereIn('id', array_keys($ids));
-
- // If we're sorting by relevance, assume that the IDs we've been provided
- // are already sorted by relevance. Therefore, we'll get discussions in
- // the order that they are in.
- if ($this->sort == 'relevance') {
- foreach ($ids as $id) {
- $query->orderBy(DB::raw('id != '.(int) $id));
- }
- }
- }
-
- if (empty($this->sort)) {
- reset($this->sortMap);
- $this->sort = key($this->sortMap);
- }
- if (! empty($this->sortMap[$this->sort])) {
- list($column, $order) = $this->sortMap[$this->sort];
- $query->orderBy($column, $this->order ?: $order);
- }
-
- if ($start > 0) {
- $query->skip($start);
- }
- if ($count > 0) {
- $query->take($count + 1);
- $results = $query->get();
- $this->areMoreResults = $results->count() > $count;
- if ($this->areMoreResults) {
- $results->pop();
- }
- } else {
- $results = $query->get();
- }
-
- if (!empty($relevantPosts)) {
- $postIds = [];
- foreach ($ids as $id => &$posts) {
- $postIds = array_merge($postIds, array_slice($posts, 0, 2));
- }
- $posts = Post::with('user')->whereCan($this->user, 'view')->whereIn('id', $postIds)->get();
-
- foreach ($results as $discussion) {
- $discussion->relevantPosts = $posts->filter(function ($post) use ($discussion) {
- return $post->discussion_id == $discussion->id;
- })
- ->slice(0, 2)
- ->each(function ($post) {
- $pos = strpos(strtolower($post->content), strtolower($this->fulltext));
- // TODO: make clipping more intelligent (full words only)
- $start = max(0, $pos - 50);
- $post->content = ($start > 0 ? '...' : '').str_limit(substr($post->content, $start), 300);
- });
- }
- }
-
- return $results;
- }
-}
diff --git a/src/Flarum/Core/Discussions/DiscussionRepository.php b/src/Flarum/Core/Discussions/DiscussionRepository.php
deleted file mode 100755
index b73f512f2..000000000
--- a/src/Flarum/Core/Discussions/DiscussionRepository.php
+++ /dev/null
@@ -1,43 +0,0 @@
-whereCanView($user);
- }
-
- return $query->findOrFail($id);
- }
-
- public function save(Discussion $discussion)
- {
- $discussion->assertValid();
- $discussion->save();
- }
-
- public function delete(Discussion $discussion)
- {
- $discussion->delete();
- }
-
- public function getState(Discussion $discussion, User $user)
- {
- return $discussion->stateFor($user);
- }
-
- public function saveState(DiscussionState $state)
- {
- $state->save();
- }
-}
diff --git a/src/Flarum/Core/Discussions/DiscussionState.php b/src/Flarum/Core/Discussions/DiscussionState.php
deleted file mode 100644
index 8bbae7e6a..000000000
--- a/src/Flarum/Core/Discussions/DiscussionState.php
+++ /dev/null
@@ -1,49 +0,0 @@
-belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id');
- }
-
- public function user()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
- }
-
- public function read($number)
- {
- $this->read_number = $number; // only if it's greater than the old one
- $this->read_time = time();
-
- $this->raise(new Events\DiscussionWasRead($this));
- }
-
- /**
- * Set the keys for a save update query.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @return \Illuminate\Database\Eloquent\Builder
- */
- protected function setKeysForSaveQuery(\Illuminate\Database\Eloquent\Builder $query)
- {
- $query->where('discussion_id', $this->discussion_id)
- ->where('user_id', $this->user_id);
-
- return $query;
- }
-}
diff --git a/src/Flarum/Core/Entity.php b/src/Flarum/Core/Entity.php
deleted file mode 100755
index a76613323..000000000
--- a/src/Flarum/Core/Entity.php
+++ /dev/null
@@ -1,81 +0,0 @@
-validator = $validator ?: \App::make('validator');
- }
-
- public function valid()
- {
- return $this->getValidator()->passes();
- }
-
- public function assertValid()
- {
- $validation = $this->getValidator();
-
- if ($validation->fails()) {
- $this->throwValidationException($validation->errors(), $validation->getData());
- }
- }
-
- protected function getValidator()
- {
- $rules = $this->expandUniqueRules(static::$rules);
-
- return $this->validator->make($this->attributes, $rules, static::$messages);
- }
-
- protected function expandUniqueRules($rules)
- {
- foreach ($rules as $column => &$ruleset) {
- if (is_string($ruleset)) {
- $ruleset = explode('|', $ruleset);
- }
- foreach ($ruleset as &$rule) {
- if (strpos($rule, 'unique') === 0) {
- $parts = explode(':', $rule);
- $key = $this->getKey() ?: 'NULL';
- $rule = 'unique:'.$this->getTable().','.$column.','.$key.','.$this->getKeyName();
- if (! empty($parts[1])) {
- $wheres = explode(',', $parts[1]);
- foreach ($wheres as &$where) {
- $where .= ','.$this->$where;
- }
- $rule .= ','.implode(',', $wheres);
- }
- }
- }
- }
-
- return $rules;
- }
-
- protected function throwValidationException($errors, $input)
- {
- $exception = new ValidationFailureException;
- $exception->setErrors($errors)->setInput($input);
- throw $exception;
- }
-}
diff --git a/src/Flarum/Core/Forum.php b/src/Flarum/Core/Forum.php
deleted file mode 100755
index 2270e254b..000000000
--- a/src/Flarum/Core/Forum.php
+++ /dev/null
@@ -1,26 +0,0 @@
-granted($user, $permission, 'forum');
- });
- }
-
- public function assertCan($user, $permission)
- {
- if (! $this->can($user, $permission)) {
- throw new PermissionDeniedException;
- }
- }
-}
diff --git a/src/Flarum/Core/Groups/Group.php b/src/Flarum/Core/Groups/Group.php
deleted file mode 100755
index 8f0f50ae6..000000000
--- a/src/Flarum/Core/Groups/Group.php
+++ /dev/null
@@ -1,18 +0,0 @@
-belongsToMany('Flarum\Core\Users\User', 'users_groups');
- }
-
-}
diff --git a/src/Flarum/Core/Groups/GroupRepository.php b/src/Flarum/Core/Groups/GroupRepository.php
deleted file mode 100755
index b5e4c2969..000000000
--- a/src/Flarum/Core/Groups/GroupRepository.php
+++ /dev/null
@@ -1,15 +0,0 @@
-save();
- }
-
- public function delete(Group $group)
- {
- $group->delete();
- }
-}
diff --git a/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php b/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php
deleted file mode 100755
index c06aa3157..000000000
--- a/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php
+++ /dev/null
@@ -1,63 +0,0 @@
-discussionRepo = $discussionRepo;
- }
-
- public function whenReplyWasPosted(ReplyWasPosted $event)
- {
- $discussion = $this->discussionRepo->find($event->post->discussion_id);
-
- $discussion->comments_count++;
- $discussion->setLastPost($event->post);
-
- $this->discussionRepo->save($discussion);
- }
-
- public function whenPostWasDeleted(PostWasDeleted $event)
- {
- $this->removePost($event->post);
- }
-
- public function whenPostWasHidden(PostWasHidden $event)
- {
- $this->removePost($event->post);
- }
-
- public function whenPostWasRestored(PostWasRestored $event)
- {
- $discussion = $this->discussionRepo->find($event->post->discussion_id);
-
- $discussion->refreshCommentsCount();
- $discussion->refreshLastPost();
-
- $this->discussionRepo->save($discussion);
- }
-
- protected function removePost(Post $post)
- {
- $discussion = $this->discussionRepo->find($post->discussion_id);
-
- $discussion->refreshCommentsCount();
-
- if ($discussion->last_post_id == $post->id) {
- $discussion->refreshLastPost();
- }
-
- $this->discussionRepo->save($discussion);
- }
-}
diff --git a/src/Flarum/Core/Listeners/EmailConfirmationMailer.php b/src/Flarum/Core/Listeners/EmailConfirmationMailer.php
deleted file mode 100755
index 90bdda0b9..000000000
--- a/src/Flarum/Core/Listeners/EmailConfirmationMailer.php
+++ /dev/null
@@ -1,36 +0,0 @@
-mailer = $mailer;
- }
-
- public function whenUserWasRegistered(UserWasRegistered $event)
- {
- $user = $event->user;
-
- $data = [
- 'user' => $user,
- 'url' => route('flarum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
- ];
-
- $this->mailer->send('flarum::emails.confirm', $data, function ($message) use ($user) {
- $message->to($user->email)->subject('Welcome!');
- });
- }
-
- public function whenEmailWasChanged(EmailWasChanged $event)
- {
-
- }
-}
diff --git a/src/Flarum/Core/Listeners/RenamedPostCreator.php b/src/Flarum/Core/Listeners/RenamedPostCreator.php
deleted file mode 100755
index 349e94ee8..000000000
--- a/src/Flarum/Core/Listeners/RenamedPostCreator.php
+++ /dev/null
@@ -1,31 +0,0 @@
-postRepo = $postRepo;
- }
-
- public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
- {
- $post = RenamedPost::reply(
- $event->discussion->id,
- $event->user->id,
- $event->oldTitle,
- $event->discussion->title
- );
-
- $this->postRepo->save($post);
-
- $event->discussion->postWasAdded($post);
- }
-}
diff --git a/src/Flarum/Core/Listeners/UserMetadataUpdater.php b/src/Flarum/Core/Listeners/UserMetadataUpdater.php
deleted file mode 100755
index ee54c2916..000000000
--- a/src/Flarum/Core/Listeners/UserMetadataUpdater.php
+++ /dev/null
@@ -1,70 +0,0 @@
-userRepo = $userRepo;
- }
-
- protected function updateRepliesCount($userId, $amount)
- {
- $user = $this->userRepo->find($userId);
-
- $user->posts_count += $amount;
-
- $this->userRepo->save($user);
- }
-
- protected function updateDiscussionsCount($userId, $amount)
- {
- $user = $this->userRepo->find($userId);
-
- $user->discussions_count += $amount;
-
- $this->userRepo->save($user);
- }
-
- public function whenReplyWasPosted(ReplyWasPosted $event)
- {
- $this->updateRepliesCount($event->post->user_id, 1);
- }
-
- public function whenPostWasDeleted(PostWasDeleted $event)
- {
- $this->updateRepliesCount($event->post->user_id, -1);
- }
-
- public function whenPostWasHidden(PostWasHidden $event)
- {
- $this->updateRepliesCount($event->post->user_id, -1);
- }
-
- public function whenPostWasRestored(PostWasRestored $event)
- {
- $this->updateRepliesCount($event->post->user_id, 1);
- }
-
- public function whenDiscussionWasStarted(DiscussionWasStarted $event)
- {
- $this->updateDiscussionsCount($event->discussion->start_user_id, 1);
- }
-
- public function whenDiscussionWasDeleted(DiscussionWasDeleted $event)
- {
- $this->updateDiscussionsCount($event->discussion->start_user_id, -1);
- }
-}
diff --git a/src/Flarum/Core/Permissions/Manager.php b/src/Flarum/Core/Permissions/Manager.php
deleted file mode 100755
index ce09184da..000000000
--- a/src/Flarum/Core/Permissions/Manager.php
+++ /dev/null
@@ -1,42 +0,0 @@
-permissions = $permissions;
- }
-
- public function getMap()
- {
- if (is_null($this->map)) {
- $permissions = $this->permissions->get();
- foreach ($permissions as $permission) {
- $this->map[$permission->entity.'.'.$permission->permission][] = $permission->grantee;
- }
- }
-
- return $this->map;
- }
-
- public function granted($user, $permission, $entity)
- {
- $grantees = $user->getGrantees();
-
- // If user has admin, then yes!
- if (in_array('group.1', $grantees)) {
- return true;
- }
-
- $permission = $entity.'.'.$permission;
-
- $map = $this->getMap();
- $mappedGrantees = isset($map[$permission]) ? $map[$permission] : [];
-
- return (bool) array_intersect($grantees, $mappedGrantees);
- }
-}
diff --git a/src/Flarum/Core/Permissions/Permission.php b/src/Flarum/Core/Permissions/Permission.php
deleted file mode 100644
index 2c2e3b4b9..000000000
--- a/src/Flarum/Core/Permissions/Permission.php
+++ /dev/null
@@ -1,7 +0,0 @@
-assertValid();
- $permission->save();
- }
-
- public function delete(Permission $permission)
- {
- $permission->delete();
- }
-}
diff --git a/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php b/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php
deleted file mode 100644
index 0493f86ac..000000000
--- a/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php
+++ /dev/null
@@ -1,33 +0,0 @@
-posts = $posts;
- }
-
- public function handle($command)
- {
- $user = $command->user;
- $post = $this->posts->findOrFail($command->postId, $user);
-
- $post->assertCan($user, 'delete');
-
- Event::fire('Flarum.Core.Posts.Commands.DeletePost.PostWillBeDeleted', [$post, $command]);
-
- $this->posts->delete($post);
- $this->dispatchEventsFor($post);
-
- return $post;
- }
-}
diff --git a/src/Flarum/Core/Posts/Commands/DeletePostValidator.php b/src/Flarum/Core/Posts/Commands/DeletePostValidator.php
deleted file mode 100644
index c3e9aa4c5..000000000
--- a/src/Flarum/Core/Posts/Commands/DeletePostValidator.php
+++ /dev/null
@@ -1,7 +0,0 @@
-number = ++$post->discussion->number_index;
- $post->discussion->save();
- });
- }
-
- public static function reply($discussionId, $content, $userId)
- {
- $post = new static;
-
- $post->content = $content;
- $post->content_html = static::formatContent($post->content);
- $post->time = time();
- $post->discussion_id = $discussionId;
- $post->user_id = $userId;
- $post->type = 'comment';
-
- $post->raise(new Events\ReplyWasPosted($post));
-
- return $post;
- }
-
- public function revise($content, $user)
- {
- if ($this->content === $content) {
- return;
- }
-
- $this->content = $content;
- $this->content_html = static::formatContent($this->content);
-
- $this->edit_time = time();
- $this->edit_user_id = $user->id;
-
- $this->raise(new Events\PostWasRevised($this));
- }
-
- public function hide($user)
- {
- if ($this->hide_time) {
- return;
- }
-
- $this->hide_time = time();
- $this->hide_user_id = $user->id;
-
- $this->raise(new Events\PostWasHidden($this));
- }
-
- public function restore($user)
- {
- if ($this->hide_time === null) {
- return;
- }
-
- $this->hide_time = null;
- $this->hide_user_id = null;
-
- $this->raise(new Events\PostWasRestored($this));
- }
-
- public function getContentHtmlAttribute($value)
- {
- if (! $value) {
- $this->content_html = $value = static::formatContent($this->content);
- $this->save();
- }
-
- return $value;
- }
-
- protected static function formatContent($content)
- {
- $formatter = App::make('flarum.formatter');
- return $formatter->format($content);
- }
-}
diff --git a/src/Flarum/Core/Posts/Post.php b/src/Flarum/Core/Posts/Post.php
deleted file mode 100755
index 33699b1c5..000000000
--- a/src/Flarum/Core/Posts/Post.php
+++ /dev/null
@@ -1,138 +0,0 @@
- 'required|integer',
- 'time' => 'required|date',
- 'content' => 'required',
- 'number' => 'integer',
- 'user_id' => 'integer',
- 'edit_time' => 'date',
- 'edit_user_id' => 'integer',
- 'hide_time' => 'date',
- 'hide_user_id' => 'integer',
- ];
-
- protected static $types = [];
-
- public static function boot()
- {
- parent::boot();
-
- static::grant(function ($grant, $user, $permission) {
- return app('flarum.permissions')->granted($user, $permission, 'post');
- });
-
- // Grant view access to a post only if the user can also view the
- // discussion which the post is in. Also, the if the post is hidden,
- // the user must have edit permissions too.
- static::grant('view', function ($grant, $user) {
- $grant->whereCan('view', 'discussion');
- });
-
- static::check('view', function ($check, $user) {
- $check->whereNull('hide_user_id')
- ->orWhereCan('edit');
- });
-
- // Allow a user to edit their own post, unless it has been hidden by
- // someone else.
- static::grant('edit', function ($grant, $user) {
- $grant->whereCan('editOwn')
- ->where('user_id', $user->id);
- });
-
- static::check('editOwn', function ($check, $user) {
- $check->whereNull('hide_user_id')
- ->orWhere('hide_user_id', $user->id);
- });
-
- static::deleted(function ($post) {
- $post->raise(new Events\PostWasDeleted($post));
- });
- }
-
- public function discussion()
- {
- return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id');
- }
-
- public function user()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
- }
-
- public function editUser()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'edit_user_id');
- }
-
- public function hideUser()
- {
- return $this->belongsTo('Flarum\Core\Users\User', 'hide_user_id');
- }
-
- public function getDates()
- {
- return ['time', 'edit_time', 'hide_time'];
- }
-
- // Terminates the query and returns an array of matching IDs.
- // Example usage: $discussion->posts()->ids();
- public function scopeIds($query)
- {
- return array_map('intval', $query->get(['id'])->fetch('id')->all());
- }
-
- public function scopeWhereCanView($query, $user = null)
- {
- if (is_null($user)) {
- $user = User::current();
- }
- return $this->scopeWhereCan($query, $user, 'view');
- }
-
- public function assertCan($user, $permission)
- {
- if (! $this->can($user, $permission)) {
- throw new PermissionDeniedException;
- }
- }
-
- public static function addType($type, $class)
- {
- static::$types[$type] = $class;
- }
-
- public function newFromBuilder($attributes = [])
- {
- if (!empty($attributes->type)) {
- $type = $attributes->type;
- if (isset(static::$types[$type])) {
- $class = static::$types[$type];
- if (class_exists($class)) {
- $instance = new $class;
- $instance->exists = true;
- $instance->setRawAttributes((array) $attributes, true);
- return $instance;
- }
- }
- }
-
- return parent::newFromBuilder($attributes);
- }
-}
diff --git a/src/Flarum/Core/Posts/PostRepository.php b/src/Flarum/Core/Posts/PostRepository.php
deleted file mode 100755
index 8718fa829..000000000
--- a/src/Flarum/Core/Posts/PostRepository.php
+++ /dev/null
@@ -1,65 +0,0 @@
-whereCanView($user);
- }
-
- return $query->findOrFail($id);
- }
-
- public function getIndexForNumber($discussionId, $number)
- {
- return Post::whereCanView()
- ->where('discussion_id', $discussionId)
- ->where('time', '<', function ($query) use ($discussionId, $number) {
- $query->select('time')
- ->from('posts')
- ->where('discussion_id', $discussionId)
- ->whereNotNull('number')
- ->orderByRaw('ABS(CAST(number AS SIGNED) - ?)', [$number])
- ->take(1);
- })
- ->count();
- }
-
- public function findByDiscussion($discussionId, $relations = [], $sortBy = 'time', $sortOrder = 'asc', $count = null, $start = 0)
- {
- return Post::with($relations)
- ->whereCanView()
- ->where('discussion_id', $discussionId)
- ->skip($start)
- ->take($count)
- ->orderBy($sortBy, $sortOrder)
- ->get();
- }
-
- public function findMany($ids, $relations = [])
- {
- return Post::with($relations)
- ->whereCanView()
- ->whereIn('id', $ids)
- ->get();
- }
-
- public function save(Post $post)
- {
- $post->assertValid();
- $post->save();
- }
-
- public function delete(Post $post)
- {
- $post->delete();
- }
-}
diff --git a/src/Flarum/Core/Search/FulltextSearchDriver.php b/src/Flarum/Core/Search/FulltextSearchDriver.php
deleted file mode 100644
index b85055bc4..000000000
--- a/src/Flarum/Core/Search/FulltextSearchDriver.php
+++ /dev/null
@@ -1,48 +0,0 @@
-table = $table;
- // inject db connection?
- // pass primary key name?
- }
-
- public function results(SearchCriteria $criteria)
- {
- $query = DB::table($this->table);
-
- $this->parseConditions($criteria->conditions, $query);
-
- return $query->get('id');
- }
-
- protected function parseConditions(ConditionCollection $conditions, Query $query)
- {
- foreach ($conditions as $condition)
- {
- if ($condition instanceof ConditionOr)
- {
- $query->orWhere(function($query)
- {
- $this->parseConditions($condition->conditions, $query);
- })
- }
- elseif ($condition instanceof ConditionComparison)
- {
- // etc
- }
- }
- }
-}
diff --git a/src/Flarum/Core/Search/SearchDriverInterface.php b/src/Flarum/Core/Search/SearchDriverInterface.php
deleted file mode 100644
index f257675c9..000000000
--- a/src/Flarum/Core/Search/SearchDriverInterface.php
+++ /dev/null
@@ -1,8 +0,0 @@
-client = $client;
- $this->index = $index;
- }
-
- public function results(SearchCriteria $criteria)
- {
- foreach ($query->conditions as $condition)
- {
- if ($condition instanceof ConditionOr)
- {
- // $search->setSelect("*, IF(code = 1 OR productid = 2, 1,0) AS filter");
- // $->setFilter('filter',array(1));
- }
- }
-
- // etc
- }
-}
diff --git a/src/Flarum/Core/Search/Tokenizer.php b/src/Flarum/Core/Search/Tokenizer.php
deleted file mode 100644
index e495e1d04..000000000
--- a/src/Flarum/Core/Search/Tokenizer.php
+++ /dev/null
@@ -1,17 +0,0 @@
-query = $query;
- }
-
- public function tokenize()
- {
- return $this->query ? [$this->query] : [];
- }
-
-}
diff --git a/src/Flarum/Core/Search/Tokens/AuthorToken.php b/src/Flarum/Core/Search/Tokens/AuthorToken.php
deleted file mode 100644
index 90494f2d1..000000000
--- a/src/Flarum/Core/Search/Tokens/AuthorToken.php
+++ /dev/null
@@ -1,25 +0,0 @@
-validator = $validator;
- }
-
- public function validate($command)
- {
- if (empty($command->user)) {
- throw new InvalidArgumentException('Empty argument [user] in command ['.get_class($command).']');
- }
-
- $validator = $this->validator->make(get_object_vars($command), $this->rules);
-
- $this->fireValidationEvent([$validator, $command]);
-
- if ($validator->fails()) {
- $this->throwValidationException($validator->errors(), $validator->getData());
- }
- }
-
- protected function fireValidationEvent(array $arguments)
- {
- Event::fire(str_replace('\\', '.', get_class($this)), $arguments);
- }
-
- protected function throwValidationException($errors, $input)
- {
- $exception = new ValidationFailureException;
- $exception->setErrors($errors)->setInput($input);
- throw $exception;
- }
-}
diff --git a/src/Flarum/Core/Support/Extensions/Extension.php b/src/Flarum/Core/Support/Extensions/Extension.php
deleted file mode 100755
index a30627850..000000000
--- a/src/Flarum/Core/Support/Extensions/Extension.php
+++ /dev/null
@@ -1,6 +0,0 @@
-truncate();
- }
-
- $this->call('Flarum\Core\Support\Seeders\ConfigTableSeeder');
- $this->call('Flarum\Core\Support\Seeders\UserTableSeeder');
- $this->call('Flarum\Core\Support\Seeders\DiscussionTableSeeder');
- }
-
-}
diff --git a/src/Flarum/Core/Support/Seeders/UserTableSeeder.php b/src/Flarum/Core/Support/Seeders/UserTableSeeder.php
deleted file mode 100644
index a15f012cd..000000000
--- a/src/Flarum/Core/Support/Seeders/UserTableSeeder.php
+++ /dev/null
@@ -1,77 +0,0 @@
- $group]);
- }
-
- for ($i = 0; $i < 100; $i++) {
- $user = User::create([
- 'username' => $faker->userName,
- 'email' => $faker->safeEmail,
- 'is_confirmed' => true,
- 'password' => 'password',
- 'join_time' => $faker->dateTimeThisYear
- ]);
-
- // Assign the users to the 'Member' group, and possibly some others.
- $user->groups()->attach(3);
- if (rand(1, 50) == 1) {
- $user->groups()->attach(4);
- }
- if (rand(1, 20) == 1) {
- $user->groups()->attach(5);
- }
- if (rand(1, 20) == 1) {
- $user->groups()->attach(1);
- }
- }
-
- // Set up the default permissions.
- $permissions = [
-
- // Guests can view the forum
- ['group.2' , 'forum' , 'view'],
- ['group.2' , 'forum' , 'register'],
-
- // Members can create and reply to discussions + edit their own stuff
- ['group.3' , 'forum' , 'startDiscussion'],
- ['group.3' , 'discussion' , 'editOwn'],
- ['group.3' , 'discussion' , 'reply'],
- ['group.3' , 'post' , 'editOwn'],
-
- // Moderators can edit + delete stuff and suspend users
- ['group.4' , 'discussion' , 'delete'],
- ['group.4' , 'discussion' , 'edit'],
- ['group.4' , 'post' , 'delete'],
- ['group.4' , 'post' , 'edit'],
- ['group.4' , 'user' , 'suspend'],
-
- ];
- foreach ($permissions as &$permission) {
- $permission = [
- 'grantee' => $permission[0],
- 'entity' => $permission[1],
- 'permission' => $permission[2]
- ];
- }
- DB::table('permissions')->insert($permissions);
- }
-}
diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php b/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php
deleted file mode 100644
index 62b8219e2..000000000
--- a/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php
+++ /dev/null
@@ -1,38 +0,0 @@
-userRepo = $userRepo;
- }
-
- public function handle($command)
- {
- $user = $this->userRepo->findOrFail($command->userId);
-
- $user->confirmEmail($command->token);
-
- // If the user hasn't yet had their account activated,
- if (! $user->join_time) {
- $user->activate();
- }
-
- Event::fire('Flarum.Core.Users.Commands.ConfirmEmail.UserWillBeSaved', [$user, $command]);
-
- $this->userRepo->save($user);
- $this->dispatchEventsFor($user);
-
- return $user;
- }
-}
diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php b/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php
deleted file mode 100644
index feac12843..000000000
--- a/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php
+++ /dev/null
@@ -1,11 +0,0 @@
-userRepo = $userRepo;
- }
-
- public function handle($command)
- {
- $user = $command->user;
- $userToDelete = $this->userRepo->findOrFail($command->userId, $user);
-
- $userToDelete->assertCan($user, 'delete');
-
- Event::fire('Flarum.Core.Users.Commands.DeleteUser.UserWillBeDeleted', [$userToDelete, $command]);
-
- $this->userRepo->delete($userToDelete);
- $this->dispatchEventsFor($userToDelete);
-
- return $userToDelete;
- }
-}
diff --git a/src/Flarum/Core/Users/Commands/DeleteUserValidator.php b/src/Flarum/Core/Users/Commands/DeleteUserValidator.php
deleted file mode 100644
index 7719a5468..000000000
--- a/src/Flarum/Core/Users/Commands/DeleteUserValidator.php
+++ /dev/null
@@ -1,7 +0,0 @@
-userRepo = $userRepo;
- }
-
- public function handle($command)
- {
- $user = $command->user;
- $userToEdit = $this->userRepo->findOrFail($command->userId, $user);
-
- $userToEdit->assertCan($user, 'edit');
-
- if (isset($command->username)) {
- $userToEdit->username = $command->username;
- }
-
- if (isset($command->email)) {
- $userToEdit->email = $command->email;
- }
-
- if (isset($command->password)) {
- $userToEdit->password = $command->password;
- }
-
- if (! empty($command->readTime)) {
- $userToEdit->read_time = time();
- }
-
- Event::fire('Flarum.Core.Users.Commands.EditUser.UserWillBeSaved', [$userToEdit, $command]);
-
- $this->userRepo->save($userToEdit);
- $this->dispatchEventsFor($userToEdit);
-
- return $userToEdit;
- }
-}
diff --git a/src/Flarum/Core/Users/Commands/EditUserValidator.php b/src/Flarum/Core/Users/Commands/EditUserValidator.php
deleted file mode 100644
index 358c98f90..000000000
--- a/src/Flarum/Core/Users/Commands/EditUserValidator.php
+++ /dev/null
@@ -1,7 +0,0 @@
-setAttribute($this->getKeyName(), 0);
-
- return parent::__construct($attributes);
- }
-
- public function getGroupsAttribute()
- {
- if ( ! isset($this->attributes['groups']))
- {
- $this->attributes['groups'] = $this->relations['groups'] = Group::where('id', Group::GUEST_ID)->get();
- }
-
- return $this->attributes['groups'];
- }
-
- public function guest()
- {
- return true;
- }
-
-}
diff --git a/src/Flarum/Core/Users/User.php b/src/Flarum/Core/Users/User.php
deleted file mode 100755
index 68c6637a4..000000000
--- a/src/Flarum/Core/Users/User.php
+++ /dev/null
@@ -1,261 +0,0 @@
- 'required|username|unique',
- 'email' => 'required|email|unique',
- 'password' => 'required',
- 'join_time' => 'date',
- 'last_seen_time' => 'date',
- 'discussions_count' => 'integer',
- 'posts_count' => 'integer',
- ];
-
- protected $table = 'users';
-
- protected $hidden = ['password'];
-
- public static function boot()
- {
- parent::boot();
-
- static::grant(function ($grant, $user, $permission) {
- return app('flarum.permissions')->granted($user, $permission, 'forum');
- });
-
- // Grant view access to a user if the user can view the forum.
- static::grant('view', function ($grant, $user) {
- return app('forum')->can($user, 'view');
- });
-
- // Allow a user to edit their own account.
- static::grant('edit', function ($grant, $user) {
- $grant->where('id', $user->id);
- });
-
- static::deleted(function ($user) {
- $user->raise(new Events\UserWasDeleted($user));
- });
- }
-
- public function setUsernameAttribute($username)
- {
- if ($username === $this->username) {
- return;
- }
-
- $this->attributes['username'] = $username;
- $this->raise(new Events\UserWasRenamed($this));
- }
-
- public function setEmailAttribute($email)
- {
- if ($email === $this->email) {
- return;
- }
-
- $this->attributes['email'] = $email;
- $this->raise(new Events\EmailWasChanged($this));
- }
-
- public function setPasswordAttribute($password)
- {
- $this->attributes['password'] = $password ? Hash::make($password) : null;
- $this->raise(new Events\PasswordWasChanged($this));
- }
-
- public function activate()
- {
- $this->join_time = time();
- $this->groups()->sync([3]);
-
- $this->raise(new Events\UserWasActivated($this));
- }
-
- public static function register($username, $email, $password)
- {
- $user = new static;
-
- $user->username = $username;
- $user->email = $email;
- $user->password = $password;
-
- $user->refreshConfirmationToken();
-
- $user->raise(new Events\UserWasRegistered($user));
-
- return $user;
- }
-
- public function validateConfirmationToken($token)
- {
- return ! $this->is_confirmed
- && $token
- && $this->confirmation_token === $token;
- }
-
- public function refreshConfirmationToken()
- {
- $this->is_confirmed = false;
- $this->confirmation_token = str_random(30);
- }
-
- public function confirmEmail($token)
- {
- if (! $this->validateConfirmationToken($token)) {
- throw new InvalidConfirmationTokenException;
- }
-
- $this->is_confirmed = true;
- $this->confirmation_token = null;
-
- $this->raise(new Events\EmailWasConfirmed($this));
- }
-
- public function getDates()
- {
- return ['join_time', 'last_seen_time', 'read_time'];
- }
-
- public function getAvatarUrlAttribute()
- {
- return '';
- }
-
- public static function current()
- {
- static $current = null;
-
- if (Auth::guest()) {
- if (! isset($current)) {
- $current = new Guest;
- }
- return $current;
- }
-
- return Auth::user();
- }
-
- public function getGrantees()
- {
- $grantees = ['group.2']; // guests
- if ($this->id) {
- $grantees[] = 'user.'.$this->id;
- }
- foreach ($this->groups as $group) {
- $grantees[] = 'group.'.$group->id;
- }
-
- /*
- TODO: maybe we should rethink how groups and permissions work a bit.
-
- Permissions table could be like:
- GRANTEE ENTITY PERMISSION
- all forum view
- all discussion view
- all post view
- all user view
- user discussion create
- user discussion reply
- group.1 forum administrate
- group.1 post delete
- etc
-
- sit on it. what about for suspended users? we could hook in and remove the 'user' grantee?
- */
-
- return $grantees;
- }
-
- public function permission($permission, $user = null)
- {
- if (is_null($user)) {
- $user = User::current();
- }
- return $this->can($user, $permission);
- }
-
- public function scopePermission($query, $permission, $user = null)
- {
- if (is_null($user)) {
- $user = User::current();
- }
- return $this->scopeWhereCan($query, $user, $permission);
- }
-
- public function scopeWhereCanView($query, $user = null)
- {
- return $this->scopePermission($query, 'view', $user);
- }
-
- public function assertCan($user, $permission)
- {
- if (! $this->can($user, $permission)) {
- throw new PermissionDeniedException;
- }
- }
-
- // public function granted($permission, $scope)
- // {
- // return isset($this->permissions[$scope]) && in_array($permission, $this->permissions[$scope]);
- // }
-
- // public function mustBeAbleTo($permission, $scope = 'forum', $entity = null)
- // {
- // if (! $this->can($permission, $scope, $entity)) {
- // throw new PermissionDeniedException;
- // }
- // }
-
- public function admin()
- {
- return $this->can('administrate');
- }
-
- public function isAdmin()
- {
- return $this->groups->contains(1);
- }
-
- public function guest()
- {
- return false;
- }
-
- public function groups()
- {
- return $this->belongsToMany('Flarum\Core\Groups\Group', 'users_groups');
- }
-
- public function activity()
- {
- return $this->hasMany('Flarum\Core\Activity\Activity');
- }
-
- public function setRememberToken($value)
- {
- return;
- }
-}
diff --git a/src/Flarum/Core/Users/UserFinder.php b/src/Flarum/Core/Users/UserFinder.php
deleted file mode 100644
index 3672ea579..000000000
--- a/src/Flarum/Core/Users/UserFinder.php
+++ /dev/null
@@ -1,167 +0,0 @@
- ['username', 'asc'],
- 'posts' => ['count_posts', 'desc'],
- 'discussions' => ['count_discussions', 'desc'],
- 'last_active' => ['last_action_time', 'desc'],
- 'created' => ['join_time', 'asc']
- ];
-
- protected $order;
-
- protected $key;
-
- protected $count;
-
- protected $areMoreResults;
-
- public function __construct($user = null, $tokens = null, $sort = null, $order = '', $key = null)
- {
- $this->user = $user;
- $this->tokens = $tokens;
- $this->sort = $sort;
- $this->order = $order;
- $this->key = $key;
- }
-
- public function getUser()
- {
- return $this->user;
- }
-
- public function setUser($user)
- {
- $this->user = $user;
- }
-
- public function getTokens()
- {
- return $this->tokens;
- }
-
- public function setTokens($tokens)
- {
- $this->tokens = $tokens;
- }
-
- public function setQuery($query)
- {
- $tokenizer = new Tokenizer($query);
- $this->setTokens($tokenizer->tokenize());
- }
-
- public function getSort()
- {
- return $this->sort;
- }
-
- public function setSort($sort)
- {
- $this->sort = $sort;
- }
-
- public function getOrder()
- {
- return $this->order;
- }
-
- public function setOrder($order)
- {
- $this->order = $order;
- }
-
- public function getKey()
- {
- return $this->key;
- }
-
- public function setKey($key)
- {
- $this->key = $key;
- }
-
- protected function getCacheKey()
- {
- return 'users.'.$this->key;
- }
-
- public function getCount()
- {
- return $this->count;
- }
-
- public function areMoreResults()
- {
- return $this->areMoreResults;
- }
-
- public function results($count = null, $start = 0)
- {
- $ids = null;
- $query = User::whereCan($this->user, 'view');
-
- // not sure if we need any of this stuff - especially ID filters?
-
- // if ($this->key and Cache::has($key = $this->getCacheKey()))
- // {
- // $ids = Cache::get($key);
- // }
- // elseif (count($this->tokens))
- // {
- // // parse the tokens.
- // // run ID filters.
- // /*
- // for fulltext token:
- // if ( ! $this->sort) $this->sort = 'relevance';
- // */
- // if ( ! is_null($ids))
- // {
- // $this->key = str_random();
- // }
-
- // // run other tokens
- // // $discussions->where('');
- // }
-
- // if ( ! is_null($ids))
- // {
- // Cache::put($this->getCacheKey(), $ids, 10); // recache
- // $this->count = count($ids);
-
- // if ( ! $ids) return false;
- // $query->whereIn('id', $ids);
- // }
-
- $this->count = (int) $query->count();
-
- if (empty($this->sort)) {
- reset($this->sortMap);
- $this->sort = key($this->sortMap);
- }
- if (! empty($this->sortMap[$this->sort])) {
- list($column, $order) = $this->sortMap[$this->sort];
- $query->orderBy($column, $this->order ?: $order);
- }
-
- if ($start > 0) {
- $query->skip($count);
- }
- if ($count > 0) {
- $query->take($count);
- }
- return $query->get();
- }
-}
diff --git a/src/Flarum/Core/Users/UserRepository.php b/src/Flarum/Core/Users/UserRepository.php
deleted file mode 100755
index 111fe9bd8..000000000
--- a/src/Flarum/Core/Users/UserRepository.php
+++ /dev/null
@@ -1,48 +0,0 @@
-whereCanView($user);
- }
-
- return $query->findOrFail($id);
- }
-
- public function save(User $user)
- {
- $user->assertValid();
- $user->save();
- }
-
- public function delete(User $user)
- {
- $user->delete();
-
- // do something with their posts/discussions?
- }
-
- public function attachGroup(User $user, $groupId)
- {
- $user->groups()->attach($groupId);
- }
-
- public function detachGroup(User $user, $groupId)
- {
- $user->groups()->detach($groupId);
- }
-
- public function syncGroups(User $user, $groupIds)
- {
- $user->groups()->sync($groupIds);
- }
-}
diff --git a/src/Flarum/Core/Users/UsernameValidator.php b/src/Flarum/Core/Users/UsernameValidator.php
deleted file mode 100644
index 3681dec43..000000000
--- a/src/Flarum/Core/Users/UsernameValidator.php
+++ /dev/null
@@ -1,10 +0,0 @@
-package('flarum/web', 'flarum.web');
-
-
- // Shouldn't do all this asset stuff in boot, because then it gets called on API requests
- $assetManager = $this->app['flarum.web.assetManager'];
-
- $assetManager->add('flarum/core', [
- 'assets/vendor.css',
- 'assets/flarum.css',
- 'assets/vendor.js',
- 'assets/flarum.js'
- ]);
-
- // publish assets in dev environment
- $publisher = new AssetPublisher($this->app['files'], $this->app['path.public']);
- $publisher->setPackagePath(base_path().'/'.(strpos($this->guessPackagePath(), 'workbench') === false ? 'vendor' : 'workbench'));
- $publisher->publishPackage('flarum/core');
-
- include __DIR__.'/../../routes.php';
- }
-
- /**
- * Register the service provider.
- *
- * @return void
- */
- public function register()
- {
- $this->app['flarum.web.assetManager'] = $this->app->share(function($app)
- {
- return new AssetManager($app['files'], $app['path.public']);
- });
- }
-
- /**
- * Get the services provided by the provider.
- *
- * @return array
- */
- public function provides()
- {
- return array();
- }
-
-}
diff --git a/src/Web/Actions/Action.php b/src/Web/Actions/Action.php
new file mode 100644
index 000000000..ac57010d7
--- /dev/null
+++ b/src/Web/Actions/Action.php
@@ -0,0 +1,27 @@
+actor = $actor;
+ }
+
+ protected function callAction($class, $params = [])
+ {
+ $action = app($class);
+ return $action->call($params);
+ }
+
+ protected function dispatch($command, $params)
+ {
+ $this->event(new CommandWillBeDispatched($command, $params));
+ return $this->bus->dispatch($command);
+ }
+}
diff --git a/src/Web/Actions/ConfirmAction.php b/src/Web/Actions/ConfirmAction.php
new file mode 100644
index 000000000..b85a87161
--- /dev/null
+++ b/src/Web/Actions/ConfirmAction.php
@@ -0,0 +1,26 @@
+dispatch($command);
+ } catch (InvalidConfirmationTokenException $e) {
+ return 'Invalid confirmation token';
+ }
+
+ $token = AccessToken::generate($user->id);
+ $token->save();
+
+ return Redirect::to('/')
+ ->withCookie($this->makeRememberCookie($token->id))
+ ->with('alert', ['type' => 'success', 'message' => 'Thanks for confirming!']);
+ }
+}
diff --git a/src/Web/Actions/IndexAction.php b/src/Web/Actions/IndexAction.php
new file mode 100644
index 000000000..ab0e0b0d6
--- /dev/null
+++ b/src/Web/Actions/IndexAction.php
@@ -0,0 +1,50 @@
+ 'flarum',
+ 'environment' => 'production',
+ 'baseURL' => '/',
+ 'apiURL' => '/api',
+ 'locationType' => 'hash',
+ 'EmberENV' => [],
+ 'APP' => []
+ ];
+ $data = [];
+ $session = [];
+ $alert = Session::get('alert');
+
+ if (($user = $this->actor->getUser()) && $user->exists) {
+ $session = [
+ 'userId' => $user->id,
+ 'token' => Cookie::get('flarum_remember')
+ ];
+
+ $response = $this->callAction('Flarum\Api\Actions\Users\ShowAction', ['id' => $user->id]);
+
+ $data['users'] = [$response->getData()->users];
+ $data['groups'] = $response->getData()->linked->groups;
+ }
+
+
+ return View::make('flarum.web::ember')
+ ->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'))
+ ->with('styles', app('flarum.web.assetManager')->styles())
+ ->with('scripts', app('flarum.web.assetManager')->scripts())
+ ->with('config', $config)
+ ->with('content', '')
+ ->with('data', $data)
+ ->with('session', $session)
+ ->with('alert', $alert);
+ }
+}
diff --git a/src/Web/Actions/LoginAction.php b/src/Web/Actions/LoginAction.php
new file mode 100644
index 000000000..665e7e0c3
--- /dev/null
+++ b/src/Web/Actions/LoginAction.php
@@ -0,0 +1,31 @@
+users = $users;
+ }
+
+ public function handle(Request $request, $routeParams = [])
+ {
+ $response = $this->callAction('Flarum\Api\Actions\TokenAction', $request->only('identification', 'password'));
+
+ $data = $response->getData();
+ if (! empty($data->token)) {
+ $response->withCookie($this->makeRememberCookie($data->token));
+
+ event(new UserLoggedIn($this->users->findOrFail($data->userId), $data->token));
+ }
+
+ return $response;
+ }
+}
diff --git a/src/Web/Actions/LogoutAction.php b/src/Web/Actions/LogoutAction.php
new file mode 100644
index 000000000..a9775cc40
--- /dev/null
+++ b/src/Web/Actions/LogoutAction.php
@@ -0,0 +1,20 @@
+accessTokens()->delete();
+
+ $this->event(new UserLoggedOut(Auth::user()));
+
+ return Redirect::to('')->withCookie($this->makeForgetCookie());
+ }
+
+ public function makeForgetCookie()
+ {
+ return Cookie::forget('flarum_remember');
+ }
+}
diff --git a/src/Web/Actions/MakesRememberCookie.php b/src/Web/Actions/MakesRememberCookie.php
new file mode 100644
index 000000000..198d9e710
--- /dev/null
+++ b/src/Web/Actions/MakesRememberCookie.php
@@ -0,0 +1,11 @@
+files = $files;
- $this->publishPath = $publishPath;
- }
-
- protected function getPackageDir($package)
- {
- // TODO: First search vendor, then search workbench.
- // TODO: inject path.base
- return app('path.base').'/workbench/'.$package.'/dist/';
- }
-
- public function add($package, $files)
- {
- $packageDir = $this->getPackageDir($package);
-
- foreach ((array) $files as $file)
- {
- $ext = pathinfo($file, PATHINFO_EXTENSION);
- switch ($ext)
- {
- case 'css':
- $this->css[] = 'packages/'.$package.'/'.$file;
- break;
-
- case 'js':
- $this->js[] = 'packages/'.$package.'/'.$file;
- break;
- }
- }
- }
-
- public function getCSSFiles()
- {
- // TODO: in a production environment, we would concat+minify all the CSS files together
- // (would probably need to check filemtimes etc.)
-
- // But in a development environment, we just copy all the css files to the public directory.
- // foreach ($this->css as $file)
- // {
-
- // }
-
- return $this->css;
- }
-
- public function getJSFiles()
- {
- return $this->js;
- }
-
- public function styles()
- {
- $output = '';
-
- foreach ($this->getCSSFiles() as $file)
- {
- $output .= ''.PHP_EOL;
- }
-
- return $output;
- }
-
- public function scripts()
- {
- $output = '';
-
- foreach ($this->getJSFiles() as $file)
- {
- $output .= ''.PHP_EOL;
- }
-
- return $output;
- }
-
-}
+files = $files;
+ $this->publishPath = $publishPath;
+ }
+
+ public function add($files)
+ {
+ foreach ((array) $files as $file)
+ {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ switch ($ext)
+ {
+ case 'css':
+ $this->css[] = $file;
+ break;
+
+ case 'js':
+ $this->js[] = $file;
+ break;
+ }
+ }
+ }
+
+ protected function getAssetDirectory()
+ {
+ $dir = $this->publishPath.'/flarum';
+ if (! $this->files->isDirectory($dir)) {
+ $this->files->makeDirectory($dir);
+ }
+ return $dir;
+ }
+
+ public function getCSSFiles()
+ {
+ // TODO: in a production environment, we would concat+minify all the CSS files together
+ // (would probably need to check filemtimes etc.)
+
+ // But in a development environment, we just copy all the css files to the public directory.
+ $css = [];
+ $dir = $this->getAssetDirectory();
+ foreach ($this->css as $file)
+ {
+ $basename = pathinfo($file, PATHINFO_BASENAME);
+ $target = $dir.'/'.$basename;
+ $this->files->copy($file, $target);
+ $css[] = str_replace($this->publishPath, '', $target);
+ }
+
+ return $css;
+ }
+
+ public function getJSFiles()
+ {
+ $js = [];
+ $dir = $this->getAssetDirectory();
+ foreach ($this->js as $file)
+ {
+ $basename = pathinfo($file, PATHINFO_BASENAME);
+ $target = $dir.'/'.$basename;
+ $this->files->copy($file, $target);
+ $js[] = str_replace($this->publishPath, '', $target);
+ }
+
+ return $js;
+ }
+
+ public function styles()
+ {
+ $output = '';
+
+ foreach ($this->getCSSFiles() as $file)
+ {
+ $output .= ''.PHP_EOL;
+ }
+
+ return $output;
+ }
+
+ public function scripts()
+ {
+ $output = '';
+
+ foreach ($this->getJSFiles() as $file)
+ {
+ $output .= ''.PHP_EOL;
+ }
+
+ return $output;
+ }
+
+}
diff --git a/src/Web/Events/CommandWillBeDispatched.php b/src/Web/Events/CommandWillBeDispatched.php
new file mode 100644
index 000000000..637f6da65
--- /dev/null
+++ b/src/Web/Events/CommandWillBeDispatched.php
@@ -0,0 +1,14 @@
+command = $command;
+ $this->params = $params;
+ }
+}
diff --git a/src/Web/Events/UserLoggedIn.php b/src/Web/Events/UserLoggedIn.php
new file mode 100644
index 000000000..084ee3356
--- /dev/null
+++ b/src/Web/Events/UserLoggedIn.php
@@ -0,0 +1,16 @@
+user = $user;
+ $this->token = $token;
+ }
+}
diff --git a/src/Web/Events/UserLoggedOut.php b/src/Web/Events/UserLoggedOut.php
new file mode 100644
index 000000000..2488bd33b
--- /dev/null
+++ b/src/Web/Events/UserLoggedOut.php
@@ -0,0 +1,13 @@
+user = $user;
+ }
+}
diff --git a/src/Web/Middleware/LoginWithCookieMiddleware.php b/src/Web/Middleware/LoginWithCookieMiddleware.php
new file mode 100644
index 000000000..f3d2379dd
--- /dev/null
+++ b/src/Web/Middleware/LoginWithCookieMiddleware.php
@@ -0,0 +1,26 @@
+actor = $actor;
+ }
+
+ public function handle($request, Closure $next)
+ {
+ if (($token = $request->cookie('flarum_remember')) &&
+ ($accessToken = AccessToken::where('id', $token)->first())) {
+ $this->actor->setUser($accessToken->user);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/Web/WebServiceProvider.php b/src/Web/WebServiceProvider.php
new file mode 100644
index 000000000..a4a03ca20
--- /dev/null
+++ b/src/Web/WebServiceProvider.php
@@ -0,0 +1,44 @@
+loadViewsFrom(__DIR__.'/../../views', 'flarum.web');
+
+ // Shouldn't do all this asset stuff in boot, because then it gets called on API requests
+ $assetManager = $this->app['flarum.web.assetManager'];
+
+ $assetManager->add([
+ __DIR__.'/../../public/assets/flarum.css',
+ __DIR__.'/../../public/assets/vendor.js',
+ __DIR__.'/../../public/assets/flarum.js'
+ ]);
+
+ include __DIR__.'/routes.php';
+
+ $this->publishes([
+ __DIR__.'/../../public/font-awesome' => public_path('font-awesome')
+ ]);
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app['flarum.web.assetManager'] = $this->app->share(function($app)
+ {
+ return new AssetManager($app['files'], $app['path.public']);
+ });
+ }
+}
diff --git a/src/Web/routes.php b/src/Web/routes.php
new file mode 100755
index 000000000..b21f6aac9
--- /dev/null
+++ b/src/Web/routes.php
@@ -0,0 +1,34 @@
+app->make($class);
+ $request = $this->app['request']->instance();
+ $parameters = $this->app['router']->current()->parameters();
+ return $action->handle($request, $parameters);
+ };
+};
+
+Route::group(['middleware' => 'Flarum\Web\Middleware\LoginWithCookieMiddleware'], function () use ($action) {
+
+ Route::get('/', [
+ 'as' => 'flarum.index',
+ 'uses' => $action('Flarum\Web\Actions\IndexAction')
+ ]);
+
+ Route::get('logout', [
+ 'as' => 'flarum.logout',
+ 'uses' => $action('Flarum\Web\Actions\LogoutAction')
+ ]);
+
+});
+
+Route::post('login', [
+ 'as' => 'flarum.login',
+ 'uses' => $action('Flarum\Web\Actions\LoginAction')
+]);
+
+Route::get('confirm/{id}/{token}', [
+ 'as' => 'flarum.confirm',
+ 'uses' => $action('Flarum\Web\Actions\ConfirmAction')
+]);
diff --git a/src/config/config.php b/src/config/config.php
deleted file mode 100755
index d0b19a548..000000000
--- a/src/config/config.php
+++ /dev/null
@@ -1,13 +0,0 @@
- true,
- 'title' => 'Flarum Prototype Forum',
-
- 'route_rules' => array(
- // 'prefix' => 'blog',
- // 'domain' => 'blog.site.com'
- )
-
-);
diff --git a/src/lang/.gitkeep b/src/lang/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/lang/en/reminders.php b/src/lang/en/reminders.php
deleted file mode 100755
index 4a9f17661..000000000
--- a/src/lang/en/reminders.php
+++ /dev/null
@@ -1,22 +0,0 @@
- "Passwords must be six characters and match the confirmation.",
-
- "user" => "We can't find a user with that e-mail address.",
-
- "token" => "This password reset token is invalid.",
-
-);
\ No newline at end of file
diff --git a/src/lang/en/validation.php b/src/lang/en/validation.php
deleted file mode 100755
index 85a62aa50..000000000
--- a/src/lang/en/validation.php
+++ /dev/null
@@ -1,93 +0,0 @@
- "The :attribute must be accepted.",
- "active_url" => "The :attribute is not a valid URL.",
- "after" => "The :attribute must be a date after :date.",
- "alpha" => "The :attribute may only contain letters.",
- "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.",
- "alpha_num" => "The :attribute may only contain letters and numbers.",
- "before" => "The :attribute must be a date before :date.",
- "between" => array(
- "numeric" => "The :attribute must be between :min - :max.",
- "file" => "The :attribute must be between :min - :max kilobytes.",
- "string" => "The :attribute must be between :min - :max characters.",
- ),
- "confirmed" => "The :attribute confirmation does not match.",
- "date" => "The :attribute is not a valid 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.",
- "digits_between" => "The :attribute must be between :min and :max digits.",
- "email" => "The :attribute format is invalid.",
- "exists" => "The selected :attribute is invalid.",
- "image" => "The :attribute must be an image.",
- "in" => "The selected :attribute is invalid.",
- "integer" => "The :attribute must be an integer.",
- "ip" => "The :attribute must be a valid IP address.",
- "max" => array(
- "numeric" => "The :attribute may not be greater than :max.",
- "file" => "The :attribute may not be greater than :max kilobytes.",
- "string" => "The :attribute may not be greater than :max characters.",
- ),
- "mimes" => "The :attribute must be a file of type: :values.",
- "min" => array(
- "numeric" => "The :attribute must be at least :min.",
- "file" => "The :attribute must be at least :min kilobytes.",
- "string" => "The :attribute must be at least :min characters.",
- ),
- "not_in" => "The selected :attribute is invalid.",
- "numeric" => "The :attribute must be a number.",
- "regex" => "The :attribute format is invalid.",
- "required" => "The :attribute field is required.",
- "required_if" => "The :attribute field is required when :other is :value.",
- "required_with" => "The :attribute field is required when :values is present.",
- "required_without" => "The :attribute field is required when :values is not present.",
- "same" => "The :attribute and :other must match.",
- "size" => array(
- "numeric" => "The :attribute must be :size.",
- "file" => "The :attribute must be :size kilobytes.",
- "string" => "The :attribute must be :size characters.",
- ),
- "unique" => "The :attribute has already been taken.",
- "url" => "The :attribute format is invalid.",
-
- /*
- |--------------------------------------------------------------------------
- | Custom Validation Language Lines
- |--------------------------------------------------------------------------
- |
- | Here you may specify custom validation messages for attributes using the
- | convention "attribute.rule" to name the lines. This makes it quick to
- | specify a specific custom language line for a given attribute rule.
- |
- */
-
- 'custom' => array(),
-
- /*
- |--------------------------------------------------------------------------
- | Custom Validation Attributes
- |--------------------------------------------------------------------------
- |
- | The following language lines are used to swap attribute place-holders
- | with something more reader friendly such as E-Mail Address instead
- | of "email". This simply helps us make messages a little cleaner.
- |
- */
-
- 'attributes' => array(),
-
-);
diff --git a/src/migrations/.gitkeep b/src/migrations/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/routes.php b/src/routes.php
deleted file mode 100755
index 961be7629..000000000
--- a/src/routes.php
+++ /dev/null
@@ -1,13 +0,0 @@
-with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'));
-});
-
-Route::get('confirm/{id}/{token}', ['as' => 'flarum.confirm', function ($userId, $token) {
- $command = new Flarum\Core\Users\Commands\ConfirmEmailCommand($userId, $token);
-
- $commandBus = App::make('Laracasts\Commander\CommandBus');
- $commandBus->execute($command);
-}]);
diff --git a/src/views/.gitkeep b/src/views/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/views/emails/confirm.blade.php b/src/views/emails/confirm.blade.php
deleted file mode 100644
index 6bd5a7fc2..000000000
--- a/src/views/emails/confirm.blade.php
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
- Welcome, {{ $user->username }}
-
-
- To confirm your email, click here: {{ $url }}
-
-
-
diff --git a/src/views/index.blade.php b/src/views/index.blade.php
deleted file mode 100644
index a3a0e399f..000000000
--- a/src/views/index.blade.php
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
- {{ $title }}
-
-
-
-
-
-
-
-
-
- {{ app('flarum.web.assetManager')->styles() }}
-
-
-
- {{ app('flarum.web.assetManager')->scripts() }}
-
-
-
diff --git a/tests/_support/ApiHelper.php b/tests/_support/ApiHelper.php
index 1381f6d8a..1cf475ac2 100644
--- a/tests/_support/ApiHelper.php
+++ b/tests/_support/ApiHelper.php
@@ -2,19 +2,23 @@
namespace Codeception\Module;
use Laracasts\TestDummy\Factory;
-use Auth;
-use DB;
class ApiHelper extends \Codeception\Module
{
public function haveAnAccount($data = [])
{
- return Factory::create('Flarum\Core\Users\User', $data);
+ $user = Factory::create('Flarum\Core\Models\User', $data);
+ $user->activate();
+
+ return $user;
}
public function login($identification, $password)
{
- $this->getModule('REST')->sendPOST('/api/auth/login', ['identification' => $identification, 'password' => $password]);
+ $this->getModule('REST')->sendPOST('/api/token', [
+ 'identification' => $identification,
+ 'password' => $password
+ ]);
$response = json_decode($this->getModule('REST')->grabResponse(), true);
if ($response && is_array($response) && isset($response['token'])) {
@@ -27,8 +31,8 @@ class ApiHelper extends \Codeception\Module
public function amAuthenticated()
{
$user = $this->haveAnAccount();
- $user->groups()->attach(3); // Add member group
- Auth::onceUsingId($user->id);
+ $token = $this->login($user->email, 'password');
+ $this->getModule('REST')->haveHttpHeader('Authorization', 'Token '.$token);
return $user;
}
diff --git a/tests/acceptance/AcceptanceTester.php b/tests/acceptance/AcceptanceTester.php
index d7f83ad13..9045754f8 100644
--- a/tests/acceptance/AcceptanceTester.php
+++ b/tests/acceptance/AcceptanceTester.php
@@ -1,4 +1,4 @@
-
* ```
*
- * @param $cookie
- * @param $value
+ * @param $name
+ * @param $val
+ * @param array $params
+ * @internal param $cookie
+ * @internal param $value
*
* @return mixed
* @see \Codeception\Lib\InnerBrowser::setCookie()
*/
- public function setCookie($name, $val) {
+ public function setCookie($name, $val, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('setCookie', func_get_args()));
}
@@ -1139,13 +1143,15 @@ class AcceptanceTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Grabs a cookie value.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::grabCookie()
*/
- public function grabCookie($name) {
+ public function grabCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabCookie', func_get_args()));
}
@@ -1154,6 +1160,7 @@ class AcceptanceTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args()));
}
@@ -1195,27 +1203,31 @@ class AcceptanceTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function cantSeeCookie($name) {
+ public function cantSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function dontSeeCookie($name) {
+ public function dontSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args()));
}
@@ -1224,13 +1236,15 @@ class AcceptanceTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Unsets cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::resetCookie()
*/
- public function resetCookie($name) {
+ public function resetCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('resetCookie', func_get_args()));
}
diff --git a/tests/api.suite.yml b/tests/api.suite.yml
index f610bdb76..867353b3f 100644
--- a/tests/api.suite.yml
+++ b/tests/api.suite.yml
@@ -1,3 +1,7 @@
class_name: ApiTester
modules:
- enabled: [Laravel4, REST, Asserts, ApiHelper]
+ enabled: [Laravel5, REST, Asserts, ApiHelper]
+ config:
+ Laravel5:
+ root: ../../
+ environment_file: .env.testing
diff --git a/tests/api/ApiTester.php b/tests/api/ApiTester.php
index 61058172a..9762b23ee 100644
--- a/tests/api/ApiTester.php
+++ b/tests/api/ApiTester.php
@@ -1,11 +1,11 @@
-scenario->runStep(new \Codeception\Step\Action('haveEnabledFilters', func_get_args()));
- }
-
-
- /**
- * [!] Method is generated. Documentation taken from corresponding module.
+ * Provides access the Laravel application object.
*
- * Disable Laravel filters for next requests.
- * @see \Codeception\Module\Laravel4::haveDisabledFilters()
+ * @return \Illuminate\Foundation\Application
+ * @see \Codeception\Module\Laravel5::getApplication()
*/
- public function haveDisabledFilters() {
- return $this->scenario->runStep(new \Codeception\Step\Action('haveDisabledFilters', func_get_args()));
+ public function getApplication() {
+ return $this->scenario->runStep(new \Codeception\Step\Action('getApplication', func_get_args()));
}
@@ -63,7 +54,7 @@ class ApiTester extends \Codeception\Actor
*
* @param $route
* @param array $params
- * @see \Codeception\Module\Laravel4::amOnRoute()
+ * @see \Codeception\Module\Laravel5::amOnRoute()
*/
public function amOnRoute($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amOnRoute', func_get_args()));
@@ -83,7 +74,7 @@ class ApiTester extends \Codeception\Actor
*
* @param $action
* @param array $params
- * @see \Codeception\Module\Laravel4::amOnAction()
+ * @see \Codeception\Module\Laravel5::amOnAction()
*/
public function amOnAction($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amOnAction', func_get_args()));
@@ -103,7 +94,7 @@ class ApiTester extends \Codeception\Actor
* @param $route
* @param array $params
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeCurrentRouteIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentRouteIs()
*/
public function canSeeCurrentRouteIs($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentRouteIs', func_get_args()));
@@ -120,7 +111,7 @@ class ApiTester extends \Codeception\Actor
* ```
* @param $route
* @param array $params
- * @see \Codeception\Module\Laravel4::seeCurrentRouteIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentRouteIs()
*/
public function seeCurrentRouteIs($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentRouteIs', func_get_args()));
@@ -141,7 +132,7 @@ class ApiTester extends \Codeception\Actor
* @param $action
* @param array $params
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeCurrentActionIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentActionIs()
*/
public function canSeeCurrentActionIs($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentActionIs', func_get_args()));
@@ -159,7 +150,7 @@ class ApiTester extends \Codeception\Actor
*
* @param $action
* @param array $params
- * @see \Codeception\Module\Laravel4::seeCurrentActionIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentActionIs()
*/
public function seeCurrentActionIs($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentActionIs', func_get_args()));
@@ -175,7 +166,7 @@ class ApiTester extends \Codeception\Actor
* @param mixed $value
* @return void
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeInSession()
+ * @see \Codeception\Module\Laravel5::seeInSession()
*/
public function canSeeInSession($key, $value = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInSession', func_get_args()));
@@ -188,7 +179,7 @@ class ApiTester extends \Codeception\Actor
* @param string|array $key
* @param mixed $value
* @return void
- * @see \Codeception\Module\Laravel4::seeInSession()
+ * @see \Codeception\Module\Laravel5::seeInSession()
*/
public function seeInSession($key, $value = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInSession', func_get_args()));
@@ -203,7 +194,7 @@ class ApiTester extends \Codeception\Actor
* @param array $bindings
* @return void
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionHasValues()
+ * @see \Codeception\Module\Laravel5::seeSessionHasValues()
*/
public function canSeeSessionHasValues($bindings) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionHasValues', func_get_args()));
@@ -215,7 +206,7 @@ class ApiTester extends \Codeception\Actor
*
* @param array $bindings
* @return void
- * @see \Codeception\Module\Laravel4::seeSessionHasValues()
+ * @see \Codeception\Module\Laravel5::seeSessionHasValues()
*/
public function seeSessionHasValues($bindings) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionHasValues', func_get_args()));
@@ -225,74 +216,121 @@ class ApiTester extends \Codeception\Actor
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that Session has error messages
- * The seeSessionHasValues cannot be used, as Message bag Object is returned by Laravel4
+ * Assert that the form errors are bound to the View.
*
- * Useful for validation messages and generally messages array
- * e.g.
- * return `Redirect::to('register')->withErrors($validator);`
- *
- * Example of Usage
- *
- * ``` php
- * seeSessionErrorMessage(array('username'=>'Invalid Username'));
- * ?>
- * ```
- * @param array $bindings
+ * @return bool
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionErrorMessage()
+ * @see \Codeception\Module\Laravel5::seeFormHasErrors()
*/
- public function canSeeSessionErrorMessage($bindings) {
- return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionErrorMessage', func_get_args()));
+ public function canSeeFormHasErrors() {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormHasErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that Session has error messages
- * The seeSessionHasValues cannot be used, as Message bag Object is returned by Laravel4
+ * Assert that the form errors are bound to the View.
*
- * Useful for validation messages and generally messages array
- * e.g.
- * return `Redirect::to('register')->withErrors($validator);`
- *
- * Example of Usage
- *
- * ``` php
- * seeSessionErrorMessage(array('username'=>'Invalid Username'));
- * ?>
- * ```
- * @param array $bindings
- * @see \Codeception\Module\Laravel4::seeSessionErrorMessage()
+ * @return bool
+ * @see \Codeception\Module\Laravel5::seeFormHasErrors()
*/
- public function seeSessionErrorMessage($bindings) {
- return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionErrorMessage', func_get_args()));
+ public function seeFormHasErrors() {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormHasErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that the session has errors bound.
+ * Assert that specific form error messages are set in the view.
*
- * @return bool
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessages(array('username'=>'Invalid Username'));
+ * ?>
+ * ```
+ * @param array $bindings
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionHasErrors()
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessages()
*/
- public function canSeeSessionHasErrors() {
- return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionHasErrors', func_get_args()));
+ public function canSeeFormErrorMessages($bindings) {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormErrorMessages', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that the session has errors bound.
+ * Assert that specific form error messages are set in the view.
*
- * @return bool
- * @see \Codeception\Module\Laravel4::seeSessionHasErrors()
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessages(array('username'=>'Invalid Username'));
+ * ?>
+ * ```
+ * @param array $bindings
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessages()
*/
- public function seeSessionHasErrors() {
- return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionHasErrors', func_get_args()));
+ public function seeFormErrorMessages($bindings) {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormErrorMessages', func_get_args()));
+ }
+
+
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Assert that specific form error message is set in the view.
+ *
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessage('username', 'Invalid Username');
+ * ?>
+ * ```
+ * @param string $key
+ * @param string $errorMessage
+ * Conditional Assertion: Test won't be stopped on fail
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessage()
+ */
+ public function canSeeFormErrorMessage($key, $errorMessage) {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormErrorMessage', func_get_args()));
+ }
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Assert that specific form error message is set in the view.
+ *
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessage('username', 'Invalid Username');
+ * ?>
+ * ```
+ * @param string $key
+ * @param string $errorMessage
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessage()
+ */
+ public function seeFormErrorMessage($key, $errorMessage) {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormErrorMessage', func_get_args()));
}
@@ -300,12 +338,13 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Set the currently logged in user for the application.
- * Takes either `UserInterface` instance or array of credentials.
+ * Takes either an object that implements the User interface or
+ * an array of credentials.
*
- * @param \Illuminate\Auth\UserInterface|array $user
+ * @param \Illuminate\Contracts\Auth\User|array $user
* @param string $driver
* @return void
- * @see \Codeception\Module\Laravel4::amLoggedAs()
+ * @see \Codeception\Module\Laravel5::amLoggedAs()
*/
public function amLoggedAs($user, $driver = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amLoggedAs', func_get_args()));
@@ -316,7 +355,7 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Logs user out
- * @see \Codeception\Module\Laravel4::logout()
+ * @see \Codeception\Module\Laravel5::logout()
*/
public function logout() {
return $this->scenario->runStep(new \Codeception\Step\Action('logout', func_get_args()));
@@ -328,7 +367,7 @@ class ApiTester extends \Codeception\Actor
*
* Checks that user is authenticated
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeAuthentication()
+ * @see \Codeception\Module\Laravel5::seeAuthentication()
*/
public function canSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeAuthentication', func_get_args()));
@@ -337,7 +376,7 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that user is authenticated
- * @see \Codeception\Module\Laravel4::seeAuthentication()
+ * @see \Codeception\Module\Laravel5::seeAuthentication()
*/
public function seeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeAuthentication', func_get_args()));
@@ -349,7 +388,7 @@ class ApiTester extends \Codeception\Actor
*
* Check that user is not authenticated
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::dontSeeAuthentication()
+ * @see \Codeception\Module\Laravel5::dontSeeAuthentication()
*/
public function cantSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeAuthentication', func_get_args()));
@@ -358,7 +397,7 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Check that user is not authenticated
- * @see \Codeception\Module\Laravel4::dontSeeAuthentication()
+ * @see \Codeception\Module\Laravel5::dontSeeAuthentication()
*/
public function dontSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeAuthentication', func_get_args()));
@@ -389,7 +428,7 @@ class ApiTester extends \Codeception\Actor
*
* @param string $class
* @return mixed
- * @see \Codeception\Module\Laravel4::grabService()
+ * @see \Codeception\Module\Laravel5::grabService()
*/
public function grabService($class) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabService', func_get_args()));
@@ -410,7 +449,7 @@ class ApiTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* @return mixed
- * @see \Codeception\Module\Laravel4::haveRecord()
+ * @see \Codeception\Module\Laravel5::haveRecord()
*/
public function haveRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('haveRecord', func_get_args()));
@@ -429,7 +468,7 @@ class ApiTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeRecord()
+ * @see \Codeception\Module\Laravel5::seeRecord()
*/
public function canSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeRecord', func_get_args()));
@@ -445,7 +484,7 @@ class ApiTester extends \Codeception\Actor
*
* @param $model
* @param array $attributes
- * @see \Codeception\Module\Laravel4::seeRecord()
+ * @see \Codeception\Module\Laravel5::seeRecord()
*/
public function seeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeRecord', func_get_args()));
@@ -466,7 +505,7 @@ class ApiTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::dontSeeRecord()
+ * @see \Codeception\Module\Laravel5::dontSeeRecord()
*/
public function cantSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeRecord', func_get_args()));
@@ -484,7 +523,7 @@ class ApiTester extends \Codeception\Actor
*
* @param $model
* @param array $attributes
- * @see \Codeception\Module\Laravel4::dontSeeRecord()
+ * @see \Codeception\Module\Laravel5::dontSeeRecord()
*/
public function dontSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeRecord', func_get_args()));
@@ -505,7 +544,7 @@ class ApiTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* @return mixed
- * @see \Codeception\Module\Laravel4::grabRecord()
+ * @see \Codeception\Module\Laravel5::grabRecord()
*/
public function grabRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabRecord', func_get_args()));
@@ -1519,6 +1558,7 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Sets a cookie with the given name and value.
+ * You can set additional cookie params like `domain`, `path`, `expire`, `secure` in array passed as last argument.
*
* ``` php
*
* ```
*
- * @param $cookie
- * @param $value
+ * @param $name
+ * @param $val
+ * @param array $params
+ * @internal param $cookie
+ * @internal param $value
*
* @return mixed
* @see \Codeception\Lib\InnerBrowser::setCookie()
*/
- public function setCookie($name, $val) {
+ public function setCookie($name, $val, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('setCookie', func_get_args()));
}
@@ -1541,13 +1584,15 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Grabs a cookie value.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::grabCookie()
*/
- public function grabCookie($name) {
+ public function grabCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabCookie', func_get_args()));
}
@@ -1556,6 +1601,7 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args()));
}
@@ -1597,27 +1644,31 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function cantSeeCookie($name) {
+ public function cantSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function dontSeeCookie($name) {
+ public function dontSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args()));
}
@@ -1626,13 +1677,15 @@ class ApiTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Unsets cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::resetCookie()
*/
- public function resetCookie($name) {
+ public function resetCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('resetCookie', func_get_args()));
}
@@ -2527,7 +2580,6 @@ class ApiTester extends \Codeception\Actor
* This assertion allows you to check the structure of response json.
* *
* ```json
- * ```json
* { "store": {
* "book": [
* { "category": "reference",
@@ -2575,7 +2627,6 @@ class ApiTester extends \Codeception\Actor
* This assertion allows you to check the structure of response json.
* *
* ```json
- * ```json
* { "store": {
* "book": [
* { "category": "reference",
@@ -2715,6 +2766,31 @@ class ApiTester extends \Codeception\Actor
}
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Opposite to seeResponseJsonMatchesJsonPath
+ *
+ * @param array $jsonPath
+ * Conditional Assertion: Test won't be stopped on fail
+ * @see \Codeception\Module\REST::dontSeeResponseJsonMatchesJsonPath()
+ */
+ public function cantSeeResponseJsonMatchesJsonPath($jsonPath) {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeResponseJsonMatchesJsonPath', func_get_args()));
+ }
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Opposite to seeResponseJsonMatchesJsonPath
+ *
+ * @param array $jsonPath
+ * @see \Codeception\Module\REST::dontSeeResponseJsonMatchesJsonPath()
+ */
+ public function dontSeeResponseJsonMatchesJsonPath($jsonPath) {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeResponseJsonMatchesJsonPath', func_get_args()));
+ }
+
+
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
@@ -3048,7 +3124,7 @@ class ApiTester extends \Codeception\Actor
*
* @see \Codeception\Module\ApiHelper::login()
*/
- public function login($identifier, $password) {
+ public function login($identification, $password) {
return $this->scenario->runStep(new \Codeception\Step\Action('login', func_get_args()));
}
diff --git a/tests/api/AuthCest.php b/tests/api/AuthCest.php
index cdb994620..47eaf8a93 100644
--- a/tests/api/AuthCest.php
+++ b/tests/api/AuthCest.php
@@ -19,11 +19,11 @@ class AuthCest
$I->login('foo@bar.com', 'pass7word');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
-
+
$token = $I->grabDataFromJsonResponse('token');
$userId = $I->grabDataFromJsonResponse('userId');
$I->assertNotEmpty($token);
-
+
$loggedIn = User::where('token', $token)->where('id', $userId)->first();
$I->assertEquals($user->id, $loggedIn->id);
}
@@ -40,7 +40,7 @@ class AuthCest
$I->login('tobscure', 'pass7word');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
-
+
$token = $I->grabDataFromJsonResponse('token');
$userId = $I->grabDataFromJsonResponse('userId');
$I->assertNotEmpty($token);
@@ -60,4 +60,4 @@ class AuthCest
$I->seeResponseCodeIs(401);
$I->seeResponseIsJson();
}
-}
\ No newline at end of file
+}
diff --git a/tests/api/DiscussionsResourceCest.php b/tests/api/DiscussionsResourceCest.php
index 13624f100..9c12bd2a8 100644
--- a/tests/api/DiscussionsResourceCest.php
+++ b/tests/api/DiscussionsResourceCest.php
@@ -1,17 +1,16 @@
wantTo('get discussions via API');
- $discussions = Factory::times(2)->create('Flarum\Core\Discussions\Discussion');
+ $discussions = Factory::times(2)->create('Flarum\Core\Models\Discussion');
$I->sendGET($this->endpoint);
$I->seeResponseCodeIs(200);
@@ -20,7 +19,7 @@ class DiscussionsResourceCest {
$I->expect('there are two discussions in the response');
$I->assertEquals(2, count($I->grabDataFromJsonResponse('discussions')));
- $I->expect('they are the discussions we created');
+ $I->expect('the discussions exist');
$I->seeResponseContainsJson(['id' => (string) $discussions[0]->id, 'title' => $discussions[0]->title]);
$I->seeResponseContainsJson(['id' => (string) $discussions[1]->id, 'title' => $discussions[1]->title]);
}
@@ -29,12 +28,13 @@ class DiscussionsResourceCest {
{
$I->wantTo('show a single discussion via API');
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion');
+ $discussion = Factory::create('Flarum\Core\Models\Discussion');
$I->sendGET($this->endpoint.'/'.$discussion->id);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
+ $I->expect('the discussion in the response exists');
$I->seeResponseContainsJson(['discussions' => ['id' => (string) $discussion->id, 'title' => $discussion->title]]);
}
@@ -47,11 +47,14 @@ class DiscussionsResourceCest {
$I->sendPOST($this->endpoint, ['discussions' => ['title' => 'foo', 'content' => 'bar']]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
+
+ $I->expect('the discussion is included in the response');
$I->seeResponseContainsJson(['title' => 'foo']);
+
+ $I->expect('posts are included in the response');
$I->seeResponseContainsJson(['type' => 'comment', 'contentHtml' => 'bar
']);
- // @todo check for post in response
-
+ $I->expect('the discussion was created in the database');
$id = $I->grabDataFromJsonResponse('discussions.id');
$I->seeRecord('discussions', ['id' => $id, 'title' => 'foo']);
}
@@ -62,13 +65,16 @@ class DiscussionsResourceCest {
$user = $I->amAuthenticated();
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion', ['start_user_id' => $user->id]);
+ $discussion = Factory::create('Flarum\Core\Models\Discussion', ['start_user_id' => $user->id]);
$I->sendPUT($this->endpoint.'/'.$discussion->id, ['discussions' => ['title' => 'foo']]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
+
+ $I->expect('the discussion title was updated');
$I->seeResponseContainsJson(['title' => 'foo']);
+ $I->expect('the discussion was updated in the database');
$id = $I->grabDataFromJsonResponse('discussions.id');
$I->seeRecord('discussions', ['id' => $id, 'title' => 'foo']);
}
@@ -78,14 +84,15 @@ class DiscussionsResourceCest {
$I->wantTo('delete a discussion via API');
$user = $I->amAuthenticated();
- $user->groups()->attach(4);
+ $user->groups()->attach(4); // Make the user a moderator
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion', ['start_user_id' => $user->id]);
+ $discussion = Factory::create('Flarum\Core\Models\Discussion', ['start_user_id' => $user->id]);
$I->sendDELETE($this->endpoint.'/'.$discussion->id);
$I->seeResponseCodeIs(204);
$I->seeResponseEquals('');
+ $I->expect('the discussion was deleted in the database');
$I->dontSeeRecord('discussions', ['id' => $discussion->id]);
}
}
diff --git a/tests/factories/factories.php b/tests/factories/factories.php
index 9d9d37a40..50e72eab7 100644
--- a/tests/factories/factories.php
+++ b/tests/factories/factories.php
@@ -1,15 +1,14 @@
$faker->sentence,
'start_time' => $faker->dateTimeThisYear,
- 'start_user_id' => 'factory:Flarum\Core\Users\User'
+ 'start_user_id' => 'factory:Flarum\Core\Models\User'
]);
-$factory('Flarum\Core\Users\User', [
+$factory('Flarum\Core\Models\User', [
'username' => $faker->userName,
'email' => $faker->safeEmail,
'password' => 'password',
- 'join_time' => $faker->dateTimeThisYear,
- 'time_zone' => $faker->timezone
-]);
\ No newline at end of file
+ 'join_time' => $faker->dateTimeThisYear
+]);
diff --git a/tests/functional/FunctionalTester.php b/tests/functional/FunctionalTester.php
index 58ca272f2..76a14cf79 100644
--- a/tests/functional/FunctionalTester.php
+++ b/tests/functional/FunctionalTester.php
@@ -1,4 +1,4 @@
-repo = new DiscussionRepository;
- }
-
- /** @test */
- public function testGetDiscussionByID()
- {
- // Given I have a discussion
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion');
-
- // When I fetch the discussion by its ID
- $result = $this->repo->find($discussion->id);
-
- // Then I should receive it
- $this->assertEquals($discussion->id, $result->id);
- }
-
- /**
- * @test
- * @expectedException Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function testDiscussionCannotBeViewed()
- {
- // Given I have a discussion
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion');
-
- // And forum permissions do not allow guests to view the forum
- $manager = m::mock('Flarum\Core\Permissions\Manager');
- $manager->shouldReceive('granted')->andReturn(false);
- app()->instance('flarum.permissions', $manager);
-
- // When I fetch the discussion by its ID, I expect an exception to be thrown
- $this->repo->findOrFail($discussion->id, new Flarum\Core\Users\Guest);
- }
-
- /** @test */
- public function testSaveDiscussion()
- {
- // Given I have a new discussion
- $user = Factory::create('Flarum\Core\Users\User');
- $discussion = Discussion::start('foo', $user);
-
- // When I save it
- $discussion = $this->repo->save($discussion);
-
- // Then it should be in the database
- $this->tester->seeRecord('discussions', ['title' => 'foo']);
- }
-
- /**
- * @test
- * @expectedException Flarum\Core\Support\Exceptions\ValidationFailureException
- */
- public function testSaveInvalidDiscussion()
- {
- // Given I have a new discussion containing no information
- $discussion = new Discussion;
-
- // When I save it, I expect an exception to be thrown
- $this->repo->save($discussion);
- }
-
- /** @test */
- public function testDeleteDiscussion()
- {
- // Given I have a discussion
- $discussion = Factory::create('Flarum\Core\Discussions\Discussion');
-
- // When I delete it
- $this->repo->delete($discussion);
-
- // Then it should no longer be in the database
- $this->tester->dontSeeRecord('discussions', ['id' => $discussion->id]);
- }
-
-}
diff --git a/tests/integration/IntegrationTester.php b/tests/integration/IntegrationTester.php
index f7d53fed3..e67e65f0b 100644
--- a/tests/integration/IntegrationTester.php
+++ b/tests/integration/IntegrationTester.php
@@ -1,4 +1,4 @@
-scenario->runStep(new \Codeception\Step\Action('haveEnabledFilters', func_get_args()));
- }
-
-
- /**
- * [!] Method is generated. Documentation taken from corresponding module.
+ * Provides access the Laravel application object.
*
- * Disable Laravel filters for next requests.
- * @see \Codeception\Module\Laravel4::haveDisabledFilters()
+ * @return \Illuminate\Foundation\Application
+ * @see \Codeception\Module\Laravel5::getApplication()
*/
- public function haveDisabledFilters() {
- return $this->scenario->runStep(new \Codeception\Step\Action('haveDisabledFilters', func_get_args()));
+ public function getApplication() {
+ return $this->scenario->runStep(new \Codeception\Step\Action('getApplication', func_get_args()));
}
@@ -62,7 +53,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param $route
* @param array $params
- * @see \Codeception\Module\Laravel4::amOnRoute()
+ * @see \Codeception\Module\Laravel5::amOnRoute()
*/
public function amOnRoute($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amOnRoute', func_get_args()));
@@ -82,7 +73,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param $action
* @param array $params
- * @see \Codeception\Module\Laravel4::amOnAction()
+ * @see \Codeception\Module\Laravel5::amOnAction()
*/
public function amOnAction($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amOnAction', func_get_args()));
@@ -102,7 +93,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $route
* @param array $params
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeCurrentRouteIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentRouteIs()
*/
public function canSeeCurrentRouteIs($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentRouteIs', func_get_args()));
@@ -119,7 +110,7 @@ class IntegrationTester extends \Codeception\Actor
* ```
* @param $route
* @param array $params
- * @see \Codeception\Module\Laravel4::seeCurrentRouteIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentRouteIs()
*/
public function seeCurrentRouteIs($route, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentRouteIs', func_get_args()));
@@ -140,7 +131,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $action
* @param array $params
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeCurrentActionIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentActionIs()
*/
public function canSeeCurrentActionIs($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentActionIs', func_get_args()));
@@ -158,7 +149,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param $action
* @param array $params
- * @see \Codeception\Module\Laravel4::seeCurrentActionIs()
+ * @see \Codeception\Module\Laravel5::seeCurrentActionIs()
*/
public function seeCurrentActionIs($action, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentActionIs', func_get_args()));
@@ -174,7 +165,7 @@ class IntegrationTester extends \Codeception\Actor
* @param mixed $value
* @return void
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeInSession()
+ * @see \Codeception\Module\Laravel5::seeInSession()
*/
public function canSeeInSession($key, $value = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInSession', func_get_args()));
@@ -187,7 +178,7 @@ class IntegrationTester extends \Codeception\Actor
* @param string|array $key
* @param mixed $value
* @return void
- * @see \Codeception\Module\Laravel4::seeInSession()
+ * @see \Codeception\Module\Laravel5::seeInSession()
*/
public function seeInSession($key, $value = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInSession', func_get_args()));
@@ -202,7 +193,7 @@ class IntegrationTester extends \Codeception\Actor
* @param array $bindings
* @return void
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionHasValues()
+ * @see \Codeception\Module\Laravel5::seeSessionHasValues()
*/
public function canSeeSessionHasValues($bindings) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionHasValues', func_get_args()));
@@ -214,7 +205,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param array $bindings
* @return void
- * @see \Codeception\Module\Laravel4::seeSessionHasValues()
+ * @see \Codeception\Module\Laravel5::seeSessionHasValues()
*/
public function seeSessionHasValues($bindings) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionHasValues', func_get_args()));
@@ -224,74 +215,121 @@ class IntegrationTester extends \Codeception\Actor
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that Session has error messages
- * The seeSessionHasValues cannot be used, as Message bag Object is returned by Laravel4
+ * Assert that the form errors are bound to the View.
*
- * Useful for validation messages and generally messages array
- * e.g.
- * return `Redirect::to('register')->withErrors($validator);`
- *
- * Example of Usage
- *
- * ``` php
- * seeSessionErrorMessage(array('username'=>'Invalid Username'));
- * ?>
- * ```
- * @param array $bindings
+ * @return bool
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionErrorMessage()
+ * @see \Codeception\Module\Laravel5::seeFormHasErrors()
*/
- public function canSeeSessionErrorMessage($bindings) {
- return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionErrorMessage', func_get_args()));
+ public function canSeeFormHasErrors() {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormHasErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that Session has error messages
- * The seeSessionHasValues cannot be used, as Message bag Object is returned by Laravel4
+ * Assert that the form errors are bound to the View.
*
- * Useful for validation messages and generally messages array
- * e.g.
- * return `Redirect::to('register')->withErrors($validator);`
- *
- * Example of Usage
- *
- * ``` php
- * seeSessionErrorMessage(array('username'=>'Invalid Username'));
- * ?>
- * ```
- * @param array $bindings
- * @see \Codeception\Module\Laravel4::seeSessionErrorMessage()
+ * @return bool
+ * @see \Codeception\Module\Laravel5::seeFormHasErrors()
*/
- public function seeSessionErrorMessage($bindings) {
- return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionErrorMessage', func_get_args()));
+ public function seeFormHasErrors() {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormHasErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that the session has errors bound.
+ * Assert that specific form error messages are set in the view.
*
- * @return bool
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessages(array('username'=>'Invalid Username'));
+ * ?>
+ * ```
+ * @param array $bindings
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeSessionHasErrors()
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessages()
*/
- public function canSeeSessionHasErrors() {
- return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeSessionHasErrors', func_get_args()));
+ public function canSeeFormErrorMessages($bindings) {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormErrorMessages', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
- * Assert that the session has errors bound.
+ * Assert that specific form error messages are set in the view.
*
- * @return bool
- * @see \Codeception\Module\Laravel4::seeSessionHasErrors()
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessages(array('username'=>'Invalid Username'));
+ * ?>
+ * ```
+ * @param array $bindings
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessages()
*/
- public function seeSessionHasErrors() {
- return $this->scenario->runStep(new \Codeception\Step\Assertion('seeSessionHasErrors', func_get_args()));
+ public function seeFormErrorMessages($bindings) {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormErrorMessages', func_get_args()));
+ }
+
+
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Assert that specific form error message is set in the view.
+ *
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessage('username', 'Invalid Username');
+ * ?>
+ * ```
+ * @param string $key
+ * @param string $errorMessage
+ * Conditional Assertion: Test won't be stopped on fail
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessage()
+ */
+ public function canSeeFormErrorMessage($key, $errorMessage) {
+ return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFormErrorMessage', func_get_args()));
+ }
+ /**
+ * [!] Method is generated. Documentation taken from corresponding module.
+ *
+ * Assert that specific form error message is set in the view.
+ *
+ * Useful for validation messages and generally messages array
+ * e.g.
+ * return `Redirect::to('register')->withErrors($validator);`
+ *
+ * Example of Usage
+ *
+ * ``` php
+ * seeFormErrorMessage('username', 'Invalid Username');
+ * ?>
+ * ```
+ * @param string $key
+ * @param string $errorMessage
+ * @see \Codeception\Module\Laravel5::seeFormErrorMessage()
+ */
+ public function seeFormErrorMessage($key, $errorMessage) {
+ return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFormErrorMessage', func_get_args()));
}
@@ -299,12 +337,13 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Set the currently logged in user for the application.
- * Takes either `UserInterface` instance or array of credentials.
+ * Takes either an object that implements the User interface or
+ * an array of credentials.
*
- * @param \Illuminate\Auth\UserInterface|array $user
+ * @param \Illuminate\Contracts\Auth\User|array $user
* @param string $driver
* @return void
- * @see \Codeception\Module\Laravel4::amLoggedAs()
+ * @see \Codeception\Module\Laravel5::amLoggedAs()
*/
public function amLoggedAs($user, $driver = null) {
return $this->scenario->runStep(new \Codeception\Step\Condition('amLoggedAs', func_get_args()));
@@ -315,7 +354,7 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Logs user out
- * @see \Codeception\Module\Laravel4::logout()
+ * @see \Codeception\Module\Laravel5::logout()
*/
public function logout() {
return $this->scenario->runStep(new \Codeception\Step\Action('logout', func_get_args()));
@@ -327,7 +366,7 @@ class IntegrationTester extends \Codeception\Actor
*
* Checks that user is authenticated
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeAuthentication()
+ * @see \Codeception\Module\Laravel5::seeAuthentication()
*/
public function canSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeAuthentication', func_get_args()));
@@ -336,7 +375,7 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that user is authenticated
- * @see \Codeception\Module\Laravel4::seeAuthentication()
+ * @see \Codeception\Module\Laravel5::seeAuthentication()
*/
public function seeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeAuthentication', func_get_args()));
@@ -348,7 +387,7 @@ class IntegrationTester extends \Codeception\Actor
*
* Check that user is not authenticated
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::dontSeeAuthentication()
+ * @see \Codeception\Module\Laravel5::dontSeeAuthentication()
*/
public function cantSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeAuthentication', func_get_args()));
@@ -357,7 +396,7 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Check that user is not authenticated
- * @see \Codeception\Module\Laravel4::dontSeeAuthentication()
+ * @see \Codeception\Module\Laravel5::dontSeeAuthentication()
*/
public function dontSeeAuthentication() {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeAuthentication', func_get_args()));
@@ -388,7 +427,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param string $class
* @return mixed
- * @see \Codeception\Module\Laravel4::grabService()
+ * @see \Codeception\Module\Laravel5::grabService()
*/
public function grabService($class) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabService', func_get_args()));
@@ -409,7 +448,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* @return mixed
- * @see \Codeception\Module\Laravel4::haveRecord()
+ * @see \Codeception\Module\Laravel5::haveRecord()
*/
public function haveRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('haveRecord', func_get_args()));
@@ -428,7 +467,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::seeRecord()
+ * @see \Codeception\Module\Laravel5::seeRecord()
*/
public function canSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeRecord', func_get_args()));
@@ -444,7 +483,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param $model
* @param array $attributes
- * @see \Codeception\Module\Laravel4::seeRecord()
+ * @see \Codeception\Module\Laravel5::seeRecord()
*/
public function seeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('seeRecord', func_get_args()));
@@ -465,7 +504,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* Conditional Assertion: Test won't be stopped on fail
- * @see \Codeception\Module\Laravel4::dontSeeRecord()
+ * @see \Codeception\Module\Laravel5::dontSeeRecord()
*/
public function cantSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeRecord', func_get_args()));
@@ -483,7 +522,7 @@ class IntegrationTester extends \Codeception\Actor
*
* @param $model
* @param array $attributes
- * @see \Codeception\Module\Laravel4::dontSeeRecord()
+ * @see \Codeception\Module\Laravel5::dontSeeRecord()
*/
public function dontSeeRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeRecord', func_get_args()));
@@ -504,7 +543,7 @@ class IntegrationTester extends \Codeception\Actor
* @param $model
* @param array $attributes
* @return mixed
- * @see \Codeception\Module\Laravel4::grabRecord()
+ * @see \Codeception\Module\Laravel5::grabRecord()
*/
public function grabRecord($model, $attributes = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabRecord', func_get_args()));
@@ -1518,6 +1557,7 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Sets a cookie with the given name and value.
+ * You can set additional cookie params like `domain`, `path`, `expire`, `secure` in array passed as last argument.
*
* ``` php
*
* ```
*
- * @param $cookie
- * @param $value
+ * @param $name
+ * @param $val
+ * @param array $params
+ * @internal param $cookie
+ * @internal param $value
*
* @return mixed
* @see \Codeception\Lib\InnerBrowser::setCookie()
*/
- public function setCookie($name, $val) {
+ public function setCookie($name, $val, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('setCookie', func_get_args()));
}
@@ -1540,13 +1583,15 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Grabs a cookie value.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::grabCookie()
*/
- public function grabCookie($name) {
+ public function grabCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('grabCookie', func_get_args()));
}
@@ -1555,6 +1600,7 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a cookie with the given name is set.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* ``` php
* scenario->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args()));
}
@@ -1596,27 +1643,31 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function cantSeeCookie($name) {
+ public function cantSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that there isn't a cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` as array passed in last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::dontSeeCookie()
*/
- public function dontSeeCookie($name) {
+ public function dontSeeCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args()));
}
@@ -1625,13 +1676,15 @@ class IntegrationTester extends \Codeception\Actor
* [!] Method is generated. Documentation taken from corresponding module.
*
* Unsets cookie with the given name.
+ * You can set additional cookie params like `domain`, `path` in array passed as last argument.
*
* @param $cookie
*
+ * @param array $params
* @return mixed
* @see \Codeception\Lib\InnerBrowser::resetCookie()
*/
- public function resetCookie($name) {
+ public function resetCookie($name, $params = null) {
return $this->scenario->runStep(new \Codeception\Step\Action('resetCookie', func_get_args()));
}
diff --git a/tests/unit/UnitTester.php b/tests/unit/UnitTester.php
index 9b88387ef..1b95ec3d2 100644
--- a/tests/unit/UnitTester.php
+++ b/tests/unit/UnitTester.php
@@ -1,4 +1,4 @@
-
+
+
+
+
+ {{ $title }}
+
+
+
+
+ {!! $styles !!}
+
+
+ {!! $content !!}
+
+ {!! $scripts !!}
+
+