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 !!} + +