1
0
mirror of https://github.com/flarum/core.git synced 2025-08-23 08:33:45 +02:00

Compare commits

...

165 Commits

Author SHA1 Message Date
David Sevilla Martín
a720f6f651 Update Application version string to beta 9 (#1784) 2019-07-05 12:37:02 +02:00
flarum-bot
54d7c0d3b6 Bundled output for commit b5876d9f31 [skip ci] 2019-06-27 19:23:54 +00:00
David Sevilla Martín
b5876d9f31 Merge pull request #1803 from flarum/ds/1777-previous-route-default
Visit home page if previous route does not exist when going back in history
2019-06-27 15:17:41 -04:00
David Sevilla Martín
25ef4c10bd Update CHANGELOG.md 2019-06-27 15:07:53 -04:00
David Sevilla Martín
985b87da6c Visit home page if no previous route exists
Fixes #1777
2019-06-27 14:58:05 -04:00
Daniël Klabbers
a6aa28566c added changelog item for mediumText fix in posts.content 2019-06-24 14:57:13 +02:00
Daniël Klabbers
e3340ba3e1 Merge branch 'master' of github.com:flarum/core 2019-06-24 14:55:05 +02:00
Daniël Klabbers
590b311570 fixes #1801, increasing the size of posts.content to mediumText correctly 2019-06-24 14:53:56 +02:00
Daniël Klabbers
935a968257 fixed tests on master, missing views directory and suppressing notices from tempnam when storing files in tmp 2019-06-24 13:00:36 +02:00
Daniël Klabbers
fe558eb0ba Merge branch 'master' into advisory-fix-1 2019-06-24 12:53:37 +02:00
Daniël Klabbers
fda9cba4ce Merge branch 'master' of github.com:flarum/core 2019-06-24 10:49:39 +02:00
Daniël Klabbers
89f6cfd949 removed link to home, go back, which is always the case with csrf token invalidation 2019-06-24 10:49:31 +02:00
Daniël Klabbers
803582c437 Apply fixes from StyleCI (#1800)
[ci skip] [skip ci]
2019-06-24 09:15:15 +02:00
Franz Liedke
8e86d38804 Merge pull request from GHSA-3wjh-93gr-chh6
* Integration tests: Memoize request handler as well

This is useful to send HTTP requests (or their PSR-7 equivalents)
through the entire application's middleware stack (instead of
talking to specific controllers, which should be considered
implementation detail).

* Add tests for CSRF token check

* Integration tests: Configure vendor path

Now that this is possible, make the easy change...

* Implement middleware for CSRF token verification

This fixes a rather large oversight in Flarum's codebase, which was that
we had no explicit CSRF protection using the traditional token approach.

The JS frontend was actually sending these tokens, but the backend did
not require them.

* Accept CSRF token in request body as well

* Refactor tests to shorten HTTP requests

Multiple tests now provide JSON request bodies, and others copy cookies
from previous responses, so let's provide convenient helpers for these.

* Fixed issue with tmp/storage/views not existing, this caused tmpname to notice.
Fixed csrf test that assumed an access token allows application access, which is actually api token.
Improved return type hinting in the StartSession middleware

* Using a different setting key now, so that it won't break tests whenever you re-run them once smtp is set.
Fixed, badly, the test to create users etc caused by the prepareDatabase flushing all settings by default.

* added custom view, now needs translation
2019-06-24 09:14:38 +02:00
Daniël Klabbers
fd66722945 added custom view, now needs translation 2019-06-22 19:40:20 +02:00
Daniël Klabbers
ce42b5e035 Using a different setting key now, so that it won't break tests whenever you re-run them once smtp is set.
Fixed, badly, the test to create users etc caused by the prepareDatabase flushing all settings by default.
2019-06-18 17:45:29 +02:00
Daniël Klabbers
bfd3a667dd Fixed issue with tmp/storage/views not existing, this caused tmpname to notice.
Fixed csrf test that assumed an access token allows application access, which is actually api token.
Improved return type hinting in the StartSession middleware
2019-06-18 17:22:23 +02:00
Daniël Klabbers
b669490d33 Update CHANGELOG.md
clarifying reason for change on the `like` fix
2019-06-13 09:13:31 +02:00
Franz Liedke
ba956f51ac Update changelog 2019-06-13 01:03:39 +02:00
Franz Liedke
c126b95451 Refactor tests to shorten HTTP requests
Multiple tests now provide JSON request bodies, and others copy cookies
from previous responses, so let's provide convenient helpers for these.
2019-06-13 00:13:59 +02:00
Franz Liedke
7f7484e790 Accept CSRF token in request body as well 2019-06-13 00:13:58 +02:00
Franz Liedke
5d64056e89 Implement middleware for CSRF token verification
This fixes a rather large oversight in Flarum's codebase, which was that
we had no explicit CSRF protection using the traditional token approach.

The JS frontend was actually sending these tokens, but the backend did
not require them.
2019-06-13 00:13:58 +02:00
Franz Liedke
e927254e99 Add tests for CSRF token check 2019-06-13 00:13:57 +02:00
Franz Liedke
8061bfd74a Integration tests: Configure vendor path
Now that this is possible, make the easy change...
2019-06-13 00:13:57 +02:00
Franz Liedke
4c309d2ad7 Integration tests: Memoize request handler as well
This is useful to send HTTP requests (or their PSR-7 equivalents)
through the entire application's middleware stack (instead of
talking to specific controllers, which should be considered
implementation detail).
2019-06-13 00:13:57 +02:00
Franz Liedke
54876cfbd6 Integration tests: Fix test setup 2019-06-13 00:13:38 +02:00
Franz Liedke
9e2b796a7c Fix syntax error 2019-06-13 00:11:57 +02:00
Franz Liedke
7f5bd1e96b Apply fixes from StyleCI (#1793)
[ci skip] [skip ci]
2019-06-12 23:50:21 +02:00
Franz Liedke
5e1680c458 Introduce a vendor path
This lets us or anyone modify the path from where dependencies (usually
installed into /vendor by Composer) are loaded. We need to be able to
tweak this in our integration tests, where the application code under
test needs access to certain dependencies.
2019-06-12 23:48:22 +02:00
Franz Liedke
6e26b988bd Inject app, not container, to avoid global helpers 2019-06-12 23:48:22 +02:00
Daniël Klabbers
2e8d4e4b6b Update CHANGELOG.md
added fix for js compiler tmp path fix to changelog
2019-06-12 17:18:21 +02:00
Daniël Klabbers
14bede2847 Merge branch 'master' of github.com:flarum/core 2019-06-12 16:47:15 +02:00
Daniël Klabbers
54660ebd63 fixed issue with the Js compiler being unable to use the system tmp directory, using the one in storage is much safer across different operating systems 2019-06-12 16:46:53 +02:00
Daniël Klabbers
1a62b7e07a Update CHANGELOG.md
fixed missing link markdown
2019-06-12 00:43:57 +02:00
Daniël Klabbers
4b04c0e0ce Update CHANGELOG.md
added missing changelog item for #1738
2019-06-12 00:43:09 +02:00
Daniël Klabbers
4d45ce389b Update CHANGELOG.md
referenced incorrect (parent) commit in changelog
2019-06-12 00:38:54 +02:00
Daniël Klabbers
d2674fb309 patched constraint for components/font-awesome, fixes #1790 2019-06-11 20:22:35 +02:00
Annim Banerjee
5eb69e1f59 Updated names to match components in fontawsome (#1791)
fa-* named components are not present, hence updated to matching names.
2019-06-11 20:17:59 +02:00
Franz Liedke
f42142979d Load LESS variables via path traversal
Since these files are part of the same package, there is no need
to assume a Composer context to load these from. Instead, we can
just load them via the path relative to the current PHP file.

This assumption may break in certain environments, and it is
already broken when running (integration) tests.
2019-06-09 00:19:06 +02:00
Franz Liedke
5f79d3b499 This method should be private 2019-06-09 00:19:05 +02:00
Franz Liedke
8e4d97260f Do not rely on extensions_enabled being present
This mostly simplifies setup in complex integration tests.
2019-06-09 00:19:05 +02:00
Daniël Klabbers
ee3640e160 remove use of like which might cause unwanted side effects (#1787) 2019-06-03 12:04:17 +02:00
Franz Liedke
bd584802e5 Update changelog 2019-06-01 20:12:30 +02:00
flarum-bot
f4dd045326 Bundled output for commit 24522943f6 [skip ci] 2019-06-01 18:10:13 +00:00
Franz Liedke
24522943f6 Update insecure jQuery version
Thanks, GitHub security alerts!
2019-06-01 20:03:07 +02:00
Franz Liedke
56fde28e43 Restore "originalUri" request attribute
This is helpful when Flarum is installed in subfolders.

Fixes #778.
2019-06-01 12:51:05 +02:00
Franz Liedke
1c1d661bdd Use the settings repository's default value
Updates commit bf2c5a5564.
2019-05-24 20:11:34 +02:00
Franz Liedke
d3be186fb6 Update changelog 2019-05-24 20:11:31 +02:00
Daniël Klabbers
8f8cc558be Update SECURITY.md
fixed typo
2019-05-23 11:15:55 +02:00
Franz Liedke
5ea9e1cf5e Add a security policy 2019-05-23 11:10:53 +02:00
Toby Zerner
99a6066f96 Merge pull request #1779 from clarkwinkelmann/fix-userpage-card-dropdown
Fix dropdown icon not showing in UserCard when on UserPage
2019-05-02 19:21:39 +09:30
Toby Zerner
8b7db726dc Merge pull request #1780 from clarkwinkelmann/remove-notification-id
Remove notification id from serializer attributes
2019-05-02 19:20:42 +09:30
Clark Winkelmann
7a44086bf3 Remove notification id from serializer attributes 2019-05-01 23:05:25 +02:00
Clark Winkelmann
12fdfc9b54 Fix dropdown icon not showing in UserCard when on UserPage
The rule hiding the icon in the UserHero was too broad and applied to UserCard in the list of posts as well
The float rule was redundant
2019-05-01 22:54:13 +02:00
Clark Winkelmann
ecc3b5e227 Remove post id from serializer attributes (#1775) 2019-04-19 21:37:14 +02:00
Daniël Klabbers
bf2c5a5564 This small fix prevents that the forum frontend breaks whenever
custom_less is NULL or unavailable in the database. We cannot rely
on this value to exist or is incorrectly set to null and thus
completely bricking the app.
2019-04-12 14:10:20 +02:00
Toby Zerner
d3a5c91845 Update changelog 2019-03-24 12:26:02 +10:30
Toby Zerner
e17bb0b433 Fix is:unread gambit 2019-03-24 12:24:44 +10:30
flarum-bot
c4ba41f850 Bundled output for commit 0c4de6f163 [skip ci] 2019-03-20 21:09:11 +00:00
Franz Liedke
0c4de6f163 Fix storing dynamic mail settings
Refs #1169.
2019-03-20 22:02:06 +01:00
flarum-bot
cd313952c7 Bundled output for commit 5154d7e5a6 [skip ci] 2019-03-19 09:06:21 +00:00
Franz Liedke
ef57b443c1 Apply fixes from StyleCI (#1761)
[ci skip] [skip ci]
2019-03-19 09:59:09 +01:00
Franz Liedke
5154d7e5a6 Allow configuring all drivers via frontend (#1169)
This includes an API endpoint for fetching the list of possible
drivers and their configuration fields. In the future, this can
be extended to include more meta information about each field.
2019-03-19 09:56:20 +01:00
Franz Liedke
2bd40b50c7 Remove dead code
Probably a leftover from copy-pasting the BasicsPage.
2019-03-17 19:02:46 +01:00
Franz Liedke
c50d58d0f4 Add drivers for Mailgun, Mandrill, SES (#1169) 2019-03-16 12:58:35 +01:00
Franz Liedke
8c65316961 Rely on default contribution guidelines
See https://help.github.com/en/articles/creating-a-default-community-health-file-for-your-organization.
2019-03-16 12:32:29 +01:00
flarum-bot
0a818cfdf3 Bundled output for commit a21052c903 [skip ci] 2019-03-15 17:01:09 +00:00
Franz Liedke
57204c6ed0 Fix last commit 2019-03-15 17:57:11 +01:00
Franz Liedke
a21052c903 Mail settings: Only show necessary fields (#1169) 2019-03-15 17:54:14 +01:00
Franz Liedke
441ebacfd7 Apply fixes from StyleCI (#1760)
[ci skip] [skip ci]
2019-03-13 21:32:18 +01:00
Franz Liedke
46acfb6c23 Implement mail driver classes (#1169)
This adds an interface for mail drivers to implement, defining several
methods that we need throughout Flarum to configure, validate and use
the various email drivers we can support through Laravel.

More mail drivers can be added by `extend()`ing the container binding
"mail.supported_drivers" with an arbitrary key and the name of a class
that implements our new `DriverInterface`.

This will ensure that drivers added by extensions can be properly built
and validated, even in the frontend.
2019-03-13 21:31:19 +01:00
Daniël Klabbers
9910e884fc Allow fallback to check for bound mail drivers (#1757) 2019-03-12 19:45:42 +01:00
Franz Liedke
d292aaabf8 Fix another documentation link
Forgotten in #1699, closes #1736.
2019-03-07 00:33:25 +01:00
Franz Liedke
d822a6f84c Apply fixes from StyleCI (#1756)
[ci skip] [skip ci]
2019-03-07 00:22:15 +01:00
Franz Liedke
26c3bcdb74 Add regression test for #1738
This should ensure we can always search for search terms that appear
either only in the subject or only in the text of discussions.
2019-03-07 00:21:43 +01:00
bdumaspilhou
33deea4791 Fixes #1738 : Search Title within discussions (#1741) 2019-03-07 00:20:37 +01:00
flarum-bot
20227a2201 Bundled output for commit 0493682dba [skip ci] 2019-03-03 19:45:38 +00:00
Franz Liedke
0493682dba Travis: Fix build job 2019-03-03 20:39:30 +01:00
Franz Liedke
49dda87e86 npm audit fix 2019-03-03 20:29:50 +01:00
Franz Liedke
d959d08561 NPM: Update bootstrap package
The old version had a vulnerability.

See https://nvd.nist.gov/vuln/detail/CVE-2019-8331.
2019-03-03 20:27:14 +01:00
Franz Liedke
e8ab49abc1 Merge pull request #1743 from flarum/fl/test-structure
Improve test suite structure
2019-03-03 20:17:35 +01:00
flarum-bot
296677b5fc Bundled output for commit f3931b537c [skip ci] 2019-02-18 07:51:00 +00:00
Daniël Klabbers
f3931b537c Copied over logic from EditTagModal to allow additional attributes to be send
to the API based on additional fields rendered by extending the fields.
2019-02-18 08:43:47 +01:00
Franz Liedke
d0ba4e5268 Update changelog 2019-02-14 23:40:18 +01:00
Daniël Klabbers
654ab4cc29 prefixes indices when installing too 2019-02-05 09:50:15 +01:00
Daniël Klabbers
e0becd0c7b Capsule manager (#1744)
Refactored to use the Capsule Database manager for setting up the
Flarum (mysql) connection.

This will introduce the reconnector automatically, fixing #1740
2019-02-04 23:31:12 +01:00
Franz Liedke
ed43ad9c3f Properly wrap error bag in session
Second part of fixing #1683.
2019-02-03 21:16:43 +01:00
Franz Liedke
4611abe5db Fix error redirect when resetting passwords
This was an oversight from the large database column renamings.

Fixes #1683.
2019-02-03 21:06:47 +01:00
Franz Liedke
df0bd52283 Add helpful (?) output to test setup script 2019-02-03 20:39:33 +01:00
Franz Liedke
d387a9ff02 travis: Configure setup for integration tests 2019-02-03 20:39:33 +01:00
Franz Liedke
5556df54f9 Setup Composer commands for testing and setup 2019-02-03 20:39:33 +01:00
Franz Liedke
cf746079ed Make integration tests independent
This creates a dedicated test suite for integration tests. All of them
can be run independently, and there is no order dependency - previously,
all integration tests needed the installer test to run first, and they
would fail if installation failed.

Now, the developer will have to set up a Flarum database to be used by
these tests. A setup script to make this simple will be added in the
next commit.

Small tradeoff: the installer is NOT tested in our test suite anymore,
only implicitly through the setup script. If we decide that this is a
problem, we can still set up separate, dedicated installer tests which
should probably test the web installer.
2019-02-03 20:39:32 +01:00
Franz Liedke
4d10536d35 Move integration tests to separate directory
Again, we do all of this to prepare for creating "real" test suites for
each type of tests.
2019-02-01 19:01:12 +01:00
Franz Liedke
ba16ebe61f Extract pure unit tests so that they can run fast
- Move to separate directory (base for a separate test suite)
- Inherit directly from PhpUnit
- Configure test suite with dedicated XML file
2019-02-01 19:01:09 +01:00
Franz Liedke
6484dc4982 Merge pull request #1617 from flarum/fl/installer-cleanup
Split up the installer logic
2019-02-01 17:43:59 +01:00
Franz Liedke
1a9f1f7a3d Use Collection class rather than collect() helper 2019-02-01 14:12:29 +01:00
Franz Liedke
4d1411e2a8 Improve problem description for wrong PHP version 2019-02-01 13:00:25 +01:00
Franz Liedke
968152b740 DatabaseConfig: Implement Arrayable contract 2019-02-01 13:00:07 +01:00
Franz Liedke
af185fd3d1 Fix tests 2019-02-01 10:33:21 +01:00
Franz Liedke
ed9591c16f Installer: Support reverting asset publication 2019-01-31 22:43:07 +01:00
Franz Liedke
8ad326941f Migrator: Fix resetting core migrations 2019-01-31 22:42:35 +01:00
Franz Liedke
8e4f02d994 Fix table name in migration 2019-01-31 22:01:05 +01:00
Franz Liedke
8ae85bc49f Remove obsolete dropForeign() migration
Forgotten in commit 5a04635e7a.
2019-01-31 22:00:41 +01:00
Franz Liedke
7ff9a90204 Check MariaDB version, update MySQL constraint
See flarum/docs#43.
2019-01-31 21:52:10 +01:00
Franz Liedke
f4fb1ab272 Simplify DataProviderInterface
Instead of passing all these objects / arrays from one object to the
next, simply pass an Installation instance around for configuration.
2019-01-31 21:52:10 +01:00
Franz Liedke
484c6d2edb Extract DatabaseConfig class with validation 2019-01-31 21:52:09 +01:00
Franz Liedke
8b68ff6232 Extract AdminUser class that enforces invariants 2019-01-31 21:52:09 +01:00
Franz Liedke
0a59b7164e Move password confirmation validation to frontends
Since this is not strictly speaking a domain invariant, but rather
specific to the user interface where passwords are not displayed, and
should therefore be entered twice to prevent mistakes going unnoticed,
this stuff should be checked in the frontend, not in the install steps.

Next step: Ensure that all domain-specific validation is done in the
installer's domain layer. This will ensure these validations cannot be
forgotten, and keep the frontends DRY.
2019-01-31 21:52:08 +01:00
Franz Liedke
0879829dc4 Use dedicated temporary variable instead of array 2019-01-31 21:52:08 +01:00
Franz Liedke
78ba3bd854 Combine building and storing config in one step 2019-01-31 21:52:08 +01:00
Franz Liedke
44c91099cd Get rid of DefaultsDataProvider
Since we do not provide a development VM anymore, it does not make sense
to have "default" credentials etc.

To reproduce something similar, I'd suggest using a YAML or JSON file
together with the `--file` option.
2019-01-31 21:52:07 +01:00
Franz Liedke
4585f03ee3 Switch to a whitelist for enabling extensions 2019-01-31 21:52:07 +01:00
Franz Liedke
bc9e8f68f1 Move default settings to install step
The various installation "frontends" (such as GUI and console) can now
provide custom overrides, if they want to.
2019-01-31 21:52:06 +01:00
Franz Liedke
f5a21584c2 Collapse namespace imports 2019-01-31 21:52:06 +01:00
Franz Liedke
e0a508a765 Catch pipeline's own exception 2019-01-31 21:52:06 +01:00
Franz Liedke
89e018a4f0 Simplify PrerequisiteInterface
I went with a return type of Collection, because it is easier to call
methods such as isEmpty() directly on those objects.
2019-01-31 21:52:05 +01:00
Franz Liedke
de6001f4cf Fix the test setup and installer tests
We are still testing the installation logic, but not testing the
actual CLI task. I would love to do that, but IMO we first need to
find a way to do this fully from the outside, by invoking and
talking to the installer through the shell.

Because acceptance tests are easier to do when fully decoupled from
the application. (After all, they are intended to save us from
breaking things when changing code; and we cannot prove that when
we change the tests at the same time.)

It might be easier to start with acceptance tests for the web
installer, though.
2019-01-31 21:52:05 +01:00
Franz Liedke
790d5beee5 Split up the installer logic
This is probably the most complicated way I could find to fix #1587.

Jokes aside, this was done with a few goals in mind:
- Reduce coupling between the installer and the rest of Flarum's
  "Application", which we are building during installation.
- Move the installer logic to several smaller classes, which can then
  be used by the web frontend and the console task, instead of the
  former hacking its way into the latter to be "DRY".
- Separate installer infrastructure (the "pipeline", with the ability
  to revert steps upon failure) from the actual steps being taken.

The problem was conceptual, and would certainly re-occur in a similar
fashion if we wouldn't tackle it at its roots.

It is fixed now, because we no longer use the ExtensionManager for
enabling extensions, but instead duplicate some of its logic. That is
fine because we don't want to do everything it does, e.g. omit
extenders' lifecycle hooks (which depend on the Application instance
being complete).

> for each desired change, make the change easy (warning: this may be
> hard), then make the easy change

- Kent Beck, https://twitter.com/kentbeck/status/250733358307500032

Fixes #1587.
2019-01-31 21:52:04 +01:00
flarum-bot
abf224bb0a Bundled output for commit c7d2e165d7 [skip ci] 2019-01-25 04:46:37 +00:00
Daniël Klabbers
c7d2e165d7 Fixes #1686
- further cleaned up the toggle action
- there's no way to remove the redraws because then the jquery isn't being fired properly
2019-01-25 05:37:45 +01:00
Toby Zerner
0ab9facc4b Make the Request available to the Formatter\Rendering event (#1721)
This is important because extensions may wish to render post content
differently depending on Request factors such as the actor. For example,
an attachments extension might wish to hide attachments from guests.

This solution is a bit of a hack-job for now, but soon when we refactor
the API layer to use tobscure/json-api-server, and also refactor the
Formatter layer, it can be revised.
2019-01-22 23:33:49 +01:00
Daniël Klabbers
9b68bbe44e Merge branch 'master' of github.com:flarum/core 2019-01-16 11:59:04 +01:00
Daniël Klabbers
862404f052 update symfony/console, because illuminate/console needs a higher version 2019-01-16 11:58:42 +01:00
flarum-bot
b9a93f3440 Bundled output for commit c67fb2d4b6 [skip ci] 2019-01-16 09:05:46 +00:00
Daniël Klabbers
c67fb2d4b6 fixes #1686, unable to edit user password 2019-01-16 09:58:22 +01:00
Daniël Klabbers
1b2d4f1e1d set prefixing indices to be done automatically, now that illuminate can take care of that 2019-01-15 20:49:33 +01:00
Daniël Klabbers
54fdc40d87 further revert #96e2824 2019-01-15 20:49:06 +01:00
Daniël Klabbers
390148456c reverts #96e2824 2019-01-15 20:39:38 +01:00
Daniël Klabbers
167059027e Increasing test coverage (#1711)
* added a few more tests, renamed singular to plural to match controller

* increase error reporting

* removed debugging and wait for tests
2019-01-01 21:02:18 +01:00
Franz Liedke
208bad393f Mail: Add an array of supported drivers
This can be used for e.g. validation, or a dropdown in the frontend.
It can also be extended by extensions, such as flagrow/mail-drivers.

Refs #1169.
2018-12-20 13:36:08 +01:00
Franz Liedke
8a93f8b6b6 Apply fixes from StyleCI (#1714)
[ci skip] [skip ci]
2018-12-20 13:13:58 +01:00
Franz Liedke
9db04a4e19 Register service providers alphabetically
Order should not matter - and this is the only one that can
realistically stay consistent.
2018-12-20 13:13:04 +01:00
Franz Liedke
ac5e26a254 Use a custom service provider for email configuration 2018-12-20 13:10:30 +01:00
Daniël Klabbers
9794a08f39 updated constraint for 5.7 (#1698) 2018-12-20 08:20:52 +10:30
Franz Liedke
ababb8ebef Don't resolve services when binding listeners
Refs #1578.
2018-12-19 22:47:58 +01:00
Franz Liedke
cb3baf9955 Apply fixes from StyleCI (#1713)
[ci skip] [skip ci]
2018-12-19 22:42:54 +01:00
Franz Liedke
dbe8cba14e Avoid unnecessary event subscribers
Refs #1578.
2018-12-19 22:27:32 +01:00
Franz Liedke
9fe671c9bb Fix UpdateServiceProvider
- Shorten registration of routes
- Do not resolve view factory before booting
2018-12-19 22:17:44 +01:00
Franz Liedke
0e5f334a0b Locale: Don't resolve manager just to configure it
Refs #1578.
2018-12-19 22:07:31 +01:00
Franz Liedke
e4514d8413 Shorten registration of routes 2018-12-19 21:57:59 +01:00
Franz Liedke
1080d25561 Frontends: Populate default routes only when they are resolved 2018-12-19 21:55:58 +01:00
Franz Liedke
ba594de13a Make site extenders run after extensions
Fixes #1708.
2018-12-19 21:30:29 +01:00
Daniël Klabbers
209d13affd add 7.3 to travis (#1710) 2018-12-19 18:09:36 +01:00
Daniel Klabbers
671fdec8d0 fixes #1695, post comment count is incorrectly calculated based on all posts, including events 2018-12-19 15:07:32 +01:00
Daniel Klabbers
9eca9192ca fixes a notice due to the forum variable not being defined before compacting 2018-12-19 11:40:48 +01:00
Franz Liedke
3468bdf511 Run local extenders before booting service providers
We still need to discuss the priority of local extenders vs. those
from enabled extensions, but let's first fix the actual bug.

Refs #1708.
2018-12-18 11:16:57 +01:00
Franz Liedke
54503d2c29 API: Populate default routes only when they are resolved
Refs #1708.
2018-12-18 10:33:01 +01:00
Franz Liedke
565131e2a7 Allow passing strings (names of invokable classes) to Formatter extender
In preparation for fixing #1703.
2018-12-15 12:05:17 +01:00
Toby Zerner
f0da3cf304 Remove obsolete binding 2018-12-14 11:28:11 +10:30
Franz Liedke
6acc91577d Apply fixes from StyleCI (#1701)
[ci skip] [skip ci]
2018-12-14 01:48:19 +01:00
Franz Liedke
3e0cd3a21f Use class constant to get qualified class names 2018-12-14 01:47:54 +01:00
Franz Liedke
5c9fa4c62d Get rid of docblocks that don't add information 2018-12-13 23:08:49 +01:00
Franz Liedke
4b00f7996b Early returns 2018-12-13 23:06:59 +01:00
Kirill
b58380e224 Fix incorrect docs link (#1699) 2018-12-13 20:19:13 +01:00
Franz Liedke
b0e996e7ff Merge pull request #1697 from flarum/fl/1578-speed-up-extenders
Do not resolve services in extenders
2018-12-13 10:33:00 +01:00
Franz Liedke
b41d9fb0e7 Inject dependencies when firing events, not before
The event subscriber approach means that dependencies have to be
injected (and thus instantiated, along with all *their* dependencies) at
the time of registering event listeners - even when events are never
fired within a request's lifecycle.

This is unnecessary and causes more classes than necessary to be loaded.

In this case, we can explicitly register event listeners that will
resolve their dependencies when the event is fired, not before.

Refs #1578.
2018-12-13 02:01:50 +01:00
Franz Liedke
ed02eed88f Do not resolve services when extending them
Refs #1578.
2018-12-13 01:58:54 +01:00
David Sevilla Martín
c761802900 Fix DELETE /api/extensions/* returning 500 (#1580)
* Use extension string as parameter for ::disable & ::uninstall

* Remove repeated 'ExtensionManager::disable' call

* Fix StyleCI
2018-12-13 00:16:03 +01:00
Arda Çebi
16eb1fa63b Profile group badge overlapping fix (#1506) 2018-12-12 22:24:30 +01:00
Franz Liedke
0ceb8d64df Update changelog 2018-12-10 22:52:50 +01:00
Franz Liedke
9712eccb03 Add an issue template for security vulnerabilities 2018-12-10 22:43:03 +01:00
David Sevilla Martín
9684fbc4da Add 'hasPermission' helper to Group (#1688)
* Add Group@hasPermission helper

* Improve performance of method
2018-12-10 22:32:21 +01:00
Franz Liedke
67f9375d47 Fix incorrect column name for registration token
Oversight from the database renamings, I suppose.

Fixes #1691.
2018-12-09 23:17:04 +01:00
Toby Zerner
0d16fac001 Performance: Actually make use of the translator cache
We had added a `storage/locale` directory to our skeleton, but we had
forgotten to hook it up with the translator. Enabling caching saves
parsing that locale YAML files on every pageload which should be good
for performance.

The locale cache will be cleared whenever an extension that uses the
`Locales` or `LanguagePack` extenders is enabled/disabled. If debug
mode is ON, then the caching mechanism will automatically check if any
of the loaded YAML files are dirty and update accordingly.
2018-12-07 09:38:08 +10:30
Toby Zerner
a8f5ca8d97 Add another commit 2018-12-07 09:13:44 +10:30
194 changed files with 4408 additions and 2295 deletions

View File

@@ -0,0 +1,8 @@
---
name: "🔒 Security Vulnerability"
about: "When you discover a security issue"
---
If you discover a security vulnerability within Flarum, please send an email to [security@flarum.org](mailto:security@flarum.org) instead.
**DO NOT open an issue on this repository.**
We will address these with the utmost urgency and it will prevent vulnerabilities, which may be abused, from popping up on our issue tracker.

13
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
During the beta phase, we will only patch security vulnerabilities in the latest beta release.
## Reporting a Vulnerability
If you discover a security vulnerability within Flarum, please send an email to security@flarum.org so we can address it promptly.
We will get back to you as time allows.
Discussions may commence internally, so you may not hear back immediately.
When reporting a vulnerability, please provide your GitHub username (if available), so that we can invite you to collaborate on a [security advisory on GitHub](https://help.github.com/en/articles/about-maintainer-security-advisories).

2
.gitignore vendored
View File

@@ -4,6 +4,6 @@ composer.phar
node_modules
.DS_Store
Thumbs.db
/tests/tmp
/tests/integration/tmp
.vagrant
.idea/*

View File

@@ -7,13 +7,14 @@ cache:
install:
- composer install
- mysql -e 'CREATE DATABASE flarum;'
- mysql -e 'CREATE DATABASE flarum_test;'
before_script:
- echo 'error_reporting = E_ALL' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
script:
- vendor/bin/phpunit --coverage-clover=coverage.xml
after_success:
- bash <(curl -s https://codecov.io/bash)
- composer test:setup
- composer test
jobs:
include:
@@ -23,8 +24,8 @@ jobs:
- php: 7.2
env: DB=mysql
- php: 7.2
env: DB=mysql PREFIX=forum_
- php: 7.3
env: DB=mysql
- php: 7.1
addons:
@@ -36,6 +37,14 @@ jobs:
mariadb: '10.2'
env: DB=mariadb
- php: 7.3
addons:
mariadb: '10.2'
env: DB=mariadb
- php: 7.2
env: DB=mysql PREFIX=forum_
- stage: build
language: generic
if: branch = master AND type = push

View File

@@ -1,12 +1,59 @@
# Changelog
## [0.1.0-beta.9](https://github.com/flarum/core/compare/v0.1.0-beta.8.2...v0.1.0-beta.9)
### Added
- New `hasPermission()` helper method for `Group` objects ([9684fbc](https://github.com/flarum/core/commit/9684fbc4da07d32aa322d9228302a23418412cb9))
- Expose supported mail drivers in IoC container ([208bad3](https://github.com/flarum/core/commit/208bad393f37bfdb76007afcddfa4b7451563e9d))
- More test for some API endpoints ([1670590](https://github.com/flarum/core/commit/167059027e5a066d618599c90164ef1b5a509148))
- The `Formatter\Rendering` event now receives the HTTP request instance as well ([0ab9fac](https://github.com/flarum/core/commit/0ab9facc4bd59a260575e6fc650793c663e5866a))
- More and better validation in installer UIs
- Check and enforce minimum MariaDB ([7ff9a90](https://github.com/flarum/core/commit/7ff9a90204923293adc520d3c02dc984845d4f9f))
- Revert publication of assets when installation fails ([ed9591c](https://github.com/flarum/core/commit/ed9591c16fb2ea7a4be3387b805d855a53e0a7d5))
- Benefit from Laravel's database reconnection logic in long-running tasks ([e0becd0](https://github.com/flarum/core/commit/e0becd0c7bda939048923c1f86648793feee78d5))
- The "vendor path" (where Composer dependencies can be found) can now be configured ([5e1680c](https://github.com/flarum/core/commit/5e1680c458cd3ba274faeb92de3ac2053789131e))
### Changed
- Performance: Actually cache translations on disk ([0d16fac](https://github.com/flarum/core/commit/0d16fac001bb735ee66e82871183516aeac269b7))
- Allow per-site extenders to override extension extenders ([ba594de](https://github.com/flarum/core/commit/ba594de13a033480834d53d73f747b05fe9796f8))
- Do not resolve objects from the IoC container (in service providers and extenders) until they are actually used
- Replace event subscribers (that resolve objects from the IoC container) with listeners (that resolve lazily)
- Use custom service provider for Mail component ([ac5e26a](https://github.com/flarum/core/commit/ac5e26a254d89e21bd4c115b6cbd40338e2e4b4b))
- Update to Laravel 5.7, revert custom logic for building database index names
- Refactored installer, extracted Installation class and pipeline for reuse in CLI and web installers ([790d5be](https://github.com/flarum/core/commit/790d5beee5e283178716bc8f9901c758d9e5b6a0))
- Use whitelist for enabling pre-installed extensions during installation ([4585f03](https://github.com/flarum/core/commit/4585f03ee356c92942fbc2ae8c683c651b473954))
- Update minimum MySQL version ([7ff9a90](https://github.com/flarum/core/commit/7ff9a90204923293adc520d3c02dc984845d4f9f))
### Fixed
- Signing up via OAuth providers was broken ([67f9375](https://github.com/flarum/core/commit/67f9375d4745add194ae3249d526197c32fd5461))
- Group badges were overlapping ([16eb1fa](https://github.com/flarum/core/commit/16eb1fa63b6d7b80ec30c24c0e406a2b7ab09934))
- API: Endpoint for uninstalling extensions returned an error ([c761802](https://github.com/flarum/core/commit/c76180290056ddbab67baf5ede814fcedf1dcf14))
- Documentation links in installer were outdated ([b58380e](https://github.com/flarum/core/commit/b58380e224ee54abdade3d0a4cc107ef5c91c9a9))
- Event posts where counted when aggregating user posts ([671fdec](https://github.com/flarum/core/commit/671fdec8d0a092ccceb5d4d5f657d0f4287fc4c7))
- Admins could not reset user passwords ([c67fb2d](https://github.com/flarum/core/commit/c67fb2d4b6a128c71d65dc6703310c0b62f91be2))
- Several down migrations were invalid
- Validation errors on reset password page resulted in HTTP 404 ([4611abe](https://github.com/flarum/core/commit/4611abe5db8b94ca3dc7bf9c447fca7c67358ee3))
- `is:unread` gambit generated an invalid query ([e17bb0b](https://github.com/flarum/core/commit/e17bb0b4331f2c92459292195c6b7db8cde1f9f3))
- Entire forum was breaking when the `custom_less` setting was missing from the database ([bf2c5a5](https://github.com/flarum/core/commit/bf2c5a5564dff3f5ef13efe7a8d69f2617570ce6))
- Dropdown icon was not showing in user card when on user page ([12fdfc9](https://github.com/flarum/core/commit/12fdfc9b544a27f6fe59c82ad6bddd3420cc0181))
- Requests were missing the `original*` attributes, which broke installations in subfolders ([56fde28](https://github.com/flarum/core/commit/56fde28e436f52fee0c03c538f0a6049bc584b53))
- Special characters such as `%` and `_` could return incorrect results ([ee3640e](https://github.com/flarum/core/commit/ee3640e1605ff67fef4b3d5cd0596f14a6ae73c9))
- FontAwesome component package changed paths in version 5.9.0 ([5eb69e1](https://github.com/flarum/core/commit/5eb69e1f59fa73fdfd5badbf41a05a6a040e7426))
- Some server environments had problems accessing the system-wide tmp path for storing JS file maps ([54660eb](https://github.com/flarum/core/commit/54660ebd6311f9ea142f1b573263d0d907400786))
- Content length of posts.content was not migrated to mediumText in 2017 ([590b311](https://github.com/flarum/core/commit/590b3115708bf94a9c7f169d98c6126380c7056e))
- An error occurred when going to the previous route if there was no previous route found ([985b87da](https://github.com/flarum/core/commit/985b87da6c9942c568a1a192e2fdcfde72e030ee))
### Removed
- `php flarum install --defaults` - this was meant to be used in our old development VM ([44c9109](https://github.com/flarum/core/commit/44c91099cd77138bb5fc29f14fb1e81a9781272d))
- Obsolete `id` attributes in JSON-API responses ([ecc3b5e](https://github.com/flarum/core/commit/ecc3b5e2271f8d9b38d52cd54476d86995dbe32e) and [7a44086](https://github.com/flarum/core/commit/7a44086bf3a0e3ba907dceb13d07ac695eca05ea))
## [0.1.0-beta.8.1](https://github.com/flarum/core/compare/v0.1.0-beta.8...v0.1.0-beta.8.1)
### Fixed
- Fix live output in `migrate:reset` command ([f591585](https://github.com/flarum/core/commit/f591585d02f8c4ff0211c5bf4413dd6baa724c05))
- Fix search with database prefix ([7705a2b](https://github.com/flarum/core/commit/7705a2b7d751943ef9d0c7379ec34f8530b99310))
- Fix invalid join time of admin user created by installer ([57f73c9](https://github.com/flarum/core/commit/57f73c9638eeb825f9e336ed3c443afccfd8995e))
- Ensure InnoDB engine is used for all tables ([fb6b51b](https://github.com/flarum/core/commit/fb6b51b1cfef0af399607fe038603c8240800b2b))
- Ensure InnoDB engine is used for all tables ([fb6b51b](https://github.com/flarum/core/commit/fb6b51b1cfef0af399607fe038603c8240800b2b), [6370f7e](https://github.com/flarum/core/commit/6370f7ecffa9ea7d5fb64d9551400edbc63318db))
- Fix dropping foreign keys in `down` migrations ([57d5846](https://github.com/flarum/core/commit/57d5846b647881009d9e60f9ffca20b1bb77776e))
- Fix discussion list scroll position not being maintained when hero is not visible ([40dc6ac](https://github.com/flarum/core/commit/40dc6ac604c2a0973356b38217aa8d09352daae5))
- Fix empty meta description tag ([88e43cc](https://github.com/flarum/core/commit/88e43cc6940ee30d6529e9ce659471ec4fb1c474))

View File

@@ -1,3 +0,0 @@
# Contributing to Flarum
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://flarum.org/docs/contributing.html)** to learn how you can help.

View File

@@ -22,24 +22,24 @@
"require": {
"php": ">=7.1",
"axy/sourcemap": "^0.1.4",
"components/font-awesome": "^5.4.2",
"components/font-awesome": "5.9.*",
"dflydev/fig-cookies": "^1.0.2",
"doctrine/dbal": "^2.7",
"franzl/whoops-middleware": "^0.4.0",
"illuminate/bus": "5.5.*",
"illuminate/cache": "5.5.*",
"illuminate/config": "5.5.*",
"illuminate/container": "5.5.*",
"illuminate/contracts": "5.5.*",
"illuminate/database": "5.5.*",
"illuminate/events": "5.5.*",
"illuminate/filesystem": "5.5.*",
"illuminate/hashing": "5.5.*",
"illuminate/mail": "5.5.*",
"illuminate/session": "5.5.*",
"illuminate/support": "5.5.*",
"illuminate/validation": "5.5.*",
"illuminate/view": "5.5.*",
"illuminate/bus": "5.7.*",
"illuminate/cache": "5.7.*",
"illuminate/config": "5.7.*",
"illuminate/container": "5.7.*",
"illuminate/contracts": "5.7.*",
"illuminate/database": "5.7.*",
"illuminate/events": "5.7.*",
"illuminate/filesystem": "5.7.*",
"illuminate/hashing": "5.7.*",
"illuminate/mail": "5.7.*",
"illuminate/session": "5.7.*",
"illuminate/support": "5.7.*",
"illuminate/validation": "5.7.*",
"illuminate/view": "5.7.*",
"intervention/image": "^2.3.0",
"league/flysystem": "^1.0.11",
"matthiasmullie/minify": "^1.3",
@@ -54,8 +54,7 @@
"psr/http-server-middleware": "^1.0",
"s9e/text-formatter": "^1.2.0",
"symfony/config": "^3.3",
"symfony/console": "^3.3",
"symfony/http-foundation": "^3.3",
"symfony/console": "^4.2",
"symfony/translation": "^3.3",
"symfony/yaml": "^3.3",
"tobscure/json-api": "^0.3.0",
@@ -87,5 +86,14 @@
"branch-alias": {
"dev-master": "0.1.x-dev"
}
},
"scripts": {
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.unit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:setup": "@php tests/integration/setup.php"
}
}

22
js/dist/admin.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
js/dist/forum.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

707
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
"private": true,
"name": "@flarum/core",
"dependencies": {
"bootstrap": "^3.3.7",
"bootstrap": "^3.4.1",
"classnames": "^2.2.5",
"color-thief-browser": "^2.0.2",
"expose-loader": "^0.7.5",
"flarum-webpack-config": "0.1.0-beta.10",
"jquery": "^3.3.1",
"jquery": "^3.4.1",
"jquery.hotkeys": "^0.1.0",
"lodash-es": "^4.17.11",
"m.attrs.bidi": "github:tobscure/m.attrs.bidi",

View File

@@ -84,17 +84,21 @@ export default class EditGroupModal extends Modal {
return items;
}
submitData() {
return {
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
};
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
this.group.save({
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
}, {errorHandler: this.onerror.bind(this)})
this.group.save(this.submitData(), {errorHandler: this.onerror.bind(this)})
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;

View File

@@ -2,36 +2,55 @@ import Page from './Page';
import FieldSet from '../../common/components/FieldSet';
import Button from '../../common/components/Button';
import Alert from '../../common/components/Alert';
import Select from '../../common/components/Select';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import saveSettings from '../utils/saveSettings';
export default class MailPage extends Page {
init() {
super.init();
this.loading = false;
this.loading = true;
this.saving = false;
this.fields = [
'mail_driver',
'mail_host',
'mail_from',
'mail_port',
'mail_username',
'mail_password',
'mail_encryption'
];
this.driverFields = {};
this.fields = ['mail_driver', 'mail_from'];
this.values = {};
const settings = app.data.settings;
this.fields.forEach(key => this.values[key] = m.prop(settings[key]));
this.localeOptions = {};
const locales = app.locales;
for (const i in locales) {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
app.request({
method: 'GET',
url: app.forum.attribute('apiUrl') + '/mail-drivers'
}).then(response => {
this.driverFields = response['data'].reduce(
(hash, driver) => ({...hash, [driver['id']]: driver['attributes']['fields']}),
{}
);
Object.keys(this.driverFields).flatMap(key => this.driverFields[key]).forEach(
key => {
this.fields.push(key);
this.values[key] = m.prop(settings[key]);
}
);
this.loading = false;
m.redraw();
});
}
view() {
if (this.loading) {
return (
<div className="MailPage">
<div className="container">
<LoadingIndicator />
</div>
</div>
);
}
return (
<div className="MailPage">
<div className="container">
@@ -41,36 +60,6 @@ export default class MailPage extends Page {
{app.translator.trans('core.admin.email.text')}
</div>
{FieldSet.component({
label: app.translator.trans('core.admin.email.server_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.driver_label')}</label>
<input className="FormControl" value={this.values.mail_driver() || ''} oninput={m.withAttr('value', this.values.mail_driver)} />
<label>{app.translator.trans('core.admin.email.host_label')}</label>
<input className="FormControl" value={this.values.mail_host() || ''} oninput={m.withAttr('value', this.values.mail_host)} />
<label>{app.translator.trans('core.admin.email.port_label')}</label>
<input className="FormControl" value={this.values.mail_port() || ''} oninput={m.withAttr('value', this.values.mail_port)} />
<label>{app.translator.trans('core.admin.email.encryption_label')}</label>
<input className="FormControl" value={this.values.mail_encryption() || ''} oninput={m.withAttr('value', this.values.mail_encryption)} />
</div>
]
})}
{FieldSet.component({
label: app.translator.trans('core.admin.email.account_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.username_label')}</label>
<input className="FormControl" value={this.values.mail_username() || ''} oninput={m.withAttr('value', this.values.mail_username)} />
<label>{app.translator.trans('core.admin.email.password_label')}</label>
<input className="FormControl" value={this.values.mail_password() || ''} oninput={m.withAttr('value', this.values.mail_password)} />
</div>
]
})}
{FieldSet.component({
label: app.translator.trans('core.admin.email.addresses_heading'),
className: 'MailPage-MailSettings',
@@ -82,11 +71,35 @@ export default class MailPage extends Page {
]
})}
{FieldSet.component({
label: app.translator.trans('core.admin.email.driver_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.driver_label')}</label>
<Select value={this.values.mail_driver()} options={Object.keys(this.driverFields).reduce((memo, val) => ({...memo, [val]: val}), {})} onchange={this.values.mail_driver} />
</div>
]
})}
{Object.keys(this.driverFields[this.values.mail_driver()]).length > 0 && FieldSet.component({
label: app.translator.trans(`core.admin.email.${this.values.mail_driver()}_heading`),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
{this.driverFields[this.values.mail_driver()].flatMap(field => [
<label>{app.translator.trans(`core.admin.email.${field}_label`)}</label>,
<input className="FormControl" value={this.values[field]() || ''} oninput={m.withAttr('value', this.values[field])} />
])}
</div>
]
})}
{Button.component({
type: 'submit',
className: 'Button Button--primary',
children: app.translator.trans('core.admin.email.submit_button'),
loading: this.loading,
loading: this.saving,
disabled: !this.changed()
})}
</form>
@@ -102,9 +115,9 @@ export default class MailPage extends Page {
onsubmit(e) {
e.preventDefault();
if (this.loading) return;
if (this.saving) return;
this.loading = true;
this.saving = true;
app.alerts.dismiss(this.successAlert);
const settings = {};
@@ -117,7 +130,7 @@ export default class MailPage extends Page {
})
.catch(() => {})
.then(() => {
this.loading = false;
this.saving = false;
m.redraw();
});
}

View File

@@ -77,7 +77,7 @@ export default class EditUserModal extends Modal {
<label>{app.translator.trans('core.forum.edit_user.password_heading')}</label>
<div>
<label className="checkbox">
<input type="checkbox" checked={this.setPassword()} onChange={e => {
<input type="checkbox" onchange={e => {
this.setPassword(e.target.checked);
m.redraw(true);
if (e.target.checked) this.$('[name=password]').select();

View File

@@ -86,6 +86,10 @@ export default class History {
* @public
*/
back() {
if (! this.canGoBack()) {
return this.home();
}
this.stack.pop();
m.route(this.getCurrent().url);

View File

@@ -29,6 +29,10 @@
margin-bottom: 7px;
}
.Select {
display: block;
}
:last-child {
margin-bottom: 0;
}

View File

@@ -1,7 +1,7 @@
@import "fontawesome";
@import "fa-brands";
@import "fa-regular";
@import "fa-solid";
@import "brands";
@import "regular";
@import "solid";
@fa-font-path: "./fonts";
@import "normalize";

View File

@@ -78,6 +78,7 @@
&, > li {
display: inline-block;
margin-right: 5px
}
}
.UserCard-info {

View File

@@ -1,10 +1,5 @@
.UserPage {
.UserCard-controls {
float: right;
.Dropdown-toggle .Button-icon {
display: none;
}
.UserHero .Dropdown-toggle .Button-icon {
display: none;
}
}

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('access_tokens', function (Blueprint $table) use ($schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) use ($schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,21 +9,18 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropPrimary(['id']);
$table->renameColumn('id', 'key');
$table->unique('key');
Migration::fixIndexNames($schema, $table);
});
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->increments('id');
$table->string('allowed_ips')->nullable();
$table->string('scopes')->nullable();
@@ -32,25 +29,19 @@ return [
$table->dateTime('last_activity_at')->nullable();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('id', 'allowed_ips', 'user_id', 'scopes', 'created_at');
Migration::fixIndexNames($schema, $table);
});
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropUnique(['key']);
$table->renameColumn('key', 'id');
$table->primary('id');
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -34,26 +33,22 @@ return [
'last_post_id' => $selectId('posts', 'last_post_id'),
]);
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('last_posted_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('hidden_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('first_post_id')->references('id')->on('posts')->onDelete('set null');
$table->foreign('last_post_id')->references('id')->on('posts')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['last_posted_user_id']);
$table->dropForeign(['hidden_user_id']);
$table->dropForeign(['first_post_id']);
$table->dropForeign(['last_post_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -27,20 +26,16 @@ return [
})
->delete();
$schema->table('discussion_user', function (Blueprint $table) use ($schema) {
$schema->table('discussion_user', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('discussion_id')->references('id')->on('discussions')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussion_user', function (Blueprint $table) use ($schema) {
$schema->table('discussion_user', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['discussion_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('email_tokens', function (Blueprint $table) use ($schema) {
$schema->table('email_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('email_tokens', function (Blueprint $table) use ($schema) {
$schema->table('email_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('group_permission', function (Blueprint $table) use ($schema) {
$schema->table('group_permission', function (Blueprint $table) {
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('group_permission', function (Blueprint $table) use ($schema) {
$schema->table('group_permission', function (Blueprint $table) {
$table->dropForeign(['group_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -27,20 +26,16 @@ return [
})
->delete();
$schema->table('group_user', function (Blueprint $table) use ($schema) {
$schema->table('group_user', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('group_user', function (Blueprint $table) use ($schema) {
$schema->table('group_user', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['group_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -31,20 +30,16 @@ return [
})
->update(['from_user_id' => null]);
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('from_user_id')->references('id')->on('users')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['from_user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('password_tokens', function (Blueprint $table) use ($schema) {
$schema->table('password_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('password_tokens', function (Blueprint $table) use ($schema) {
$schema->table('password_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@@ -32,23 +31,18 @@ return [
'hidden_user_id' => $selectId('users', 'hidden_user_id'),
]);
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('edited_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('hidden_user_id')->references('id')->on('users')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['discussion_id']);
$table->dropForeign(['edited_user_id']);
$table->dropForeign(['hidden_user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,30 +9,25 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('users', function (Blueprint $table) use ($schema) {
$schema->table('users', function (Blueprint $table) {
$table->index('joined_at');
$table->index('last_seen_at');
$table->index('discussion_count');
$table->index('comment_count');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('users', function (Blueprint $table) use ($schema) {
$schema->table('users', function (Blueprint $table) {
$table->dropIndex(['joined_at']);
$table->dropIndex(['last_seen_at']);
$table->dropIndex(['discussion_count']);
$table->dropIndex(['comment_count']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,13 +9,12 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->index('last_posted_at');
$table->index('last_posted_user_id');
$table->index('created_at');
@@ -23,13 +22,11 @@ return [
$table->index('comment_count');
$table->index('participant_count');
$table->index('hidden_at');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->dropIndex(['last_posted_at']);
$table->dropIndex(['last_posted_user_id']);
$table->dropIndex(['created_at']);
@@ -37,8 +34,6 @@ return [
$table->dropIndex(['comment_count']);
$table->dropIndex(['participant_count']);
$table->dropIndex(['hidden_at']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,24 +9,19 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->index('user_id');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->dropIndex(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -9,28 +9,23 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->index(['discussion_id', 'number']);
$table->index(['discussion_id', 'created_at']);
$table->index(['user_id', 'created_at']);
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->dropIndex(['discussion_id', 'number']);
$table->dropIndex(['discussion_id', 'created_at']);
$table->dropIndex(['user_id', 'created_at']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@@ -24,7 +24,7 @@ return [
},
'down' => function (Builder $schema) {
$schema->table('auth_tokens', function (Blueprint $table) {
$schema->table('registration_tokens', function (Blueprint $table) {
$table->dropColumn('provider', 'identifier', 'user_attributes');
$table->string('payload', 150)->change();

View File

@@ -12,16 +12,17 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
// https://github.com/doctrine/dbal/issues/2566#issuecomment-480217999
return [
'up' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) {
$table->mediumText('content')->change();
$table->mediumText('content')->comment(' ')->change();
});
},
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) {
$table->text('content')->change();
$table->text('content')->comment('')->change();
});
}
];

View File

@@ -12,8 +12,11 @@
namespace Flarum\Admin;
use Flarum\Event\ConfigureMiddleware;
use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Flarum\Foundation\Event\ClearingCache;
use Flarum\Frontend\AddLocaleAssets;
use Flarum\Frontend\AddTranslations;
use Flarum\Frontend\Compiler\Source\SourceCollector;
@@ -22,6 +25,8 @@ use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Zend\Stratigility\MiddlewarePipe;
class AdminServiceProvider extends AbstractServiceProvider
@@ -36,7 +41,10 @@ class AdminServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.admin.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.admin.middleware', function (Application $app) {
@@ -53,6 +61,7 @@ class AdminServiceProvider extends AbstractServiceProvider
$pipe->pipe($app->make(HttpMiddleware\StartSession::class));
$pipe->pipe($app->make(HttpMiddleware\RememberFromCookie::class));
$pipe->pipe($app->make(HttpMiddleware\AuthenticateWithSession::class));
$pipe->pipe($app->make(HttpMiddleware\CheckCsrfToken::class));
$pipe->pipe($app->make(HttpMiddleware\SetLocale::class));
$pipe->pipe($app->make(Middleware\RequireAdministrateAbility::class));
@@ -98,15 +107,30 @@ class AdminServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.admin.routes'));
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
$this->app->make('events')->subscribe(
new RecompileFrontendAssets(
$this->app->make('flarum.assets.admin'),
$this->app->make('flarum.locales')
)
$events = $this->app->make('events');
$events->listen(
[Enabled::class, Disabled::class, ClearingCache::class],
function () {
$recompile = new RecompileFrontendAssets(
$this->app->make('flarum.assets.admin'),
$this->app->make(LocaleManager::class)
);
$recompile->flush();
}
);
$events->listen(
Saved::class,
function (Saved $event) {
$recompile = new RecompileFrontendAssets(
$this->app->make('flarum.assets.admin'),
$this->app->make(LocaleManager::class)
);
$recompile->whenSettingsSaved($event);
}
);
}

View File

@@ -40,7 +40,10 @@ class ApiServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.api.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.api.middleware', function (Application $app) {
@@ -54,6 +57,7 @@ class ApiServiceProvider extends AbstractServiceProvider
$pipe->pipe($app->make(HttpMiddleware\RememberFromCookie::class));
$pipe->pipe($app->make(HttpMiddleware\AuthenticateWithSession::class));
$pipe->pipe($app->make(HttpMiddleware\AuthenticateWithHeader::class));
$pipe->pipe($app->make(HttpMiddleware\CheckCsrfToken::class));
$pipe->pipe($app->make(HttpMiddleware\SetLocale::class));
event(new ConfigureMiddleware($pipe, 'api'));
@@ -90,8 +94,6 @@ class ApiServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.api.routes'));
$this->registerNotificationSerializers();
AbstractSerializeController::setContainer($this->app);

View File

@@ -102,7 +102,7 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
);
$serializer = static::$container->make($this->serializer);
$serializer->setActor($request->getAttribute('actor'));
$serializer->setRequest($request);
$element = $this->createElement($data, $serializer)
->with($this->extractInclude($request))

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Api\Controller;
use Flarum\Api\Serializer\MailDriverSerializer;
use Flarum\User\AssertPermissionTrait;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class ListMailDriversController extends AbstractListController
{
use AssertPermissionTrait;
/**
* {@inheritdoc}
*/
public $serializer = MailDriverSerializer::class;
/**
* {@inheritdoc}
*/
protected function data(ServerRequestInterface $request, Document $document)
{
$this->assertAdmin($request->getAttribute('actor'));
$drivers = self::$container->make('mail.supported_drivers');
array_walk($drivers, function (&$driver, $key) {
$driver = [
'id' => $key,
'driver' => self::$container->make($driver),
];
});
return $drivers;
}
}

View File

@@ -38,9 +38,10 @@ class UninstallExtensionController extends AbstractDeleteController
$name = array_get($request->getQueryParams(), 'name');
$extension = $this->extensions->getExtension($name);
if ($this->extensions->getExtension($name) == null) {
return;
}
$this->extensions->disable($extension);
$this->extensions->uninstall($extension);
$this->extensions->uninstall($name);
}
}

View File

@@ -20,6 +20,7 @@ use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use InvalidArgumentException;
use LogicException;
use Psr\Http\Message\ServerRequestInterface as Request;
use Tobscure\JsonApi\AbstractSerializer as BaseAbstractSerializer;
use Tobscure\JsonApi\Collection;
use Tobscure\JsonApi\Relationship;
@@ -28,6 +29,11 @@ use Tobscure\JsonApi\SerializerInterface;
abstract class AbstractSerializer extends BaseAbstractSerializer
{
/**
* @var Request
*/
protected $request;
/**
* @var User
*/
@@ -43,6 +49,23 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
*/
protected static $container;
/**
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* @param Request $request
*/
public function setRequest(Request $request)
{
$this->request = $request;
$this->actor = $request->getAttribute('actor');
}
/**
* @return User
*/
@@ -51,14 +74,6 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
return $this->actor;
}
/**
* @param User $actor
*/
public function setActor(User $actor)
{
$this->actor = $actor;
}
/**
* {@inheritdoc}
*/
@@ -231,7 +246,7 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
{
$serializer = static::$container->make($class);
$serializer->setActor($this->actor);
$serializer->setRequest($this->request);
return $serializer;
}

View File

@@ -37,14 +37,13 @@ class BasicPostSerializer extends AbstractSerializer
}
$attributes = [
'id' => (int) $post->id,
'number' => (int) $post->number,
'createdAt' => $this->formatDate($post->created_at),
'contentType' => $post->type
];
if ($post instanceof CommentPost) {
$attributes['contentHtml'] = $post->content_html;
$attributes['contentHtml'] = $post->formatContent($this->request);
} else {
$attributes['content'] = $post->content;
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Api\Serializer;
use Flarum\Mail\DriverInterface;
use InvalidArgumentException;
class MailDriverSerializer extends AbstractSerializer
{
/**
* {@inheritdoc}
*/
protected $type = 'mail-drivers';
/**
* {@inheritdoc}
*
* @param \Flarum\Mail\DriverInterface $driver
* @throws InvalidArgumentException
*/
protected function getDefaultAttributes($driver)
{
if (! ($driver['driver'] instanceof DriverInterface)) {
throw new InvalidArgumentException(
get_class($this).' can only serialize instances of '.DriverInterface::class
);
}
$driver = $driver['driver'];
return [
'fields' => $driver->availableSettings(),
];
}
public function getId($model)
{
return $model['id'];
}
}

View File

@@ -44,7 +44,6 @@ class NotificationSerializer extends AbstractSerializer
}
return [
'id' => (int) $notification->id,
'contentType' => $notification->type,
'content' => $notification->data,
'createdAt' => $this->formatDate($notification->created_at),

View File

@@ -43,8 +43,6 @@ class PostSerializer extends BasicPostSerializer
$canEdit = $gate->allows('edit', $post);
if ($post instanceof CommentPost) {
$attributes['contentHtml'] = $post->content_html;
if ($canEdit) {
$attributes['content'] = $post->content;
}

View File

@@ -308,4 +308,11 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
'cache.clear',
$route->toController(Controller\ClearCacheController::class)
);
// List available mail drivers and their configuration fields
$map->get(
'/mail-drivers',
'mailDrivers.index',
$route->toController(Controller\ListMailDriversController::class)
);
};

View File

@@ -55,13 +55,13 @@ abstract class AbstractModel extends Eloquent
{
parent::boot();
static::saved(function (AbstractModel $model) {
static::saved(function (self $model) {
foreach ($model->releaseAfterSaveCallbacks() as $callback) {
$callback($model);
}
});
static::deleted(function (AbstractModel $model) {
static::deleted(function (self $model) {
foreach ($model->releaseAfterDeleteCallbacks() as $callback) {
$callback($model);
}

View File

@@ -12,21 +12,21 @@
namespace Flarum\Database\Console;
use Flarum\Console\AbstractCommand;
use Illuminate\Contracts\Container\Container;
use Flarum\Foundation\Application;
class MigrateCommand extends AbstractCommand
{
/**
* @var Container
* @var Application
*/
protected $container;
protected $app;
/**
* @param Container $container
* @param Application $application
*/
public function __construct(Container $container)
public function __construct(Application $application)
{
$this->container = $container;
$this->app = $application;
parent::__construct();
}
@@ -55,16 +55,16 @@ class MigrateCommand extends AbstractCommand
public function upgrade()
{
$this->container->bind('Illuminate\Database\Schema\Builder', function ($container) {
return $container->make('Illuminate\Database\ConnectionInterface')->getSchemaBuilder();
$this->app->bind('Illuminate\Database\Schema\Builder', function ($app) {
return $app->make('Illuminate\Database\ConnectionInterface')->getSchemaBuilder();
});
$migrator = $this->container->make('Flarum\Database\Migrator');
$migrator = $this->app->make('Flarum\Database\Migrator');
$migrator->setOutput($this->output);
$migrator->run(__DIR__.'/../../../migrations');
$extensions = $this->container->make('Flarum\Extension\ExtensionManager');
$extensions = $this->app->make('Flarum\Extension\ExtensionManager');
$extensions->getMigrator()->setOutput($this->output);
foreach ($extensions->getEnabledExtensions() as $name => $extension) {
@@ -75,13 +75,13 @@ class MigrateCommand extends AbstractCommand
}
}
$this->container->make('Flarum\Settings\SettingsRepositoryInterface')->set('version', $this->container->version());
$this->app->make('Flarum\Settings\SettingsRepositoryInterface')->set('version', $this->app->version());
$this->info('Publishing assets...');
$this->container->make('files')->copyDirectory(
base_path().'/vendor/components/font-awesome/webfonts',
public_path().'/assets/fonts'
$this->app->make('files')->copyDirectory(
$this->app->vendorPath().'/components/font-awesome/webfonts',
$this->app->publicPath().'/assets/fonts'
);
}
}

View File

@@ -12,8 +12,9 @@
namespace Flarum\Database;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Database\ConnectionResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Capsule\Manager;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\ConnectionResolverInterface;
class DatabaseServiceProvider extends AbstractServiceProvider
{
@@ -22,29 +23,39 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->singleton('flarum.db', function () {
$factory = new ConnectionFactory($this->app);
$this->app->singleton(Manager::class, function ($app) {
$manager = new Manager($app);
$dbConfig = $this->app->config('database');
$dbConfig['engine'] = 'InnoDB';
$connection = $factory->make($dbConfig);
$connection->setEventDispatcher($this->app->make('Illuminate\Contracts\Events\Dispatcher'));
$config = $app->config('database');
$config['engine'] = 'InnoDB';
$config['prefix_indexes'] = true;
return $connection;
$manager->addConnection($config, 'flarum');
return $manager;
});
$this->app->alias('flarum.db', 'Illuminate\Database\ConnectionInterface');
$this->app->singleton(ConnectionResolverInterface::class, function ($app) {
$manager = $app->make(Manager::class);
$manager->setAsGlobal();
$manager->bootEloquent();
$this->app->singleton('Illuminate\Database\ConnectionResolverInterface', function () {
$resolver = new ConnectionResolver([
'flarum' => $this->app->make('flarum.db'),
]);
$resolver->setDefaultConnection('flarum');
$dbManager = $manager->getDatabaseManager();
$dbManager->setDefaultConnection('flarum');
return $resolver;
return $dbManager;
});
$this->app->alias('Illuminate\Database\ConnectionResolverInterface', 'db');
$this->app->alias(ConnectionResolverInterface::class, 'db');
$this->app->singleton(ConnectionInterface::class, function ($app) {
$resolver = $app->make(ConnectionResolverInterface::class);
return $resolver->connection();
});
$this->app->alias(ConnectionInterface::class, 'db.connection');
$this->app->alias(ConnectionInterface::class, 'flarum.db');
}
/**
@@ -52,7 +63,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface'));
AbstractModel::setConnectionResolver($this->app->make(ConnectionResolverInterface::class));
AbstractModel::setEventDispatcher($this->app->make('events'));
}
}

View File

@@ -31,8 +31,6 @@ abstract class Migration
'up' => function (Builder $schema) use ($name, $definition) {
$schema->create($name, function (Blueprint $table) use ($schema, $definition) {
$definition($table);
static::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) use ($name) {
@@ -68,8 +66,6 @@ abstract class Migration
$type = array_shift($options);
$table->addColumn($type, $columnName, $options);
}
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) use ($tableName, $columnDefinitions) {
@@ -193,27 +189,4 @@ abstract class Migration
}
];
}
/**
* Add a prefix to index names on the given table blueprint.
*
* Laravel 5.5 doesn't automatically add the table prefix to index
* names, but this has been fixed in 5.7. We will manually fix the
* names for now, and we can remove this when we upgrade to 5.7.
*/
public static function fixIndexNames(Builder $schema, Blueprint $table)
{
$indexCommands = [
'unique', 'index', 'spatialIndex', 'foreign',
'dropUnique', 'dropIndex', 'dropSpatialIndex', 'dropForeign'
];
$prefix = $schema->getConnection()->getTablePrefix();
foreach ($table->getCommands() as $command) {
if (in_array($command->name, $indexCommands)) {
$command->index = $prefix.$command->index;
}
}
}
}

View File

@@ -13,6 +13,7 @@ namespace Flarum\Database;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Illuminate\Filesystem\Filesystem;
class MigrationServiceProvider extends AbstractServiceProvider
{
@@ -21,12 +22,12 @@ class MigrationServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->singleton('Flarum\Database\MigrationRepositoryInterface', function ($app) {
$this->app->singleton(MigrationRepositoryInterface::class, function ($app) {
return new DatabaseMigrationRepository($app['flarum.db'], 'migrations');
});
$this->app->bind(MigrationCreator::class, function (Application $app) {
return new MigrationCreator($app->make('Illuminate\Filesystem\Filesystem'), $app->basePath());
return new MigrationCreator($app->make(Filesystem::class), $app->basePath());
});
}
}

View File

@@ -146,7 +146,9 @@ class Migrator
*/
public function reset($path, Extension $extension = null)
{
$migrations = array_reverse($this->repository->getRan($extension->getId()));
$migrations = array_reverse($this->repository->getRan(
$extension ? $extension->getId() : null
));
$count = count($migrations);

View File

@@ -98,7 +98,7 @@ class Discussion extends AbstractModel
{
parent::boot();
static::deleting(function (Discussion $discussion) {
static::deleting(function (self $discussion) {
Notification::whereSubjectModel(Post::class)
->whereIn('subject_id', function ($query) use ($discussion) {
$query->select('id')->from('posts')->where('discussion_id', $discussion->id);
@@ -106,13 +106,13 @@ class Discussion extends AbstractModel
->delete();
});
static::deleted(function (Discussion $discussion) {
static::deleted(function (self $discussion) {
$discussion->raise(new Deleted($discussion));
Notification::whereSubject($discussion)->delete();
});
static::saving(function (Discussion $discussion) {
static::saving(function (self $discussion) {
$event = new GetModelIsPrivate($discussion);
$discussion->is_private = static::$dispatcher->until($event) === true;

View File

@@ -20,9 +20,6 @@ use Illuminate\Contracts\Events\Dispatcher;
class DiscussionMetadataUpdater
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Posted::class, [$this, 'whenPostWasPosted']);
@@ -31,9 +28,6 @@ class DiscussionMetadataUpdater
$events->listen(Restored::class, [$this, 'whenPostWasRestored']);
}
/**
* @param Posted $event
*/
public function whenPostWasPosted(Posted $event)
{
$discussion = $event->post->discussion;
@@ -46,9 +40,6 @@ class DiscussionMetadataUpdater
}
}
/**
* @param \Flarum\Post\Event\Deleted $event
*/
public function whenPostWasDeleted(Deleted $event)
{
$this->removePost($event->post);
@@ -60,17 +51,11 @@ class DiscussionMetadataUpdater
}
}
/**
* @param \Flarum\Post\Event\Hidden $event
*/
public function whenPostWasHidden(Hidden $event)
{
$this->removePost($event->post);
}
/**
* @param Restored $event
*/
public function whenPostWasRestored(Restored $event)
{
$discussion = $event->post->discussion;
@@ -83,9 +68,6 @@ class DiscussionMetadataUpdater
}
}
/**
* @param Post $post
*/
protected function removePost(Post $post)
{
$discussion = $post->discussion;

View File

@@ -15,7 +15,6 @@ use Flarum\Discussion\Event\Renamed;
use Flarum\Notification\Blueprint\DiscussionRenamedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\DiscussionRenamedPost;
use Illuminate\Contracts\Events\Dispatcher;
class DiscussionRenamedLogger
{
@@ -24,26 +23,12 @@ class DiscussionRenamedLogger
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Renamed::class, [$this, 'whenDiscussionWasRenamed']);
}
/**
* @param \Flarum\Discussion\Event\Renamed $event
*/
public function whenDiscussionWasRenamed(Renamed $event)
public function handle(Renamed $event)
{
$post = DiscussionRenamedPost::reply(
$event->discussion->id,

View File

@@ -49,8 +49,8 @@ class DiscussionRepository
*/
public function getReadIds(User $user)
{
return Discussion::leftJoin('discussions_users', 'discussions_users.discussion_id', '=', 'discussions.id')
->where('user_id', $user->id)
return Discussion::leftJoin('discussion_user', 'discussion_user.discussion_id', '=', 'discussions.id')
->where('discussion_user.user_id', $user->id)
->whereColumn('last_read_post_number', '>=', 'last_post_number')
->pluck('id')
->all();

View File

@@ -11,6 +11,7 @@
namespace Flarum\Discussion;
use Flarum\Discussion\Event\Renamed;
use Flarum\Foundation\AbstractServiceProvider;
class DiscussionServiceProvider extends AbstractServiceProvider
@@ -24,6 +25,9 @@ class DiscussionServiceProvider extends AbstractServiceProvider
$events->subscribe(DiscussionMetadataUpdater::class);
$events->subscribe(DiscussionPolicy::class);
$events->subscribe(DiscussionRenamedLogger::class);
$events->listen(
Renamed::class, DiscussionRenamedLogger::class
);
}
}

View File

@@ -52,7 +52,7 @@ class FulltextGambit implements GambitInterface
// discussions that have a relevant title or that contain relevant posts.
$query
->addSelect('posts_ft.most_relevant_post_id')
->join(
->leftJoin(
new Expression('('.$subquery->toSql().') '.$grammar->wrapTable('posts_ft')),
'posts_ft.discussion_id', '=', 'discussions.id'
)

View File

@@ -21,7 +21,7 @@ class Formatter implements ExtenderInterface, LifecycleInterface
{
protected $callback;
public function configure(callable $callback)
public function configure($callback)
{
$this->callback = $callback;
@@ -34,8 +34,14 @@ class Formatter implements ExtenderInterface, LifecycleInterface
$events->listen(
Configuring::class,
function (Configuring $event) {
call_user_func($this->callback, $event->configurator);
function (Configuring $event) use ($container) {
if (is_string($this->callback)) {
$callback = $container->make($this->callback);
} else {
$callback = $this->callback;
}
$callback($event->configurator);
}
);
}

View File

@@ -11,12 +11,18 @@
namespace Flarum\Extend;
use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled;
use Flarum\Extension\Extension;
use Flarum\Foundation\Event\ClearingCache;
use Flarum\Frontend\Assets;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\Frontend as ActualFrontend;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Illuminate\Contracts\Container\Container;
class Frontend implements ExtenderInterface
@@ -107,11 +113,29 @@ class Frontend implements ExtenderInterface
return $container->make('flarum.assets.factory')($this->frontend);
});
$container->make('events')->subscribe(
new RecompileFrontendAssets(
$container->make($abstract),
$container->make('flarum.locales')
)
/** @var \Illuminate\Contracts\Events\Dispatcher $events */
$events = $container->make('events');
$events->listen(
[Enabled::class, Disabled::class, ClearingCache::class],
function () use ($container, $abstract) {
$recompile = new RecompileFrontendAssets(
$container->make($abstract),
$container->make(LocaleManager::class)
);
$recompile->flush();
}
);
$events->listen(
Saved::class,
function (Saved $event) use ($container, $abstract) {
$recompile = new RecompileFrontendAssets(
$container->make($abstract),
$container->make(LocaleManager::class)
);
$recompile->whenSettingsSaved($event);
}
);
}
}
@@ -122,15 +146,20 @@ class Frontend implements ExtenderInterface
return;
}
$routes = $container->make("flarum.$this->frontend.routes");
$factory = $container->make(RouteHandlerFactory::class);
$container->resolving(
"flarum.{$this->frontend}.routes",
function (RouteCollection $collection, Container $container) {
/** @var RouteHandlerFactory $factory */
$factory = $container->make(RouteHandlerFactory::class);
foreach ($this->routes as $route) {
$routes->get(
$route['path'], $route['name'],
$factory->toFrontend($this->frontend, $route['content'])
);
}
foreach ($this->routes as $route) {
$collection->get(
$route['path'], $route['name'],
$factory->toFrontend($this->frontend, $route['content'])
);
}
}
);
}
private function registerContent(Container $container)

View File

@@ -18,7 +18,7 @@ use Illuminate\Contracts\Container\Container;
use InvalidArgumentException;
use RuntimeException;
class LanguagePack implements ExtenderInterface
class LanguagePack implements ExtenderInterface, LifecycleInterface
{
public function extend(Container $container, Extension $extension = null)
{
@@ -37,8 +37,16 @@ class LanguagePack implements ExtenderInterface
);
}
/** @var LocaleManager $locales */
$locales = $container->make(LocaleManager::class);
$container->resolving(
LocaleManager::class,
function (LocaleManager $locales) use ($extension, $locale, $title) {
$this->registerLocale($locales, $extension, $locale, $title);
}
);
}
private function registerLocale(LocaleManager $locales, Extension $extension, $locale, $title)
{
$locales->addLocale($locale, $title);
$directory = $extension->getPath().'/locale';
@@ -63,4 +71,14 @@ class LanguagePack implements ExtenderInterface
}
}
}
public function onEnable(Container $container, Extension $extension)
{
$container->make('flarum.locales')->clearCache();
}
public function onDisable(Container $container, Extension $extension)
{
$container->make('flarum.locales')->clearCache();
}
}

View File

@@ -16,7 +16,7 @@ use Flarum\Extension\Extension;
use Flarum\Locale\LocaleManager;
use Illuminate\Contracts\Container\Container;
class Locales implements ExtenderInterface
class Locales implements ExtenderInterface, LifecycleInterface
{
protected $directory;
@@ -27,23 +27,35 @@ class Locales implements ExtenderInterface
public function extend(Container $container, Extension $extension = null)
{
/** @var LocaleManager $locales */
$locales = $container->make(LocaleManager::class);
$container->resolving(
LocaleManager::class,
function (LocaleManager $locales) {
foreach (new DirectoryIterator($this->directory) as $file) {
if (! $file->isFile()) {
continue;
}
foreach (new DirectoryIterator($this->directory) as $file) {
if (! $file->isFile()) {
continue;
$extension = $file->getExtension();
if (! in_array($extension, ['yml', 'yaml'])) {
continue;
}
$locales->addTranslations(
$file->getBasename(".$extension"),
$file->getPathname()
);
}
}
);
}
$extension = $file->getExtension();
if (! in_array($extension, ['yml', 'yaml'])) {
continue;
}
public function onEnable(Container $container, Extension $extension)
{
$container->make(LocaleManager::class)->clearCache();
}
$locales->addTranslations(
$file->getBasename(".$extension"),
$file->getPathname()
);
}
public function onDisable(Container $container, Extension $extension)
{
$container->make(LocaleManager::class)->clearCache();
}
}

View File

@@ -12,6 +12,7 @@
namespace Flarum\Extend;
use Flarum\Extension\Extension;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Illuminate\Contracts\Container\Container;
@@ -69,19 +70,21 @@ class Routes implements ExtenderInterface
return;
}
/** @var \Flarum\Http\RouteCollection $collection */
$collection = $container->make("flarum.{$this->appName}.routes");
$container->resolving(
"flarum.{$this->appName}.routes",
function (RouteCollection $collection, Container $container) {
/** @var RouteHandlerFactory $factory */
$factory = $container->make(RouteHandlerFactory::class);
/** @var RouteHandlerFactory $factory */
$factory = $container->make(RouteHandlerFactory::class);
foreach ($this->routes as $route) {
$collection->addRoute(
$route['method'],
$route['path'],
$route['name'],
$factory->toController($route['handler'])
);
}
foreach ($this->routes as $route) {
$collection->addRoute(
$route['method'],
$route['path'],
$route['name'],
$factory->toController($route['handler'])
);
}
}
);
}
}

View File

@@ -14,7 +14,6 @@ namespace Flarum\Extension;
use Flarum\Extension\Event\Disabling;
use Flarum\Http\Exception\ForbiddenException;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Events\Dispatcher;
class DefaultLanguagePackGuard
{
@@ -28,26 +27,17 @@ class DefaultLanguagePackGuard
$this->settings = $settings;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
public function handle(Disabling $event)
{
$events->listen(Disabling::class, [$this, 'whenExtensionWillBeDisabled']);
}
if (! in_array('flarum-locale', $event->extension->extra)) {
return;
}
/**
* @param Disabling $event
* @throws ForbiddenException
*/
public function whenExtensionWillBeDisabled(Disabling $event)
{
if (in_array('flarum-locale', $event->extension->extra)) {
$defaultLocale = $this->settings->get('default_locale');
$locale = array_get($event->extension->extra, 'flarum-locale.code');
if ($locale === $defaultLocale) {
throw new ForbiddenException('You cannot disable the default language pack!');
}
$defaultLocale = $this->settings->get('default_locale');
$locale = array_get($event->extension->extra, 'flarum-locale.code');
if ($locale === $defaultLocale) {
throw new ForbiddenException('You cannot disable the default language pack!');
}
}
}

View File

@@ -11,12 +11,18 @@
namespace Flarum\Extension;
use Flarum\Database\Migrator;
use Flarum\Extend\Compat;
use Flarum\Extend\LifecycleInterface;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\MountManager;
use League\Flysystem\Plugin\ListFiles;
/**
* @property string $name
@@ -307,6 +313,25 @@ class Extension implements Arrayable
return realpath($this->path.'/assets/') !== false;
}
public function copyAssetsTo(FilesystemInterface $target)
{
if (! $this->hasAssets()) {
return;
}
$mount = new MountManager([
'source' => $source = new Filesystem(new Local($this->getPath().'/assets')),
'target' => $target,
]);
$source->addPlugin(new ListFiles);
$assetFiles = $source->listFiles('/', true);
foreach ($assetFiles as $file) {
$mount->copy("source://$file[path]", "target://extensions/$this->id/$file[path]");
}
}
/**
* Tests whether the extension has migrations.
*
@@ -317,6 +342,19 @@ class Extension implements Arrayable
return realpath($this->path.'/migrations/') !== false;
}
public function migrate(Migrator $migrator, $direction = 'up')
{
if (! $this->hasMigrations()) {
return;
}
if ($direction == 'up') {
return $migrator->run($this->getPath().'/migrations', $this);
} else {
return $migrator->reset($this->getPath().'/migrations', $this);
}
}
/**
* Generates an array result for the object.
*

View File

@@ -67,11 +67,11 @@ class ExtensionManager
*/
public function getExtensions()
{
if (is_null($this->extensions) && $this->filesystem->exists($this->app->basePath().'/vendor/composer/installed.json')) {
if (is_null($this->extensions) && $this->filesystem->exists($this->app->vendorPath().'/composer/installed.json')) {
$extensions = new Collection();
// Load all packages installed by composer.
$installed = json_decode($this->filesystem->get($this->app->basePath().'/vendor/composer/installed.json'), true);
$installed = json_decode($this->filesystem->get($this->app->vendorPath().'/composer/installed.json'), true);
foreach ($installed as $package) {
if (Arr::get($package, 'type') != 'flarum-extension' || empty(Arr::get($package, 'name'))) {
@@ -222,26 +222,16 @@ class ExtensionManager
* Runs the database migrations for the extension.
*
* @param Extension $extension
* @param bool|true $up
* @param string $direction
* @return void
*/
public function migrate(Extension $extension, $up = true)
public function migrate(Extension $extension, $direction = 'up')
{
if (! $extension->hasMigrations()) {
return;
}
$migrationDir = $extension->getPath().'/migrations';
$this->app->bind('Illuminate\Database\Schema\Builder', function ($container) {
return $container->make('Illuminate\Database\ConnectionInterface')->getSchemaBuilder();
});
if ($up) {
$this->migrator->run($migrationDir, $extension);
} else {
$this->migrator->reset($migrationDir, $extension);
}
$extension->migrate($this->migrator, $direction);
}
/**
@@ -252,7 +242,7 @@ class ExtensionManager
*/
public function migrateDown(Extension $extension)
{
return $this->migrate($extension, false);
return $this->migrate($extension, 'down');
}
/**
@@ -303,7 +293,7 @@ class ExtensionManager
*/
public function getEnabled()
{
return json_decode($this->config->get('extensions_enabled'), true);
return json_decode($this->config->get('extensions_enabled'), true) ?? [];
}
/**
@@ -336,6 +326,6 @@ class ExtensionManager
*/
protected function getExtensionsDir()
{
return $this->app->basePath().'/vendor';
return $this->app->vendorPath();
}
}

View File

@@ -11,6 +11,7 @@
namespace Flarum\Extension;
use Flarum\Extension\Event\Disabling;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container;
@@ -27,7 +28,7 @@ class ExtensionServiceProvider extends AbstractServiceProvider
// Boot extensions when the app is booting. This must be done as a boot
// listener on the app rather than in the service provider's boot method
// below, so that extensions have a chance to register things on the
// container before the core boot code runs.
// container before the core boots up (and starts resolving services).
$this->app->booting(function (Container $app) {
$app->make('flarum.extensions')->extend($app);
});
@@ -38,8 +39,9 @@ class ExtensionServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$events = $this->app->make('events');
$events->subscribe(DefaultLanguagePackGuard::class);
$this->app->make('events')->listen(
Disabling::class,
DefaultLanguagePackGuard::class
);
}
}

View File

@@ -11,6 +11,7 @@
namespace Flarum\Formatter\Event;
use Psr\Http\Message\ServerRequestInterface;
use s9e\TextFormatter\Renderer;
class Rendering
@@ -30,15 +31,22 @@ class Rendering
*/
public $xml;
/**
* @var ServerRequestInterface
*/
public $request;
/**
* @param Renderer $renderer
* @param mixed $context
* @param string $xml
* @param ServerRequestInterface|null $request
*/
public function __construct(Renderer $renderer, $context, &$xml)
public function __construct(Renderer $renderer, $context, &$xml, ServerRequestInterface $request = null)
{
$this->renderer = $renderer;
$this->context = $context;
$this->xml = &$xml;
$this->request = $request;
}
}

View File

@@ -16,6 +16,7 @@ use Flarum\Formatter\Event\Parsing;
use Flarum\Formatter\Event\Rendering;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Events\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Unparser;
@@ -69,13 +70,14 @@ class Formatter
*
* @param string $xml
* @param mixed $context
* @param ServerRequestInterface|null $request
* @return string
*/
public function render($xml, $context = null)
public function render($xml, $context = null, ServerRequestInterface $request = null)
{
$renderer = $this->getRenderer();
$this->events->dispatch(new Rendering($renderer, $context, $xml));
$this->events->dispatch(new Rendering($renderer, $context, $xml, $request));
return $renderer->render($xml);
}

View File

@@ -56,7 +56,7 @@ class ResponseFactory
$provided,
$registration->getSuggested(),
[
'token' => $token->id,
'token' => $token->token,
'provided' => array_keys($provided)
]
));

View File

@@ -58,7 +58,7 @@ class Index
$apiDocument = $this->getApiDocument($request->getAttribute('actor'), $params);
$document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page', 'forum'));
$document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page'));
$document->payload['apiDocument'] = $apiDocument;
return $document;

View File

@@ -18,6 +18,7 @@ use Flarum\User\PasswordToken;
use Flarum\User\UserValidator;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Validation\Factory;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -86,9 +87,9 @@ class SavePasswordController implements RequestHandlerInterface
throw new ValidationException($validator);
}
} catch (ValidationException $e) {
$request->getAttribute('session')->put('errors', $e->errors());
$request->getAttribute('session')->put('errors', new MessageBag($e->errors()));
return new RedirectResponse($this->url->to('forum')->route('resetPassword', ['token' => $token->id]));
return new RedirectResponse($this->url->to('forum')->route('resetPassword', ['token' => $token->token]));
}
$token->user->changePassword($password);

View File

@@ -13,9 +13,12 @@ namespace Flarum\Forum;
use Flarum\Event\ConfigureForumRoutes;
use Flarum\Event\ConfigureMiddleware;
use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled;
use Flarum\Formatter\Formatter;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Flarum\Foundation\Event\ClearingCache;
use Flarum\Frontend\AddLocaleAssets;
use Flarum\Frontend\AddTranslations;
use Flarum\Frontend\Assets;
@@ -25,6 +28,9 @@ use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Flarum\Settings\Event\Saving;
use Flarum\Settings\SettingsRepositoryInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Zend\Stratigility\MiddlewarePipe;
@@ -41,7 +47,10 @@ class ForumServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.forum.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.forum.middleware', function (Application $app) {
@@ -59,6 +68,7 @@ class ForumServiceProvider extends AbstractServiceProvider
$pipe->pipe($app->make(HttpMiddleware\StartSession::class));
$pipe->pipe($app->make(HttpMiddleware\RememberFromCookie::class));
$pipe->pipe($app->make(HttpMiddleware\AuthenticateWithSession::class));
$pipe->pipe($app->make(HttpMiddleware\CheckCsrfToken::class));
$pipe->pipe($app->make(HttpMiddleware\SetLocale::class));
$pipe->pipe($app->make(HttpMiddleware\ShareErrorsFromSession::class));
@@ -85,7 +95,7 @@ class ForumServiceProvider extends AbstractServiceProvider
$assets->css(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../less/forum.less');
$sources->addString(function () {
return $this->app->make(SettingsRepositoryInterface::class)->get('custom_less');
return $this->app->make(SettingsRepositoryInterface::class)->get('custom_less', '');
});
});
@@ -105,8 +115,6 @@ class ForumServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.forum.routes'));
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.forum');
$this->app->make('view')->share([
@@ -116,19 +124,45 @@ class ForumServiceProvider extends AbstractServiceProvider
$events = $this->app->make('events');
$events->subscribe(
new RecompileFrontendAssets(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales')
)
$events->listen(
[Enabled::class, Disabled::class, ClearingCache::class],
function () {
$recompile = new RecompileFrontendAssets(
$this->app->make('flarum.assets.forum'),
$this->app->make(LocaleManager::class)
);
$recompile->flush();
}
);
$events->subscribe(
new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
)
$events->listen(
Saved::class,
function (Saved $event) {
$recompile = new RecompileFrontendAssets(
$this->app->make('flarum.assets.forum'),
$this->app->make(LocaleManager::class)
);
$recompile->whenSettingsSaved($event);
$validator = new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
);
$validator->whenSettingsSaved($event);
}
);
$events->listen(
Saving::class,
function (Saving $event) {
$validator = new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
);
$validator->whenSettingsSaving($event);
}
);
}

View File

@@ -19,7 +19,6 @@ use Flarum\Settings\Event\Saving;
use Flarum\Settings\OverrideSettingsRepository;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Filesystem\FilesystemAdapter;
use League\Flysystem\Adapter\NullAdapter;
use League\Flysystem\Filesystem;
@@ -54,67 +53,55 @@ class ValidateCustomLess
$this->container = $container;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Saving::class, [$this, 'whenSettingsSaving']);
$events->listen(Saved::class, [$this, 'whenSettingsSaved']);
}
/**
* @param Saving $event
* @throws ValidationException
*/
public function whenSettingsSaving(Saving $event)
{
if (isset($event->settings['custom_less'])) {
// We haven't saved the settings yet, but we want to trial a full
// recompile of the CSS to see if this custom LESS will break
// anything. In order to do that, we will temporarily override the
// settings repository with the new settings so that the recompile
// is effective. We will also use a dummy filesystem so that nothing
// is actually written yet.
$settings = $this->container->make(SettingsRepositoryInterface::class);
$this->container->extend(
SettingsRepositoryInterface::class,
function ($settings) use ($event) {
return new OverrideSettingsRepository($settings, $event->settings);
}
);
$assetsDir = $this->assets->getAssetsDir();
$this->assets->setAssetsDir(new FilesystemAdapter(new Filesystem(new NullAdapter)));
try {
$this->assets->makeCss()->commit();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->commit();
}
} catch (Less_Exception_Parser $e) {
throw new ValidationException(['custom_less' => $e->getMessage()]);
}
$this->assets->setAssetsDir($assetsDir);
$this->container->instance(SettingsRepositoryInterface::class, $settings);
if (! isset($event->settings['custom_less'])) {
return;
}
}
/**
* @param Saved $event
*/
public function whenSettingsSaved(Saved $event)
{
if (isset($event->settings['custom_less'])) {
$this->assets->makeCss()->flush();
// We haven't saved the settings yet, but we want to trial a full
// recompile of the CSS to see if this custom LESS will break
// anything. In order to do that, we will temporarily override the
// settings repository with the new settings so that the recompile
// is effective. We will also use a dummy filesystem so that nothing
// is actually written yet.
$settings = $this->container->make(SettingsRepositoryInterface::class);
$this->container->extend(
SettingsRepositoryInterface::class,
function ($settings) use ($event) {
return new OverrideSettingsRepository($settings, $event->settings);
}
);
$assetsDir = $this->assets->getAssetsDir();
$this->assets->setAssetsDir(new FilesystemAdapter(new Filesystem(new NullAdapter)));
try {
$this->assets->makeCss()->commit();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->flush();
$this->assets->makeLocaleCss($locale)->commit();
}
} catch (Less_Exception_Parser $e) {
throw new ValidationException(['custom_less' => $e->getMessage()]);
}
$this->assets->setAssetsDir($assetsDir);
$this->container->instance(SettingsRepositoryInterface::class, $settings);
}
public function whenSettingsSaved(Saved $event)
{
if (! isset($event->settings['custom_less'])) {
return;
}
$this->assets->makeCss()->flush();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->flush();
}
}
}

View File

@@ -25,7 +25,7 @@ class Application extends Container implements ApplicationContract
*
* @var string
*/
const VERSION = '0.1.0-beta.8.1';
const VERSION = '0.1.0-beta.9';
/**
* The base path for the Flarum installation.
@@ -41,6 +41,20 @@ class Application extends Container implements ApplicationContract
*/
protected $publicPath;
/**
* The custom storage path defined by the developer.
*
* @var string
*/
protected $storagePath;
/**
* A custom vendor path to find dependencies in non-standard environments.
*
* @var string
*/
protected $vendorPath;
/**
* Indicates if the application has "booted".
*
@@ -83,13 +97,6 @@ class Application extends Container implements ApplicationContract
*/
protected $deferredServices = [];
/**
* The custom storage path defined by the developer.
*
* @var string
*/
protected $storagePath;
/**
* Create a new Flarum application instance.
*
@@ -226,7 +233,7 @@ class Application extends Container implements ApplicationContract
*/
protected function bindPathsInContainer()
{
foreach (['base', 'public', 'storage'] as $path) {
foreach (['base', 'public', 'storage', 'vendor'] as $path) {
$this->instance('path.'.$path, $this->{$path.'Path'}());
}
}
@@ -261,6 +268,16 @@ class Application extends Container implements ApplicationContract
return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
}
/**
* Get the path to the vendor directory where dependencies are installed.
*
* @return string
*/
public function vendorPath()
{
return $this->vendorPath ?: $this->basePath.DIRECTORY_SEPARATOR.'vendor';
}
/**
* Set the storage directory.
*
@@ -276,6 +293,21 @@ class Application extends Container implements ApplicationContract
return $this;
}
/**
* Set the vendor directory.
*
* @param string $path
* @return $this
*/
public function useVendorPath($path)
{
$this->vendorPath = $path;
$this->instance('path.vendor', $path);
return $this;
}
/**
* Get or check the current application environment.
*
@@ -680,7 +712,7 @@ class Application extends Container implements ApplicationContract
public function registerCoreContainerAliases()
{
$aliases = [
'app' => [\Flarum\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],

View File

@@ -22,6 +22,7 @@ use Illuminate\Contracts\Container\Container;
use Middlewares\BasePath;
use Middlewares\BasePathRouter;
use Middlewares\RequestHandler;
use Zend\Stratigility\Middleware\OriginalMessages;
use Zend\Stratigility\MiddlewarePipe;
class InstalledApp implements AppInterface
@@ -42,6 +43,11 @@ class InstalledApp implements AppInterface
$this->config = $config;
}
public function getContainer()
{
return $this->container;
}
/**
* @return \Psr\Http\Server\RequestHandlerInterface
*/
@@ -56,6 +62,7 @@ class InstalledApp implements AppInterface
$pipe = new MiddlewarePipe;
$pipe->pipe(new BasePath($this->basePath()));
$pipe->pipe(new OriginalMessages);
$pipe->pipe(
new BasePathRouter([
$this->subPath('api') => 'flarum.api.middleware',
@@ -84,7 +91,7 @@ class InstalledApp implements AppInterface
/**
* @return \Psr\Http\Server\RequestHandlerInterface
*/
public function getUpdaterHandler()
private function getUpdaterHandler()
{
$pipe = new MiddlewarePipe;
$pipe->pipe(

View File

@@ -23,10 +23,10 @@ use Flarum\Forum\ForumServiceProvider;
use Flarum\Frontend\FrontendServiceProvider;
use Flarum\Group\GroupServiceProvider;
use Flarum\Locale\LocaleServiceProvider;
use Flarum\Mail\MailServiceProvider;
use Flarum\Notification\NotificationServiceProvider;
use Flarum\Post\PostServiceProvider;
use Flarum\Search\SearchServiceProvider;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\Settings\SettingsServiceProvider;
use Flarum\Update\UpdateServiceProvider;
use Flarum\User\SessionServiceProvider;
@@ -36,10 +36,10 @@ use Illuminate\Cache\Repository as CacheRepository;
use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Cache\Store;
use Illuminate\Contracts\Container\Container;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemServiceProvider;
use Illuminate\Hashing\HashServiceProvider;
use Illuminate\Mail\MailServiceProvider;
use Illuminate\Validation\ValidationServiceProvider;
use Illuminate\View\ViewServiceProvider;
use Monolog\Formatter\LineFormatter;
@@ -73,7 +73,7 @@ class InstalledSite implements SiteInterface
/**
* Create and boot a Flarum application instance.
*
* @return AppInterface
* @return InstalledApp
*/
public function bootApp(): AppInterface
{
@@ -100,6 +100,10 @@ class InstalledSite implements SiteInterface
$laravel->useStoragePath($this->paths['storage']);
if (isset($this->paths['vendor'])) {
$laravel->useVendorPath($this->paths['vendor']);
}
$laravel->instance('env', 'production');
$laravel->instance('flarum.config', $this->config);
$laravel->instance('config', $config = $this->getIlluminateConfig($laravel));
@@ -107,51 +111,42 @@ class InstalledSite implements SiteInterface
$this->registerLogger($laravel);
$this->registerCache($laravel);
$laravel->register(DatabaseServiceProvider::class);
$laravel->register(MigrationServiceProvider::class);
$laravel->register(SettingsServiceProvider::class);
$laravel->register(LocaleServiceProvider::class);
$laravel->register(AdminServiceProvider::class);
$laravel->register(ApiServiceProvider::class);
$laravel->register(BusServiceProvider::class);
$laravel->register(FilesystemServiceProvider::class);
$laravel->register(HashServiceProvider::class);
$laravel->register(MailServiceProvider::class);
$laravel->register(ViewServiceProvider::class);
$laravel->register(ValidationServiceProvider::class);
$settings = $laravel->make(SettingsRepositoryInterface::class);
$config->set('mail.driver', $settings->get('mail_driver'));
$config->set('mail.host', $settings->get('mail_host'));
$config->set('mail.port', $settings->get('mail_port'));
$config->set('mail.from.address', $settings->get('mail_from'));
$config->set('mail.from.name', $settings->get('forum_title'));
$config->set('mail.encryption', $settings->get('mail_encryption'));
$config->set('mail.username', $settings->get('mail_username'));
$config->set('mail.password', $settings->get('mail_password'));
$laravel->register(DatabaseServiceProvider::class);
$laravel->register(DiscussionServiceProvider::class);
$laravel->register(ExtensionServiceProvider::class);
$laravel->register(FilesystemServiceProvider::class);
$laravel->register(FormatterServiceProvider::class);
$laravel->register(ForumServiceProvider::class);
$laravel->register(FrontendServiceProvider::class);
$laravel->register(GroupServiceProvider::class);
$laravel->register(HashServiceProvider::class);
$laravel->register(LocaleServiceProvider::class);
$laravel->register(MailServiceProvider::class);
$laravel->register(MigrationServiceProvider::class);
$laravel->register(NotificationServiceProvider::class);
$laravel->register(PostServiceProvider::class);
$laravel->register(SearchServiceProvider::class);
$laravel->register(SessionServiceProvider::class);
$laravel->register(UserServiceProvider::class);
$laravel->register(SettingsServiceProvider::class);
$laravel->register(UpdateServiceProvider::class);
$laravel->register(UserServiceProvider::class);
$laravel->register(ValidationServiceProvider::class);
$laravel->register(ViewServiceProvider::class);
$laravel->register(ApiServiceProvider::class);
$laravel->register(ForumServiceProvider::class);
$laravel->register(AdminServiceProvider::class);
$laravel->register(ExtensionServiceProvider::class);
$laravel->booting(function (Container $app) {
// Run all local-site extenders before booting service providers
// (but after those from "real" extensions, which have been set up
// in a service provider above).
foreach ($this->extenders as $extension) {
$extension->extend($app);
}
});
$laravel->boot();
foreach ($this->extenders as $extension) {
$extension->extend($laravel);
}
return $laravel;
}

View File

@@ -59,6 +59,10 @@ class UninstalledSite implements SiteInterface
$laravel->useStoragePath($this->paths['storage']);
if (isset($this->paths['vendor'])) {
$laravel->useVendorPath($this->paths['vendor']);
}
$laravel->instance('env', 'production');
$laravel->instance('flarum.config', []);
$laravel->instance('config', $config = $this->getIlluminateConfig());

View File

@@ -58,7 +58,7 @@ class JsCompiler extends RevisionCompiler
$this->assetsDir->put($file, implode("\n", $output));
$mapTemp = tempnam(sys_get_temp_dir(), $mapFile);
$mapTemp = @tempnam(storage_path('tmp'), $mapFile);
$map->save($mapTemp);
$this->assetsDir->put($mapFile, file_get_contents($mapTemp));
@unlink($mapTemp);

View File

@@ -30,7 +30,7 @@ class FrontendServiceProvider extends AbstractServiceProvider
);
$assets->setLessImportDirs([
$this->app->basePath().'/vendor/components/font-awesome/less' => ''
$this->app->vendorPath().'/components/font-awesome/less' => ''
]);
$assets->css([$this, 'addBaseCss']);
@@ -72,8 +72,8 @@ class FrontendServiceProvider extends AbstractServiceProvider
public function addBaseCss(SourceCollector $sources)
{
$sources->addFile(base_path().'/vendor/flarum/core/less/common/variables.less');
$sources->addFile(base_path().'/vendor/flarum/core/less/common/mixins.less');
$sources->addFile(__DIR__.'/../../less/common/variables.less');
$sources->addFile(__DIR__.'/../../less/common/mixins.less');
$this->addLessVariables($sources);
}

View File

@@ -11,12 +11,8 @@
namespace Flarum\Frontend;
use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled;
use Flarum\Foundation\Event\ClearingCache;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Illuminate\Contracts\Events\Dispatcher;
class RecompileFrontendAssets
{
@@ -40,17 +36,6 @@ class RecompileFrontendAssets
$this->locales = $locales;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Saved::class, [$this, 'whenSettingsSaved']);
$events->listen(Enabled::class, [$this, 'flush']);
$events->listen(Disabled::class, [$this, 'flush']);
$events->listen(ClearingCache::class, [$this, 'flush']);
}
public function whenSettingsSaved(Saved $event)
{
if (preg_grep('/^theme_/i', array_keys($event->settings))) {

View File

@@ -62,7 +62,7 @@ class Group extends AbstractModel
{
parent::boot();
static::deleted(function (Group $group) {
static::deleted(function (self $group) {
$group->raise(new Deleted($group));
});
}
@@ -126,4 +126,19 @@ class Group extends AbstractModel
{
return $this->hasMany(Permission::class);
}
/**
* Check whether the group has a certain permission.
*
* @param string $permission
* @return bool
*/
public function hasPermission($permission)
{
if ($this->id == self::ADMINISTRATOR_ID) {
return true;
}
return $this->permissions->contains('permission', $permission);
}
}

View File

@@ -15,4 +15,8 @@ use Exception;
class TokenMismatchException extends Exception
{
public function __construct($message = null, $code = 419, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -40,6 +40,7 @@ class AuthenticateWithHeader implements Middleware
$request = $request->withAttribute('apiKey', $key);
$request = $request->withAttribute('bypassFloodgate', true);
$request = $request->withAttribute('bypassCsrfToken', true);
} elseif ($token = AccessToken::find($id)) {
$token->touch();

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Http\Middleware;
use Flarum\Http\Exception\TokenMismatchException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as Handler;
class CheckCsrfToken implements Middleware
{
public function process(Request $request, Handler $handler): Response
{
if (in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {
return $handler->handle($request);
}
if ($request->getAttribute('bypassCsrfToken', false)) {
return $handler->handle($request);
}
if ($this->tokensMatch($request)) {
return $handler->handle($request);
}
throw new TokenMismatchException('CSRF token did not match');
}
private function tokensMatch(Request $request): bool
{
$expected = (string) $request->getAttribute('session')->token();
$provided = $request->getParsedBody()['csrfToken'] ??
$request->getHeaderLine('X-CSRF-Token');
return hash_equals($expected, $provided);
}
}

View File

@@ -67,7 +67,7 @@ class StartSession implements Middleware
return $this->withSessionCookie($response, $session);
}
private function makeSession(Request $request)
private function makeSession(Request $request): Store
{
return new Store(
$this->config['cookie'],
@@ -76,12 +76,12 @@ class StartSession implements Middleware
);
}
private function withCsrfTokenHeader(Response $response, Session $session)
private function withCsrfTokenHeader(Response $response, Session $session): Response
{
return $response->withHeader('X-CSRF-Token', $session->token());
}
private function withSessionCookie(Response $response, Session $session)
private function withSessionCookie(Response $response, Session $session): Response
{
return FigResponseCookies::set(
$response,
@@ -89,7 +89,7 @@ class StartSession implements Middleware
);
}
private function getSessionLifetimeInSeconds()
private function getSessionLifetimeInSeconds(): int
{
return $this->config['lifetime'] * 60;
}

58
src/Install/AdminUser.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Install;
use Carbon\Carbon;
use Illuminate\Hashing\BcryptHasher;
class AdminUser
{
private $username;
private $password;
private $email;
public function __construct($username, $password, $email)
{
$this->username = $username;
$this->password = $password;
$this->email = $email;
$this->validate();
}
public function getUsername()
{
return $this->username;
}
public function getAttributes(): array
{
return [
'username' => $this->username,
'email' => $this->email,
'password' => (new BcryptHasher)->make($this->password),
'joined_at' => Carbon::now(),
'is_email_confirmed' => 1,
];
}
private function validate()
{
if (! filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
throw new ValidationFailed('You must enter a valid email.');
}
if (! $this->username || preg_match('/[^a-z0-9_-]/i', $this->username)) {
throw new ValidationFailed('Username can only contain letters, numbers, underscores, and dashes.');
}
}
}

View File

@@ -11,15 +11,9 @@
namespace Flarum\Install\Console;
use Flarum\Install\Installation;
interface DataProviderInterface
{
public function getDatabaseConfiguration();
public function getBaseUrl();
public function getAdminUser();
public function getSettings();
public function isDebugMode(): bool;
public function configure(Installation $installation): Installation;
}

View File

@@ -1,111 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Install\Console;
class DefaultsDataProvider implements DataProviderInterface
{
protected $databaseConfiguration = [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'flarum',
'username' => 'root',
'password' => '',
'prefix' => '',
'port' => '3306',
];
protected $debug = false;
protected $baseUrl = 'http://flarum.local';
protected $adminUser = [
'username' => 'admin',
'password' => 'password',
'password_confirmation' => 'password',
'email' => 'admin@example.com',
];
protected $settings = [
'allow_post_editing' => 'reply',
'allow_renaming' => '10',
'allow_sign_up' => '1',
'custom_less' => '',
'default_locale' => 'en',
'default_route' => '/all',
'extensions_enabled' => '[]',
'forum_title' => 'Development Forum',
'forum_description' => '',
'mail_driver' => 'mail',
'mail_from' => 'noreply@flarum.dev',
'theme_colored_header' => '0',
'theme_dark_mode' => '0',
'theme_primary_color' => '#4D698E',
'theme_secondary_color' => '#4D698E',
'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to Development Forum',
];
public function getDatabaseConfiguration()
{
return $this->databaseConfiguration;
}
public function setDatabaseConfiguration(array $databaseConfiguration)
{
$this->databaseConfiguration = $databaseConfiguration;
}
public function getBaseUrl()
{
return $this->baseUrl;
}
public function setBaseUrl($baseUrl)
{
$this->baseUrl = $baseUrl;
}
public function getAdminUser()
{
return $this->adminUser;
}
public function setAdminUser(array $adminUser)
{
$this->adminUser = $adminUser;
}
public function getSettings()
{
return $this->settings;
}
public function setSettings(array $settings)
{
$this->settings = $settings;
}
public function setSetting($key, $value)
{
$this->settings[$key] = $value;
}
public function isDebugMode(): bool
{
return $this->debug;
}
public function setDebugMode(bool $debug = true)
{
$this->debug = $debug;
}
}

View File

@@ -12,12 +12,14 @@
namespace Flarum\Install\Console;
use Exception;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Yaml\Yaml;
class FileDataProvider implements DataProviderInterface
{
protected $default;
protected $debug = false;
protected $baseUrl = null;
protected $databaseConfiguration = [];
@@ -26,9 +28,6 @@ class FileDataProvider implements DataProviderInterface
public function __construct(InputInterface $input)
{
// Get default configuration
$this->default = new DefaultsDataProvider();
// Get configuration file path
$configurationFile = $input->getOption('file');
@@ -55,28 +54,35 @@ class FileDataProvider implements DataProviderInterface
}
}
public function getDatabaseConfiguration()
public function configure(Installation $installation): Installation
{
return $this->databaseConfiguration + $this->default->getDatabaseConfiguration();
return $installation
->debugMode($this->debug)
->baseUrl($this->baseUrl ?? 'http://flarum.local')
->databaseConfig($this->getDatabaseConfiguration())
->adminUser($this->getAdminUser())
->settings($this->settings);
}
public function getBaseUrl()
private function getDatabaseConfiguration(): DatabaseConfig
{
return (! is_null($this->baseUrl)) ? $this->baseUrl : $this->default->getBaseUrl();
return new DatabaseConfig(
$this->databaseConfiguration['driver'] ?? 'mysql',
$this->databaseConfiguration['host'] ?? 'localhost',
$this->databaseConfiguration['port'] ?? 3306,
$this->databaseConfiguration['database'] ?? 'flarum',
$this->databaseConfiguration['username'] ?? 'root',
$this->databaseConfiguration['password'] ?? '',
$this->databaseConfiguration['prefix'] ?? ''
);
}
public function getAdminUser()
private function getAdminUser(): AdminUser
{
return $this->adminUser + $this->default->getAdminUser();
}
public function getSettings()
{
return $this->settings + $this->default->getSettings();
}
public function isDebugMode(): bool
{
return $this->debug;
return new AdminUser(
$this->adminUser['username'] ?? 'admin',
$this->adminUser['password'] ?? 'password',
$this->adminUser['email'] ?? 'admin@example.com'
);
}
}

View File

@@ -11,65 +11,32 @@
namespace Flarum\Install\Console;
use Carbon\Carbon;
use Exception;
use Flarum\Console\AbstractCommand;
use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator;
use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\Application as FlarumApplication;
use Flarum\Foundation\Site;
use Flarum\Group\Group;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Settings\DatabaseSettingsRepository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Validation\Factory;
use PDO;
use Flarum\Install\Installation;
use Flarum\Install\Pipeline;
use Flarum\Install\Step;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends AbstractCommand
{
/**
* @var Installation
*/
protected $installation;
/**
* @var DataProviderInterface
*/
protected $dataSource;
/**
* @var Application
* @param Installation $installation
*/
protected $application;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @var ConnectionInterface
*/
protected $db;
/**
* @var Migrator
*/
protected $migrator;
/**
* @param Application $application
* @param Filesystem $filesystem
*/
public function __construct(Application $application, Filesystem $filesystem)
public function __construct(Installation $installation)
{
$this->application = $application;
$this->installation = $installation;
parent::__construct();
$this->filesystem = $filesystem;
}
protected function configure()
@@ -77,12 +44,6 @@ class InstallCommand extends AbstractCommand
$this
->setName('install')
->setDescription("Run Flarum's installation migration and seeds")
->addOption(
'defaults',
'd',
InputOption::VALUE_NONE,
'Create default settings and user'
)
->addOption(
'file',
'f',
@@ -104,274 +65,64 @@ class InstallCommand extends AbstractCommand
{
$this->init();
$prerequisites = $this->getPrerequisites();
$prerequisites->check();
$errors = $prerequisites->getErrors();
$problems = $this->installation->prerequisites()->problems();
if (empty($errors)) {
if ($problems->isEmpty()) {
$this->info('Installing Flarum...');
$this->install();
$this->info('DONE.');
} else {
$this->output->writeln(
'<error>Please fix the following errors before we can continue with the installation.</error>'
);
$this->showErrors($errors);
$this->showProblems($problems);
}
}
protected function init()
{
if ($this->dataSource === null) {
if ($this->input->getOption('defaults')) {
$this->dataSource = new DefaultsDataProvider();
} elseif ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input);
} else {
$this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
}
if ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input);
} else {
$this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
}
}
public function setDataSource(DataProviderInterface $dataSource)
{
$this->dataSource = $dataSource;
}
protected function install()
{
try {
$this->dbConfig = $this->dataSource->getDatabaseConfiguration();
$pipeline = $this->dataSource->configure(
$this->installation->configPath($this->input->getOption('config'))
)->build();
$validation = $this->getValidator()->make(
$this->dbConfig,
[
'driver' => 'required|in:mysql',
'host' => 'required',
'database' => 'required|string',
'username' => 'required|string',
'prefix' => 'nullable|alpha_dash|max:10',
'port' => 'nullable|integer|min:1|max:65535',
]
);
if ($validation->fails()) {
throw new Exception(implode("\n", call_user_func_array('array_merge', $validation->getMessageBag()->toArray())));
}
$this->baseUrl = $this->dataSource->getBaseUrl();
$this->settings = $this->dataSource->getSettings();
$this->adminUser = $admin = $this->dataSource->getAdminUser();
if (strlen($admin['password']) < 8) {
throw new Exception('Password must be at least 8 characters.');
}
if ($admin['password'] !== $admin['password_confirmation']) {
throw new Exception('The password did not match its confirmation.');
}
if (! filter_var($admin['email'], FILTER_VALIDATE_EMAIL)) {
throw new Exception('You must enter a valid email.');
}
if (! $admin['username'] || preg_match('/[^a-z0-9_-]/i', $admin['username'])) {
throw new Exception('Username can only contain letters, numbers, underscores, and dashes.');
}
$this->storeConfiguration($this->dataSource->isDebugMode());
$this->runMigrations();
$this->writeSettings();
$this->createAdminUser();
$this->publishAssets();
// Now that the installation of core is complete, boot up a new
// application instance before enabling extensions so that all of
// the application services are available.
Site::fromPaths([
'base' => $this->application->basePath(),
'public' => $this->application->publicPath(),
'storage' => $this->application->storagePath(),
])->bootApp();
$this->application = FlarumApplication::getInstance();
$this->enableBundledExtensions();
} catch (Exception $e) {
@unlink($this->getConfigFile());
throw $e;
}
$this->runPipeline($pipeline);
}
protected function storeConfiguration(bool $debugMode)
private function runPipeline(Pipeline $pipeline)
{
$dbConfig = $this->dbConfig;
$config = [
'debug' => $debugMode,
'database' => $laravelDbConfig = [
'driver' => $dbConfig['driver'],
'host' => $dbConfig['host'],
'database' => $dbConfig['database'],
'username' => $dbConfig['username'],
'password' => $dbConfig['password'],
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => $dbConfig['prefix'],
'port' => $dbConfig['port'],
'strict' => false
],
'url' => $this->baseUrl,
'paths' => [
'api' => 'api',
'admin' => 'admin',
],
];
$this->info('Testing config');
$factory = new ConnectionFactory($this->application);
$laravelDbConfig['engine'] = 'InnoDB';
$this->db = $factory->make($laravelDbConfig);
$version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
if (version_compare($version, '5.5.0', '<')) {
throw new Exception('MySQL version too low. You need at least MySQL 5.5.');
}
$repository = new DatabaseMigrationRepository(
$this->db, 'migrations'
);
$files = $this->application->make('files');
$this->migrator = new Migrator($repository, $this->db, $files);
$this->info('Writing config');
file_put_contents(
$this->getConfigFile(),
'<?php return '.var_export($config, true).';'
);
$pipeline
->on('start', function (Step $step) {
$this->output->write($step->getMessage().'...');
})->on('end', function () {
$this->output->write("<info>done</info>\n");
})->on('fail', function () {
$this->output->write("<error>failed</error>\n");
$this->output->writeln('Rolling back...');
})->on('rollback', function (Step $step) {
$this->output->writeln($step->getMessage().' (rollback)');
})
->run();
}
protected function runMigrations()
protected function showProblems($problems)
{
$this->migrator->setOutput($this->output);
$this->migrator->getRepository()->createRepository();
$this->migrator->run(__DIR__.'/../../../migrations');
}
protected function writeSettings()
{
$settings = new DatabaseSettingsRepository($this->db);
$this->info('Writing default settings');
$settings->set('version', $this->application->version());
foreach ($this->settings as $k => $v) {
$settings->set($k, $v);
}
}
protected function createAdminUser()
{
$admin = $this->adminUser;
if ($admin['password'] !== $admin['password_confirmation']) {
throw new Exception('The password did not match its confirmation.');
}
$this->info('Creating admin user '.$admin['username']);
$uid = $this->db->table('users')->insertGetId([
'username' => $admin['username'],
'email' => $admin['email'],
'password' => (new BcryptHasher)->make($admin['password']),
'joined_at' => Carbon::now(),
'is_email_confirmed' => 1,
]);
$this->db->table('group_user')->insert([
'user_id' => $uid,
'group_id' => Group::ADMINISTRATOR_ID,
]);
}
protected function enableBundledExtensions()
{
$extensions = new ExtensionManager(
new DatabaseSettingsRepository($this->db),
$this->application,
$this->migrator,
$this->application->make(Dispatcher::class),
$this->application->make('files')
$this->output->writeln(
'<error>Please fix the following problems before we can continue with the installation.</error>'
);
$disabled = [
'flarum-akismet',
'flarum-auth-facebook',
'flarum-auth-github',
'flarum-auth-twitter',
'flarum-pusher',
];
foreach ($problems as $problem) {
$this->info($problem['message']);
foreach ($extensions->getExtensions() as $name => $extension) {
if (in_array($name, $disabled)) {
continue;
}
$this->info('Enabling extension: '.$name);
$extensions->enable($name);
}
}
protected function publishAssets()
{
$this->filesystem->copyDirectory(
$this->application->basePath().'/vendor/components/font-awesome/webfonts',
$this->application->publicPath().'/assets/fonts'
);
}
protected function getConfigFile()
{
return $this->input->getOption('config') ?: base_path('config.php');
}
/**
* @return \Flarum\Install\Prerequisite\PrerequisiteInterface
*/
protected function getPrerequisites()
{
return $this->application->make(PrerequisiteInterface::class);
}
/**
* @return \Illuminate\Contracts\Validation\Factory
*/
protected function getValidator()
{
return new Factory($this->application->make(Translator::class));
}
protected function showErrors($errors)
{
foreach ($errors as $error) {
$this->info($error['message']);
if (isset($error['detail'])) {
$this->output->writeln('<comment>'.$error['detail'].'</comment>');
if (isset($problem['detail'])) {
$this->output->writeln('<comment>'.$problem['detail'].'</comment>');
}
}
}

View File

@@ -11,6 +11,9 @@
namespace Flarum\Install\Console;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -33,75 +36,91 @@ class UserDataProvider implements DataProviderInterface
$this->questionHelper = $questionHelper;
}
public function getDatabaseConfiguration()
public function configure(Installation $installation): Installation
{
return $installation
->debugMode(false)
->baseUrl($this->getBaseUrl())
->databaseConfig($this->getDatabaseConfiguration())
->adminUser($this->getAdminUser())
->settings($this->getSettings());
}
private function getDatabaseConfiguration(): DatabaseConfig
{
$host = $this->ask('Database host:');
$port = '3306';
$port = 3306;
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
return [
'driver' => 'mysql',
'host' => $host,
'port' => $port,
'database' => $this->ask('Database name:'),
'username' => $this->ask('Database user:'),
'password' => $this->secret('Database password:'),
'prefix' => $this->ask('Prefix:'),
];
return new DatabaseConfig(
'mysql',
$host,
intval($port),
$this->ask('Database name:'),
$this->ask('Database user:'),
$this->secret('Database password:'),
$this->ask('Prefix:')
);
}
public function getBaseUrl()
private function getBaseUrl()
{
return $this->baseUrl = rtrim($this->ask('Base URL:'), '/');
}
public function getAdminUser()
private function getAdminUser(): AdminUser
{
return [
'username' => $this->ask('Admin username:'),
'password' => $this->secret('Admin password:'),
'password_confirmation' => $this->secret('Admin password (confirmation):'),
'email' => $this->ask('Admin email address:'),
];
return new AdminUser(
$this->ask('Admin username:'),
$this->askForAdminPassword(),
$this->ask('Admin email address:')
);
}
public function getSettings()
private function askForAdminPassword()
{
while (true) {
$password = $this->secret('Admin password:');
if (strlen($password) < 8) {
$this->validationError('Password must be at least 8 characters.');
continue;
}
$confirmation = $this->secret('Admin password (confirmation):');
if ($password !== $confirmation) {
$this->validationError('The password did not match its confirmation.');
continue;
}
return $password;
}
}
private function getSettings()
{
$title = $this->ask('Forum title:');
$baseUrl = $this->baseUrl ?: 'http://localhost';
return [
'allow_post_editing' => 'reply',
'allow_renaming' => '10',
'allow_sign_up' => '1',
'custom_less' => '',
'default_locale' => 'en',
'default_route' => '/all',
'extensions_enabled' => '[]',
'forum_title' => $title,
'forum_description' => '',
'mail_driver' => 'mail',
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'theme_colored_header' => '0',
'theme_dark_mode' => '0',
'theme_primary_color' => '#4D698E',
'theme_secondary_color' => '#4D698E',
'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to '.$title,
];
}
protected function ask($question, $default = null)
private function ask($question, $default = null)
{
$question = new Question("<question>$question</question> ", $default);
return $this->questionHelper->ask($this->input, $this->output, $question);
}
protected function secret($question)
private function secret($question)
{
$question = new Question("<question>$question</question> ");
@@ -110,8 +129,9 @@ class UserDataProvider implements DataProviderInterface
return $this->questionHelper->ask($this->input, $this->output, $question);
}
public function isDebugMode(): bool
private function validationError($message)
{
return false;
$this->output->writeln("<error>$message</error>");
$this->output->writeln('Please try again.');
}
}

View File

@@ -12,7 +12,7 @@
namespace Flarum\Install\Controller;
use Flarum\Http\Controller\AbstractHtmlController;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Install\Installation;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -24,18 +24,18 @@ class IndexController extends AbstractHtmlController
protected $view;
/**
* @var \Flarum\Install\Prerequisite\PrerequisiteInterface
* @var Installation
*/
protected $prerequisite;
protected $installation;
/**
* @param Factory $view
* @param PrerequisiteInterface $prerequisite
* @param Installation $installation
*/
public function __construct(Factory $view, PrerequisiteInterface $prerequisite)
public function __construct(Factory $view, Installation $installation)
{
$this->view = $view;
$this->prerequisite = $prerequisite;
$this->installation = $installation;
}
/**
@@ -46,13 +46,12 @@ class IndexController extends AbstractHtmlController
{
$view = $this->view->make('flarum.install::app')->with('title', 'Install Flarum');
$this->prerequisite->check();
$errors = $this->prerequisite->getErrors();
$problems = $this->installation->prerequisites()->problems();
if (count($errors)) {
$view->with('content', $this->view->make('flarum.install::errors')->with('errors', $errors));
} else {
if ($problems->isEmpty()) {
$view->with('content', $this->view->make('flarum.install::install'));
} else {
$view->with('content', $this->view->make('flarum.install::problems')->with('problems', $problems));
}
return $view;

View File

@@ -11,21 +11,23 @@
namespace Flarum\Install\Controller;
use Exception;
use Flarum\Http\SessionAuthenticator;
use Flarum\Install\Console\DefaultsDataProvider;
use Flarum\Install\Console\InstallCommand;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Flarum\Install\StepFailed;
use Flarum\Install\ValidationFailed;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\HtmlResponse;
class InstallController implements RequestHandlerInterface
{
protected $command;
/**
* @var Installation
*/
protected $installation;
/**
* @var SessionAuthenticator
@@ -34,12 +36,12 @@ class InstallController implements RequestHandlerInterface
/**
* InstallController constructor.
* @param InstallCommand $command
* @param Installation $installation
* @param SessionAuthenticator $authenticator
*/
public function __construct(InstallCommand $command, SessionAuthenticator $authenticator)
public function __construct(Installation $installation, SessionAuthenticator $authenticator)
{
$this->command = $command;
$this->installation = $installation;
$this->authenticator = $authenticator;
}
@@ -50,55 +52,78 @@ class InstallController implements RequestHandlerInterface
public function handle(Request $request): ResponseInterface
{
$input = $request->getParsedBody();
$data = new DefaultsDataProvider;
$host = array_get($input, 'mysqlHost');
$port = '3306';
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
$data->setDatabaseConfiguration([
'driver' => 'mysql',
'host' => $host,
'database' => array_get($input, 'mysqlDatabase'),
'username' => array_get($input, 'mysqlUsername'),
'password' => array_get($input, 'mysqlPassword'),
'prefix' => array_get($input, 'tablePrefix'),
'port' => $port,
]);
$data->setAdminUser([
'username' => array_get($input, 'adminUsername'),
'password' => array_get($input, 'adminPassword'),
'password_confirmation' => array_get($input, 'adminPasswordConfirmation'),
'email' => array_get($input, 'adminEmail'),
]);
$baseUrl = rtrim((string) $request->getUri(), '/');
$data->setBaseUrl($baseUrl);
$data->setSetting('forum_title', array_get($input, 'forumTitle'));
$data->setSetting('mail_from', 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)));
$data->setSetting('welcome_title', 'Welcome to '.array_get($input, 'forumTitle'));
$body = fopen('php://temp', 'wb+');
$input = new StringInput('');
$output = new StreamOutput($body);
$this->command->setDataSource($data);
try {
$this->command->run($input, $output);
} catch (Exception $e) {
return new HtmlResponse($e->getMessage(), 500);
$pipeline = $this->installation
->baseUrl($baseUrl)
->databaseConfig($this->makeDatabaseConfig($input))
->adminUser($this->makeAdminUser($input))
->settings([
'forum_title' => array_get($input, 'forumTitle'),
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'welcome_title' => 'Welcome to '.array_get($input, 'forumTitle'),
])
->build();
} catch (ValidationFailed $e) {
return new Response\HtmlResponse($e->getMessage(), 500);
}
try {
$pipeline->run();
} catch (StepFailed $e) {
return new Response\HtmlResponse($e->getPrevious()->getMessage(), 500);
}
$session = $request->getAttribute('session');
$this->authenticator->logIn($session, 1);
return new Response($body);
return new Response\EmptyResponse;
}
private function makeDatabaseConfig(array $input): DatabaseConfig
{
$host = array_get($input, 'mysqlHost');
$port = 3306;
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
return new DatabaseConfig(
'mysql',
$host,
intval($port),
array_get($input, 'mysqlDatabase'),
array_get($input, 'mysqlUsername'),
array_get($input, 'mysqlPassword'),
array_get($input, 'tablePrefix')
);
}
/**
* @param array $input
* @return AdminUser
* @throws ValidationFailed
*/
private function makeAdminUser(array $input): AdminUser
{
return new AdminUser(
array_get($input, 'adminUsername'),
$this->getConfirmedAdminPassword($input),
array_get($input, 'adminEmail')
);
}
private function getConfirmedAdminPassword(array $input): string
{
$password = array_get($input, 'adminPassword');
$confirmation = array_get($input, 'adminPasswordConfirmation');
if ($password !== $confirmation) {
throw new ValidationFailed('The admin password did not match its confirmation.');
}
return $password;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Install;
use Illuminate\Contracts\Support\Arrayable;
class DatabaseConfig implements Arrayable
{
private $driver;
private $host;
private $port;
private $database;
private $username;
private $password;
private $prefix;
public function __construct($driver, $host, $port, $database, $username, $password, $prefix)
{
$this->driver = $driver;
$this->host = $host;
$this->port = $port;
$this->database = $database;
$this->username = $username;
$this->password = $password;
$this->prefix = $prefix;
$this->validate();
}
public function toArray()
{
return [
'driver' => $this->driver,
'host' => $this->host,
'port' => $this->port,
'database' => $this->database,
'username' => $this->username,
'password' => $this->password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => $this->prefix,
'strict' => false,
'engine' => 'InnoDB',
'prefix_indexes' => true
];
}
private function validate()
{
if (empty($this->driver)) {
throw new ValidationFailed('Please specify a database driver.');
}
if ($this->driver !== 'mysql') {
throw new ValidationFailed('Currently, only MySQL/MariaDB is supported.');
}
if (empty($this->host)) {
throw new ValidationFailed('Please specify the hostname of your database server.');
}
if (! is_int($this->port) || $this->port < 1 || $this->port > 65535) {
throw new ValidationFailed('Please provide a valid port number between 1 and 65535.');
}
if (empty($this->database)) {
throw new ValidationFailed('Please specify the database name.');
}
if (! is_string($this->database)) {
throw new ValidationFailed('The database name must be a non-empty string.');
}
if (empty($this->username)) {
throw new ValidationFailed('Please specify the username for accessing the database.');
}
if (! is_string($this->database)) {
throw new ValidationFailed('The username must be a non-empty string.');
}
if (! empty($this->prefix)) {
if (! preg_match('/^[\pL\pM\pN_-]+$/u', $this->prefix)) {
throw new ValidationFailed('The prefix may only contain characters, dashes and underscores.');
}
if (strlen($this->prefix) > 10) {
throw new ValidationFailed('The prefix should be no longer than 10 characters.');
}
}
}
}

View File

@@ -14,11 +14,6 @@ namespace Flarum\Install;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Install\Prerequisite\Composite;
use Flarum\Install\Prerequisite\PhpExtensions;
use Flarum\Install\Prerequisite\PhpVersion;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Install\Prerequisite\WritablePaths;
class InstallServiceProvider extends AbstractServiceProvider
{
@@ -27,32 +22,18 @@ class InstallServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->bind(
PrerequisiteInterface::class,
function () {
return new Composite(
new PhpVersion('7.1.0'),
new PhpExtensions([
'dom',
'gd',
'json',
'mbstring',
'openssl',
'pdo_mysql',
'tokenizer',
]),
new WritablePaths([
base_path(),
public_path('assets'),
storage_path(),
])
);
}
);
$this->app->singleton('flarum.install.routes', function () {
return new RouteCollection;
});
$this->app->singleton(Installation::class, function () {
return new Installation(
$this->app->basePath(),
$this->app->publicPath(),
$this->app->storagePath(),
$this->app->vendorPath()
);
});
}
/**

View File

@@ -0,0 +1,166 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Install;
class Installation
{
private $basePath;
private $publicPath;
private $storagePath;
private $vendorPath;
private $configPath;
private $debug = false;
private $baseUrl;
private $customSettings = [];
/** @var DatabaseConfig */
private $dbConfig;
/** @var AdminUser */
private $adminUser;
// A few instance variables to persist objects between steps.
// Could also be local variables in build(), but this way
// access in closures is easier. :)
/** @var \Illuminate\Database\ConnectionInterface */
private $db;
public function __construct($basePath, $publicPath, $storagePath, $vendorPath)
{
$this->basePath = $basePath;
$this->publicPath = $publicPath;
$this->storagePath = $storagePath;
$this->vendorPath = $vendorPath;
}
public function configPath($path)
{
$this->configPath = $path;
return $this;
}
public function debugMode($flag)
{
$this->debug = $flag;
return $this;
}
public function databaseConfig(DatabaseConfig $dbConfig)
{
$this->dbConfig = $dbConfig;
return $this;
}
public function baseUrl($baseUrl)
{
$this->baseUrl = $baseUrl;
return $this;
}
public function settings($settings)
{
$this->customSettings = $settings;
return $this;
}
public function adminUser(AdminUser $admin)
{
$this->adminUser = $admin;
return $this;
}
public function prerequisites(): Prerequisite\PrerequisiteInterface
{
return new Prerequisite\Composite(
new Prerequisite\PhpVersion('7.1.0'),
new Prerequisite\PhpExtensions([
'dom',
'gd',
'json',
'mbstring',
'openssl',
'pdo_mysql',
'tokenizer',
]),
new Prerequisite\WritablePaths([
$this->basePath,
$this->getAssetPath(),
$this->storagePath,
])
);
}
public function build(): Pipeline
{
$pipeline = new Pipeline;
$pipeline->pipe(function () {
return new Steps\ConnectToDatabase(
$this->dbConfig,
function ($connection) {
$this->db = $connection;
}
);
});
$pipeline->pipe(function () {
return new Steps\StoreConfig(
$this->debug, $this->dbConfig, $this->baseUrl, $this->getConfigPath()
);
});
$pipeline->pipe(function () {
return new Steps\RunMigrations($this->db, $this->getMigrationPath());
});
$pipeline->pipe(function () {
return new Steps\WriteSettings($this->db, $this->customSettings);
});
$pipeline->pipe(function () {
return new Steps\CreateAdminUser($this->db, $this->adminUser);
});
$pipeline->pipe(function () {
return new Steps\PublishAssets($this->vendorPath, $this->getAssetPath());
});
$pipeline->pipe(function () {
return new Steps\EnableBundledExtensions($this->db, $this->vendorPath, $this->getAssetPath());
});
return $pipeline;
}
private function getConfigPath()
{
return $this->basePath.'/'.($this->configPath ?? 'config.php');
}
private function getAssetPath()
{
return "$this->publicPath/assets";
}
private function getMigrationPath()
{
return __DIR__.'/../../migrations';
}
}

View File

@@ -17,6 +17,8 @@ use Flarum\Http\Middleware\HandleErrorsWithWhoops;
use Flarum\Http\Middleware\StartSession;
use Flarum\Install\Console\InstallCommand;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Validation\Factory;
use Zend\Stratigility\MiddlewarePipe;
class Installer implements AppInterface
@@ -52,7 +54,10 @@ class Installer implements AppInterface
public function getConsoleCommands()
{
return [
$this->container->make(InstallCommand::class),
new InstallCommand(
$this->container->make(Installation::class),
new Factory($this->container->make(Translator::class))
),
];
}
}

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