From 47a107ac5b27c41a0b1ef6cc0f13a39cc0e5ceee Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 23 May 2019 10:45:15 -0400 Subject: [PATCH 001/257] Update maintenance.php * Added a link to the Github releases page when someone clicks the current release version (to look for changelog information, or to see if there are new updates) * Removed unnecessary BR tag by fixing the CSS class for the version display so it is properly aligned with the rest of the menu --- resources/views/settings/maintenance.blade.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php index c3ca8c96f..8e60b65aa 100644 --- a/resources/views/settings/maintenance.blade.php +++ b/resources/views/settings/maintenance.blade.php @@ -7,9 +7,10 @@
@include('settings.navbar', ['selected' => 'maintenance'])
-
-
+
From 3c41b15be65ca59a89f1588522d2d58f775e71cc Mon Sep 17 00:00:00 2001 From: Daniel Seiler Date: Mon, 5 Aug 2019 20:06:39 +0200 Subject: [PATCH 002/257] Initial work on SAML integration --- app/Config/app.php | 1 + app/Config/saml2_settings.php | 230 ++++++++++++++++++ app/Http/Controllers/Auth/LoginController.php | 7 +- composer.json | 3 +- composer.lock | 152 +++++++++++- resources/views/auth/login.blade.php | 12 +- 6 files changed, 400 insertions(+), 5 deletions(-) create mode 100644 app/Config/saml2_settings.php diff --git a/app/Config/app.php b/app/Config/app.php index 88052e94c..23025a6c4 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -106,6 +106,7 @@ return [ Intervention\Image\ImageServiceProvider::class, Barryvdh\DomPDF\ServiceProvider::class, Barryvdh\Snappy\ServiceProvider::class, + Aacotroneo\Saml2\Saml2ServiceProvider::class, // BookStack replacement service providers (Extends Laravel) diff --git a/app/Config/saml2_settings.php b/app/Config/saml2_settings.php new file mode 100644 index 000000000..a6d7a0204 --- /dev/null +++ b/app/Config/saml2_settings.php @@ -0,0 +1,230 @@ + env("SAML2_ENABLED", false), + + /** + * If 'useRoutes' is set to true, the package defines five new routes: + * + * Method | URI | Name + * -------|--------------------------|------------------ + * POST | {routesPrefix}/acs | saml_acs + * GET | {routesPrefix}/login | saml_login + * GET | {routesPrefix}/logout | saml_logout + * GET | {routesPrefix}/metadata | saml_metadata + * GET | {routesPrefix}/sls | saml_sls + */ + 'useRoutes' => true, + + 'routesPrefix' => '/saml2', + + /** + * which middleware group to use for the saml routes + * Laravel 5.2 will need a group which includes StartSession + */ + 'routesMiddleware' => [], + + /** + * Indicates how the parameters will be + * retrieved from the sls request for signature validation + */ + 'retrieveParametersFromServer' => false, + + /** + * Where to redirect after logout + */ + 'logoutRoute' => '/', + + /** + * Where to redirect after login if no other option was provided + */ + 'loginRoute' => '/', + + + /** + * Where to redirect after login if no other option was provided + */ + 'errorRoute' => '/', + + + + + /***** + * One Login Settings + */ + + + + // If 'strict' is True, then the PHP Toolkit will reject unsigned + // or unencrypted messages if it expects them signed or encrypted + // Also will reject the messages if not strictly follow the SAML + // standard: Destination, NameId, Conditions ... are validated too. + 'strict' => true, //@todo: make this depend on laravel config + + // Enable debug mode (to print errors) + 'debug' => env('APP_DEBUG', false), + + // If 'proxyVars' is True, then the Saml lib will trust proxy headers + // e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if + // your application is running behind a load balancer which terminates + // SSL. + 'proxyVars' => false, + + // Service Provider Data that we are deploying + 'sp' => array( + + // Specifies constraints on the name identifier to be used to + // represent the requested subject. + // Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported + 'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + + // Usually x509cert and privateKey of the SP are provided by files placed at + // the certs folder. But we can also provide them with the following parameters + 'x509cert' => env('SAML2_SP_x509',''), + 'privateKey' => env('SAML2_SP_PRIVATEKEY',''), + + // Identifier (URI) of the SP entity. + // Leave blank to use the 'saml_metadata' route. + 'entityId' => env('SAML2_SP_ENTITYID',''), + + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + 'assertionConsumerService' => array( + // URL Location where the from the IdP will be returned, + // using HTTP-POST binding. + // Leave blank to use the 'saml_acs' route + 'url' => '', + ), + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + // Remove this part to not include any URL Location in the metadata. + 'singleLogoutService' => array( + // URL Location where the from the IdP will be returned, + // using HTTP-Redirect binding. + // Leave blank to use the 'saml_sls' route + 'url' => '', + ), + ), + + // Identity Provider Data that we want connect with our SP + 'idp' => array( + // Identifier of the IdP entity (must be a URI) + 'entityId' => env('SAML2_IDP_ENTITYID', $idp_host . '/saml2/idp/metadata.php'), + // SSO endpoint info of the IdP. (Authentication Request protocol) + 'singleSignOnService' => array( + // URL Target of the IdP where the SP will send the Authentication Request Message, + // using HTTP-Redirect binding. + 'url' => env('SAML2_IDP_SSO', $idp_host . '/saml2/idp/SSOService.php'), + ), + // SLO endpoint info of the IdP. + 'singleLogoutService' => array( + // URL Location of the IdP where the SP will send the SLO Request, + // using HTTP-Redirect binding. + 'url' => env('SAML2_IDP_SLO', $idp_host . '/saml2/idp/SingleLogoutService.php'), + ), + // Public x509 certificate of the IdP + 'x509cert' => env('SAML2_IDP_x509', 'MIID/TCCAuWgAwIBAgIJAI4R3WyjjmB1MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjAeFw0xNDEyMDExNDM2MjVaFw0yNDExMzAxNDM2MjVaMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbzW/EpEv+qqZzfT1Buwjg9nnNNVrxkCfuR9fQiQw2tSouS5X37W5h7RmchRt54wsm046PDKtbSz1NpZT2GkmHN37yALW2lY7MyVUC7itv9vDAUsFr0EfKIdCKgxCKjrzkZ5ImbNvjxf7eA77PPGJnQ/UwXY7W+cvLkirp0K5uWpDk+nac5W0JXOCFR1BpPUJRbz2jFIEHyChRt7nsJZH6ejzNqK9lABEC76htNy1Ll/D3tUoPaqo8VlKW3N3MZE0DB9O7g65DmZIIlFqkaMH3ALd8adodJtOvqfDU/A6SxuwMfwDYPjoucykGDu1etRZ7dF2gd+W+1Pn7yizPT1q8CAwEAAaNQME4wHQYDVR0OBBYEFPsn8tUHN8XXf23ig5Qro3beP8BuMB8GA1UdIwQYMBaAFPsn8tUHN8XXf23ig5Qro3beP8BuMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGu60odWFiK+DkQekozGnlpNBQz5lQ/bwmOWdktnQj6HYXu43e7sh9oZWArLYHEOyMUekKQAxOK51vbTHzzw66BZU91/nqvaOBfkJyZKGfluHbD0/hfOl/D5kONqI9kyTu4wkLQcYGyuIi75CJs15uA03FSuULQdY/Liv+czS/XYDyvtSLnu43VuAQWN321PQNhuGueIaLJANb2C5qq5ilTBUw6PxY9Z+vtMjAjTJGKEkE/tQs7CvzLPKXX3KTD9lIILmX5yUC3dLgjVKi1KGDqNApYGOMtjr5eoxPQrqDBmyx3flcy0dQTdLXud3UjWVW3N0PYgJtw5yBsS74QTGD4='), + /* + * Instead of use the whole x509cert you can use a fingerprint + * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it) + */ + // 'certFingerprint' => '', + ), + + + + /*** + * + * OneLogin advanced settings + * + * + */ + // Security settings + 'security' => array( + + /** signatures and encryptions offered */ + + // Indicates that the nameID of the sent by this SP + // will be encrypted. + 'nameIdEncrypted' => false, + + // Indicates whether the messages sent by this SP + // will be signed. [The Metadata of the SP will offer this info] + 'authnRequestsSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutRequestSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutResponseSigned' => false, + + /* Sign the Metadata + False || True (use sp certs) || array ( + keyFileName => 'metadata.key', + certFileName => 'metadata.crt' + ) + */ + 'signMetadata' => false, + + + /** signatures and encryptions required **/ + + // Indicates a requirement for the , and + // elements received by this SP to be signed. + 'wantMessagesSigned' => false, + + // Indicates a requirement for the elements received by + // this SP to be signed. [The Metadata of the SP will offer this info] + 'wantAssertionsSigned' => false, + + // Indicates a requirement for the NameID received by + // this SP to be encrypted. + 'wantNameIdEncrypted' => false, + + // Authentication context. + // Set to false and no AuthContext will be sent in the AuthNRequest, + // Set true or don't present thi parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + // Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'), + 'requestedAuthnContext' => true, + ), + + // Contact information template, it is recommended to suply a technical and support contacts + 'contactPerson' => array( + 'technical' => array( + 'givenName' => 'name', + 'emailAddress' => 'no@reply.com' + ), + 'support' => array( + 'givenName' => 'Support', + 'emailAddress' => 'no@reply.com' + ), + ), + + // Organization information template, the info in en_US lang is recomended, add more if required + 'organization' => array( + 'en-US' => array( + 'name' => 'Name', + 'displayname' => 'Display Name', + 'url' => 'http://url' + ), + ), + +/* Interoperable SAML 2.0 Web Browser SSO Profile [saml2int] http://saml2int.org/profile/current + + 'authnRequestsSigned' => false, // SP SHOULD NOT sign the , + // MUST NOT assume that the IdP validates the sign + 'wantAssertionsSigned' => true, + 'wantAssertionsEncrypted' => true, // MUST be enabled if SSL/HTTPs is disabled + 'wantNameIdEncrypted' => false, +*/ + +); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index c739fd9a3..9c5467e25 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -118,6 +118,7 @@ class LoginController extends Controller { $socialDrivers = $this->socialAuthService->getActiveDrivers(); $authMethod = config('auth.method'); + $samlEnabled = config('saml2_settings.enabled') == true; if ($request->has('email')) { session()->flashInput([ @@ -126,7 +127,11 @@ class LoginController extends Controller ]); } - return view('auth.login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]); + return view('auth.login', [ + 'socialDrivers' => $socialDrivers, + 'authMethod' => $authMethod, + 'samlEnabled' => $samlEnabled, + ]); } /** diff --git a/composer.json b/composer.json index 61bb8509e..457ce5093 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "socialiteproviders/gitlab": "^3.0", "socialiteproviders/twitch": "^3.0", "socialiteproviders/discord": "^2.0", - "doctrine/dbal": "^2.5" + "doctrine/dbal": "^2.5", + "aacotroneo/laravel-saml2": "^1.0" }, "require-dev": { "filp/whoops": "~2.0", diff --git a/composer.lock b/composer.lock index d7734ce1a..d35594642 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,70 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0946a07729a7a1bfef9bac185a870afd", + "content-hash": "26a2c3ad0409c970f4f0c9b6dad49322", "packages": [ + { + "name": "aacotroneo/laravel-saml2", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/aacotroneo/laravel-saml2.git", + "reference": "5045701a07bcd7600a17c92971368669870f546a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aacotroneo/laravel-saml2/zipball/5045701a07bcd7600a17c92971368669870f546a", + "reference": "5045701a07bcd7600a17c92971368669870f546a", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "illuminate/support": ">=5.0.0", + "onelogin/php-saml": "^3.0.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Aacotroneo\\Saml2\\Saml2ServiceProvider" + ], + "aliases": { + "Saml2": "Aacotroneo\\Saml2\\Facades\\Saml2Auth" + } + } + }, + "autoload": { + "psr-0": { + "Aacotroneo\\Saml2\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "aacotroneo", + "email": "aacotroneo@gmail.com" + } + ], + "description": "A Laravel package for Saml2 integration as a SP (service provider) based on OneLogin toolkit, which is much lightweight than simplesamlphp", + "homepage": "https://github.com/aacotroneo/laravel-saml2", + "keywords": [ + "SAML2", + "laravel", + "onelogin", + "saml" + ], + "time": "2018-11-08T14:03:58+00:00" + }, { "name": "aws/aws-sdk-php", "version": "3.86.2", @@ -1947,6 +2006,56 @@ ], "time": "2018-12-28T10:07:33+00:00" }, + { + "name": "onelogin/php-saml", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/onelogin/php-saml.git", + "reference": "845a6ce39e839ed9e687f80bffb02ffde16a70d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/onelogin/php-saml/zipball/845a6ce39e839ed9e687f80bffb02ffde16a70d0", + "reference": "845a6ce39e839ed9e687f80bffb02ffde16a70d0", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "robrichards/xmlseclibs": ">=3.0.3" + }, + "require-dev": { + "pdepend/pdepend": "^2.5.0", + "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", + "phploc/phploc": "^2.1 || ^3.0 || ^4.0", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1", + "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", + "squizlabs/php_codesniffer": "^3.1.1" + }, + "suggest": { + "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", + "ext-gettext": "Install gettext and php5-gettext libs to handle translations", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)" + }, + "type": "library", + "autoload": { + "psr-4": { + "OneLogin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "OneLogin PHP SAML Toolkit", + "homepage": "https://developers.onelogin.com/saml/php", + "keywords": [ + "SAML2", + "onelogin", + "saml" + ], + "time": "2019-06-25T10:28:20+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -2435,6 +2544,44 @@ ], "time": "2018-07-19T23:38:55+00:00" }, + { + "name": "robrichards/xmlseclibs", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "406c68ac9124db033d079284b719958b829cb830" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/406c68ac9124db033d079284b719958b829cb830", + "reference": "406c68ac9124db033d079284b719958b829cb830", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ], + "time": "2018-11-15T11:59:02+00:00" + }, { "name": "sabberworm/php-css-parser", "version": "8.1.0", @@ -5416,6 +5563,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2018-08-09T05:50:03+00:00" }, { diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 76aa3a6e9..72d8d00aa 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -46,6 +46,16 @@ @endforeach @endif + @if($samlEnabled) +
+ + @endif + @if(setting('registration-enabled', false))

@@ -55,4 +65,4 @@
-@stop \ No newline at end of file +@stop From bda0082461c4609b7333c8e3d9373f8d68da3da7 Mon Sep 17 00:00:00 2001 From: Daniel Seiler Date: Tue, 6 Aug 2019 23:42:46 +0200 Subject: [PATCH 003/257] Add login and automatic registration; Prepare Group sync --- app/Auth/Access/Saml2Service.php | 241 ++++++++++++++++++ app/Config/saml2_settings.php | 15 +- app/Config/services.php | 25 +- app/Exceptions/SamlException.php | 6 + app/Http/Controllers/Auth/LoginController.php | 2 +- app/Http/Kernel.php | 5 + app/Listeners/Saml2LoginEventListener.php | 42 +++ app/Providers/EventServiceProvider.php | 4 + resources/views/settings/roles/form.blade.php | 4 +- resources/views/users/form.blade.php | 4 +- 10 files changed, 334 insertions(+), 14 deletions(-) create mode 100644 app/Auth/Access/Saml2Service.php create mode 100644 app/Exceptions/SamlException.php create mode 100644 app/Listeners/Saml2LoginEventListener.php diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php new file mode 100644 index 000000000..0b6cbe805 --- /dev/null +++ b/app/Auth/Access/Saml2Service.php @@ -0,0 +1,241 @@ +config = config('services.saml'); + $this->userRepo = $userRepo; + $this->user = $user; + $this->enabled = config('saml2_settings.enabled') === true; + } + + /** + * Check if groups should be synced. + * @return bool + */ + public function shouldSyncGroups() + { + return $this->enabled && $this->config['user_to_groups'] !== false; + } + + /** + * Extract the details of a user from a SAML response. + * @param $samlID + * @param $samlAttributes + * @return array + */ + public function getUserDetails($samlID, $samlAttributes) + { + $emailAttr = $this->config['email_attribute']; + $displayNameAttr = $this->config['display_name_attribute']; + $userNameAttr = $this->config['user_name_attribute']; + + $email = $this->getSamlResponseAttribute($samlAttributes, $emailAttr, null); + + if ($userNameAttr === null) { + $userName = $samlID; + } else { + $userName = $this->getSamlResponseAttribute($samlAttributes, $userNameAttr, $samlID); + } + + $displayName = []; + foreach ($displayNameAttr as $dnAttr) { + $dnComponent = $this->getSamlResponseAttribute($samlAttributes, $dnAttr, null); + if ($dnComponent !== null) { + $displayName[] = $dnComponent; + } + } + + if (count($displayName) == 0) { + $displayName = $userName; + } else { + $displayName = implode(' ', $displayName); + } + + return [ + 'uid' => $userName, + 'name' => $displayName, + 'dn' => $samlID, + 'email' => $email, + ]; + } + + /** + * Get the groups a user is a part of from the SAML response. + * @param array $samlAttributes + * @return array + */ + public function getUserGroups($samlAttributes) + { + $groupsAttr = $this->config['group_attribute']; + $userGroups = $samlAttributes[$groupsAttr]; + + if (!is_array($userGroups)) { + $userGroups = []; + } + + return $userGroups; + } + + /** + * Get a property from an SAML response. + * Handles properties potentially being an array. + * @param array $userDetails + * @param string $propertyKey + * @param $defaultValue + * @return mixed + */ + protected function getSamlResponseAttribute(array $samlAttributes, string $propertyKey, $defaultValue) + { + if (isset($samlAttributes[$propertyKey])) { + $data = $samlAttributes[$propertyKey]; + if (!is_array($data)) { + return $data; + } else if (count($data) == 0) { + return $defaultValue; + } else if (count($data) == 1) { + return $data[0]; + } else { + return $data; + } + } + + return $defaultValue; + } + + protected function registerUser($userDetails) { + + // Create an array of the user data to create a new user instance + $userData = [ + 'name' => $userDetails['name'], + 'email' => $userDetails['email'], + 'password' => str_random(30), + 'external_auth_id' => $userDetails['uid'], + 'email_confirmed' => true, + ]; + + $user = $this->user->forceCreate($userData); + $this->userRepo->attachDefaultRole($user); + $this->userRepo->downloadAndAssignUserAvatar($user); + return $user; + } + + public function processLoginCallback($samlID, $samlAttributes) { + + $userDetails = $this->getUserDetails($samlID, $samlAttributes); + $user = $this->user + ->where('external_auth_id', $userDetails['uid']) + ->first(); + + $isLoggedIn = auth()->check(); + + if (!$isLoggedIn) { + if ($user === null && config('services.saml.auto_register') === true) { + $user = $this->registerUser($userDetails); + } + + if ($user !== null) { + auth()->login($user); + } + } + + return $user; + } + + /** + * Sync the SAML groups to the user roles for the current user + * @param \BookStack\Auth\User $user + * @param array $samlAttributes + */ + public function syncGroups(User $user, array $samlAttributes) + { + $userSamlGroups = $this->getUserGroups($samlAttributes); + + // Get the ids for the roles from the names + $samlGroupsAsRoles = $this->matchSamlGroupsToSystemsRoles($userSamlGroups); + + // Sync groups + if ($this->config['remove_from_groups']) { + $user->roles()->sync($samlGroupsAsRoles); + $this->userRepo->attachDefaultRole($user); + } else { + $user->roles()->syncWithoutDetaching($samlGroupsAsRoles); + } + } + + /** + * Match an array of group names from SAML to BookStack system roles. + * Formats group names to be lower-case and hyphenated. + * @param array $groupNames + * @return \Illuminate\Support\Collection + */ + protected function matchSamlGroupsToSystemsRoles(array $groupNames) + { + foreach ($groupNames as $i => $groupName) { + $groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName))); + } + + $roles = Role::query()->where(function (Builder $query) use ($groupNames) { + $query->whereIn('name', $groupNames); + foreach ($groupNames as $groupName) { + $query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%'); + } + })->get(); + + $matchedRoles = $roles->filter(function (Role $role) use ($groupNames) { + return $this->roleMatchesGroupNames($role, $groupNames); + }); + + return $matchedRoles->pluck('id'); + } + + /** + * Check a role against an array of group names to see if it matches. + * Checked against role 'external_auth_id' if set otherwise the name of the role. + * @param \BookStack\Auth\Role $role + * @param array $groupNames + * @return bool + */ + protected function roleMatchesGroupNames(Role $role, array $groupNames) + { + if ($role->external_auth_id) { + $externalAuthIds = explode(',', strtolower($role->external_auth_id)); + foreach ($externalAuthIds as $externalAuthId) { + if (in_array(trim($externalAuthId), $groupNames)) { + return true; + } + } + return false; + } + + $roleName = str_replace(' ', '-', trim(strtolower($role->display_name))); + return in_array($roleName, $groupNames); + } + +} diff --git a/app/Config/saml2_settings.php b/app/Config/saml2_settings.php index a6d7a0204..015763b46 100644 --- a/app/Config/saml2_settings.php +++ b/app/Config/saml2_settings.php @@ -29,7 +29,7 @@ return $settings = array( * which middleware group to use for the saml routes * Laravel 5.2 will need a group which includes StartSession */ - 'routesMiddleware' => [], + 'routesMiddleware' => ['saml'], /** * Indicates how the parameters will be @@ -101,6 +101,8 @@ return $settings = array( // using HTTP-POST binding. // Leave blank to use the 'saml_acs' route 'url' => '', + + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', ), // Specifies info about where and how the message MUST be // returned to the requester, in this case our SP. @@ -138,7 +140,16 @@ return $settings = array( // 'certFingerprint' => '', ), - + /*** + * OneLogin compression settings + * + */ + 'compress' => array( + /** Whether requests should be GZ encoded */ + 'requests' => true, + /** Whether responses should be GZ compressed */ + 'responses' => true, + ), /*** * diff --git a/app/Config/services.php b/app/Config/services.php index 97cb71ddc..9cd647e6d 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -98,8 +98,8 @@ return [ 'okta' => [ 'client_id' => env('OKTA_APP_ID'), 'client_secret' => env('OKTA_APP_SECRET'), - 'redirect' => env('APP_URL') . '/login/service/okta/callback', - 'base_url' => env('OKTA_BASE_URL'), + 'redirect' => env('APP_URL') . '/login/service/okta/callback', + 'base_url' => env('OKTA_BASE_URL'), 'name' => 'Okta', 'auto_register' => env('OKTA_AUTO_REGISTER', false), 'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false), @@ -143,10 +143,21 @@ return [ 'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'), 'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'), 'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false), - 'user_to_groups' => env('LDAP_USER_TO_GROUPS',false), - 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), - 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false), - 'tls_insecure' => env('LDAP_TLS_INSECURE', false), - ] + 'user_to_groups' => env('LDAP_USER_TO_GROUPS',false), + 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), + 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false), + 'tls_insecure' => env('LDAP_TLS_INSECURE', false), + ], + + 'saml' => [ + 'enabled' => env('SAML2_ENABLED', false), + 'auto_register' => env('SAML_AUTO_REGISTER', false), + 'email_attribute' => env('SAML_EMAIL_ATTRIBUTE', 'email'), + 'display_name_attribute' => explode('|', env('SAML_DISPLAY_NAME_ATTRIBUTE', 'username')), + 'user_name_attribute' => env('SAML_USER_NAME_ATTRIBUTE', null), + 'group_attribute' => env('SAML_GROUP_ATTRIBUTE', 'group'), + 'user_to_groups' => env('SAML_USER_TO_GROUPS', false), + 'id_is_user_name' => env('SAML_ID_IS_USER_NAME', true), + ] ]; diff --git a/app/Exceptions/SamlException.php b/app/Exceptions/SamlException.php new file mode 100644 index 000000000..f9668919c --- /dev/null +++ b/app/Exceptions/SamlException.php @@ -0,0 +1,6 @@ +socialAuthService->getActiveDrivers(); $authMethod = config('auth.method'); - $samlEnabled = config('saml2_settings.enabled') == true; + $samlEnabled = config('services.saml.enabled') == true; if ($request->has('email')) { session()->flashInput([ diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cd894de95..7794f3401 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -37,6 +37,11 @@ class Kernel extends HttpKernel 'throttle:60,1', 'bindings', ], + 'saml' => [ + \BookStack\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + ], ]; /** diff --git a/app/Listeners/Saml2LoginEventListener.php b/app/Listeners/Saml2LoginEventListener.php new file mode 100644 index 000000000..74c4d6f27 --- /dev/null +++ b/app/Listeners/Saml2LoginEventListener.php @@ -0,0 +1,42 @@ +saml = $saml; + } + + /** + * Handle the event. + * + * @param Saml2LoginEvent $event + * @return void + */ + public function handle(Saml2LoginEvent $event) + { + $messageId = $event->getSaml2Auth()->getLastMessageId(); + // TODO: Add your own code preventing reuse of a $messageId to stop replay attacks + + $samlUser = $event->getSaml2User(); + + $attrs = $samlUser->getAttributes(); + $id = $samlUser->getUserId(); + //$assertion = $user->getRawSamlAssertion() + + $user = $this->saml->processLoginCallback($id, $attrs); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a826185d8..50436916a 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -4,6 +4,7 @@ namespace BookStack\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use SocialiteProviders\Manager\SocialiteWasCalled; +use Aacotroneo\Saml2\Events\Saml2LoginEvent; class EventServiceProvider extends ServiceProvider { @@ -21,6 +22,9 @@ class EventServiceProvider extends ServiceProvider 'SocialiteProviders\Twitch\TwitchExtendSocialite@handle', 'SocialiteProviders\Discord\DiscordExtendSocialite@handle', ], + Saml2LoginEvent::class => [ + 'BookStack\Listeners\Saml2LoginEventListener@handle', + ] ]; /** diff --git a/resources/views/settings/roles/form.blade.php b/resources/views/settings/roles/form.blade.php index 68b841e03..d7c1fc47c 100644 --- a/resources/views/settings/roles/form.blade.php +++ b/resources/views/settings/roles/form.blade.php @@ -19,7 +19,7 @@ @include('form.text', ['name' => 'description']) - @if(config('auth.method') === 'ldap') + @if(config('auth.method') === 'ldap' || config('services.saml.enabled') === true)
@include('form.text', ['name' => 'external_auth_id']) @@ -254,4 +254,4 @@ {{ trans('settings.role_users_none') }}

@endif -
\ No newline at end of file + diff --git a/resources/views/users/form.blade.php b/resources/views/users/form.blade.php index 96beb7b2f..7a3d44935 100644 --- a/resources/views/users/form.blade.php +++ b/resources/views/users/form.blade.php @@ -25,7 +25,7 @@ -@if($authMethod === 'ldap' && userCan('users-manage')) +@if(($authMethod === 'ldap' || config('services.saml.enabled') === true) && userCan('users-manage'))
@@ -67,4 +67,4 @@
-@endif \ No newline at end of file +@endif From 03dbe32f9926b53c1a0c35534e57f526c5d2bc2b Mon Sep 17 00:00:00 2001 From: Daniel Seiler Date: Wed, 7 Aug 2019 12:07:21 +0200 Subject: [PATCH 004/257] Refactor for codestyle --- app/Auth/Access/ExternalAuthService.php | 75 ++++++++++ app/Auth/Access/LdapService.php | 64 +------- app/Auth/Access/Saml2Service.php | 189 ++++++++++-------------- 3 files changed, 157 insertions(+), 171 deletions(-) create mode 100644 app/Auth/Access/ExternalAuthService.php diff --git a/app/Auth/Access/ExternalAuthService.php b/app/Auth/Access/ExternalAuthService.php new file mode 100644 index 000000000..b1c036018 --- /dev/null +++ b/app/Auth/Access/ExternalAuthService.php @@ -0,0 +1,75 @@ +external_auth_id) { + $externalAuthIds = explode(',', strtolower($role->external_auth_id)); + foreach ($externalAuthIds as $externalAuthId) { + if (in_array(trim($externalAuthId), $groupNames)) { + return true; + } + } + return false; + } + + $roleName = str_replace(' ', '-', trim(strtolower($role->display_name))); + return in_array($roleName, $groupNames); + } + + /** + * Match an array of group names to BookStack system roles. + * Formats group names to be lower-case and hyphenated. + * @param array $groupNames + * @return \Illuminate\Support\Collection + */ + protected function matchGroupsToSystemsRoles(array $groupNames) + { + foreach ($groupNames as $i => $groupName) { + $groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName))); + } + + $roles = Role::query()->where(function (Builder $query) use ($groupNames) { + $query->whereIn('name', $groupNames); + foreach ($groupNames as $groupName) { + $query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%'); + } + })->get(); + + $matchedRoles = $roles->filter(function (Role $role) use ($groupNames) { + return $this->roleMatchesGroupNames($role, $groupNames); + }); + + return $matchedRoles->pluck('id'); + } + + /** + * Sync the groups to the user roles for the current user + * @param \BookStack\Auth\User $user + * @param array $samlAttributes + */ + public function syncWithGroups(User $user, array $userGroups) + { + // Get the ids for the roles from the names + $samlGroupsAsRoles = $this->matchGroupsToSystemsRoles($userSamlGroups); + + // Sync groups + if ($this->config['remove_from_groups']) { + $user->roles()->sync($samlGroupsAsRoles); + $this->userRepo->attachDefaultRole($user); + } else { + $user->roles()->syncWithoutDetaching($samlGroupsAsRoles); + } + } +} diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index c7415e1f7..3111ea9fa 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -1,7 +1,6 @@ getUserGroups($username); - - // Get the ids for the roles from the names - $ldapGroupsAsRoles = $this->matchLdapGroupsToSystemsRoles($userLdapGroups); - - // Sync groups - if ($this->config['remove_from_groups']) { - $user->roles()->sync($ldapGroupsAsRoles); - $this->userRepo->attachDefaultRole($user); - } else { - $user->roles()->syncWithoutDetaching($ldapGroupsAsRoles); - } - } - - /** - * Match an array of group names from LDAP to BookStack system roles. - * Formats LDAP group names to be lower-case and hyphenated. - * @param array $groupNames - * @return \Illuminate\Support\Collection - */ - protected function matchLdapGroupsToSystemsRoles(array $groupNames) - { - foreach ($groupNames as $i => $groupName) { - $groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName))); - } - - $roles = Role::query()->where(function (Builder $query) use ($groupNames) { - $query->whereIn('name', $groupNames); - foreach ($groupNames as $groupName) { - $query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%'); - } - })->get(); - - $matchedRoles = $roles->filter(function (Role $role) use ($groupNames) { - return $this->roleMatchesGroupNames($role, $groupNames); - }); - - return $matchedRoles->pluck('id'); - } - - /** - * Check a role against an array of group names to see if it matches. - * Checked against role 'external_auth_id' if set otherwise the name of the role. - * @param \BookStack\Auth\Role $role - * @param array $groupNames - * @return bool - */ - protected function roleMatchesGroupNames(Role $role, array $groupNames) - { - if ($role->external_auth_id) { - $externalAuthIds = explode(',', strtolower($role->external_auth_id)); - foreach ($externalAuthIds as $externalAuthId) { - if (in_array(trim($externalAuthId), $groupNames)) { - return true; - } - } - return false; - } - - $roleName = str_replace(' ', '-', trim(strtolower($role->display_name))); - return in_array($roleName, $groupNames); + $this->syncWithGroups($user, $userLdapGroups); } } diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index 0b6cbe805..95049efd2 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -1,13 +1,11 @@ enabled && $this->config['user_to_groups'] !== false; } - /** - * Extract the details of a user from a SAML response. - * @param $samlID - * @param $samlAttributes - * @return array + /** Calculate the display name + * @param array $samlAttributes + * @param string $defaultValue + * @return string */ - public function getUserDetails($samlID, $samlAttributes) + protected function getUserDisplayName(array $samlAttributes, string $defaultValue) { - $emailAttr = $this->config['email_attribute']; $displayNameAttr = $this->config['display_name_attribute']; - $userNameAttr = $this->config['user_name_attribute']; - - $email = $this->getSamlResponseAttribute($samlAttributes, $emailAttr, null); - - if ($userNameAttr === null) { - $userName = $samlID; - } else { - $userName = $this->getSamlResponseAttribute($samlAttributes, $userNameAttr, $samlID); - } $displayName = []; foreach ($displayNameAttr as $dnAttr) { @@ -73,16 +60,43 @@ class Saml2Service } if (count($displayName) == 0) { - $displayName = $userName; + $displayName = $defaultValue; } else { $displayName = implode(' ', $displayName); } + return $displayName; + } + + protected function getUserName(array $samlAttributes, string $defaultValue) + { + $userNameAttr = $this->config['user_name_attribute']; + + if ($userNameAttr === null) { + $userName = $defaultValue; + } else { + $userName = $this->getSamlResponseAttribute($samlAttributes, $userNameAttr, $defaultValue); + } + + return $userName; + } + + /** + * Extract the details of a user from a SAML response. + * @param $samlID + * @param $samlAttributes + * @return array + */ + public function getUserDetails($samlID, $samlAttributes) + { + $emailAttr = $this->config['email_attribute']; + $userName = $this->getUserName($samlAttributes, $samlID); + return [ 'uid' => $userName, - 'name' => $displayName, + 'name' => $this->getUserDisplayName($samlAttributes, $userName), 'dn' => $samlID, - 'email' => $email, + 'email' => $this->getSamlResponseAttribute($samlAttributes, $emailAttr, null), ]; } @@ -115,22 +129,28 @@ class Saml2Service { if (isset($samlAttributes[$propertyKey])) { $data = $samlAttributes[$propertyKey]; - if (!is_array($data)) { - return $data; - } else if (count($data) == 0) { - return $defaultValue; - } else if (count($data) == 1) { - return $data[0]; - } else { - return $data; + if (is_array($data)) { + if (count($data) == 0) { + $data = $defaultValue; + } else if (count($data) == 1) { + $data = $data[0]; + } } + } else { + $data = $defaultValue; } - return $defaultValue; + return $data; } - protected function registerUser($userDetails) { - + /** + * Register a user that is authenticated but not + * already registered. + * @param array $userDetails + * @return User + */ + protected function registerUser($userDetails) + { // Create an array of the user data to create a new user instance $userData = [ 'name' => $userDetails['name'], @@ -146,96 +166,47 @@ class Saml2Service return $user; } - public function processLoginCallback($samlID, $samlAttributes) { - - $userDetails = $this->getUserDetails($samlID, $samlAttributes); + /** + * Get the user from the database for the specified details. + * @param array $userDetails + * @return User|null + */ + protected function getOrRegisterUser($userDetails) + { + $isRegisterEnabled = config('services.saml.auto_register') === true; $user = $this->user - ->where('external_auth_id', $userDetails['uid']) - ->first(); + ->where('external_auth_id', $userDetails['uid']) + ->first(); - $isLoggedIn = auth()->check(); - - if (!$isLoggedIn) { - if ($user === null && config('services.saml.auto_register') === true) { - $user = $this->registerUser($userDetails); - } - - if ($user !== null) { - auth()->login($user); - } + if ($user === null && $isRegisterEnabled) { + $user = $this->registerUser($userDetails); } return $user; } /** - * Sync the SAML groups to the user roles for the current user - * @param \BookStack\Auth\User $user - * @param array $samlAttributes + * Process the SAML response for a user. Login the user when + * they exist, optionally registering them automatically. + * @param string $samlID + * @param array $samlAttributes */ - public function syncGroups(User $user, array $samlAttributes) + public function processLoginCallback($samlID, $samlAttributes) { - $userSamlGroups = $this->getUserGroups($samlAttributes); + $userDetails = $this->getUserDetails($samlID, $samlAttributes); + $isLoggedIn = auth()->check(); - // Get the ids for the roles from the names - $samlGroupsAsRoles = $this->matchSamlGroupsToSystemsRoles($userSamlGroups); - - // Sync groups - if ($this->config['remove_from_groups']) { - $user->roles()->sync($samlGroupsAsRoles); - $this->userRepo->attachDefaultRole($user); + if ($isLoggedIn) { + logger()->error("Already logged in"); } else { - $user->roles()->syncWithoutDetaching($samlGroupsAsRoles); - } - } - - /** - * Match an array of group names from SAML to BookStack system roles. - * Formats group names to be lower-case and hyphenated. - * @param array $groupNames - * @return \Illuminate\Support\Collection - */ - protected function matchSamlGroupsToSystemsRoles(array $groupNames) - { - foreach ($groupNames as $i => $groupName) { - $groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName))); - } - - $roles = Role::query()->where(function (Builder $query) use ($groupNames) { - $query->whereIn('name', $groupNames); - foreach ($groupNames as $groupName) { - $query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%'); + $user = $this->getOrRegisterUser($userDetails); + if ($user === null) { + logger()->error("User does not exist"); + } else { + auth()->login($user); } - })->get(); - - $matchedRoles = $roles->filter(function (Role $role) use ($groupNames) { - return $this->roleMatchesGroupNames($role, $groupNames); - }); - - return $matchedRoles->pluck('id'); - } - - /** - * Check a role against an array of group names to see if it matches. - * Checked against role 'external_auth_id' if set otherwise the name of the role. - * @param \BookStack\Auth\Role $role - * @param array $groupNames - * @return bool - */ - protected function roleMatchesGroupNames(Role $role, array $groupNames) - { - if ($role->external_auth_id) { - $externalAuthIds = explode(',', strtolower($role->external_auth_id)); - foreach ($externalAuthIds as $externalAuthId) { - if (in_array(trim($externalAuthId), $groupNames)) { - return true; - } - } - return false; } - $roleName = str_replace(' ', '-', trim(strtolower($role->display_name))); - return in_array($roleName, $groupNames); + return $user; } - } From 8e723f10dc3db49df9dc66ea5a90e3153eda54e8 Mon Sep 17 00:00:00 2001 From: Daniel Seiler Date: Wed, 7 Aug 2019 15:31:10 +0200 Subject: [PATCH 005/257] Add error messages, fix LDAP error --- app/Auth/Access/ExternalAuthService.php | 9 +++--- app/Auth/Access/LdapService.php | 1 - app/Auth/Access/Saml2Service.php | 41 +++++++++++++++++-------- app/Config/services.php | 2 ++ app/Exceptions/SamlException.php | 2 +- resources/lang/de/errors.php | 2 ++ resources/lang/de_informal/errors.php | 1 + resources/lang/en/errors.php | 2 ++ resources/views/auth/login.blade.php | 2 +- 9 files changed, 42 insertions(+), 20 deletions(-) diff --git a/app/Auth/Access/ExternalAuthService.php b/app/Auth/Access/ExternalAuthService.php index b1c036018..77c7d1351 100644 --- a/app/Auth/Access/ExternalAuthService.php +++ b/app/Auth/Access/ExternalAuthService.php @@ -2,6 +2,7 @@ use BookStack\Auth\Role; use BookStack\Auth\User; +use Illuminate\Database\Eloquent\Builder; class ExternalAuthService { @@ -57,19 +58,19 @@ class ExternalAuthService /** * Sync the groups to the user roles for the current user * @param \BookStack\Auth\User $user - * @param array $samlAttributes + * @param array $userGroups */ public function syncWithGroups(User $user, array $userGroups) { // Get the ids for the roles from the names - $samlGroupsAsRoles = $this->matchGroupsToSystemsRoles($userSamlGroups); + $groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups); // Sync groups if ($this->config['remove_from_groups']) { - $user->roles()->sync($samlGroupsAsRoles); + $user->roles()->sync($groupsAsRoles); $this->userRepo->attachDefaultRole($user); } else { - $user->roles()->syncWithoutDetaching($samlGroupsAsRoles); + $user->roles()->syncWithoutDetaching($groupsAsRoles); } } } diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 3111ea9fa..b0700322f 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -5,7 +5,6 @@ use BookStack\Auth\User; use BookStack\Auth\UserRepo; use BookStack\Exceptions\LdapException; use Illuminate\Contracts\Auth\Authenticatable; -use Illuminate\Database\Eloquent\Builder; /** * Class LdapService diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index 95049efd2..056977a3d 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -5,8 +5,6 @@ use BookStack\Auth\User; use BookStack\Auth\UserRepo; use BookStack\Exceptions\SamlException; use Illuminate\Contracts\Auth\Authenticatable; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Log; /** @@ -117,6 +115,27 @@ class Saml2Service extends Access\ExternalAuthService return $userGroups; } + /** + * For an array of strings, return a default for an empty array, + * a string for an array with one element and the full array for + * more than one element. + * + * @param array $data + * @param $defaultValue + * @return string + */ + protected function simplifyValue(array $data, $defaultValue) { + switch (count($data)) { + case 0: + $data = $defaultValue; + break; + case 1: + $data = $data[0]; + break; + } + return $data; + } + /** * Get a property from an SAML response. * Handles properties potentially being an array. @@ -128,16 +147,9 @@ class Saml2Service extends Access\ExternalAuthService protected function getSamlResponseAttribute(array $samlAttributes, string $propertyKey, $defaultValue) { if (isset($samlAttributes[$propertyKey])) { - $data = $samlAttributes[$propertyKey]; - if (is_array($data)) { - if (count($data) == 0) { - $data = $defaultValue; - } else if (count($data) == 1) { - $data = $data[0]; - } - } + $data = $this->simplifyValue($samlAttributes[$propertyKey], $defaultValue); } else { - $data = $defaultValue; + $data = $defaultValue; } return $data; @@ -190,6 +202,7 @@ class Saml2Service extends Access\ExternalAuthService * they exist, optionally registering them automatically. * @param string $samlID * @param array $samlAttributes + * @throws SamlException */ public function processLoginCallback($samlID, $samlAttributes) { @@ -197,12 +210,14 @@ class Saml2Service extends Access\ExternalAuthService $isLoggedIn = auth()->check(); if ($isLoggedIn) { - logger()->error("Already logged in"); + throw new SamlException(trans('errors.saml_already_logged_in'), '/login'); } else { $user = $this->getOrRegisterUser($userDetails); if ($user === null) { - logger()->error("User does not exist"); + throw new SamlException(trans('errors.saml_user_not_registered', ['name' => $userDetails['uid']]), '/login'); } else { + $groups = $this->getUserGroups($samlAttributes); + $this->syncWithGroups($user, $groups); auth()->login($user); } } diff --git a/app/Config/services.php b/app/Config/services.php index 9cd647e6d..b3dc9f087 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -150,12 +150,14 @@ return [ ], 'saml' => [ + 'name' => env('SAML_NAME', 'SSO'), 'enabled' => env('SAML2_ENABLED', false), 'auto_register' => env('SAML_AUTO_REGISTER', false), 'email_attribute' => env('SAML_EMAIL_ATTRIBUTE', 'email'), 'display_name_attribute' => explode('|', env('SAML_DISPLAY_NAME_ATTRIBUTE', 'username')), 'user_name_attribute' => env('SAML_USER_NAME_ATTRIBUTE', null), 'group_attribute' => env('SAML_GROUP_ATTRIBUTE', 'group'), + 'remove_from_groups' => env('SAML_REMOVE_FROM_GROUPS',false), 'user_to_groups' => env('SAML_USER_TO_GROUPS', false), 'id_is_user_name' => env('SAML_ID_IS_USER_NAME', true), ] diff --git a/app/Exceptions/SamlException.php b/app/Exceptions/SamlException.php index f9668919c..13db23f27 100644 --- a/app/Exceptions/SamlException.php +++ b/app/Exceptions/SamlException.php @@ -1,6 +1,6 @@ 'LDAP-Zugriff mit DN und Passwort ist fehlgeschlagen', 'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert.', 'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.', + 'saml_already_logged_in' => 'Sie sind bereits angemeldet', + 'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert', 'social_no_action_defined' => 'Es ist keine Aktion definiert', 'social_login_bad_response' => "Fehler bei der :socialAccount-Anmeldung: \n:error", 'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melden Sie sich mit dem :socialAccount-Konto an.', diff --git a/resources/lang/de_informal/errors.php b/resources/lang/de_informal/errors.php index 924deee0d..420c35c8d 100644 --- a/resources/lang/de_informal/errors.php +++ b/resources/lang/de_informal/errors.php @@ -9,6 +9,7 @@ return [ // Auth 'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.', 'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.', + 'saml_already_logged_in' => 'Du bist bereits angemeldet', 'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.', 'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.', 'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.', diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index b91a0c3e1..40c0bbffb 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -17,6 +17,8 @@ return [ 'ldap_fail_authed' => 'LDAP access failed using given dn & password details', 'ldap_extension_not_installed' => 'LDAP PHP extension not installed', 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', + 'saml_already_logged_in' => 'Already logged in', + 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', 'social_no_action_defined' => 'No action defined', 'social_login_bad_response' => "Error received during :socialAccount login: \n:error", 'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.', diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 72d8d00aa..8d89c1288 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -51,7 +51,7 @@ @endif From 3bcfe2a460d862d3be1b17f435a5cfdfad512d04 Mon Sep 17 00:00:00 2001 From: Virgile Date: Tue, 13 Aug 2019 17:30:29 +0200 Subject: [PATCH 006/257] Adds autofocus on the email field of the standard login page. --- resources/views/auth/forms/login/standard.blade.php | 2 +- resources/views/form/text.blade.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/views/auth/forms/login/standard.blade.php b/resources/views/auth/forms/login/standard.blade.php index dc6081637..bfe437a8c 100644 --- a/resources/views/auth/forms/login/standard.blade.php +++ b/resources/views/auth/forms/login/standard.blade.php @@ -1,6 +1,6 @@
- @include('form.text', ['name' => 'email', 'tabindex' => 1]) + @include('form.text', ['name' => 'email', 'tabindex' => 1, 'focus' => 1])
diff --git a/resources/views/form/text.blade.php b/resources/views/form/text.blade.php index 909e87286..89e72ddb4 100644 --- a/resources/views/form/text.blade.php +++ b/resources/views/form/text.blade.php @@ -1,4 +1,5 @@ has($name)) class="text-neg" @endif @if(isset($placeholder)) placeholder="{{$placeholder}}" @endif @if(isset($disabled) && $disabled) disabled="disabled" @endif @@ -6,4 +7,4 @@ @if(isset($model) || old($name)) value="{{ old($name) ? old($name) : $model->$name}}" @endif> @if($errors->has($name))
{{ $errors->first($name) }}
-@endif \ No newline at end of file +@endif From c14611d14bfd87148fbd96c9e33ed171b01dbd6a Mon Sep 17 00:00:00 2001 From: James Geiger Date: Wed, 14 Aug 2019 23:45:48 -0500 Subject: [PATCH 007/257] Fixed inline code overflowing off of page in issue #1575. --- resources/assets/js/services/code.js | 8 +++++--- resources/assets/sass/_text.scss | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/assets/js/services/code.js b/resources/assets/js/services/code.js index 1e0e48289..4ab25aeb7 100644 --- a/resources/assets/js/services/code.js +++ b/resources/assets/js/services/code.js @@ -102,6 +102,7 @@ function highlightElem(elem) { value: content, mode: mode, lineNumbers: true, + lineWrapping: true, theme: getTheme(), readOnly: true }); @@ -188,6 +189,7 @@ function wysiwygView(elem) { value: content, mode: getMode(lang), lineNumbers: true, + lineWrapping: true, theme: getTheme(), readOnly: true }); @@ -213,8 +215,8 @@ function popupEditor(elem, modeSuggestion) { value: content, mode: getMode(modeSuggestion), lineNumbers: true, - theme: getTheme(), - lineWrapping: true + lineWrapping: true, + theme: getTheme() }); } @@ -254,8 +256,8 @@ function markdownEditor(elem) { value: content, mode: "markdown", lineNumbers: true, - theme: getTheme(), lineWrapping: true, + theme: getTheme(), scrollPastEnd: true, }); } diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index f1d165a47..5959ddab1 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -220,7 +220,7 @@ code { @extend .code-base; display: inline; padding: 1px 3px; - white-space:pre; + white-space:pre-wrap; line-height: 1.2em; margin-bottom: 1.2em; } @@ -366,4 +366,3 @@ span.sep { margin-right: $-xs; pointer-events: none; } - From 213e9d2941dbedbce8fd639fbf09552ac72ad728 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 6 Sep 2019 22:14:39 +0100 Subject: [PATCH 008/257] Upgraded to Laravel 5.6 --- app/Config/app.php | 4 - app/Config/hashing.php | 38 + app/Config/logging.php | 74 + .../Controllers/Auth/RegisterController.php | 3 +- app/Http/Kernel.php | 2 +- .../Middleware/CheckForMaintenanceMode.php | 17 + app/Http/Middleware/TrustProxies.php | 12 +- composer.json | 31 +- composer.lock | 2411 ++++++++++------- database/seeds/DatabaseSeeder.php | 2 +- phpunit.xml | 4 +- resources/lang/en/validation.php | 30 +- .../views/pages/markdown-editor.blade.php | 2 +- .../views/pages/wysiwyg-editor.blade.php | 2 +- 14 files changed, 1678 insertions(+), 954 deletions(-) create mode 100644 app/Config/hashing.php create mode 100644 app/Config/logging.php create mode 100644 app/Http/Middleware/CheckForMaintenanceMode.php diff --git a/app/Config/app.php b/app/Config/app.php index 88052e94c..ec78e828b 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -72,10 +72,6 @@ return [ // Encryption cipher 'cipher' => 'AES-256-CBC', - // Logging configuration - // Options: single, daily, syslog, errorlog - 'log' => env('APP_LOGGING', 'single'), - // Application Services Provides 'providers' => [ diff --git a/app/Config/hashing.php b/app/Config/hashing.php new file mode 100644 index 000000000..ca73c5586 --- /dev/null +++ b/app/Config/hashing.php @@ -0,0 +1,38 @@ + 'bcrypt', + + // Bcrypt Options + // Here you may specify the configuration options that should be used when + // passwords are hashed using the Bcrypt algorithm. This will allow you + // to control the amount of time it takes to hash the given password. + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + // Argon Options + // Here you may specify the configuration options that should be used when + // passwords are hashed using the Argon algorithm. These will allow you + // to control the amount of time it takes to hash the given password. + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; \ No newline at end of file diff --git a/app/Config/logging.php b/app/Config/logging.php new file mode 100644 index 000000000..880b35453 --- /dev/null +++ b/app/Config/logging.php @@ -0,0 +1,74 @@ + env('LOG_CHANNEL', 'single'), + + // Log Channels + // Here you may configure the log channels for your application. Out of + // the box, Laravel uses the Monolog PHP logging library. This gives + // you a variety of powerful log handlers / formatters to utilize. + // Available Drivers: "single", "daily", "slack", "syslog", + // "errorlog", "monolog", + // "custom", "stack" + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + 'days' => 7, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + +]; \ No newline at end of file diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index c411f2363..83bd307e4 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -18,6 +18,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Redirector; +use Illuminate\Support\Facades\Hash; use Laravel\Socialite\Contracts\User as SocialUser; use Validator; @@ -129,7 +130,7 @@ class RegisterController extends Controller return User::create([ 'name' => $data['name'], 'email' => $data['email'], - 'password' => bcrypt($data['password']), + 'password' => Hash::make($data['password']), ]); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cd894de95..67e01cd04 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -12,7 +12,7 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, + \BookStack\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \BookStack\Http\Middleware\TrimStrings::class, \BookStack\Http\Middleware\TrustProxies::class, diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php new file mode 100644 index 000000000..4b4bacd83 --- /dev/null +++ b/app/Http/Middleware/CheckForMaintenanceMode.php @@ -0,0 +1,17 @@ + 'FORWARDED', - Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', - Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', - Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', - Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', - ]; + protected $headers = Request::HEADER_X_FORWARDED_ALL; /** * Handle the request, Set the correct user-configured proxy information. diff --git a/composer.json b/composer.json index 61bb8509e..49ec185cb 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "type": "project", "require": { - "php": ">=7.0.5", + "php": "^7.1.3", "ext-json": "*", "ext-tidy": "*", "ext-dom": "*", @@ -13,8 +13,8 @@ "ext-mbstring": "*", "ext-gd": "*", "ext-curl": "*", - "laravel/framework": "~5.5.44", - "fideloper/proxy": "~3.3", + "laravel/framework": "5.6.*", + "fideloper/proxy": "^4.0", "intervention/image": "^2.4", "laravel/socialite": "3.0.x-dev", "league/flysystem-aws-s3-v3": "^1.0", @@ -31,16 +31,15 @@ "doctrine/dbal": "^2.5" }, "require-dev": { - "filp/whoops": "~2.0", - "fzaninotto/faker": "~1.4", - "mockery/mockery": "~1.0", - "phpunit/phpunit": "~6.0", - "symfony/css-selector": "3.1.*", - "symfony/dom-crawler": "3.1.*", - "laravel/browser-kit-testing": "^2.0", - "barryvdh/laravel-ide-helper": "^2.4.1", - "barryvdh/laravel-debugbar": "^3.1.0", - "squizlabs/php_codesniffer": "^3.2" + "filp/whoops": "^2.0", + "fzaninotto/faker": "^1.4", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0", + "nunomaduro/collision": "^2.0", + "laravel/browser-kit-testing": "^4.2.1", + "barryvdh/laravel-ide-helper": "^2.6.4", + "barryvdh/laravel-debugbar": "^3.2.8", + "squizlabs/php_codesniffer": "^3.4" }, "autoload": { "classmap": [ @@ -87,7 +86,9 @@ "optimize-autoloader": true, "preferred-install": "dist", "platform": { - "php": "7.0.5" + "php": "7.1.3" } - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/composer.lock b/composer.lock index d7734ce1a..fe9bc4578 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,26 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "0946a07729a7a1bfef9bac185a870afd", + "content-hash": "ce7b23f15edb5a2dd6bdaf23ce1c8b5d", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.86.2", + "version": "3.110.11", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "50224232ac7a4e2a6fa4ebbe0281e5b7503acf76" + "reference": "3f222649634fa039c59f58082e60159a6bb59bbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/50224232ac7a4e2a6fa4ebbe0281e5b7503acf76", - "reference": "50224232ac7a4e2a6fa4ebbe0281e5b7503acf76", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3f222649634fa039c59f58082e60159a6bb59bbf", + "reference": "3f222649634fa039c59f58082e60159a6bb59bbf", "shasum": "" }, "require": { "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "ext-spl": "*", "guzzlehttp/guzzle": "^5.3.3|^6.2.1", "guzzlehttp/promises": "~1.0", "guzzlehttp/psr7": "^1.4.1", @@ -42,7 +41,8 @@ "ext-sockets": "*", "nette/neon": "^2.3", "phpunit/phpunit": "^4.8.35|^5.4.3", - "psr/cache": "^1.0" + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -87,25 +87,25 @@ "s3", "sdk" ], - "time": "2019-01-18T21:10:44+00:00" + "time": "2019-09-06T18:21:14+00:00" }, { "name": "barryvdh/laravel-dompdf", - "version": "v0.8.3", + "version": "v0.8.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-dompdf.git", - "reference": "46781d0304277845a19c09c169bc595fd182cce4" + "reference": "7393732b2f3a3ee357974cbb0c46c9b65b84dad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/46781d0304277845a19c09c169bc595fd182cce4", - "reference": "46781d0304277845a19c09c169bc595fd182cce4", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/7393732b2f3a3ee357974cbb0c46c9b65b84dad1", + "reference": "7393732b2f3a3ee357974cbb0c46c9b65b84dad1", "shasum": "" }, "require": { "dompdf/dompdf": "^0.8", - "illuminate/support": "5.5.x|5.6.x|5.7.x", + "illuminate/support": "^5.5|^6", "php": ">=7" }, "type": "library", @@ -143,25 +143,25 @@ "laravel", "pdf" ], - "time": "2018-08-31T13:25:44+00:00" + "time": "2019-08-23T14:30:33+00:00" }, { "name": "barryvdh/laravel-snappy", - "version": "v0.4.3", + "version": "v0.4.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-snappy.git", - "reference": "62bb5017b7004bf3e48bfed3d5c00d3dc6e60478" + "reference": "9be767fc7a082665a84945f36c70b0cbead91ce9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/62bb5017b7004bf3e48bfed3d5c00d3dc6e60478", - "reference": "62bb5017b7004bf3e48bfed3d5c00d3dc6e60478", + "url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/9be767fc7a082665a84945f36c70b0cbead91ce9", + "reference": "9be767fc7a082665a84945f36c70b0cbead91ce9", "shasum": "" }, "require": { - "illuminate/filesystem": "5.5.x|5.6.x|5.7.x", - "illuminate/support": "5.5.x|5.6.x|5.7.x", + "illuminate/filesystem": "5.5.x|5.6.x|5.7.x|5.8.x|6.0.*", + "illuminate/support": "5.5.x|5.6.x|5.7.x|5.8.x|6.0.*", "knplabs/knp-snappy": "^1", "php": ">=7" }, @@ -195,7 +195,7 @@ "email": "barryvdh@gmail.com" } ], - "description": "Snappy PDF/Image for Laravel 4", + "description": "Snappy PDF/Image for Laravel", "keywords": [ "image", "laravel", @@ -204,7 +204,7 @@ "wkhtmltoimage", "wkhtmltopdf" ], - "time": "2018-09-06T10:14:15+00:00" + "time": "2019-08-30T16:12:23+00:00" }, { "name": "cogpowered/finediff", @@ -257,103 +257,40 @@ ], "time": "2014-05-19T10:25:02+00:00" }, - { - "name": "doctrine/annotations", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2017-02-24T16:22:25+00:00" - }, { "name": "doctrine/cache", - "version": "v1.6.2", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" + "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", + "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57", + "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57", "shasum": "" }, "require": { - "php": "~5.5|~7.0" + "php": "~7.1" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0", - "predis/predis": "~1.0", - "satooshi/php-coveralls": "~0.6" + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^4.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -388,43 +325,57 @@ } ], "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "http://www.doctrine-project.org", + "homepage": "https://www.doctrine-project.org", "keywords": [ "cache", "caching" ], - "time": "2017-07-22T12:49:21+00:00" + "time": "2018-08-21T18:01:43+00:00" }, { - "name": "doctrine/collections", - "version": "v1.4.0", + "name": "doctrine/dbal", + "version": "v2.9.2", "source": { "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + "url": "https://github.com/doctrine/dbal.git", + "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "doctrine/cache": "^1.0", + "doctrine/event-manager": "^1.0", + "ext-pdo": "*", + "php": "^7.1" }, "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" + "doctrine/coding-standard": "^5.0", + "jetbrains/phpstorm-stubs": "^2018.1.2", + "phpstan/phpstan": "^0.10.1", + "phpunit/phpunit": "^7.4", + "symfony/console": "^2.0.5|^3.0|^4.0", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5" }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "3.0.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" + "psr-4": { + "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" } }, "notification-url": "https://packagist.org/downloads/", @@ -447,50 +398,50 @@ { "name": "Jonathan Wage", "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" } ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", "keywords": [ - "array", - "collections", - "iterator" + "abstraction", + "database", + "dbal", + "mysql", + "persistence", + "pgsql", + "php", + "queryobject" ], - "time": "2017-01-03T10:49:41+00:00" + "time": "2018-12-31T03:27:51+00:00" }, { - "name": "doctrine/common", - "version": "v2.7.3", + "name": "doctrine/event-manager", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9" + "url": "https://github.com/doctrine/event-manager.git", + "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/4acb8f89626baafede6ee5475bc5844096eba8a9", - "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", "shasum": "" }, "require": { - "doctrine/annotations": "1.*", - "doctrine/cache": "1.*", - "doctrine/collections": "1.*", - "doctrine/inflector": "1.*", - "doctrine/lexer": "1.*", - "php": "~5.6|~7.0" + "php": "^7.1" + }, + "conflict": { + "doctrine/common": "<2.9@dev" }, "require-dev": { - "phpunit/phpunit": "^5.4.6" + "doctrine/coding-standard": "^4.0", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { @@ -522,106 +473,37 @@ { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" } ], - "description": "Common Library for Doctrine projects", - "homepage": "http://www.doctrine-project.org", + "description": "Doctrine Event Manager component", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ - "annotations", - "collections", - "eventmanager", - "persistence", - "spl" + "event", + "eventdispatcher", + "eventmanager" ], - "time": "2017-07-22T08:35:12+00:00" - }, - { - "name": "doctrine/dbal", - "version": "v2.5.13", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "729340d8d1eec8f01bff708e12e449a3415af873" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/729340d8d1eec8f01bff708e12e449a3415af873", - "reference": "729340d8d1eec8f01bff708e12e449a3415af873", - "shasum": "" - }, - "require": { - "doctrine/common": ">=2.4,<2.8-dev", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "symfony/console": "2.*||^3.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\DBAL\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Database Abstraction Layer", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "database", - "dbal", - "persistence", - "queryobject" - ], - "time": "2017-07-22T20:44:48+00:00" + "time": "2018-06-11T11:59:03+00:00" }, { "name": "doctrine/inflector", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462" + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462", - "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { "phpunit/phpunit": "^6.2" @@ -629,7 +511,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { @@ -671,25 +553,28 @@ "singularize", "string" ], - "time": "2017-07-22T12:18:28+00:00" + "time": "2018-01-09T20:05:19+00:00" }, { "name": "doctrine/lexer", - "version": "v1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", "shasum": "" }, "require": { "php": ">=5.3.2" }, + "require-dev": { + "phpunit/phpunit": "^4.5" + }, "type": "library", "extra": { "branch-alias": { @@ -697,8 +582,8 @@ } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -719,13 +604,16 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ + "annotations", + "docblock", "lexer", - "parser" + "parser", + "php" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2019-06-08T11:03:04+00:00" }, { "name": "dompdf/dompdf", @@ -794,17 +682,71 @@ "time": "2018-12-14T02:40:31+00:00" }, { - "name": "egulias/email-validator", - "version": "2.1.7", + "name": "dragonmantank/cron-expression", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e" + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/709f21f92707308cdf8f9bcfa1af4cb26586521e", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/72b6fbf76adb3cf5bc0db68559b33d41219aba27", + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4|^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "time": "2019-03-31T00:38:28+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.11", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23", + "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23", "shasum": "" }, "require": { @@ -814,7 +756,8 @@ "require-dev": { "dominicsayers/isemail": "dev-master", "phpunit/phpunit": "^4.8.35||^5.7||^6.0", - "satooshi/php-coveralls": "^1.0.1" + "satooshi/php-coveralls": "^1.0.1", + "symfony/phpunit-bridge": "^4.4@dev" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -822,7 +765,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -848,20 +791,20 @@ "validation", "validator" ], - "time": "2018-12-04T22:38:24+00:00" + "time": "2019-08-13T17:33:27+00:00" }, { "name": "erusev/parsedown", - "version": "1.7.1", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", "shasum": "" }, "require": { @@ -894,36 +837,33 @@ "markdown", "parser" ], - "time": "2018-03-08T01:11:30+00:00" + "time": "2019-03-17T18:48:37+00:00" }, { "name": "fideloper/proxy", - "version": "3.3.4", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f" + "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9cdf6f118af58d89764249bbcc7bb260c132924f", - "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/03085e58ec7bee24773fa5a8850751a6e61a7e8a", + "reference": "03085e58ec7bee24773fa5a8850751a6e61a7e8a", "shasum": "" }, "require": { - "illuminate/contracts": "~5.0", + "illuminate/contracts": "^5.0|^6.0|^7.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "~5.0", - "mockery/mockery": "~0.9.3", - "phpunit/phpunit": "^5.7" + "illuminate/http": "^5.0|^6.0|^7.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - }, "laravel": { "providers": [ "Fideloper\\Proxy\\TrustedProxyServiceProvider" @@ -951,7 +891,7 @@ "proxy", "trusted proxy" ], - "time": "2017-06-15T17:19:42+00:00" + "time": "2019-09-03T16:45:42+00:00" }, { "name": "gathercontent/htmldiff", @@ -1120,33 +1060,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.5.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9f83dded91781a01c63574e387eaa769be769115" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", - "reference": "9f83dded91781a01c63574e387eaa769be769115", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-zlib": "*", "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1183,20 +1127,20 @@ "uri", "url" ], - "time": "2018-12-04T20:46:45+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "intervention/image", - "version": "2.4.2", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb" + "reference": "39eaef720d082ecc54c64bf54541c55f10db546d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb", - "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb", + "url": "https://api.github.com/repos/Intervention/image/zipball/39eaef720d082ecc54c64bf54541c55f10db546d", + "reference": "39eaef720d082ecc54c64bf54541c55f10db546d", "shasum": "" }, "require": { @@ -1253,29 +1197,29 @@ "thumbnail", "watermark" ], - "time": "2018-05-29T14:19:03+00:00" + "time": "2019-06-24T14:06:31+00:00" }, { "name": "knplabs/knp-snappy", - "version": "v1.0.4", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/KnpLabs/snappy.git", - "reference": "144c4ecd1ccaeda936bf832b93079efc490e6850" + "reference": "ea037298d3c613454da77ecb9588cf0397d695e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/144c4ecd1ccaeda936bf832b93079efc490e6850", - "reference": "144c4ecd1ccaeda936bf832b93079efc490e6850", + "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/ea037298d3c613454da77ecb9588cf0397d695e1", + "reference": "ea037298d3c613454da77ecb9588cf0397d695e1", "shasum": "" }, "require": { - "php": ">=5.6", + "php": ">=7.1", "psr/log": "^1.0", - "symfony/process": "~2.3 || ~3.0 || ~4.0" + "symfony/process": "~3.4||~4.1" }, "require-dev": { - "phpunit/phpunit": "~4.8.36" + "phpunit/phpunit": "~7.4" }, "suggest": { "h4cc/wkhtmltoimage-amd64": "Provides wkhtmltoimage-amd64 binary for Linux-compatible machines, use version `~0.12` as dependency", @@ -1287,7 +1231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -1319,47 +1263,95 @@ "thumbnail", "wkhtmltopdf" ], - "time": "2018-01-22T19:40:51+00:00" + "time": "2018-12-14T14:59:37+00:00" }, { - "name": "laravel/framework", - "version": "v5.5.44", + "name": "kylekatarnls/update-helper", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b" + "url": "https://github.com/kylekatarnls/update-helper.git", + "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b", - "reference": "00615aa27eb98f0ee6fb9f2160c6c60ae04abd1b", + "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/5786fa188e0361b9adf9e8199d7280d1b2db165e", + "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0.0", + "php": ">=5.3.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "composer/composer": "2.0.x-dev || ^2.0.0-dev", + "phpunit/phpunit": ">=4.8.35 <6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "UpdateHelper\\ComposerPlugin" + }, + "autoload": { + "psr-0": { + "UpdateHelper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Update helper", + "time": "2019-07-29T11:03:54+00:00" + }, + { + "name": "laravel/framework", + "version": "v5.6.39", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "37bb306f516669ab4f888c16003f694313ab299e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/37bb306f516669ab4f888c16003f694313ab299e", + "reference": "37bb306f516669ab4f888c16003f694313ab299e", "shasum": "" }, "require": { "doctrine/inflector": "~1.1", + "dragonmantank/cron-expression": "~2.0", "erusev/parsedown": "~1.7", "ext-mbstring": "*", "ext-openssl": "*", "league/flysystem": "^1.0.8", "monolog/monolog": "~1.12", - "mtdowling/cron-expression": "~1.0", - "nesbot/carbon": "^1.24.1", - "php": ">=7.0", + "nesbot/carbon": "1.25.*", + "php": "^7.1.3", "psr/container": "~1.0", "psr/simple-cache": "^1.0", - "ramsey/uuid": "~3.0", + "ramsey/uuid": "^3.7", "swiftmailer/swiftmailer": "~6.0", - "symfony/console": "~3.3", - "symfony/debug": "~3.3", - "symfony/finder": "~3.3", - "symfony/http-foundation": "~3.3", - "symfony/http-kernel": "~3.3", - "symfony/process": "~3.3", - "symfony/routing": "~3.3", - "symfony/var-dumper": "~3.3", - "tijsverkoyen/css-to-inline-styles": "~2.2", + "symfony/console": "~4.0", + "symfony/debug": "~4.0", + "symfony/finder": "~4.0", + "symfony/http-foundation": "~4.0", + "symfony/http-kernel": "~4.0", + "symfony/process": "~4.0", + "symfony/routing": "~4.0", + "symfony/var-dumper": "~4.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.1", "vlucas/phpdotenv": "~2.2" }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, "replace": { "illuminate/auth": "self.version", "illuminate/broadcasting": "self.version", @@ -1388,44 +1380,46 @@ "illuminate/support": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version", - "tightenco/collect": "<5.5.33" + "illuminate/view": "self.version" }, "require-dev": { "aws/aws-sdk-php": "~3.0", - "doctrine/dbal": "~2.5", + "doctrine/dbal": "~2.6", "filp/whoops": "^2.1.4", + "league/flysystem-cached-adapter": "~1.0", "mockery/mockery": "~1.0", - "orchestra/testbench-core": "3.5.*", + "moontoast/math": "^1.1", + "orchestra/testbench-core": "3.6.*", "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~6.0", + "phpunit/phpunit": "~7.0", "predis/predis": "^1.1.1", - "symfony/css-selector": "~3.3", - "symfony/dom-crawler": "~3.3" + "symfony/css-selector": "~4.0", + "symfony/dom-crawler": "~4.0" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.6).", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", "laravel/tinker": "Required to use the tinker console command (~1.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", - "league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (~1.0).", "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (~1.0).", "nexmo/client": "Required to use the Nexmo transport (~1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~4.0).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~4.0).", "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "5.6-dev" } }, "autoload": { @@ -1453,7 +1447,7 @@ "framework", "laravel" ], - "time": "2018-10-04T14:51:24+00:00" + "time": "2018-10-04T14:50:41+00:00" }, { "name": "laravel/socialite", @@ -1520,16 +1514,16 @@ }, { "name": "league/flysystem", - "version": "1.0.49", + "version": "1.0.55", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd" + "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a63cc83d8a931b271be45148fa39ba7156782ffd", - "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/33c91155537c6dc899eacdc54a13ac6303f156e6", + "reference": "33c91155537c6dc899eacdc54a13ac6303f156e6", "shasum": "" }, "require": { @@ -1600,20 +1594,20 @@ "sftp", "storage" ], - "time": "2018-11-23T23:41:29+00:00" + "time": "2019-08-24T11:17:19+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.21", + "version": "1.0.23", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "43523fec10a831ea48bedb3277e3f3fa218f4e49" + "reference": "15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/43523fec10a831ea48bedb3277e3f3fa218f4e49", - "reference": "43523fec10a831ea48bedb3277e3f3fa218f4e49", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4", + "reference": "15b0cdeab7240bf8e8bffa85ae5275bbc3692bf4", "shasum": "" }, "require": { @@ -1647,7 +1641,7 @@ } ], "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2018-10-08T07:53:55+00:00" + "time": "2019-06-05T17:18:29+00:00" }, { "name": "league/oauth1-client", @@ -1714,16 +1708,16 @@ }, { "name": "monolog/monolog", - "version": "1.24.0", + "version": "1.25.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf", + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf", "shasum": "" }, "require": { @@ -1788,51 +1782,7 @@ "logging", "psr-3" ], - "time": "2018-11-05T09:00:11+00:00" - }, - { - "name": "mtdowling/cron-expression", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/mtdowling/cron-expression.git", - "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad", - "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Cron\\": "src/Cron/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", - "keywords": [ - "cron", - "schedule" - ], - "time": "2017-01-23T04:29:33+00:00" + "time": "2019-09-06T13:49:17+00:00" }, { "name": "mtdowling/jmespath.php", @@ -1891,40 +1841,38 @@ }, { "name": "nesbot/carbon", - "version": "1.36.2", + "version": "1.25.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9" + "reference": "ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9", - "reference": "cd324b98bc30290f233dd0e75e6ce49f7ab2a6c9", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8", + "reference": "ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8", "shasum": "" }, "require": { + "kylekatarnls/update-helper": "^1.1", "php": ">=5.3.9", "symfony/translation": "~2.6 || ~3.0 || ~4.0" }, "require-dev": { + "composer/composer": "^1.2", + "friendsofphp/php-cs-fixer": "~2", "phpunit/phpunit": "^4.8.35 || ^5.7" }, - "suggest": { - "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.", - "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors." - }, + "bin": [ + "bin/upgrade-carbon" + ], "type": "library", "extra": { - "laravel": { - "providers": [ - "Carbon\\Laravel\\ServiceProvider" - ] - } + "update-helper": "Carbon\\Upgrade" }, "autoload": { "psr-4": { - "": "src/" + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1945,7 +1893,7 @@ "datetime", "time" ], - "time": "2018-12-28T10:07:33+00:00" + "time": "2019-06-03T17:56:44+00:00" }, { "name": "paragonie/random_compat", @@ -2315,24 +2263,24 @@ }, { "name": "ralouphie/getallheaders", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~3.7.0", - "satooshi/php-coveralls": ">=1.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -2351,7 +2299,7 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2016-02-11T07:05:27+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { "name": "ramsey/uuid", @@ -2555,19 +2503,20 @@ }, { "name": "socialiteproviders/manager", - "version": "v3.3.4", + "version": "v3.4.1", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "58b72a667da292a1d0a0b1e6e9aeda4053617030" + "reference": "e79a1abb21f153f4a46d1a60abc72cba82d55f35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/58b72a667da292a1d0a0b1e6e9aeda4053617030", - "reference": "58b72a667da292a1d0a0b1e6e9aeda4053617030", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e79a1abb21f153f4a46d1a60abc72cba82d55f35", + "reference": "e79a1abb21f153f4a46d1a60abc72cba82d55f35", "shasum": "" }, "require": { + "illuminate/support": "~5.4|~5.7.0|~5.8.0|^6.0", "laravel/socialite": "~3.0|~4.0", "php": "^5.6 || ^7.0" }, @@ -2600,10 +2549,14 @@ { "name": "Anton Komarev", "email": "a.komarev@cybercog.su" + }, + { + "name": "Miguel Piedrafita", + "email": "soy@miguelpiedrafita.com" } ], "description": "Easily add new or override built-in providers in Laravel Socialite.", - "time": "2019-01-16T07:58:54+00:00" + "time": "2019-09-05T22:58:45+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -2755,25 +2708,28 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v6.1.3", + "version": "v6.2.1", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4" + "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", + "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", "shasum": "" }, "require": { "egulias/email-validator": "~2.0", - "php": ">=7.0.0" + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.3@dev" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" }, "suggest": { "ext-intl": "Needed to support internationalized email addresses", @@ -2782,7 +2738,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -2810,49 +2766,55 @@ "mail", "mailer" ], - "time": "2018-09-11T07:12:52+00:00" + "time": "2019-04-21T09:21:45+00:00" }, { "name": "symfony/console", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "b0878233cb5c4391347e5495089c7af11b8e6201" + "reference": "de63799239b3881b8a08f8481b22348f77ed7b36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201", - "reference": "b0878233cb5c4391347e5495089c7af11b8e6201", + "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36", + "reference": "de63799239b3881b8a08f8481b22348f77ed7b36", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/debug": "~2.8|~3.0", - "symfony/polyfill-mbstring": "~1.0" + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.3", - "symfony/dependency-injection": "~3.3", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/http-kernel": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", - "symfony/filesystem": "", + "symfony/lock": "", "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2879,29 +2841,29 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-07-29T21:27:59+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "symfony/css-selector", - "version": "v3.1.10", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d" + "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", - "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/c6e5e2a00db768c92c3ae131532af4e1acc7bd03", + "reference": "c6e5e2a00db768c92c3ae131532af4e1acc7bd03", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2917,14 +2879,14 @@ "MIT" ], "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" @@ -2932,36 +2894,36 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:31:54+00:00" + "time": "2019-08-20T14:07:54+00:00" }, { "name": "symfony/debug", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13" + "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13", - "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13", + "url": "https://api.github.com/repos/symfony/debug/zipball/afcdea44a2e399c1e4b52246ec8d54c715393ced", + "reference": "afcdea44a2e399c1e4b52246ec8d54c715393ced", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^7.1.3", "psr/log": "~1.0" }, "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~2.8|~3.0" + "symfony/http-kernel": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2988,34 +2950,41 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-07-28T15:27:31+00:00" + "time": "2019-08-20T14:27:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" + "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/429d0a1451d4c9c4abe1959b2986b88794b9b7d2", + "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", + "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3024,7 +2993,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3051,29 +3020,87 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-09T14:53:08+00:00" + "time": "2019-08-26T08:55:16+00:00" }, { - "name": "symfony/finder", - "version": "v3.3.6", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c61766f4440ca687de1084a5c00b08e167a2575c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c", + "reference": "c61766f4440ca687de1084a5c00b08e167a2575c", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-20T06:46:26+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" } }, "autoload": { @@ -3100,33 +3127,35 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2019-08-14T12:26:46+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031" + "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49e8cd2d59a7aa9bfab19e46de680c76e500a031", - "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d804bea118ff340a12e22a79f9c7e7eb56b35adc", + "reference": "d804bea118ff340a12e22a79f9c7e7eb56b35adc", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^7.1.3", + "symfony/mime": "^4.3", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0" + "predis/predis": "~1.0", + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3153,66 +3182,72 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-07-21T11:04:46+00:00" + "time": "2019-08-26T08:55:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5" + "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db10d05f1d95e4168e638db7a81c79616f568ea5", - "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5e0fc71be03d52cd00c423061cfd300bd6f92a52", + "reference": "5e0fc71be03d52cd00c423061cfd300bd6f92a52", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "~2.8|~3.0", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/http-foundation": "~3.3" + "symfony/debug": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/http-foundation": "^4.1.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php73": "^1.9" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.3", - "symfony/var-dumper": "<3.3", + "symfony/browser-kit": "<4.3", + "symfony/config": "<3.4", + "symfony/dependency-injection": "<4.3", + "symfony/translation": "<4.2", + "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0", - "symfony/console": "~2.8|~3.0", - "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/dom-crawler": "~2.8|~3.0", - "symfony/expression-language": "~2.8|~3.0", - "symfony/finder": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0", - "symfony/routing": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0", - "symfony/templating": "~2.8|~3.0", - "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.3" + "symfony/browser-kit": "^4.3", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dependency-injection": "^4.3", + "symfony/dom-crawler": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "~4.2", + "symfony/translation-contracts": "^1.1", + "symfony/var-dumper": "^4.1.1", + "twig/twig": "^1.34|^2.4" }, "suggest": { "symfony/browser-kit": "", - "symfony/class-loader": "", "symfony/config": "", "symfony/console": "", "symfony/dependency-injection": "", - "symfony/finder": "", "symfony/var-dumper": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3239,20 +3274,79 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-08-01T10:25:59+00:00" + "time": "2019-08-26T16:47:42+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "name": "symfony/mime", + "version": "v4.3.4", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/symfony/mime.git", + "reference": "987a05df1c6ac259b34008b932551353f4f408df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/mime/zipball/987a05df1c6ac259b34008b932551353f4f408df", + "reference": "987a05df1c6ac259b34008b932551353f4f408df", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "~3.4|^4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "time": "2019-08-22T08:16:11+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -3264,7 +3358,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3280,13 +3374,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3297,20 +3391,141 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "name": "symfony/polyfill-iconv", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "685968b11e61a347c18bf25db32effa478be610f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", + "reference": "685968b11e61a347c18bf25db32effa478be610f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -3322,7 +3537,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3356,29 +3571,142 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/process", - "version": "v3.3.6", + "name": "symfony/polyfill-php72", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "04ce3335667451138df4307d6a9b61565560199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/process", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/e89969c00d762349f078db1128506f7f3dcc0d4a", + "reference": "e89969c00d762349f078db1128506f7f3dcc0d4a", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" } }, "autoload": { @@ -3405,44 +3733,42 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-13T13:05:09+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "symfony/routing", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26" + "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26", - "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26", + "url": "https://api.github.com/repos/symfony/routing/zipball/ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", + "reference": "ff1049f6232dc5b6023b1ff1c6de56f82bcd264f", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.3" + "symfony/config": "<4.2", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/common": "~2.2", + "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/http-foundation": "~2.8|~3.0", - "symfony/yaml": "~3.3" + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", - "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" @@ -3450,7 +3776,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3483,45 +3809,114 @@ "uri", "url" ], - "time": "2017-07-21T17:43:13+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { - "name": "symfony/translation", - "version": "v3.3.6", + "name": "symfony/service-contracts", + "version": "v1.1.6", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3", - "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3", + "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-08-20T14:44:19+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "28498169dd334095fa981827992f3a24d50fed0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/28498169dd334095fa981827992f3a24d50fed0f", + "reference": "28498169dd334095fa981827992f3a24d50fed0f", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.6" }, "conflict": { - "symfony/config": "<2.8", - "symfony/yaml": "<3.3" + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "symfony/translation-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/intl": "^2.8.18|^3.2.5", - "symfony/yaml": "~3.3" + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { - "psr/log": "To use logging capability in translator", + "psr/log-implementation": "To use logging capability in translator", "symfony/config": "", "symfony/yaml": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3548,41 +3943,106 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-06-24T16:45:30+00:00" + "time": "2019-08-26T08:55:16+00:00" }, { - "name": "symfony/var-dumper", - "version": "v3.3.6", + "name": "symfony/translation-contracts", + "version": "v1.1.6", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "b2623bccb969ad595c2090f9be498b74670d0663" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2623bccb969ad595c2090f9be498b74670d0663", - "reference": "b2623bccb969ad595c2090f9be498b74670d0663", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", + "reference": "325b17c24f3ee23cbecfa63ba809c6d89b5fa04a", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" - }, - "require-dev": { - "ext-iconv": "*", - "twig/twig": "~1.34|~2.4" + "php": "^7.1.3" }, "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-symfony_debug": "" + "symfony/translation-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-08-02T12:15:04+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/641043e0f3e615990a0f29479f9c117e8a6698c6", + "reference": "641043e0f3e615990a0f29479f9c117e8a6698c6", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" } }, "autoload": { @@ -3616,7 +4076,7 @@ "debug", "dump" ], - "time": "2017-07-28T06:06:09+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3667,20 +4127,21 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.5.2", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "cfd5dc225767ca154853752abc93aeec040fcf36" + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/cfd5dc225767ca154853752abc93aeec040fcf36", - "reference": "cfd5dc225767ca154853752abc93aeec040fcf36", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-ctype": "^1.9" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.0" @@ -3688,7 +4149,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -3713,28 +4174,28 @@ "env", "environment" ], - "time": "2018-10-30T17:29:25+00:00" + "time": "2019-01-29T11:11:52+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.2.1", + "version": "v3.2.8", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c" + "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/9d5caf43c5f3a3aea2178942f281054805872e7c", - "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/18208d64897ab732f6c04a19b319fe8f1d57a9c0", + "reference": "18208d64897ab732f6c04a19b319fe8f1d57a9c0", "shasum": "" }, "require": { - "illuminate/routing": "5.5.x|5.6.x|5.7.x", - "illuminate/session": "5.5.x|5.6.x|5.7.x", - "illuminate/support": "5.5.x|5.6.x|5.7.x", + "illuminate/routing": "^5.5|^6", + "illuminate/session": "^5.5|^6", + "illuminate/support": "^5.5|^6", "maximebf/debugbar": "~1.15.0", "php": ">=7.0", "symfony/debug": "^3|^4", @@ -3783,34 +4244,34 @@ "profiler", "webprofiler" ], - "time": "2018-11-09T08:37:55+00:00" + "time": "2019-08-29T07:01:03+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.5.3", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2" + "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/3d7f1240896a075aa23b13f82dfcbe165dadeef2", - "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", + "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6", - "illuminate/console": "^5.5,<5.8", - "illuminate/filesystem": "^5.5,<5.8", - "illuminate/support": "^5.5,<5.8", + "illuminate/console": "^5.5|^6", + "illuminate/filesystem": "^5.5|^6", + "illuminate/support": "^5.5|^6", "php": ">=7" }, "require-dev": { "doctrine/dbal": "~2.3", - "illuminate/config": "^5.1,<5.8", - "illuminate/view": "^5.1,<5.8", + "illuminate/config": "^5.5|^6", + "illuminate/view": "^5.5|^6", "phpro/grumphp": "^0.14", "phpunit/phpunit": "4.*", "scrutinizer/ocular": "~1.1", @@ -3822,7 +4283,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" }, "laravel": { "providers": [ @@ -3857,7 +4318,7 @@ "phpstorm", "sublime" ], - "time": "2018-12-19T12:12:05+00:00" + "time": "2019-09-03T17:51:13+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -3910,25 +4371,25 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0 || ^4.0" }, @@ -3962,20 +4423,20 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "time": "2019-08-30T08:44:50+00:00" }, { "name": "composer/composer", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "d8aef3af866b28786ce9b8647e52c42496436669" + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/d8aef3af866b28786ce9b8647e52c42496436669", - "reference": "d8aef3af866b28786ce9b8647e52c42496436669", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", "shasum": "" }, "require": { @@ -4011,7 +4472,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -4035,27 +4496,27 @@ "homepage": "http://seld.be" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "homepage": "https://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2018-12-03T09:31:16+00:00" + "time": "2019-08-02T18:55:33+00:00" }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -4104,28 +4565,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -4165,20 +4625,20 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-07-29T10:31:59+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.1", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "dc523135366eb68f22268d069ea7749486458562" + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", - "reference": "dc523135366eb68f22268d069ea7749486458562", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", "shasum": "" }, "require": { @@ -4209,36 +4669,38 @@ "Xdebug", "performance" ], - "time": "2018-11-29T10:59:02+00:00" + "time": "2019-05-27T17:52:04+00:00" }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -4258,25 +4720,25 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "filp/whoops", - "version": "2.3.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7" + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", + "url": "https://api.github.com/repos/filp/whoops/zipball/cde50e6720a39fdacb240159d3eea6865d51fd96", + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96", "shasum": "" }, "require": { @@ -4310,8 +4772,8 @@ "authors": [ { "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "role": "Developer", + "homepage": "https://github.com/filp" } ], "description": "php error handling for cool kids", @@ -4324,7 +4786,7 @@ "throwable", "whoops" ], - "time": "2018-10-23T09:00:00+00:00" + "time": "2019-08-07T09:00:00+00:00" }, { "name": "fzaninotto/faker", @@ -4424,6 +4886,94 @@ ], "time": "2016-01-20T08:20:44+00:00" }, + { + "name": "jakub-onderka/php-console-color", + "version": "v0.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "~4.3", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "time": "2018-09-29T17:23:10+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.4", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "jakub-onderka/php-console-color": "~0.2", + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~1.0", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "time": "2018-09-29T18:48:56+00:00" + }, { "name": "justinrainbow/json-schema", "version": "5.2.8", @@ -4492,28 +5042,30 @@ }, { "name": "laravel/browser-kit-testing", - "version": "v2.0.1", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/laravel/browser-kit-testing.git", - "reference": "f0bb9f200ec35f9d876ded6eacfbc60868d311b9" + "reference": "b042ed965910a4ba69c0ebe8863d4029af3e242e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/f0bb9f200ec35f9d876ded6eacfbc60868d311b9", - "reference": "f0bb9f200ec35f9d876ded6eacfbc60868d311b9", + "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/b042ed965910a4ba69c0ebe8863d4029af3e242e", + "reference": "b042ed965910a4ba69c0ebe8863d4029af3e242e", "shasum": "" }, "require": { - "php": ">=5.5.9", - "phpunit/phpunit": "~6.0", - "symfony/css-selector": "~3.1", - "symfony/dom-crawler": "~3.1" + "illuminate/support": "^5.6", + "mockery/mockery": "^1.0", + "php": ">=7.1.3", + "phpunit/phpunit": "^7.0", + "symfony/css-selector": "~4.0", + "symfony/dom-crawler": "~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -4531,12 +5083,12 @@ "email": "taylor@laravel.com" } ], - "description": "Provides backwards compatibility for BrowserKit testing in Laravel 5.4.", + "description": "Provides backwards compatibility for BrowserKit testing in the latest Laravel release.", "keywords": [ "laravel", "testing" ], - "time": "2017-06-21T11:44:53+00:00" + "time": "2019-02-05T13:27:14+00:00" }, { "name": "maximebf/debugbar", @@ -4601,16 +5153,16 @@ }, { "name": "mockery/mockery", - "version": "1.2.0", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a" + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a", + "url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031", + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031", "shasum": "" }, "require": { @@ -4619,7 +5171,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0" + "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" }, "type": "library", "extra": { @@ -4662,29 +5214,32 @@ "test double", "testing" ], - "time": "2018-10-02T21:52:37+00:00" + "time": "2019-08-07T15:01:07+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -4707,26 +5262,90 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2019-08-09T12:45:53+00:00" }, { - "name": "phar-io/manifest", - "version": "1.0.1", + "name": "nunomaduro/collision", + "version": "v2.1.1", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "url": "https://github.com/nunomaduro/collision.git", + "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5feb0c0d92978ec7169232ce5d70d6da6b29f63", + "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.1.4", + "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", + "php": "^7.1", + "symfony/console": "~2.8|~3.3|~4.0" + }, + "require-dev": { + "laravel/framework": "5.7.*", + "nunomaduro/larastan": "^0.3.0", + "phpstan/phpstan": "^0.10", + "phpunit/phpunit": "~7.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "time": "2018-11-21T21:40:54+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", + "phar-io/version": "^2.0", "php": "^5.6 || ^7.0" }, "type": "library", @@ -4747,35 +5366,35 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" }, { "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpeople.de" }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpunit.de" } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", "shasum": "" }, "require": { @@ -4809,7 +5428,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -4867,16 +5486,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", "shasum": "" }, "require": { @@ -4914,7 +5533,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2019-04-30T17:48:53+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -4965,16 +5584,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -4995,8 +5614,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -5024,44 +5643,44 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "version": "6.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^3.1 || ^4.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^7.0" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -5087,29 +5706,32 @@ "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "time": "2018-10-31T16:06:48+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "050bedf145a257b1ff02746c31894800e5122946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -5124,7 +5746,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -5134,7 +5756,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "time": "2018-09-13T20:33:42+00:00" }, { "name": "phpunit/php-text-template", @@ -5179,28 +5801,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -5215,7 +5837,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -5224,33 +5846,33 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -5273,57 +5895,57 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2019-07-25T05:29:42+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.13", + "version": "7.5.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + "reference": "d79c053d972856b8b941bb233e39dc521a5093f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0", + "reference": "d79c053d972856b8b941bb233e39dc521a5093f0", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.9", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" + "phpunit/phpunit-mock-objects": "*" }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0" }, "bin": [ "phpunit" @@ -5331,7 +5953,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -5357,66 +5979,7 @@ "testing", "xunit" ], - "time": "2018-09-08T15:10:43+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5.11" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-08-09T05:50:03+00:00" + "time": "2019-08-21T07:05:16+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5465,30 +6028,30 @@ }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": "^7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -5525,32 +6088,33 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2018-07-12T15:12:46+00:00" }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -5575,34 +6139,40 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -5627,20 +6197,20 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "06a9a5947f47b3029d76118eb5c22802e5869687" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687", + "reference": "06a9a5947f47b3029d76118eb5c22802e5869687", "shasum": "" }, "require": { @@ -5667,6 +6237,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -5675,17 +6249,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -5694,7 +6264,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-08-11T12:43:14+00:00" }, { "name": "sebastian/global-state", @@ -5894,25 +6464,25 @@ }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5932,7 +6502,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2018-10-04T04:07:39+00:00" }, { "name": "sebastian/version", @@ -6072,16 +6642,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.4.0", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "379deb987e26c7cd103a7b387aea178baec96e48" + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48", - "reference": "379deb987e26c7cd103a7b387aea178baec96e48", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", "shasum": "" }, "require": { @@ -6114,33 +6684,38 @@ } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards" ], - "time": "2018-12-19T23:57:18+00:00" + "time": "2019-04-10T23:49:02+00:00" }, { "name": "symfony/dom-crawler", - "version": "v3.1.10", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7eede2a901a19928494194f7d1815a77b9a473a0" + "reference": "cc686552948d627528c0e2e759186dff67c2610e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7eede2a901a19928494194f7d1815a77b9a473a0", - "reference": "7eede2a901a19928494194f7d1815a77b9a473a0", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e", + "reference": "cc686552948d627528c0e2e759186dff67c2610e", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "masterminds/html5": "<2.6" + }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0" + "masterminds/html5": "^2.6", + "symfony/css-selector": "~3.4|~4.0" }, "suggest": { "symfony/css-selector": "" @@ -6148,7 +6723,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -6175,29 +6750,30 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:13:55+00:00" + "time": "2019-08-26T08:26:39+00:00" }, { "name": "symfony/filesystem", - "version": "v3.3.6", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -6224,20 +6800,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-11T07:17:58+00:00" + "time": "2019-08-20T14:07:54+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -6259,25 +6835,25 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "shasum": "" }, "require": { @@ -6285,8 +6861,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", "extra": { @@ -6315,18 +6890,18 @@ "check", "validate" ], - "time": "2018-12-25T11:19:39+00:00" + "time": "2019-08-24T08:43:50+00:00" } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": { "laravel/socialite": 20 }, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.0.5", + "php": "^7.1.3", "ext-json": "*", "ext-tidy": "*", "ext-dom": "*", @@ -6337,6 +6912,6 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.0.5" + "php": "7.1.3" } } diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 988ea2100..d86cb0ddd 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class DatabaseSeeder extends Seeder { /** - * Run the database seeds. + * Seed the application's database. * * @return void */ diff --git a/phpunit.xml b/phpunit.xml index 53722a71b..06f702bd5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,8 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" - syntaxCheck="false"> + stopOnFailure="false"> ./tests/ @@ -28,6 +27,7 @@ + diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 210980ac2..6f8fcb781 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -12,7 +12,7 @@ return [ 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', 'alpha_num' => 'The :attribute may only contain letters and numbers.', 'array' => 'The :attribute must be an array.', 'before' => 'The :attribute must be a date before :date.', @@ -31,12 +31,39 @@ return [ 'digits_between' => 'The :attribute must be between :min and :max digits.', 'email' => 'The :attribute must be a valid email address.', 'filled' => 'The :attribute field is required.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], 'exists' => 'The selected :attribute is invalid.', 'image' => 'The :attribute must be an image.', 'image_extension' => 'The :attribute must have a valid & supported image extension.', 'in' => 'The selected :attribute is invalid.', 'integer' => 'The :attribute must be an integer.', 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', @@ -52,6 +79,7 @@ return [ ], 'no_double_extension' => 'The :attribute must only have a single file extension.', 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', diff --git a/resources/views/pages/markdown-editor.blade.php b/resources/views/pages/markdown-editor.blade.php index e39f39fc2..526441138 100644 --- a/resources/views/pages/markdown-editor.blade.php +++ b/resources/views/pages/markdown-editor.blade.php @@ -19,7 +19,7 @@
+ @if($errors->has('markdown')) class="text-neg" @endif>@if(isset($model) || old('markdown')){{ old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown) }}@endif
diff --git a/resources/views/pages/wysiwyg-editor.blade.php b/resources/views/pages/wysiwyg-editor.blade.php index f9a0f03cf..1a67ee76f 100644 --- a/resources/views/pages/wysiwyg-editor.blade.php +++ b/resources/views/pages/wysiwyg-editor.blade.php @@ -5,7 +5,7 @@ ]) + @if($errors->has('html')) class="text-neg" @endif>@if(isset($model) || old('html')){{ old('html') ? old('html') : $model->html }}@endif @if($errors->has('html')) From 6917ea088f905ec57e16d8795ad0bcb191c38b52 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 6 Sep 2019 23:36:16 +0100 Subject: [PATCH 009/257] Upgraded app to Laravel 5.7 --- .env.example.complete | 2 +- .gitignore | 3 +- app/Application.php | 7 +- app/Config/app.php | 3 + app/Config/cache.php | 2 +- app/Config/database.php | 8 +- app/Config/hashing.php | 5 +- app/Config/logging.php | 4 +- app/Config/mail.php | 6 + app/Config/queue.php | 2 +- app/Config/services.php | 5 + app/Config/session.php | 7 +- .../Auth/ResetPasswordController.php | 6 +- app/Http/Middleware/VerifyCsrfToken.php | 7 + app/helpers.php | 2 +- bootstrap/app.php | 4 +- composer.json | 44 +- composer.lock | 782 ++++++++++++++++-- phpunit.xml | 2 +- public/svg/403.svg | 1 + public/svg/404.svg | 1 + public/svg/500.svg | 1 + public/svg/503.svg | 1 + public/web.config | 28 + resources/{assets => }/icons/add-circle.svg | 0 resources/{assets => }/icons/add.svg | 0 resources/{assets => }/icons/attach.svg | 0 resources/{assets => }/icons/auth/azure.svg | 0 resources/{assets => }/icons/auth/discord.svg | 0 .../{assets => }/icons/auth/facebook.svg | 0 resources/{assets => }/icons/auth/github.svg | 0 resources/{assets => }/icons/auth/gitlab.svg | 0 resources/{assets => }/icons/auth/google.svg | 0 resources/{assets => }/icons/auth/okta.svg | 0 resources/{assets => }/icons/auth/slack.svg | 0 resources/{assets => }/icons/auth/twitch.svg | 0 resources/{assets => }/icons/auth/twitter.svg | 0 resources/{assets => }/icons/back.svg | 0 resources/{assets => }/icons/book.svg | 0 resources/{assets => }/icons/books.svg | 0 resources/{assets => }/icons/bookshelf.svg | 0 resources/{assets => }/icons/cancel.svg | 0 resources/{assets => }/icons/caret-down.svg | 0 .../{assets => }/icons/caret-left-circle.svg | 0 .../{assets => }/icons/caret-right-circle.svg | 0 resources/{assets => }/icons/caret-right.svg | 0 resources/{assets => }/icons/chapter.svg | 0 resources/{assets => }/icons/check-circle.svg | 0 resources/{assets => }/icons/check.svg | 0 resources/{assets => }/icons/chevron-down.svg | 0 .../{assets => }/icons/chevron-right.svg | 0 resources/{assets => }/icons/chevron-up.svg | 0 resources/{assets => }/icons/close.svg | 0 resources/{assets => }/icons/comment.svg | 0 resources/{assets => }/icons/copy.svg | 0 resources/{assets => }/icons/danger.svg | 0 resources/{assets => }/icons/delete.svg | 0 resources/{assets => }/icons/drawing.svg | 0 resources/{assets => }/icons/edit.svg | 0 resources/{assets => }/icons/expand-text.svg | 0 resources/{assets => }/icons/export.svg | 0 resources/{assets => }/icons/file.svg | 0 resources/{assets => }/icons/folder.svg | 0 resources/{assets => }/icons/grid.svg | 0 resources/{assets => }/icons/grip.svg | 0 resources/{assets => }/icons/history.svg | 0 resources/{assets => }/icons/image.svg | 0 resources/{assets => }/icons/images.svg | 0 resources/{assets => }/icons/include.svg | 0 resources/{assets => }/icons/info-filled.svg | 0 resources/{assets => }/icons/info.svg | 0 resources/{assets => }/icons/link.svg | 0 resources/{assets => }/icons/list.svg | 0 resources/{assets => }/icons/lock-open.svg | 0 resources/{assets => }/icons/lock.svg | 0 resources/{assets => }/icons/login.svg | 0 resources/{assets => }/icons/logout.svg | 0 resources/{assets => }/icons/more.svg | 0 resources/{assets => }/icons/new-user.svg | 0 resources/{assets => }/icons/open-book.svg | 0 resources/{assets => }/icons/page.svg | 0 resources/{assets => }/icons/permission.svg | 0 resources/{assets => }/icons/popular.svg | 0 resources/{assets => }/icons/reply.svg | 0 resources/{assets => }/icons/save.svg | 0 resources/{assets => }/icons/search.svg | 0 resources/{assets => }/icons/settings.svg | 0 resources/{assets => }/icons/sort-down.svg | 0 resources/{assets => }/icons/sort-up.svg | 0 resources/{assets => }/icons/sort.svg | 0 resources/{assets => }/icons/spanner.svg | 0 resources/{assets => }/icons/star-circle.svg | 0 resources/{assets => }/icons/star.svg | 0 .../{assets => }/icons/swap-vertical.svg | 0 resources/{assets => }/icons/tag.svg | 0 resources/{assets => }/icons/template.svg | 0 resources/{assets => }/icons/time.svg | 0 resources/{assets => }/icons/user.svg | 0 resources/{assets => }/icons/users-add.svg | 0 resources/{assets => }/icons/users.svg | 0 resources/{assets => }/icons/view.svg | 0 resources/{assets => }/icons/warning.svg | 0 .../{assets => }/js/components/back-to-top.js | 0 .../{assets => }/js/components/book-sort.js | 0 .../js/components/breadcrumb-listing.js | 0 .../js/components/chapter-toggle.js | 0 .../{assets => }/js/components/collapsible.js | 0 .../js/components/custom-checkbox.js | 0 .../{assets => }/js/components/dropdown.js | 0 .../js/components/editor-toolbox.js | 0 .../components/entity-permissions-editor.js | 0 .../js/components/entity-selector-popup.js | 0 .../js/components/entity-selector.js | 0 .../js/components/expand-toggle.js | 0 .../js/components/header-mobile-toggle.js | 0 .../js/components/homepage-control.js | 0 .../js/components/image-picker.js | 0 resources/{assets => }/js/components/index.js | 0 .../js/components/list-sort-control.js | 0 .../js/components/markdown-editor.js | 0 .../js/components/new-user-password.js | 0 .../js/components/notification.js | 0 .../{assets => }/js/components/overlay.js | 0 .../js/components/page-comments.js | 0 .../js/components/page-display.js | 0 .../{assets => }/js/components/page-picker.js | 0 .../js/components/permissions-table.js | 0 .../js/components/setting-app-color-picker.js | 0 .../{assets => }/js/components/shelf-sort.js | 0 .../{assets => }/js/components/sidebar.js | 0 .../js/components/template-manager.js | 0 .../js/components/toggle-switch.js | 0 .../{assets => }/js/components/tri-layout.js | 0 .../js/components/wysiwyg-editor.js | 0 resources/{assets => }/js/index.js | 0 .../{assets => }/js/services/animations.js | 0 resources/{assets => }/js/services/code.js | 0 resources/{assets => }/js/services/dates.js | 0 resources/{assets => }/js/services/dom.js | 0 resources/{assets => }/js/services/drawio.js | 0 resources/{assets => }/js/services/events.js | 0 resources/{assets => }/js/services/http.js | 0 .../{assets => }/js/services/translations.js | 0 resources/{assets => }/js/services/util.js | 0 .../js/vues/attachment-manager.js | 0 resources/{assets => }/js/vues/code-editor.js | 0 .../js/vues/components/autosuggest.js | 0 .../js/vues/components/dropzone.js | 0 .../{assets => }/js/vues/entity-dashboard.js | 0 .../{assets => }/js/vues/image-manager.js | 0 resources/{assets => }/js/vues/page-editor.js | 0 resources/{assets => }/js/vues/search.js | 0 resources/{assets => }/js/vues/tag-manager.js | 0 resources/{assets => }/js/vues/vues.js | 0 resources/{assets => }/sass/_animations.scss | 0 resources/{assets => }/sass/_blocks.scss | 0 resources/{assets => }/sass/_buttons.scss | 0 resources/{assets => }/sass/_codemirror.scss | 0 resources/{assets => }/sass/_colors.scss | 0 resources/{assets => }/sass/_components.scss | 0 resources/{assets => }/sass/_forms.scss | 0 resources/{assets => }/sass/_header.scss | 0 resources/{assets => }/sass/_html.scss | 0 resources/{assets => }/sass/_layout.scss | 0 resources/{assets => }/sass/_lists.scss | 0 resources/{assets => }/sass/_mixins.scss | 0 resources/{assets => }/sass/_pages.scss | 0 resources/{assets => }/sass/_reset.scss | 0 resources/{assets => }/sass/_spacing.scss | 0 resources/{assets => }/sass/_tables.scss | 0 resources/{assets => }/sass/_text.scss | 0 resources/{assets => }/sass/_tinymce.scss | 0 resources/{assets => }/sass/_variables.scss | 0 .../{assets => }/sass/export-styles.scss | 0 resources/{assets => }/sass/print-styles.scss | 0 resources/{assets => }/sass/styles.scss | 0 storage/framework/cache/.gitignore | 1 + storage/framework/cache/data/.gitignore | 2 + webpack.config.js | 8 +- 179 files changed, 829 insertions(+), 115 deletions(-) create mode 100644 public/svg/403.svg create mode 100644 public/svg/404.svg create mode 100644 public/svg/500.svg create mode 100644 public/svg/503.svg create mode 100644 public/web.config rename resources/{assets => }/icons/add-circle.svg (100%) rename resources/{assets => }/icons/add.svg (100%) rename resources/{assets => }/icons/attach.svg (100%) rename resources/{assets => }/icons/auth/azure.svg (100%) rename resources/{assets => }/icons/auth/discord.svg (100%) rename resources/{assets => }/icons/auth/facebook.svg (100%) rename resources/{assets => }/icons/auth/github.svg (100%) rename resources/{assets => }/icons/auth/gitlab.svg (100%) rename resources/{assets => }/icons/auth/google.svg (100%) rename resources/{assets => }/icons/auth/okta.svg (100%) rename resources/{assets => }/icons/auth/slack.svg (100%) rename resources/{assets => }/icons/auth/twitch.svg (100%) rename resources/{assets => }/icons/auth/twitter.svg (100%) rename resources/{assets => }/icons/back.svg (100%) rename resources/{assets => }/icons/book.svg (100%) rename resources/{assets => }/icons/books.svg (100%) rename resources/{assets => }/icons/bookshelf.svg (100%) rename resources/{assets => }/icons/cancel.svg (100%) rename resources/{assets => }/icons/caret-down.svg (100%) rename resources/{assets => }/icons/caret-left-circle.svg (100%) rename resources/{assets => }/icons/caret-right-circle.svg (100%) rename resources/{assets => }/icons/caret-right.svg (100%) rename resources/{assets => }/icons/chapter.svg (100%) rename resources/{assets => }/icons/check-circle.svg (100%) rename resources/{assets => }/icons/check.svg (100%) rename resources/{assets => }/icons/chevron-down.svg (100%) rename resources/{assets => }/icons/chevron-right.svg (100%) rename resources/{assets => }/icons/chevron-up.svg (100%) rename resources/{assets => }/icons/close.svg (100%) rename resources/{assets => }/icons/comment.svg (100%) rename resources/{assets => }/icons/copy.svg (100%) rename resources/{assets => }/icons/danger.svg (100%) rename resources/{assets => }/icons/delete.svg (100%) rename resources/{assets => }/icons/drawing.svg (100%) rename resources/{assets => }/icons/edit.svg (100%) rename resources/{assets => }/icons/expand-text.svg (100%) rename resources/{assets => }/icons/export.svg (100%) rename resources/{assets => }/icons/file.svg (100%) rename resources/{assets => }/icons/folder.svg (100%) rename resources/{assets => }/icons/grid.svg (100%) rename resources/{assets => }/icons/grip.svg (100%) rename resources/{assets => }/icons/history.svg (100%) rename resources/{assets => }/icons/image.svg (100%) rename resources/{assets => }/icons/images.svg (100%) rename resources/{assets => }/icons/include.svg (100%) rename resources/{assets => }/icons/info-filled.svg (100%) rename resources/{assets => }/icons/info.svg (100%) rename resources/{assets => }/icons/link.svg (100%) rename resources/{assets => }/icons/list.svg (100%) rename resources/{assets => }/icons/lock-open.svg (100%) rename resources/{assets => }/icons/lock.svg (100%) rename resources/{assets => }/icons/login.svg (100%) rename resources/{assets => }/icons/logout.svg (100%) rename resources/{assets => }/icons/more.svg (100%) rename resources/{assets => }/icons/new-user.svg (100%) rename resources/{assets => }/icons/open-book.svg (100%) rename resources/{assets => }/icons/page.svg (100%) rename resources/{assets => }/icons/permission.svg (100%) rename resources/{assets => }/icons/popular.svg (100%) rename resources/{assets => }/icons/reply.svg (100%) rename resources/{assets => }/icons/save.svg (100%) rename resources/{assets => }/icons/search.svg (100%) rename resources/{assets => }/icons/settings.svg (100%) rename resources/{assets => }/icons/sort-down.svg (100%) rename resources/{assets => }/icons/sort-up.svg (100%) rename resources/{assets => }/icons/sort.svg (100%) rename resources/{assets => }/icons/spanner.svg (100%) rename resources/{assets => }/icons/star-circle.svg (100%) rename resources/{assets => }/icons/star.svg (100%) rename resources/{assets => }/icons/swap-vertical.svg (100%) rename resources/{assets => }/icons/tag.svg (100%) rename resources/{assets => }/icons/template.svg (100%) rename resources/{assets => }/icons/time.svg (100%) rename resources/{assets => }/icons/user.svg (100%) rename resources/{assets => }/icons/users-add.svg (100%) rename resources/{assets => }/icons/users.svg (100%) rename resources/{assets => }/icons/view.svg (100%) rename resources/{assets => }/icons/warning.svg (100%) rename resources/{assets => }/js/components/back-to-top.js (100%) rename resources/{assets => }/js/components/book-sort.js (100%) rename resources/{assets => }/js/components/breadcrumb-listing.js (100%) rename resources/{assets => }/js/components/chapter-toggle.js (100%) rename resources/{assets => }/js/components/collapsible.js (100%) rename resources/{assets => }/js/components/custom-checkbox.js (100%) rename resources/{assets => }/js/components/dropdown.js (100%) rename resources/{assets => }/js/components/editor-toolbox.js (100%) rename resources/{assets => }/js/components/entity-permissions-editor.js (100%) rename resources/{assets => }/js/components/entity-selector-popup.js (100%) rename resources/{assets => }/js/components/entity-selector.js (100%) rename resources/{assets => }/js/components/expand-toggle.js (100%) rename resources/{assets => }/js/components/header-mobile-toggle.js (100%) rename resources/{assets => }/js/components/homepage-control.js (100%) rename resources/{assets => }/js/components/image-picker.js (100%) rename resources/{assets => }/js/components/index.js (100%) rename resources/{assets => }/js/components/list-sort-control.js (100%) rename resources/{assets => }/js/components/markdown-editor.js (100%) rename resources/{assets => }/js/components/new-user-password.js (100%) rename resources/{assets => }/js/components/notification.js (100%) rename resources/{assets => }/js/components/overlay.js (100%) rename resources/{assets => }/js/components/page-comments.js (100%) rename resources/{assets => }/js/components/page-display.js (100%) rename resources/{assets => }/js/components/page-picker.js (100%) rename resources/{assets => }/js/components/permissions-table.js (100%) rename resources/{assets => }/js/components/setting-app-color-picker.js (100%) rename resources/{assets => }/js/components/shelf-sort.js (100%) rename resources/{assets => }/js/components/sidebar.js (100%) rename resources/{assets => }/js/components/template-manager.js (100%) rename resources/{assets => }/js/components/toggle-switch.js (100%) rename resources/{assets => }/js/components/tri-layout.js (100%) rename resources/{assets => }/js/components/wysiwyg-editor.js (100%) rename resources/{assets => }/js/index.js (100%) rename resources/{assets => }/js/services/animations.js (100%) rename resources/{assets => }/js/services/code.js (100%) rename resources/{assets => }/js/services/dates.js (100%) rename resources/{assets => }/js/services/dom.js (100%) rename resources/{assets => }/js/services/drawio.js (100%) rename resources/{assets => }/js/services/events.js (100%) rename resources/{assets => }/js/services/http.js (100%) rename resources/{assets => }/js/services/translations.js (100%) rename resources/{assets => }/js/services/util.js (100%) rename resources/{assets => }/js/vues/attachment-manager.js (100%) rename resources/{assets => }/js/vues/code-editor.js (100%) rename resources/{assets => }/js/vues/components/autosuggest.js (100%) rename resources/{assets => }/js/vues/components/dropzone.js (100%) rename resources/{assets => }/js/vues/entity-dashboard.js (100%) rename resources/{assets => }/js/vues/image-manager.js (100%) rename resources/{assets => }/js/vues/page-editor.js (100%) rename resources/{assets => }/js/vues/search.js (100%) rename resources/{assets => }/js/vues/tag-manager.js (100%) rename resources/{assets => }/js/vues/vues.js (100%) rename resources/{assets => }/sass/_animations.scss (100%) rename resources/{assets => }/sass/_blocks.scss (100%) rename resources/{assets => }/sass/_buttons.scss (100%) rename resources/{assets => }/sass/_codemirror.scss (100%) rename resources/{assets => }/sass/_colors.scss (100%) rename resources/{assets => }/sass/_components.scss (100%) rename resources/{assets => }/sass/_forms.scss (100%) rename resources/{assets => }/sass/_header.scss (100%) rename resources/{assets => }/sass/_html.scss (100%) rename resources/{assets => }/sass/_layout.scss (100%) rename resources/{assets => }/sass/_lists.scss (100%) rename resources/{assets => }/sass/_mixins.scss (100%) rename resources/{assets => }/sass/_pages.scss (100%) rename resources/{assets => }/sass/_reset.scss (100%) rename resources/{assets => }/sass/_spacing.scss (100%) rename resources/{assets => }/sass/_tables.scss (100%) rename resources/{assets => }/sass/_text.scss (100%) rename resources/{assets => }/sass/_tinymce.scss (100%) rename resources/{assets => }/sass/_variables.scss (100%) rename resources/{assets => }/sass/export-styles.scss (100%) rename resources/{assets => }/sass/print-styles.scss (100%) rename resources/{assets => }/sass/styles.scss (100%) create mode 100755 storage/framework/cache/data/.gitignore diff --git a/.env.example.complete b/.env.example.complete index 829a7509b..c4c3f0b85 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -89,7 +89,7 @@ REDIS_SERVERS=127.0.0.1:6379:0 # Queue driver to use # Queue not really currently used but may be configurable in the future. # Would advise not to change this for now. -QUEUE_DRIVER=sync +QUEUE_CONNECTION=sync # Storage system to use # Can be 'local', 'local_secure' or 's3' diff --git a/.gitignore b/.gitignore index 1b53cbe7a..e5579e4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ nbproject .buildpath .project .settings/ -webpack-stats.json \ No newline at end of file +webpack-stats.json +.phpunit.result.cache \ No newline at end of file diff --git a/app/Application.php b/app/Application.php index 8c56e9dac..97105e479 100644 --- a/app/Application.php +++ b/app/Application.php @@ -13,7 +13,12 @@ class Application extends \Illuminate\Foundation\Application */ public function configPath($path = '') { - return $this->basePath.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'Config'.($path ? DIRECTORY_SEPARATOR.$path : $path); + return $this->basePath + . DIRECTORY_SEPARATOR + . 'app' + . DIRECTORY_SEPARATOR + . 'Config' + . ($path ? DIRECTORY_SEPARATOR.$path : $path); } } \ No newline at end of file diff --git a/app/Config/app.php b/app/Config/app.php index ec78e828b..d4ef8fe25 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -57,6 +57,9 @@ return [ // Application Fallback Locale 'fallback_locale' => 'en', + // Faker Locale + 'faker_locale' => 'en_GB', + // Enable right-to-left text control. 'rtl' => false, diff --git a/app/Config/cache.php b/app/Config/cache.php index 43f420457..6d8fa7ad7 100644 --- a/app/Config/cache.php +++ b/app/Config/cache.php @@ -62,6 +62,6 @@ return [ // Cache key prefix // Used to prevent collisions in shared cache systems. - 'prefix' => env('CACHE_PREFIX', 'bookstack'), + 'prefix' => env('CACHE_PREFIX', 'bookstack_cache'), ]; diff --git a/app/Config/database.php b/app/Config/database.php index 93a44854f..82156bd9d 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -14,7 +14,7 @@ if (env('REDIS_SERVERS', false)) { $redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null]; $redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ',')); - $redisConfig = []; + $redisConfig = ['client' => 'predis']; $cluster = count($redisServers) > 1; if ($cluster) { @@ -76,6 +76,7 @@ return [ 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', + 'prefix_indexes' => true, 'strict' => false, 'engine' => null, ], @@ -86,9 +87,10 @@ return [ 'database' => 'bookstack-test', 'username' => env('MYSQL_USER', 'bookstack-test'), 'password' => env('MYSQL_PASSWORD', 'bookstack-test'), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', + 'prefix_indexes' => true, 'strict' => false, ], diff --git a/app/Config/hashing.php b/app/Config/hashing.php index ca73c5586..edcc7c1da 100644 --- a/app/Config/hashing.php +++ b/app/Config/hashing.php @@ -12,9 +12,8 @@ return [ // Default Hash Driver // This option controls the default hash driver that will be used to hash - // passwords for your application. By default, the bcrypt algorithm is - // used; however, you remain free to modify this option if you wish. - // Supported: "bcrypt", "argon" + // passwords for your application. By default, the bcrypt algorithm is used. + // Supported: "bcrypt", "argon", "argon2id" 'driver' => 'bcrypt', // Bcrypt Options diff --git a/app/Config/logging.php b/app/Config/logging.php index 880b35453..c952ac02f 100644 --- a/app/Config/logging.php +++ b/app/Config/logging.php @@ -28,13 +28,15 @@ return [ 'channels' => [ 'stack' => [ 'driver' => 'stack', - 'channels' => ['single'], + 'channels' => ['daily'], + 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', + 'days' => 14, ], 'daily' => [ diff --git a/app/Config/mail.php b/app/Config/mail.php index 49407bd8e..dfb41e7e6 100644 --- a/app/Config/mail.php +++ b/app/Config/mail.php @@ -46,4 +46,10 @@ return [ ], ], + // Log Channel + // If you are using the "log" driver, you may specify the logging channel + // if you prefer to keep mail messages separate from other log entries + // for simpler reading. Otherwise, the default channel will be used. + 'log_channel' => env('MAIL_LOG_CHANNEL'), + ]; diff --git a/app/Config/queue.php b/app/Config/queue.php index 721eac136..f0dd24fd3 100644 --- a/app/Config/queue.php +++ b/app/Config/queue.php @@ -12,7 +12,7 @@ return [ // Default driver to use for the queue // Options: null, sync, redis - 'default' => env('QUEUE_DRIVER', 'sync'), + 'default' => env('QUEUE_CONNECTION', 'sync'), // Queue connection configuration 'connections' => [ diff --git a/app/Config/services.php b/app/Config/services.php index 97cb71ddc..569c0fb48 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -25,6 +25,7 @@ return [ 'mailgun' => [ 'domain' => '', 'secret' => '', + 'endpoint' => '', ], 'ses' => [ @@ -37,6 +38,10 @@ return [ 'model' => \BookStack\Auth\User::class, 'key' => '', 'secret' => '', + 'webhook' => [ + 'secret' => '', + 'tolerance' => 300, + ], ], 'github' => [ diff --git a/app/Config/session.php b/app/Config/session.php index bdb5e554b..37f1627bb 100644 --- a/app/Config/session.php +++ b/app/Config/session.php @@ -35,13 +35,18 @@ return [ // Session database table, if database driver is in use 'table' => 'sessions', + // Session Cache Store + // When using the "apc" or "memcached" session drivers, you may specify a + // cache store that should be used for these sessions. This value must + // correspond with one of the application's configured cache stores. + 'store' => null, + // Session Sweeping Lottery // Some session drivers must manually sweep their storage location to get // rid of old sessions from storage. Here are the chances that it will // happen on a given request. By default, the odds are 2 out of 100. 'lottery' => [2, 100], - // Session Cookie Name // Here you may change the name of the cookie used to identify a session // instance by ID. The name specified here will get used every time a diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 56f1cf026..d7005b739 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -4,6 +4,7 @@ namespace BookStack\Http\Controllers\Auth; use BookStack\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Http\Request; class ResetPasswordController extends Controller { @@ -36,10 +37,11 @@ class ResetPasswordController extends Controller /** * Get the response for a successful password reset. * - * @param string $response + * @param Request $request + * @param string $response * @return \Illuminate\Http\Response */ - protected function sendResetResponse($response) + protected function sendResetResponse(Request $request, $response) { $message = trans('auth.reset_password_success'); session()->flash('success', $message); diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 291b8326f..1a29a2b1d 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -6,6 +6,13 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { + /** + * Indicates whether the XSRF-TOKEN cookie should be set on the response. + * + * @var bool + */ + protected $addHttpCookie = true; + /** * The URIs that should be excluded from CSRF verification. * diff --git a/app/helpers.php b/app/helpers.php index f36f2e59d..59b9104f8 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -142,7 +142,7 @@ function icon($name, $attrs = []) $attrString .= $attrName . '="' . $attr . '" '; } - $iconPath = resource_path('assets/icons/' . $name . '.svg'); + $iconPath = resource_path('icons/' . $name . '.svg'); $themeIconPath = theme_path('icons/' . $name . '.svg'); if ($themeIconPath && file_exists($themeIconPath)) { $iconPath = $themeIconPath; diff --git a/bootstrap/app.php b/bootstrap/app.php index 516980cc1..6538aa81c 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,8 +11,8 @@ | */ -$app = new \BookStack\Application( - realpath(__DIR__.'/../') +$app = new BookStack\Application( + dirname(__DIR__) ); /* diff --git a/composer.json b/composer.json index 49ec185cb..4741d4d7e 100644 --- a/composer.json +++ b/composer.json @@ -13,24 +13,25 @@ "ext-mbstring": "*", "ext-gd": "*", "ext-curl": "*", - "laravel/framework": "5.6.*", + "laravel/framework": "5.7.*", "fideloper/proxy": "^4.0", - "intervention/image": "^2.4", - "laravel/socialite": "3.0.x-dev", + "intervention/image": "^2.5", + "laravel/socialite": "^4.2", "league/flysystem-aws-s3-v3": "^1.0", - "barryvdh/laravel-dompdf": "^0.8.1", + "barryvdh/laravel-dompdf": "^0.8.5", + "barryvdh/laravel-snappy": "^0.4.5", "predis/predis": "^1.1", "gathercontent/htmldiff": "^0.2.1", - "barryvdh/laravel-snappy": "^0.4.0", "socialiteproviders/slack": "^3.0", "socialiteproviders/microsoft-azure": "^3.0", "socialiteproviders/okta": "^1.0", "socialiteproviders/gitlab": "^3.0", "socialiteproviders/twitch": "^3.0", "socialiteproviders/discord": "^2.0", - "doctrine/dbal": "^2.5" + "doctrine/dbal": "^2.9" }, "require-dev": { + "beyondcode/laravel-dump-server": "^1.0", "filp/whoops": "^2.0", "fzaninotto/faker": "^1.4", "mockery/mockery": "^1.0", @@ -43,7 +44,8 @@ }, "autoload": { "classmap": [ - "database" + "database/seeds", + "database/factories" ], "psr-4": { "BookStack\\": "app/" @@ -56,39 +58,45 @@ }, "scripts": { "post-root-package-install": [ - "php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ - "php artisan key:generate" + "@php artisan key:generate --ansi" ], "pre-update-cmd": [ - "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", - "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" + "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", + "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" ], "pre-install-cmd": [ - "php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", - "php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" + "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", + "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" ], "post-install-cmd": [ - "php artisan cache:clear", - "php artisan view:clear" + "@php artisan cache:clear", + "@php artisan view:clear" ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", - "@php artisan package:discover" + "@php artisan package:discover --ansi" ], "refresh-test-database": [ - "php artisan migrate:refresh --database=mysql_testing", - "php artisan db:seed --class=DummyContentSeeder --database=mysql_testing" + "@php artisan migrate:refresh --database=mysql_testing", + "@php artisan db:seed --class=DummyContentSeeder --database=mysql_testing" ] }, "config": { "optimize-autoloader": true, "preferred-install": "dist", + "sort-packages": true, "platform": { "php": "7.1.3" } }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/composer.lock b/composer.lock index fe9bc4578..b3838ecf8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "ce7b23f15edb5a2dd6bdaf23ce1c8b5d", + "content-hash": "a007281b1a87cb6fc78975c49f20b3e8", "packages": [ { "name": "aws/aws-sdk-php", @@ -1312,42 +1312,45 @@ }, { "name": "laravel/framework", - "version": "v5.6.39", + "version": "v5.7.28", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "37bb306f516669ab4f888c16003f694313ab299e" + "reference": "8e69728f1c80a024588adbd24c65c4fcf9aa9192" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/37bb306f516669ab4f888c16003f694313ab299e", - "reference": "37bb306f516669ab4f888c16003f694313ab299e", + "url": "https://api.github.com/repos/laravel/framework/zipball/8e69728f1c80a024588adbd24c65c4fcf9aa9192", + "reference": "8e69728f1c80a024588adbd24c65c4fcf9aa9192", "shasum": "" }, "require": { - "doctrine/inflector": "~1.1", - "dragonmantank/cron-expression": "~2.0", - "erusev/parsedown": "~1.7", + "doctrine/inflector": "^1.1", + "dragonmantank/cron-expression": "^2.0", + "erusev/parsedown": "^1.7", "ext-mbstring": "*", "ext-openssl": "*", + "laravel/nexmo-notification-channel": "^1.0", + "laravel/slack-notification-channel": "^1.0", "league/flysystem": "^1.0.8", - "monolog/monolog": "~1.12", - "nesbot/carbon": "1.25.*", + "monolog/monolog": "^1.12", + "nesbot/carbon": "^1.26.3", + "opis/closure": "^3.1", "php": "^7.1.3", - "psr/container": "~1.0", + "psr/container": "^1.0", "psr/simple-cache": "^1.0", "ramsey/uuid": "^3.7", - "swiftmailer/swiftmailer": "~6.0", - "symfony/console": "~4.0", - "symfony/debug": "~4.0", - "symfony/finder": "~4.0", - "symfony/http-foundation": "~4.0", - "symfony/http-kernel": "~4.0", - "symfony/process": "~4.0", - "symfony/routing": "~4.0", - "symfony/var-dumper": "~4.0", + "swiftmailer/swiftmailer": "^6.0", + "symfony/console": "^4.1", + "symfony/debug": "^4.1", + "symfony/finder": "^4.1", + "symfony/http-foundation": "^4.1", + "symfony/http-kernel": "^4.1", + "symfony/process": "^4.1", + "symfony/routing": "^4.1", + "symfony/var-dumper": "^4.1", "tijsverkoyen/css-to-inline-styles": "^2.2.1", - "vlucas/phpdotenv": "~2.2" + "vlucas/phpdotenv": "^2.2" }, "conflict": { "tightenco/collect": "<5.5.33" @@ -1383,43 +1386,47 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "~3.0", - "doctrine/dbal": "~2.6", + "aws/aws-sdk-php": "^3.0", + "doctrine/dbal": "^2.6", "filp/whoops": "^2.1.4", - "league/flysystem-cached-adapter": "~1.0", - "mockery/mockery": "~1.0", + "guzzlehttp/guzzle": "^6.3", + "league/flysystem-cached-adapter": "^1.0", + "mockery/mockery": "^1.0", "moontoast/math": "^1.1", - "orchestra/testbench-core": "3.6.*", - "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~7.0", + "orchestra/testbench-core": "3.7.*", + "pda/pheanstalk": "^3.0|^4.0", + "phpunit/phpunit": "^7.5", "predis/predis": "^1.1.1", - "symfony/css-selector": "~4.0", - "symfony/dom-crawler": "~4.0" + "symfony/css-selector": "^4.1", + "symfony/dom-crawler": "^4.1", + "true/punycode": "^2.1" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.6).", + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (^3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", - "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", - "laravel/tinker": "Required to use the tinker console command (~1.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", - "league/flysystem-cached-adapter": "Required to use the Flysystem cache (~1.0).", - "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (~1.0).", - "nexmo/client": "Required to use the Nexmo transport (~1.0).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", - "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (~4.0).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~4.0).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)." + "filp/whoops": "Required for friendly error pages in development (^2.1.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (^6.0).", + "laravel/tinker": "Required to use the tinker console command (^1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "nexmo/client": "Required to use the Nexmo transport (^1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^3.0|^4.0).", + "predis/predis": "Required to use the redis cache and queue drivers (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (^4.1).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (^4.1).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (^1.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.6-dev" + "dev-master": "5.7-dev" } }, "autoload": { @@ -1447,38 +1454,153 @@ "framework", "laravel" ], - "time": "2018-10-04T14:50:41+00:00" + "time": "2019-02-26T15:41:34+00:00" }, { - "name": "laravel/socialite", - "version": "3.0.x-dev", + "name": "laravel/nexmo-notification-channel", + "version": "v1.0.1", "source": { "type": "git", - "url": "https://github.com/laravel/socialite.git", - "reference": "79316f36641f1916a50ab14d368acdf1d97e46de" + "url": "https://github.com/laravel/nexmo-notification-channel.git", + "reference": "03edd42a55b306ff980c9950899d5a2b03260d48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/79316f36641f1916a50ab14d368acdf1d97e46de", - "reference": "79316f36641f1916a50ab14d368acdf1d97e46de", + "url": "https://api.github.com/repos/laravel/nexmo-notification-channel/zipball/03edd42a55b306ff980c9950899d5a2b03260d48", + "reference": "03edd42a55b306ff980c9950899d5a2b03260d48", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "~6.0", - "illuminate/contracts": "~5.4", - "illuminate/http": "~5.4", - "illuminate/support": "~5.4", - "league/oauth1-client": "~1.0", - "php": ">=5.6.4" + "nexmo/client": "^1.0", + "php": "^7.1.3" }, "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0|~5.0" + "illuminate/notifications": "~5.7", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Illuminate\\Notifications\\NexmoChannelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Notifications\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Nexmo Notification Channel for laravel.", + "keywords": [ + "laravel", + "nexmo", + "notifications" + ], + "time": "2018-12-04T12:57:08+00:00" + }, + { + "name": "laravel/slack-notification-channel", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/slack-notification-channel.git", + "reference": "6e164293b754a95f246faf50ab2bbea3e4923cc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/6e164293b754a95f246faf50ab2bbea3e4923cc9", + "reference": "6e164293b754a95f246faf50ab2bbea3e4923cc9", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": "^7.1.3" + }, + "require-dev": { + "illuminate/notifications": "~5.7", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Illuminate\\Notifications\\SlackChannelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Notifications\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Slack Notification Channel for laravel.", + "keywords": [ + "laravel", + "notifications", + "slack" + ], + "time": "2018-12-12T13:12:06+00:00" + }, + { + "name": "laravel/socialite", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "f509d06e1e7323997b804c5152874f8aad4508e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/f509d06e1e7323997b804c5152874f8aad4508e9", + "reference": "f509d06e1e7323997b804c5152874f8aad4508e9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "~6.0", + "illuminate/http": "~5.7.0|~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0", + "league/oauth1-client": "~1.0", + "php": "^7.1.3" + }, + "require-dev": { + "illuminate/contracts": "~5.7.0|~5.8.0|^6.0|^7.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" }, "laravel": { "providers": [ @@ -1510,7 +1632,62 @@ "laravel", "oauth" ], - "time": "2018-12-21T14:06:32+00:00" + "time": "2019-09-03T15:27:17+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-openssl": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "mikey179/vfsstream": "~1.5", + "phpmd/phpmd": "~2.2", + "phpunit/php-invoker": "~1.1", + "phpunit/phpunit": "^5.7 || ^7.3", + "squizlabs/php_codesniffer": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Otávio Cobucci Oblonczyk", + "role": "Developer", + "email": "lcobucci@gmail.com" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "time": "2019-05-24T18:30:49+00:00" }, { "name": "league/flysystem", @@ -1841,16 +2018,16 @@ }, { "name": "nesbot/carbon", - "version": "1.25.3", + "version": "1.39.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8" + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8", - "reference": "ad6afecd38ce2d7f7bd1b5d47ffd8e93ebbd3ed8", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", "shasum": "" }, "require": { @@ -1868,11 +2045,16 @@ ], "type": "library", "extra": { - "update-helper": "Carbon\\Upgrade" + "update-helper": "Carbon\\Upgrade", + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } }, "autoload": { "psr-4": { - "Carbon\\": "src/Carbon/" + "": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1893,7 +2075,116 @@ "datetime", "time" ], - "time": "2019-06-03T17:56:44+00:00" + "time": "2019-06-11T09:07:59+00:00" + }, + { + "name": "nexmo/client", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/Nexmo/nexmo-php.git", + "reference": "182d41a02ebd3e4be147baea45458ccfe2f528c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nexmo/nexmo-php/zipball/182d41a02ebd3e4be147baea45458ccfe2f528c4", + "reference": "182d41a02ebd3e4be147baea45458ccfe2f528c4", + "shasum": "" + }, + "require": { + "lcobucci/jwt": "^3.2", + "php": ">=5.6", + "php-http/client-implementation": "^1.0", + "php-http/guzzle6-adapter": "^1.0", + "zendframework/zend-diactoros": "^1.8.4 || ^2.0" + }, + "require-dev": { + "estahn/phpunit-json-assertions": "^1.0.0", + "php-http/mock-client": "^0.3.0", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nexmo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tim Lytle", + "email": "tim@nexmo.com", + "homepage": "http://twitter.com/tjlytle", + "role": "Developer" + } + ], + "description": "PHP Client for using Nexmo's API.", + "time": "2019-05-13T20:27:43+00:00" + }, + { + "name": "opis/closure", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/60a97fff133b1669a5b1776aa8ab06db3f3962b7", + "reference": "60a97fff133b1669a5b1776aa8ab06db3f3962b7", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "time": "2019-09-02T21:07:33+00:00" }, { "name": "paragonie/random_compat", @@ -2017,6 +2308,172 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "time": "2018-06-03T10:10:03+00:00" }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "time": "2016-05-10T06:13:32+00:00" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2016-08-31T08:30:17+00:00" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-26T13:27:02+00:00" + }, { "name": "predis/predis", "version": "v1.1.1", @@ -2116,6 +2573,58 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -4175,6 +4684,72 @@ "environment" ], "time": "2019-01-29T11:11:52+00:00" + }, + { + "name": "zendframework/zend-diactoros", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-diactoros.git", + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1", + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.5.0", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev", + "dev-develop": "2.2.x-dev", + "dev-release-1.8": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Zend\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "keywords": [ + "http", + "psr", + "psr-7" + ], + "time": "2019-07-10T16:13:25+00:00" } ], "packages-dev": [ @@ -4369,6 +4944,67 @@ ], "time": "2018-12-13T10:34:14+00:00" }, + { + "name": "beyondcode/laravel-dump-server", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/beyondcode/laravel-dump-server.git", + "reference": "fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a", + "reference": "fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a", + "shasum": "" + }, + "require": { + "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0", + "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0", + "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0", + "php": "^7.1", + "symfony/var-dumper": "^4.1.1" + }, + "require-dev": { + "larapack/dd": "^1.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "BeyondCode\\DumpServer\\DumpServerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "BeyondCode\\DumpServer\\": "src" + }, + "files": [ + "helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marcel Pociot", + "email": "marcel@beyondco.de", + "homepage": "https://beyondco.de", + "role": "Developer" + } + ], + "description": "Symfony Var-Dump Server for Laravel", + "homepage": "https://github.com/beyondcode/laravel-dump-server", + "keywords": [ + "beyondcode", + "laravel-dump-server" + ], + "time": "2019-08-11T13:17:40+00:00" + }, { "name": "composer/ca-bundle", "version": "1.2.4", @@ -6895,9 +7531,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "laravel/socialite": 20 - }, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/phpunit.xml b/phpunit.xml index 06f702bd5..1f88f897b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,7 +25,7 @@ - + diff --git a/public/svg/403.svg b/public/svg/403.svg new file mode 100644 index 000000000..682aa9827 --- /dev/null +++ b/public/svg/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svg/404.svg b/public/svg/404.svg new file mode 100644 index 000000000..b6cd6f237 --- /dev/null +++ b/public/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svg/500.svg b/public/svg/500.svg new file mode 100644 index 000000000..9927e8d75 --- /dev/null +++ b/public/svg/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svg/503.svg b/public/svg/503.svg new file mode 100644 index 000000000..6ad109336 --- /dev/null +++ b/public/svg/503.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/web.config b/public/web.config new file mode 100644 index 000000000..474eb6898 --- /dev/null +++ b/public/web.config @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/assets/icons/add-circle.svg b/resources/icons/add-circle.svg similarity index 100% rename from resources/assets/icons/add-circle.svg rename to resources/icons/add-circle.svg diff --git a/resources/assets/icons/add.svg b/resources/icons/add.svg similarity index 100% rename from resources/assets/icons/add.svg rename to resources/icons/add.svg diff --git a/resources/assets/icons/attach.svg b/resources/icons/attach.svg similarity index 100% rename from resources/assets/icons/attach.svg rename to resources/icons/attach.svg diff --git a/resources/assets/icons/auth/azure.svg b/resources/icons/auth/azure.svg similarity index 100% rename from resources/assets/icons/auth/azure.svg rename to resources/icons/auth/azure.svg diff --git a/resources/assets/icons/auth/discord.svg b/resources/icons/auth/discord.svg similarity index 100% rename from resources/assets/icons/auth/discord.svg rename to resources/icons/auth/discord.svg diff --git a/resources/assets/icons/auth/facebook.svg b/resources/icons/auth/facebook.svg similarity index 100% rename from resources/assets/icons/auth/facebook.svg rename to resources/icons/auth/facebook.svg diff --git a/resources/assets/icons/auth/github.svg b/resources/icons/auth/github.svg similarity index 100% rename from resources/assets/icons/auth/github.svg rename to resources/icons/auth/github.svg diff --git a/resources/assets/icons/auth/gitlab.svg b/resources/icons/auth/gitlab.svg similarity index 100% rename from resources/assets/icons/auth/gitlab.svg rename to resources/icons/auth/gitlab.svg diff --git a/resources/assets/icons/auth/google.svg b/resources/icons/auth/google.svg similarity index 100% rename from resources/assets/icons/auth/google.svg rename to resources/icons/auth/google.svg diff --git a/resources/assets/icons/auth/okta.svg b/resources/icons/auth/okta.svg similarity index 100% rename from resources/assets/icons/auth/okta.svg rename to resources/icons/auth/okta.svg diff --git a/resources/assets/icons/auth/slack.svg b/resources/icons/auth/slack.svg similarity index 100% rename from resources/assets/icons/auth/slack.svg rename to resources/icons/auth/slack.svg diff --git a/resources/assets/icons/auth/twitch.svg b/resources/icons/auth/twitch.svg similarity index 100% rename from resources/assets/icons/auth/twitch.svg rename to resources/icons/auth/twitch.svg diff --git a/resources/assets/icons/auth/twitter.svg b/resources/icons/auth/twitter.svg similarity index 100% rename from resources/assets/icons/auth/twitter.svg rename to resources/icons/auth/twitter.svg diff --git a/resources/assets/icons/back.svg b/resources/icons/back.svg similarity index 100% rename from resources/assets/icons/back.svg rename to resources/icons/back.svg diff --git a/resources/assets/icons/book.svg b/resources/icons/book.svg similarity index 100% rename from resources/assets/icons/book.svg rename to resources/icons/book.svg diff --git a/resources/assets/icons/books.svg b/resources/icons/books.svg similarity index 100% rename from resources/assets/icons/books.svg rename to resources/icons/books.svg diff --git a/resources/assets/icons/bookshelf.svg b/resources/icons/bookshelf.svg similarity index 100% rename from resources/assets/icons/bookshelf.svg rename to resources/icons/bookshelf.svg diff --git a/resources/assets/icons/cancel.svg b/resources/icons/cancel.svg similarity index 100% rename from resources/assets/icons/cancel.svg rename to resources/icons/cancel.svg diff --git a/resources/assets/icons/caret-down.svg b/resources/icons/caret-down.svg similarity index 100% rename from resources/assets/icons/caret-down.svg rename to resources/icons/caret-down.svg diff --git a/resources/assets/icons/caret-left-circle.svg b/resources/icons/caret-left-circle.svg similarity index 100% rename from resources/assets/icons/caret-left-circle.svg rename to resources/icons/caret-left-circle.svg diff --git a/resources/assets/icons/caret-right-circle.svg b/resources/icons/caret-right-circle.svg similarity index 100% rename from resources/assets/icons/caret-right-circle.svg rename to resources/icons/caret-right-circle.svg diff --git a/resources/assets/icons/caret-right.svg b/resources/icons/caret-right.svg similarity index 100% rename from resources/assets/icons/caret-right.svg rename to resources/icons/caret-right.svg diff --git a/resources/assets/icons/chapter.svg b/resources/icons/chapter.svg similarity index 100% rename from resources/assets/icons/chapter.svg rename to resources/icons/chapter.svg diff --git a/resources/assets/icons/check-circle.svg b/resources/icons/check-circle.svg similarity index 100% rename from resources/assets/icons/check-circle.svg rename to resources/icons/check-circle.svg diff --git a/resources/assets/icons/check.svg b/resources/icons/check.svg similarity index 100% rename from resources/assets/icons/check.svg rename to resources/icons/check.svg diff --git a/resources/assets/icons/chevron-down.svg b/resources/icons/chevron-down.svg similarity index 100% rename from resources/assets/icons/chevron-down.svg rename to resources/icons/chevron-down.svg diff --git a/resources/assets/icons/chevron-right.svg b/resources/icons/chevron-right.svg similarity index 100% rename from resources/assets/icons/chevron-right.svg rename to resources/icons/chevron-right.svg diff --git a/resources/assets/icons/chevron-up.svg b/resources/icons/chevron-up.svg similarity index 100% rename from resources/assets/icons/chevron-up.svg rename to resources/icons/chevron-up.svg diff --git a/resources/assets/icons/close.svg b/resources/icons/close.svg similarity index 100% rename from resources/assets/icons/close.svg rename to resources/icons/close.svg diff --git a/resources/assets/icons/comment.svg b/resources/icons/comment.svg similarity index 100% rename from resources/assets/icons/comment.svg rename to resources/icons/comment.svg diff --git a/resources/assets/icons/copy.svg b/resources/icons/copy.svg similarity index 100% rename from resources/assets/icons/copy.svg rename to resources/icons/copy.svg diff --git a/resources/assets/icons/danger.svg b/resources/icons/danger.svg similarity index 100% rename from resources/assets/icons/danger.svg rename to resources/icons/danger.svg diff --git a/resources/assets/icons/delete.svg b/resources/icons/delete.svg similarity index 100% rename from resources/assets/icons/delete.svg rename to resources/icons/delete.svg diff --git a/resources/assets/icons/drawing.svg b/resources/icons/drawing.svg similarity index 100% rename from resources/assets/icons/drawing.svg rename to resources/icons/drawing.svg diff --git a/resources/assets/icons/edit.svg b/resources/icons/edit.svg similarity index 100% rename from resources/assets/icons/edit.svg rename to resources/icons/edit.svg diff --git a/resources/assets/icons/expand-text.svg b/resources/icons/expand-text.svg similarity index 100% rename from resources/assets/icons/expand-text.svg rename to resources/icons/expand-text.svg diff --git a/resources/assets/icons/export.svg b/resources/icons/export.svg similarity index 100% rename from resources/assets/icons/export.svg rename to resources/icons/export.svg diff --git a/resources/assets/icons/file.svg b/resources/icons/file.svg similarity index 100% rename from resources/assets/icons/file.svg rename to resources/icons/file.svg diff --git a/resources/assets/icons/folder.svg b/resources/icons/folder.svg similarity index 100% rename from resources/assets/icons/folder.svg rename to resources/icons/folder.svg diff --git a/resources/assets/icons/grid.svg b/resources/icons/grid.svg similarity index 100% rename from resources/assets/icons/grid.svg rename to resources/icons/grid.svg diff --git a/resources/assets/icons/grip.svg b/resources/icons/grip.svg similarity index 100% rename from resources/assets/icons/grip.svg rename to resources/icons/grip.svg diff --git a/resources/assets/icons/history.svg b/resources/icons/history.svg similarity index 100% rename from resources/assets/icons/history.svg rename to resources/icons/history.svg diff --git a/resources/assets/icons/image.svg b/resources/icons/image.svg similarity index 100% rename from resources/assets/icons/image.svg rename to resources/icons/image.svg diff --git a/resources/assets/icons/images.svg b/resources/icons/images.svg similarity index 100% rename from resources/assets/icons/images.svg rename to resources/icons/images.svg diff --git a/resources/assets/icons/include.svg b/resources/icons/include.svg similarity index 100% rename from resources/assets/icons/include.svg rename to resources/icons/include.svg diff --git a/resources/assets/icons/info-filled.svg b/resources/icons/info-filled.svg similarity index 100% rename from resources/assets/icons/info-filled.svg rename to resources/icons/info-filled.svg diff --git a/resources/assets/icons/info.svg b/resources/icons/info.svg similarity index 100% rename from resources/assets/icons/info.svg rename to resources/icons/info.svg diff --git a/resources/assets/icons/link.svg b/resources/icons/link.svg similarity index 100% rename from resources/assets/icons/link.svg rename to resources/icons/link.svg diff --git a/resources/assets/icons/list.svg b/resources/icons/list.svg similarity index 100% rename from resources/assets/icons/list.svg rename to resources/icons/list.svg diff --git a/resources/assets/icons/lock-open.svg b/resources/icons/lock-open.svg similarity index 100% rename from resources/assets/icons/lock-open.svg rename to resources/icons/lock-open.svg diff --git a/resources/assets/icons/lock.svg b/resources/icons/lock.svg similarity index 100% rename from resources/assets/icons/lock.svg rename to resources/icons/lock.svg diff --git a/resources/assets/icons/login.svg b/resources/icons/login.svg similarity index 100% rename from resources/assets/icons/login.svg rename to resources/icons/login.svg diff --git a/resources/assets/icons/logout.svg b/resources/icons/logout.svg similarity index 100% rename from resources/assets/icons/logout.svg rename to resources/icons/logout.svg diff --git a/resources/assets/icons/more.svg b/resources/icons/more.svg similarity index 100% rename from resources/assets/icons/more.svg rename to resources/icons/more.svg diff --git a/resources/assets/icons/new-user.svg b/resources/icons/new-user.svg similarity index 100% rename from resources/assets/icons/new-user.svg rename to resources/icons/new-user.svg diff --git a/resources/assets/icons/open-book.svg b/resources/icons/open-book.svg similarity index 100% rename from resources/assets/icons/open-book.svg rename to resources/icons/open-book.svg diff --git a/resources/assets/icons/page.svg b/resources/icons/page.svg similarity index 100% rename from resources/assets/icons/page.svg rename to resources/icons/page.svg diff --git a/resources/assets/icons/permission.svg b/resources/icons/permission.svg similarity index 100% rename from resources/assets/icons/permission.svg rename to resources/icons/permission.svg diff --git a/resources/assets/icons/popular.svg b/resources/icons/popular.svg similarity index 100% rename from resources/assets/icons/popular.svg rename to resources/icons/popular.svg diff --git a/resources/assets/icons/reply.svg b/resources/icons/reply.svg similarity index 100% rename from resources/assets/icons/reply.svg rename to resources/icons/reply.svg diff --git a/resources/assets/icons/save.svg b/resources/icons/save.svg similarity index 100% rename from resources/assets/icons/save.svg rename to resources/icons/save.svg diff --git a/resources/assets/icons/search.svg b/resources/icons/search.svg similarity index 100% rename from resources/assets/icons/search.svg rename to resources/icons/search.svg diff --git a/resources/assets/icons/settings.svg b/resources/icons/settings.svg similarity index 100% rename from resources/assets/icons/settings.svg rename to resources/icons/settings.svg diff --git a/resources/assets/icons/sort-down.svg b/resources/icons/sort-down.svg similarity index 100% rename from resources/assets/icons/sort-down.svg rename to resources/icons/sort-down.svg diff --git a/resources/assets/icons/sort-up.svg b/resources/icons/sort-up.svg similarity index 100% rename from resources/assets/icons/sort-up.svg rename to resources/icons/sort-up.svg diff --git a/resources/assets/icons/sort.svg b/resources/icons/sort.svg similarity index 100% rename from resources/assets/icons/sort.svg rename to resources/icons/sort.svg diff --git a/resources/assets/icons/spanner.svg b/resources/icons/spanner.svg similarity index 100% rename from resources/assets/icons/spanner.svg rename to resources/icons/spanner.svg diff --git a/resources/assets/icons/star-circle.svg b/resources/icons/star-circle.svg similarity index 100% rename from resources/assets/icons/star-circle.svg rename to resources/icons/star-circle.svg diff --git a/resources/assets/icons/star.svg b/resources/icons/star.svg similarity index 100% rename from resources/assets/icons/star.svg rename to resources/icons/star.svg diff --git a/resources/assets/icons/swap-vertical.svg b/resources/icons/swap-vertical.svg similarity index 100% rename from resources/assets/icons/swap-vertical.svg rename to resources/icons/swap-vertical.svg diff --git a/resources/assets/icons/tag.svg b/resources/icons/tag.svg similarity index 100% rename from resources/assets/icons/tag.svg rename to resources/icons/tag.svg diff --git a/resources/assets/icons/template.svg b/resources/icons/template.svg similarity index 100% rename from resources/assets/icons/template.svg rename to resources/icons/template.svg diff --git a/resources/assets/icons/time.svg b/resources/icons/time.svg similarity index 100% rename from resources/assets/icons/time.svg rename to resources/icons/time.svg diff --git a/resources/assets/icons/user.svg b/resources/icons/user.svg similarity index 100% rename from resources/assets/icons/user.svg rename to resources/icons/user.svg diff --git a/resources/assets/icons/users-add.svg b/resources/icons/users-add.svg similarity index 100% rename from resources/assets/icons/users-add.svg rename to resources/icons/users-add.svg diff --git a/resources/assets/icons/users.svg b/resources/icons/users.svg similarity index 100% rename from resources/assets/icons/users.svg rename to resources/icons/users.svg diff --git a/resources/assets/icons/view.svg b/resources/icons/view.svg similarity index 100% rename from resources/assets/icons/view.svg rename to resources/icons/view.svg diff --git a/resources/assets/icons/warning.svg b/resources/icons/warning.svg similarity index 100% rename from resources/assets/icons/warning.svg rename to resources/icons/warning.svg diff --git a/resources/assets/js/components/back-to-top.js b/resources/js/components/back-to-top.js similarity index 100% rename from resources/assets/js/components/back-to-top.js rename to resources/js/components/back-to-top.js diff --git a/resources/assets/js/components/book-sort.js b/resources/js/components/book-sort.js similarity index 100% rename from resources/assets/js/components/book-sort.js rename to resources/js/components/book-sort.js diff --git a/resources/assets/js/components/breadcrumb-listing.js b/resources/js/components/breadcrumb-listing.js similarity index 100% rename from resources/assets/js/components/breadcrumb-listing.js rename to resources/js/components/breadcrumb-listing.js diff --git a/resources/assets/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js similarity index 100% rename from resources/assets/js/components/chapter-toggle.js rename to resources/js/components/chapter-toggle.js diff --git a/resources/assets/js/components/collapsible.js b/resources/js/components/collapsible.js similarity index 100% rename from resources/assets/js/components/collapsible.js rename to resources/js/components/collapsible.js diff --git a/resources/assets/js/components/custom-checkbox.js b/resources/js/components/custom-checkbox.js similarity index 100% rename from resources/assets/js/components/custom-checkbox.js rename to resources/js/components/custom-checkbox.js diff --git a/resources/assets/js/components/dropdown.js b/resources/js/components/dropdown.js similarity index 100% rename from resources/assets/js/components/dropdown.js rename to resources/js/components/dropdown.js diff --git a/resources/assets/js/components/editor-toolbox.js b/resources/js/components/editor-toolbox.js similarity index 100% rename from resources/assets/js/components/editor-toolbox.js rename to resources/js/components/editor-toolbox.js diff --git a/resources/assets/js/components/entity-permissions-editor.js b/resources/js/components/entity-permissions-editor.js similarity index 100% rename from resources/assets/js/components/entity-permissions-editor.js rename to resources/js/components/entity-permissions-editor.js diff --git a/resources/assets/js/components/entity-selector-popup.js b/resources/js/components/entity-selector-popup.js similarity index 100% rename from resources/assets/js/components/entity-selector-popup.js rename to resources/js/components/entity-selector-popup.js diff --git a/resources/assets/js/components/entity-selector.js b/resources/js/components/entity-selector.js similarity index 100% rename from resources/assets/js/components/entity-selector.js rename to resources/js/components/entity-selector.js diff --git a/resources/assets/js/components/expand-toggle.js b/resources/js/components/expand-toggle.js similarity index 100% rename from resources/assets/js/components/expand-toggle.js rename to resources/js/components/expand-toggle.js diff --git a/resources/assets/js/components/header-mobile-toggle.js b/resources/js/components/header-mobile-toggle.js similarity index 100% rename from resources/assets/js/components/header-mobile-toggle.js rename to resources/js/components/header-mobile-toggle.js diff --git a/resources/assets/js/components/homepage-control.js b/resources/js/components/homepage-control.js similarity index 100% rename from resources/assets/js/components/homepage-control.js rename to resources/js/components/homepage-control.js diff --git a/resources/assets/js/components/image-picker.js b/resources/js/components/image-picker.js similarity index 100% rename from resources/assets/js/components/image-picker.js rename to resources/js/components/image-picker.js diff --git a/resources/assets/js/components/index.js b/resources/js/components/index.js similarity index 100% rename from resources/assets/js/components/index.js rename to resources/js/components/index.js diff --git a/resources/assets/js/components/list-sort-control.js b/resources/js/components/list-sort-control.js similarity index 100% rename from resources/assets/js/components/list-sort-control.js rename to resources/js/components/list-sort-control.js diff --git a/resources/assets/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js similarity index 100% rename from resources/assets/js/components/markdown-editor.js rename to resources/js/components/markdown-editor.js diff --git a/resources/assets/js/components/new-user-password.js b/resources/js/components/new-user-password.js similarity index 100% rename from resources/assets/js/components/new-user-password.js rename to resources/js/components/new-user-password.js diff --git a/resources/assets/js/components/notification.js b/resources/js/components/notification.js similarity index 100% rename from resources/assets/js/components/notification.js rename to resources/js/components/notification.js diff --git a/resources/assets/js/components/overlay.js b/resources/js/components/overlay.js similarity index 100% rename from resources/assets/js/components/overlay.js rename to resources/js/components/overlay.js diff --git a/resources/assets/js/components/page-comments.js b/resources/js/components/page-comments.js similarity index 100% rename from resources/assets/js/components/page-comments.js rename to resources/js/components/page-comments.js diff --git a/resources/assets/js/components/page-display.js b/resources/js/components/page-display.js similarity index 100% rename from resources/assets/js/components/page-display.js rename to resources/js/components/page-display.js diff --git a/resources/assets/js/components/page-picker.js b/resources/js/components/page-picker.js similarity index 100% rename from resources/assets/js/components/page-picker.js rename to resources/js/components/page-picker.js diff --git a/resources/assets/js/components/permissions-table.js b/resources/js/components/permissions-table.js similarity index 100% rename from resources/assets/js/components/permissions-table.js rename to resources/js/components/permissions-table.js diff --git a/resources/assets/js/components/setting-app-color-picker.js b/resources/js/components/setting-app-color-picker.js similarity index 100% rename from resources/assets/js/components/setting-app-color-picker.js rename to resources/js/components/setting-app-color-picker.js diff --git a/resources/assets/js/components/shelf-sort.js b/resources/js/components/shelf-sort.js similarity index 100% rename from resources/assets/js/components/shelf-sort.js rename to resources/js/components/shelf-sort.js diff --git a/resources/assets/js/components/sidebar.js b/resources/js/components/sidebar.js similarity index 100% rename from resources/assets/js/components/sidebar.js rename to resources/js/components/sidebar.js diff --git a/resources/assets/js/components/template-manager.js b/resources/js/components/template-manager.js similarity index 100% rename from resources/assets/js/components/template-manager.js rename to resources/js/components/template-manager.js diff --git a/resources/assets/js/components/toggle-switch.js b/resources/js/components/toggle-switch.js similarity index 100% rename from resources/assets/js/components/toggle-switch.js rename to resources/js/components/toggle-switch.js diff --git a/resources/assets/js/components/tri-layout.js b/resources/js/components/tri-layout.js similarity index 100% rename from resources/assets/js/components/tri-layout.js rename to resources/js/components/tri-layout.js diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js similarity index 100% rename from resources/assets/js/components/wysiwyg-editor.js rename to resources/js/components/wysiwyg-editor.js diff --git a/resources/assets/js/index.js b/resources/js/index.js similarity index 100% rename from resources/assets/js/index.js rename to resources/js/index.js diff --git a/resources/assets/js/services/animations.js b/resources/js/services/animations.js similarity index 100% rename from resources/assets/js/services/animations.js rename to resources/js/services/animations.js diff --git a/resources/assets/js/services/code.js b/resources/js/services/code.js similarity index 100% rename from resources/assets/js/services/code.js rename to resources/js/services/code.js diff --git a/resources/assets/js/services/dates.js b/resources/js/services/dates.js similarity index 100% rename from resources/assets/js/services/dates.js rename to resources/js/services/dates.js diff --git a/resources/assets/js/services/dom.js b/resources/js/services/dom.js similarity index 100% rename from resources/assets/js/services/dom.js rename to resources/js/services/dom.js diff --git a/resources/assets/js/services/drawio.js b/resources/js/services/drawio.js similarity index 100% rename from resources/assets/js/services/drawio.js rename to resources/js/services/drawio.js diff --git a/resources/assets/js/services/events.js b/resources/js/services/events.js similarity index 100% rename from resources/assets/js/services/events.js rename to resources/js/services/events.js diff --git a/resources/assets/js/services/http.js b/resources/js/services/http.js similarity index 100% rename from resources/assets/js/services/http.js rename to resources/js/services/http.js diff --git a/resources/assets/js/services/translations.js b/resources/js/services/translations.js similarity index 100% rename from resources/assets/js/services/translations.js rename to resources/js/services/translations.js diff --git a/resources/assets/js/services/util.js b/resources/js/services/util.js similarity index 100% rename from resources/assets/js/services/util.js rename to resources/js/services/util.js diff --git a/resources/assets/js/vues/attachment-manager.js b/resources/js/vues/attachment-manager.js similarity index 100% rename from resources/assets/js/vues/attachment-manager.js rename to resources/js/vues/attachment-manager.js diff --git a/resources/assets/js/vues/code-editor.js b/resources/js/vues/code-editor.js similarity index 100% rename from resources/assets/js/vues/code-editor.js rename to resources/js/vues/code-editor.js diff --git a/resources/assets/js/vues/components/autosuggest.js b/resources/js/vues/components/autosuggest.js similarity index 100% rename from resources/assets/js/vues/components/autosuggest.js rename to resources/js/vues/components/autosuggest.js diff --git a/resources/assets/js/vues/components/dropzone.js b/resources/js/vues/components/dropzone.js similarity index 100% rename from resources/assets/js/vues/components/dropzone.js rename to resources/js/vues/components/dropzone.js diff --git a/resources/assets/js/vues/entity-dashboard.js b/resources/js/vues/entity-dashboard.js similarity index 100% rename from resources/assets/js/vues/entity-dashboard.js rename to resources/js/vues/entity-dashboard.js diff --git a/resources/assets/js/vues/image-manager.js b/resources/js/vues/image-manager.js similarity index 100% rename from resources/assets/js/vues/image-manager.js rename to resources/js/vues/image-manager.js diff --git a/resources/assets/js/vues/page-editor.js b/resources/js/vues/page-editor.js similarity index 100% rename from resources/assets/js/vues/page-editor.js rename to resources/js/vues/page-editor.js diff --git a/resources/assets/js/vues/search.js b/resources/js/vues/search.js similarity index 100% rename from resources/assets/js/vues/search.js rename to resources/js/vues/search.js diff --git a/resources/assets/js/vues/tag-manager.js b/resources/js/vues/tag-manager.js similarity index 100% rename from resources/assets/js/vues/tag-manager.js rename to resources/js/vues/tag-manager.js diff --git a/resources/assets/js/vues/vues.js b/resources/js/vues/vues.js similarity index 100% rename from resources/assets/js/vues/vues.js rename to resources/js/vues/vues.js diff --git a/resources/assets/sass/_animations.scss b/resources/sass/_animations.scss similarity index 100% rename from resources/assets/sass/_animations.scss rename to resources/sass/_animations.scss diff --git a/resources/assets/sass/_blocks.scss b/resources/sass/_blocks.scss similarity index 100% rename from resources/assets/sass/_blocks.scss rename to resources/sass/_blocks.scss diff --git a/resources/assets/sass/_buttons.scss b/resources/sass/_buttons.scss similarity index 100% rename from resources/assets/sass/_buttons.scss rename to resources/sass/_buttons.scss diff --git a/resources/assets/sass/_codemirror.scss b/resources/sass/_codemirror.scss similarity index 100% rename from resources/assets/sass/_codemirror.scss rename to resources/sass/_codemirror.scss diff --git a/resources/assets/sass/_colors.scss b/resources/sass/_colors.scss similarity index 100% rename from resources/assets/sass/_colors.scss rename to resources/sass/_colors.scss diff --git a/resources/assets/sass/_components.scss b/resources/sass/_components.scss similarity index 100% rename from resources/assets/sass/_components.scss rename to resources/sass/_components.scss diff --git a/resources/assets/sass/_forms.scss b/resources/sass/_forms.scss similarity index 100% rename from resources/assets/sass/_forms.scss rename to resources/sass/_forms.scss diff --git a/resources/assets/sass/_header.scss b/resources/sass/_header.scss similarity index 100% rename from resources/assets/sass/_header.scss rename to resources/sass/_header.scss diff --git a/resources/assets/sass/_html.scss b/resources/sass/_html.scss similarity index 100% rename from resources/assets/sass/_html.scss rename to resources/sass/_html.scss diff --git a/resources/assets/sass/_layout.scss b/resources/sass/_layout.scss similarity index 100% rename from resources/assets/sass/_layout.scss rename to resources/sass/_layout.scss diff --git a/resources/assets/sass/_lists.scss b/resources/sass/_lists.scss similarity index 100% rename from resources/assets/sass/_lists.scss rename to resources/sass/_lists.scss diff --git a/resources/assets/sass/_mixins.scss b/resources/sass/_mixins.scss similarity index 100% rename from resources/assets/sass/_mixins.scss rename to resources/sass/_mixins.scss diff --git a/resources/assets/sass/_pages.scss b/resources/sass/_pages.scss similarity index 100% rename from resources/assets/sass/_pages.scss rename to resources/sass/_pages.scss diff --git a/resources/assets/sass/_reset.scss b/resources/sass/_reset.scss similarity index 100% rename from resources/assets/sass/_reset.scss rename to resources/sass/_reset.scss diff --git a/resources/assets/sass/_spacing.scss b/resources/sass/_spacing.scss similarity index 100% rename from resources/assets/sass/_spacing.scss rename to resources/sass/_spacing.scss diff --git a/resources/assets/sass/_tables.scss b/resources/sass/_tables.scss similarity index 100% rename from resources/assets/sass/_tables.scss rename to resources/sass/_tables.scss diff --git a/resources/assets/sass/_text.scss b/resources/sass/_text.scss similarity index 100% rename from resources/assets/sass/_text.scss rename to resources/sass/_text.scss diff --git a/resources/assets/sass/_tinymce.scss b/resources/sass/_tinymce.scss similarity index 100% rename from resources/assets/sass/_tinymce.scss rename to resources/sass/_tinymce.scss diff --git a/resources/assets/sass/_variables.scss b/resources/sass/_variables.scss similarity index 100% rename from resources/assets/sass/_variables.scss rename to resources/sass/_variables.scss diff --git a/resources/assets/sass/export-styles.scss b/resources/sass/export-styles.scss similarity index 100% rename from resources/assets/sass/export-styles.scss rename to resources/sass/export-styles.scss diff --git a/resources/assets/sass/print-styles.scss b/resources/sass/print-styles.scss similarity index 100% rename from resources/assets/sass/print-styles.scss rename to resources/sass/print-styles.scss diff --git a/resources/assets/sass/styles.scss b/resources/sass/styles.scss similarity index 100% rename from resources/assets/sass/styles.scss rename to resources/sass/styles.scss diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore index c96a04f00..869804c2a 100755 --- a/storage/framework/cache/.gitignore +++ b/storage/framework/cache/.gitignore @@ -1,2 +1,3 @@ * +!data/ !.gitignore \ No newline at end of file diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100755 index 000000000..d6b7ef32c --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/webpack.config.js b/webpack.config.js index 78b679a0a..e496340c4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,10 +7,10 @@ const config = { target: 'web', mode: dev? 'development' : 'production', entry: { - app: './resources/assets/js/index.js', - styles: './resources/assets/sass/styles.scss', - "export-styles": './resources/assets/sass/export-styles.scss', - "print-styles": './resources/assets/sass/print-styles.scss', + app: './resources/js/index.js', + styles: './resources/sass/styles.scss', + "export-styles": './resources/sass/export-styles.scss', + "print-styles": './resources/sass/print-styles.scss', }, output: { filename: '[name].js', From de8a1a372d9a84358ee1f46a9fe7a0edc7ebba35 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 7 Sep 2019 00:10:03 +0100 Subject: [PATCH 010/257] Updated travis-ci php versions --- .travis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29727f488..87d7bc71c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ -dist: trusty -sudo: false +dist: bionic language: php php: - - 7.0.20 - - 7.1.9 + - '7.1.3' + - '7.2' cache: directories: @@ -23,6 +22,3 @@ before_script: after_failure: - cat storage/logs/laravel.log - -script: - - phpunit From 58f5508b05e9d3265bc79ffc13e1c9b32d031591 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 7 Sep 2019 00:14:19 +0100 Subject: [PATCH 011/257] Added mysql service for travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 87d7bc71c..d066747db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ php: - '7.1.3' - '7.2' +services: + - mysql + cache: directories: - $HOME/.composer/cache From 339d3849b87963e982be7f9ef5837b5a116c2947 Mon Sep 17 00:00:00 2001 From: kostefun <43705266+kostefun@users.noreply.github.com> Date: Mon, 9 Sep 2019 10:07:16 +0700 Subject: [PATCH 012/257] Update settings.php fix rus --- resources/lang/ru/settings.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/ru/settings.php b/resources/lang/ru/settings.php index c182cf22e..95f453a27 100755 --- a/resources/lang/ru/settings.php +++ b/resources/lang/ru/settings.php @@ -13,7 +13,7 @@ return [ // App Settings 'app_customization' => 'Настройки', - 'app_features_security' => 'Функции & Безопасность', + 'app_features_security' => 'Функционал & Безопасность', 'app_name' => 'Имя приложения', 'app_name_desc' => 'Имя отображается в заголовке email отправленных системой.', 'app_name_header' => 'Отображать имя приложения в заголовке', @@ -43,13 +43,13 @@ return [ // Registration Settings 'reg_settings' => 'Настройки регистрации', - 'reg_enable' => 'Разрешить регистрацияю', + 'reg_enable' => 'Разрешить регистрацию', 'reg_enable_toggle' => 'Разрешить регистрацию', - 'reg_enable_desc' => 'Если регистрация разрешена, пользователь сможет зарегистрироваться в системе самомтоятельно. При регистрации назначается роль пользователя по умолчанию', + 'reg_enable_desc' => 'Если регистрация разрешена, пользователь сможет зарегистрироваться в системе самостоятельно. При регистрации назначается роль пользователя по умолчанию', 'reg_default_role' => 'Роль пользователя по умолчанию после регистрации', 'reg_email_confirmation' => 'Подтверждение электонной почты', 'reg_email_confirmation_toggle' => 'Требовать подтверждение по электронной почте', - 'reg_confirm_email_desc' => 'Если используется ограничение по домену, подтверждение будет обязательно, а этот пункт проигнорирован.', + 'reg_confirm_email_desc' => 'При использовании ограничения по домену - подтверждение обязательно, этот пункт игнорируется.', 'reg_confirm_restrict_domain' => 'Ограничить регистрацию по домену', 'reg_confirm_restrict_domain_desc' => 'Введите список доменов почты через запятую, для которых разрешена регистрация. Пользователям будет отправлено письмо для подтверждения адреса перед входом в приложение.
Обратите внимание, что пользователи смогут изменить свои адреса уже после регистрации.', 'reg_confirm_restrict_domain_placeholder' => 'Без ограничений', From f311635de6589307abd2707fad4c38245eab7947 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 12 Sep 2019 21:19:05 +0100 Subject: [PATCH 013/257] Add testing via GitHub actions (#1657) To replace Travis CI. Travis to be removed after migration to Laravel 6. --- .github/workflows/phpunit.yml | 26 ++++++++++++++++++++++++++ readme.md | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/phpunit.yml diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 000000000..d1b458bdb --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,26 @@ +name: phpunit + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + php: [7.2, 7.3] + steps: + - uses: actions/checkout@v1 + - name: Setup Database + run: | + mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;' + mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';" + mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';" + mysql -uroot -proot -e 'FLUSH PRIVILEGES;' + - name: Install composer dependencies & Test + run: composer install --prefer-dist --no-interaction + - name: Migrate and seed the database + run: | + php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing + php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing + - name: phpunit + run: php${{ matrix.php }} ./vendor/bin/phpunit diff --git a/readme.md b/readme.md index 2efc6b160..489fc3365 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ [![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest) [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE) -[![Build Status](https://travis-ci.org/BookStackApp/BookStack.svg)](https://travis-ci.org/BookStackApp/BookStack) +[![Build Status](https://github.com/BookStackApp/BookStack/workflows/phpunit/badge.svg)](https://github.com/BookStackApp/BookStack/actions) [![Discord](https://img.shields.io/static/v1?label=Chat&message=Discord&color=738adb&logo=discord)](https://discord.gg/ztkBqR2) A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/. From 9e8e5b2ca081dbbc9748c1451dcee80b327d6f75 Mon Sep 17 00:00:00 2001 From: oykenfurkan Date: Fri, 13 Sep 2019 15:32:40 +0300 Subject: [PATCH 014/257] Create tr Adding Turkish translations --- resources/lang/tr | 1 + 1 file changed, 1 insertion(+) create mode 100644 resources/lang/tr diff --git a/resources/lang/tr b/resources/lang/tr new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/resources/lang/tr @@ -0,0 +1 @@ + From ec29c08bebf2f5464f5ed040f41e2d5922c22d7f Mon Sep 17 00:00:00 2001 From: oykenfurkan Date: Fri, 13 Sep 2019 15:32:54 +0300 Subject: [PATCH 015/257] Delete tr --- resources/lang/tr | 1 - 1 file changed, 1 deletion(-) delete mode 100644 resources/lang/tr diff --git a/resources/lang/tr b/resources/lang/tr deleted file mode 100644 index 8b1378917..000000000 --- a/resources/lang/tr +++ /dev/null @@ -1 +0,0 @@ - From 84c36f6032c9baaefc7db9714d5897c82913f6c8 Mon Sep 17 00:00:00 2001 From: Furkan Date: Fri, 13 Sep 2019 15:45:38 +0300 Subject: [PATCH 016/257] Added Turkish translations. --- resources/lang/tr/activities.php | 48 +++++ resources/lang/tr/auth.php | 67 +++++++ resources/lang/tr/common.php | 70 +++++++ resources/lang/tr/components.php | 33 ++++ resources/lang/tr/entities.php | 304 +++++++++++++++++++++++++++++++ resources/lang/tr/errors.php | 84 +++++++++ resources/lang/tr/pagination.php | 12 ++ resources/lang/tr/passwords.php | 15 ++ resources/lang/tr/settings.php | 134 ++++++++++++++ resources/lang/tr/validation.php | 85 +++++++++ 10 files changed, 852 insertions(+) create mode 100644 resources/lang/tr/activities.php create mode 100644 resources/lang/tr/auth.php create mode 100644 resources/lang/tr/common.php create mode 100644 resources/lang/tr/components.php create mode 100644 resources/lang/tr/entities.php create mode 100644 resources/lang/tr/errors.php create mode 100644 resources/lang/tr/pagination.php create mode 100644 resources/lang/tr/passwords.php create mode 100755 resources/lang/tr/settings.php create mode 100644 resources/lang/tr/validation.php diff --git a/resources/lang/tr/activities.php b/resources/lang/tr/activities.php new file mode 100644 index 000000000..ccc55e172 --- /dev/null +++ b/resources/lang/tr/activities.php @@ -0,0 +1,48 @@ + 'saya oluşturuldu', + 'page_create_notification' => 'Sayfa Başarıyla Oluşturuldu', + 'page_update' => 'sayfa güncellendi', + 'page_update_notification' => 'Sayfa Başarıyla Güncellendi', + 'page_delete' => 'sayfa silindi', + 'page_delete_notification' => 'Sayfa Başarıyla Silindi', + 'page_restore' => 'sayfa kurtarıldı', + 'page_restore_notification' => 'Sayfa Başarıyla Kurtarıldı', + 'page_move' => 'sayfa taşındı', + + // Chapters + 'chapter_create' => 'bölüm oluşturuldu', + 'chapter_create_notification' => 'Bölüm Başarıyla Oluşturuldu', + 'chapter_update' => 'bölüm güncellendi', + 'chapter_update_notification' => 'Bölüm Başarıyla Güncellendi', + 'chapter_delete' => 'bölüm silindi', + 'chapter_delete_notification' => 'Bölüm Başarıyla Silindi', + 'chapter_move' => 'bölüm taşındı', + + // Books + 'book_create' => 'kitap oluşturuldu', + 'book_create_notification' => 'Kitap Başarıyla Oluşturuldu', + 'book_update' => 'kitap güncellendi', + 'book_update_notification' => 'Kitap Başarıyla Güncellendi', + 'book_delete' => 'kitap silindi', + 'book_delete_notification' => 'Kitap Başarıyla Silindi', + 'book_sort' => 'kitap düzenlendi', + 'book_sort_notification' => 'Kitap Başarıyla Yeniden Sıralandı', + + // Bookshelves + 'bookshelf_create' => 'kitaplık oluşturuldu', + 'bookshelf_create_notification' => 'Kitaplık Başarıyla Oluşturuldu', + 'bookshelf_update' => 'kitaplık güncellendi', + 'bookshelf_update_notification' => 'Kitaplık Başarıyla Güncellendi', + 'bookshelf_delete' => 'kitaplık silindi', + 'bookshelf_delete_notification' => 'Kitaplık Başarıyla Silindi', + + // Other + 'commented_on' => 'yorum yaptı', +]; diff --git a/resources/lang/tr/auth.php b/resources/lang/tr/auth.php new file mode 100644 index 000000000..1988c01b5 --- /dev/null +++ b/resources/lang/tr/auth.php @@ -0,0 +1,67 @@ + 'Girilen bilgiler bizdeki kayıtlarla uyuşmuyor.', + 'throttle' => 'Çok fazla giriş yapmaya çalıştınız. Lütfen :seconds saniye içinde tekrar deneyin.', + + // Login & Register + 'sign_up' => 'Kayıt Ol', + 'log_in' => 'Giriş Yap', + 'log_in_with' => ':socialDriver ile giriş yap', + 'sign_up_with' => ':socialDriver ile kayıt ol', + 'logout' => 'Çıkış Yap', + + 'name' => 'İsim', + 'username' => 'Kullanıcı Adı', + 'email' => 'Email', + 'password' => 'Şifre', + 'password_confirm' => 'Şifreyi onayla', + 'password_hint' => 'En az 5 karakter olmalı', + 'forgot_password' => 'Şifrenizi mi unuttunuz?', + 'remember_me' => 'Beni Hatırla', + 'ldap_email_hint' => 'Hesabı kullanmak istediğiniz e-mail adresinizi giriniz.', + 'create_account' => 'Hesap Oluştur', + 'already_have_account' => 'Zaten bir hesabınız var mı?', + 'dont_have_account' => 'Hesabınız yok mu?', + 'social_login' => 'Diğer Servisler ile Giriş Yap', + 'social_registration' => 'Diğer Servisler ile Kayıt Ol', + 'social_registration_text' => 'Diğer servisler ile kayıt ol ve giriş yap.', + + 'register_thanks' => 'Kayıt olduğunuz için teşekkürler!', + 'register_confirm' => 'Lütfen e-posta adresinizi kontrol edin ve gelen doğrulama bağlantısına tıklayınız. :appName.', + 'registrations_disabled' => 'Kayıt olma özelliği geçici olarak kısıtlanmıştır', + 'registration_email_domain_invalid' => 'Bu e-mail sağlayıcısının bu uygulamaya erişim izni yoktur.', + 'register_success' => 'Artık kayıtlı bir kullanıcı olarak giriş yaptınız.', + + + // Password Reset + 'reset_password' => 'Parolayı Sıfırla', + 'reset_password_send_instructions' => 'Aşağıya e-mail adresinizi girdiğinizde parola yenileme bağlantısı mail adresinize gönderilecektir.', + 'reset_password_send_button' => '>Sıfırlama Bağlantısını Gönder', + 'reset_password_sent_success' => 'Sıfırlama bağlantısı :email adresinize gönderildi.', + 'reset_password_success' => 'Parolanız başarıyla sıfırlandı.', + 'email_reset_subject' => ':appName şifrenizi sıfırlayın.', + 'email_reset_text' => ' Parola sıfırlama isteğinde bulunduğunuz için bu maili görüntülüyorsunuz.', + 'email_reset_not_requested' => 'Eğer bu parola sıfırlama isteğinde bulunmadıysanız herhangi bir işlem yapmanıza gerek yoktur.', + + + // Email Confirmation + 'email_confirm_subject' => ':appName için girdiğiniz mail adresiniz onaylayınız', + 'email_confirm_greeting' => ':appName\'e katıldığınız için teşekkürler!', + 'email_confirm_text' => 'Lütfen e-mail adresinizi aşağıda bulunan butona tıklayarak onaylayınız:', + 'email_confirm_action' => 'E-Maili Onayla', + 'email_confirm_send_error' => 'e-mail onayı gerekli fakat sistem mail göndermeyi başaramadı. Yöneticiniz ile görüşüp kurulumlarda bir sorun olmadığını doğrulayın.', + 'email_confirm_success' => 'e-mail adresiniz onaylandı!', + 'email_confirm_resent' => 'Doğrulama maili gönderildi, lütfen gelen kutunuzu kontrol ediniz...', + + 'email_not_confirmed' => 'E-mail Adresi Doğrulanmadı', + 'email_not_confirmed_text' => 'Sağlamış olduğunuz e-mail adresi henüz doğrulanmadı.', + 'email_not_confirmed_click_link' => 'Lütfen kayıt olduktan kısa süre sonra size gönderilen maildeki bağlantıya tıklayın ve mail adresinizi onaylayın.', + 'email_not_confirmed_resend' => 'Eğer gelen maili bulamadıysanız aşağıdaki formu tekrar doldurarak onay mailini kendinize tekrar gönderebilirsiniz.', + 'email_not_confirmed_resend_button' => 'Doğrulama Mailini Yeniden Yolla', +]; \ No newline at end of file diff --git a/resources/lang/tr/common.php b/resources/lang/tr/common.php new file mode 100644 index 000000000..54425aef3 --- /dev/null +++ b/resources/lang/tr/common.php @@ -0,0 +1,70 @@ + 'İptal', + 'confirm' => 'Onayla', + 'back' => 'Geri', + 'save' => 'Kaydet', + 'continue' => 'Devam', + 'select' => 'Seç', + 'toggle_all' => 'Hepsini Değiştir', + 'more' => 'Daha Fazla', + + // Form Labels + 'name' => 'İsim', + 'description' => 'Açıklama', + 'role' => 'Rol', + 'cover_image' => 'Kapak resmi', + 'cover_image_description' => 'Bu resim yaklaşık 440x250px boyutlarında olmalıdır.', + + // Actions + 'actions' => 'Aksiyonlar', + 'view' => 'Görüntüle', + 'view_all' => 'Hepsini Görüntüle', + 'create' => 'Oluştur', + 'update' => 'Güncelle', + 'edit' => 'Düzenle', + 'sort' => 'Sırala', + 'move' => 'Taşı', + 'copy' => 'Kopyala', + 'reply' => 'Yanıtla', + 'delete' => 'Sil', + 'search' => 'Ara', + 'search_clear' => 'Aramayı Temizle', + 'reset' => 'Sıfırla', + 'remove' => 'Kaldır', + 'add' => 'Ekle', + + // Sort Options + 'sort_name' => 'İsim', + 'sort_created_at' => 'Oluşturulma Tarihi', + 'sort_updated_at' => 'Güncellenme Tarihi', + + // Misc + 'deleted_user' => 'Silinmiş Kullanıcı', + 'no_activity' => 'Gösterilecek aktivite yok', + 'no_items' => 'Kullanılabilir öge yok', + 'back_to_top' => 'Başa dön', + 'toggle_details' => 'Detayları değiştir', + 'toggle_thumbnails' => 'Küçük resimleri değiştir', + 'details' => 'Detaylar', + 'grid_view' => 'Grid görünümü', + 'list_view' => 'Liste görünümü', + 'default' => 'Varsayılan', + + // Header + 'view_profile' => 'Profili Görüntüle', + 'edit_profile' => 'Profili Düzenle', + + // Layout tabs + 'tab_info' => 'Bilgi', + 'tab_content' => 'İçerik', + + // Email Content + 'email_action_help' => 'Eğer ":actionText" butonuna tıklamakta zorluk çekiyorsanız, aşağıda bulunan linki kopyalayıp tarayıcınıza yapıştırabilirsiniz.', + 'email_rights' => 'Bütün hakları saklıdır', +]; diff --git a/resources/lang/tr/components.php b/resources/lang/tr/components.php new file mode 100644 index 000000000..d52f2d006 --- /dev/null +++ b/resources/lang/tr/components.php @@ -0,0 +1,33 @@ + 'Görsel Seç', + 'image_all' => 'Tümü', + 'image_all_title' => 'Tüm görselleri temizle', + 'image_book_title' => 'Bu kitaba ait görselleri görüntüle', + 'image_page_title' => 'Bu sayfaya ait görselleri görüntüle', + 'image_search_hint' => 'Görsel adı ile ara', + 'image_uploaded' => ':uploadedDate tarihinde yüklendi', + 'image_load_more' => 'Daha Fazla ', + 'image_image_name' => 'Görsel Adı', + 'image_delete_used' => 'Bu görsel aşağıda bulunan görsellerde kullanılmış.', + 'image_delete_confirm' => 'Gerçekten bu görseli silmek istiyorsanız sil tuşuna basınız.', + 'image_select_image' => 'Görsel Seç', + 'image_dropzone' => 'Görselleri buraya sürükle veya seçmek için buraya tıkla', + 'images_deleted' => 'Görseller Silindi', + 'image_preview' => 'Görsel Önizleme', + 'image_upload_success' => 'Görsel başarıyla yüklendi', + 'image_update_success' => 'Görsel başarıyla güncellendi', + 'image_delete_success' => 'Görsel başarıyla silindi', + 'image_upload_remove' => 'Kaldır', + + // Code Editor + 'code_editor' => 'Kodu Güncelle', + 'code_language' => 'Kod Dil', + 'code_content' => 'Kod İçeriği', + 'code_save' => 'Kodu Kaydet', +]; diff --git a/resources/lang/tr/entities.php b/resources/lang/tr/entities.php new file mode 100644 index 000000000..698355a46 --- /dev/null +++ b/resources/lang/tr/entities.php @@ -0,0 +1,304 @@ + 'Yakın Zamanda Oluşturuldu', + 'recently_created_pages' => 'Yakın Zamanda Oluşturulmuş Sayfalar', + 'recently_updated_pages' => 'Yakın Zamanda Güncellenmiş Sayfalar', + 'recently_created_chapters' => 'Yakın Zamanda Oluşturulmuş Bölümler', + 'recently_created_books' => 'Yakın Zamanda Olşturulmuş Kitaplar', + 'recently_created_shelves' => 'Yakın Zamanda Oluşturulmuş Kitaplıklar', + 'recently_update' => 'Yakın Zamanda Güncellenmiş', + 'recently_viewed' => 'Yakın Zamanda Görüntülenmiş', + 'recent_activity' => 'Son Hareketler', + 'create_now' => 'Hemen bir tane oluştur', + 'revisions' => 'Revizyonlar', + 'meta_revision' => 'Revizyon #:revisionCount', + 'meta_created' => 'Oluşturuldu :timeLength', + 'meta_created_name' => ':user tarafından :timeLength tarihinde oluşturuldu', + 'meta_updated' => 'Güncellendi :timeLength', + 'meta_updated_name' => ':user tarafından :timeLength tarihinde güncellendi', + 'entity_select' => 'Öğe Seçme', + 'images' => 'Görseller', + 'my_recent_drafts' => 'Son Taslaklarım', + 'my_recently_viewed' => 'Son Görüntülemelerim', + 'no_pages_viewed' => 'Herhangi bir sayfa görüntülemediniz', + 'no_pages_recently_created' => 'Yakın zamanda bir sayfa oluşturulmadı', + 'no_pages_recently_updated' => 'Yakın zamanda bir sayfa güncellenmedi', + 'export' => 'Dışa Aktar', + 'export_html' => 'Contained Web Dosyası', + 'export_pdf' => 'PDF Dosyası', + 'export_text' => 'Düz Metin Dosyası', + + // Permissions and restrictions + 'permissions' => 'İzinler', + 'permissions_intro' => 'Etkinleştirildikten sonra bu izinler diğer diğer bütün izinlerden öncelikli olacaktır.', + 'permissions_enable' => 'Özelleştirilmiş Yetkileri Etkinleştir', + 'permissions_save' => 'İzinleri Kaydet', + + // Search + 'search_results' => 'Arama Sonuçları', + 'search_total_results_found' => ':count sonuç bulundu |:count toplam sonuç bulundu', + 'search_clear' => 'Aramaları Temizle', + 'search_no_pages' => 'Bu aramayla herhangi bir sonuç eşleşmedi', + 'search_for_term' => ':term için ara', + 'search_more' => 'Daha Fazla Sonuç', + 'search_filters' => 'Arama Filtreleri', + 'search_content_type' => 'İçerik Türü', + 'search_exact_matches' => 'Tam Eşleşmeler', + 'search_tags' => 'Etiket Aramaları', + 'search_options' => 'Ayarlar', + 'search_viewed_by_me' => 'Benim tarafımdan görüntülendi', + 'search_not_viewed_by_me' => 'Benim tarafımdan görüntülenmedi', + 'search_permissions_set' => 'İzinler ayarlandı', + 'search_created_by_me' => 'Benim tarafımdan oluşturuldu', + 'search_updated_by_me' => 'Benim tarafımdan güncellendi', + 'search_date_options' => 'Tarih Seçenekleri', + 'search_updated_before' => 'Önce güncellendi', + 'search_updated_after' => 'Sonra güncellendi', + 'search_created_before' => 'Önce oluşturuldu', + 'search_created_after' => 'Sonra oluşturuldu', + 'search_set_date' => 'Tarih Ayarla', + 'search_update' => 'Aramayı Güncelle', + + // Shelves + 'shelf' => 'Kitaplık', + 'shelves' => 'Kitaplıklar', + 'x_shelves' => ':count Kitaplık|:count Kitaplıklar', + 'shelves_long' => 'Kitaplıklar', + 'shelves_empty' => 'Hiç kitaplık oluşturulmadı', + 'shelves_create' => 'Yeni Kitaplık Oluştur', + 'shelves_popular' => 'Popüler Kitaplıklar', + 'shelves_new' => 'Yeni Kitaplıklar', + 'shelves_new_action' => 'Yeni Kitaplık', + 'shelves_popular_empty' => 'En popüler kitaplıklar burada görüntülenecek.', + 'shelves_new_empty' => 'En son oluşturulmuş kitaplıklar burada görüntülenecek.', + 'shelves_save' => 'Kitaplığı Kaydet', + 'shelves_books' => 'Bu kitaplıktaki kitaplar', + 'shelves_add_books' => 'Bu kitaplığa kitap ekle', + 'shelves_drag_books' => 'Bu kitaplığa kitap eklemek için kitapları buraya sürükle', + 'shelves_empty_contents' => 'Bu kitaplığa henüz hiç bir kitap atanmamış', + 'shelves_edit_and_assign' => 'Kitaplığa kitap eklemek için güncelle', + 'shelves_edit_named' => ':name Kitaplığını Güncelle', + 'shelves_edit' => 'Kitaplığı Güncelle', + 'shelves_delete' => 'Kitaplığı Sil', + 'shelves_delete_named' => ':name Kitaplığını Sil', + 'shelves_delete_explain' => "Bu işlem :name kitaplığını silecektir. İçerdiği kitaplar silinmeyecektir.", + 'shelves_delete_confirmation' => 'Bu kitaplığı silmek istediğinizden emin misiniz?', + 'shelves_permissions' => 'Kitaplık İzinleri', + 'shelves_permissions_updated' => 'Kitaplık İzinleri Güncellendi', + 'shelves_permissions_active' => 'Kitaplık İzinleri Aktif', + 'shelves_copy_permissions_to_books' => 'İzinleri Kitaplara Kopyala', + 'shelves_copy_permissions' => 'İzinleri Kopyala', + 'shelves_copy_permissions_explain' => 'Bu işlem sonucunda kitaplığınızın izinleri içerdiği kitaplara da aynen uygulanır. Aktifleştirmeden bu kitaplığa ait izinleri kaydettiğinizden emin olun.', + 'shelves_copy_permission_success' => 'Kitaplık izinleri :count adet kitaba kopyalandı', + + // Books + 'book' => 'Kitap', + 'books' => 'Kitaplar', + 'x_books' => ':count Kitap|:count Kitaplar', + 'books_empty' => 'Hiç kitap oluşturulmadı', + 'books_popular' => 'Popüler Kitaplar', + 'books_recent' => 'En Son Kitaplar', + 'books_new' => 'Yeni Kitaplar', + 'books_new_action' => 'Yeni Kitap', + 'books_popular_empty' => 'En popüler kitaplar burada görüntülenecek.', + 'books_new_empty' => 'En yeni kitaplar burada görüntülenecek.', + 'books_create' => 'Yeni Kitap Oluştur', + 'books_delete' => 'Kitabı Sil', + 'books_delete_named' => ':bookName kitabını sil', + 'books_delete_explain' => 'Bu işlem \':bookName\' kitabını silecek. Bütün sayfa ve bölümler silinecektir.', + 'books_delete_confirmation' => 'Bu kitabı silmek istediğinizden emin misiniz?', + 'books_edit' => 'Kitabı Güncelle', + 'books_edit_named' => ':bookName Kitabını Güncelle', + 'books_form_book_name' => 'Kitap Adı', + 'books_save' => 'Kitabı Kaydet', + 'books_permissions' => 'Kitap İzinleri', + 'books_permissions_updated' => 'Kitap İzinleri Güncellendi', + 'books_empty_contents' => 'Bu kitaba ait sayfa veya bölüm oluşturulmadı.', + 'books_empty_create_page' => 'Yeni sayfa oluştur', + 'books_empty_sort_current_book' => 'Mevcut kitabı sırala', + 'books_empty_add_chapter' => 'Yeni bölüm ekle', + 'books_permissions_active' => 'Kitap İzinleri Aktif', + 'books_search_this' => 'Bu kitapta ara', + 'books_navigation' => 'Kitap Navigasyonu', + 'books_sort' => 'Kitap İçeriklerini Sırala', + 'books_sort_named' => ':bookName Kitabını Sırala', + 'books_sort_name' => 'İsme Göre Sırala', + 'books_sort_created' => 'Oluşturulma Tarihine Göre Sırala', + 'books_sort_updated' => 'Güncellenme Tarihine Göre Sırala', + 'books_sort_chapters_first' => 'Önce Bölümler', + 'books_sort_chapters_last' => 'En Son Bölümler', + 'books_sort_show_other' => 'Diğer Kitapları Göster', + 'books_sort_save' => 'Yeni Düzeni Kaydet', + + // Chapters + 'chapter' => 'Bölüm', + 'chapters' => 'Bölümler', + 'x_chapters' => ':count Bölüm|:count Bölümler', + 'chapters_popular' => 'Popüler Bölümler', + 'chapters_new' => 'Yeni Bölüm', + 'chapters_create' => 'Yeni Bölüm Oluştur', + 'chapters_delete' => 'Bölümü Sil', + 'chapters_delete_named' => ':chapterName Bölümünü Sil', + 'chapters_delete_explain' => 'Bu işlem \':chapterName\' kitabını silecek. Bütün sayfalar silinecek ve direkt olarak ana kitab eklenecektir.', + 'chapters_delete_confirm' => 'Bölümü silmek istediğinizden emin misiniz?', + 'chapters_edit' => 'Bölümü Güncelle', + 'chapters_edit_named' => ':chapterName Bölümünü Güncelle', + 'chapters_save' => 'Bölümü Kaydet', + 'chapters_move' => 'Bölümü Taşı', + 'chapters_move_named' => ':chapterName Bölümünü Taşı', + 'chapter_move_success' => 'Bölüm :bookName Kitabına Taşındı', + 'chapters_permissions' => 'Bölüm İzinleri', + 'chapters_empty' => 'Bu bölümde henüz bir sayfa yok.', + 'chapters_permissions_active' => 'Bölüm İzinleri Aktif', + 'chapters_permissions_success' => 'Bölüm İzinleri Güncellendi', + 'chapters_search_this' => 'Bu bölümü ara', + + // Pages + 'page' => 'Sayfa', + 'pages' => 'Sayfalar', + 'x_pages' => ':count Sayfa|:count Sayfalar', + 'pages_popular' => 'Popüler Sayfalar', + 'pages_new' => 'Yeni Sayfa', + 'pages_attachments' => 'Ekler', + 'pages_navigation' => 'Sayfa Navigasyonu', + 'pages_delete' => 'Sayfayı Sil', + 'pages_delete_named' => ':pageName Sayfasını Sil', + 'pages_delete_draft_named' => ':pageName Taslak Sayfasını Sil', + 'pages_delete_draft' => 'Taslak Sayfayı Sil', + 'pages_delete_success' => 'Sayfa silindi', + 'pages_delete_draft_success' => 'Taslak sayfa silindi', + 'pages_delete_confirm' => 'Bu sayfayı silmek istediğinizden emin misiniz?', + 'pages_delete_draft_confirm' => 'Bu taslak sayfayı silmek istediğinizden emin misiniz?', + 'pages_editing_named' => ':pageName Sayfası Düzenleniyor', + 'pages_edit_save_draft' => 'Taslağı Kaydet', + 'pages_edit_draft' => 'Taslak Sayfasını Düzenle', + 'pages_editing_draft' => 'Taslak Düzenleniyor', + 'pages_editing_page' => 'Sayfa Düzenleniyor', + 'pages_edit_draft_save_at' => 'Taslak kaydedildi ', + 'pages_edit_delete_draft' => 'Taslağı Sl', + 'pages_edit_discard_draft' => 'Taslağı Yoksay', + 'pages_edit_set_changelog' => 'Değişiklik Logunu Kaydet', + 'pages_edit_enter_changelog_desc' => 'Yaptığınız değişiklikler hakkında kısa bir bilgilendirme ekleyin', + 'pages_edit_enter_changelog' => 'Değişim Günlüğü Ekleyin', + 'pages_save' => 'Sayfayı Kaydet', + 'pages_title' => 'Sayfa Başlığı', + 'pages_name' => 'Sayfa İsmi', + 'pages_md_editor' => 'Editör', + 'pages_md_preview' => 'Önizleme', + 'pages_md_insert_image' => 'Görsel Ekle', + 'pages_md_insert_link' => 'Öge Linki Ekle', + 'pages_md_insert_drawing' => 'Çizim Ekle', + 'pages_not_in_chapter' => 'Sayfa Bu Bölümde Değil', + 'pages_move' => 'Sayfayı Taşı', + 'pages_move_success' => 'Sayfa ":parentName"\'a taşındı', + 'pages_copy' => 'Sayfayı Kopyala', + 'pages_copy_desination' => 'Kopyalanacak Hedef', + 'pages_copy_success' => 'Sayfa başarıyla kopyalandı', + 'pages_permissions' => 'Sayfa İzinleri', + 'pages_permissions_success' => 'Sayfa izinleri güncellendi', + 'pages_revision' => 'Revizyon', + 'pages_revisions' => 'Sayfa Revizyonları', + 'pages_revisions_named' => ':pageName için Sayfa Revizyonları', + 'pages_revision_named' => ':pageName için Sayfa Revizyonu', + 'pages_revisions_created_by' => 'Oluşturan', + 'pages_revisions_date' => 'Revizyon Tarihi', + 'pages_revisions_number' => '#', + 'pages_revisions_numbered' => 'Revizyon #:id', + 'pages_revisions_numbered_changes' => 'Revizyon #:id Değişiklikleri', + 'pages_revisions_changelog' => 'Değişim Günlüğü', + 'pages_revisions_changes' => 'Değişiklikler', + 'pages_revisions_current' => 'Mevcut Versiyon', + 'pages_revisions_preview' => 'Önizleme', + 'pages_revisions_restore' => 'Kurtar', + 'pages_revisions_none' => 'Bu sayfaya ait revizyon yok', + 'pages_copy_link' => 'Linki kopyala', + 'pages_edit_content_link' => 'İçeriği Düzenle', + 'pages_permissions_active' => 'Sayfa İzinleri Aktif', + 'pages_initial_revision' => 'İlk Yayın', + 'pages_initial_name' => 'Yeni Sayfa', + 'pages_editing_draft_notification' => 'Şu anda :timeDiff tarhinde kaydedilmiş olan taslağı düzenlemektesiniz.', + 'pages_draft_edited_notification' => 'Bu sayfa son girişinizden bu yana güncellendi. Değişiklikleri yoksayıp, kaydetmeden çıkmanız önerilir.', + 'pages_draft_edit_active' => [ + 'start_a' => ':count kullanıcı bu sayfayı düzenlemeye başladı', + 'start_b' => ':userName kullanıcısı bu sayfayı düzenlemeye başladı', + 'time_a' => 'sayfa son güncellendiğinden beri', + 'time_b' => 'son :minCount dakikada', + 'message' => ':start :time. Birbirinizin düzenlemelerinin çakışmamasına dikkat edin!', + ], + 'pages_draft_discarded' => 'Taslak yok sayıldı, editör mevcut sayfa içeriği ile güncellendi', + 'pages_specific' => 'Özel Sayfa', + + // Editor Sidebar + 'page_tags' => 'Sayfa Etiketleri', + 'chapter_tags' => 'Bölüm Etiketleri', + 'book_tags' => 'Kitap Etiketleri', + 'shelf_tags' => 'Kitaplık Etiketleri', + 'tag' => 'Etiket', + 'tags' => 'Etiketler', + 'tag_value' => 'Etiket İçeriği (Opsiyonel)', + 'tags_explain' => "İçeriğini daha iyi kategorize etmek için bazı etiketler ekle. Etiketlere değer atayarak daha derin bir organizasyon yapısına sahip olabilirsin.", + 'tags_add' => 'Başka etiket ekle', + 'attachments' => 'Ekler', + 'attachments_explain' => 'Sayfanızda göstermek için bazı dosyalar yükleyin veya bazı bağlantılar ekleyin. Bunlar sayfanın sidebarında görülebilir.', + 'attachments_explain_instant_save' => 'Burada yapılan değişiklikler anında kaydedilir.', + 'attachments_items' => 'Eklenmiş Ögeler', + 'attachments_upload' => 'Dosya Yükle', + 'attachments_link' => 'Link Ekle', + 'attachments_set_link' => 'Link Düzenle', + 'attachments_delete_confirm' => 'Eki gerçekten silmek istiyor musunuz?', + 'attachments_dropzone' => 'Dosyaları buraya sürükle veya eklemek için buraya tıkla', + 'attachments_no_files' => 'Hiç bir dosya yüklenmedi', + 'attachments_explain_link' => 'Eğer dosya yüklememeyi tercih ederseniz link ekleyebilirsiniz. Bu başka bir sayfaya veya buluttaki bir dosyanın linki olabilir.', + 'attachments_link_name' => 'Bağlantı Adı', + 'attachment_link' => 'Ek linki', + 'attachments_link_url' => 'Dosya linki', + 'attachments_link_url_hint' => 'Dosyanın veya sitenin url adres', + 'attach' => 'Ekle', + 'attachments_edit_file' => 'Dosyayı Düzenle', + 'attachments_edit_file_name' => 'Dosya Adı', + 'attachments_edit_drop_upload' => 'Dosyaları sürükle veya yüklemek için buraya tıkla', + 'attachments_order_updated' => 'Ek sırası güncellendi', + 'attachments_updated_success' => 'Ek detayları güncellendi', + 'attachments_deleted' => 'Ek silindi', + 'attachments_file_uploaded' => 'Dosya başarıyla yüklendi', + 'attachments_file_updated' => 'Dosya başarıyla güncellendi', + 'attachments_link_attached' => 'Link sayfaya başarıyla eklendi', + + // Profile View + 'profile_user_for_x' => 'Kullanıcı :time', + 'profile_created_content' => 'Oluşturulan İçerik', + 'profile_not_created_pages' => ':userName herhangi bir sayfa oluşturmadı', + 'profile_not_created_chapters' => ':userName herhangi bir bölüm oluşturmadı', + 'profile_not_created_books' => ':userName herhangi bir kitap oluşturmadı', + 'profile_not_created_shelves' => ':userName herhangi bir kitaplık oluşturmadı', + + // Comments + 'comment' => 'Yorum', + 'comments' => 'Yorumlar', + 'comment_add' => 'Yorum Ekle', + 'comment_placeholder' => 'Buraya yorum ekle', + 'comment_count' => '{0} Yorum Yok|{1} 1 Yorum|[2,*] :count Yorun', + 'comment_save' => 'Yorum Kaydet', + 'comment_saving' => 'Yorum kaydediliyor...', + 'comment_deleting' => 'Yorum siliniyor...', + 'comment_new' => 'Yeni Yorum', + 'comment_created' => 'yorum yaptı :createDiff', + 'comment_updated' => ':username tarafından :updateDiff önce güncellendi', + 'comment_deleted_success' => 'Yorum silindi', + 'comment_created_success' => 'Yorum eklendi', + 'comment_updated_success' => 'Yorum güncellendi', + 'comment_delete_confirm' => 'Bu yorumu silmek istediğinizden emin misiniz?', + 'comment_in_reply_to' => ':commentId yorumuna yanıt olarak', + + // Revision + 'revision_delete_confirm' => 'Bu revizyonu silmek istediğinizden emin misiniz?', + 'revision_restore_confirm' => 'Bu revizyonu yeniden yüklemek istediğinizden emin misiniz? Mevcut sayfa içeriği değiştirilecektir.', + 'revision_delete_success' => 'Revizyon silindi', + 'revision_cannot_delete_latest' => 'Son revizyon silinemez.' +]; \ No newline at end of file diff --git a/resources/lang/tr/errors.php b/resources/lang/tr/errors.php new file mode 100644 index 000000000..962057e73 --- /dev/null +++ b/resources/lang/tr/errors.php @@ -0,0 +1,84 @@ + 'Bu sayfaya erişme yetkiniz yok.', + 'permissionJson' => 'Bu işlemi yapmak için yetkiniz yo.', + + // Auth + 'error_user_exists_different_creds' => ':email adresi farklı kullanıcı bilgileri ile zaten kullanımda.', + 'email_already_confirmed' => 'E-mail halihazırda onaylanmış, giriş yapmayı dene.', + 'email_confirmation_invalid' => 'Bu doğrulama tokenı daha önce kullanılmış veya geçerli değil, lütfen tekrar kayıt olmayı deneyin.', + 'email_confirmation_expired' => 'Doğrulama token\'ının süresi geçmiş, yeni bir mail gönderildi.', + 'ldap_fail_anonymous' => 'Anonim LDAP girişi başarısız oldu', + 'ldap_fail_authed' => 'Verdiğiniz bilgiler ile LDAP girişi başarısız oldu.', + 'ldap_extension_not_installed' => 'LDAP PHP eklentisi yüklenmedi', + 'ldap_cannot_connect' => 'LDAP sunucusuna bağlanılamadı, ilk bağlantı başarısız oldu', + 'social_no_action_defined' => 'Bir aksiyon tanımlanmadı', + 'social_login_bad_response' => ":socialAccount girişi sırasında hata oluştu: \n:error", + 'social_account_in_use' => 'Bu :socialAccount zaten kullanımda, :socialAccount hesabıyla giriş yapmayı deneyin.', + 'social_account_email_in_use' => ':email adresi zaten kullanımda. Eğer zaten bir hesabınız varsa :socialAccount hesabınızı profil ayarları kısmından bağlayabilirsiniz.', + 'social_account_existing' => 'Bu :socialAccount zaten profilinize eklenmiş.', + 'social_account_already_used_existing' => 'Bu :socialAccount başka bir kullanıcı tarafından kullanılıyor.', + 'social_account_not_used' => 'Bu :socialAccount hesabı hiç bir kullanıcıya bağlı değil. Lütfen profil ayarlarına gidiniz ve bağlayınız. ', + 'social_account_register_instructions' => 'Hala bir hesabınız yoksa :socialAccount ile kayıt olabilirsiniz.', + 'social_driver_not_found' => 'Social driver bulunamadı', + 'social_driver_not_configured' => ':socialAccount ayarlarınız doğru bir şekilde ayarlanmadı.', + + // System + 'path_not_writable' => ':filePath dosya yolu yüklenemedi. Sunucuya yazılabilir olduğundan emin olun.', + 'cannot_get_image_from_url' => ':url\'den görsel alınamadı', + 'cannot_create_thumbs' => 'Sunucu küçük resimleri oluşturamadı. Lütfen GD PHP eklentisinin yüklü olduğundan emin olun.', + 'server_upload_limit' => 'Sunucu bu boyutta dosya yüklemenize izin vermiyor. Lütfen daha küçük boyutta dosya yüklemeyi deneyiniz.', + 'uploaded' => 'Sunucu bu boyutta dosya yüklemenize izin vermiyor. Lütfen daha küçük boyutta dosya yüklemeyi deneyiniz.', + 'image_upload_error' => 'Görsel yüklenirken bir hata oluştu', + 'image_upload_type_error' => 'Yüklemeye çalıştığınız dosya türü geçerli değildir', + 'file_upload_timeout' => 'Dosya yüklemesi zaman aşımına uğradı', + + // Attachments + 'attachment_page_mismatch' => 'Ek güncellemesi sırasında sayfa uyuşmazlığı yaşandı', + 'attachment_not_found' => 'Ek bulunamadı', + + // Pages + 'page_draft_autosave_fail' => 'Taslak kaydetme başarısız. Sayfanızı kaydetmeden önce internet bağlantınız olduğundan emin olun', + 'page_custom_home_deletion' => 'Bu sayfa anasayfa olarak ayarlandığı için silinemez', + + // Entities + 'entity_not_found' => 'Eleman bulunamadı', + 'bookshelf_not_found' => 'Kitaplık bulunamadı', + 'book_not_found' => 'Kitap bulunamadı', + 'page_not_found' => 'Sayfa bulunamadı', + 'chapter_not_found' => 'Bölüm bulunamadı', + 'selected_book_not_found' => 'Seçilen kitap bulunamadı', + 'selected_book_chapter_not_found' => 'Seçilen kitap veya bölüm bulunamadı', + 'guests_cannot_save_drafts' => 'Misafirler taslak kaydedemezler', + + // Users + 'users_cannot_delete_only_admin' => 'Tek olan yöneticiyi silemezsiniz', + 'users_cannot_delete_guest' => 'Misafir kullanıyıcıyı silemezsiniz', + + // Roles + 'role_cannot_be_edited' => 'Bu rol düzenlenemez', + 'role_system_cannot_be_deleted' => 'Bu bir yönetici rolüdür ve silinemez', + 'role_registration_default_cannot_delete' => 'Bu rol varsayılan yönetici rolü olarak atandığı için silinemez ', + 'role_cannot_remove_only_admin' => 'Bu kullanıcı yönetici rolü olan tek kullanıcı olduğu için silinemez. Bu kullanıcıyı silmek için önce başka bir kullanıcıya yönetici rolü atayın.', + + // Comments + 'comment_list' => 'Yorumlar yüklenirken bir hata oluştu.', + 'cannot_add_comment_to_draft' => 'Taslaklara yorum ekleyemezsiniz.', + 'comment_add' => 'Yorum eklerken/güncellerken bir hata olıuştu.', + 'comment_delete' => 'Yorum silinirken bir hata oluştu.', + 'empty_comment' => 'Boş bir yorum eklenemez.', + + // Error pages + '404_page_not_found' => 'Sayfa Bulunamadı', + 'sorry_page_not_found' => 'Üzgünüz, aradığınız sayfa bulunamıyor.', + 'return_home' => 'Anasayfaya dön', + 'error_occurred' => 'Bir Hata Oluştu', + 'app_down' => ':appName şu anda inaktif', + 'back_soon' => 'En kısa zamanda aktif hale gelecek.', + +]; diff --git a/resources/lang/tr/pagination.php b/resources/lang/tr/pagination.php new file mode 100644 index 000000000..8550a186b --- /dev/null +++ b/resources/lang/tr/pagination.php @@ -0,0 +1,12 @@ + '« Önceki', + 'next' => 'Sonraki »', + +]; diff --git a/resources/lang/tr/passwords.php b/resources/lang/tr/passwords.php new file mode 100644 index 000000000..42e9ef2c7 --- /dev/null +++ b/resources/lang/tr/passwords.php @@ -0,0 +1,15 @@ + 'Parolanız en az 6 karakterden oluşmalı ve doğrulama parolası ile eşleşmelidir. ', + 'user' => "Bu e-mail adresi ile ilişkilendirilmiş bir kullanıcı bulamadık.", + 'token' => 'Parola yenileme tokeni geçerli değil.', + 'sent' => 'Parola sıfırlanma bağlantısını e-mail adresinize gönderdik!', + 'reset' => 'Parolanız sıfırlandı!', + +]; diff --git a/resources/lang/tr/settings.php b/resources/lang/tr/settings.php new file mode 100755 index 000000000..cf20b0684 --- /dev/null +++ b/resources/lang/tr/settings.php @@ -0,0 +1,134 @@ + 'Ayarlar', + 'settings_save' => 'Ayarları Kaydet', + 'settings_save_success' => 'Ayarlar Kaydedildi', + + // App Settings + 'app_customization' => 'Özelleştirme', + 'app_features_security' => 'Özellikler & Güvenlik', + 'app_name' => 'Uygulama Adı', + 'app_name_desc' => 'Bu isim başlıkta ve sistem tarafında gönderilen tüm mesajlarda gösterilecektir.', + 'app_name_header' => 'İsmi başlıkta göster', + 'app_public_access' => 'Açık Erişim', + 'app_public_access_desc' => 'Bu özelliği aktif etmek giriş yapmamış misafir kullanıcıların sizin BookStack uygulamanıza erişmesini sağlar', + 'app_public_access_desc_guest' => 'Kayıtlı olmayan kullanıcılar için erişim yetkisi "Guest" kullanıcısı üzerinden düzenlenebilir.', + 'app_public_access_toggle' => 'Açık erişime izin ver', + 'app_public_viewing' => 'Herkese açık görüntülenmeye izin verilsin mi?', + 'app_secure_images' => 'Daha Yüksek Güvenlikli Görsel Yüklemeleri', + 'app_secure_images_toggle' => 'Daha yüksek güveblikli görsel yüklemelerine izin ver', + 'app_secure_images_desc' => 'Performans sebepleri nedeniyle bütün görseller halka açık. Bu opsiyon rastgele ve tahmin edilmesi zor dizileri görsel linklerinin önüne ekler. Dizin indexlerinin kapalı olduğundan emin olun.', + 'app_editor' => 'Sayfa Editörü', + 'app_editor_desc' => 'Sayfa düzenlemesi yapılırken hangi editörün kullanılacağını seçin.', + 'app_custom_html' => 'Özel HTML Head İçeriği', + 'app_custom_html_desc' => 'Buraya eklenecek olan içerik taginin en sonuna eklenecektir. Bu stilleri override ederken veya analytics eklerken faydalı bir kullanım şeklidir.', + 'app_custom_html_disabled_notice' => 'Yapılan hatalı değişikliklerin geriye alınabilmesi için bu sayfada özel HTML head içeriği kapalı.', + 'app_logo' => 'Uygulama Logosu', + 'app_logo_desc' => 'Bu görsel 43px yüksekliğinde olmalı.
Büyük görseller ölçeklenecektir.', + 'app_primary_color' => 'Uygulamanın Birincil Rengi', + 'app_primary_color_desc' => 'Bu bir hex değeri olmalıdır.
Varsayılan rengi seçmek için boş bırakın.', + 'app_homepage' => 'Uygulama Anasayfası', + 'app_homepage_desc' => 'Anasayfada görünmesi için bir view seçin. Sayfa izinleri seçili sayfalar için yok sayılacaktır.', + 'app_homepage_select' => 'Sayfa seçiniz', + 'app_disable_comments' => 'Yorumları Engelle', + 'app_disable_comments_toggle' => 'Yorumları engelle', + 'app_disable_comments_desc' => 'Yorumları uygulamadaki bütün sayfalar için engelle.
Mevcut yorumlar gösterilmeyecektir.', + + // Registration Settings + 'reg_settings' => 'Kayıt', + 'reg_enable' => 'Kaydolmaya İzin Ver', + 'reg_enable_toggle' => 'Kaydolmaya izin ver', + 'reg_enable_desc' => 'Kayıt olmaya izin verdiğinizde kullanıcılar kendilerini uygulamaya kaydedebilecekler. Kayıt olduktan sonra kendilerine varsayılan kullanıcı rolü atanacaktır.', + 'reg_default_role' => 'Kayıt olduktan sonra varsayılan kullanıcı rolü', + 'reg_email_confirmation' => 'Email Doğrulama', + 'reg_email_confirmation_toggle' => 'E-mail onayı gerektir', + 'reg_confirm_email_desc' => 'Eğer domain kısıtlaması kullanılıyorsa o zaman email doğrulaması gereklidir ve bu seçenek yok sayılacaktır.', + 'reg_confirm_restrict_domain' => 'Domain Kısıtlaması', + 'reg_confirm_restrict_domain_desc' => 'Kısıtlamak istediğiniz email domainlerini vigül ile ayırarak yazınız. Kullanıcılara uygulamaya erişmeden önce adreslerini doğrulamak için bir mail gönderilecektir.
Kullanıcılar başarıyla kaydolduktan sonra email adreslerini değiştiremeyeceklerdir.', + 'reg_confirm_restrict_domain_placeholder' => 'Hiçbir kısıtlama tanımlanmamış', + + // Maintenance settings + 'maint' => 'Bakım', + 'maint_image_cleanup' => 'Görsel Temizliği', + 'maint_image_cleanup_desc' => "Sayfaları ve revizyon içeriklerini tarayarak hangi gösel ve çizimlerin kullanımda olduğunu ve hangilerinin gereksiz olduğunu tespit eder. Bunu başlatmadan veritabanı ve görsellerin tam bir yedeğinin alındığından emin olun.", + 'maint_image_cleanup_ignore_revisions' => 'Revizyonlardaki görselleri yoksay', + 'maint_image_cleanup_run' => 'Temizliği Başlat', + 'maint_image_cleanup_warning' => ':count potansiyel kullanılmayan görsel bulundu. Bu görselleri silmek istediğinizden emin misiniz?', + 'maint_image_cleanup_success' => ':count potanisyel kullanılmayan görsel bulundu ve silindi!', + 'maint_image_cleanup_nothing_found' => 'Kullanılmayan görsel bulunamadı ve birşey silinmedi!', + + // Role Settings + 'roles' => 'Roller', + 'role_user_roles' => 'Kullanıcı Rolleri', + 'role_create' => 'Yeni Rol Oluştur', + 'role_create_success' => 'Rol Başarıyla Oluşturuldu', + 'role_delete' => 'Rolü Sil', + 'role_delete_confirm' => 'Bu işlem \':roleName\' rolünü silecektir.', + 'role_delete_users_assigned' => 'Bu role atanmış :userCount adet kullanıcı var. Eğer bu kullanıcıların rollerini değiştirmek istiyorsanız aşağıdan yeni bir rol seçin.', + 'role_delete_no_migration' => "Kullanıcıları taşıma", + 'role_delete_sure' => 'Bu rolü silmek istediğinizden emin misiniz?', + 'role_delete_success' => 'Rol başarıyla silindi', + 'role_edit' => 'Rolü Düzenle', + 'role_details' => 'Rol Detayları', + 'role_name' => 'Rol Adı', + 'role_desc' => 'Rolün Kısa Tanımı', + 'role_external_auth_id' => 'Harici Authentication ID\'leri', + 'role_system' => 'Sistem Yetkileri', + 'role_manage_users' => 'Kullanıcıları yönet', + 'role_manage_roles' => 'Rolleri ve rol izinlerini yönet', + 'role_manage_entity_permissions' => 'Bütün kitap, bölüm ve sayfa izinlerini yönet', + 'role_manage_own_entity_permissions' => 'Sahip olunan kitap, bölüm ve sayfaların izinlerini yönet', + 'role_manage_settings' => 'Uygulama ayarlarını yönet', + 'role_asset' => 'Asset Yetkileri', + 'role_asset_desc' => 'Bu izinleri assetlere sistem içinden varsayılan erişimi kontrol eder. Kitaplar, bölümler ve sayfaların izinleri bu izinleri override eder.', + 'role_asset_admins' => 'Yöneticilere otomatik olarak bütün içeriğe erişim yetkisi verilir fakat bu opsiyonlar UI özelliklerini gösterir veya gizler.', + 'role_all' => 'Hepsi', + 'role_own' => 'Sahip Olunan', + 'role_controlled_by_asset' => 'Yükledikleri asset tarafından kontrol ediliyor', + 'role_save' => 'Rolü Kaydet', + 'role_update_success' => 'Rol başarıyla güncellendi', + 'role_users' => 'Bu roldeki kullanıcılar', + 'role_users_none' => 'Bu role henüz bir kullanıcı atanmadı', + + // Users + 'users' => 'Kullanıcılar', + 'user_profile' => 'Kullanıcı Profili', + 'users_add_new' => 'Yeni Kullanıcı Ekle', + 'users_search' => 'Kullanıcıları Ara', + 'users_details' => 'Kullanıcı Detayları', + 'users_details_desc' => 'Bu kullanıcı için gösterilecek bir isim ve mail adresi belirleyin. Bu e-mail adresi kullanıcı tarafından giriş yaparken kullanılacak.', + 'users_details_desc_no_email' => 'Diğer kullanıcılar tarafından tanınabilmesi için bir isim belirleyin.', + 'users_role' => 'Kullanıcı Rolleri', + 'users_role_desc' => 'Bu kullanıcının hangi rollere atanabileceğini belirleyin. Eğer bir kullanıcıya birden fazla rol atanırsa, kullanıcı bütün rollerin özelliklerini kullanabilir.', + 'users_password' => 'Kullanıcı Parolası', + 'users_password_desc' => 'Kullanıcının giriş yaparken kullanacağı bir parola belirleyin. Parola en az 5 karakter olmalıdır.', + 'users_external_auth_id' => 'Harici Authentication ID\'si', + 'users_external_auth_id_desc' => 'Bu ID kullanıcı LDAP sunucu ile bağlantı kurarken kullanılır.', + 'users_password_warning' => 'Sadece parolanızı değiştirmek istiyorsanız aşağıyı doldurunuz.', + 'users_system_public' => 'Bu kullanıcı sizin uygulamanızı ziyaret eden bütün misafir kullanıcıları temsil eder. Giriş yapmak için kullanılamaz, otomatik olarak atanır.', + 'users_delete' => 'Kullanıcı Sil', + 'users_delete_named' => ':userName kullanıcısını sil ', + 'users_delete_warning' => 'Bu işlem \':userName\' kullanıcısını sistemden tamamen silecektir.', + 'users_delete_confirm' => 'Bu kullanıcıyı tamamen silmek istediğinize emin misiniz?', + 'users_delete_success' => 'Kullanıcılar başarıyla silindi.', + 'users_edit' => 'Kullanıcıyı Güncelle', + 'users_edit_profile' => 'Profili Düzenle', + 'users_edit_success' => 'Kullanıcı başarıyla güncellendi', + 'users_avatar' => 'Kullanıcı Avatarı', + 'users_avatar_desc' => 'Bu kullanıcıyı temsil eden bir görsel seçin. Yaklaşık 256px kare olmalıdır.', + 'users_preferred_language' => 'Tercih Edilen Dil', + 'users_preferred_language_desc' => 'Bu seçenek kullanıcı arayüzünün dilini değiştirecektir. Herhangi bir kullanıcı içeriğini etkilemeyecektir.', + 'users_social_accounts' => 'Sosyal Hesaplar', + 'users_social_accounts_info' => 'Burada diğer hesaplarınızı ekleyerek daha hızlı ve kolay giriş sağlayabilirsiniz. Bir hesabın bağlantısını kesmek daha önce edilnilen erişiminizi kaldırmaz. Profil ayarlarınızdan bağlı sosyal hesabınızın erişimini kaldırınız.', + 'users_social_connect' => 'Hesap Bağla', + 'users_social_disconnect' => 'Hesabın Bağlantısını Kes', + 'users_social_connected' => ':socialAccount hesabı profilinize başarıyla bağlandı.', + 'users_social_disconnected' => ':socialAccount hesabınızın profilinizle ilişiği başarıyla kesildi.', +]; diff --git a/resources/lang/tr/validation.php b/resources/lang/tr/validation.php new file mode 100644 index 000000000..e7683a024 --- /dev/null +++ b/resources/lang/tr/validation.php @@ -0,0 +1,85 @@ + ':attribute kabul edilmelidir.', + 'active_url' => ':attribute geçerli bir URL adresi değildir.', + 'after' => ':attribute :date tarihinden sonra bir tarih olmalıdır.', + 'alpha' => ':attribute sadece harflerden oluşabilir.', + 'alpha_dash' => ':attribute sadece harf, rakam ve tirelerden oluşabilir.', + 'alpha_num' => ':attribute sadece harf ve rakam oluşabilir.', + 'array' => ':attribute array olmalıdır..', + 'before' => ':attribute :date tarihinden önce bir tarih olmalıdır.', + 'between' => [ + 'numeric' => ':attribute, :min ve :max değerleri arasında olmalıdır.', + 'file' => ':attribute, :min ve :max kilobyte boyutları arasında olmalıdır.', + 'string' => ':attribute, :min ve :max karakter arasında olmalıdır.', + 'array' => ':attribute :min ve :max öge arasında olmalıdır.', + ], + 'boolean' => ':attribute true veya false olmalıdır.', + 'confirmed' => ':attribute doğrulaması eşleşmiyor.', + 'date' => ':attribute geçerli bir tarih değil.', + 'date_format' => ':attribute formatı :format\'ına uymuyor.', + 'different' => ':attribute be :other birbirinden farklı olmalıdır.', + 'digits' => ':attribute :digits basamaklı olmalıdır.', + 'digits_between' => ':attribute :min ve :max basamaklı olmalıdır.', + 'email' => ':attribute geçerli bir e-mail adresi olmalıdır.', + 'filled' => ':attribute gerekli bir alandır.', + 'exists' => 'Seçilen :attribute geçerli bir alan değildir.', + 'image' => ':attribute bir görsel olmalıdır.', + 'image_extension' => ':attribute geçerli ve desteklenen bir görsel uzantısı değildir.', + 'in' => 'Seçilen :attribute geçerli değildir.', + 'integer' => ':attribute bir integer değeri olmalıdır.', + 'ip' => ':attribute geçerli bir IP adresi olmalıdır.', + 'max' => [ + 'numeric' => ':attribute, :max değerinden büyük olmamalıdır.', + 'file' => ':attribute, :max kilobyte boyutundan büyük olmamalıdır.', + 'string' => ':attribute, :max karakter boyutundan büyük olmamalıdır.', + 'array' => ':attribute, en fazla :max öge içermelidir.', + ], + 'mimes' => ':attribute :values dosya tipinde olmalıdır.', + 'min' => [ + 'numeric' => ':attribute, :min değerinden az olmamalıdır.', + 'file' => ':attribute, :min kilobyte boyutundan küçük olmamalıdır.', + 'string' => ':attribute, :min karakter boyutundan küçük olmamalıdır.', + 'array' => ':attribute, en az :min öge içermelidir.', + ], + 'no_double_extension' => ':attribute sadece tek bir dosya tipinde olmalıdır.', + 'not_in' => 'Seçili :attribute geçerli değildir.', + 'numeric' => ':attribute rakam olmalıdır.', + 'regex' => ':attribute formatı geçerli değildir.', + 'required' => 'The :attribute field is required. :attribute alanı gereklidir.', + 'required_if' => ':other alanı :value değerinde ise :attribute alanı gereklidir.', + 'required_with' => 'Eğer :values değeri geçerli ise :attribute alanı gereklidir.', + 'required_with_all' => 'Eğer :values değeri geçerli ise :attribute alanı gereklidir. ', + 'required_without' => 'Eğer :values değeri geçerli değil ise :attribute alanı gereklidir.', + 'required_without_all' => 'Eğer :values değerlerinden hiçbiri geçerli değil ise :attribute alanı gereklidir.', + 'same' => ':attribute ve :other eşleşmelidir.', + 'size' => [ + 'numeric' => ':attribute, :size boyutunda olmalıdır.', + 'file' => ':attribute, :size kilobyte boyutunda olmalıdır.', + 'string' => ':attribute, :size karakter uzunluğunda olmalıdır.', + 'array' => ':attribute, :size sayıda öge içermelidir.', + ], + 'string' => ':attribute string olmalıdır.', + 'timezone' => ':attribute geçerli bir alan olmalıdır.', + 'unique' => ':attribute daha önce alınmış.', + 'url' => ':attribute formatı geçerli değil.', + 'uploaded' => 'Dosya yüklemesi başarısız oldu. Server bu boyutta dosyaları kabul etmiyor olabilir.', + + // Custom validation lines + 'custom' => [ + 'password-confirm' => [ + 'required_with' => 'Parola onayı gereklidir.', + ], + ], + + // Custom validation attributes + 'attributes' => [], +]; From 140298bd96778ded6af37401b83112256ac750bf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Sep 2019 23:58:40 +0100 Subject: [PATCH 017/257] Updated to Laravel 5.8 --- .travis.yml | 2 + app/Auth/Access/SocialAuthService.php | 16 +- app/Auth/Access/UserTokenService.php | 5 +- app/Auth/Permissions/PermissionsRepo.php | 3 +- app/Config/app.php | 2 + app/Config/auth.php | 1 + app/Config/broadcasting.php | 13 +- app/Config/database.php | 32 +- app/Config/queue.php | 32 +- app/Config/services.php | 22 - app/Entities/BreadcrumbsViewComposer.php | 5 +- app/Entities/SearchService.php | 5 +- .../Controllers/Auth/RegisterController.php | 5 +- .../Controllers/Auth/UserInviteController.php | 2 +- app/Http/Controllers/UserController.php | 3 +- app/Uploads/Attachment.php | 2 +- app/Uploads/AttachmentService.php | 5 +- app/Uploads/ImageService.php | 7 +- composer.json | 6 +- composer.lock | 717 ++++-------------- phpunit.xml | 56 +- public/svg/403.svg | 1 - public/svg/404.svg | 1 - public/svg/500.svg | 1 - public/svg/503.svg | 1 - resources/lang/ar/auth.php | 2 +- resources/lang/cs/auth.php | 2 +- resources/lang/de/auth.php | 2 +- resources/lang/en/auth.php | 2 +- resources/lang/en/passwords.php | 2 +- resources/lang/en/validation.php | 1 + resources/lang/es/auth.php | 2 +- resources/lang/es_AR/auth.php | 2 +- resources/lang/fr/auth.php | 2 +- resources/lang/hu/auth.php | 2 +- resources/lang/it/auth.php | 2 +- resources/lang/ja/auth.php | 2 +- resources/lang/kr/auth.php | 2 +- resources/lang/nl/auth.php | 2 +- resources/lang/pl/auth.php | 2 +- resources/lang/pt_BR/auth.php | 2 +- resources/lang/ru/auth.php | 2 +- resources/lang/sk/auth.php | 2 +- resources/lang/sv/auth.php | 2 +- resources/lang/uk/auth.php | 2 +- resources/lang/zh_CN/auth.php | 2 +- resources/lang/zh_TW/auth.php | 2 +- storage/framework/cache/data/.gitignore | 2 - tests/Auth/AuthTest.php | 2 +- tests/Auth/LdapTest.php | 2 +- tests/Auth/UserInviteTest.php | 11 +- tests/BrowserKitTest.php | 2 +- tests/Entity/BookShelfTest.php | 9 +- tests/Entity/CommentSettingTest.php | 2 +- tests/Entity/ExportTest.php | 3 +- tests/Entity/MarkdownTest.php | 2 +- tests/Entity/PageDraftTest.php | 2 +- tests/Entity/SortTest.php | 2 +- tests/LanguageTest.php | 2 +- tests/Permissions/RestrictionsTest.php | 2 +- tests/Permissions/RolesTest.php | 2 +- tests/Unit/ConfigTest.php | 5 +- tests/Unit/PageRepoTest.php | 2 +- tests/Uploads/ImageTest.php | 7 +- tests/UserProfileTest.php | 2 +- 65 files changed, 282 insertions(+), 767 deletions(-) delete mode 100644 public/svg/403.svg delete mode 100644 public/svg/404.svg delete mode 100644 public/svg/500.svg delete mode 100644 public/svg/503.svg delete mode 100755 storage/framework/cache/data/.gitignore diff --git a/.travis.yml b/.travis.yml index d066747db..8311f7d20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,5 +23,7 @@ before_script: - php artisan migrate --force -n --database=mysql_testing - php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing +script: vendor/bin/phpunit --configuration phpunit.xml + after_failure: - cat storage/logs/laravel.log diff --git a/app/Auth/Access/SocialAuthService.php b/app/Auth/Access/SocialAuthService.php index 0d46b9f88..9c8d1a81f 100644 --- a/app/Auth/Access/SocialAuthService.php +++ b/app/Auth/Access/SocialAuthService.php @@ -5,6 +5,7 @@ use BookStack\Auth\UserRepo; use BookStack\Exceptions\SocialDriverNotConfigured; use BookStack\Exceptions\SocialSignInAccountNotUsed; use BookStack\Exceptions\UserRegistrationException; +use Illuminate\Support\Str; use Laravel\Socialite\Contracts\Factory as Socialite; use Laravel\Socialite\Contracts\User as SocialUser; @@ -104,6 +105,7 @@ class SocialAuthService $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first(); $isLoggedIn = auth()->check(); $currentUser = user(); + $titleCaseDriver = Str::title($socialDriver); // When a user is not logged in and a matching SocialAccount exists, // Simply log the user into the application. @@ -117,26 +119,26 @@ class SocialAuthService if ($isLoggedIn && $socialAccount === null) { $this->fillSocialAccount($socialDriver, $socialUser); $currentUser->socialAccounts()->save($this->socialAccount); - session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => title_case($socialDriver)])); + session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver])); return redirect($currentUser->getEditUrl()); } // When a user is logged in and the social account exists and is already linked to the current user. if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) { - session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => title_case($socialDriver)])); + session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver])); return redirect($currentUser->getEditUrl()); } // When a user is logged in, A social account exists but the users do not match. if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) { - session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => title_case($socialDriver)])); + session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver])); return redirect($currentUser->getEditUrl()); } // Otherwise let the user know this social account is not used by anyone. - $message = trans('errors.social_account_not_used', ['socialAccount' => title_case($socialDriver)]); + $message = trans('errors.social_account_not_used', ['socialAccount' => $titleCaseDriver]); if (setting('registration-enabled')) { - $message .= trans('errors.social_account_register_instructions', ['socialAccount' => title_case($socialDriver)]); + $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]); } throw new SocialSignInAccountNotUsed($message, '/login'); @@ -157,7 +159,7 @@ class SocialAuthService abort(404, trans('errors.social_driver_not_found')); } if (!$this->checkDriverConfigured($driver)) { - throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)])); + throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => Str::title($socialDriver)])); } return $driver; @@ -244,7 +246,7 @@ class SocialAuthService public function detachSocialAccount($socialDriver) { user()->socialAccounts()->where('driver', '=', $socialDriver)->delete(); - session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)])); + session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => Str::title($socialDriver)])); return redirect(user()->getEditUrl()); } diff --git a/app/Auth/Access/UserTokenService.php b/app/Auth/Access/UserTokenService.php index 34f3b2851..09a2f761b 100644 --- a/app/Auth/Access/UserTokenService.php +++ b/app/Auth/Access/UserTokenService.php @@ -5,6 +5,7 @@ use BookStack\Exceptions\UserTokenExpiredException; use BookStack\Exceptions\UserTokenNotFoundException; use Carbon\Carbon; use Illuminate\Database\Connection as Database; +use Illuminate\Support\Str; use stdClass; class UserTokenService @@ -73,9 +74,9 @@ class UserTokenService */ protected function generateToken() : string { - $token = str_random(24); + $token = Str::random(24); while ($this->tokenExists($token)) { - $token = str_random(25); + $token = Str::random(25); } return $token; } diff --git a/app/Auth/Permissions/PermissionsRepo.php b/app/Auth/Permissions/PermissionsRepo.php index 18d5089be..e7840ff76 100644 --- a/app/Auth/Permissions/PermissionsRepo.php +++ b/app/Auth/Permissions/PermissionsRepo.php @@ -3,6 +3,7 @@ use BookStack\Auth\Permissions; use BookStack\Auth\Role; use BookStack\Exceptions\PermissionsException; +use Illuminate\Support\Str; class PermissionsRepo { @@ -66,7 +67,7 @@ class PermissionsRepo $role->name = str_replace(' ', '-', strtolower($roleData['display_name'])); // Prevent duplicate names while ($this->role->where('name', '=', $role->name)->count() > 0) { - $role->name .= strtolower(str_random(2)); + $role->name .= strtolower(Str::random(2)); } $role->save(); diff --git a/app/Config/app.php b/app/Config/app.php index d4ef8fe25..b4a1076a9 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -136,6 +136,7 @@ return [ // Laravel 'App' => Illuminate\Support\Facades\App::class, + 'Arr' => Illuminate\Support\Arr::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, 'Auth' => Illuminate\Support\Facades\Auth::class, 'Blade' => Illuminate\Support\Facades\Blade::class, @@ -165,6 +166,7 @@ return [ 'Schema' => Illuminate\Support\Facades\Schema::class, 'Session' => Illuminate\Support\Facades\Session::class, 'Storage' => Illuminate\Support\Facades\Storage::class, + 'Str' => Illuminate\Support\Str::class, 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, diff --git a/app/Config/auth.php b/app/Config/auth.php index 7bf1ae772..cd74c2739 100644 --- a/app/Config/auth.php +++ b/app/Config/auth.php @@ -36,6 +36,7 @@ return [ 'api' => [ 'driver' => 'token', 'provider' => 'users', + 'hash' => false, ], ], diff --git a/app/Config/broadcasting.php b/app/Config/broadcasting.php index 3d9eb78f9..7aaaa5693 100644 --- a/app/Config/broadcasting.php +++ b/app/Config/broadcasting.php @@ -24,9 +24,13 @@ return [ 'pusher' => [ 'driver' => 'pusher', - 'key' => env('PUSHER_KEY'), - 'secret' => env('PUSHER_SECRET'), + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'useTLS' => true, + ], ], 'redis' => [ @@ -38,6 +42,11 @@ return [ 'driver' => 'log', ], + 'null' => [ + 'driver' => 'null', + ], + + ], ]; diff --git a/app/Config/database.php b/app/Config/database.php index 82156bd9d..a98b46a2c 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -59,14 +59,9 @@ return [ // Many of those shown here are unsupported by BookStack. 'connections' => [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => storage_path('database.sqlite'), - 'prefix' => '', - ], - 'mysql' => [ 'driver' => 'mysql', + 'url' => env('DATABASE_URL'), 'host' => $mysql_host, 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), @@ -79,10 +74,14 @@ return [ 'prefix_indexes' => true, 'strict' => false, 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], ], 'mysql_testing' => [ 'driver' => 'mysql', + 'url' => env('TEST_DATABASE_URL'), 'host' => '127.0.0.1', 'database' => 'bookstack-test', 'username' => env('MYSQL_USER', 'bookstack-test'), @@ -94,27 +93,6 @@ return [ 'strict' => false, ], - 'pgsql' => [ - 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', - ], - - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - ], - ], // Migration Repository Table diff --git a/app/Config/queue.php b/app/Config/queue.php index f0dd24fd3..46f6962c5 100644 --- a/app/Config/queue.php +++ b/app/Config/queue.php @@ -17,6 +17,7 @@ return [ // Queue connection configuration 'connections' => [ + 'sync' => [ 'driver' => 'sync', ], @@ -25,38 +26,15 @@ return [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', - 'expire' => 60, - ], - - 'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => 'localhost', - 'queue' => 'default', - 'ttr' => 60, - ], - - 'sqs' => [ - 'driver' => 'sqs', - 'key' => 'your-public-key', - 'secret' => 'your-secret-key', - 'queue' => 'your-queue-url', - 'region' => 'us-east-1', - ], - - 'iron' => [ - 'driver' => 'iron', - 'host' => 'mq-aws-us-east-1.iron.io', - 'token' => 'your-token', - 'project' => 'your-project-id', - 'queue' => 'your-queue-name', - 'encrypt' => true, + 'retry_after' => 90, ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', - 'queue' => 'default', - 'expire' => 60, + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, ], ], diff --git a/app/Config/services.php b/app/Config/services.php index 569c0fb48..2136f8116 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -22,28 +22,6 @@ return [ // Callback URL for social authentication methods 'callback_url' => env('APP_URL', false), - 'mailgun' => [ - 'domain' => '', - 'secret' => '', - 'endpoint' => '', - ], - - 'ses' => [ - 'key' => '', - 'secret' => '', - 'region' => 'us-east-1', - ], - - 'stripe' => [ - 'model' => \BookStack\Auth\User::class, - 'key' => '', - 'secret' => '', - 'webhook' => [ - 'secret' => '', - 'tolerance' => 300, - ], - ], - 'github' => [ 'client_id' => env('GITHUB_APP_ID', false), 'client_secret' => env('GITHUB_APP_SECRET', false), diff --git a/app/Entities/BreadcrumbsViewComposer.php b/app/Entities/BreadcrumbsViewComposer.php index 97ddbc2dc..e46d54ec2 100644 --- a/app/Entities/BreadcrumbsViewComposer.php +++ b/app/Entities/BreadcrumbsViewComposer.php @@ -23,8 +23,9 @@ class BreadcrumbsViewComposer public function compose(View $view) { $crumbs = $view->getData()['crumbs']; - if (array_first($crumbs) instanceof Book) { - $shelf = $this->entityContextManager->getContextualShelfForBook(array_first($crumbs)); + $firstCrumb = $crumbs[0] ?? null; + if ($firstCrumb instanceof Book) { + $shelf = $this->entityContextManager->getContextualShelfForBook($firstCrumb); if ($shelf) { array_unshift($crumbs, $shelf); $view->with('crumbs', $crumbs); diff --git a/app/Entities/SearchService.php b/app/Entities/SearchService.php index 9e7cfdd0c..ee9b87786 100644 --- a/app/Entities/SearchService.php +++ b/app/Entities/SearchService.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Illuminate\Support\Str; class SearchService { @@ -210,7 +211,7 @@ class SearchService // Handle filters foreach ($terms['filters'] as $filterTerm => $filterValue) { - $functionName = camel_case('filter_' . $filterTerm); + $functionName = Str::camel('filter_' . $filterTerm); if (method_exists($this, $functionName)) { $this->$functionName($entitySelect, $entity, $filterValue); } @@ -514,7 +515,7 @@ class SearchService protected function filterSortBy(EloquentBuilder $query, Entity $model, $input) { - $functionName = camel_case('sort_by_' . $input); + $functionName = Str::camel('sort_by_' . $input); if (method_exists($this, $functionName)) { $this->$functionName($query, $model); } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 83bd307e4..3b9738835 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -19,6 +19,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Redirector; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Laravel\Socialite\Contracts\User as SocialUser; use Validator; @@ -78,7 +79,7 @@ class RegisterController extends Controller return Validator::make($data, [ 'name' => 'required|min:2|max:255', 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|min:6', + 'password' => 'required|min:8', ]); } @@ -262,7 +263,7 @@ class RegisterController extends Controller $userData = [ 'name' => $socialUser->getName(), 'email' => $socialUser->getEmail(), - 'password' => str_random(30) + 'password' => Str::random(30) ]; return $this->registerUser($userData, $socialAccount, $emailVerified); } diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 5d9373f45..cfeb69648 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -62,7 +62,7 @@ class UserInviteController extends Controller public function setPassword(string $token, Request $request) { $this->validate($request, [ - 'password' => 'required|min:6' + 'password' => 'required|min:8' ]); try { diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index c9d2560ba..156256cb9 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -8,6 +8,7 @@ use BookStack\Exceptions\UserUpdateException; use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Str; class UserController extends Controller { @@ -92,7 +93,7 @@ class UserController extends Controller $user = $this->user->fill($request->all()); if ($authMethod === 'standard') { - $user->password = bcrypt($request->get('password', str_random(32))); + $user->password = bcrypt($request->get('password', Str::random(32))); } elseif ($authMethod === 'ldap') { $user->external_auth_id = $request->get('external_auth_id'); } diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 8720d3c09..3f0b447df 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -13,7 +13,7 @@ class Attachment extends Ownable */ public function getFileName() { - if (str_contains($this->name, '.')) { + if (strpos($this->name, '.') !== false) { return $this->name; } return $this->name . '.' . $this->extension; diff --git a/app/Uploads/AttachmentService.php b/app/Uploads/AttachmentService.php index 6e875a1e7..ae4fb6e96 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -2,6 +2,7 @@ use BookStack\Exceptions\FileUploadException; use Exception; +use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachmentService extends UploadService @@ -185,9 +186,9 @@ class AttachmentService extends UploadService $storage = $this->getStorage(); $basePath = 'uploads/files/' . Date('Y-m-M') . '/'; - $uploadFileName = str_random(16) . '.' . $uploadedFile->getClientOriginalExtension(); + $uploadFileName = Str::random(16) . '.' . $uploadedFile->getClientOriginalExtension(); while ($storage->exists($basePath . $uploadFileName)) { - $uploadFileName = str_random(3) . $uploadFileName; + $uploadFileName = Str::random(3) . $uploadFileName; } $attachmentPath = $basePath . $uploadFileName; diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index 860230d00..e7668471b 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -7,6 +7,7 @@ use DB; use Exception; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Filesystem\Factory as FileSystem; +use Illuminate\Support\Str; use Intervention\Image\Exception\NotSupportedException; use Intervention\Image\ImageManager; use phpDocumentor\Reflection\Types\Integer; @@ -140,12 +141,12 @@ class ImageService extends UploadService $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m') . '/'; while ($storage->exists($imagePath . $imageName)) { - $imageName = str_random(3) . $imageName; + $imageName = Str::random(3) . $imageName; } $fullPath = $imagePath . $imageName; if ($secureUploads) { - $fullPath = $imagePath . str_random(16) . '-' . $imageName; + $fullPath = $imagePath . Str::random(16) . '-' . $imageName; } try { @@ -220,7 +221,7 @@ class ImageService extends UploadService $storage->put($thumbFilePath, $thumbData); $storage->setVisibility($thumbFilePath, 'public'); - $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72); + $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 60 * 72); return $this->getPublicUrl($thumbFilePath); } diff --git a/composer.json b/composer.json index 4741d4d7e..b8c3e5536 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "ext-mbstring": "*", "ext-gd": "*", "ext-curl": "*", - "laravel/framework": "5.7.*", + "laravel/framework": "5.8.*", "fideloper/proxy": "^4.0", "intervention/image": "^2.5", "laravel/socialite": "^4.2", @@ -35,8 +35,8 @@ "filp/whoops": "^2.0", "fzaninotto/faker": "^1.4", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0", - "nunomaduro/collision": "^2.0", + "phpunit/phpunit": "^7.5", + "nunomaduro/collision": "^3.0", "laravel/browser-kit-testing": "^4.2.1", "barryvdh/laravel-ide-helper": "^2.6.4", "barryvdh/laravel-debugbar": "^3.2.8", diff --git a/composer.lock b/composer.lock index b3838ecf8..bf072fef8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "a007281b1a87cb6fc78975c49f20b3e8", + "content-hash": "e1ffc91b76f0e5949245144507d6dddc", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.110.11", + "version": "3.112.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3f222649634fa039c59f58082e60159a6bb59bbf" + "reference": "1e21446c6780a3b9b5e4315bd6d4347d2c3381eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3f222649634fa039c59f58082e60159a6bb59bbf", - "reference": "3f222649634fa039c59f58082e60159a6bb59bbf", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1e21446c6780a3b9b5e4315bd6d4347d2c3381eb", + "reference": "1e21446c6780a3b9b5e4315bd6d4347d2c3381eb", "shasum": "" }, "require": { @@ -87,7 +87,7 @@ "s3", "sdk" ], - "time": "2019-09-06T18:21:14+00:00" + "time": "2019-09-12T18:09:53+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -1312,45 +1312,45 @@ }, { "name": "laravel/framework", - "version": "v5.7.28", + "version": "v5.8.35", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "8e69728f1c80a024588adbd24c65c4fcf9aa9192" + "reference": "5a9e4d241a8b815e16c9d2151e908992c38db197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/8e69728f1c80a024588adbd24c65c4fcf9aa9192", - "reference": "8e69728f1c80a024588adbd24c65c4fcf9aa9192", + "url": "https://api.github.com/repos/laravel/framework/zipball/5a9e4d241a8b815e16c9d2151e908992c38db197", + "reference": "5a9e4d241a8b815e16c9d2151e908992c38db197", "shasum": "" }, "require": { "doctrine/inflector": "^1.1", "dragonmantank/cron-expression": "^2.0", + "egulias/email-validator": "^2.0", "erusev/parsedown": "^1.7", + "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "laravel/nexmo-notification-channel": "^1.0", - "laravel/slack-notification-channel": "^1.0", "league/flysystem": "^1.0.8", "monolog/monolog": "^1.12", - "nesbot/carbon": "^1.26.3", + "nesbot/carbon": "^1.26.3 || ^2.0", "opis/closure": "^3.1", "php": "^7.1.3", "psr/container": "^1.0", "psr/simple-cache": "^1.0", "ramsey/uuid": "^3.7", "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^4.1", - "symfony/debug": "^4.1", - "symfony/finder": "^4.1", - "symfony/http-foundation": "^4.1", - "symfony/http-kernel": "^4.1", - "symfony/process": "^4.1", - "symfony/routing": "^4.1", - "symfony/var-dumper": "^4.1", + "symfony/console": "^4.2", + "symfony/debug": "^4.2", + "symfony/finder": "^4.2", + "symfony/http-foundation": "^4.2", + "symfony/http-kernel": "^4.2", + "symfony/process": "^4.2", + "symfony/routing": "^4.2", + "symfony/var-dumper": "^4.2", "tijsverkoyen/css-to-inline-styles": "^2.2.1", - "vlucas/phpdotenv": "^2.2" + "vlucas/phpdotenv": "^3.3" }, "conflict": { "tightenco/collect": "<5.5.33" @@ -1393,17 +1393,18 @@ "league/flysystem-cached-adapter": "^1.0", "mockery/mockery": "^1.0", "moontoast/math": "^1.1", - "orchestra/testbench-core": "3.7.*", - "pda/pheanstalk": "^3.0|^4.0", - "phpunit/phpunit": "^7.5", + "orchestra/testbench-core": "3.8.*", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^7.5|^8.0", "predis/predis": "^1.1.1", - "symfony/css-selector": "^4.1", - "symfony/dom-crawler": "^4.1", + "symfony/css-selector": "^4.2", + "symfony/dom-crawler": "^4.2", "true/punycode": "^2.1" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (^3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", "filp/whoops": "Required for friendly error pages in development (^2.1.4).", @@ -1416,17 +1417,18 @@ "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", "nexmo/client": "Required to use the Nexmo transport (^1.0).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^3.0|^4.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", "predis/predis": "Required to use the redis cache and queue drivers (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (^4.1).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (^4.1).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (^1.0)." + "symfony/css-selector": "Required to use some of the crawler integration testing tools (^4.2).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (^4.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.1).", + "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7-dev" + "dev-master": "5.8-dev" } }, "autoload": { @@ -1454,121 +1456,7 @@ "framework", "laravel" ], - "time": "2019-02-26T15:41:34+00:00" - }, - { - "name": "laravel/nexmo-notification-channel", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/nexmo-notification-channel.git", - "reference": "03edd42a55b306ff980c9950899d5a2b03260d48" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/nexmo-notification-channel/zipball/03edd42a55b306ff980c9950899d5a2b03260d48", - "reference": "03edd42a55b306ff980c9950899d5a2b03260d48", - "shasum": "" - }, - "require": { - "nexmo/client": "^1.0", - "php": "^7.1.3" - }, - "require-dev": { - "illuminate/notifications": "~5.7", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "laravel": { - "providers": [ - "Illuminate\\Notifications\\NexmoChannelServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Notifications\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Nexmo Notification Channel for laravel.", - "keywords": [ - "laravel", - "nexmo", - "notifications" - ], - "time": "2018-12-04T12:57:08+00:00" - }, - { - "name": "laravel/slack-notification-channel", - "version": "v1.0.3", - "source": { - "type": "git", - "url": "https://github.com/laravel/slack-notification-channel.git", - "reference": "6e164293b754a95f246faf50ab2bbea3e4923cc9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/6e164293b754a95f246faf50ab2bbea3e4923cc9", - "reference": "6e164293b754a95f246faf50ab2bbea3e4923cc9", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.0", - "php": "^7.1.3" - }, - "require-dev": { - "illuminate/notifications": "~5.7", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, - "laravel": { - "providers": [ - "Illuminate\\Notifications\\SlackChannelServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Notifications\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Slack Notification Channel for laravel.", - "keywords": [ - "laravel", - "notifications", - "slack" - ], - "time": "2018-12-12T13:12:06+00:00" + "time": "2019-09-03T16:44:30+00:00" }, { "name": "laravel/socialite", @@ -1634,61 +1522,6 @@ ], "time": "2019-09-03T15:27:17+00:00" }, - { - "name": "lcobucci/jwt", - "version": "3.3.1", - "source": { - "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "ext-openssl": "*", - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "mikey179/vfsstream": "~1.5", - "phpmd/phpmd": "~2.2", - "phpunit/php-invoker": "~1.1", - "phpunit/phpunit": "^5.7 || ^7.3", - "squizlabs/php_codesniffer": "~2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Lcobucci\\JWT\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Luís Otávio Cobucci Oblonczyk", - "role": "Developer", - "email": "lcobucci@gmail.com" - } - ], - "description": "A simple library to work with JSON Web Token and JSON Web Signature", - "keywords": [ - "JWS", - "jwt" - ], - "time": "2019-05-24T18:30:49+00:00" - }, { "name": "league/flysystem", "version": "1.0.55", @@ -2077,54 +1910,6 @@ ], "time": "2019-06-11T09:07:59+00:00" }, - { - "name": "nexmo/client", - "version": "1.8.1", - "source": { - "type": "git", - "url": "https://github.com/Nexmo/nexmo-php.git", - "reference": "182d41a02ebd3e4be147baea45458ccfe2f528c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nexmo/nexmo-php/zipball/182d41a02ebd3e4be147baea45458ccfe2f528c4", - "reference": "182d41a02ebd3e4be147baea45458ccfe2f528c4", - "shasum": "" - }, - "require": { - "lcobucci/jwt": "^3.2", - "php": ">=5.6", - "php-http/client-implementation": "^1.0", - "php-http/guzzle6-adapter": "^1.0", - "zendframework/zend-diactoros": "^1.8.4 || ^2.0" - }, - "require-dev": { - "estahn/phpunit-json-assertions": "^1.0.0", - "php-http/mock-client": "^0.3.0", - "phpunit/phpunit": "^5.7", - "squizlabs/php_codesniffer": "^3.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Nexmo\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tim Lytle", - "email": "tim@nexmo.com", - "homepage": "http://twitter.com/tjlytle", - "role": "Developer" - } - ], - "description": "PHP Client for using Nexmo's API.", - "time": "2019-05-13T20:27:43+00:00" - }, { "name": "opis/closure", "version": "3.4.0", @@ -2270,28 +2055,28 @@ }, { "name": "phenx/php-svg-lib", - "version": "v0.3.2", + "version": "v0.3.3", "source": { "type": "git", "url": "https://github.com/PhenX/php-svg-lib.git", - "reference": "ccc46ef6340d4b8a4a68047e68d8501ea961442c" + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/ccc46ef6340d4b8a4a68047e68d8501ea961442c", - "reference": "ccc46ef6340d4b8a4a68047e68d8501ea961442c", + "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", "shasum": "" }, "require": { - "sabberworm/php-css-parser": "8.1.*" + "sabberworm/php-css-parser": "^8.3" }, "require-dev": { - "phpunit/phpunit": "~5.0" + "phpunit/phpunit": "^5.5|^6.5" }, "type": "library", "autoload": { - "psr-0": { - "Svg\\": "src/" + "psr-4": { + "Svg\\": "src/Svg" } }, "notification-url": "https://packagist.org/downloads/", @@ -2306,173 +2091,57 @@ ], "description": "A library to read, parse and export to PDF SVG files.", "homepage": "https://github.com/PhenX/php-svg-lib", - "time": "2018-06-03T10:10:03+00:00" + "time": "2019-09-11T20:02:13+00:00" }, { - "name": "php-http/guzzle6-adapter", - "version": "v1.1.1", + "name": "phpoption/phpoption", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/php-http/guzzle6-adapter.git", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0", - "php": ">=5.5.0", - "php-http/httplug": "^1.0" - }, - "provide": { - "php-http/async-client-implementation": "1.0", - "php-http/client-implementation": "1.0" + "php": ">=5.3.0" }, "require-dev": { - "ext-curl": "*", - "php-http/adapter-integration-tests": "^0.4" + "phpunit/phpunit": "4.7.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { - "psr-4": { - "Http\\Adapter\\Guzzle6\\": "src/" + "psr-0": { + "PhpOption\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache2" ], "authors": [ { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "David de Boer", - "email": "david@ddeboer.nl" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "Guzzle 6 HTTP Adapter", - "homepage": "http://httplug.io", + "description": "Option Type for PHP", "keywords": [ - "Guzzle", - "http" + "language", + "option", + "php", + "type" ], - "time": "2016-05-10T06:13:32+00:00" - }, - { - "name": "php-http/httplug", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/httplug.git", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "php-http/promise": "^1.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric GELOEN", - "email": "geloen.eric@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "HTTPlug, the HTTP client abstraction for PHP", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "http" - ], - "time": "2016-08-31T08:30:17+00:00" - }, - { - "name": "php-http/promise", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/promise.git", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", - "shasum": "" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "Joel Wurtz", - "email": "joel.wurtz@gmail.com" - } - ], - "description": "Promise used for asynchronous HTTP requests", - "homepage": "http://httplug.io", - "keywords": [ - "promise" - ], - "time": "2016-01-26T13:27:02+00:00" + "time": "2015-07-25T16:39:46+00:00" }, { "name": "predis/predis", @@ -2573,58 +2242,6 @@ ], "time": "2017-02-14T16:28:37+00:00" }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "time": "2019-04-30T12:38:16+00:00" - }, { "name": "psr/http-message", "version": "1.0.1", @@ -2894,23 +2511,24 @@ }, { "name": "sabberworm/php-css-parser", - "version": "8.1.0", + "version": "8.3.0", "source": { "type": "git", "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef" + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/850cbbcbe7fbb155387a151ea562897a67e242ef", - "reference": "850cbbcbe7fbb155387a151ea562897a67e242ef", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", "shasum": "" }, "require": { "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "*" + "codacy/coverage": "^1.4", + "phpunit/phpunit": "~4.8" }, "type": "library", "autoload": { @@ -2934,7 +2552,7 @@ "parser", "stylesheet" ], - "time": "2016-07-19T19:14:21+00:00" + "time": "2019-02-22T07:42:52+00:00" }, { "name": "socialiteproviders/discord", @@ -3012,16 +2630,16 @@ }, { "name": "socialiteproviders/manager", - "version": "v3.4.1", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "e79a1abb21f153f4a46d1a60abc72cba82d55f35" + "reference": "e3e8e78b9a3060801cd008941a0894a0a0c479e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e79a1abb21f153f4a46d1a60abc72cba82d55f35", - "reference": "e79a1abb21f153f4a46d1a60abc72cba82d55f35", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e3e8e78b9a3060801cd008941a0894a0a0c479e1", + "reference": "e3e8e78b9a3060801cd008941a0894a0a0c479e1", "shasum": "" }, "require": { @@ -3065,7 +2683,7 @@ } ], "description": "Easily add new or override built-in providers in Laravel Socialite.", - "time": "2019-09-05T22:58:45+00:00" + "time": "2019-09-09T03:07:52+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -4636,29 +4254,30 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.6.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" + "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", - "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1bdf24f065975594f6a117f0f1f6cabf1333b156", + "reference": "1bdf24f065975594f6a117f0f1f6cabf1333b156", "shasum": "" }, "require": { - "php": ">=5.3.9", + "php": "^5.4 || ^7.0", + "phpoption/phpoption": "^1.5", "symfony/polyfill-ctype": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.0" + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.6-dev" } }, "autoload": { @@ -4671,10 +4290,15 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", - "homepage": "http://www.vancelucas.com" + "homepage": "https://vancelucas.com/" } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", @@ -4683,73 +4307,7 @@ "env", "environment" ], - "time": "2019-01-29T11:11:52+00:00" - }, - { - "name": "zendframework/zend-diactoros", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1", - "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1", - "shasum": "" - }, - "require": { - "php": "^7.1", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-dom": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.5.0", - "php-http/psr7-integration-tests": "dev-master", - "phpunit/phpunit": "^7.0.2", - "zendframework/zend-coding-standard": "~1.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev", - "dev-develop": "2.2.x-dev", - "dev-release-1.8": "1.8.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php" - ], - "psr-4": { - "Zend\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "keywords": [ - "http", - "psr", - "psr-7" - ], - "time": "2019-07-10T16:13:25+00:00" + "time": "2019-09-10T21:37:39+00:00" } ], "packages-dev": [ @@ -4823,28 +4381,28 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.6.4", + "version": "v2.6.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a" + "reference": "8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", - "reference": "16eb4f65ee0d51b1f1182d56ae28ee00a70ce75a", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3", + "reference": "8740a9a158d3dd5cfc706a9d4cc1bf7a518f99f3", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6", + "doctrine/dbal": "~2.3", "illuminate/console": "^5.5|^6", "illuminate/filesystem": "^5.5|^6", "illuminate/support": "^5.5|^6", "php": ">=7" }, "require-dev": { - "doctrine/dbal": "~2.3", "illuminate/config": "^5.5|^6", "illuminate/view": "^5.5|^6", "phpro/grumphp": "^0.14", @@ -4852,9 +4410,6 @@ "scrutinizer/ocular": "~1.1", "squizlabs/php_codesniffer": "^3" }, - "suggest": { - "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" - }, "type": "library", "extra": { "branch-alias": { @@ -4893,7 +4448,7 @@ "phpstorm", "sublime" ], - "time": "2019-09-03T17:51:13+00:00" + "time": "2019-09-08T09:56:38+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -5902,16 +5457,16 @@ }, { "name": "nunomaduro/collision", - "version": "v2.1.1", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63" + "reference": "af42d339fe2742295a54f6fdd42aaa6f8c4aca68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5feb0c0d92978ec7169232ce5d70d6da6b29f63", - "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/af42d339fe2742295a54f6fdd42aaa6f8c4aca68", + "reference": "af42d339fe2742295a54f6fdd42aaa6f8c4aca68", "shasum": "" }, "require": { @@ -5921,10 +5476,10 @@ "symfony/console": "~2.8|~3.3|~4.0" }, "require-dev": { - "laravel/framework": "5.7.*", + "laravel/framework": "5.8.*", "nunomaduro/larastan": "^0.3.0", - "phpstan/phpstan": "^0.10", - "phpunit/phpunit": "~7.3" + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "~8.0" }, "type": "library", "extra": { @@ -5962,7 +5517,7 @@ "php", "symfony" ], - "time": "2018-11-21T21:40:54+00:00" + "time": "2019-03-07T21:35:13+00:00" }, { "name": "phar-io/manifest", @@ -6068,35 +5623,33 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "~6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6118,30 +5671,30 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", "webmozart/assert": "^1.0" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", + "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.4" }, @@ -6169,41 +5722,40 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-04-30T17:48:53+00:00" + "time": "2019-09-12T14:27:41+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -6216,7 +5768,8 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" }, { "name": "phpspec/prophecy", diff --git a/phpunit.xml b/phpunit.xml index 1f88f897b..21f81e32c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,33 +19,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svg/403.svg b/public/svg/403.svg deleted file mode 100644 index 682aa9827..000000000 --- a/public/svg/403.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/svg/404.svg b/public/svg/404.svg deleted file mode 100644 index b6cd6f237..000000000 --- a/public/svg/404.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/svg/500.svg b/public/svg/500.svg deleted file mode 100644 index 9927e8d75..000000000 --- a/public/svg/500.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/svg/503.svg b/public/svg/503.svg deleted file mode 100644 index 6ad109336..000000000 --- a/public/svg/503.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/lang/ar/auth.php b/resources/lang/ar/auth.php index bad0910a2..8c822a5a7 100644 --- a/resources/lang/ar/auth.php +++ b/resources/lang/ar/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'البريد الإلكتروني', 'password' => 'كلمة المرور', 'password_confirm' => 'تأكيد كلمة المرور', - 'password_hint' => 'يجب أن تكون أكثر من 5 حروف', + 'password_hint' => 'يجب أن تكون أكثر من 7 حروف', 'forgot_password' => 'نسيت كلمة المرور؟', 'remember_me' => 'تذكرني', 'ldap_email_hint' => 'الرجاء إدخال عنوان بريد إلكتروني لاستخدامه مع الحساب.', diff --git a/resources/lang/cs/auth.php b/resources/lang/cs/auth.php index 69d6f0b97..27cb33880 100644 --- a/resources/lang/cs/auth.php +++ b/resources/lang/cs/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'Email', 'password' => 'Heslo', 'password_confirm' => 'Potvrdit heslo', - 'password_hint' => 'Musí mít víc než 5 znaků', + 'password_hint' => 'Musí mít víc než 7 znaků', 'forgot_password' => 'Zapomněli jste heslo?', 'remember_me' => 'Neodhlašovat', 'ldap_email_hint' => 'Zadejte email, který chcete přiřadit k tomuto účtu.', diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php index 46d4070b8..b367fc63b 100644 --- a/resources/lang/de/auth.php +++ b/resources/lang/de/auth.php @@ -25,7 +25,7 @@ return [ 'email' => 'E-Mail', 'password' => 'Passwort', 'password_confirm' => 'Passwort bestätigen', - 'password_hint' => 'Mindestlänge: 5 Zeichen', + 'password_hint' => 'Mindestlänge: 7 Zeichen', 'forgot_password' => 'Passwort vergessen?', 'remember_me' => 'Angemeldet bleiben', 'ldap_email_hint' => 'Bitte geben Sie eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.', diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 37346097f..6961e049b 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'Email', 'password' => 'Password', 'password_confirm' => 'Confirm Password', - 'password_hint' => 'Must be over 5 characters', + 'password_hint' => 'Must be over 7 characters', 'forgot_password' => 'Forgot Password?', 'remember_me' => 'Remember Me', 'ldap_email_hint' => 'Please enter an email to use for this account.', diff --git a/resources/lang/en/passwords.php b/resources/lang/en/passwords.php index 9f7d9e3cb..f41ca7868 100644 --- a/resources/lang/en/passwords.php +++ b/resources/lang/en/passwords.php @@ -6,7 +6,7 @@ */ return [ - 'password' => 'Passwords must be at least six characters and match the confirmation.', + 'password' => 'Passwords must be at least eight characters and match the confirmation.', 'user' => "We can't find a user with that e-mail address.", 'token' => 'This password reset token is invalid.', 'sent' => 'We have e-mailed your password reset link!', diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 6f8fcb781..76b57a2a3 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -30,6 +30,7 @@ return [ 'digits' => 'The :attribute must be :digits digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.', 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values', 'filled' => 'The :attribute field is required.', 'gt' => [ 'numeric' => 'The :attribute must be greater than :value.', diff --git a/resources/lang/es/auth.php b/resources/lang/es/auth.php index c93751a10..5da1a2d44 100644 --- a/resources/lang/es/auth.php +++ b/resources/lang/es/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'Correo electrónico', 'password' => 'Contraseña', 'password_confirm' => 'Confirmar Contraseña', - 'password_hint' => 'Debe contener más de 5 caracteres', + 'password_hint' => 'Debe contener más de 7 caracteres', 'forgot_password' => '¿Contraseña Olvidada?', 'remember_me' => 'Recordarme', 'ldap_email_hint' => 'Por favor introduzca un mail para utilizar con esta cuenta.', diff --git a/resources/lang/es_AR/auth.php b/resources/lang/es_AR/auth.php index 4899b3d89..cd616e4bf 100644 --- a/resources/lang/es_AR/auth.php +++ b/resources/lang/es_AR/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Correo electrónico', 'password' => 'Contraseña', 'password_confirm' => 'Confirmar contraseña', - 'password_hint' => 'Debe contener al menos 5 caracteres', + 'password_hint' => 'Debe contener al menos 7 caracteres', 'forgot_password' => '¿Olvidó la contraseña?', 'remember_me' => 'Recordarme', 'ldap_email_hint' => 'Por favor introduzca un correo electrónico para utilizar con esta cuenta.', diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php index c9ce6a4d7..89908c8c4 100644 --- a/resources/lang/fr/auth.php +++ b/resources/lang/fr/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'E-mail', 'password' => 'Mot de passe', 'password_confirm' => 'Confirmez le mot de passe', - 'password_hint' => 'Doit faire plus de 5 caractères', + 'password_hint' => 'Doit faire plus de 7 caractères', 'forgot_password' => 'Mot de passe oublié ?', 'remember_me' => 'Se souvenir de moi', 'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte", diff --git a/resources/lang/hu/auth.php b/resources/lang/hu/auth.php index a1809b0de..d96509d05 100644 --- a/resources/lang/hu/auth.php +++ b/resources/lang/hu/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'Email', 'password' => 'Jelszó', 'password_confirm' => 'Jelszó megerősítése', - 'password_hint' => 'Öt karakternél hosszabbnak kell lennie', + 'password_hint' => 'Négy karakternél hosszabbnak kell lennie', 'forgot_password' => 'Elfelejtett jelszó?', 'remember_me' => 'Emlékezzen rám', 'ldap_email_hint' => 'A fiókhoz használt email cím megadása.', diff --git a/resources/lang/it/auth.php b/resources/lang/it/auth.php index 68fee41a5..59af1fe0d 100755 --- a/resources/lang/it/auth.php +++ b/resources/lang/it/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email', 'password' => 'Password', 'password_confirm' => 'Conferma Password', - 'password_hint' => 'Deve essere più di 5 caratteri', + 'password_hint' => 'Deve essere più di 7 caratteri', 'forgot_password' => 'Password dimenticata?', 'remember_me' => 'Ricordami', 'ldap_email_hint' => 'Inserisci un email per usare quest\'account.', diff --git a/resources/lang/ja/auth.php b/resources/lang/ja/auth.php index 4d5aee8b3..fdfac5f39 100644 --- a/resources/lang/ja/auth.php +++ b/resources/lang/ja/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'メールアドレス', 'password' => 'パスワード', 'password_confirm' => 'パスワード (確認)', - 'password_hint' => '5文字以上である必要があります', + 'password_hint' => '7文字以上である必要があります', 'forgot_password' => 'パスワードをお忘れですか?', 'remember_me' => 'ログイン情報を保存する', 'ldap_email_hint' => 'このアカウントで使用するEメールアドレスを入力してください。', diff --git a/resources/lang/kr/auth.php b/resources/lang/kr/auth.php index 671ddc654..36534f0d4 100644 --- a/resources/lang/kr/auth.php +++ b/resources/lang/kr/auth.php @@ -27,7 +27,7 @@ return [ 'email' => '이메일', 'password' => '비밀번호', 'password_confirm' => '비밀번호 (확인)', - 'password_hint' => '5자 이상이어야 합니다.', + 'password_hint' => '7자 이상이어야 합니다.', 'forgot_password' => '비밀번호를 잊으셨습니까?', 'remember_me' => '자동로그인', 'ldap_email_hint' => '이 계정에서 사용하는 이메일을 입력해 주세요.', diff --git a/resources/lang/nl/auth.php b/resources/lang/nl/auth.php index 31bd330cc..30dfdd78d 100644 --- a/resources/lang/nl/auth.php +++ b/resources/lang/nl/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email', 'password' => 'Wachtwoord', 'password_confirm' => 'Wachtwoord Bevestigen', - 'password_hint' => 'Minimaal 6 tekens', + 'password_hint' => 'Minimaal 8 tekens', 'forgot_password' => 'Wachtwoord vergeten?', 'remember_me' => 'Mij onthouden', 'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.', diff --git a/resources/lang/pl/auth.php b/resources/lang/pl/auth.php index 5cec651a9..40c458c61 100644 --- a/resources/lang/pl/auth.php +++ b/resources/lang/pl/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'E-mail', 'password' => 'Hasło', 'password_confirm' => 'Potwierdzenie hasła', - 'password_hint' => 'Musi mieć więcej niż 5 znaków', + 'password_hint' => 'Musi mieć więcej niż 7 znaków', 'forgot_password' => 'Zapomniałem hasła', 'remember_me' => 'Zapamiętaj mnie', 'ldap_email_hint' => 'Wprowadź adres e-mail dla tego konta.', diff --git a/resources/lang/pt_BR/auth.php b/resources/lang/pt_BR/auth.php index 20dc690af..79f743617 100644 --- a/resources/lang/pt_BR/auth.php +++ b/resources/lang/pt_BR/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'E-mail', 'password' => 'Senha', 'password_confirm' => 'Confirmar Senha', - 'password_hint' => 'Senha deverá ser maior que 5 caracteres', + 'password_hint' => 'Senha deverá ser maior que 7 caracteres', 'forgot_password' => 'Esqueceu a senha?', 'remember_me' => 'Lembrar de mim', 'ldap_email_hint' => 'Por favor, digite um e-mail para essa conta.', diff --git a/resources/lang/ru/auth.php b/resources/lang/ru/auth.php index 3a1fbbf97..c70e85d61 100644 --- a/resources/lang/ru/auth.php +++ b/resources/lang/ru/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email', 'password' => 'Пароль', 'password_confirm' => 'Подтверждение пароля', - 'password_hint' => 'Должен быть больше 5 символов', + 'password_hint' => 'Должен быть больше 7 символов', 'forgot_password' => 'Забыли пароль?', 'remember_me' => 'Запомнить меня', 'ldap_email_hint' => 'Введите email адрес для данной учетной записи.', diff --git a/resources/lang/sk/auth.php b/resources/lang/sk/auth.php index 2fa69ac3e..69004e87a 100644 --- a/resources/lang/sk/auth.php +++ b/resources/lang/sk/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email', 'password' => 'Heslo', 'password_confirm' => 'Potvrdiť heslo', - 'password_hint' => 'Musí mať viac ako 5 znakov', + 'password_hint' => 'Musí mať viac ako 7 znakov', 'forgot_password' => 'Zabudli ste heslo?', 'remember_me' => 'Zapamätať si ma', 'ldap_email_hint' => 'Zadajte prosím email, ktorý sa má použiť pre tento účet.', diff --git a/resources/lang/sv/auth.php b/resources/lang/sv/auth.php index 30e1a1937..4eb2be028 100644 --- a/resources/lang/sv/auth.php +++ b/resources/lang/sv/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'E-post', 'password' => 'Lösenord', 'password_confirm' => 'Bekräfta lösenord', - 'password_hint' => 'Måste vara fler än 5 tecken', + 'password_hint' => 'Måste vara fler än 7 tecken', 'forgot_password' => 'Glömt lösenord?', 'remember_me' => 'Kom ihåg mig', 'ldap_email_hint' => 'Vänligen ange en e-postadress att använda till kontot.', diff --git a/resources/lang/uk/auth.php b/resources/lang/uk/auth.php index cd73f92db..32b35a156 100644 --- a/resources/lang/uk/auth.php +++ b/resources/lang/uk/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'Email', 'password' => 'Пароль', 'password_confirm' => 'Підтвердження пароля', - 'password_hint' => 'Має бути більше 5 символів', + 'password_hint' => 'Має бути більше 7 символів', 'forgot_password' => 'Забули пароль?', 'remember_me' => 'Запам’ятати мене', 'ldap_email_hint' => 'Введіть email для цього облікового запису.', diff --git a/resources/lang/zh_CN/auth.php b/resources/lang/zh_CN/auth.php index 046f2360b..f4159826c 100644 --- a/resources/lang/zh_CN/auth.php +++ b/resources/lang/zh_CN/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email地址', 'password' => '密码', 'password_confirm' => '确认密码', - 'password_hint' => '必须超过5个字符', + 'password_hint' => '必须超过7个字符', 'forgot_password' => '忘记密码?', 'remember_me' => '记住我', 'ldap_email_hint' => '请输入用于此帐户的电子邮件。', diff --git a/resources/lang/zh_TW/auth.php b/resources/lang/zh_TW/auth.php index f44ac8af0..9e7fcd036 100644 --- a/resources/lang/zh_TW/auth.php +++ b/resources/lang/zh_TW/auth.php @@ -27,7 +27,7 @@ return [ 'email' => 'Email位址', 'password' => '密碼', 'password_confirm' => '確認密碼', - 'password_hint' => '必須超過5個字元', + 'password_hint' => '必須超過7個字元', 'forgot_password' => '忘記密碼?', 'remember_me' => '記住我', 'ldap_email_hint' => '請輸入用於此帳號的電子郵件。', diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore deleted file mode 100755 index d6b7ef32c..000000000 --- a/storage/framework/cache/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 42b44c152..eb83faded 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -81,7 +81,7 @@ class AuthTest extends BrowserKitTest ->press('Create Account') ->see('The name must be at least 2 characters.') ->see('The email must be a valid email address.') - ->see('The password must be at least 6 characters.') + ->see('The password must be at least 8 characters.') ->seePageIs('/register'); } diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index 5923ef377..fe28698df 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -15,7 +15,7 @@ class LdapTest extends BrowserKitTest protected $mockUser; protected $resourceId = 'resource-test'; - public function setUp() + public function setUp(): void { parent::setUp(); if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1); diff --git a/tests/Auth/UserInviteTest.php b/tests/Auth/UserInviteTest.php index 331262690..d200134a5 100644 --- a/tests/Auth/UserInviteTest.php +++ b/tests/Auth/UserInviteTest.php @@ -6,6 +6,7 @@ use BookStack\Auth\User; use BookStack\Notifications\UserInvite; use Carbon\Carbon; use DB; +use Illuminate\Support\Str; use Notification; class UserInviteTest extends TestCase @@ -68,11 +69,13 @@ class UserInviteTest extends TestCase $inviteService->sendInvitation($user); $token = DB::table('user_invites')->where('user_id', '=', $user->id)->first()->token; + $this->get('/register/invite/' . $token); $shortPassword = $this->followingRedirects()->post('/register/invite/' . $token, [ - 'password' => 'mypas', + 'password' => 'mypassw', ]); - $shortPassword->assertSee('The password must be at least 6 characters.'); + $shortPassword->assertSee('The password must be at least 8 characters.'); + $this->get('/register/invite/' . $token); $noPassword = $this->followingRedirects()->post('/register/invite/' . $token, [ 'password' => '', ]); @@ -85,10 +88,10 @@ class UserInviteTest extends TestCase public function test_non_existent_invite_token_redirects_to_home() { - $setPasswordPageResp = $this->get('/register/invite/' . str_random(12)); + $setPasswordPageResp = $this->get('/register/invite/' . Str::random(12)); $setPasswordPageResp->assertRedirect('/'); - $setPasswordResp = $this->post('/register/invite/' . str_random(12), ['password' => 'Password Test']); + $setPasswordResp = $this->post('/register/invite/' . Str::random(12), ['password' => 'Password Test']); $setPasswordResp->assertRedirect('/'); } diff --git a/tests/BrowserKitTest.php b/tests/BrowserKitTest.php index ab0d9d898..b81afe311 100644 --- a/tests/BrowserKitTest.php +++ b/tests/BrowserKitTest.php @@ -21,7 +21,7 @@ abstract class BrowserKitTest extends TestCase */ protected $baseUrl = 'http://localhost'; - public function tearDown() + public function tearDown() : void { \DB::disconnect(); parent::tearDown(); diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index 158fb5ca1..5c7673847 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -4,6 +4,7 @@ use BookStack\Auth\Role; use BookStack\Auth\User; use BookStack\Entities\Book; use BookStack\Entities\Bookshelf; +use Illuminate\Support\Str; class BookShelfTest extends TestCase { @@ -55,8 +56,8 @@ class BookShelfTest extends TestCase { $booksToInclude = Book::take(2)->get(); $shelfInfo = [ - 'name' => 'My test book' . str_random(4), - 'description' => 'Test book description ' . str_random(10) + 'name' => 'My test book' . Str::random(4), + 'description' => 'Test book description ' . Str::random(10) ]; $resp = $this->asEditor()->post('/shelves', array_merge($shelfInfo, [ 'books' => $booksToInclude->implode('id', ','), @@ -120,8 +121,8 @@ class BookShelfTest extends TestCase $booksToInclude = Book::take(2)->get(); $shelfInfo = [ - 'name' => 'My test book' . str_random(4), - 'description' => 'Test book description ' . str_random(10) + 'name' => 'My test book' . Str::random(4), + 'description' => 'Test book description ' . Str::random(10) ]; $resp = $this->asEditor()->put($shelf->getUrl(), array_merge($shelfInfo, [ diff --git a/tests/Entity/CommentSettingTest.php b/tests/Entity/CommentSettingTest.php index 2683f57cb..967e550a7 100644 --- a/tests/Entity/CommentSettingTest.php +++ b/tests/Entity/CommentSettingTest.php @@ -3,7 +3,7 @@ class CommentSettingTest extends BrowserKitTest { protected $page; - public function setUp() { + public function setUp(): void { parent::setUp(); $this->page = \BookStack\Entities\Page::first(); } diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index e3a74f64d..9a2d32028 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -4,6 +4,7 @@ use BookStack\Entities\Chapter; use BookStack\Entities\Page; use BookStack\Uploads\HttpFetcher; +use Illuminate\Support\Str; class ExportTest extends TestCase { @@ -79,7 +80,7 @@ class ExportTest extends TestCase public function test_book_html_export_shows_chapter_descriptions() { - $chapterDesc = 'My custom test chapter description ' . str_random(12); + $chapterDesc = 'My custom test chapter description ' . Str::random(12); $chapter = Chapter::query()->first(); $chapter->description = $chapterDesc; $chapter->save(); diff --git a/tests/Entity/MarkdownTest.php b/tests/Entity/MarkdownTest.php index c481e444f..5d3af4f6e 100644 --- a/tests/Entity/MarkdownTest.php +++ b/tests/Entity/MarkdownTest.php @@ -4,7 +4,7 @@ class MarkdownTest extends BrowserKitTest { protected $page; - public function setUp() + public function setUp(): void { parent::setUp(); $this->page = \BookStack\Entities\Page::first(); diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index f15651f39..f29231c39 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -8,7 +8,7 @@ class PageDraftTest extends BrowserKitTest protected $page; protected $pageRepo; - public function setUp() + public function setUp(): void { parent::setUp(); $this->page = \BookStack\Entities\Page::first(); diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php index a3c20e84c..cad6d3c01 100644 --- a/tests/Entity/SortTest.php +++ b/tests/Entity/SortTest.php @@ -10,7 +10,7 @@ class SortTest extends TestCase { protected $book; - public function setUp() + public function setUp(): void { parent::setUp(); $this->book = Book::first(); diff --git a/tests/LanguageTest.php b/tests/LanguageTest.php index d9b8655ee..c8bc44451 100644 --- a/tests/LanguageTest.php +++ b/tests/LanguageTest.php @@ -8,7 +8,7 @@ class LanguageTest extends TestCase /** * LanguageTest constructor. */ - public function setUp() + public function setUp(): void { parent::setUp(); $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php', 'format.php']); diff --git a/tests/Permissions/RestrictionsTest.php b/tests/Permissions/RestrictionsTest.php index a7f681a37..f6e07c0f1 100644 --- a/tests/Permissions/RestrictionsTest.php +++ b/tests/Permissions/RestrictionsTest.php @@ -21,7 +21,7 @@ class RestrictionsTest extends BrowserKitTest */ protected $viewer; - public function setUp() + public function setUp(): void { parent::setUp(); $this->user = $this->getEditor(); diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index a1f193643..371cffc0f 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -11,7 +11,7 @@ class RolesTest extends BrowserKitTest { protected $user; - public function setUp() + public function setUp(): void { parent::setUp(); $this->user = $this->getViewer(); diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 967915af9..ec9967227 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -50,11 +50,14 @@ class ConfigTest extends TestCase protected function checkEnvConfigResult(string $envName, $envVal, string $configKey, string $expectedResult) { $originalVal = getenv($envName); + $envString = $envName . (is_null($envVal) ? '' : '=') . ($envVal ?? ''); putenv($envString); $this->refreshApplication(); $this->assertEquals($expectedResult, config($configKey)); - putenv($envString = $envName . (empty($originalVal) ? '' : '=') . ($originalVal ?? '')); + + $envString = $envName . (empty($originalVal) ? '' : '=') . ($originalVal ?? ''); + putenv($envString); } } \ No newline at end of file diff --git a/tests/Unit/PageRepoTest.php b/tests/Unit/PageRepoTest.php index 41e7c2f78..c5e094b63 100644 --- a/tests/Unit/PageRepoTest.php +++ b/tests/Unit/PageRepoTest.php @@ -10,7 +10,7 @@ class PageRepoTest extends TestCase */ protected $pageRepo; - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->pageRepo = app()->make(PageRepo::class); diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index f92653378..4d3e8a498 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -4,6 +4,7 @@ use BookStack\Entities\Repos\PageRepo; use BookStack\Uploads\Image; use BookStack\Entities\Page; use BookStack\Uploads\ImageService; +use Illuminate\Support\Str; use Tests\TestCase; class ImageTest extends TestCase @@ -43,7 +44,7 @@ class ImageTest extends TestCase $imgDetails = $this->uploadGalleryImage(); $image = Image::query()->first(); - $newName = str_random(); + $newName = Str::random(); $update = $this->put('/images/' . $image->id, ['name' => $newName]); $update->assertSuccessful(); $update->assertJson([ @@ -89,7 +90,7 @@ class ImageTest extends TestCase $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}"); $searchHitRequest->assertSuccessful()->assertJson($resultJson); - $namePartial = str_random(16); + $namePartial = Str::random(16); $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}"); $searchHitRequest->assertSuccessful()->assertExactJson($emptyJson); } @@ -208,7 +209,7 @@ class ImageTest extends TestCase $encodedImageContent = base64_encode(file_get_contents($expectedPath)); $export = $this->get($page->getUrl('/export/html')); - $this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content'); + $this->assertTrue(strpos($export->getContent(), $encodedImageContent) !== false, 'Uploaded image in export content'); if (file_exists($expectedPath)) { unlink($expectedPath); diff --git a/tests/UserProfileTest.php b/tests/UserProfileTest.php index a7c7505a8..fc1a529ae 100644 --- a/tests/UserProfileTest.php +++ b/tests/UserProfileTest.php @@ -4,7 +4,7 @@ class UserProfileTest extends BrowserKitTest { protected $user; - public function setUp() + public function setUp(): void { parent::setUp(); $this->user = \BookStack\Auth\User::all()->last(); From cbf9d701af4e510be51fd1f5d2fd8a179534a523 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 14 Sep 2019 14:12:39 +0100 Subject: [PATCH 018/257] Updated to laravel 6 --- .travis.yml | 2 +- app/Config/logging.php | 6 + app/Http/Kernel.php | 1 + composer.json | 45 +- composer.lock | 698 +++++++++++------- database/factories/ModelFactory.php | 12 +- phpunit.xml | 3 +- resources/views/auth/login.blade.php | 4 +- resources/views/auth/register.blade.php | 2 +- .../views/partials/breadcrumbs.blade.php | 4 +- tests/Auth/SocialAuthTest.php | 2 +- tests/Entity/PageContentTest.php | 2 +- tests/SharedTestHelpers.php | 59 +- tests/Unit/ConfigTest.php | 31 +- tests/Unit/PageRepoTest.php | 14 +- tests/Unit/UrlTest.php | 14 +- tests/Uploads/AttachmentTest.php | 2 +- 17 files changed, 573 insertions(+), 328 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8311f7d20..10a5d1e6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ dist: bionic language: php php: - - '7.1.3' - '7.2' + - '7.3' services: - mysql diff --git a/app/Config/logging.php b/app/Config/logging.php index c952ac02f..9c3218a01 100644 --- a/app/Config/logging.php +++ b/app/Config/logging.php @@ -1,5 +1,6 @@ 'errorlog', 'level' => 'debug', ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], ], ]; \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 67e01cd04..004f30139 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -29,6 +29,7 @@ class Kernel extends HttpKernel \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Illuminate\Routing\Middleware\ThrottleRequests::class, \BookStack\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \BookStack\Http\Middleware\Localization::class diff --git a/composer.json b/composer.json index b8c3e5536..d9d115017 100644 --- a/composer.json +++ b/composer.json @@ -5,39 +5,38 @@ "license": "MIT", "type": "project", "require": { - "php": "^7.1.3", - "ext-json": "*", - "ext-tidy": "*", - "ext-dom": "*", - "ext-xml": "*", - "ext-mbstring": "*", - "ext-gd": "*", + "php": "^7.2", "ext-curl": "*", - "laravel/framework": "5.8.*", - "fideloper/proxy": "^4.0", - "intervention/image": "^2.5", - "laravel/socialite": "^4.2", - "league/flysystem-aws-s3-v3": "^1.0", + "ext-dom": "*", + "ext-gd": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-tidy": "*", + "ext-xml": "*", "barryvdh/laravel-dompdf": "^0.8.5", "barryvdh/laravel-snappy": "^0.4.5", - "predis/predis": "^1.1", + "doctrine/dbal": "^2.9", + "fideloper/proxy": "^4.0", "gathercontent/htmldiff": "^0.2.1", - "socialiteproviders/slack": "^3.0", + "intervention/image": "^2.5", + "laravel/framework": "^6.0", + "laravel/socialite": "^4.2", + "league/flysystem-aws-s3-v3": "^1.0", + "predis/predis": "^1.1", + "socialiteproviders/discord": "^2.0", + "socialiteproviders/gitlab": "^3.0", "socialiteproviders/microsoft-azure": "^3.0", "socialiteproviders/okta": "^1.0", - "socialiteproviders/gitlab": "^3.0", - "socialiteproviders/twitch": "^3.0", - "socialiteproviders/discord": "^2.0", - "doctrine/dbal": "^2.9" + "socialiteproviders/slack": "^3.0", + "socialiteproviders/twitch": "^3.0" }, "require-dev": { - "beyondcode/laravel-dump-server": "^1.0", - "filp/whoops": "^2.0", + "facade/ignition": "^1.4", "fzaninotto/faker": "^1.4", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5", + "phpunit/phpunit": "^8.0", "nunomaduro/collision": "^3.0", - "laravel/browser-kit-testing": "^4.2.1", + "laravel/browser-kit-testing": "^5.1", "barryvdh/laravel-ide-helper": "^2.6.4", "barryvdh/laravel-debugbar": "^3.2.8", "squizlabs/php_codesniffer": "^3.4" @@ -89,7 +88,7 @@ "preferred-install": "dist", "sort-packages": true, "platform": { - "php": "7.1.3" + "php": "7.2.0" } }, "extra": { diff --git a/composer.lock b/composer.lock index bf072fef8..5c3cb66b1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "e1ffc91b76f0e5949245144507d6dddc", + "content-hash": "322b6b585d85e066f97711fe8f1f7ccd", "packages": [ { "name": "aws/aws-sdk-php", @@ -557,28 +557,30 @@ }, { "name": "doctrine/lexer", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^4.5" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -591,14 +593,14 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" @@ -613,7 +615,7 @@ "parser", "php" ], - "time": "2019-06-08T11:03:04+00:00" + "time": "2019-07-30T19:33:28+00:00" }, { "name": "dompdf/dompdf", @@ -1265,90 +1267,45 @@ ], "time": "2018-12-14T14:59:37+00:00" }, - { - "name": "kylekatarnls/update-helper", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/kylekatarnls/update-helper.git", - "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kylekatarnls/update-helper/zipball/5786fa188e0361b9adf9e8199d7280d1b2db165e", - "reference": "5786fa188e0361b9adf9e8199d7280d1b2db165e", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0.0", - "php": ">=5.3.0" - }, - "require-dev": { - "codeclimate/php-test-reporter": "dev-master", - "composer/composer": "2.0.x-dev || ^2.0.0-dev", - "phpunit/phpunit": ">=4.8.35 <6.0" - }, - "type": "composer-plugin", - "extra": { - "class": "UpdateHelper\\ComposerPlugin" - }, - "autoload": { - "psr-0": { - "UpdateHelper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle", - "email": "kylekatarnls@gmail.com" - } - ], - "description": "Update helper", - "time": "2019-07-29T11:03:54+00:00" - }, { "name": "laravel/framework", - "version": "v5.8.35", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "5a9e4d241a8b815e16c9d2151e908992c38db197" + "reference": "56789e9dec750e0fbe8e9e6ae90a01a4e6887902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/5a9e4d241a8b815e16c9d2151e908992c38db197", - "reference": "5a9e4d241a8b815e16c9d2151e908992c38db197", + "url": "https://api.github.com/repos/laravel/framework/zipball/56789e9dec750e0fbe8e9e6ae90a01a4e6887902", + "reference": "56789e9dec750e0fbe8e9e6ae90a01a4e6887902", "shasum": "" }, "require": { "doctrine/inflector": "^1.1", "dragonmantank/cron-expression": "^2.0", - "egulias/email-validator": "^2.0", + "egulias/email-validator": "^2.1.10", "erusev/parsedown": "^1.7", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", "league/flysystem": "^1.0.8", - "monolog/monolog": "^1.12", - "nesbot/carbon": "^1.26.3 || ^2.0", + "monolog/monolog": "^1.12|^2.0", + "nesbot/carbon": "^2.0", "opis/closure": "^3.1", - "php": "^7.1.3", + "php": "^7.2", "psr/container": "^1.0", "psr/simple-cache": "^1.0", "ramsey/uuid": "^3.7", "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^4.2", - "symfony/debug": "^4.2", - "symfony/finder": "^4.2", - "symfony/http-foundation": "^4.2", - "symfony/http-kernel": "^4.2", - "symfony/process": "^4.2", - "symfony/routing": "^4.2", - "symfony/var-dumper": "^4.2", + "symfony/console": "^4.3.4", + "symfony/debug": "^4.3.4", + "symfony/finder": "^4.3.4", + "symfony/http-foundation": "^4.3.4", + "symfony/http-kernel": "^4.3.4", + "symfony/process": "^4.3.4", + "symfony/routing": "^4.3.4", + "symfony/var-dumper": "^4.3.4", "tijsverkoyen/css-to-inline-styles": "^2.2.1", "vlucas/phpdotenv": "^3.3" }, @@ -1388,47 +1345,44 @@ "require-dev": { "aws/aws-sdk-php": "^3.0", "doctrine/dbal": "^2.6", - "filp/whoops": "^2.1.4", + "filp/whoops": "^2.4", "guzzlehttp/guzzle": "^6.3", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.0", + "mockery/mockery": "^1.2.3", "moontoast/math": "^1.1", - "orchestra/testbench-core": "3.8.*", + "orchestra/testbench-core": "^4.0", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^7.5|^8.0", + "phpunit/phpunit": "^8.3", "predis/predis": "^1.1.1", - "symfony/css-selector": "^4.2", - "symfony/dom-crawler": "^4.2", + "symfony/cache": "^4.3", "true/punycode": "^2.1" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (^3.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", - "filp/whoops": "Required for friendly error pages in development (^2.1.4).", + "ext-redis": "Required to use the Redis cache and queue drivers.", + "filp/whoops": "Required for friendly error pages in development (^2.4).", "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (^6.0).", + "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0).", "laravel/tinker": "Required to use the tinker console command (^1.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", - "nexmo/client": "Required to use the Nexmo transport (^1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "predis/predis": "Required to use the redis cache and queue drivers (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (^4.2).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (^4.2).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.1).", + "symfony/cache": "Required to PSR-6 cache bridge (^4.3.4).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.2).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "6.x-dev" } }, "autoload": { @@ -1456,7 +1410,7 @@ "framework", "laravel" ], - "time": "2019-09-03T16:44:30+00:00" + "time": "2019-09-10T18:46:24+00:00" }, { "name": "laravel/socialite", @@ -1718,21 +1672,21 @@ }, { "name": "monolog/monolog", - "version": "1.25.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf" + "reference": "68545165e19249013afd1d6f7485aecff07a2d22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf", - "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/68545165e19249013afd1d6f7485aecff07a2d22", + "reference": "68545165e19249013afd1d6f7485aecff07a2d22", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": "^7.2", + "psr/log": "^1.0.1" }, "provide": { "psr/log-implementation": "1.0.0" @@ -1740,33 +1694,36 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", + "elasticsearch/elasticsearch": "^6.0", + "graylog2/gelf-php": "^1.4.2", + "jakub-onderka/php-parallel-lint": "^0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", + "phpspec/prophecy": "^1.6.1", + "phpunit/phpunit": "^8.3", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1792,7 +1749,7 @@ "logging", "psr-3" ], - "time": "2019-09-06T13:49:17+00:00" + "time": "2019-08-30T09:56:44+00:00" }, { "name": "mtdowling/jmespath.php", @@ -1851,34 +1808,36 @@ }, { "name": "nesbot/carbon", - "version": "1.39.0", + "version": "2.24.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0" + "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", - "reference": "dd62a58af4e0775a45ea5f99d0363d81b7d9a1e0", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/934459c5ac0658bc765ad1e53512c7c77adcac29", + "reference": "934459c5ac0658bc765ad1e53512c7c77adcac29", "shasum": "" }, "require": { - "kylekatarnls/update-helper": "^1.1", - "php": ">=5.3.9", - "symfony/translation": "~2.6 || ~3.0 || ~4.0" + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/translation": "^3.4 || ^4.0" }, "require-dev": { - "composer/composer": "^1.2", - "friendsofphp/php-cs-fixer": "~2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^1.1", + "phpmd/phpmd": "dev-php-7.1-compatibility", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.5 || ^8.0", + "squizlabs/php_codesniffer": "^3.4" }, "bin": [ - "bin/upgrade-carbon" + "bin/carbon" ], "type": "library", "extra": { - "update-helper": "Carbon\\Upgrade", "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -1887,7 +1846,7 @@ }, "autoload": { "psr-4": { - "": "src/" + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1899,16 +1858,20 @@ "name": "Brian Nesbitt", "email": "brian@nesbot.com", "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" } ], - "description": "A simple API extension for DateTime.", + "description": "A API extension for DateTime that supports 281 different languages.", "homepage": "http://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], - "time": "2019-06-11T09:07:59+00:00" + "time": "2019-08-31T16:37:55+00:00" }, { "name": "opis/closure", @@ -4499,67 +4462,6 @@ ], "time": "2018-12-13T10:34:14+00:00" }, - { - "name": "beyondcode/laravel-dump-server", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/beyondcode/laravel-dump-server.git", - "reference": "fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/beyondcode/laravel-dump-server/zipball/fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a", - "reference": "fcc88fa66895f8c1ff83f6145a5eff5fa2a0739a", - "shasum": "" - }, - "require": { - "illuminate/console": "5.6.*|5.7.*|5.8.*|^6.0", - "illuminate/http": "5.6.*|5.7.*|5.8.*|^6.0", - "illuminate/support": "5.6.*|5.7.*|5.8.*|^6.0", - "php": "^7.1", - "symfony/var-dumper": "^4.1.1" - }, - "require-dev": { - "larapack/dd": "^1.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BeyondCode\\DumpServer\\DumpServerServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "BeyondCode\\DumpServer\\": "src" - }, - "files": [ - "helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marcel Pociot", - "email": "marcel@beyondco.de", - "homepage": "https://beyondco.de", - "role": "Developer" - } - ], - "description": "Symfony Var-Dump Server for Laravel", - "homepage": "https://github.com/beyondcode/laravel-dump-server", - "keywords": [ - "beyondcode", - "laravel-dump-server" - ], - "time": "2019-08-11T13:17:40+00:00" - }, { "name": "composer/ca-bundle", "version": "1.2.4", @@ -4918,6 +4820,172 @@ ], "time": "2019-03-17T17:37:11+00:00" }, + { + "name": "facade/flare-client-php", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/facade/flare-client-php.git", + "reference": "7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e", + "reference": "7128b251b48f24ef64e5cddd7f8d40cc3a06fd3e", + "shasum": "" + }, + "require": { + "facade/ignition-contracts": "~1.0", + "illuminate/pipeline": "~5.5|~5.6|~5.7|~5.8|^6.0", + "php": "^7.1", + "symfony/http-foundation": "~3.3|~4.1", + "symfony/var-dumper": "^3.4|^4.0" + }, + "require-dev": { + "larapack/dd": "^1.1", + "phpunit/phpunit": "^7.0", + "spatie/phpunit-snapshot-assertions": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Facade\\FlareClient\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/facade/flare-client-php", + "keywords": [ + "exception", + "facade", + "flare", + "reporting" + ], + "time": "2019-09-11T14:19:56+00:00" + }, + { + "name": "facade/ignition", + "version": "1.6.5", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition.git", + "reference": "97244f6d511332f3574acab8242c09ddcfda892b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition/zipball/97244f6d511332f3574acab8242c09ddcfda892b", + "reference": "97244f6d511332f3574acab8242c09ddcfda892b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "facade/flare-client-php": "^1.0.4", + "facade/ignition-contracts": "^1.0", + "filp/whoops": "^2.4", + "illuminate/support": "~5.5.0 || ~5.6.0 || ~5.7.0 || ~5.8.0 || ^6.0", + "monolog/monolog": "^1.12 || ^2.0", + "php": "^7.1", + "scrivo/highlight.php": "^9.15", + "symfony/console": "^3.4 || ^4.0", + "symfony/var-dumper": "^3.4 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "mockery/mockery": "^1.2", + "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0" + }, + "suggest": { + "laravel/telescope": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Facade\\Ignition\\IgnitionServiceProvider" + ], + "aliases": { + "Flare": "Facade\\Ignition\\Facades\\Flare" + } + } + }, + "autoload": { + "psr-4": { + "Facade\\Ignition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://github.com/facade/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "time": "2019-09-13T13:38:04+00:00" + }, + { + "name": "facade/ignition-contracts", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition-contracts.git", + "reference": "f445db0fb86f48e205787b2592840dd9c80ded28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/f445db0fb86f48e205787b2592840dd9c80ded28", + "reference": "f445db0fb86f48e205787b2592840dd9c80ded28", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facade\\IgnitionContracts\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://flareapp.io", + "role": "Developer" + } + ], + "description": "Solution contracts for Ignition", + "homepage": "https://github.com/facade/ignition-contracts", + "keywords": [ + "contracts", + "flare", + "ignition" + ], + "time": "2019-08-30T14:06:08+00:00" + }, { "name": "filp/whoops", "version": "2.5.0", @@ -5233,30 +5301,41 @@ }, { "name": "laravel/browser-kit-testing", - "version": "v4.2.1", + "version": "v5.1.3", "source": { "type": "git", "url": "https://github.com/laravel/browser-kit-testing.git", - "reference": "b042ed965910a4ba69c0ebe8863d4029af3e242e" + "reference": "cb0cf22cf38fe8796842adc8b9ad550ded2a1377" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/b042ed965910a4ba69c0ebe8863d4029af3e242e", - "reference": "b042ed965910a4ba69c0ebe8863d4029af3e242e", + "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/cb0cf22cf38fe8796842adc8b9ad550ded2a1377", + "reference": "cb0cf22cf38fe8796842adc8b9ad550ded2a1377", "shasum": "" }, "require": { - "illuminate/support": "^5.6", + "ext-dom": "*", + "ext-json": "*", + "illuminate/contracts": "~5.7.0|~5.8.0|^6.0", + "illuminate/database": "~5.7.0|~5.8.0|^6.0", + "illuminate/http": "~5.7.0|~5.8.0|^6.0", + "illuminate/support": "~5.7.0|~5.8.0|^6.0", "mockery/mockery": "^1.0", "php": ">=7.1.3", - "phpunit/phpunit": "^7.0", - "symfony/css-selector": "~4.0", - "symfony/dom-crawler": "~4.0" + "phpunit/phpunit": "^7.0|^8.0", + "symfony/console": "^4.2", + "symfony/css-selector": "^4.2", + "symfony/dom-crawler": "^4.2", + "symfony/http-foundation": "^4.2", + "symfony/http-kernel": "^4.2" + }, + "require-dev": { + "laravel/framework": "~5.7.0|~5.8.0|^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5279,7 +5358,7 @@ "laravel", "testing" ], - "time": "2019-02-05T13:27:14+00:00" + "time": "2019-07-30T14:57:44+00:00" }, { "name": "maximebf/debugbar", @@ -5836,40 +5915,40 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "version": "7.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", + "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^3.1.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -5895,7 +5974,7 @@ "testing", "xunit" ], - "time": "2018-10-31T16:06:48+00:00" + "time": "2019-07-25T05:31:54+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6088,53 +6167,52 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.15", + "version": "8.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d79c053d972856b8b941bb233e39dc521a5093f0" + "reference": "302faed7059fde575cf3403a78c730c5e3a62750" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0", - "reference": "d79c053d972856b8b941bb233e39dc521a5093f0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/302faed7059fde575cf3403a78c730c5e3a62750", + "reference": "302faed7059fde575cf3403a78c730c5e3a62750", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -6142,7 +6220,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-master": "8.3-dev" } }, "autoload": { @@ -6168,7 +6246,74 @@ "testing", "xunit" ], - "time": "2019-08-21T07:05:16+00:00" + "time": "2019-09-14T09:12:03+00:00" + }, + { + "name": "scrivo/highlight.php", + "version": "v9.15.10.0", + "source": { + "type": "git", + "url": "https://github.com/scrivo/highlight.php.git", + "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/9ad3adb4456dc91196327498dbbce6aa1ba1239e", + "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "symfony/finder": "^2.8" + }, + "suggest": { + "ext-dom": "Needed to make use of the features in the utilities namespace" + }, + "type": "library", + "autoload": { + "psr-0": { + "Highlight\\": "", + "HighlightUtilities\\": "" + }, + "files": [ + "HighlightUtilities/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Geert Bergman", + "role": "Project Author", + "homepage": "http://www.scrivo.org/" + }, + { + "name": "Vladimir Jimenez", + "role": "Contributor", + "homepage": "https://allejo.io" + }, + { + "name": "Martin Folkers", + "role": "Contributor", + "homepage": "https://twobrain.io" + } + ], + "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js", + "keywords": [ + "code", + "highlight", + "highlight.js", + "highlight.php", + "syntax" + ], + "time": "2019-08-27T04:27:48+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6390,16 +6535,16 @@ }, { "name": "sebastian/exporter", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -6453,27 +6598,30 @@ "export", "exporter" ], - "time": "2019-08-11T12:43:14+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -6481,7 +6629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6504,7 +6652,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -6693,6 +6841,52 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "time": "2018-10-04T04:07:39+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -7088,17 +7282,17 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^7.1.3", - "ext-json": "*", - "ext-tidy": "*", + "php": "^7.2", + "ext-curl": "*", "ext-dom": "*", - "ext-xml": "*", - "ext-mbstring": "*", "ext-gd": "*", - "ext-curl": "*" + "ext-json": "*", + "ext-mbstring": "*", + "ext-tidy": "*", + "ext-xml": "*" }, "platform-dev": [], "platform-overrides": { - "php": "7.1.3" + "php": "7.2.0" } } diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index de6b0b276..ddf3c295d 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -15,8 +15,8 @@ $factory->define(\BookStack\Auth\User::class, function ($faker) { return [ 'name' => $faker->name, 'email' => $faker->email, - 'password' => str_random(10), - 'remember_token' => str_random(10), + 'password' => Str::random(10), + 'remember_token' => Str::random(10), 'email_confirmed' => 1 ]; }); @@ -24,7 +24,7 @@ $factory->define(\BookStack\Auth\User::class, function ($faker) { $factory->define(\BookStack\Entities\Bookshelf::class, function ($faker) { return [ 'name' => $faker->sentence, - 'slug' => str_random(10), + 'slug' => Str::random(10), 'description' => $faker->paragraph ]; }); @@ -32,7 +32,7 @@ $factory->define(\BookStack\Entities\Bookshelf::class, function ($faker) { $factory->define(\BookStack\Entities\Book::class, function ($faker) { return [ 'name' => $faker->sentence, - 'slug' => str_random(10), + 'slug' => Str::random(10), 'description' => $faker->paragraph ]; }); @@ -40,7 +40,7 @@ $factory->define(\BookStack\Entities\Book::class, function ($faker) { $factory->define(\BookStack\Entities\Chapter::class, function ($faker) { return [ 'name' => $faker->sentence, - 'slug' => str_random(10), + 'slug' => Str::random(10), 'description' => $faker->paragraph ]; }); @@ -49,7 +49,7 @@ $factory->define(\BookStack\Entities\Page::class, function ($faker) { $html = '

' . implode('

', $faker->paragraphs(5)) . '

'; return [ 'name' => $faker->sentence, - 'slug' => str_random(10), + 'slug' => Str::random(10), 'html' => $html, 'text' => strip_tags($html), 'revision_count' => 1 diff --git a/phpunit.xml b/phpunit.xml index 21f81e32c..7de7233af 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -28,7 +28,8 @@ - + + diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 438339e92..2f3cc1f73 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -7,7 +7,7 @@
 
-

{{ title_case(trans('auth.log_in')) }}

+

{{ Str::title(trans('auth.log_in')) }}

{!! csrf_field() !!} @@ -28,7 +28,7 @@
- +
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 60ceba93c..0e996a00d 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -6,7 +6,7 @@
 
-

{{ title_case(trans('auth.sign_up')) }}

+

{{ Str::title(trans('auth.sign_up')) }}

{!! csrf_field() !!} diff --git a/resources/views/partials/breadcrumbs.blade.php b/resources/views/partials/breadcrumbs.blade.php index 5e11a9190..58ccd5125 100644 --- a/resources/views/partials/breadcrumbs.blade.php +++ b/resources/views/partials/breadcrumbs.blade.php @@ -2,7 +2,7 @@ {{-- Show top level books item --}} - @if (count($crumbs) > 0 && array_first($crumbs) instanceof \BookStack\Entities\Book) + @if (count($crumbs) > 0 && ($crumbs[0] ?? null) instanceof \BookStack\Entities\Book) @icon('books') {{ trans('entities.books') }} @@ -11,7 +11,7 @@ @endif {{-- Show top level shelves item --}} - @if (count($crumbs) > 0 && array_first($crumbs) instanceof \BookStack\Entities\Bookshelf) + @if (count($crumbs) > 0 && ($crumbs[0] ?? null) instanceof \BookStack\Entities\Bookshelf) @icon('bookshelf') {{ trans('entities.shelves') }} diff --git a/tests/Auth/SocialAuthTest.php b/tests/Auth/SocialAuthTest.php index b8ca81174..526c0e199 100644 --- a/tests/Auth/SocialAuthTest.php +++ b/tests/Auth/SocialAuthTest.php @@ -153,7 +153,7 @@ class SocialAuthTest extends TestCase config()->set('services.google.select_account', 'true'); $resp = $this->get('/login/service/google'); - $this->assertContains('prompt=select_account', $resp->headers->get('Location')); + $this->assertStringContainsString('prompt=select_account', $resp->headers->get('Location')); } } diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index e812d5bfe..a0fcb5ca8 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -50,7 +50,7 @@ class PageContentTest extends TestCase $resp->assertStatus(302); $page = Page::find($page->id); - $this->assertContains($includeTag, $page->html); + $this->assertStringContainsString($includeTag, $page->html); $this->assertEquals('', $page->text); } diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index 1d87e942a..eb3f7b4bb 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -1,8 +1,6 @@ 'test page', 'html' => 'My new test page']) { $book = Book::first(); @@ -204,4 +205,58 @@ trait SharedTestHelpers ->andReturn($returnData); } + /** + * Run a set test with the given env variable. + * Remembers the original and resets the value after test. + * @param string $name + * @param $value + * @param callable $callback + */ + protected function runWithEnv(string $name, $value, callable $callback) + { + Env::disablePutenv(); + $originalVal = $_ENV[$name] ?? null; + + if (is_null($value)) { + unset($_ENV[$name]); + unset($_SERVER[$name]); + } else { + $_ENV[$name] = $value; + $_SERVER[$name] = $value; + } + + $this->refreshApplication(); + $callback(); + + if (is_null($originalVal)) { + unset($_SERVER[$name]); + unset($_ENV[$name]); + } else { + $_SERVER[$name] = $originalVal; + $_ENV[$name] = $originalVal; + } + } + + /** + * Check the keys and properties in the given map to include + * exist, albeit not exclusively, within the map to check. + * @param array $mapToInclude + * @param array $mapToCheck + * @param string $message + */ + protected function assertArrayMapIncludes(array $mapToInclude, array $mapToCheck, string $message = '') : void + { + $passed = true; + + foreach ($mapToInclude as $key => $value) { + if (!isset($mapToCheck[$key]) || $mapToCheck[$key] !== $mapToInclude[$key]) { + $passed = false; + } + } + + $toIncludeStr = print_r($mapToInclude, true); + $toCheckStr = print_r($mapToCheck, true); + self::assertThat($passed, self::isTrue(), "Failed asserting that given map:\n\n{$toCheckStr}\n\nincludes:\n\n{$toIncludeStr}"); + } + } \ No newline at end of file diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index ec9967227..7b9f64e6a 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -12,22 +12,19 @@ class ConfigTest extends TestCase public function test_filesystem_images_falls_back_to_storage_type_var() { - putenv('STORAGE_TYPE=local_secure'); - - $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', 's3', 'filesystems.images', 's3'); - $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', null, 'filesystems.images', 'local_secure'); - - putenv('STORAGE_TYPE=local'); + $this->runWithEnv('STORAGE_TYPE', 'local_secure', function() { + $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', 's3', 'filesystems.images', 's3'); + $this->checkEnvConfigResult('STORAGE_IMAGE_TYPE', null, 'filesystems.images', 'local_secure'); + }); } public function test_filesystem_attachments_falls_back_to_storage_type_var() { putenv('STORAGE_TYPE=local_secure'); - - $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', 's3', 'filesystems.attachments', 's3'); - $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', null, 'filesystems.attachments', 'local_secure'); - - putenv('STORAGE_TYPE=local'); + $this->runWithEnv('STORAGE_TYPE', 'local_secure', function() { + $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', 's3', 'filesystems.attachments', 's3'); + $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', null, 'filesystems.attachments', 'local_secure'); + }); } public function test_app_url_blank_if_old_default_value() @@ -49,15 +46,9 @@ class ConfigTest extends TestCase */ protected function checkEnvConfigResult(string $envName, $envVal, string $configKey, string $expectedResult) { - $originalVal = getenv($envName); - - $envString = $envName . (is_null($envVal) ? '' : '=') . ($envVal ?? ''); - putenv($envString); - $this->refreshApplication(); - $this->assertEquals($expectedResult, config($configKey)); - - $envString = $envName . (empty($originalVal) ? '' : '=') . ($originalVal ?? ''); - putenv($envString); + $this->runWithEnv($envName, $envVal, function() use ($configKey, $expectedResult) { + $this->assertEquals($expectedResult, config($configKey)); + }); } } \ No newline at end of file diff --git a/tests/Unit/PageRepoTest.php b/tests/Unit/PageRepoTest.php index c5e094b63..38ffbf616 100644 --- a/tests/Unit/PageRepoTest.php +++ b/tests/Unit/PageRepoTest.php @@ -22,19 +22,19 @@ class PageRepoTest extends TestCase $navMap = $this->pageRepo->getPageNav($content); $this->assertCount(3, $navMap); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h1', 'link' => '#testa', 'text' => 'Hello', 'level' => 1, ], $navMap[0]); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h2', 'link' => '#testb', 'text' => 'There', 'level' => 2, ], $navMap[1]); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h3', 'link' => '#testc', 'text' => 'Donkey', @@ -48,7 +48,7 @@ class PageRepoTest extends TestCase $navMap = $this->pageRepo->getPageNav($content); $this->assertCount(1, $navMap); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h1', 'link' => '#testa', 'text' => 'Hello' @@ -61,15 +61,15 @@ class PageRepoTest extends TestCase $navMap = $this->pageRepo->getPageNav($content); $this->assertCount(3, $navMap); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h4', 'level' => 1, ], $navMap[0]); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h5', 'level' => 2, ], $navMap[1]); - $this->assertArraySubset([ + $this->assertArrayMapIncludes([ 'nodeName' => 'h6', 'level' => 3, ], $navMap[2]); diff --git a/tests/Unit/UrlTest.php b/tests/Unit/UrlTest.php index 1667f5f7b..c2386443c 100644 --- a/tests/Unit/UrlTest.php +++ b/tests/Unit/UrlTest.php @@ -16,18 +16,16 @@ class UrlTest extends TestCase public function test_url_helper_takes_custom_url_into_account() { - putenv('APP_URL=http://example.com/bookstack'); - $this->refreshApplication(); - $this->assertEquals('http://example.com/bookstack/books', url('/books')); - putenv('APP_URL='); + $this->runWithEnv('APP_URL', 'http://example.com/bookstack', function() { + $this->assertEquals('http://example.com/bookstack/books', url('/books')); + }); } public function test_url_helper_sets_correct_scheme_even_when_request_scheme_is_different() { - putenv('APP_URL=https://example.com/'); - $this->refreshApplication(); - $this->get('http://example.com/login')->assertSee('https://example.com/dist/styles.css'); - putenv('APP_URL='); + $this->runWithEnv('APP_URL', 'https://example.com/', function() { + $this->get('http://example.com/login')->assertSee('https://example.com/dist/styles.css'); + }); } } \ No newline at end of file diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 35ffda821..0d51e050f 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -78,7 +78,7 @@ class AttachmentTest extends TestCase $upload->assertStatus(200); $attachment = Attachment::query()->orderBy('id', 'desc')->first(); - $this->assertNotContains($fileName, $attachment->path); + $this->assertStringNotContainsString($fileName, $attachment->path); $this->assertStringEndsWith('.txt', $attachment->path); } From 58a79fcb19c7a8663c61771a2fe93cde3264c503 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 14 Sep 2019 14:17:55 +0100 Subject: [PATCH 019/257] Removed old str_random functions from seeders --- database/seeds/DummyContentSeeder.php | 34 +++++++++++++++++---------- database/seeds/LargeContentSeeder.php | 21 +++++++++++------ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/database/seeds/DummyContentSeeder.php b/database/seeds/DummyContentSeeder.php index ce3cd1307..deb1aa11c 100644 --- a/database/seeds/DummyContentSeeder.php +++ b/database/seeds/DummyContentSeeder.php @@ -1,6 +1,14 @@ create(); - $editorRole = \BookStack\Auth\Role::getRole('editor'); + $editorUser = factory(User::class)->create(); + $editorRole = Role::getRole('editor'); $editorUser->attachRole($editorRole); // Create a viewer user - $viewerUser = factory(\BookStack\Auth\User::class)->create(); - $role = \BookStack\Auth\Role::getRole('viewer'); + $viewerUser = factory(User::class)->create(); + $role = Role::getRole('viewer'); $viewerUser->attachRole($role); $byData = ['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]; factory(\BookStack\Entities\Book::class, 5)->create($byData) ->each(function($book) use ($editorUser, $byData) { - $chapters = factory(\BookStack\Entities\Chapter::class, 3)->create($byData) + $chapters = factory(Chapter::class, 3)->create($byData) ->each(function($chapter) use ($editorUser, $book, $byData){ - $pages = factory(\BookStack\Entities\Page::class, 3)->make(array_merge($byData, ['book_id' => $book->id])); + $pages = factory(Page::class, 3)->make(array_merge($byData, ['book_id' => $book->id])); $chapter->pages()->saveMany($pages); }); - $pages = factory(\BookStack\Entities\Page::class, 3)->make($byData); + $pages = factory(Page::class, 3)->make($byData); $book->chapters()->saveMany($chapters); $book->pages()->saveMany($pages); }); - $largeBook = factory(\BookStack\Entities\Book::class)->create(array_merge($byData, ['name' => 'Large book' . str_random(10)])); - $pages = factory(\BookStack\Entities\Page::class, 200)->make($byData); - $chapters = factory(\BookStack\Entities\Chapter::class, 50)->make($byData); + $largeBook = factory(\BookStack\Entities\Book::class)->create(array_merge($byData, ['name' => 'Large book' . Str::random(10)])); + $pages = factory(Page::class, 200)->make($byData); + $chapters = factory(Chapter::class, 50)->make($byData); $largeBook->pages()->saveMany($pages); $largeBook->chapters()->saveMany($chapters); - $shelves = factory(\BookStack\Entities\Bookshelf::class, 10)->create($byData); + $shelves = factory(Bookshelf::class, 10)->create($byData); $largeBook->shelves()->attach($shelves->pluck('id')); - app(\BookStack\Auth\Permissions\PermissionService::class)->buildJointPermissions(); - app(\BookStack\Entities\SearchService::class)->indexAllEntities(); + app(PermissionService::class)->buildJointPermissions(); + app(SearchService::class)->indexAllEntities(); } } diff --git a/database/seeds/LargeContentSeeder.php b/database/seeds/LargeContentSeeder.php index 136b6cb6a..4db10395a 100644 --- a/database/seeds/LargeContentSeeder.php +++ b/database/seeds/LargeContentSeeder.php @@ -1,6 +1,13 @@ create(); - $editorRole = \BookStack\Auth\Role::getRole('editor'); + $editorUser = factory(User::class)->create(); + $editorRole = Role::getRole('editor'); $editorUser->attachRole($editorRole); - $largeBook = factory(\BookStack\Entities\Book::class)->create(['name' => 'Large book' . str_random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); - $pages = factory(\BookStack\Entities\Page::class, 200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); - $chapters = factory(\BookStack\Entities\Chapter::class, 50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); + $largeBook = factory(\BookStack\Entities\Book::class)->create(['name' => 'Large book' . Str::random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); + $pages = factory(Page::class, 200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); + $chapters = factory(Chapter::class, 50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); $largeBook->pages()->saveMany($pages); $largeBook->chapters()->saveMany($chapters); - app(\BookStack\Auth\Permissions\PermissionService::class)->buildJointPermissions(); - app(\BookStack\Entities\SearchService::class)->indexAllEntities(); + app(PermissionService::class)->buildJointPermissions(); + app(SearchService::class)->indexAllEntities(); } } From 2ab5df75dda7d0c0625a83783b655588f1fb657d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 14 Sep 2019 14:23:16 +0100 Subject: [PATCH 020/257] Updated version and removed travis --- .travis.yml | 29 ----------------------------- version | 2 +- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 10a5d1e6b..000000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -dist: bionic -language: php -php: - - '7.2' - - '7.3' - -services: - - mysql - -cache: - directories: - - $HOME/.composer/cache - -before_script: - - mysql -u root -e 'create database `bookstack-test`;' - - mysql -u root -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';" - - mysql -u root -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';" - - mysql -u root -e "FLUSH PRIVILEGES;" - - phpenv config-rm xdebug.ini - - composer install --prefer-dist --no-interaction - - php artisan clear-compiled -n - - php artisan optimize -n - - php artisan migrate --force -n --database=mysql_testing - - php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing - -script: vendor/bin/phpunit --configuration phpunit.xml - -after_failure: - - cat storage/logs/laravel.log diff --git a/version b/version index 4494802a4..d40040377 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.27-dev +v0.28-dev From 8e212bdeab9ec063262ef216e47a6d394e8e8e9c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 17:39:07 +0100 Subject: [PATCH 021/257] Ran NPM audit fix --- package-lock.json | 49 +++++++++++++---------------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3330ad5c7..47afc27a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3231,9 +3231,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "lodash.tail": { @@ -3528,9 +3528,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -4799,9 +4799,9 @@ "dev": true }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -5557,38 +5557,15 @@ "dev": true }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { From e81f90d9bd9e98e054971c580817bbb234515546 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 17:50:08 +0100 Subject: [PATCH 022/257] Updated twitch provider --- .github/workflows/phpunit.yml | 2 +- composer.json | 2 +- composer.lock | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index d1b458bdb..922aa5067 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -17,7 +17,7 @@ jobs: mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';" mysql -uroot -proot -e 'FLUSH PRIVILEGES;' - name: Install composer dependencies & Test - run: composer install --prefer-dist --no-interaction + run: composer install --prefer-dist --no-interaction --ansi - name: Migrate and seed the database run: | php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing diff --git a/composer.json b/composer.json index d9d115017..68e6efda9 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "socialiteproviders/microsoft-azure": "^3.0", "socialiteproviders/okta": "^1.0", "socialiteproviders/slack": "^3.0", - "socialiteproviders/twitch": "^3.0" + "socialiteproviders/twitch": "^5.0" }, "require-dev": { "facade/ignition": "^1.4", diff --git a/composer.lock b/composer.lock index 5c3cb66b1..b2e298e85 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "322b6b585d85e066f97711fe8f1f7ccd", + "content-hash": "822da5f0292bf0d88faa51281434145c", "packages": [ { "name": "aws/aws-sdk-php", @@ -2761,21 +2761,21 @@ }, { "name": "socialiteproviders/twitch", - "version": "v3.0.0", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Twitch.git", - "reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93" + "reference": "8c19b26ff24c40cc019413042a5492c5ed21a658" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/a7ad148c0b42d0c607d8a034b6e47faf5fc85e93", - "reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93", + "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/8c19b26ff24c40cc019413042a5492c5ed21a658", + "reference": "8c19b26ff24c40cc019413042a5492c5ed21a658", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "socialiteproviders/manager": "~3.0" + "socialiteproviders/manager": "~2.0 || ~3.0" }, "type": "library", "autoload": { @@ -2794,7 +2794,7 @@ } ], "description": "Twitch OAuth2 Provider for Laravel Socialite", - "time": "2017-01-25T09:48:29+00:00" + "time": "2018-06-20T10:59:51+00:00" }, { "name": "swiftmailer/swiftmailer", From b1566099a3c49b27d0a560785035bf7c40452721 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 18:07:00 +0100 Subject: [PATCH 023/257] Added laravel stats package and enabled debugbar models --- app/Config/debugbar.php | 1 + composer.json | 13 +-- composer.lock | 193 +++++++++++++++++++++++++++++++++++++++- readme.md | 1 + 4 files changed, 201 insertions(+), 7 deletions(-) diff --git a/app/Config/debugbar.php b/app/Config/debugbar.php index ec942dcd3..fe624eb7d 100644 --- a/app/Config/debugbar.php +++ b/app/Config/debugbar.php @@ -79,6 +79,7 @@ return [ 'files' => false, // Show the included files 'config' => false, // Display config settings 'cache' => false, // Display cache events + 'models' => true, // Display models ], // Configure some DataCollectors diff --git a/composer.json b/composer.json index 68e6efda9..a8b9456a1 100644 --- a/composer.json +++ b/composer.json @@ -31,15 +31,16 @@ "socialiteproviders/twitch": "^5.0" }, "require-dev": { + "barryvdh/laravel-debugbar": "^3.2.8", + "barryvdh/laravel-ide-helper": "^2.6.4", "facade/ignition": "^1.4", "fzaninotto/faker": "^1.4", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^8.0", - "nunomaduro/collision": "^3.0", "laravel/browser-kit-testing": "^5.1", - "barryvdh/laravel-ide-helper": "^2.6.4", - "barryvdh/laravel-debugbar": "^3.2.8", - "squizlabs/php_codesniffer": "^3.4" + "mockery/mockery": "^1.0", + "nunomaduro/collision": "^3.0", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.4", + "wnx/laravel-stats": "^2.0" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index b2e298e85..3ec106ded 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "822da5f0292bf0d88faa51281434145c", + "content-hash": "c156e1738dbab2a57f9a926d9a9a5a6a", "packages": [ { "name": "aws/aws-sdk-php", @@ -5850,6 +5850,55 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "time": "2019-08-22T18:11:29+00:00" }, + { + "name": "phploc/phploc", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phploc.git", + "reference": "5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phploc/zipball/5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884", + "reference": "5b714ccb7cb8ca29ccf9caf6eb1aed0131d3a884", + "shasum": "" + }, + "require": { + "php": "^7.2", + "sebastian/finder-facade": "^1.1", + "sebastian/version": "^2.0", + "symfony/console": "^4.0" + }, + "bin": [ + "phploc" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "A tool for quickly measuring the size of a PHP project.", + "homepage": "https://github.com/sebastianbergmann/phploc", + "time": "2019-03-16T10:41:19+00:00" + }, { "name": "phpspec/prophecy", "version": "1.8.1", @@ -6600,6 +6649,45 @@ ], "time": "2019-09-14T09:02:43+00:00" }, + { + "name": "sebastian/finder-facade", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/finder-facade.git", + "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", + "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", + "shasum": "" + }, + "require": { + "symfony/finder": "~2.3|~3.0|~4.0", + "theseer/fdomdocument": "~1.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", + "homepage": "https://github.com/sebastianbergmann/finder-facade", + "time": "2017-11-18T17:31:49+00:00" + }, { "name": "sebastian/global-state", "version": "3.0.0", @@ -7185,6 +7273,46 @@ "homepage": "https://symfony.com", "time": "2019-08-20T14:07:54+00:00" }, + { + "name": "theseer/fdomdocument", + "version": "1.6.6", + "source": { + "type": "git", + "url": "https://github.com/theseer/fDOMDocument.git", + "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca", + "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "lib-libxml": "*", + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "role": "lead", + "email": "arne@blankerts.de" + } + ], + "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", + "homepage": "https://github.com/theseer/fDOMDocument", + "time": "2017-06-30T11:53:12+00:00" + }, { "name": "theseer/tokenizer", "version": "1.1.3", @@ -7274,6 +7402,69 @@ "validate" ], "time": "2019-08-24T08:43:50+00:00" + }, + { + "name": "wnx/laravel-stats", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/stefanzweifel/laravel-stats.git", + "reference": "1b3c60bfbf81233973cbc2a63be4e6f83b2d6205" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stefanzweifel/laravel-stats/zipball/1b3c60bfbf81233973cbc2a63be4e6f83b2d6205", + "reference": "1b3c60bfbf81233973cbc2a63be4e6f83b2d6205", + "shasum": "" + }, + "require": { + "illuminate/console": "~5.8.0|^6.0", + "illuminate/support": "~5.8.0|^6.0", + "php": ">=7.2.0", + "phploc/phploc": "~4.0|~5.0", + "symfony/finder": "~3.3|~4.0" + }, + "require-dev": { + "laravel/browser-kit-testing": "~2.0|~3.0|~4.0|~5.0", + "laravel/dusk": "~3.0|~4.0|~5.0", + "mockery/mockery": "^1.1", + "orchestra/testbench": "^3.8", + "phpunit/phpunit": "6.*|7.*|8.*" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Wnx\\LaravelStats\\StatsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Wnx\\LaravelStats\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stefan Zweifel", + "email": "hello@stefanzweifel.io", + "homepage": "https://stefanzweifel.io", + "role": "Developer" + } + ], + "description": "Get insights about your Laravel Project", + "homepage": "https://github.com/stefanzweifel/laravel-stats", + "keywords": [ + "laravel", + "statistics", + "stats", + "wnx" + ], + "time": "2019-09-01T14:18:49+00:00" } ], "aliases": [], diff --git a/readme.md b/readme.md index 489fc3365..ca90be305 100644 --- a/readme.md +++ b/readme.md @@ -174,3 +174,4 @@ These are the great open-source projects used to help build BookStack: * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper) * [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html) * [Draw.io](https://github.com/jgraph/drawio) +* [Laravel Stats](https://github.com/stefanzweifel/laravel-stats) \ No newline at end of file From be08dc158845d52f4de2661140f758a1949d8b24 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 18:29:51 +0100 Subject: [PATCH 024/257] Ran phpcbf and updated helpers typehinting --- app/Application.php | 3 +-- app/Auth/Access/EmailConfirmationService.php | 1 - app/Auth/Access/UserInviteService.php | 1 - app/Auth/Access/UserTokenService.php | 3 +-- app/Config/auth.php | 2 +- app/Config/cache.php | 8 +++++-- app/Config/database.php | 1 - app/Config/dompdf.php | 2 +- app/Config/hashing.php | 2 +- app/Config/logging.php | 2 +- app/Config/mail.php | 2 +- app/Config/services.php | 14 ++++++------ app/Config/setting-defaults.php | 2 +- app/Entities/Repos/PageRepo.php | 8 +++---- app/Exceptions/UserTokenExpiredException.php | 7 +++--- app/Exceptions/UserTokenNotFoundException.php | 5 ++++- .../Auth/ConfirmEmailController.php | 2 -- .../Controllers/Auth/UserInviteController.php | 1 - .../Controllers/PageTemplateController.php | 1 - .../Middleware/CheckForMaintenanceMode.php | 2 +- app/Http/Request.php | 3 +-- app/Providers/AppServiceProvider.php | 2 +- app/helpers.php | 22 +++++++++---------- 23 files changed, 46 insertions(+), 50 deletions(-) diff --git a/app/Application.php b/app/Application.php index 97105e479..499fdeaa6 100644 --- a/app/Application.php +++ b/app/Application.php @@ -20,5 +20,4 @@ class Application extends \Illuminate\Foundation\Application . 'Config' . ($path ? DIRECTORY_SEPARATOR.$path : $path); } - -} \ No newline at end of file +} diff --git a/app/Auth/Access/EmailConfirmationService.php b/app/Auth/Access/EmailConfirmationService.php index a94c54d19..9aa3b9b98 100644 --- a/app/Auth/Access/EmailConfirmationService.php +++ b/app/Auth/Access/EmailConfirmationService.php @@ -36,5 +36,4 @@ class EmailConfirmationService extends UserTokenService return setting('registration-confirmation') || setting('registration-restrict'); } - } diff --git a/app/Auth/Access/UserInviteService.php b/app/Auth/Access/UserInviteService.php index 8e04d7b22..20519fc7d 100644 --- a/app/Auth/Access/UserInviteService.php +++ b/app/Auth/Access/UserInviteService.php @@ -19,5 +19,4 @@ class UserInviteService extends UserTokenService $token = $this->createTokenForUser($user); $user->notify(new UserInvite($token)); } - } diff --git a/app/Auth/Access/UserTokenService.php b/app/Auth/Access/UserTokenService.php index 09a2f761b..a1defbf62 100644 --- a/app/Auth/Access/UserTokenService.php +++ b/app/Auth/Access/UserTokenService.php @@ -131,5 +131,4 @@ class UserTokenService return Carbon::now()->subHours($this->expiryTime) ->gt(new Carbon($tokenEntry->created_at)); } - -} \ No newline at end of file +} diff --git a/app/Config/auth.php b/app/Config/auth.php index cd74c2739..5535a6f9c 100644 --- a/app/Config/auth.php +++ b/app/Config/auth.php @@ -70,4 +70,4 @@ return [ ], ], -]; \ No newline at end of file +]; diff --git a/app/Config/cache.php b/app/Config/cache.php index 6d8fa7ad7..33d3a1a0b 100644 --- a/app/Config/cache.php +++ b/app/Config/cache.php @@ -14,8 +14,12 @@ if (env('CACHE_DRIVER') === 'memcached') { $memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ',')); foreach ($memcachedServers as $index => $memcachedServer) { $memcachedServerDetails = explode(':', $memcachedServer); - if (count($memcachedServerDetails) < 2) $memcachedServerDetails[] = '11211'; - if (count($memcachedServerDetails) < 3) $memcachedServerDetails[] = '100'; + if (count($memcachedServerDetails) < 2) { + $memcachedServerDetails[] = '11211'; + } + if (count($memcachedServerDetails) < 3) { + $memcachedServerDetails[] = '100'; + } $memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails); } } diff --git a/app/Config/database.php b/app/Config/database.php index a98b46a2c..ed654ffb9 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -11,7 +11,6 @@ // REDIS // Split out configuration into an array if (env('REDIS_SERVERS', false)) { - $redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null]; $redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ',')); $redisConfig = ['client' => 'predis']; diff --git a/app/Config/dompdf.php b/app/Config/dompdf.php index 77f0cff9c..87be53df5 100644 --- a/app/Config/dompdf.php +++ b/app/Config/dompdf.php @@ -69,7 +69,7 @@ return [ * should be an absolute path. * This is only checked on command line call by dompdf.php, but not by * direct class use like: - * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); + * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); */ "DOMPDF_CHROOT" => realpath(base_path()), diff --git a/app/Config/hashing.php b/app/Config/hashing.php index edcc7c1da..756718ce2 100644 --- a/app/Config/hashing.php +++ b/app/Config/hashing.php @@ -34,4 +34,4 @@ return [ 'time' => 2, ], -]; \ No newline at end of file +]; diff --git a/app/Config/logging.php b/app/Config/logging.php index 9c3218a01..0b55dc24d 100644 --- a/app/Config/logging.php +++ b/app/Config/logging.php @@ -79,4 +79,4 @@ return [ ], ], -]; \ No newline at end of file +]; diff --git a/app/Config/mail.php b/app/Config/mail.php index dfb41e7e6..a91bdf237 100644 --- a/app/Config/mail.php +++ b/app/Config/mail.php @@ -23,7 +23,7 @@ return [ // Global "From" address & name 'from' => [ 'address' => env('MAIL_FROM', 'mail@bookstackapp.com'), - 'name' => env('MAIL_FROM_NAME','BookStack') + 'name' => env('MAIL_FROM_NAME', 'BookStack') ], // Email encryption protocol diff --git a/app/Config/services.php b/app/Config/services.php index 2136f8116..923015f6e 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -81,8 +81,8 @@ return [ 'okta' => [ 'client_id' => env('OKTA_APP_ID'), 'client_secret' => env('OKTA_APP_SECRET'), - 'redirect' => env('APP_URL') . '/login/service/okta/callback', - 'base_url' => env('OKTA_BASE_URL'), + 'redirect' => env('APP_URL') . '/login/service/okta/callback', + 'base_url' => env('OKTA_BASE_URL'), 'name' => 'Okta', 'auto_register' => env('OKTA_AUTO_REGISTER', false), 'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false), @@ -126,10 +126,10 @@ return [ 'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'), 'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'), 'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false), - 'user_to_groups' => env('LDAP_USER_TO_GROUPS',false), - 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), - 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false), - 'tls_insecure' => env('LDAP_TLS_INSECURE', false), - ] + 'user_to_groups' => env('LDAP_USER_TO_GROUPS', false), + 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), + 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false), + 'tls_insecure' => env('LDAP_TLS_INSECURE', false), + ] ]; diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index 4a135573b..c6080df1d 100644 --- a/app/Config/setting-defaults.php +++ b/app/Config/setting-defaults.php @@ -19,4 +19,4 @@ return [ 'app-custom-head' => false, 'registration-enabled' => false, -]; \ No newline at end of file +]; diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index ed142eb61..a7daf549b 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -432,7 +432,7 @@ class PageRepo extends EntityRepo return []; } - $tree = collect($headers)->map(function($header) { + $tree = collect($headers)->map(function ($header) { $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue)); $text = mb_substr($text, 0, 100); @@ -442,7 +442,7 @@ class PageRepo extends EntityRepo 'link' => '#' . $header->getAttribute('id'), 'text' => $text, ]; - })->filter(function($header) { + })->filter(function ($header) { return mb_strlen($header['text']) > 0; }); @@ -541,12 +541,12 @@ class PageRepo extends EntityRepo * @param string $search * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - public function getPageTemplates(int $count = 10, int $page = 1, string $search = '') + public function getPageTemplates(int $count = 10, int $page = 1, string $search = '') { $query = $this->entityQuery('page') ->where('template', '=', true) ->orderBy('name', 'asc') - ->skip( ($page - 1) * $count) + ->skip(($page - 1) * $count) ->take($count); if ($search) { diff --git a/app/Exceptions/UserTokenExpiredException.php b/app/Exceptions/UserTokenExpiredException.php index 203e08c85..e19707457 100644 --- a/app/Exceptions/UserTokenExpiredException.php +++ b/app/Exceptions/UserTokenExpiredException.php @@ -1,6 +1,7 @@ userId = $userId; parent::__construct($message); } - - -} \ No newline at end of file +} diff --git a/app/Exceptions/UserTokenNotFoundException.php b/app/Exceptions/UserTokenNotFoundException.php index 08c1fd830..3ed53f72a 100644 --- a/app/Exceptions/UserTokenNotFoundException.php +++ b/app/Exceptions/UserTokenNotFoundException.php @@ -1,3 +1,6 @@ emailConfirmationService->checkTokenAndGetUserId($token); } catch (Exception $exception) { - if ($exception instanceof UserTokenNotFoundException) { session()->flash('error', trans('errors.email_confirmation_invalid')); return redirect('/register'); @@ -114,5 +113,4 @@ class ConfirmEmailController extends Controller session()->flash('success', trans('auth.email_confirm_resent')); return redirect('/register/confirm'); } - } diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index cfeb69648..a7b9f597f 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -102,5 +102,4 @@ class UserInviteController extends Controller throw $exception; } - } diff --git a/app/Http/Controllers/PageTemplateController.php b/app/Http/Controllers/PageTemplateController.php index 05943351a..b47205a1b 100644 --- a/app/Http/Controllers/PageTemplateController.php +++ b/app/Http/Controllers/PageTemplateController.php @@ -59,5 +59,4 @@ class PageTemplateController extends Controller 'markdown' => $page->markdown, ]); } - } diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php index 4b4bacd83..0c7683836 100644 --- a/app/Http/Middleware/CheckForMaintenanceMode.php +++ b/app/Http/Middleware/CheckForMaintenanceMode.php @@ -14,4 +14,4 @@ class CheckForMaintenanceMode extends Middleware protected $except = [ // ]; -} \ No newline at end of file +} diff --git a/app/Http/Request.php b/app/Http/Request.php index bd2761a0b..183686f67 100644 --- a/app/Http/Request.php +++ b/app/Http/Request.php @@ -22,5 +22,4 @@ class Request extends LaravelRequest return $base; } - -} \ No newline at end of file +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b46a716cc..3a1b4f42e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -48,7 +48,7 @@ class AppServiceProvider extends ServiceProvider return ""; }); - Blade::directive('exposeTranslations', function($expression) { + Blade::directive('exposeTranslations', function ($expression) { return "startPush('translations'); ?>" . "" . '' . "\n" . diff --git a/app/helpers.php b/app/helpers.php index 59b9104f8..d14379ddd 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -12,7 +12,7 @@ use BookStack\Settings\SettingService; * @return string * @throws Exception */ -function versioned_asset($file = '') : string +function versioned_asset(string $file = ''): string { static $version = null; @@ -35,7 +35,7 @@ function versioned_asset($file = '') : string * Defaults to public 'Guest' user if not logged in. * @return User */ -function user() : User +function user(): User { return auth()->user() ?: User::getDefault(); } @@ -44,7 +44,7 @@ function user() : User * Check if current user is a signed in user. * @return bool */ -function signedInUser() : bool +function signedInUser(): bool { return auth()->user() && !auth()->user()->isDefault(); } @@ -53,7 +53,7 @@ function signedInUser() : bool * Check if the current user has general access. * @return bool */ -function hasAppAccess() : bool +function hasAppAccess(): bool { return !auth()->guest() || setting('app-public'); } @@ -66,7 +66,7 @@ function hasAppAccess() : bool * @param Ownable $ownable * @return bool */ -function userCan(string $permission, Ownable $ownable = null) : bool +function userCan(string $permission, Ownable $ownable = null): bool { if ($ownable === null) { return user() && user()->can($permission); @@ -84,7 +84,7 @@ function userCan(string $permission, Ownable $ownable = null) : bool * @param string|null $entityClass * @return bool */ -function userCanOnAny(string $permission, string $entityClass = null) : bool +function userCanOnAny(string $permission, string $entityClass = null): bool { $permissionService = app(PermissionService::class); return $permissionService->checkUserHasPermissionOnAnything($permission, $entityClass); @@ -96,7 +96,7 @@ function userCanOnAny(string $permission, string $entityClass = null) : bool * @param bool $default * @return bool|string|SettingService */ -function setting($key = null, $default = false) +function setting(string $key = null, bool $default = false) { $settingService = resolve(SettingService::class); if (is_null($key)) { @@ -110,7 +110,7 @@ function setting($key = null, $default = false) * @param string $path * @return string */ -function theme_path($path = '') : string +function theme_path(string $path = ''): string { $theme = config('view.theme'); if (!$theme) { @@ -130,7 +130,7 @@ function theme_path($path = '') : string * @param array $attrs * @return mixed */ -function icon($name, $attrs = []) +function icon(string $name, array $attrs = []): string { $attrs = array_merge([ 'class' => 'svg-icon', @@ -158,12 +158,12 @@ function icon($name, $attrs = []) * Generate a url with multiple parameters for sorting purposes. * Works out the logic to set the correct sorting direction * Discards empty parameters and allows overriding. - * @param $path + * @param string $path * @param array $data * @param array $overrideData * @return string */ -function sortUrl($path, $data, $overrideData = []) +function sortUrl(string $path, array $data, array $overrideData = []): string { $queryStringSections = []; $queryData = array_merge($data, $overrideData); From 3281925375363a7c2b1684515aa0e2f8311f299f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 18:53:30 +0100 Subject: [PATCH 025/257] Standardised how request is injected into controller methods Puts it in-line with how Laravel recommend. --- app/Http/Controllers/AttachmentController.php | 15 ++++++---- .../Controllers/Auth/RegisterController.php | 4 +-- .../Controllers/Auth/UserInviteController.php | 4 +-- app/Http/Controllers/BookController.php | 13 +++++---- app/Http/Controllers/BookshelfController.php | 4 +-- app/Http/Controllers/ChapterController.php | 28 ++++++++++--------- .../Controllers/Images/ImageController.php | 4 +-- app/Http/Controllers/PageController.php | 12 ++++---- app/Http/Controllers/PermissionController.php | 9 +++--- app/Http/Controllers/UserController.php | 16 +++++------ 10 files changed, 59 insertions(+), 50 deletions(-) diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index 0289f8e1d..1b063b4ea 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -59,11 +59,12 @@ class AttachmentController extends Controller /** * Update an uploaded attachment. - * @param int $attachmentId * @param Request $request + * @param int $attachmentId * @return mixed + * @throws \Illuminate\Validation\ValidationException */ - public function uploadUpdate($attachmentId, Request $request) + public function uploadUpdate(Request $request, $attachmentId) { $this->validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id', @@ -94,11 +95,12 @@ class AttachmentController extends Controller /** * Update the details of an existing file. - * @param $attachmentId * @param Request $request + * @param $attachmentId * @return Attachment|mixed + * @throws \Illuminate\Validation\ValidationException */ - public function update($attachmentId, Request $request) + public function update(Request $request, $attachmentId) { $this->validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id', @@ -161,11 +163,12 @@ class AttachmentController extends Controller /** * Update the attachment sorting. - * @param $pageId * @param Request $request + * @param $pageId * @return mixed + * @throws \Illuminate\Validation\ValidationException */ - public function sortForPage($pageId, Request $request) + public function sortForPage(Request $request, $pageId) { $this->validate($request, [ 'files' => 'required|array', diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 3b9738835..70090fed0 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -193,14 +193,14 @@ class RegisterController extends Controller /** * The callback for social login services. - * @param $socialDriver * @param Request $request + * @param string $socialDriver * @return RedirectResponse|Redirector * @throws SocialSignInException * @throws UserRegistrationException * @throws SocialDriverNotConfigured */ - public function socialCallback($socialDriver, Request $request) + public function socialCallback(Request $request, string $socialDriver) { if (!session()->has('social-callback')) { throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login'); diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index a7b9f597f..8799d264c 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -54,12 +54,12 @@ class UserInviteController extends Controller /** * Sets the password for an invited user and then grants them access. - * @param string $token * @param Request $request + * @param string $token * @return RedirectResponse|Redirector * @throws Exception */ - public function setPassword(string $token, Request $request) + public function setPassword(Request $request, string $token) { $this->validate($request, [ 'password' => 'required|min:8' diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index a990eed9a..7a36a6ca3 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -137,12 +137,12 @@ class BookController extends Controller /** * Display the specified book. - * @param $slug * @param Request $request + * @param string $slug * @return Response * @throws \BookStack\Exceptions\NotFoundException */ - public function show($slug, Request $request) + public function show(Request $request, string $slug) { $book = $this->entityRepo->getBySlug('book', $slug); $this->checkOwnablePermission('book-view', $book); @@ -247,11 +247,12 @@ class BookController extends Controller /** * Saves an array of sort mapping to pages and chapters. - * @param string $bookSlug * @param Request $request + * @param string $bookSlug * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws \BookStack\Exceptions\NotFoundException */ - public function saveSort($bookSlug, Request $request) + public function saveSort(Request $request, string $bookSlug) { $book = $this->entityRepo->getBySlug('book', $bookSlug); $this->checkOwnablePermission('book-update', $book); @@ -353,13 +354,13 @@ class BookController extends Controller /** * Set the restrictions for this book. - * @param $bookSlug * @param Request $request + * @param string $bookSlug * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \BookStack\Exceptions\NotFoundException * @throws \Throwable */ - public function permissions($bookSlug, Request $request) + public function permissions(Request $request, string $bookSlug) { $book = $this->entityRepo->getBySlug('book', $bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index bcf2e12df..15c3e93ad 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -242,13 +242,13 @@ class BookshelfController extends Controller /** * Set the permissions for this bookshelf. - * @param string $slug * @param Request $request + * @param string $slug * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \BookStack\Exceptions\NotFoundException * @throws \Throwable */ - public function permissions(string $slug, Request $request) + public function permissions(Request $request, string $slug) { $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); $this->checkOwnablePermission('restrictions-manage', $shelf); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index c19e45694..83d45e823 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -44,11 +44,13 @@ class ChapterController extends Controller /** * Store a newly created chapter in storage. - * @param $bookSlug - * @param Request $request + * @param Request $request + * @param string $bookSlug * @return Response + * @throws \BookStack\Exceptions\NotFoundException + * @throws \Illuminate\Validation\ValidationException */ - public function store($bookSlug, Request $request) + public function store(Request $request, string $bookSlug) { $this->validate($request, [ 'name' => 'required|string|max:255' @@ -103,13 +105,13 @@ class ChapterController extends Controller /** * Update the specified chapter in storage. - * @param Request $request - * @param $bookSlug - * @param $chapterSlug + * @param Request $request + * @param string $bookSlug + * @param string $chapterSlug * @return Response * @throws \BookStack\Exceptions\NotFoundException */ - public function update(Request $request, $bookSlug, $chapterSlug) + public function update(Request $request, string $bookSlug, string $chapterSlug) { $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); @@ -170,13 +172,13 @@ class ChapterController extends Controller /** * Perform the move action for a chapter. - * @param $bookSlug - * @param $chapterSlug * @param Request $request + * @param string $bookSlug + * @param string $chapterSlug * @return mixed * @throws \BookStack\Exceptions\NotFoundException */ - public function move($bookSlug, $chapterSlug, Request $request) + public function move(Request $request, string $bookSlug, string $chapterSlug) { $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); @@ -229,14 +231,14 @@ class ChapterController extends Controller /** * Set the restrictions for this chapter. - * @param $bookSlug - * @param $chapterSlug * @param Request $request + * @param string $bookSlug + * @param string $chapterSlug * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \BookStack\Exceptions\NotFoundException * @throws \Throwable */ - public function permissions($bookSlug, $chapterSlug, Request $request) + public function permissions(Request $request, string $bookSlug, string $chapterSlug) { $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); diff --git a/app/Http/Controllers/Images/ImageController.php b/app/Http/Controllers/Images/ImageController.php index 024003f87..79a23df27 100644 --- a/app/Http/Controllers/Images/ImageController.php +++ b/app/Http/Controllers/Images/ImageController.php @@ -47,13 +47,13 @@ class ImageController extends Controller /** * Update image details - * @param integer $id * @param Request $request + * @param integer $id * @return \Illuminate\Http\JsonResponse * @throws ImageUploadException * @throws \Exception */ - public function update($id, Request $request) + public function update(Request $request, $id) { $this->validate($request, [ 'name' => 'required|min:2|string' diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index ad1e32665..a6b158bbb 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -574,13 +574,14 @@ class PageController extends Controller /** * Does the action of moving the location of a page + * @param Request $request * @param string $bookSlug * @param string $pageSlug - * @param Request $request * @return mixed * @throws NotFoundException + * @throws \Throwable */ - public function move($bookSlug, $pageSlug, Request $request) + public function move(Request $request, string $bookSlug, string $pageSlug) { $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); @@ -632,13 +633,14 @@ class PageController extends Controller /** * Create a copy of a page within the requested target destination. + * @param Request $request * @param string $bookSlug * @param string $pageSlug - * @param Request $request * @return mixed * @throws NotFoundException + * @throws \Throwable */ - public function copy($bookSlug, $pageSlug, Request $request) + public function copy(Request $request, string $bookSlug, string $pageSlug) { $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-view', $page); @@ -696,7 +698,7 @@ class PageController extends Controller * @throws NotFoundException * @throws \Throwable */ - public function permissions($bookSlug, $pageSlug, Request $request) + public function permissions(Request $request, string $bookSlug, string $pageSlug) { $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $page); diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php index 9893d5993..f19e7e763 100644 --- a/app/Http/Controllers/PermissionController.php +++ b/app/Http/Controllers/PermissionController.php @@ -75,12 +75,13 @@ class PermissionController extends Controller /** * Updates a user role. - * @param $id * @param Request $request + * @param $id * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws PermissionsException + * @throws \Illuminate\Validation\ValidationException */ - public function updateRole($id, Request $request) + public function updateRole(Request $request, $id) { $this->checkPermission('user-roles-manage'); $this->validate($request, [ @@ -112,11 +113,11 @@ class PermissionController extends Controller /** * Delete a role from the system, * Migrate from a previous role if set. - * @param $id * @param Request $request + * @param $id * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function deleteRole($id, Request $request) + public function deleteRole(Request $request, $id) { $this->checkPermission('user-roles-manage'); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 156256cb9..4f4f98104 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -274,22 +274,22 @@ class UserController extends Controller /** * Update the user's preferred book-list display setting. - * @param $id * @param Request $request + * @param $id * @return \Illuminate\Http\RedirectResponse */ - public function switchBookView($id, Request $request) + public function switchBookView(Request $request, $id) { return $this->switchViewType($id, $request, 'books'); } /** * Update the user's preferred shelf-list display setting. - * @param $id * @param Request $request + * @param $id * @return \Illuminate\Http\RedirectResponse */ - public function switchShelfView($id, Request $request) + public function switchShelfView(Request $request, $id) { return $this->switchViewType($id, $request, 'bookshelves'); } @@ -319,12 +319,12 @@ class UserController extends Controller /** * Change the stored sort type for a particular view. + * @param Request $request * @param string $id * @param string $type - * @param Request $request * @return \Illuminate\Http\RedirectResponse */ - public function changeSort(string $id, string $type, Request $request) + public function changeSort(Request $request, string $id, string $type) { $validSortTypes = ['books', 'bookshelves']; if (!in_array($type, $validSortTypes)) { @@ -335,12 +335,12 @@ class UserController extends Controller /** * Update the stored section expansion preference for the given user. + * @param Request $request * @param string $id * @param string $key - * @param Request $request * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response */ - public function updateExpansionPreference(string $id, string $key, Request $request) + public function updateExpansionPreference(Request $request, string $id, string $key) { $this->checkPermissionOrCurrentUser('users-manage', $id); $keyWhitelist = ['home-details']; From d28abf24d404fe4eb281866e6d37d704602e87a0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 22:33:27 +0100 Subject: [PATCH 026/257] Split out export actions into own controllers --- app/Http/Controllers/BookController.php | 40 -------- app/Http/Controllers/BookExportController.php | 74 ++++++++++++++ app/Http/Controllers/ChapterController.php | 44 +-------- .../Controllers/ChapterExportController.php | 78 +++++++++++++++ app/Http/Controllers/PageController.php | 98 ++++++------------- app/Http/Controllers/PageExportController.php | 81 +++++++++++++++ routes/web.php | 18 ++-- 7 files changed, 272 insertions(+), 161 deletions(-) create mode 100644 app/Http/Controllers/BookExportController.php create mode 100644 app/Http/Controllers/ChapterExportController.php create mode 100644 app/Http/Controllers/PageExportController.php diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 7a36a6ca3..c6ff9ff62 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -16,7 +16,6 @@ class BookController extends Controller protected $entityRepo; protected $userRepo; - protected $exportService; protected $entityContextManager; protected $imageRepo; @@ -24,20 +23,17 @@ class BookController extends Controller * BookController constructor. * @param EntityRepo $entityRepo * @param UserRepo $userRepo - * @param ExportService $exportService * @param EntityContextManager $entityContextManager * @param ImageRepo $imageRepo */ public function __construct( EntityRepo $entityRepo, UserRepo $userRepo, - ExportService $exportService, EntityContextManager $entityContextManager, ImageRepo $imageRepo ) { $this->entityRepo = $entityRepo; $this->userRepo = $userRepo; - $this->exportService = $exportService; $this->entityContextManager = $entityContextManager; $this->imageRepo = $imageRepo; parent::__construct(); @@ -369,42 +365,6 @@ class BookController extends Controller return redirect($book->getUrl()); } - /** - * Export a book as a PDF file. - * @param string $bookSlug - * @return mixed - */ - public function exportPdf($bookSlug) - { - $book = $this->entityRepo->getBySlug('book', $bookSlug); - $pdfContent = $this->exportService->bookToPdf($book); - return $this->downloadResponse($pdfContent, $bookSlug . '.pdf'); - } - - /** - * Export a book as a contained HTML file. - * @param string $bookSlug - * @return mixed - */ - public function exportHtml($bookSlug) - { - $book = $this->entityRepo->getBySlug('book', $bookSlug); - $htmlContent = $this->exportService->bookToContainedHtml($book); - return $this->downloadResponse($htmlContent, $bookSlug . '.html'); - } - - /** - * Export a book as a plain text file. - * @param $bookSlug - * @return mixed - */ - public function exportPlainText($bookSlug) - { - $book = $this->entityRepo->getBySlug('book', $bookSlug); - $textContent = $this->exportService->bookToPlainText($book); - return $this->downloadResponse($textContent, $bookSlug . '.txt'); - } - /** * Common actions to run on book update. * Handles updating the cover image. diff --git a/app/Http/Controllers/BookExportController.php b/app/Http/Controllers/BookExportController.php new file mode 100644 index 000000000..d0294ec35 --- /dev/null +++ b/app/Http/Controllers/BookExportController.php @@ -0,0 +1,74 @@ +entityRepo = $entityRepo; + $this->exportService = $exportService; + parent::__construct(); + } + + /** + * Export a book as a PDF file. + * @param string $bookSlug + * @return mixed + * @throws NotFoundException + * @throws Throwable + */ + public function pdf(string $bookSlug) + { + $book = $this->entityRepo->getBySlug('book', $bookSlug); + $pdfContent = $this->exportService->bookToPdf($book); + return $this->downloadResponse($pdfContent, $bookSlug . '.pdf'); + } + + /** + * Export a book as a contained HTML file. + * @param string $bookSlug + * @return mixed + * @throws NotFoundException + * @throws Throwable + */ + public function html(string $bookSlug) + { + $book = $this->entityRepo->getBySlug('book', $bookSlug); + $htmlContent = $this->exportService->bookToContainedHtml($book); + return $this->downloadResponse($htmlContent, $bookSlug . '.html'); + } + + /** + * Export a book as a plain text file. + * @param $bookSlug + * @return mixed + * @throws NotFoundException + */ + public function plainText(string $bookSlug) + { + $book = $this->entityRepo->getBySlug('book', $bookSlug); + $textContent = $this->exportService->bookToPlainText($book); + return $this->downloadResponse($textContent, $bookSlug . '.txt'); + } +} diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 83d45e823..b90a6dcb2 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -13,19 +13,16 @@ class ChapterController extends Controller protected $userRepo; protected $entityRepo; - protected $exportService; /** * ChapterController constructor. * @param EntityRepo $entityRepo * @param UserRepo $userRepo - * @param \BookStack\Entities\ExportService $exportService */ - public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService) + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo) { $this->entityRepo = $entityRepo; $this->userRepo = $userRepo; - $this->exportService = $exportService; parent::__construct(); } @@ -246,43 +243,4 @@ class ChapterController extends Controller session()->flash('success', trans('entities.chapters_permissions_success')); return redirect($chapter->getUrl()); } - - /** - * Exports a chapter to pdf . - * @param string $bookSlug - * @param string $chapterSlug - * @return \Illuminate\Http\Response - */ - public function exportPdf($bookSlug, $chapterSlug) - { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); - $pdfContent = $this->exportService->chapterToPdf($chapter); - return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf'); - } - - /** - * Export a chapter to a self-contained HTML file. - * @param string $bookSlug - * @param string $chapterSlug - * @return \Illuminate\Http\Response - */ - public function exportHtml($bookSlug, $chapterSlug) - { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); - $containedHtml = $this->exportService->chapterToContainedHtml($chapter); - return $this->downloadResponse($containedHtml, $chapterSlug . '.html'); - } - - /** - * Export a chapter to a simple plaintext .txt file. - * @param string $bookSlug - * @param string $chapterSlug - * @return \Illuminate\Http\Response - */ - public function exportPlainText($bookSlug, $chapterSlug) - { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); - $chapterText = $this->exportService->chapterToPlainText($chapter); - return $this->downloadResponse($chapterText, $chapterSlug . '.txt'); - } } diff --git a/app/Http/Controllers/ChapterExportController.php b/app/Http/Controllers/ChapterExportController.php new file mode 100644 index 000000000..de46baa12 --- /dev/null +++ b/app/Http/Controllers/ChapterExportController.php @@ -0,0 +1,78 @@ +entityRepo = $entityRepo; + $this->exportService = $exportService; + parent::__construct(); + } + + /** + * Exports a chapter to pdf . + * @param string $bookSlug + * @param string $chapterSlug + * @return Response + * @throws NotFoundException + * @throws Throwable + */ + public function pdf(string $bookSlug, string $chapterSlug) + { + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $pdfContent = $this->exportService->chapterToPdf($chapter); + return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf'); + } + + /** + * Export a chapter to a self-contained HTML file. + * @param string $bookSlug + * @param string $chapterSlug + * @return Response + * @throws NotFoundException + * @throws Throwable + */ + public function html(string $bookSlug, string $chapterSlug) + { + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $containedHtml = $this->exportService->chapterToContainedHtml($chapter); + return $this->downloadResponse($containedHtml, $chapterSlug . '.html'); + } + + /** + * Export a chapter to a simple plaintext .txt file. + * @param string $bookSlug + * @param string $chapterSlug + * @return Response + * @throws NotFoundException + */ + public function plainText(string $bookSlug, string $chapterSlug) + { + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapterText = $this->exportService->chapterToPlainText($chapter); + return $this->downloadResponse($chapterText, $chapterSlug . '.txt'); + } +} diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index a6b158bbb..62a706c0d 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -2,32 +2,34 @@ use Activity; use BookStack\Auth\UserRepo; -use BookStack\Entities\Repos\EntityRepo; -use BookStack\Entities\ExportService; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\NotFoundException; +use Exception; use GatherContent\Htmldiff\Htmldiff; +use Illuminate\Contracts\View\Factory; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Routing\Redirector; +use Illuminate\View\View; +use Throwable; use Views; class PageController extends Controller { protected $pageRepo; - protected $exportService; protected $userRepo; /** * PageController constructor. - * @param \BookStack\Entities\Repos\PageRepo $pageRepo - * @param \BookStack\Entities\ExportService $exportService + * @param PageRepo $pageRepo * @param UserRepo $userRepo */ - public function __construct(PageRepo $pageRepo, ExportService $exportService, UserRepo $userRepo) + public function __construct(PageRepo $pageRepo, UserRepo $userRepo) { $this->pageRepo = $pageRepo; - $this->exportService = $exportService; $this->userRepo = $userRepo; parent::__construct(); } @@ -101,7 +103,7 @@ class PageController extends Controller * Show form to continue editing a draft page. * @param string $bookSlug * @param int $pageId - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @return Factory|View */ public function editDraft($bookSlug, $pageId) { @@ -199,7 +201,7 @@ class PageController extends Controller /** * Get page from an ajax request. * @param int $pageId - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function getPageAjax($pageId) { @@ -276,7 +278,7 @@ class PageController extends Controller * Save a draft update as a revision. * @param Request $request * @param int $pageId - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function saveDraft(Request $request, $pageId) { @@ -304,7 +306,7 @@ class PageController extends Controller * Redirect from a special link url which * uses the page id rather than the name. * @param int $pageId - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return RedirectResponse|Redirector */ public function redirectFromLink($pageId) { @@ -316,7 +318,7 @@ class PageController extends Controller * Show the deletion page for the specified page. * @param string $bookSlug * @param string $pageSlug - * @return \Illuminate\View\View + * @return View */ public function showDelete($bookSlug, $pageSlug) { @@ -331,7 +333,7 @@ class PageController extends Controller * Show the deletion page for the specified page. * @param string $bookSlug * @param int $pageId - * @return \Illuminate\View\View + * @return View * @throws NotFoundException */ public function showDeleteDraft($bookSlug, $pageId) @@ -382,7 +384,7 @@ class PageController extends Controller * Shows the last revisions for this page. * @param string $bookSlug * @param string $pageSlug - * @return \Illuminate\View\View + * @return View * @throws NotFoundException */ public function showRevisions($bookSlug, $pageSlug) @@ -397,7 +399,7 @@ class PageController extends Controller * @param string $bookSlug * @param string $pageSlug * @param int $revisionId - * @return \Illuminate\View\View + * @return View */ public function showRevision($bookSlug, $pageSlug, $revisionId) { @@ -423,7 +425,7 @@ class PageController extends Controller * @param string $bookSlug * @param string $pageSlug * @param int $revisionId - * @return \Illuminate\View\View + * @return View */ public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) { @@ -453,7 +455,7 @@ class PageController extends Controller * @param string $bookSlug * @param string $pageSlug * @param int $revisionId - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return RedirectResponse|Redirector */ public function restoreRevision($bookSlug, $pageSlug, $revisionId) { @@ -470,9 +472,9 @@ class PageController extends Controller * @param string $bookSlug * @param string $pageSlug * @param int $revId + * @return RedirectResponse|Redirector + *@throws BadRequestException * @throws NotFoundException - * @throws BadRequestException - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ public function destroyRevision($bookSlug, $pageSlug, $revId) { @@ -498,51 +500,9 @@ class PageController extends Controller return redirect($page->getUrl('/revisions')); } - /** - * Exports a page to a PDF. - * https://github.com/barryvdh/laravel-dompdf - * @param string $bookSlug - * @param string $pageSlug - * @return \Illuminate\Http\Response - */ - public function exportPdf($bookSlug, $pageSlug) - { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); - $page->html = $this->pageRepo->renderPage($page); - $pdfContent = $this->exportService->pageToPdf($page); - return $this->downloadResponse($pdfContent, $pageSlug . '.pdf'); - } - - /** - * Export a page to a self-contained HTML file. - * @param string $bookSlug - * @param string $pageSlug - * @return \Illuminate\Http\Response - */ - public function exportHtml($bookSlug, $pageSlug) - { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); - $page->html = $this->pageRepo->renderPage($page); - $containedHtml = $this->exportService->pageToContainedHtml($page); - return $this->downloadResponse($containedHtml, $pageSlug . '.html'); - } - - /** - * Export a page to a simple plaintext .txt file. - * @param string $bookSlug - * @param string $pageSlug - * @return \Illuminate\Http\Response - */ - public function exportPlainText($bookSlug, $pageSlug) - { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); - $pageText = $this->exportService->pageToPlainText($page); - return $this->downloadResponse($pageText, $pageSlug . '.txt'); - } - /** * Show a listing of recently created pages - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @return Factory|View */ public function showRecentlyUpdated() { @@ -579,7 +539,7 @@ class PageController extends Controller * @param string $pageSlug * @return mixed * @throws NotFoundException - * @throws \Throwable + * @throws Throwable */ public function move(Request $request, string $bookSlug, string $pageSlug) { @@ -599,7 +559,7 @@ class PageController extends Controller try { $parent = $this->pageRepo->getById($entityType, $entityId); - } catch (\Exception $e) { + } catch (Exception $e) { session()->flash(trans('entities.selected_book_chapter_not_found')); return redirect()->back(); } @@ -638,7 +598,7 @@ class PageController extends Controller * @param string $pageSlug * @return mixed * @throws NotFoundException - * @throws \Throwable + * @throws Throwable */ public function copy(Request $request, string $bookSlug, string $pageSlug) { @@ -655,7 +615,7 @@ class PageController extends Controller try { $parent = $this->pageRepo->getById($entityType, $entityId); - } catch (\Exception $e) { + } catch (Exception $e) { session()->flash(trans('entities.selected_book_chapter_not_found')); return redirect()->back(); } @@ -675,7 +635,7 @@ class PageController extends Controller * Show the Permissions view. * @param string $bookSlug * @param string $pageSlug - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @return Factory|View * @throws NotFoundException */ public function showPermissions($bookSlug, $pageSlug) @@ -694,9 +654,9 @@ class PageController extends Controller * @param string $bookSlug * @param string $pageSlug * @param Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return RedirectResponse|Redirector * @throws NotFoundException - * @throws \Throwable + * @throws Throwable */ public function permissions(Request $request, string $bookSlug, string $pageSlug) { diff --git a/app/Http/Controllers/PageExportController.php b/app/Http/Controllers/PageExportController.php new file mode 100644 index 000000000..c5b796ec8 --- /dev/null +++ b/app/Http/Controllers/PageExportController.php @@ -0,0 +1,81 @@ +pageRepo = $pageRepo; + $this->exportService = $exportService; + parent::__construct(); + } + + /** + * Exports a page to a PDF. + * https://github.com/barryvdh/laravel-dompdf + * @param string $bookSlug + * @param string $pageSlug + * @return Response + * @throws NotFoundException + * @throws Throwable + */ + public function pdf(string $bookSlug, string $pageSlug) + { + $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page->html = $this->pageRepo->renderPage($page); + $pdfContent = $this->exportService->pageToPdf($page); + return $this->downloadResponse($pdfContent, $pageSlug . '.pdf'); + } + + /** + * Export a page to a self-contained HTML file. + * @param string $bookSlug + * @param string $pageSlug + * @return Response + * @throws NotFoundException + * @throws Throwable + */ + public function html(string $bookSlug, string $pageSlug) + { + $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page->html = $this->pageRepo->renderPage($page); + $containedHtml = $this->exportService->pageToContainedHtml($page); + return $this->downloadResponse($containedHtml, $pageSlug . '.html'); + } + + /** + * Export a page to a simple plaintext .txt file. + * @param string $bookSlug + * @param string $pageSlug + * @return Response + * @throws NotFoundException + */ + public function plainText(string $bookSlug, string $pageSlug) + { + $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $pageText = $this->exportService->pageToPlainText($page); + return $this->downloadResponse($pageText, $pageSlug . '.txt'); + } +} diff --git a/routes/web.php b/routes/web.php index d9fdc7455..0277c1bb4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -47,9 +47,9 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{slug}/delete', 'BookController@showDelete'); Route::get('/{bookSlug}/sort', 'BookController@sort'); Route::put('/{bookSlug}/sort', 'BookController@saveSort'); - Route::get('/{bookSlug}/export/html', 'BookController@exportHtml'); - Route::get('/{bookSlug}/export/pdf', 'BookController@exportPdf'); - Route::get('/{bookSlug}/export/plaintext', 'BookController@exportPlainText'); + Route::get('/{bookSlug}/export/html', 'BookExportController@html'); + Route::get('/{bookSlug}/export/pdf', 'BookExportController@pdf'); + Route::get('/{bookSlug}/export/plaintext', 'BookExportController@plainText'); // Pages Route::get('/{bookSlug}/create-page', 'PageController@create'); @@ -57,9 +57,9 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft'); Route::post('/{bookSlug}/draft/{pageId}', 'PageController@store'); Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show'); - Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf'); - Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml'); - Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText'); + Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageExportController@pdf'); + Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageExportController@html'); + Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageExportController@plainText'); Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit'); Route::get('/{bookSlug}/page/{pageSlug}/move', 'PageController@showMove'); Route::put('/{bookSlug}/page/{pageSlug}/move', 'PageController@move'); @@ -91,9 +91,9 @@ Route::group(['middleware' => 'auth'], function () { Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move'); Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showPermissions'); - Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf'); - Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml'); - Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText'); + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterExportController@pdf'); + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterExportController@html'); + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterExportController@plainText'); Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@permissions'); Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); From 60d0f96cd754a85c637a5f34dcc0341f4ef72a46 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Sep 2019 23:28:23 +0100 Subject: [PATCH 027/257] Extracted some methods into a BookRepo --- app/Auth/Permissions/PermissionsRepo.php | 2 +- app/Entities/Bookshelf.php | 2 +- app/Entities/Repos/BookRepo.php | 63 ++++++++ app/Entities/Repos/EntityRepo.php | 74 ++++----- app/Entities/Repos/PageRepo.php | 4 +- app/Http/Controllers/BookController.php | 143 ++++++++++-------- app/Http/Controllers/BookExportController.php | 18 +-- app/Http/Controllers/BookshelfController.php | 16 +- app/Http/Controllers/ChapterController.php | 22 +-- .../Controllers/ChapterExportController.php | 6 +- app/Http/Controllers/PageController.php | 40 ++--- app/Http/Controllers/PageExportController.php | 6 +- app/helpers.php | 6 +- tests/SharedTestHelpers.php | 4 +- 14 files changed, 233 insertions(+), 173 deletions(-) create mode 100644 app/Entities/Repos/BookRepo.php diff --git a/app/Auth/Permissions/PermissionsRepo.php b/app/Auth/Permissions/PermissionsRepo.php index e7840ff76..56ef19301 100644 --- a/app/Auth/Permissions/PermissionsRepo.php +++ b/app/Auth/Permissions/PermissionsRepo.php @@ -137,7 +137,7 @@ class PermissionsRepo // Prevent deleting admin role or default registration role. if ($role->system_name && in_array($role->system_name, $this->systemRoles)) { throw new PermissionsException(trans('errors.role_system_cannot_be_deleted')); - } else if ($role->id == setting('registration-role')) { + } else if ($role->id === intval(setting('registration-role'))) { throw new PermissionsException(trans('errors.role_registration_default_cannot_delete')); } diff --git a/app/Entities/Bookshelf.php b/app/Entities/Bookshelf.php index db6685688..745611ba7 100644 --- a/app/Entities/Bookshelf.php +++ b/app/Entities/Bookshelf.php @@ -100,7 +100,7 @@ class Bookshelf extends Entity * @param Book $book * @return bool */ - public function contains(Book $book) + public function contains(Book $book): bool { return $this->books()->where('id', '=', $book->id)->count() > 0; } diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php new file mode 100644 index 000000000..c82780fea --- /dev/null +++ b/app/Entities/Repos/BookRepo.php @@ -0,0 +1,63 @@ +getEntityBySlug('book', $slug); + return $book; + } + + /** + * Append a Book to a BookShelf. + * @param Bookshelf $shelf + * @param Book $book + */ + public function appendBookToShelf(Bookshelf $shelf, Book $book) + { + if ($shelf->contains($book)) { + return; + } + + $maxOrder = $shelf->books()->max('order'); + $shelf->books()->attach($book->id, ['order' => $maxOrder + 1]); + } + + /** + * Destroy the provided book and all its child entities. + * @param Book $book + * @throws NotifyException + * @throws \Throwable + */ + public function destroyBook(Book $book) + { + foreach ($book->pages as $page) { + $this->destroyPage($page); + } + + foreach ($book->chapters as $chapter) { + $this->destroyChapter($chapter); + } + + $this->destroyEntityCommonRelations($book); + $book->delete(); + } + +} \ No newline at end of file diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index 996873bcc..925e3c30d 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -20,6 +20,7 @@ use DOMNode; use DOMXPath; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Throwable; @@ -79,7 +80,7 @@ class EntityRepo * @param string $type * @param bool $allowDrafts * @param string $permission - * @return \Illuminate\Database\Query\Builder + * @return QueryBuilder */ protected function entityQuery($type, $allowDrafts = false, $permission = 'view') { @@ -142,25 +143,29 @@ class EntityRepo * Get an entity by its url slug. * @param string $type * @param string $slug - * @param string|bool $bookSlug + * @param string|null $bookSlug * @return Entity * @throws NotFoundException */ - public function getBySlug($type, $slug, $bookSlug = false) + public function getEntityBySlug(string $type, string $slug, string $bookSlug = null): Entity { - $q = $this->entityQuery($type)->where('slug', '=', $slug); + $type = strtolower($type); + $query = $this->entityQuery($type)->where('slug', '=', $slug); - if (strtolower($type) === 'chapter' || strtolower($type) === 'page') { - $q = $q->where('book_id', '=', function ($query) use ($bookSlug) { + if ($type === 'chapter' || $type === 'page') { + $query = $query->where('book_id', '=', function (QueryBuilder $query) use ($bookSlug) { $query->select('id') ->from($this->entityProvider->book->getTable()) ->where('slug', '=', $bookSlug)->limit(1); }); } - $entity = $q->first(); + + $entity = $query->first(); + if ($entity === null) { - throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found')); + throw new NotFoundException(trans('errors.' . $type . '_not_found')); } + return $entity; } @@ -507,23 +512,27 @@ class EntityRepo } - /** * Create a new entity from request input. * Used for books and chapters. * @param string $type * @param array $input - * @param bool|Book $book + * @param Book|null $book * @return Entity + * @throws Throwable */ - public function createFromInput($type, $input = [], $book = false) + public function createFromInput(string $type, array $input = [], Book $book = null) { - $isChapter = strtolower($type) === 'chapter'; $entityModel = $this->entityProvider->get($type)->newInstance($input); - $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $isChapter ? $book->id : false); + $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $book ? $book->id : false); $entityModel->created_by = user()->id; $entityModel->updated_by = user()->id; - $isChapter ? $book->chapters()->save($entityModel) : $entityModel->save(); + + if ($book) { + $entityModel->book_id = $book->id; + } + + $entityModel->save(); if (isset($input['tags'])) { $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); @@ -541,12 +550,14 @@ class EntityRepo * @param Entity $entityModel * @param array $input * @return Entity + * @throws Throwable */ - public function updateFromInput($type, Entity $entityModel, $input = []) + public function updateFromInput(string $type, Entity $entityModel, array $input = []) { if ($entityModel->name !== $input['name']) { $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id); } + $entityModel->fill($input); $entityModel->updated_by = user()->id; $entityModel->save(); @@ -582,21 +593,6 @@ class EntityRepo $shelf->books()->sync($syncData); } - /** - * Append a Book to a BookShelf. - * @param Bookshelf $shelf - * @param Book $book - */ - public function appendBookToShelf(Bookshelf $shelf, Book $book) - { - if ($shelf->contains($book)) { - return; - } - - $maxOrder = $shelf->books()->max('order'); - $shelf->books()->attach($book->id, ['order' => $maxOrder + 1]); - } - /** * Change the book that an entity belongs to. * @param string $type @@ -815,24 +811,6 @@ class EntityRepo $shelf->delete(); } - /** - * Destroy the provided book and all its child entities. - * @param Book $book - * @throws NotifyException - * @throws Throwable - */ - public function destroyBook(Book $book) - { - foreach ($book->pages as $page) { - $this->destroyPage($page); - } - foreach ($book->chapters as $chapter) { - $this->destroyChapter($chapter); - } - $this->destroyEntityCommonRelations($book); - $book->delete(); - } - /** * Destroy a chapter and its relations. * @param Chapter $chapter diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index a7daf549b..ba59d1277 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -21,9 +21,9 @@ class PageRepo extends EntityRepo * @return Page * @throws \BookStack\Exceptions\NotFoundException */ - public function getPageBySlug(string $pageSlug, string $bookSlug) + public function getBySlug(string $pageSlug, string $bookSlug) { - return $this->getBySlug('page', $pageSlug, $bookSlug); + return $this->getEntityBySlug('page', $pageSlug, $bookSlug); } /** diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index c6ff9ff62..97659ed96 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -4,35 +4,45 @@ use Activity; use BookStack\Auth\UserRepo; use BookStack\Entities\Book; use BookStack\Entities\EntityContextManager; +use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\EntityRepo; use BookStack\Entities\ExportService; +use BookStack\Exceptions\ImageUploadException; +use BookStack\Exceptions\NotFoundException; +use BookStack\Exceptions\NotifyException; use BookStack\Uploads\ImageRepo; +use Illuminate\Contracts\View\Factory; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Routing\Redirector; +use Illuminate\Validation\ValidationException; +use Illuminate\View\View; +use Throwable; use Views; class BookController extends Controller { - protected $entityRepo; + protected $bookRepo; protected $userRepo; protected $entityContextManager; protected $imageRepo; /** * BookController constructor. - * @param EntityRepo $entityRepo + * @param BookRepo $bookRepo * @param UserRepo $userRepo * @param EntityContextManager $entityContextManager * @param ImageRepo $imageRepo */ public function __construct( - EntityRepo $entityRepo, + BookRepo $bookRepo, UserRepo $userRepo, EntityContextManager $entityContextManager, ImageRepo $imageRepo ) { - $this->entityRepo = $entityRepo; + $this->bookRepo = $bookRepo; $this->userRepo = $userRepo; $this->entityContextManager = $entityContextManager; $this->imageRepo = $imageRepo; @@ -54,10 +64,10 @@ class BookController extends Controller 'updated_at' => trans('common.sort_updated_at'), ]; - $books = $this->entityRepo->getAllPaginated('book', 18, $sort, $order); - $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false; - $popular = $this->entityRepo->getPopular('book', 4, 0); - $new = $this->entityRepo->getRecentlyCreated('book', 4, 0); + $books = $this->bookRepo->getAllPaginated('book', 18, $sort, $order); + $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed('book', 4, 0) : false; + $popular = $this->bookRepo->getPopular('book', 4, 0); + $new = $this->bookRepo->getRecentlyCreated('book', 4, 0); $this->entityContextManager->clearShelfContext(); @@ -78,13 +88,13 @@ class BookController extends Controller * Show the form for creating a new book. * @param string $shelfSlug * @return Response - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ public function create(string $shelfSlug = null) { $bookshelf = null; if ($shelfSlug !== null) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug); + $bookshelf = $this->bookRepo->getEntityBySlug('bookshelf', $shelfSlug); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } @@ -101,8 +111,9 @@ class BookController extends Controller * @param Request $request * @param string $shelfSlug * @return Response - * @throws \BookStack\Exceptions\NotFoundException - * @throws \BookStack\Exceptions\ImageUploadException + * @throws NotFoundException + * @throws ImageUploadException + * @throws ValidationException */ public function store(Request $request, string $shelfSlug = null) { @@ -115,16 +126,16 @@ class BookController extends Controller $bookshelf = null; if ($shelfSlug !== null) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug); + $bookshelf = $this->bookRepo->getEntityBySlug('bookshelf', $shelfSlug); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } - $book = $this->entityRepo->createFromInput('book', $request->all()); + $book = $this->bookRepo->createFromInput('book', $request->all()); $this->bookUpdateActions($book, $request); Activity::add($book, 'book_create', $book->id); if ($bookshelf) { - $this->entityRepo->appendBookToShelf($bookshelf, $book); + $this->bookRepo->appendBookToShelf($bookshelf, $book); Activity::add($bookshelf, 'bookshelf_update'); } @@ -136,14 +147,14 @@ class BookController extends Controller * @param Request $request * @param string $slug * @return Response - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ public function show(Request $request, string $slug) { - $book = $this->entityRepo->getBySlug('book', $slug); + $book = $this->bookRepo->getBySlug($slug); $this->checkOwnablePermission('book-view', $book); - $bookChildren = $this->entityRepo->getBookChildren($book); + $bookChildren = $this->bookRepo->getBookChildren($book); Views::add($book); if ($request->has('shelf')) { @@ -161,12 +172,13 @@ class BookController extends Controller /** * Show the form for editing the specified book. - * @param $slug + * @param string $slug * @return Response + * @throws NotFoundException */ - public function edit($slug) + public function edit(string $slug) { - $book = $this->entityRepo->getBySlug('book', $slug); + $book = $this->bookRepo->getBySlug($slug); $this->checkOwnablePermission('book-update', $book); $this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()])); return view('books.edit', ['book' => $book, 'current' => $book]); @@ -175,14 +187,15 @@ class BookController extends Controller /** * Update the specified book in storage. * @param Request $request - * @param $slug + * @param string $slug * @return Response - * @throws \BookStack\Exceptions\ImageUploadException - * @throws \BookStack\Exceptions\NotFoundException + * @throws ImageUploadException + * @throws NotFoundException + * @throws ValidationException */ public function update(Request $request, string $slug) { - $book = $this->entityRepo->getBySlug('book', $slug); + $book = $this->bookRepo->getBySlug($slug); $this->checkOwnablePermission('book-update', $book); $this->validate($request, [ 'name' => 'required|string|max:255', @@ -190,7 +203,7 @@ class BookController extends Controller 'image' => $this->imageRepo->getImageValidationRules(), ]); - $book = $this->entityRepo->updateFromInput('book', $book, $request->all()); + $book = $this->bookRepo->updateFromInput('book', $book, $request->all()); $this->bookUpdateActions($book, $request); Activity::add($book, 'book_update', $book->id); @@ -200,29 +213,30 @@ class BookController extends Controller /** * Shows the page to confirm deletion - * @param $bookSlug - * @return \Illuminate\View\View + * @param string $bookSlug + * @return View + * @throws NotFoundException */ - public function showDelete($bookSlug) + public function showDelete(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); - $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()])); + $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()])); return view('books.delete', ['book' => $book, 'current' => $book]); } /** * Shows the view which allows pages to be re-ordered and sorted. * @param string $bookSlug - * @return \Illuminate\View\View - * @throws \BookStack\Exceptions\NotFoundException + * @return View + * @throws NotFoundException */ - public function sort($bookSlug) + public function sort(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-update', $book); - $bookChildren = $this->entityRepo->getBookChildren($book, true); + $bookChildren = $this->bookRepo->getBookChildren($book, true); $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()])); return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); @@ -231,13 +245,14 @@ class BookController extends Controller /** * Shows the sort box for a single book. * Used via AJAX when loading in extra books to a sort. - * @param $bookSlug - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param string $bookSlug + * @return Factory|View + * @throws NotFoundException */ - public function getSortItem($bookSlug) + public function getSortItem(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); - $bookChildren = $this->entityRepo->getBookChildren($book); + $book = $this->bookRepo->getBySlug($bookSlug); + $bookChildren = $this->bookRepo->getBookChildren($book); return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); } @@ -245,12 +260,12 @@ class BookController extends Controller * Saves an array of sort mapping to pages and chapters. * @param Request $request * @param string $bookSlug - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \BookStack\Exceptions\NotFoundException + * @return RedirectResponse|Redirector + * @throws NotFoundException */ public function saveSort(Request $request, string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-update', $book); // Return if no map sent @@ -265,7 +280,7 @@ class BookController extends Controller // Load models into map $sortMap->each(function ($mapItem) use ($bookIdsInvolved) { $mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter'); - $mapItem->model = $this->entityRepo->getById($mapItem->type, $mapItem->id); + $mapItem->model = $this->bookRepo->getById($mapItem->type, $mapItem->id); // Store source and target books $bookIdsInvolved->push(intval($mapItem->model->book_id)); $bookIdsInvolved->push(intval($mapItem->book)); @@ -273,7 +288,7 @@ class BookController extends Controller // Get the books involved in the sort $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); - $booksInvolved = $this->entityRepo->getManyById('book', $bookIdsInvolved, false, true); + $booksInvolved = $this->bookRepo->getManyById('book', $bookIdsInvolved, false, true); // Throw permission error if invalid ids or inaccessible books given. if (count($bookIdsInvolved) !== count($booksInvolved)) { $this->showPermissionError(); @@ -292,7 +307,7 @@ class BookController extends Controller $chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter; if ($bookChanged) { - $this->entityRepo->changeBook($mapItem->type, $mapItem->book, $model); + $this->bookRepo->changeBook($mapItem->type, $mapItem->book, $model); } if ($chapterChanged) { $model->chapter_id = intval($mapItem->parentChapter); @@ -306,7 +321,7 @@ class BookController extends Controller // Rebuild permissions and add activity for involved books. $booksInvolved->each(function (Book $book) { - $this->entityRepo->buildJointPermissionsForBook($book); + $this->bookRepo->buildJointPermissionsForBook($book); Activity::add($book, 'book_sort', $book->id); }); @@ -315,31 +330,35 @@ class BookController extends Controller /** * Remove the specified book from storage. - * @param $bookSlug + * @param string $bookSlug * @return Response + * @throws NotFoundException + * @throws Throwable + * @throws NotifyException */ - public function destroy($bookSlug) + public function destroy(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); Activity::addMessage('book_delete', 0, $book->name); if ($book->cover) { $this->imageRepo->destroyImage($book->cover); } - $this->entityRepo->destroyBook($book); + $this->bookRepo->destroyBook($book); return redirect('/books'); } /** * Show the Restrictions view. - * @param $bookSlug - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param string $bookSlug + * @return Factory|View + * @throws NotFoundException */ - public function showPermissions($bookSlug) + public function showPermissions(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); $roles = $this->userRepo->getRestrictableRoles(); return view('books.permissions', [ @@ -352,15 +371,15 @@ class BookController extends Controller * Set the restrictions for this book. * @param Request $request * @param string $bookSlug - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \BookStack\Exceptions\NotFoundException - * @throws \Throwable + * @return RedirectResponse|Redirector + * @throws NotFoundException + * @throws Throwable */ public function permissions(Request $request, string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); - $this->entityRepo->updateEntityPermissionsFromRequest($request, $book); + $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); session()->flash('success', trans('entities.books_permissions_updated')); return redirect($book->getUrl()); } @@ -370,7 +389,7 @@ class BookController extends Controller * Handles updating the cover image. * @param Book $book * @param Request $request - * @throws \BookStack\Exceptions\ImageUploadException + * @throws ImageUploadException */ protected function bookUpdateActions(Book $book, Request $request) { diff --git a/app/Http/Controllers/BookExportController.php b/app/Http/Controllers/BookExportController.php index d0294ec35..ae3f56b81 100644 --- a/app/Http/Controllers/BookExportController.php +++ b/app/Http/Controllers/BookExportController.php @@ -3,16 +3,16 @@ namespace BookStack\Http\Controllers; use BookStack\Entities\ExportService; -use BookStack\Entities\Repos\EntityRepo; +use BookStack\Entities\Repos\BookRepo; use BookStack\Exceptions\NotFoundException; use Throwable; class BookExportController extends Controller { /** - * @var EntityRepo + * @var BookRepo */ - protected $entityRepo; + protected $bookRepo; /** * @var ExportService @@ -21,12 +21,12 @@ class BookExportController extends Controller /** * BookExportController constructor. - * @param EntityRepo $entityRepo + * @param BookRepo $bookRepo * @param ExportService $exportService */ - public function __construct(EntityRepo $entityRepo, ExportService $exportService) + public function __construct(BookRepo $bookRepo, ExportService $exportService) { - $this->entityRepo = $entityRepo; + $this->bookRepo = $bookRepo; $this->exportService = $exportService; parent::__construct(); } @@ -40,7 +40,7 @@ class BookExportController extends Controller */ public function pdf(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $pdfContent = $this->exportService->bookToPdf($book); return $this->downloadResponse($pdfContent, $bookSlug . '.pdf'); } @@ -54,7 +54,7 @@ class BookExportController extends Controller */ public function html(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $htmlContent = $this->exportService->bookToContainedHtml($book); return $this->downloadResponse($htmlContent, $bookSlug . '.html'); } @@ -67,7 +67,7 @@ class BookExportController extends Controller */ public function plainText(string $bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->bookRepo->getBySlug($bookSlug); $textContent = $this->exportService->bookToPlainText($book); return $this->downloadResponse($textContent, $bookSlug . '.txt'); } diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index 15c3e93ad..32d08dc77 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -116,7 +116,7 @@ class BookshelfController extends Controller public function show(string $slug) { /** @var Bookshelf $shelf */ - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); $this->checkOwnablePermission('book-view', $shelf); $books = $this->entityRepo->getBookshelfChildren($shelf); @@ -140,7 +140,7 @@ class BookshelfController extends Controller */ public function edit(string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ $this->checkOwnablePermission('bookshelf-update', $shelf); $shelfBooks = $this->entityRepo->getBookshelfChildren($shelf); @@ -169,7 +169,7 @@ class BookshelfController extends Controller */ public function update(Request $request, string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ $this->checkOwnablePermission('bookshelf-update', $shelf); $this->validate($request, [ 'name' => 'required|string|max:255', @@ -194,7 +194,7 @@ class BookshelfController extends Controller */ public function showDelete(string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -210,7 +210,7 @@ class BookshelfController extends Controller */ public function destroy(string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ $this->checkOwnablePermission('bookshelf-delete', $shelf); Activity::addMessage('bookshelf_delete', 0, $shelf->name); @@ -230,7 +230,7 @@ class BookshelfController extends Controller */ public function showPermissions(string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $roles = $this->userRepo->getRestrictableRoles(); @@ -250,7 +250,7 @@ class BookshelfController extends Controller */ public function permissions(Request $request, string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf); @@ -266,7 +266,7 @@ class BookshelfController extends Controller */ public function copyPermissions(string $slug) { - $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $updateCount = $this->entityRepo->copyBookshelfPermissions($shelf); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index b90a6dcb2..796689c66 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -33,7 +33,7 @@ class ChapterController extends Controller */ public function create($bookSlug) { - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->entityRepo->getEntityBySlug('book', $bookSlug); $this->checkOwnablePermission('chapter-create', $book); $this->setPageTitle(trans('entities.chapters_create')); return view('chapters.create', ['book' => $book, 'current' => $book]); @@ -53,7 +53,7 @@ class ChapterController extends Controller 'name' => 'required|string|max:255' ]); - $book = $this->entityRepo->getBySlug('book', $bookSlug); + $book = $this->entityRepo->getEntityBySlug('book', $bookSlug); $this->checkOwnablePermission('chapter-create', $book); $input = $request->all(); @@ -71,7 +71,7 @@ class ChapterController extends Controller */ public function show($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-view', $chapter); $sidebarTree = $this->entityRepo->getBookChildren($chapter->book); Views::add($chapter); @@ -94,7 +94,7 @@ class ChapterController extends Controller */ public function edit($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); return view('chapters.edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]); @@ -110,7 +110,7 @@ class ChapterController extends Controller */ public function update(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->entityRepo->updateFromInput('chapter', $chapter, $request->all()); @@ -126,7 +126,7 @@ class ChapterController extends Controller */ public function showDelete($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-delete', $chapter); $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); return view('chapters.delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]); @@ -140,7 +140,7 @@ class ChapterController extends Controller */ public function destroy($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $book = $chapter->book; $this->checkOwnablePermission('chapter-delete', $chapter); Activity::addMessage('chapter_delete', $book->id, $chapter->name); @@ -157,7 +157,7 @@ class ChapterController extends Controller */ public function showMove($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -177,7 +177,7 @@ class ChapterController extends Controller */ public function move(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -217,7 +217,7 @@ class ChapterController extends Controller */ public function showPermissions($bookSlug, $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $roles = $this->userRepo->getRestrictableRoles(); return view('chapters.permissions', [ @@ -237,7 +237,7 @@ class ChapterController extends Controller */ public function permissions(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter); session()->flash('success', trans('entities.chapters_permissions_success')); diff --git a/app/Http/Controllers/ChapterExportController.php b/app/Http/Controllers/ChapterExportController.php index de46baa12..15d67d539 100644 --- a/app/Http/Controllers/ChapterExportController.php +++ b/app/Http/Controllers/ChapterExportController.php @@ -42,7 +42,7 @@ class ChapterExportController extends Controller */ public function pdf(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $pdfContent = $this->exportService->chapterToPdf($chapter); return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf'); } @@ -57,7 +57,7 @@ class ChapterExportController extends Controller */ public function html(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $containedHtml = $this->exportService->chapterToContainedHtml($chapter); return $this->downloadResponse($containedHtml, $chapterSlug . '.html'); } @@ -71,7 +71,7 @@ class ChapterExportController extends Controller */ public function plainText(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $chapterText = $this->exportService->chapterToPlainText($chapter); return $this->downloadResponse($chapterText, $chapterSlug . '.txt'); } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 62a706c0d..45aa8f934 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -45,11 +45,11 @@ class PageController extends Controller public function create($bookSlug, $chapterSlug = null) { if ($chapterSlug !== null) { - $chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->pageRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $book = $chapter->book; } else { $chapter = null; - $book = $this->pageRepo->getBySlug('book', $bookSlug); + $book = $this->pageRepo->getEntityBySlug('book', $bookSlug); } $parent = $chapter ? $chapter : $book; @@ -81,11 +81,11 @@ class PageController extends Controller ]); if ($chapterSlug !== null) { - $chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->pageRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $book = $chapter->book; } else { $chapter = null; - $book = $this->pageRepo->getBySlug('book', $bookSlug); + $book = $this->pageRepo->getEntityBySlug('book', $bookSlug); } $parent = $chapter ? $chapter : $book; @@ -166,7 +166,7 @@ class PageController extends Controller public function show($bookSlug, $pageSlug) { try { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); } catch (NotFoundException $e) { $page = $this->pageRepo->getPageByOldSlug($pageSlug, $bookSlug); if ($page === null) { @@ -218,7 +218,7 @@ class PageController extends Controller */ public function edit($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()])); $page->isDraft = false; @@ -267,7 +267,7 @@ class PageController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255' ]); - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); $this->pageRepo->updatePage($page, $page->book->id, $request->all()); Activity::add($page, 'page_update', $page->book->id); @@ -322,7 +322,7 @@ class PageController extends Controller */ public function showDelete($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()])); return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]); @@ -353,7 +353,7 @@ class PageController extends Controller */ public function destroy($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $book = $page->book; $this->checkOwnablePermission('page-delete', $page); $this->pageRepo->destroyPage($page); @@ -389,7 +389,7 @@ class PageController extends Controller */ public function showRevisions($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()])); return view('pages.revisions', ['page' => $page, 'current' => $page]); } @@ -403,7 +403,7 @@ class PageController extends Controller */ public function showRevision($bookSlug, $pageSlug, $revisionId) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $revision = $page->revisions()->where('id', '=', $revisionId)->first(); if ($revision === null) { abort(404); @@ -429,7 +429,7 @@ class PageController extends Controller */ public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $revision = $page->revisions()->where('id', '=', $revisionId)->first(); if ($revision === null) { abort(404); @@ -459,7 +459,7 @@ class PageController extends Controller */ public function restoreRevision($bookSlug, $pageSlug, $revisionId) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); $page = $this->pageRepo->restorePageRevision($page, $page->book, $revisionId); Activity::add($page, 'page_restore', $page->book->id); @@ -478,7 +478,7 @@ class PageController extends Controller */ public function destroyRevision($bookSlug, $pageSlug, $revId) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-delete', $page); $revision = $page->revisions()->where('id', '=', $revId)->first(); @@ -523,7 +523,7 @@ class PageController extends Controller */ public function showMove($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); return view('pages.move', [ @@ -543,7 +543,7 @@ class PageController extends Controller */ public function move(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); @@ -582,7 +582,7 @@ class PageController extends Controller */ public function showCopy($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-view', $page); session()->flashInput(['name' => $page->name]); return view('pages.copy', [ @@ -602,7 +602,7 @@ class PageController extends Controller */ public function copy(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('page-view', $page); $entitySelection = $request->get('entity_selection', null); @@ -640,7 +640,7 @@ class PageController extends Controller */ public function showPermissions($bookSlug, $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $page); $roles = $this->userRepo->getRestrictableRoles(); return view('pages.permissions', [ @@ -660,7 +660,7 @@ class PageController extends Controller */ public function permissions(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $page); $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); session()->flash('success', trans('entities.pages_permissions_success')); diff --git a/app/Http/Controllers/PageExportController.php b/app/Http/Controllers/PageExportController.php index c5b796ec8..addcc5513 100644 --- a/app/Http/Controllers/PageExportController.php +++ b/app/Http/Controllers/PageExportController.php @@ -43,7 +43,7 @@ class PageExportController extends Controller */ public function pdf(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $page->html = $this->pageRepo->renderPage($page); $pdfContent = $this->exportService->pageToPdf($page); return $this->downloadResponse($pdfContent, $pageSlug . '.pdf'); @@ -59,7 +59,7 @@ class PageExportController extends Controller */ public function html(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $page->html = $this->pageRepo->renderPage($page); $containedHtml = $this->exportService->pageToContainedHtml($page); return $this->downloadResponse($containedHtml, $pageSlug . '.html'); @@ -74,7 +74,7 @@ class PageExportController extends Controller */ public function plainText(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $pageText = $this->exportService->pageToPlainText($page); return $this->downloadResponse($pageText, $pageSlug . '.txt'); } diff --git a/app/helpers.php b/app/helpers.php index d14379ddd..6211f41be 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -92,11 +92,11 @@ function userCanOnAny(string $permission, string $entityClass = null): bool /** * Helper to access system settings. - * @param $key - * @param bool $default + * @param string $key + * @param $default * @return bool|string|SettingService */ -function setting(string $key = null, bool $default = false) +function setting(string $key = null, $default = false) { $settingService = resolve(SettingService::class); if (is_null($key)) { diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index eb3f7b4bb..ed92fa59f 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -90,7 +90,7 @@ trait SharedTestHelpers * @return \BookStack\Entities\Bookshelf */ public function newShelf($input = ['name' => 'test shelf', 'description' => 'My new test shelf']) { - return app(EntityRepo::class)->createFromInput('bookshelf', $input, false); + return app(EntityRepo::class)->createFromInput('bookshelf', $input); } /** @@ -99,7 +99,7 @@ trait SharedTestHelpers * @return Book */ public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) { - return app(EntityRepo::class)->createFromInput('book', $input, false); + return app(EntityRepo::class)->createFromInput('book', $input); } /** From 228aa4740bf9b1e7cf737f356cf92e549bfcdaf6 Mon Sep 17 00:00:00 2001 From: Connor O'Shea Date: Sun, 15 Sep 2019 20:20:11 -0400 Subject: [PATCH 028/257] Add support for properties (INI) --- resources/js/services/code.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/js/services/code.js b/resources/js/services/code.js index 1e0e48289..7dddc92fd 100644 --- a/resources/js/services/code.js +++ b/resources/js/services/code.js @@ -16,6 +16,7 @@ import 'codemirror/mode/mllike/mllike'; import 'codemirror/mode/nginx/nginx'; import 'codemirror/mode/php/php'; import 'codemirror/mode/powershell/powershell'; +import 'codemirror/mode/properties/properties'; import 'codemirror/mode/python/python'; import 'codemirror/mode/ruby/ruby'; import 'codemirror/mode/rust/rust'; @@ -42,6 +43,7 @@ const modeMap = { haskell: 'haskell', hs: 'haskell', html: 'htmlmixed', + ini: 'text/x-ini', javascript: 'javascript', json: {name: 'javascript', json: true}, js: 'javascript', @@ -54,6 +56,7 @@ const modeMap = { ml: 'mllike', nginx: 'nginx', powershell: 'powershell', + properties: 'text/x-properties', ocaml: 'mllike', php: 'php', py: 'python', From 4ad43b1a1fd8dcd980581f2615730a25981c05e0 Mon Sep 17 00:00:00 2001 From: Connor O'Shea Date: Sun, 15 Sep 2019 20:22:26 -0400 Subject: [PATCH 029/257] Add support for properties --- resources/views/components/code-editor.blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/components/code-editor.blade.php b/resources/views/components/code-editor.blade.php index 31a583182..863cdfd85 100644 --- a/resources/views/components/code-editor.blade.php +++ b/resources/views/components/code-editor.blade.php @@ -24,6 +24,7 @@ Lua PHP Powershell + Properties MarkDown Nginx Python From fca69643db409c028e3f09c8dcdf2cb0f572d824 Mon Sep 17 00:00:00 2001 From: Connor O'Shea Date: Sun, 15 Sep 2019 20:24:47 -0400 Subject: [PATCH 030/257] Normalize ini and properties values --- resources/js/services/code.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/services/code.js b/resources/js/services/code.js index 7dddc92fd..f92752109 100644 --- a/resources/js/services/code.js +++ b/resources/js/services/code.js @@ -43,7 +43,7 @@ const modeMap = { haskell: 'haskell', hs: 'haskell', html: 'htmlmixed', - ini: 'text/x-ini', + ini: 'ini', javascript: 'javascript', json: {name: 'javascript', json: true}, js: 'javascript', @@ -56,7 +56,7 @@ const modeMap = { ml: 'mllike', nginx: 'nginx', powershell: 'powershell', - properties: 'text/x-properties', + properties: 'properties', ocaml: 'mllike', php: 'php', py: 'python', From 2a2cc858f0f95c606f2ad2542f915d3bd761775f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 19 Sep 2019 15:12:10 +0100 Subject: [PATCH 031/257] Refactored notification showing and global view data --- .../Auth/ConfirmEmailController.php | 10 ++-- .../Auth/ForgotPasswordController.php | 2 +- .../Controllers/Auth/RegisterController.php | 4 +- .../Auth/ResetPasswordController.php | 2 +- .../Controllers/Auth/UserInviteController.php | 4 +- app/Http/Controllers/BookController.php | 2 +- app/Http/Controllers/BookshelfController.php | 4 +- app/Http/Controllers/ChapterController.php | 6 +-- app/Http/Controllers/Controller.php | 49 ++++++++++++------- app/Http/Controllers/PageController.php | 18 +++---- app/Http/Controllers/PermissionController.php | 8 +-- app/Http/Controllers/SettingController.php | 8 +-- app/Http/Controllers/UserController.php | 12 ++--- app/Http/Kernel.php | 3 +- app/Http/Middleware/GlobalViewData.php | 28 +++++++++++ 15 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 app/Http/Middleware/GlobalViewData.php diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index 4300ab458..3959fe685 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -65,14 +65,14 @@ class ConfirmEmailController extends Controller $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token); } catch (Exception $exception) { if ($exception instanceof UserTokenNotFoundException) { - session()->flash('error', trans('errors.email_confirmation_invalid')); + $this->showErrorNotification( trans('errors.email_confirmation_invalid')); return redirect('/register'); } if ($exception instanceof UserTokenExpiredException) { $user = $this->userRepo->getById($exception->userId); $this->emailConfirmationService->sendConfirmation($user); - session()->flash('error', trans('errors.email_confirmation_expired')); + $this->showErrorNotification( trans('errors.email_confirmation_expired')); return redirect('/register/confirm'); } @@ -84,7 +84,7 @@ class ConfirmEmailController extends Controller $user->save(); auth()->login($user); - session()->flash('success', trans('auth.email_confirm_success')); + $this->showSuccessNotification( trans('auth.email_confirm_success')); $this->emailConfirmationService->deleteByUser($user); return redirect('/'); @@ -106,11 +106,11 @@ class ConfirmEmailController extends Controller try { $this->emailConfirmationService->sendConfirmation($user); } catch (Exception $e) { - session()->flash('error', trans('auth.email_confirm_send_error')); + $this->showErrorNotification( trans('auth.email_confirm_send_error')); return redirect('/register/confirm'); } - session()->flash('success', trans('auth.email_confirm_resent')); + $this->showSuccessNotification( trans('auth.email_confirm_resent')); return redirect('/register/confirm'); } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index a0cbae9c6..4a0a69ae4 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -53,7 +53,7 @@ class ForgotPasswordController extends Controller if ($response === Password::RESET_LINK_SENT) { $message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]); - session()->flash('success', $message); + $this->showSuccessNotification( $message); return back()->with('status', trans($response)); } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 70090fed0..304d3bed2 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -166,14 +166,14 @@ class RegisterController extends Controller try { $this->emailConfirmationService->sendConfirmation($newUser); } catch (Exception $e) { - session()->flash('error', trans('auth.email_confirm_send_error')); + $this->showErrorNotification(trans('auth.email_confirm_send_error')); } return redirect('/register/confirm'); } auth()->login($newUser); - session()->flash('success', trans('auth.register_success')); + $this->showSuccessNotification(trans('auth.register_success')); return redirect($this->redirectPath()); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index d7005b739..540d2e679 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -44,7 +44,7 @@ class ResetPasswordController extends Controller protected function sendResetResponse(Request $request, $response) { $message = trans('auth.reset_password_success'); - session()->flash('success', $message); + $this->showSuccessNotification( $message); return redirect($this->redirectPath()) ->with('status', trans($response)); } diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 8799d264c..313faf5bc 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -77,7 +77,7 @@ class UserInviteController extends Controller $user->save(); auth()->login($user); - session()->flash('success', trans('auth.user_invite_success', ['appName' => setting('app-name')])); + $this->showSuccessNotification( trans('auth.user_invite_success', ['appName' => setting('app-name')])); $this->inviteService->deleteByUser($user); return redirect('/'); @@ -96,7 +96,7 @@ class UserInviteController extends Controller } if ($exception instanceof UserTokenExpiredException) { - session()->flash('error', trans('errors.invite_token_expired')); + $this->showErrorNotification( trans('errors.invite_token_expired')); return redirect('/password/email'); } diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 97659ed96..7a0ee0f0e 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -380,7 +380,7 @@ class BookController extends Controller $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); - session()->flash('success', trans('entities.books_permissions_updated')); + $this->showSuccessNotification(trans('entities.books_permissions_updated')); return redirect($book->getUrl()); } diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index 32d08dc77..c369ce655 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -254,7 +254,7 @@ class BookshelfController extends Controller $this->checkOwnablePermission('restrictions-manage', $shelf); $this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf); - session()->flash('success', trans('entities.shelves_permissions_updated')); + $this->showSuccessNotification( trans('entities.shelves_permissions_updated')); return redirect($shelf->getUrl()); } @@ -270,7 +270,7 @@ class BookshelfController extends Controller $this->checkOwnablePermission('restrictions-manage', $shelf); $updateCount = $this->entityRepo->copyBookshelfPermissions($shelf); - session()->flash('success', trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); + $this->showSuccessNotification( trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); return redirect($shelf->getUrl()); } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 796689c66..a4a1dbf24 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -197,13 +197,13 @@ class ChapterController extends Controller } if ($parent === false || $parent === null) { - session()->flash('error', trans('errors.selected_book_not_found')); + $this->showErrorNotification( trans('errors.selected_book_not_found')); return redirect()->back(); } $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true); Activity::add($chapter, 'chapter_move', $chapter->book->id); - session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name])); + $this->showSuccessNotification( trans('entities.chapter_move_success', ['bookName' => $parent->name])); return redirect($chapter->getUrl()); } @@ -240,7 +240,7 @@ class ChapterController extends Controller $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter); - session()->flash('success', trans('entities.chapters_permissions_success')); + $this->showSuccessNotification( trans('entities.chapters_permissions_success')); return redirect($chapter->getUrl()); } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 5bc62c601..e34cb7e59 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -18,6 +18,7 @@ abstract class Controller extends BaseController * @var User static */ protected $currentUser; + /** * @var bool */ @@ -28,28 +29,15 @@ abstract class Controller extends BaseController */ public function __construct() { - $this->middleware(function ($request, $next) { - - // Get a user instance for the current user - $user = user(); - - // Share variables with controllers - $this->currentUser = $user; - $this->signedIn = auth()->check(); - - // Share variables with views - view()->share('signedIn', $this->signedIn); - view()->share('currentUser', $user); - - return $next($request); - }); + $this->currentUser = user(); + $this->signedIn = auth()->check(); } /** * Stops the application and shows a permission error if * the application is in demo mode. */ - protected function preventAccessForDemoUsers() + protected function preventAccessInDemoMode() { if (config('app.env') === 'demo') { $this->showPermissionError(); @@ -75,7 +63,7 @@ abstract class Controller extends BaseController $response = response()->json(['error' => trans('errors.permissionJson')], 403); } else { $response = redirect('/'); - session()->flash('error', trans('errors.permission')); + $this->showErrorNotification( trans('errors.permission')); } throw new HttpResponseException($response); @@ -178,4 +166,31 @@ abstract class Controller extends BaseController 'Content-Disposition' => 'attachment; filename="' . $fileName . '"' ]); } + + /** + * Show a positive, successful notification to the user on next view load. + * @param string $message + */ + protected function showSuccessNotification(string $message) + { + session()->flash('success', $message); + } + + /** + * Show a warning notification to the user on next view load. + * @param string $message + */ + protected function showWarningNotification(string $message) + { + session()->flash('warning', $message); + } + + /** + * Show an error notification to the user on next view load. + * @param string $message + */ + protected function showErrorNotification(string $message) + { + session()->flash('error', $message); + } } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 45aa8f934..30f58ab6a 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -240,7 +240,7 @@ class PageController extends Controller } if (count($warnings) > 0) { - session()->flash('warning', implode("\n", $warnings)); + $this->showWarningNotification( implode("\n", $warnings)); } $draftsEnabled = $this->signedIn; @@ -359,7 +359,7 @@ class PageController extends Controller $this->pageRepo->destroyPage($page); Activity::addMessage('page_delete', $book->id, $page->name); - session()->flash('success', trans('entities.pages_delete_success')); + $this->showSuccessNotification( trans('entities.pages_delete_success')); return redirect($book->getUrl()); } @@ -375,7 +375,7 @@ class PageController extends Controller $page = $this->pageRepo->getById('page', $pageId, true); $book = $page->book; $this->checkOwnablePermission('page-update', $page); - session()->flash('success', trans('entities.pages_delete_draft_success')); + $this->showSuccessNotification( trans('entities.pages_delete_draft_success')); $this->pageRepo->destroyPage($page); return redirect($book->getUrl()); } @@ -491,12 +491,12 @@ class PageController extends Controller // Check if its the latest revision, cannot delete latest revision. if (intval($currentRevision->id) === intval($revId)) { - session()->flash('error', trans('entities.revision_cannot_delete_latest')); + $this->showErrorNotification( trans('entities.revision_cannot_delete_latest')); return response()->view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400); } $revision->delete(); - session()->flash('success', trans('entities.revision_delete_success')); + $this->showSuccessNotification( trans('entities.revision_delete_success')); return redirect($page->getUrl('/revisions')); } @@ -568,7 +568,7 @@ class PageController extends Controller $this->pageRepo->changePageParent($page, $parent); Activity::add($page, 'page_move', $page->book->id); - session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name])); + $this->showSuccessNotification( trans('entities.pages_move_success', ['parentName' => $parent->name])); return redirect($page->getUrl()); } @@ -616,7 +616,7 @@ class PageController extends Controller try { $parent = $this->pageRepo->getById($entityType, $entityId); } catch (Exception $e) { - session()->flash(trans('entities.selected_book_chapter_not_found')); + $this->showErrorNotification(trans('entities.selected_book_chapter_not_found')); return redirect()->back(); } } @@ -626,7 +626,7 @@ class PageController extends Controller $pageCopy = $this->pageRepo->copyPage($page, $parent, $request->get('name', '')); Activity::add($pageCopy, 'page_create', $pageCopy->book->id); - session()->flash('success', trans('entities.pages_copy_success')); + $this->showSuccessNotification( trans('entities.pages_copy_success')); return redirect($pageCopy->getUrl()); } @@ -663,7 +663,7 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); $this->checkOwnablePermission('restrictions-manage', $page); $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); - session()->flash('success', trans('entities.pages_permissions_success')); + $this->showSuccessNotification( trans('entities.pages_permissions_success')); return redirect($page->getUrl()); } } diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php index f19e7e763..b8ca5a646 100644 --- a/app/Http/Controllers/PermissionController.php +++ b/app/Http/Controllers/PermissionController.php @@ -53,7 +53,7 @@ class PermissionController extends Controller ]); $this->permissionsRepo->saveNewRole($request->all()); - session()->flash('success', trans('settings.role_create_success')); + $this->showSuccessNotification( trans('settings.role_create_success')); return redirect('/settings/roles'); } @@ -90,7 +90,7 @@ class PermissionController extends Controller ]); $this->permissionsRepo->updateRole($id, $request->all()); - session()->flash('success', trans('settings.role_update_success')); + $this->showSuccessNotification( trans('settings.role_update_success')); return redirect('/settings/roles'); } @@ -124,11 +124,11 @@ class PermissionController extends Controller try { $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id')); } catch (PermissionsException $e) { - session()->flash('error', $e->getMessage()); + $this->showErrorNotification( $e->getMessage()); return redirect()->back(); } - session()->flash('success', trans('settings.role_delete_success')); + $this->showSuccessNotification( trans('settings.role_delete_success')); return redirect('/settings/roles'); } } diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 650833c7f..68687dc95 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -47,7 +47,7 @@ class SettingController extends Controller */ public function update(Request $request) { - $this->preventAccessForDemoUsers(); + $this->preventAccessInDemoMode(); $this->checkPermission('settings-manage'); $this->validate($request, [ 'app_logo' => $this->imageRepo->getImageValidationRules(), @@ -76,7 +76,7 @@ class SettingController extends Controller setting()->remove('app-logo'); } - session()->flash('success', trans('settings.settings_save_success')); + $this->showSuccessNotification( trans('settings.settings_save_success')); return redirect('/settings'); } @@ -111,14 +111,14 @@ class SettingController extends Controller $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun); $deleteCount = count($imagesToDelete); if ($deleteCount === 0) { - session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found')); + $this->showWarningNotification( trans('settings.maint_image_cleanup_nothing_found')); return redirect('/settings/maintenance')->withInput(); } if ($dryRun) { session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount])); } else { - session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount])); + $this->showSuccessNotification( trans('settings.maint_image_cleanup_success', ['count' => $deleteCount])); } return redirect('/settings/maintenance#image-cleanup')->withInput(); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 4f4f98104..6984fef1e 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -144,7 +144,7 @@ class UserController extends Controller */ public function update(Request $request, $id) { - $this->preventAccessForDemoUsers(); + $this->preventAccessInDemoMode(); $this->checkPermissionOrCurrentUser('users-manage', $id); $this->validate($request, [ @@ -202,7 +202,7 @@ class UserController extends Controller } $user->save(); - session()->flash('success', trans('settings.users_edit_success')); + $this->showSuccessNotification( trans('settings.users_edit_success')); $redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id); return redirect($redirectUrl); @@ -230,23 +230,23 @@ class UserController extends Controller */ public function destroy($id) { - $this->preventAccessForDemoUsers(); + $this->preventAccessInDemoMode(); $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); if ($this->userRepo->isOnlyAdmin($user)) { - session()->flash('error', trans('errors.users_cannot_delete_only_admin')); + $this->showErrorNotification( trans('errors.users_cannot_delete_only_admin')); return redirect($user->getEditUrl()); } if ($user->system_name === 'public') { - session()->flash('error', trans('errors.users_cannot_delete_guest')); + $this->showErrorNotification( trans('errors.users_cannot_delete_guest')); return redirect($user->getEditUrl()); } $this->userRepo->destroy($user); - session()->flash('success', trans('settings.users_delete_success')); + $this->showSuccessNotification( trans('settings.users_delete_success')); return redirect('/settings/users'); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 004f30139..f9752da09 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -32,7 +32,8 @@ class Kernel extends HttpKernel \Illuminate\Routing\Middleware\ThrottleRequests::class, \BookStack\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, - \BookStack\Http\Middleware\Localization::class + \BookStack\Http\Middleware\Localization::class, + \BookStack\Http\Middleware\GlobalViewData::class, ], 'api' => [ 'throttle:60,1', diff --git a/app/Http/Middleware/GlobalViewData.php b/app/Http/Middleware/GlobalViewData.php new file mode 100644 index 000000000..0c2419016 --- /dev/null +++ b/app/Http/Middleware/GlobalViewData.php @@ -0,0 +1,28 @@ +share('signedIn', auth()->check()); + view()->share('currentUser', user()); + + return $next($request); + } + +} \ No newline at end of file From 615b2de43396978777a6c82b595212289ac8c8e8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 19 Sep 2019 18:03:17 +0100 Subject: [PATCH 032/257] Simplified activity facade interface Also cleaned up any other bits along the way. --- app/Actions/Activity.php | 13 +++-- app/Actions/ActivityService.php | 59 +++++++++++--------- app/Actions/ViewService.php | 9 +-- app/Entities/Book.php | 7 +++ app/Entities/Entity.php | 2 +- app/Http/Controllers/BookController.php | 10 +--- app/Http/Controllers/BookshelfController.php | 2 +- app/Http/Controllers/ChapterController.php | 2 +- app/Http/Controllers/Controller.php | 2 + app/Http/Controllers/PageController.php | 2 +- resources/views/books/list.blade.php | 6 +- 11 files changed, 68 insertions(+), 46 deletions(-) diff --git a/app/Actions/Activity.php b/app/Actions/Activity.php index 1ae1811e1..05f0129dd 100644 --- a/app/Actions/Activity.php +++ b/app/Actions/Activity.php @@ -3,13 +3,18 @@ namespace BookStack\Actions; use BookStack\Auth\User; +use BookStack\Entities\Entity; use BookStack\Model; /** - * @property string key - * @property \User user - * @property \Entity entity - * @property string extra + * @property string $key + * @property User $user + * @property Entity $entity + * @property string $extra + * @property string $entity_type + * @property int $entity_id + * @property int $user_id + * @property int $book_id */ class Activity extends Model { diff --git a/app/Actions/ActivityService.php b/app/Actions/ActivityService.php index f4f82a6f4..8511e4d6d 100644 --- a/app/Actions/ActivityService.php +++ b/app/Actions/ActivityService.php @@ -2,7 +2,6 @@ use BookStack\Auth\Permissions\PermissionService; use BookStack\Entities\Entity; -use Session; class ActivityService { @@ -12,7 +11,7 @@ class ActivityService /** * ActivityService constructor. - * @param \BookStack\Actions\Activity $activity + * @param Activity $activity * @param PermissionService $permissionService */ public function __construct(Activity $activity, PermissionService $permissionService) @@ -24,42 +23,46 @@ class ActivityService /** * Add activity data to database. - * @param Entity $entity - * @param $activityKey + * @param \BookStack\Entities\Entity $entity + * @param string $activityKey * @param int $bookId - * @param bool $extra */ - public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false) + public function add(Entity $entity, string $activityKey, int $bookId = null) { - $activity = $this->activity->newInstance(); - $activity->user_id = $this->user->id; - $activity->book_id = $bookId; - $activity->key = strtolower($activityKey); - if ($extra !== false) { - $activity->extra = $extra; - } + $activity = $this->newActivityForUser($activityKey, $bookId); $entity->activity()->save($activity); $this->setNotification($activityKey); } /** - * Adds a activity history with a message & without binding to a entity. - * @param $activityKey + * Adds a activity history with a message, without binding to a entity. + * @param string $activityKey + * @param string $message * @param int $bookId - * @param bool|false $extra */ - public function addMessage($activityKey, $bookId = 0, $extra = false) + public function addMessage(string $activityKey, string $message, int $bookId = null) { - $this->activity->user_id = $this->user->id; - $this->activity->book_id = $bookId; - $this->activity->key = strtolower($activityKey); - if ($extra !== false) { - $this->activity->extra = $extra; - } - $this->activity->save(); + $this->newActivityForUser($activityKey, $bookId)->forceFill([ + 'extra' => $message + ])->save(); + $this->setNotification($activityKey); } + /** + * Get a new activity instance for the current user. + * @param string $key + * @param int|null $bookId + * @return Activity + */ + protected function newActivityForUser(string $key, int $bookId = null) + { + return $this->activity->newInstance()->forceFill([ + 'key' => strtolower($key), + 'user_id' => $this->user->id, + 'book_id' => $bookId ?? 0, + ]); + } /** * Removes the entity attachment from each of its activities @@ -90,7 +93,11 @@ class ActivityService { $activityList = $this->permissionService ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') - ->orderBy('created_at', 'desc')->with('user', 'entity')->skip($count * $page)->take($count)->get(); + ->orderBy('created_at', 'desc') + ->with('user', 'entity') + ->skip($count * $page) + ->take($count) + ->get(); return $this->filterSimilar($activityList); } @@ -171,7 +178,7 @@ class ActivityService $notificationTextKey = 'activities.' . $activityKey . '_notification'; if (trans()->has($notificationTextKey)) { $message = trans($notificationTextKey); - Session::flash('success', $message); + session()->flash('success', $message); } } } diff --git a/app/Actions/ViewService.php b/app/Actions/ViewService.php index 532f31c42..b576483a0 100644 --- a/app/Actions/ViewService.php +++ b/app/Actions/ViewService.php @@ -3,6 +3,7 @@ use BookStack\Auth\Permissions\PermissionService; use BookStack\Entities\Entity; use BookStack\Entities\EntityProvider; +use DB; use Illuminate\Support\Collection; class ViewService @@ -13,8 +14,8 @@ class ViewService /** * ViewService constructor. - * @param \BookStack\Actions\View $view - * @param \BookStack\Auth\Permissions\PermissionService $permissionService + * @param View $view + * @param PermissionService $permissionService * @param EntityProvider $entityProvider */ public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider) @@ -26,7 +27,7 @@ class ViewService /** * Add a view to the given entity. - * @param Entity $entity + * @param \BookStack\Entities\Entity $entity * @return int */ public function add(Entity $entity) @@ -64,7 +65,7 @@ class ViewService $skipCount = $count * $page; $query = $this->permissionService ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action) - ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) + ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count')) ->groupBy('viewable_id', 'viewable_type') ->orderBy('view_count', 'desc'); diff --git a/app/Entities/Book.php b/app/Entities/Book.php index 7d3d5e4ae..ce4c4b90e 100644 --- a/app/Entities/Book.php +++ b/app/Entities/Book.php @@ -2,6 +2,13 @@ use BookStack\Uploads\Image; +/** + * Class Book + * @property string $description + * @property int $image_id + * @property Image|null $cover + * @package BookStack\Entities + */ class Book extends Entity { public $searchFactor = 2; diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 482d21766..480d7caa7 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; * The base class for book-like items such as pages, chapters & books. * This is not a database model in itself but extended. * - * @property integer $id + * @property int $id * @property string $name * @property string $slug * @property Carbon $created_at diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 7a0ee0f0e..1f9caf756 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -58,11 +58,6 @@ class BookController extends Controller $view = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books')); $sort = setting()->getUser($this->currentUser, 'books_sort', 'name'); $order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc'); - $sortOptions = [ - 'name' => trans('common.sort_name'), - 'created_at' => trans('common.sort_created_at'), - 'updated_at' => trans('common.sort_updated_at'), - ]; $books = $this->bookRepo->getAllPaginated('book', 18, $sort, $order); $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed('book', 4, 0) : false; @@ -80,7 +75,6 @@ class BookController extends Controller 'view' => $view, 'sort' => $sort, 'order' => $order, - 'sortOptions' => $sortOptions, ]); } @@ -114,6 +108,7 @@ class BookController extends Controller * @throws NotFoundException * @throws ImageUploadException * @throws ValidationException + * @throws Throwable */ public function store(Request $request, string $shelfSlug = null) { @@ -192,6 +187,7 @@ class BookController extends Controller * @throws ImageUploadException * @throws NotFoundException * @throws ValidationException + * @throws Throwable */ public function update(Request $request, string $slug) { @@ -340,7 +336,7 @@ class BookController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); - Activity::addMessage('book_delete', 0, $book->name); + Activity::addMessage('book_delete', $book->name); if ($book->cover) { $this->imageRepo->destroyImage($book->cover); diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index c369ce655..9bb94484c 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -212,7 +212,7 @@ class BookshelfController extends Controller { $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ $this->checkOwnablePermission('bookshelf-delete', $shelf); - Activity::addMessage('bookshelf_delete', 0, $shelf->name); + Activity::addMessage('bookshelf_delete', $shelf->name); if ($shelf->cover) { $this->imageRepo->destroyImage($shelf->cover); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index a4a1dbf24..8090556c9 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -143,7 +143,7 @@ class ChapterController extends Controller $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $book = $chapter->book; $this->checkOwnablePermission('chapter-delete', $chapter); - Activity::addMessage('chapter_delete', $book->id, $chapter->name); + Activity::addMessage('chapter_delete', $chapter->name, $book->id); $this->entityRepo->destroyChapter($chapter); return redirect($book->getUrl()); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index e34cb7e59..7bca77495 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -3,6 +3,8 @@ namespace BookStack\Http\Controllers; use BookStack\Auth\User; +use BookStack\Entities\Entity; +use BookStack\Facades\Activity; use BookStack\Ownable; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 30f58ab6a..37b575010 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -358,7 +358,7 @@ class PageController extends Controller $this->checkOwnablePermission('page-delete', $page); $this->pageRepo->destroyPage($page); - Activity::addMessage('page_delete', $book->id, $page->name); + Activity::addMessage('page_delete', $page->name, $book->id); $this->showSuccessNotification( trans('entities.pages_delete_success')); return redirect($book->getUrl()); } diff --git a/resources/views/books/list.blade.php b/resources/views/books/list.blade.php index 871d931f1..42a2757f9 100644 --- a/resources/views/books/list.blade.php +++ b/resources/views/books/list.blade.php @@ -4,7 +4,11 @@

{{ trans('entities.books') }}

- @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort, 'type' => 'books']) + @include('partials.sort', ['options' => [ + 'name' => trans('common.sort_name'), + 'created_at' => trans('common.sort_created_at'), + 'updated_at' => trans('common.sort_updated_at'), + ], 'order' => $order, 'sort' => $sort, 'type' => 'books'])
From f7a5a0705bba304a705df74b1e7e724827900a0c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 19 Sep 2019 18:20:09 +0100 Subject: [PATCH 033/257] Moved shelf book append logic --- app/Entities/Bookshelf.php | 14 ++++++++++++++ app/Entities/Repos/BookRepo.php | 17 ----------------- app/Http/Controllers/BookController.php | 7 ++++--- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/app/Entities/Bookshelf.php b/app/Entities/Bookshelf.php index 745611ba7..1db348b6b 100644 --- a/app/Entities/Bookshelf.php +++ b/app/Entities/Bookshelf.php @@ -104,4 +104,18 @@ class Bookshelf extends Entity { return $this->books()->where('id', '=', $book->id)->count() > 0; } + + /** + * Add a book to the end of this shelf. + * @param Book $book + */ + public function appendBook(Book $book) + { + if (!$this->contains($book)) { + return; + } + + $maxOrder = $this->books()->max('order'); + $this->books()->attach($book->id, ['order' => $maxOrder + 1]); + } } diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index c82780fea..91bc9a1b4 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -3,9 +3,7 @@ namespace BookStack\Entities\Repos; - use BookStack\Entities\Book; -use BookStack\Entities\Bookshelf; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; @@ -25,21 +23,6 @@ class BookRepo extends EntityRepo return $book; } - /** - * Append a Book to a BookShelf. - * @param Bookshelf $shelf - * @param Book $book - */ - public function appendBookToShelf(Bookshelf $shelf, Book $book) - { - if ($shelf->contains($book)) { - return; - } - - $maxOrder = $shelf->books()->max('order'); - $shelf->books()->attach($book->id, ['order' => $maxOrder + 1]); - } - /** * Destroy the provided book and all its child entities. * @param Book $book diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 1f9caf756..1a7872030 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -3,10 +3,9 @@ use Activity; use BookStack\Auth\UserRepo; use BookStack\Entities\Book; +use BookStack\Entities\Bookshelf; use BookStack\Entities\EntityContextManager; use BookStack\Entities\Repos\BookRepo; -use BookStack\Entities\Repos\EntityRepo; -use BookStack\Entities\ExportService; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; @@ -121,16 +120,18 @@ class BookController extends Controller $bookshelf = null; if ($shelfSlug !== null) { + /** @var Bookshelf $bookshelf */ $bookshelf = $this->bookRepo->getEntityBySlug('bookshelf', $shelfSlug); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } + /** @var Book $book */ $book = $this->bookRepo->createFromInput('book', $request->all()); $this->bookUpdateActions($book, $request); Activity::add($book, 'book_create', $book->id); if ($bookshelf) { - $this->bookRepo->appendBookToShelf($bookshelf, $book); + $bookshelf->appendBook($book); Activity::add($bookshelf, 'bookshelf_update'); } From 8b550991a4dbcd7a05e08e8f600b5444d5f4004d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 20 Sep 2019 00:18:28 +0100 Subject: [PATCH 034/257] Refactored some core entity actions - Created BookChild class to share some page/chapter logic. - Gave entities the power to generate their own permissions and slugs. - Moved bits out of BaseController constructor since it was overly sticky. - Moved slug generation logic into its own class. - Created a facade for permissions due to high use. - Fixed failing test issues from last commits --- app/Actions/ActivityService.php | 1 + app/Auth/Permissions/PermissionService.php | 1 - app/Config/app.php | 1 + app/Entities/BookChild.php | 21 ++++ app/Entities/Bookshelf.php | 2 +- app/Entities/Chapter.php | 13 +-- app/Entities/Entity.php | 36 +++++- app/Entities/Page.php | 11 +- app/Entities/Repos/EntityRepo.php | 112 +++++-------------- app/Entities/Repos/PageRepo.php | 33 +++--- app/Entities/SlugGenerator.php | 64 +++++++++++ app/Facades/Permissions.php | 16 +++ app/Http/Controllers/BookController.php | 17 +-- app/Http/Controllers/BookshelfController.php | 8 +- app/Http/Controllers/ChapterController.php | 4 +- app/Http/Controllers/Controller.php | 26 ++--- app/Http/Controllers/HomeController.php | 10 +- app/Http/Controllers/PageController.php | 10 +- app/Http/Controllers/UserController.php | 2 +- app/Providers/CustomFacadeProvider.php | 13 ++- phpunit.xml | 2 +- routes/web.php | 2 +- tests/Entity/PageDraftTest.php | 15 +-- tests/Entity/TagTest.php | 7 +- tests/SharedTestHelpers.php | 2 +- 25 files changed, 242 insertions(+), 187 deletions(-) create mode 100644 app/Entities/BookChild.php create mode 100644 app/Entities/SlugGenerator.php create mode 100644 app/Facades/Permissions.php diff --git a/app/Actions/ActivityService.php b/app/Actions/ActivityService.php index 8511e4d6d..d092a35c2 100644 --- a/app/Actions/ActivityService.php +++ b/app/Actions/ActivityService.php @@ -73,6 +73,7 @@ class ActivityService */ public function removeEntity(Entity $entity) { + // TODO - Rewrite to db query. $activities = $entity->activity; foreach ($activities as $activity) { $activity->extra = $entity->name; diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index a5ab4ea9a..b4ea17cec 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -215,7 +215,6 @@ class PermissionService * @param Collection $books * @param array $roles * @param bool $deleteOld - * @throws \Throwable */ protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false) { diff --git a/app/Config/app.php b/app/Config/app.php index b4a1076a9..50e3bdb6a 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -182,6 +182,7 @@ return [ 'Setting' => BookStack\Facades\Setting::class, 'Views' => BookStack\Facades\Views::class, 'Images' => BookStack\Facades\Images::class, + 'Permissions' => BookStack\Facades\Permissions::class, ], diff --git a/app/Entities/BookChild.php b/app/Entities/BookChild.php new file mode 100644 index 000000000..c76baf29a --- /dev/null +++ b/app/Entities/BookChild.php @@ -0,0 +1,21 @@ +belongsTo(Book::class); + } + +} \ No newline at end of file diff --git a/app/Entities/Bookshelf.php b/app/Entities/Bookshelf.php index 1db348b6b..7ad2415ed 100644 --- a/app/Entities/Bookshelf.php +++ b/app/Entities/Bookshelf.php @@ -111,7 +111,7 @@ class Bookshelf extends Entity */ public function appendBook(Book $book) { - if (!$this->contains($book)) { + if ($this->contains($book)) { return; } diff --git a/app/Entities/Chapter.php b/app/Entities/Chapter.php index b204f1903..d121432fa 100644 --- a/app/Entities/Chapter.php +++ b/app/Entities/Chapter.php @@ -1,6 +1,8 @@ belongsTo(Book::class); - } - /** * Get the pages that this chapter contains. * @param string $dir diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 480d7caa7..4e54a9e27 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -6,6 +6,7 @@ use BookStack\Actions\Tag; use BookStack\Actions\View; use BookStack\Auth\Permissions\EntityPermission; use BookStack\Auth\Permissions\JointPermission; +use BookStack\Facades\Permissions; use BookStack\Ownable; use Carbon\Carbon; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -91,7 +92,8 @@ class Entity extends Ownable */ public function activity() { - return $this->morphMany(Activity::class, 'entity')->orderBy('created_at', 'desc'); + return $this->morphMany(Activity::class, 'entity') + ->orderBy('created_at', 'desc'); } /** @@ -102,11 +104,6 @@ class Entity extends Ownable return $this->morphMany(View::class, 'viewable'); } - public function viewCountQuery() - { - return $this->views()->selectRaw('viewable_id, sum(views) as view_count')->groupBy('viewable_id'); - } - /** * Get the Tag models that have been user assigned to this entity. * @return \Illuminate\Database\Eloquent\Relations\MorphMany @@ -185,6 +182,14 @@ class Entity extends Ownable return strtolower(static::getClassName()); } + /** + * Get the type of this entity. + */ + public function type(): string + { + return static::getType(); + } + /** * Get an instance of an entity of the given type. * @param $type @@ -255,4 +260,23 @@ class Entity extends Ownable { return $path; } + + /** + * Rebuild the permissions for this entity. + */ + public function rebuildPermissions() + { + /** @noinspection PhpUnhandledExceptionInspection */ + Permissions::buildJointPermissionsForEntity($this); + } + + /** + * Generate and set a new URL slug for this model. + */ + public function refreshSlug(): string + { + $generator = new SlugGenerator($this); + $this->slug = $generator->generate(); + return $this->slug; + } } diff --git a/app/Entities/Page.php b/app/Entities/Page.php index c32417418..752b3c9dd 100644 --- a/app/Entities/Page.php +++ b/app/Entities/Page.php @@ -2,7 +2,7 @@ use BookStack\Uploads\Attachment; -class Page extends Entity +class Page extends BookChild { protected $fillable = ['name', 'html', 'priority', 'markdown']; @@ -30,15 +30,6 @@ class Page extends Entity return $array; } - /** - * Get the book this page sits in. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function book() - { - return $this->belongsTo(Book::class); - } - /** * Get the parent item * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index 925e3c30d..c5fb3501e 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -6,6 +6,7 @@ use BookStack\Actions\ViewService; use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\User; use BookStack\Entities\Book; +use BookStack\Entities\BookChild; use BookStack\Entities\Bookshelf; use BookStack\Entities\Chapter; use BookStack\Entities\Entity; @@ -16,7 +17,6 @@ use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; use BookStack\Uploads\AttachmentService; use DOMDocument; -use DOMNode; use DOMXPath; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; @@ -465,25 +465,6 @@ class EntityRepo return $slug; } - /** - * Check if a slug already exists in the database. - * @param string $type - * @param string $slug - * @param bool|integer $currentId - * @param bool|integer $bookId - * @return bool - */ - protected function slugExists($type, $slug, $currentId = false, $bookId = false) - { - $query = $this->entityProvider->get($type)->where('slug', '=', $slug); - if (strtolower($type) === 'page' || strtolower($type) === 'chapter') { - $query = $query->where('book_id', '=', $bookId); - } - if ($currentId) { - $query = $query->where('id', '!=', $currentId); - } - return $query->count() > 0; - } /** * Updates entity restrictions from a request @@ -501,14 +482,14 @@ class EntityRepo foreach ($restrictions as $action => $value) { $entity->permissions()->create([ 'role_id' => $roleId, - 'action' => strtolower($action) + 'action' => strtolower($action), ]); } } } $entity->save(); - $this->permissionService->buildJointPermissionsForEntity($entity); + $entity->rebuildPermissions(); } @@ -519,12 +500,10 @@ class EntityRepo * @param array $input * @param Book|null $book * @return Entity - * @throws Throwable */ public function createFromInput(string $type, array $input = [], Book $book = null) { $entityModel = $this->entityProvider->get($type)->newInstance($input); - $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $book ? $book->id : false); $entityModel->created_by = user()->id; $entityModel->updated_by = user()->id; @@ -532,41 +511,39 @@ class EntityRepo $entityModel->book_id = $book->id; } + $entityModel->refreshSlug(); $entityModel->save(); if (isset($input['tags'])) { $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); } - $this->permissionService->buildJointPermissionsForEntity($entityModel); + $entityModel->rebuildPermissions(); $this->searchService->indexEntity($entityModel); return $entityModel; } /** * Update entity details from request input. - * Used for books and chapters - * @param string $type - * @param Entity $entityModel - * @param array $input - * @return Entity - * @throws Throwable + * Used for books and chapters. + * TODO: Remove type param */ - public function updateFromInput(string $type, Entity $entityModel, array $input = []) + public function updateFromInput(string $type, Entity $entityModel, array $input = []): Entity { - if ($entityModel->name !== $input['name']) { - $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id); - } - $entityModel->fill($input); $entityModel->updated_by = user()->id; + + if ($entityModel->isDirty('name')) { + $entityModel->refreshSlug(); + } + $entityModel->save(); if (isset($input['tags'])) { $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); } - $this->permissionService->buildJointPermissionsForEntity($entityModel); + $entityModel->rebuildPermissions(); $this->searchService->indexEntity($entityModel); return $entityModel; } @@ -595,62 +572,24 @@ class EntityRepo /** * Change the book that an entity belongs to. - * @param string $type - * @param integer $newBookId - * @param Entity $entity - * @param bool $rebuildPermissions - * @return Entity */ - public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false) + public function changeBook(BookChild $bookChild, int $newBookId): Entity { - $entity->book_id = $newBookId; + $bookChild->book_id = $newBookId; + $bookChild->refreshSlug(); + $bookChild->save(); + // Update related activity - foreach ($entity->activity as $activity) { - $activity->book_id = $newBookId; - $activity->save(); - } - $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId); - $entity->save(); + $bookChild->activity()->update(['book_id' => $newBookId]); // Update all child pages if a chapter - if (strtolower($type) === 'chapter') { - foreach ($entity->pages as $page) { - $this->changeBook('page', $newBookId, $page, false); + if ($bookChild->isA('chapter')) { + foreach ($bookChild->pages as $page) { + $this->changeBook($page, $newBookId); } } - // Update permissions if applicable - if ($rebuildPermissions) { - $entity->load('book'); - $this->permissionService->buildJointPermissionsForEntity($entity->book); - } - - return $entity; - } - - /** - * Alias method to update the book jointPermissions in the PermissionService. - * @param Book $book - */ - public function buildJointPermissionsForBook(Book $book) - { - $this->permissionService->buildJointPermissionsForEntity($book); - } - - /** - * Format a name as a url slug. - * @param $name - * @return string - */ - protected function nameToSlug($name) - { - $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name)); - $slug = preg_replace('/\s{2,}/', ' ', $slug); - $slug = str_replace(' ', '-', $slug); - if ($slug === "") { - $slug = substr(md5(rand(1, 500)), 0, 5); - } - return $slug; + return $bookChild; } /** @@ -885,6 +824,7 @@ class EntityRepo $shelfBooks = $bookshelf->books()->get(); $updatedBookCount = 0; + /** @var Book $book */ foreach ($shelfBooks as $book) { if (!userCan('restrictions-manage', $book)) { continue; @@ -893,7 +833,7 @@ class EntityRepo $book->restricted = $bookshelf->restricted; $book->permissions()->createMany($shelfPermissions); $book->save(); - $this->permissionService->buildJointPermissionsForEntity($book); + $book->rebuildPermissions(); $updatedBookCount++; } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index ba59d1277..0e0585a85 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -9,7 +9,6 @@ use Carbon\Carbon; use DOMDocument; use DOMElement; use DOMXPath; -use Illuminate\Support\Collection; class PageRepo extends EntityRepo { @@ -60,11 +59,6 @@ class PageRepo extends EntityRepo $oldHtml = $page->html; $oldName = $page->name; - // Prevent slug being updated if no name change - if ($page->name !== $input['name']) { - $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id); - } - // Save page tags if present if (isset($input['tags'])) { $this->tagRepo->saveTagsToEntity($page, $input['tags']); @@ -79,11 +73,17 @@ class PageRepo extends EntityRepo $page->fill($input); $page->html = $this->formatHtml($input['html']); $page->text = $this->pageToPlainText($page); + $page->updated_by = $userId; + $page->revision_count++; + if (setting('app-editor') !== 'markdown') { $page->markdown = ''; } - $page->updated_by = $userId; - $page->revision_count++; + + if ($page->isDirty('name')) { + $page->refreshSlug(); + } + $page->save(); // Remove all update drafts for this user & page. @@ -242,8 +242,7 @@ class PageRepo extends EntityRepo } $book->pages()->save($page); - $page = $this->entityProvider->page->find($page->id); - $this->permissionService->buildJointPermissionsForEntity($page); + $page->refresh()->rebuildPermissions(); return $page; } @@ -310,12 +309,11 @@ class PageRepo extends EntityRepo $draftPage->template = ($input['template'] === 'true'); } - $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id); $draftPage->html = $this->formatHtml($input['html']); $draftPage->text = $this->pageToPlainText($draftPage); $draftPage->draft = false; $draftPage->revision_count = 1; - + $draftPage->refreshSlug(); $draftPage->save(); $this->savePageRevision($draftPage, trans('entities.pages_initial_revision')); $this->searchService->indexEntity($draftPage); @@ -468,12 +466,14 @@ class PageRepo extends EntityRepo { $page->revision_count++; $this->savePageRevision($page); + $revision = $page->revisions()->where('id', '=', $revisionId)->first(); $page->fill($revision->toArray()); - $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id); $page->text = $this->pageToPlainText($page); $page->updated_by = user()->id; + $page->refreshSlug(); $page->save(); + $this->searchService->indexEntity($page); return $page; } @@ -482,18 +482,19 @@ class PageRepo extends EntityRepo * Change the page's parent to the given entity. * @param Page $page * @param Entity $parent - * @throws \Throwable */ public function changePageParent(Page $page, Entity $parent) { $book = $parent->isA('book') ? $parent : $parent->book; $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0; $page->save(); + if ($page->book->id !== $book->id) { - $page = $this->changeBook('page', $book->id, $page); + $page = $this->changeBook($page, $book->id); } + $page->load('book'); - $this->permissionService->buildJointPermissionsForEntity($book); + $book->rebuildPermissions(); } /** diff --git a/app/Entities/SlugGenerator.php b/app/Entities/SlugGenerator.php new file mode 100644 index 000000000..e68e00b06 --- /dev/null +++ b/app/Entities/SlugGenerator.php @@ -0,0 +1,64 @@ +entity = $entity; + } + + /** + * Generate a fresh slug for the given entity. + * The slug will generated so it does not conflict within the same parent item. + */ + public function generate(): string + { + $slug = $this->formatNameAsSlug($this->entity->name); + while ($this->slugInUse($slug)) { + $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); + } + return $slug; + } + + /** + * Format a name as a url slug. + */ + protected function formatNameAsSlug(string $name): string + { + $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name)); + $slug = preg_replace('/\s{2,}/', ' ', $slug); + $slug = str_replace(' ', '-', $slug); + if ($slug === "") { + $slug = substr(md5(rand(1, 500)), 0, 5); + } + return $slug; + } + + /** + * Check if a slug is already in-use for this + * type of model within the same parent. + */ + protected function slugInUse(string $slug): bool + { + $query = $this->entity->newQuery()->where('slug', '=', $slug); + + if ($this->entity instanceof BookChild) { + $query->where('book_id', '=', $this->entity->book_id); + } + + if ($this->entity->id) { + $query->where('id', '!=', $this->entity->id); + } + + return $query->count() > 0; + } + + +} \ No newline at end of file diff --git a/app/Facades/Permissions.php b/app/Facades/Permissions.php new file mode 100644 index 000000000..c552d7cdb --- /dev/null +++ b/app/Facades/Permissions.php @@ -0,0 +1,16 @@ +getUser($this->currentUser, 'books_view_type', config('app.views.books')); - $sort = setting()->getUser($this->currentUser, 'books_sort', 'name'); - $order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc'); + $view = setting()->getForCurrentUser('books_view_type', config('app.views.books')); + $sort = setting()->getForCurrentUser('books_sort', 'name'); + $order = setting()->getForCurrentUser('books_sort_order', 'asc'); $books = $this->bookRepo->getAllPaginated('book', 18, $sort, $order); - $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed('book', 4, 0) : false; + $recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed('book', 4, 0) : false; $popular = $this->bookRepo->getPopular('book', 4, 0); $new = $this->bookRepo->getRecentlyCreated('book', 4, 0); @@ -107,7 +107,6 @@ class BookController extends Controller * @throws NotFoundException * @throws ImageUploadException * @throws ValidationException - * @throws Throwable */ public function store(Request $request, string $shelfSlug = null) { @@ -246,7 +245,7 @@ class BookController extends Controller * @return Factory|View * @throws NotFoundException */ - public function getSortItem(string $bookSlug) + public function sortItem(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); $bookChildren = $this->bookRepo->getBookChildren($book); @@ -286,10 +285,12 @@ class BookController extends Controller // Get the books involved in the sort $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); $booksInvolved = $this->bookRepo->getManyById('book', $bookIdsInvolved, false, true); + // Throw permission error if invalid ids or inaccessible books given. if (count($bookIdsInvolved) !== count($booksInvolved)) { $this->showPermissionError(); } + // Check permissions of involved books $booksInvolved->each(function (Book $book) { $this->checkOwnablePermission('book-update', $book); @@ -304,7 +305,7 @@ class BookController extends Controller $chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter; if ($bookChanged) { - $this->bookRepo->changeBook($mapItem->type, $mapItem->book, $model); + $this->bookRepo->changeBook($model, $mapItem->book); } if ($chapterChanged) { $model->chapter_id = intval($mapItem->parentChapter); @@ -318,7 +319,7 @@ class BookController extends Controller // Rebuild permissions and add activity for involved books. $booksInvolved->each(function (Book $book) { - $this->bookRepo->buildJointPermissionsForBook($book); + $book->rebuildPermissions(); Activity::add($book, 'book_sort', $book->id); }); diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index 9bb94484c..b5565eb57 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -40,9 +40,9 @@ class BookshelfController extends Controller */ public function index() { - $view = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid')); - $sort = setting()->getUser($this->currentUser, 'bookshelves_sort', 'name'); - $order = setting()->getUser($this->currentUser, 'bookshelves_sort_order', 'asc'); + $view = setting()->getForCurrentUser('bookshelves_view_type', config('app.views.bookshelves', 'grid')); + $sort = setting()->getForCurrentUser('bookshelves_sort', 'name'); + $order = setting()->getForCurrentUser('bookshelves_sort_order', 'asc'); $sortOptions = [ 'name' => trans('common.sort_name'), 'created_at' => trans('common.sort_created_at'), @@ -54,7 +54,7 @@ class BookshelfController extends Controller $shelf->books = $this->entityRepo->getBookshelfChildren($shelf); } - $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false; + $recents = $this->isSignedIn() ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false; $popular = $this->entityRepo->getPopular('bookshelf', 4, 0); $new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 8090556c9..7b1fb300c 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -201,7 +201,9 @@ class ChapterController extends Controller return redirect()->back(); } - $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true); + $this->entityRepo->changeBook($chapter, $parent->id); + $chapter->rebuildPermissions(); + Activity::add($chapter, 'chapter_move', $chapter->book->id); $this->showSuccessNotification( trans('entities.chapter_move_success', ['bookName' => $parent->name])); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 7bca77495..034c852de 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,9 +2,6 @@ namespace BookStack\Http\Controllers; -use BookStack\Auth\User; -use BookStack\Entities\Entity; -use BookStack\Facades\Activity; use BookStack\Ownable; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; @@ -16,23 +13,20 @@ abstract class Controller extends BaseController { use DispatchesJobs, ValidatesRequests; - /** - * @var User static - */ - protected $currentUser; - - /** - * @var bool - */ - protected $signedIn; - /** * Controller constructor. */ public function __construct() { - $this->currentUser = user(); - $this->signedIn = auth()->check(); + // + } + + /** + * Check if the current user is signed in. + */ + protected function isSignedIn(): bool + { + return auth()->check(); } /** @@ -123,7 +117,7 @@ abstract class Controller extends BaseController protected function checkPermissionOrCurrentUser(string $permissionName, int $userId) { return $this->checkPermissionOr($permissionName, function () use ($userId) { - return $userId === $this->currentUser->id; + return $userId === user()->id; }); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index d2c75f956..a37371d3a 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -26,9 +26,9 @@ class HomeController extends Controller public function index() { $activity = Activity::latest(10); - $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : []; + $draftPages = $this->isSignedIn() ? $this->entityRepo->getUserDraftPages(6) : []; $recentFactor = count($draftPages) > 0 ? 0.5 : 1; - $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor); + $recents = $this->isSignedIn() ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor); $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12); $homepageOptions = ['default', 'books', 'bookshelves', 'page']; @@ -47,9 +47,9 @@ class HomeController extends Controller // Add required list ordering & sorting for books & shelves views. if ($homepageOption === 'bookshelves' || $homepageOption === 'books') { $key = $homepageOption; - $view = setting()->getUser($this->currentUser, $key . '_view_type', config('app.views.' . $key)); - $sort = setting()->getUser($this->currentUser, $key . '_sort', 'name'); - $order = setting()->getUser($this->currentUser, $key . '_sort_order', 'asc'); + $view = setting()->getForCurrentUser($key . '_view_type', config('app.views.' . $key)); + $sort = setting()->getForCurrentUser($key . '_sort', 'name'); + $order = setting()->getForCurrentUser($key . '_sort_order', 'asc'); $sortOptions = [ 'name' => trans('common.sort_name'), diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 37b575010..736fcf4f6 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -56,7 +56,7 @@ class PageController extends Controller $this->checkOwnablePermission('page-create', $parent); // Redirect to draft edit screen if signed in - if ($this->signedIn) { + if ($this->isSignedIn()) { $draft = $this->pageRepo->getDraftPage($book, $chapter); return redirect($draft->getUrl()); } @@ -111,7 +111,7 @@ class PageController extends Controller $this->checkOwnablePermission('page-create', $draft->parent); $this->setPageTitle(trans('entities.pages_edit_draft')); - $draftsEnabled = $this->signedIn; + $draftsEnabled = $this->isSignedIn(); $templates = $this->pageRepo->getPageTemplates(10); return view('pages.edit', [ @@ -230,7 +230,7 @@ class PageController extends Controller } // Check for a current draft version for this user - $userPageDraft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id); + $userPageDraft = $this->pageRepo->getUserPageDraft($page, user()->id); if ($userPageDraft !== null) { $page->name = $userPageDraft->name; $page->html = $userPageDraft->html; @@ -243,7 +243,7 @@ class PageController extends Controller $this->showWarningNotification( implode("\n", $warnings)); } - $draftsEnabled = $this->signedIn; + $draftsEnabled = $this->isSignedIn(); $templates = $this->pageRepo->getPageTemplates(10); return view('pages.edit', [ @@ -285,7 +285,7 @@ class PageController extends Controller $page = $this->pageRepo->getById('page', $pageId, true); $this->checkOwnablePermission('page-update', $page); - if (!$this->signedIn) { + if (!$this->isSignedIn()) { return response()->json([ 'status' => 'error', 'message' => trans('errors.guests_cannot_save_drafts'), diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 6984fef1e..c787e78ad 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -177,7 +177,7 @@ class UserController extends Controller } // External auth id updates - if ($this->currentUser->can('users-manage') && $request->filled('external_auth_id')) { + if (user()->can('users-manage') && $request->filled('external_auth_id')) { $user->external_auth_id = $request->get('external_auth_id'); } diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index e7bde5290..b4158187c 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -4,6 +4,7 @@ namespace BookStack\Providers; use BookStack\Actions\ActivityService; use BookStack\Actions\ViewService; +use BookStack\Auth\Permissions\PermissionService; use BookStack\Settings\SettingService; use BookStack\Uploads\ImageService; use Illuminate\Support\ServiceProvider; @@ -27,20 +28,24 @@ class CustomFacadeProvider extends ServiceProvider */ public function register() { - $this->app->bind('activity', function () { + $this->app->singleton('activity', function () { return $this->app->make(ActivityService::class); }); - $this->app->bind('views', function () { + $this->app->singleton('views', function () { return $this->app->make(ViewService::class); }); - $this->app->bind('setting', function () { + $this->app->singleton('setting', function () { return $this->app->make(SettingService::class); }); - $this->app->bind('images', function () { + $this->app->singleton('images', function () { return $this->app->make(ImageService::class); }); + + $this->app->singleton('permissions', function () { + return $this->app->make(PermissionService::class); + }); } } diff --git a/phpunit.xml b/phpunit.xml index 7de7233af..9f83e95ff 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -29,7 +29,7 @@ - + diff --git a/routes/web.php b/routes/web.php index 0277c1bb4..be729f566 100644 --- a/routes/web.php +++ b/routes/web.php @@ -40,7 +40,7 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{slug}/edit', 'BookController@edit'); Route::put('/{slug}', 'BookController@update'); Route::delete('/{id}', 'BookController@destroy'); - Route::get('/{slug}/sort-item', 'BookController@getSortItem'); + Route::get('/{slug}/sort-item', 'BookController@sortItem'); Route::get('/{slug}', 'BookController@show'); Route::get('/{bookSlug}/permissions', 'BookController@showPermissions'); Route::put('/{bookSlug}/permissions', 'BookController@permissions'); diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index f29231c39..e374575f5 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -18,25 +18,26 @@ class PageDraftTest extends BrowserKitTest public function test_draft_content_shows_if_available() { $addedContent = '

test message content

'; - $this->asAdmin()->visit($this->page->getUrl() . '/edit') + $this->asAdmin()->visit($this->page->getUrl('/edit')) ->dontSeeInField('html', $addedContent); $newContent = $this->page->html . $addedContent; $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]); - $this->asAdmin()->visit($this->page->getUrl() . '/edit') + $this->asAdmin()->visit($this->page->getUrl('/edit')) ->seeInField('html', $newContent); } public function test_draft_not_visible_by_others() { $addedContent = '

test message content

'; - $this->asAdmin()->visit($this->page->getUrl() . '/edit') + $this->asAdmin()->visit($this->page->getUrl('/edit')) ->dontSeeInField('html', $addedContent); $newContent = $this->page->html . $addedContent; $newUser = $this->getEditor(); $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]); - $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit') + + $this->actingAs($newUser)->visit($this->page->getUrl('/edit')) ->dontSeeInField('html', $newContent); } @@ -44,7 +45,7 @@ class PageDraftTest extends BrowserKitTest { $this->asAdmin(); $this->pageRepo->updatePageDraft($this->page, ['html' => 'test content']); - $this->asAdmin()->visit($this->page->getUrl() . '/edit') + $this->asAdmin()->visit($this->page->getUrl('/edit')) ->see('You are currently editing a draft'); } @@ -52,7 +53,7 @@ class PageDraftTest extends BrowserKitTest { $nonEditedPage = \BookStack\Entities\Page::take(10)->get()->last(); $addedContent = '

test message content

'; - $this->asAdmin()->visit($this->page->getUrl() . '/edit') + $this->asAdmin()->visit($this->page->getUrl('/edit')) ->dontSeeInField('html', $addedContent); $newContent = $this->page->html . $addedContent; @@ -60,7 +61,7 @@ class PageDraftTest extends BrowserKitTest $this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]); $this->actingAs($newUser) - ->visit($this->page->getUrl() . '/edit') + ->visit($this->page->getUrl('/edit')) ->see('Admin has started editing this page'); $this->flushSession(); $this->visit($nonEditedPage->getUrl() . '/edit') diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php index 70b8c960b..13876410a 100644 --- a/tests/Entity/TagTest.php +++ b/tests/Entity/TagTest.php @@ -3,6 +3,7 @@ use BookStack\Entities\Book; use BookStack\Entities\Chapter; use BookStack\Actions\Tag; +use BookStack\Entities\Entity; use BookStack\Entities\Page; use BookStack\Auth\Permissions\PermissionService; @@ -14,9 +15,9 @@ class TagTest extends BrowserKitTest /** * Get an instance of a page that has many tags. * @param \BookStack\Actions\Tag[]|bool $tags - * @return mixed + * @return Entity */ - protected function getEntityWithTags($class, $tags = false) + protected function getEntityWithTags($class, $tags = false): Entity { $entity = $class::first(); @@ -122,7 +123,7 @@ class TagTest extends BrowserKitTest // Set restricted permission the page $page->restricted = true; $page->save(); - $permissionService->buildJointPermissionsForEntity($page); + $page->rebuildPermissions(); $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']); $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals([]); diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index ed92fa59f..2bff27223 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -80,7 +80,7 @@ trait SharedTestHelpers */ protected function regenEntityPermissions(Entity $entity) { - app(PermissionService::class)->buildJointPermissionsForEntity($entity); + $entity->rebuildPermissions(); $entity->load('jointPermissions'); } From 7cd956b24b7a08a8b274d9f244bf6ab482de9635 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 20 Sep 2019 01:18:59 +0100 Subject: [PATCH 035/257] Removed some unused parameters and fixed env test logic --- app/Auth/Permissions/PermissionService.php | 9 ++++----- app/Entities/Repos/EntityRepo.php | 21 ++++++++++---------- app/Http/Controllers/BookController.php | 2 +- app/Http/Controllers/BookshelfController.php | 2 +- app/Http/Controllers/ChapterController.php | 3 +-- tests/SharedTestHelpers.php | 6 +----- tests/Unit/ConfigTest.php | 1 - 7 files changed, 19 insertions(+), 25 deletions(-) diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index b4ea17cec..9e1876c90 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -683,12 +683,11 @@ class PermissionService if (strtolower($entityType) === 'page') { // Prevent drafts being visible to others. $query = $query->where(function ($query) { - $query->where('draft', '=', false); - if ($this->currentUser()) { - $query->orWhere(function ($query) { - $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id); + $query->where('draft', '=', false) + ->orWhere(function ($query) { + $query->where('draft', '=', true) + ->where('created_by', '=', $this->currentUser()->id); }); - } }); } diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index c5fb3501e..13a335ea0 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -478,14 +478,16 @@ class EntityRepo $entity->permissions()->delete(); if ($request->filled('restrictions')) { - foreach ($request->get('restrictions') as $roleId => $restrictions) { - foreach ($restrictions as $action => $value) { - $entity->permissions()->create([ + $entityPermissionData = collect($request->get('restrictions'))->flatMap(function($restrictions, $roleId) { + return collect($restrictions)->keys()->map(function($action) use ($roleId) { + return [ 'role_id' => $roleId, - 'action' => strtolower($action), - ]); - } - } + 'action' => strtolower($action), + ] ; + }); + }); + + $entity->permissions()->createMany($entityPermissionData); } $entity->save(); @@ -525,10 +527,9 @@ class EntityRepo /** * Update entity details from request input. - * Used for books and chapters. - * TODO: Remove type param + * Used for shelves, books and chapters. */ - public function updateFromInput(string $type, Entity $entityModel, array $input = []): Entity + public function updateFromInput(Entity $entityModel, array $input): Entity { $entityModel->fill($input); $entityModel->updated_by = user()->id; diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index b2fb1dcba..a9a24d2ff 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -199,7 +199,7 @@ class BookController extends Controller 'image' => $this->imageRepo->getImageValidationRules(), ]); - $book = $this->bookRepo->updateFromInput('book', $book, $request->all()); + $book = $this->bookRepo->updateFromInput($book, $request->all()); $this->bookUpdateActions($book, $request); Activity::add($book, 'book_update', $book->id); diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index b5565eb57..bef96dd01 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -177,7 +177,7 @@ class BookshelfController extends Controller 'image' => $this->imageRepo->getImageValidationRules(), ]); - $shelf = $this->entityRepo->updateFromInput('bookshelf', $shelf, $request->all()); + $shelf = $this->entityRepo->updateFromInput($shelf, $request->all()); $this->shelfUpdateActions($shelf, $request); Activity::add($shelf, 'bookshelf_update'); diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 7b1fb300c..f728d1313 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -3,7 +3,6 @@ use Activity; use BookStack\Auth\UserRepo; use BookStack\Entities\Repos\EntityRepo; -use BookStack\Entities\ExportService; use Illuminate\Http\Request; use Illuminate\Http\Response; use Views; @@ -113,7 +112,7 @@ class ChapterController extends Controller $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); - $this->entityRepo->updateFromInput('chapter', $chapter, $request->all()); + $this->entityRepo->updateFromInput($chapter, $request->all()); Activity::add($chapter, 'chapter_update', $chapter->book->id); return redirect($chapter->getUrl()); } diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index 2bff27223..358bf6ee3 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -215,13 +215,11 @@ trait SharedTestHelpers protected function runWithEnv(string $name, $value, callable $callback) { Env::disablePutenv(); - $originalVal = $_ENV[$name] ?? null; + $originalVal = $_SERVER[$name] ?? null; if (is_null($value)) { - unset($_ENV[$name]); unset($_SERVER[$name]); } else { - $_ENV[$name] = $value; $_SERVER[$name] = $value; } @@ -230,10 +228,8 @@ trait SharedTestHelpers if (is_null($originalVal)) { unset($_SERVER[$name]); - unset($_ENV[$name]); } else { $_SERVER[$name] = $originalVal; - $_ENV[$name] = $originalVal; } } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 7b9f64e6a..c84305ad8 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -20,7 +20,6 @@ class ConfigTest extends TestCase public function test_filesystem_attachments_falls_back_to_storage_type_var() { - putenv('STORAGE_TYPE=local_secure'); $this->runWithEnv('STORAGE_TYPE', 'local_secure', function() { $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', 's3', 'filesystems.attachments', 's3'); $this->checkEnvConfigResult('STORAGE_ATTACHMENT_TYPE', null, 'filesystems.attachments', 'local_secure'); From 0fa4ef072f810add480ab582d50cbf89b8040ea8 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 24 Sep 2019 22:36:08 -0300 Subject: [PATCH 036/257] Updated 'Spanish Argentina' translation. --- resources/lang/es_AR/validation.php | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/resources/lang/es_AR/validation.php b/resources/lang/es_AR/validation.php index 556903cb9..1df55cdae 100644 --- a/resources/lang/es_AR/validation.php +++ b/resources/lang/es_AR/validation.php @@ -36,13 +36,41 @@ return [ 'digits' => ':attribute debe ser de :digits dígitos.', 'digits_between' => ':attribute debe ser un valor entre :min y :max dígios.', 'email' => ':attribute debe ser una dirección álida.', + 'ends_with' => 'El :attribute debe terminar con uno de los siguientes: :values', 'filled' => 'El campo :attribute es requerido.', + 'gt' => [ + 'numeric' => 'El :attribute debe ser mayor que :value.', + 'file' => 'El :attribute debe ser mayor que :value kilobytes.', + 'string' => 'El :attribute debe ser mayor que :value caracteres.', + 'array' => 'El :attribute debe tener más de :value objetos.', + ], + 'gte' => [ + 'numeric' => 'El :attribute debe ser mayor o igual a :value.', + 'file' => 'El :attribute debe ser mayor o igual a :value kilobytes.', + 'string' => 'El :attribute debe ser mayor o igual a :value caracteres.', + 'array' => 'El :attribute debe tener :value objetos o más.', + ], 'exists' => 'El :attribute seleccionado es inválido.', 'image' => 'El :attribute debe ser una imagen.', 'in' => 'El selected :attribute es inválio.', 'image_extension' => 'El :attribute debe tener una extensión de imagen válida y soportada.', 'integer' => 'El :attribute debe ser un entero.', 'ip' => 'El :attribute debe ser una dirección IP álida.', + 'ipv4' => 'El :attribute debe ser una dirección IPv4 válida.', + 'ipv6' => 'El :attribute debe ser una dirección IPv6 válida.', + 'json' => 'El :attribute debe ser una cadena JSON válida.', + 'lt' => [ + 'numeric' => 'El :attribute debe ser menor que :value.', + 'file' => 'El :attribute debe ser menor que :value kilobytes.', + 'string' => 'El :attribute debe ser menor que :value caracteres.', + 'array' => 'El :attribute debe tener menos de :value objetos.', + ], + 'lte' => [ + 'numeric' => 'El :attribute debe ser menor o igual a :value.', + 'file' => 'El :attribute debe ser menor o igual a :value kilobytes.', + 'string' => 'El :attribute debe ser menor o igual a :value caracteres.', + 'array' => 'El :attribute no debe tener más de :value objetos.', + ], 'max' => [ 'numeric' => ':attribute no puede ser mayor que :max.', 'file' => ':attribute no puede ser mayor que :max kilobytes.', @@ -57,7 +85,8 @@ return [ 'array' => ':attribute debe tener como mínimo :min items.', ], 'no_double_extension' => 'El :attribute debe tener una única extensión de archivo.', - 'not_in' => ':attribute seleccionado es inválio.', + 'not_in' => ':attribute seleccionado es inválido.', + 'not_regex' => 'El formato de :attribute es inválido.', 'numeric' => ':attribute debe ser numérico.', 'regex' => ':attribute con formato inválido', 'required' => ':attribute es requerido.', From 2f94f078e36cf65fa8c7b1d640c030b415714cdc Mon Sep 17 00:00:00 2001 From: Christopher Wilkinson Date: Thu, 26 Sep 2019 22:51:24 +0100 Subject: [PATCH 037/257] Fix Book form (create) returning to the full books list on cancel Fixes #1662 Added a small block of logic to determine the correct URL to attribute to the cancel button on a given page create form. If adding a book from a bookshelf, return to the bookshelf. If editing a book, return to the book. In all other cases, return to the full books list. --- resources/views/books/form.blade.php | 11 ++++++++++- tests/Entity/EntityTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/resources/views/books/form.blade.php b/resources/views/books/form.blade.php index 8960b4135..91a3899de 100644 --- a/resources/views/books/form.blade.php +++ b/resources/views/books/form.blade.php @@ -36,6 +36,15 @@
- {{ trans('common.cancel') }} + getUrl(); + } else if (isset($book)) { + $cancelUrl = $book->getUrl(); + } else { + $cancelUrl = '/books'; + } + ?> + {{ trans('common.cancel') }}
\ No newline at end of file diff --git a/tests/Entity/EntityTest.php b/tests/Entity/EntityTest.php index a3fb1cfe1..c28ca8b3e 100644 --- a/tests/Entity/EntityTest.php +++ b/tests/Entity/EntityTest.php @@ -1,5 +1,6 @@ assertEquals('parta-partb-partc', $book->slug); } + public function test_shelf_cancel_creation_returns_to_correct_place() + { + $shelf = Bookshelf::first(); + + // Cancel button from shelf goes back to shelf + $this->asEditor()->visit($shelf->getUrl('/create-book')) + ->see('Cancel') + ->click('Cancel') + ->seePageIs($shelf->getUrl()); + + // Cancel button from books goes back to books + $this->asEditor()->visit('/create-book') + ->see('Cancel') + ->click('Cancel') + ->seePageIs('/books'); + + // Cancel button from book edit goes back to book + $book = Book::first(); + + $this->asEditor()->visit($book->getUrl('/edit')) + ->see('Cancel') + ->click('Cancel') + ->seePageIs($book->getUrl()); + } + } From 593933c84ea3c76e5a77fc5dc3da2a59ba778feb Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Tue, 1 Oct 2019 20:34:54 +0200 Subject: [PATCH 038/257] French translation update --- resources/lang/fr/auth.php | 40 ++++++++++++++++++++------------ resources/lang/fr/common.php | 14 +++++++++-- resources/lang/fr/entities.php | 30 +++++++++++++++--------- resources/lang/fr/errors.php | 4 +++- resources/lang/fr/passwords.php | 4 ++-- resources/lang/fr/settings.php | 22 +++++++++--------- resources/lang/fr/validation.php | 29 +++++++++++++++++++++++ 7 files changed, 101 insertions(+), 42 deletions(-) diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php index 89908c8c4..8b4bb1f52 100644 --- a/resources/lang/fr/auth.php +++ b/resources/lang/fr/auth.php @@ -28,19 +28,19 @@ return [ 'password' => 'Mot de passe', 'password_confirm' => 'Confirmez le mot de passe', 'password_hint' => 'Doit faire plus de 7 caractères', - 'forgot_password' => 'Mot de passe oublié ?', + 'forgot_password' => 'Mot de passe oublié ?', 'remember_me' => 'Se souvenir de moi', - 'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte", + 'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte.", 'create_account' => 'Créer un compte', - 'already_have_account' => 'Vous avez déjà un compte?', - 'dont_have_account' => 'Vous n\'avez pas de compte?', - 'social_login' => 'Social Login', - 'social_registration' => 'Enregistrement Social', - 'social_registration_text' => "S'inscrire et se connecter avec un réseau social", + 'already_have_account' => 'Vous avez déjà un compte ?', + 'dont_have_account' => 'Vous n\'avez pas de compte ?', + 'social_login' => 'Connexion avec un réseau social', + 'social_registration' => 'Inscription avec un réseau social', + 'social_registration_text' => "S'inscrire et se connecter avec un réseau social.", - 'register_thanks' => 'Merci pour votre enregistrement', + 'register_thanks' => 'Merci pour votre inscription !', 'register_confirm' => 'Vérifiez vos e-mails et cliquez sur le lien de confirmation pour rejoindre :appName.', - 'registrations_disabled' => "L'inscription est désactivée pour le moment", + 'registrations_disabled' => "Les inscriptions sont désactivées pour le moment", 'registration_email_domain_invalid' => 'Cette adresse e-mail ne peut pas accéder à l\'application', 'register_success' => 'Merci pour votre inscription. Vous êtes maintenant inscrit(e) et connecté(e)', @@ -48,14 +48,14 @@ return [ /** * Password Reset */ - 'reset_password' => 'Reset Password', - 'reset_password_send_instructions' => 'Entrez votre adresse e-mail ci-dessous et un e-mail avec un lien de réinitialisation de mot de passe vous sera envoyé', + 'reset_password' => 'Réinitialiser le mot de passe', + 'reset_password_send_instructions' => 'Entrez votre adresse e-mail ci-dessous et un e-mail avec un lien de réinitialisation de mot de passe vous sera envoyé.', 'reset_password_send_button' => 'Envoyer un lien de réinitialisation', 'reset_password_sent_success' => 'Un lien de réinitialisation a été envoyé à :email.', 'reset_password_success' => 'Votre mot de passe a été réinitialisé avec succès.', 'email_reset_subject' => 'Réinitialisez votre mot de passe pour :appName', - 'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte', + 'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte.', 'email_reset_not_requested' => 'Si vous n\'avez pas effectué cette demande, vous pouvez ignorer cet e-mail.', @@ -63,11 +63,11 @@ return [ * Email Confirmation */ 'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName', - 'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !', - 'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :', + 'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !', + 'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :', 'email_confirm_action' => 'Confirmez votre adresse e-mail', 'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.', - 'email_confirm_success' => 'Votre adresse e-mail a été confirmée !', + 'email_confirm_success' => 'Votre adresse e-mail a été confirmée !', 'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.', 'email_not_confirmed' => 'Adresse e-mail non confirmée', @@ -75,4 +75,14 @@ return [ 'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.', 'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.', 'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation', + + // User Invite + 'user_invite_email_subject' => 'Vous avez été invité(e) à rejoindre :appName !', + 'user_invite_email_greeting' => 'Un compte vous a été créé sur :appName.', + 'user_invite_email_text' => 'Cliquez sur le bouton ci-dessous pour renseigner le mot de passe et récupérer l\'accès :', + 'user_invite_email_action' => 'Renseignez le mot de passe de votre compte', + 'user_invite_page_welcome' => 'Bienvenue dans :appName !', + 'user_invite_page_text' => 'Pour finaliser votre compte et recevoir l\'accès, vous devez renseigner le mot de passe qui sera utilisé pour la connexion à :appName les prochaines fois.', + 'user_invite_page_confirm_button' => 'Confirmez le mot de passe', + 'user_invite_success' => 'Mot de passe renseigné, vous avez maintenant accès à :appName !' ]; diff --git a/resources/lang/fr/common.php b/resources/lang/fr/common.php index 1cf6e716f..ae33ef3eb 100644 --- a/resources/lang/fr/common.php +++ b/resources/lang/fr/common.php @@ -20,7 +20,7 @@ return [ 'description' => 'Description', 'role' => 'Rôle', 'cover_image' => 'Image de couverture', - 'cover_image_description' => 'Cette image doit être environ 440x250px.', + 'cover_image_description' => 'Cette image doit faire environ 440x250 px.', /** * Actions @@ -45,6 +45,10 @@ return [ /** * Sort Options */ + 'sort_options' => 'Options de tri', + 'sort_direction_toggle' => 'Inverser la direction du tri', + 'sort_ascending' => 'Tri ascendant', + 'sort_descending' => 'Tri descendant', 'sort_name' => 'Nom', 'sort_created_at' => 'Date de création', 'sort_updated_at' => 'Date de mise à jour', @@ -62,16 +66,22 @@ return [ 'grid_view' => 'Vue en grille', 'list_view' => 'Vue en liste', 'default' => 'Défaut', + 'breadcrumb' => 'Fil d\'Ariane', /** * Header */ + 'profile_menu' => 'Menu du profil', 'view_profile' => 'Voir le profil', 'edit_profile' => 'Modifier le profil', + // Layout tabs + 'tab_info' => 'Info', + 'tab_content' => 'Contenu', + /** * Email Content */ - 'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :', + 'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :', 'email_rights' => 'Tous droits réservés', ]; diff --git a/resources/lang/fr/entities.php b/resources/lang/fr/entities.php index 4ba1a36e3..958a162da 100644 --- a/resources/lang/fr/entities.php +++ b/resources/lang/fr/entities.php @@ -92,7 +92,7 @@ return [ 'shelves_delete' => 'Supprimer l\'étagère', 'shelves_delete_named' => 'Supprimer l\'étagère :name', 'shelves_delete_explain' => "Ceci va supprimer l\'étagère nommée \':bookName\'. Les livres contenus dans cette étagère ne seront pas supprimés.", - 'shelves_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer cette étagère ?', + 'shelves_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer cette étagère ?', 'shelves_permissions' => 'Permissions de l\'étagère', 'shelves_permissions_updated' => 'Permissions de l\'étagère mises à jour', 'shelves_permissions_active' => 'Permissions de l\'étagère activées', @@ -118,7 +118,7 @@ return [ 'books_delete' => 'Supprimer un livre', 'books_delete_named' => 'Supprimer le livre :bookName', 'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', tous les chapitres et pages seront supprimés.', - 'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?', + 'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?', 'books_edit' => 'Modifier le livre', 'books_edit_named' => 'Modifier le livre :bookName', 'books_form_book_name' => 'Nom du livre', @@ -155,7 +155,7 @@ return [ 'chapters_delete' => 'Supprimer le chapitre', 'chapters_delete_named' => 'Supprimer le chapitre :chapterName', 'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', toutes les pages seront déplacées dans le livre parent.', - 'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?', + 'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?', 'chapters_edit' => 'Modifier le chapitre', 'chapters_edit_named' => 'Modifier le chapitre :chapterName', 'chapters_save' => 'Enregistrer le chapitre', @@ -184,8 +184,8 @@ return [ 'pages_delete_draft' => 'Supprimer le brouillon', 'pages_delete_success' => 'Page supprimée', 'pages_delete_draft_success' => 'Brouillon supprimé', - 'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?', - 'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?', + 'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?', + 'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?', 'pages_editing_named' => 'Modification de la page :pageName', 'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête', 'pages_edit_save_draft' => 'Enregistrer le brouillon', @@ -239,10 +239,11 @@ return [ 'start_b' => ':userName a commencé à éditer cette page', 'time_a' => 'depuis la dernière sauvegarde', 'time_b' => 'dans les :minCount dernières minutes', - 'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !', + 'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !', ], 'pages_draft_discarded' => 'Brouillon écarté, la page est dans sa version actuelle.', 'pages_specific' => 'Page Spécifique', + 'pages_is_template' => 'Modèle de page', /** * Editor sidebar @@ -281,6 +282,12 @@ return [ 'attachments_file_uploaded' => 'Fichier ajouté avec succès', 'attachments_file_updated' => 'Fichier mis à jour avec succès', 'attachments_link_attached' => 'Lien attaché à la page avec succès', + 'templates' => 'Modèles', + 'templates_set_as_template' => 'La page est un modèle', + 'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.', + 'templates_replace_content' => 'Remplacer le contenu de la page', + 'templates_append_content' => 'Ajouter après le contenu de la page', + 'templates_prepend_content' => 'Ajouter devant le contenu de la page', /** * Profile View @@ -299,23 +306,24 @@ return [ 'comments' => 'Commentaires', 'comment_add' => 'Ajouter un commentaire', 'comment_placeholder' => 'Entrez vos commentaires ici', - 'comment_count' => '{0} Pas de commentaires|{1} 1 Commentaire|[2,*] :count Commentaires', + 'comment_count' => '{0} Pas de commentaires|{1} Un commentaire|[2,*] :count commentaires', 'comment_save' => 'Enregistrer le commentaire', - 'comment_saving' => 'Enregistrement du commentaire...', - 'comment_deleting' => 'Suppression du commentaire...', + 'comment_saving' => 'Enregistrement du commentaire…', + 'comment_deleting' => 'Suppression du commentaire…', 'comment_new' => 'Nouveau commentaire', 'comment_created' => 'commenté :createDiff', 'comment_updated' => 'Mis à jour :updateDiff par :username', 'comment_deleted_success' => 'Commentaire supprimé', 'comment_created_success' => 'Commentaire ajouté', 'comment_updated_success' => 'Commentaire mis à jour', - 'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?', + 'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?', 'comment_in_reply_to' => 'En réponse à :commentId', /** * Revision */ - 'revision_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette révision?', + 'revision_delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette révision ?', + 'revision_restore_confirm' => 'Êtes-vous sûr de vouloir restaurer cette révision ? Le contenu courant de la page va être remplacé.', 'revision_delete_success' => 'Révision supprimée', 'revision_cannot_delete_latest' => 'Impossible de supprimer la dernière révision.' ]; diff --git a/resources/lang/fr/errors.php b/resources/lang/fr/errors.php index e758f6e5e..ecb751f25 100644 --- a/resources/lang/fr/errors.php +++ b/resources/lang/fr/errors.php @@ -20,7 +20,7 @@ return [ 'ldap_extension_not_installed' => 'L\'extension LDAP PHP n\'est pas installée', 'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué', 'social_no_action_defined' => 'Pas d\'action définie', - 'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error", + 'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error", 'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.', 'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.', 'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.', @@ -29,6 +29,7 @@ return [ 'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.', 'social_driver_not_found' => 'Pilote de compte social absent', 'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.', + 'invite_token_expired' => 'Le lien de cette invitation a expiré. Vous pouvez essayer de réinitiliser votre mot de passe.', // System 'path_not_writable' => 'Impossible d\'écrire dans :filePath. Assurez-vous d\'avoir les droits d\'écriture sur le serveur', @@ -66,6 +67,7 @@ return [ 'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié', 'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et ne peut pas être supprimé', 'role_registration_default_cannot_delete' => 'Ce rôle ne peut pas être supprimé tant qu\'il est le rôle par défaut', + 'role_cannot_remove_only_admin' => 'Ceci est le seul compte administrateur. Assignez un nouvel administrateur avant de le supprimer ici.', // Error pages '404_page_not_found' => 'Page non trouvée', diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php index 484b4b20c..16d5e4d2c 100644 --- a/resources/lang/fr/passwords.php +++ b/resources/lang/fr/passwords.php @@ -16,7 +16,7 @@ return [ 'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.', 'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.", 'token' => 'Le jeton de réinitialisation est invalide.', - 'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !', - 'reset' => 'Votre mot de passe a été réinitialisé !', + 'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !', + 'reset' => 'Votre mot de passe a été réinitialisé !', ]; diff --git a/resources/lang/fr/settings.php b/resources/lang/fr/settings.php index f978114c5..1f32d746a 100644 --- a/resources/lang/fr/settings.php +++ b/resources/lang/fr/settings.php @@ -20,13 +20,13 @@ return [ 'app_features_security' => 'Fonctionnalités et sécurité', 'app_name' => 'Nom de l\'application', 'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.', - 'app_name_header' => 'Afficher le nom dans l\'en-tête ?', + 'app_name_header' => 'Afficher le nom dans l\'en-tête ?', 'app_public_access' => 'Accès public', 'app_public_access_desc' => 'L\'activation de cette option permettra aux visiteurs, qui ne sont pas connectés, d\'accéder au contenu de votre instance BookStack.', 'app_public_access_desc_guest' => 'L\'accès pour les visiteurs publics peut être contrôlé par l\'utilisateur "Guest".', 'app_public_access_toggle' => 'Autoriser l\'accès public', - 'app_public_viewing' => 'Accepter le visionnage public des pages ?', - 'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?', + 'app_public_viewing' => 'Accepter le visionnage public des pages ?', + 'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?', 'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisé', 'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.', 'app_editor' => 'Editeur des pages', @@ -54,7 +54,7 @@ return [ 'reg_enable_desc' => 'Lorsque l\'inscription est activée, l\'utilisateur pourra s\'enregistrer en tant qu\'utilisateur de l\'application. Lors de l\'inscription, ils se voient attribuer un rôle par défaut.', 'reg_default_role' => 'Rôle par défaut lors de l\'inscription', 'reg_email_confirmation' => 'Confirmation de l\'e-mail', - 'reg_email_confirmation_toggle' => 'Obliger la confirmation par e-mail ?', + 'reg_email_confirmation_toggle' => 'Obliger la confirmation par e-mail ?', 'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.', 'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine', 'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateurs recevront un e-mail de confirmation à cette adresse.
Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.', @@ -69,9 +69,9 @@ return [ 'maint_image_cleanup_desc' => "Scan le contenu des pages et des révisions pour vérifier les images et les dessins en cours d'utilisation et lesquels sont redondant. Veuillez à faire une sauvegarde de la base de données et des images avant de lancer ceci.", 'maint_image_cleanup_ignore_revisions' => 'Ignorer les images dans les révisions', 'maint_image_cleanup_run' => 'Lancer le nettoyage', - 'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?', - 'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !', - 'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !', + 'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?', + 'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !', + 'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !', /** * Role settings @@ -85,7 +85,7 @@ return [ 'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.', 'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.', 'role_delete_no_migration' => "Ne pas assigner de nouveau rôle", - 'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?', + 'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?', 'role_delete_success' => 'Le rôle a été supprimé avec succès', 'role_edit' => 'Modifier le rôle', 'role_details' => 'Détails du rôle', @@ -96,7 +96,7 @@ return [ 'role_manage_users' => 'Gérer les utilisateurs', 'role_manage_roles' => 'Gérer les rôles et permissions', 'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages', - 'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres, et pages', + 'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres et pages', 'role_manage_settings' => 'Gérer les préférences de l\'application', 'role_asset' => 'Permissions des ressources', 'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions', @@ -131,13 +131,13 @@ return [ 'users_delete' => 'Supprimer un utilisateur', 'users_delete_named' => 'Supprimer l\'utilisateur :userName', 'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.', - 'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?', + 'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?', 'users_delete_success' => 'Utilisateurs supprimés avec succès', 'users_edit' => 'Modifier l\'utilisateur', 'users_edit_profile' => 'Modifier le profil', 'users_edit_success' => 'Utilisateur mis à jour avec succès', 'users_avatar' => 'Avatar de l\'utilisateur', - 'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256px.', + 'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256 px.', 'users_preferred_language' => 'Langue préférée', 'users_preferred_language_desc' => 'Cette option changera la langue utilisée pour l\'interface utilisateur de l\'application. Ceci n\'affectera aucun contenu créé par l\'utilisateur.', 'users_social_accounts' => 'Comptes sociaux', diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index 4be55df4f..04993f249 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -35,13 +35,41 @@ return [ 'digits' => ':attribute doit être de longueur :digits.', 'digits_between' => ':attribute doit avoir une longueur entre :min et :max.', 'email' => ':attribute doit être une adresse e-mail valide.', + 'ends_with' => ':attribute doit se terminer par une des valeurs suivantes : :values', 'filled' => ':attribute est un champ requis.', + 'gt' => [ + 'numeric' => ':attribute doit être plus grand que :value.', + 'file' => ':attribute doit être plus grand que :value kilobytes.', + 'string' => ':attribute doit être plus grand que :value caractères.', + 'array' => ':attribute doit avoir plus que :value éléments.', + ], + 'gte' => [ + 'numeric' => ':attribute doit être plus grand ou égal à :value.', + 'file' => ':attribute doit être plus grand ou égal à :value kilobytes.', + 'string' => ':attribute doit être plus grand ou égal à :value caractères.', + 'array' => ':attribute doit avoir :value éléments ou plus.', + ], 'exists' => 'L\'attribut :attribute est invalide.', 'image' => ':attribute doit être une image.', 'image_extension' => ':attribute doit avoir une extension d\'image valide et supportée.', 'in' => 'L\'attribut :attribute est invalide.', 'integer' => ':attribute doit être un chiffre entier.', 'ip' => ':attribute doit être une adresse IP valide.', + 'ipv4' => ':attribute doit être une adresse IPv4 valide.', + 'ipv6' => ':attribute doit être une adresse IPv6 valide.', + 'json' => ':attribute doit être une chaine JSON valide.', + 'lt' => [ + 'numeric' => ':attribute doit être plus petit que :value.', + 'file' => ':attribute doit être plus petit que :value kilobytes.', + 'string' => ':attribute doit être plus petit que :value caractères.', + 'array' => ':attribute doit avoir moins de :value éléments.', + ], + 'lte' => [ + 'numeric' => ':attribute doit être plus petit ou égal à :value.', + 'file' => ':attribute doit être plus petit ou égal à :value kilobytes.', + 'string' => ':attribute doit être plus petit ou égal à :value caractères.', + 'array' => ':attribute ne doit pas avoir plus de :value éléments.', + ], 'max' => [ 'numeric' => ':attribute ne doit pas excéder :max.', 'file' => ':attribute ne doit pas excéder :max kilobytes.', @@ -57,6 +85,7 @@ return [ ], 'no_double_extension' => ':attribute ne doit avoir qu\'une seule extension de fichier.', 'not_in' => 'L\'attribut sélectionné :attribute est invalide.', + 'not_regex' => ':attribute a un format invalide.', 'numeric' => ':attribute doit être un nombre.', 'regex' => ':attribute a un format invalide.', 'required' => ':attribute est un champ requis.', From 3f025f69cff807731ece18d5db25d178784c319c Mon Sep 17 00:00:00 2001 From: Ammar Al-Khawaldeh <3mmar.g97@gmail.com> Date: Thu, 3 Oct 2019 20:46:49 +0300 Subject: [PATCH 039/257] Add git to the apt-get install packages. --- dev/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile index 82ea2b6f6..b34820861 100644 --- a/dev/docker/Dockerfile +++ b/dev/docker/Dockerfile @@ -4,7 +4,7 @@ ENV APACHE_DOCUMENT_ROOT /app/public WORKDIR /app RUN apt-get update -y \ - && apt-get install -y libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \ + && apt-get install -y git libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \ && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \ && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd ldap \ && a2enmod rewrite \ From 31f5786e01fc5ed439f347c6979679612baca4fb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 5 Oct 2019 12:55:01 +0100 Subject: [PATCH 040/257] Entity Repo & Controller Refactor (#1690) * Started mass-refactoring of the current entity repos * Rewrote book tree logic - Now does two simple queries instead of one really complex one. - Extracted logic into its own class. - Remove model-level akward union field listing. - Logic now more readable than being large separate query and compilation functions. * Extracted and split book sort logic * Finished up Book controller/repo organisation * Refactored bookshelves controllers and repo parts * Fixed issues found via phpunit * Refactored Chapter controller * Updated Chapter export controller * Started Page controller/repo refactor * Refactored another chunk of PageController * Completed initial pagecontroller refactor pass * Fixed tests and continued reduction of old repos * Removed old page remove and further reduced entity repo * Removed old entity repo, split out page controller * Ran phpcbf and split out some page content methods * Tidied up some EntityProvider elements * Fixed issued caused by viewservice change --- app/Actions/ActivityService.php | 5 +- app/Actions/ViewService.php | 5 +- app/Auth/Permissions/PermissionService.php | 64 +- app/Auth/Role.php | 15 +- app/Auth/User.php | 13 +- app/Auth/UserRepo.php | 68 +- app/Entities/Book.php | 57 +- app/Entities/BookChild.php | 41 +- app/Entities/Bookshelf.php | 53 +- app/Entities/BreadcrumbsViewComposer.php | 5 +- app/Entities/Chapter.php | 36 +- app/Entities/Entity.php | 84 +- app/Entities/EntityProvider.php | 20 +- app/Entities/ExportService.php | 91 +- app/Entities/HasCoverImage.php | 20 + app/Entities/Managers/BookContents.php | 204 +++++ .../EntityContext.php} | 32 +- app/Entities/Managers/PageContent.php | 304 +++++++ app/Entities/Managers/PageEditActivity.php | 74 ++ app/Entities/Managers/TrashCan.php | 109 +++ app/Entities/Page.php | 54 +- app/Entities/PageRevision.php | 25 +- app/Entities/Repos/BaseRepo.php | 118 +++ app/Entities/Repos/BookRepo.php | 142 ++- app/Entities/Repos/BookshelfRepo.php | 173 ++++ app/Entities/Repos/ChapterRepo.php | 108 +++ app/Entities/Repos/EntityRepo.php | 843 ------------------ app/Entities/Repos/PageRepo.php | 715 +++++++-------- app/Entities/SlugGenerator.php | 4 +- app/Exceptions/MoveOperationException.php | 8 + app/Exceptions/SortOperationException.php | 8 + app/Http/Controllers/AttachmentController.php | 73 +- .../Auth/ConfirmEmailController.php | 10 +- .../Auth/ForgotPasswordController.php | 2 +- .../Auth/ResetPasswordController.php | 2 +- .../Controllers/Auth/UserInviteController.php | 4 +- app/Http/Controllers/BookController.php | 251 +----- app/Http/Controllers/BookExportController.php | 20 +- app/Http/Controllers/BookSortController.php | 82 ++ app/Http/Controllers/BookshelfController.php | 177 ++-- app/Http/Controllers/ChapterController.php | 169 ++-- .../Controllers/ChapterExportController.php | 38 +- app/Http/Controllers/CommentController.php | 33 +- app/Http/Controllers/Controller.php | 12 +- app/Http/Controllers/HomeController.php | 46 +- .../Controllers/Images/ImageController.php | 19 +- app/Http/Controllers/PageController.php | 496 ++++------- app/Http/Controllers/PageExportController.php | 29 +- .../Controllers/PageRevisionController.php | 128 +++ .../Controllers/PageTemplateController.php | 13 +- app/Http/Controllers/PermissionController.php | 8 +- app/Http/Controllers/SearchController.php | 49 +- app/Http/Controllers/SettingController.php | 6 +- app/Http/Controllers/UserController.php | 10 +- app/Http/Middleware/GlobalViewData.php | 3 +- resources/views/books/export.blade.php | 2 +- resources/views/errors/404.blade.php | 6 +- .../views/form/entity-permissions.blade.php | 2 +- resources/views/shelves/form.blade.php | 6 +- resources/views/shelves/show.blade.php | 4 +- routes/web.php | 20 +- tests/CommandsTest.php | 3 +- tests/Entity/EntityTest.php | 11 +- tests/Entity/PageContentTest.php | 65 +- tests/Entity/PageDraftTest.php | 11 +- tests/Entity/PageRevisionTest.php | 8 +- tests/Entity/SortTest.php | 4 +- tests/Permissions/RestrictionsTest.php | 15 +- tests/SharedTestHelpers.php | 35 +- tests/Unit/PageRepoTest.php | 78 -- tests/Uploads/AttachmentTest.php | 4 +- tests/Uploads/ImageTest.php | 4 +- 72 files changed, 2705 insertions(+), 2751 deletions(-) create mode 100644 app/Entities/HasCoverImage.php create mode 100644 app/Entities/Managers/BookContents.php rename app/Entities/{EntityContextManager.php => Managers/EntityContext.php} (53%) create mode 100644 app/Entities/Managers/PageContent.php create mode 100644 app/Entities/Managers/PageEditActivity.php create mode 100644 app/Entities/Managers/TrashCan.php create mode 100644 app/Entities/Repos/BaseRepo.php create mode 100644 app/Entities/Repos/BookshelfRepo.php create mode 100644 app/Entities/Repos/ChapterRepo.php delete mode 100644 app/Entities/Repos/EntityRepo.php create mode 100644 app/Exceptions/MoveOperationException.php create mode 100644 app/Exceptions/SortOperationException.php create mode 100644 app/Http/Controllers/BookSortController.php create mode 100644 app/Http/Controllers/PageRevisionController.php delete mode 100644 tests/Unit/PageRepoTest.php diff --git a/app/Actions/ActivityService.php b/app/Actions/ActivityService.php index d092a35c2..f56f1ca57 100644 --- a/app/Actions/ActivityService.php +++ b/app/Actions/ActivityService.php @@ -1,6 +1,7 @@ views()->save($this->view->create([ + $entity->views()->save($this->view->newInstance([ 'user_id' => $user->id, 'views' => 1 ])); @@ -60,7 +61,7 @@ class ViewService * @param string $action - used for permission checking * @return Collection */ - public function getPopular(int $count = 10, int $page = 0, $filterModels = null, string $action = 'view') + public function getPopular(int $count = 10, int $page = 0, array $filterModels = null, string $action = 'view') { $skipCount = $count * $page; $query = $this->permissionService diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 9e1876c90..97cc1ca24 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -633,42 +633,40 @@ class PermissionService } /** - * Get the children of a book in an efficient single query, Filtered by the permission system. - * @param integer $book_id - * @param bool $filterDrafts - * @param bool $fetchPageContent - * @return QueryBuilder + * Limited the given entity query so that the query will only + * return items that the user has permission for the given ability. */ - public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) + public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder { - $entities = $this->entityProvider; - $pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent)) - ->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) { - $query->where('draft', '=', 0); - if (!$filterDrafts) { - $query->orWhere(function ($query) { - $query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id); - }); - } - }); - $chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id); - $query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U")) - ->mergeBindings($pageSelect)->mergeBindings($chapterSelect); - - // Add joint permission filter - $whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)') - ->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type') - ->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles()) - ->where(function ($query) { - $query->where('jp.has_permission', '=', 1)->orWhere(function ($query) { - $query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id); - }); - }); - $query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery); - - $query->orderBy('draft', 'desc')->orderBy('priority', 'asc'); $this->clean(); - return $query; + return $query->where(function (Builder $parentQuery) use ($ability) { + $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) { + $permissionQuery->whereIn('role_id', $this->getRoles()) + ->where('action', '=', $ability) + ->where(function (Builder $query) { + $query->where('has_permission', '=', true) + ->orWhere(function (Builder $query) { + $query->where('has_permission_own', '=', true) + ->where('created_by', '=', $this->currentUser()->id); + }); + }); + }); + }); + } + + /** + * Extend the given page query to ensure draft items are not visible + * unless created by the given user. + */ + public function enforceDraftVisiblityOnQuery(Builder $query): Builder + { + return $query->where(function (Builder $query) { + $query->where('draft', '=', false) + ->orWhere(function (Builder $query) { + $query->where('draft', '=', true) + ->where('created_by', '=', $this->currentUser()->id); + }); + }); } /** diff --git a/app/Auth/Role.php b/app/Auth/Role.php index 917d8aa26..712f5299b 100644 --- a/app/Auth/Role.php +++ b/app/Auth/Role.php @@ -75,7 +75,7 @@ class Role extends Model */ public static function getRole($roleName) { - return static::where('name', '=', $roleName)->first(); + return static::query()->where('name', '=', $roleName)->first(); } /** @@ -85,7 +85,7 @@ class Role extends Model */ public static function getSystemRole($roleName) { - return static::where('system_name', '=', $roleName)->first(); + return static::query()->where('system_name', '=', $roleName)->first(); } /** @@ -94,6 +94,15 @@ class Role extends Model */ public static function visible() { - return static::where('hidden', '=', false)->orderBy('name')->get(); + return static::query()->where('hidden', '=', false)->orderBy('name')->get(); + } + + /** + * Get the roles that can be restricted. + * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection + */ + public static function restrictable() + { + return static::query()->where('system_name', '!=', 'admin')->get(); } } diff --git a/app/Auth/User.php b/app/Auth/User.php index 7ad14d9f0..bce418a74 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -53,13 +53,24 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ protected $permissions; + /** + * This holds the default user when loaded. + * @var null|User + */ + protected static $defaultUser = null; + /** * Returns the default public user. * @return User */ public static function getDefault() { - return static::where('system_name', '=', 'public')->first(); + if (!is_null(static::$defaultUser)) { + return static::$defaultUser; + } + + static::$defaultUser = static::where('system_name', '=', 'public')->first(); + return static::$defaultUser; } /** diff --git a/app/Auth/UserRepo.php b/app/Auth/UserRepo.php index dec973f6c..a903e2c38 100644 --- a/app/Auth/UserRepo.php +++ b/app/Auth/UserRepo.php @@ -1,32 +1,31 @@ user = $user; $this->role = $role; - $this->entityRepo = $entityRepo; } /** @@ -81,7 +80,7 @@ class UserRepo * Creates a new user and attaches a role to them. * @param array $data * @param boolean $verifyEmail - * @return \BookStack\Auth\User + * @return User */ public function registerNew(array $data, $verifyEmail = false) { @@ -121,7 +120,7 @@ class UserRepo /** * Checks if the give user is the only admin. - * @param \BookStack\Auth\User $user + * @param User $user * @return bool */ public function isOnlyAdmin(User $user) @@ -175,7 +174,7 @@ class UserRepo * Create a new basic instance of user. * @param array $data * @param boolean $verifyEmail - * @return \BookStack\Auth\User + * @return User */ public function create(array $data, $verifyEmail = false) { @@ -189,7 +188,7 @@ class UserRepo /** * Remove the given user from storage, Delete all related content. - * @param \BookStack\Auth\User $user + * @param User $user * @throws Exception */ public function destroy(User $user) @@ -206,7 +205,7 @@ class UserRepo /** * Get the latest activity for a user. - * @param \BookStack\Auth\User $user + * @param User $user * @param int $count * @param int $page * @return array @@ -218,36 +217,35 @@ class UserRepo /** * Get the recently created content for this given user. - * @param \BookStack\Auth\User $user - * @param int $count - * @return mixed */ - public function getRecentlyCreated(User $user, $count = 20) + public function getRecentlyCreated(User $user, int $count = 20): array { - $createdByUserQuery = function (Builder $query) use ($user) { - $query->where('created_by', '=', $user->id); + $query = function (Builder $query) use ($user, $count) { + return $query->orderBy('created_at', 'desc') + ->where('created_by', '=', $user->id) + ->take($count) + ->get(); }; return [ - 'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, $createdByUserQuery), - 'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, $createdByUserQuery), - 'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, $createdByUserQuery), - 'shelves' => $this->entityRepo->getRecentlyCreated('bookshelf', $count, 0, $createdByUserQuery) + 'pages' => $query(Page::visible()->where('draft', '=', false)), + 'chapters' => $query(Chapter::visible()), + 'books' => $query(Book::visible()), + 'shelves' => $query(Bookshelf::visible()), ]; } /** * Get asset created counts for the give user. - * @param \BookStack\Auth\User $user - * @return array */ - public function getAssetCounts(User $user) + public function getAssetCounts(User $user): array { + $createdBy = ['created_by' => $user->id]; return [ - 'pages' => $this->entityRepo->getUserTotalCreated('page', $user), - 'chapters' => $this->entityRepo->getUserTotalCreated('chapter', $user), - 'books' => $this->entityRepo->getUserTotalCreated('book', $user), - 'shelves' => $this->entityRepo->getUserTotalCreated('bookshelf', $user), + 'pages' => Page::visible()->where($createdBy)->count(), + 'chapters' => Chapter::visible()->where($createdBy)->count(), + 'books' => Book::visible()->where($createdBy)->count(), + 'shelves' => Bookshelf::visible()->where($createdBy)->count(), ]; } @@ -260,16 +258,6 @@ class UserRepo return $this->role->newQuery()->orderBy('name', 'asc')->get(); } - /** - * Get all the roles which can be given restricted access to - * other entities in the system. - * @return mixed - */ - public function getRestrictableRoles() - { - return $this->role->where('system_name', '!=', 'admin')->get(); - } - /** * Get an avatar image for a user and set it as their avatar. * Returns early if avatars disabled or not set in config. @@ -288,7 +276,7 @@ class UserRepo $user->save(); return true; } catch (Exception $e) { - \Log::error('Failed to save user avatar image'); + Log::error('Failed to save user avatar image'); return false; } } diff --git a/app/Entities/Book.php b/app/Entities/Book.php index ce4c4b90e..4e54457b8 100644 --- a/app/Entities/Book.php +++ b/app/Entities/Book.php @@ -1,6 +1,11 @@ cover ? url($this->cover->getThumb($width, $height, false)) : $default; - } catch (\Exception $err) { + } catch (Exception $err) { $cover = $default; } return $cover; @@ -60,16 +56,23 @@ class Book extends Entity /** * Get the cover image of the book - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function cover() + public function cover(): BelongsTo { return $this->belongsTo(Image::class, 'image_id'); } + /** + * Get the type of the image model that is used when storing a cover image. + */ + public function coverImageTypeKey(): string + { + return 'cover_book'; + } + /** * Get all pages within this book. - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function pages() { @@ -78,7 +81,7 @@ class Book extends Entity /** * Get the direct child pages of this book. - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function directPages() { @@ -87,7 +90,7 @@ class Book extends Entity /** * Get all chapters within this book. - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function chapters() { @@ -96,13 +99,24 @@ class Book extends Entity /** * Get the shelves this book is contained within. - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ public function shelves() { return $this->belongsToMany(Bookshelf::class, 'bookshelves_books', 'book_id', 'bookshelf_id'); } + /** + * Get the direct child items within this book. + * @return Collection + */ + public function getDirectChildren(): Collection + { + $pages = $this->directPages()->visible()->get(); + $chapters = $this->chapters()->visible()->get(); + return $pages->contact($chapters)->sortBy('priority')->sortByDesc('draft'); + } + /** * Get an excerpt of this book's description to the specified length or less. * @param int $length @@ -113,13 +127,4 @@ class Book extends Entity $description = $this->description; return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description; } - - /** - * Return a generalised, common raw query that can be 'unioned' across entities. - * @return string - */ - public function entityRawQuery() - { - return "'BookStack\\\\Book' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at"; - } } diff --git a/app/Entities/BookChild.php b/app/Entities/BookChild.php index c76baf29a..6eac4375d 100644 --- a/app/Entities/BookChild.php +++ b/app/Entities/BookChild.php @@ -1,14 +1,31 @@ with('book') + ->whereHas('book', function (Builder $query) use ($bookSlug) { + $query->where('slug', '=', $bookSlug); + }) + ->where('slug', '=', $childSlug); + } + /** * Get the book this page sits in. * @return BelongsTo @@ -18,4 +35,26 @@ class BookChild extends Entity return $this->belongsTo(Book::class); } -} \ No newline at end of file + /** + * Change the book that this entity belongs to. + */ + public function changeBook(int $newBookId): Entity + { + $this->book_id = $newBookId; + $this->refreshSlug(); + $this->save(); + $this->refresh(); + + // Update related activity + $this->activity()->update(['book_id' => $newBookId]); + + // Update all child pages if a chapter + if ($this instanceof Chapter) { + foreach ($this->pages as $page) { + $page->changeBook($newBookId); + } + } + + return $this; + } +} diff --git a/app/Entities/Bookshelf.php b/app/Entities/Bookshelf.php index 7ad2415ed..62c7e2fe4 100644 --- a/app/Entities/Bookshelf.php +++ b/app/Entities/Bookshelf.php @@ -1,8 +1,10 @@ orderBy('order', 'asc'); } + /** + * Related books that are visible to the current user. + */ + public function visibleBooks(): BelongsToMany + { + return $this->books()->visible(); + } + /** * Get the url for this bookshelf. * @param string|bool $path @@ -68,13 +69,20 @@ class Bookshelf extends Entity /** * Get the cover image of the shelf - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function cover() + public function cover(): BelongsTo { return $this->belongsTo(Image::class, 'image_id'); } + /** + * Get the type of the image model that is used when storing a cover image. + */ + public function coverImageTypeKey(): string + { + return 'cover_shelf'; + } + /** * Get an excerpt of this book's description to the specified length or less. * @param int $length @@ -86,21 +94,12 @@ class Bookshelf extends Entity return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description; } - /** - * Return a generalised, common raw query that can be 'unioned' across entities. - * @return string - */ - public function entityRawQuery() - { - return "'BookStack\\\\BookShelf' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at"; - } - /** * Check if this shelf contains the given book. * @param Book $book * @return bool */ - public function contains(Book $book): bool + public function contains(Book $book): bool { return $this->books()->where('id', '=', $book->id)->count() > 0; } @@ -111,11 +110,11 @@ class Bookshelf extends Entity */ public function appendBook(Book $book) { - if ($this->contains($book)) { - return; - } + if ($this->contains($book)) { + return; + } - $maxOrder = $this->books()->max('order'); - $this->books()->attach($book->id, ['order' => $maxOrder + 1]); + $maxOrder = $this->books()->max('order'); + $this->books()->attach($book->id, ['order' => $maxOrder + 1]); } } diff --git a/app/Entities/BreadcrumbsViewComposer.php b/app/Entities/BreadcrumbsViewComposer.php index e46d54ec2..43d63d026 100644 --- a/app/Entities/BreadcrumbsViewComposer.php +++ b/app/Entities/BreadcrumbsViewComposer.php @@ -1,5 +1,6 @@ entityContextManager = $entityContextManager; } diff --git a/app/Entities/Chapter.php b/app/Entities/Chapter.php index d121432fa..848bc6448 100644 --- a/app/Entities/Chapter.php +++ b/app/Entities/Chapter.php @@ -1,22 +1,18 @@ $pages + * @package BookStack\Entities + */ class Chapter extends BookChild { public $searchFactor = 1.3; protected $fillable = ['name', 'description', 'priority', 'book_id']; - /** - * Get the morph class for this model. - * @return string - */ - public function getMorphClass() - { - return 'BookStack\\Chapter'; - } - /** * Get the pages that this chapter contains. * @param string $dir @@ -55,15 +51,6 @@ class Chapter extends BookChild return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description; } - /** - * Return a generalised, common raw query that can be 'unioned' across entities. - * @return string - */ - public function entityRawQuery() - { - return "'BookStack\\\\Chapter' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, '' as html, book_id, priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at"; - } - /** * Check if this chapter has any child pages. * @return bool @@ -72,4 +59,15 @@ class Chapter extends BookChild { return count($this->pages) > 0; } + + /** + * Get the visible pages in this chapter. + */ + public function getVisiblePages(): Collection + { + return $this->pages()->visible() + ->orderBy('draft', 'desc') + ->orderBy('priority', 'asc') + ->get(); + } } diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 4e54a9e27..5013c39cf 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -9,6 +9,8 @@ use BookStack\Auth\Permissions\JointPermission; use BookStack\Facades\Permissions; use BookStack\Ownable; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\MorphMany; /** @@ -24,6 +26,11 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; * @property int $created_by * @property int $updated_by * @property boolean $restricted + * @property Collection $tags + * @method static Entity|Builder visible() + * @method static Entity|Builder hasPermission(string $permission) + * @method static Builder withLastView() + * @method static Builder withViewCount() * * @package BookStack\Entities */ @@ -41,14 +48,45 @@ class Entity extends Ownable public $searchFactor = 1.0; /** - * Get the morph class for this model. - * Set here since, due to folder changes, the namespace used - * in the database no longer matches the class namespace. - * @return string + * Get the entities that are visible to the current user. */ - public function getMorphClass() + public function scopeVisible(Builder $query) { - return 'BookStack\\Entity'; + return $this->scopeHasPermission($query, 'view'); + } + + /** + * Scope the query to those entities that the current user has the given permission for. + */ + public function scopeHasPermission(Builder $query, string $permission) + { + return Permissions::restrictEntityQuery($query, $permission); + } + + /** + * Query scope to get the last view from the current user. + */ + public function scopeWithLastView(Builder $query) + { + $viewedAtQuery = View::query()->select('updated_at') + ->whereColumn('viewable_id', '=', $this->getTable() . '.id') + ->where('viewable_type', '=', $this->getMorphClass()) + ->where('user_id', '=', user()->id) + ->take(1); + + return $query->addSelect(['last_viewed_at' => $viewedAtQuery]); + } + + /** + * Query scope to get the total view count of the entities. + */ + public function scopeWithViewCount(Builder $query) + { + $viewCountQuery = View::query()->selectRaw('SUM(views) as view_count') + ->whereColumn('viewable_id', '=', $this->getTable() . '.id') + ->where('viewable_type', '=', $this->getMorphClass())->take(1); + + $query->addSelect(['view_count' => $viewCountQuery]); } /** @@ -88,7 +126,7 @@ class Entity extends Ownable /** * Gets the activity objects for this entity. - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return MorphMany */ public function activity() { @@ -106,7 +144,7 @@ class Entity extends Ownable /** * Get the Tag models that have been user assigned to this entity. - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return MorphMany */ public function tags() { @@ -126,7 +164,7 @@ class Entity extends Ownable /** * Get the related search terms. - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return MorphMany */ public function searchTerms() { @@ -155,7 +193,7 @@ class Entity extends Ownable /** * Get the entity jointPermissions this is connected to. - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return MorphMany */ public function jointPermissions() { @@ -182,14 +220,6 @@ class Entity extends Ownable return strtolower(static::getClassName()); } - /** - * Get the type of this entity. - */ - public function type(): string - { - return static::getType(); - } - /** * Get an instance of an entity of the given type. * @param $type @@ -242,15 +272,6 @@ class Entity extends Ownable return trim($text); } - /** - * Return a generalised, common raw query that can be 'unioned' across entities. - * @return string - */ - public function entityRawQuery() - { - return ''; - } - /** * Get the url of this entity * @param $path @@ -270,6 +291,15 @@ class Entity extends Ownable Permissions::buildJointPermissionsForEntity($this); } + /** + * Index the current entity for search + */ + public function indexForSearch() + { + $searchService = app()->make(SearchService::class); + $searchService->indexEntity($this); + } + /** * Generate and set a new URL slug for this model. */ diff --git a/app/Entities/EntityProvider.php b/app/Entities/EntityProvider.php index d0d4a7ad6..6bf923b31 100644 --- a/app/Entities/EntityProvider.php +++ b/app/Entities/EntityProvider.php @@ -39,11 +39,6 @@ class EntityProvider /** * EntityProvider constructor. - * @param Bookshelf $bookshelf - * @param Book $book - * @param Chapter $chapter - * @param Page $page - * @param PageRevision $pageRevision */ public function __construct( Bookshelf $bookshelf, @@ -62,9 +57,8 @@ class EntityProvider /** * Fetch all core entity types as an associated array * with their basic names as the keys. - * @return Entity[] */ - public function all() + public function all(): array { return [ 'bookshelf' => $this->bookshelf, @@ -76,10 +70,8 @@ class EntityProvider /** * Get an entity instance by it's basic name. - * @param string $type - * @return Entity */ - public function get(string $type) + public function get(string $type): Entity { $type = strtolower($type); return $this->all()[$type]; @@ -87,15 +79,9 @@ class EntityProvider /** * Get the morph classes, as an array, for a single or multiple types. - * @param string|array $types - * @return array */ - public function getMorphClasses($types) + public function getMorphClasses(array $types): array { - if (is_string($types)) { - $types = [$types]; - } - $morphClasses = []; foreach ($types as $type) { $model = $this->get($type); diff --git a/app/Entities/ExportService.php b/app/Entities/ExportService.php index 09635aa21..3ec867959 100644 --- a/app/Entities/ExportService.php +++ b/app/Entities/ExportService.php @@ -1,35 +1,34 @@ entityRepo = $entityRepo; $this->imageService = $imageService; } /** * Convert a page to a self-contained HTML file. * Includes required CSS & image content. Images are base64 encoded into the HTML. - * @param \BookStack\Entities\Page $page - * @return mixed|string - * @throws \Throwable + * @throws Throwable */ public function pageToContainedHtml(Page $page) { - $this->entityRepo->renderPage($page); + $page->html = (new PageContent($page))->render(); $pageHtml = view('pages/export', [ 'page' => $page ])->render(); @@ -38,15 +37,13 @@ class ExportService /** * Convert a chapter to a self-contained HTML file. - * @param \BookStack\Entities\Chapter $chapter - * @return mixed|string - * @throws \Throwable + * @throws Throwable */ public function chapterToContainedHtml(Chapter $chapter) { - $pages = $this->entityRepo->getChapterChildren($chapter); + $pages = $chapter->getVisiblePages(); $pages->each(function ($page) { - $page->html = $this->entityRepo->renderPage($page); + $page->html = (new PageContent($page))->render(); }); $html = view('chapters/export', [ 'chapter' => $chapter, @@ -57,13 +54,11 @@ class ExportService /** * Convert a book to a self-contained HTML file. - * @param Book $book - * @return mixed|string - * @throws \Throwable + * @throws Throwable */ public function bookToContainedHtml(Book $book) { - $bookTree = $this->entityRepo->getBookChildren($book, true, true); + $bookTree = (new BookContents($book))->getTree(false, true); $html = view('books/export', [ 'book' => $book, 'bookChildren' => $bookTree @@ -73,13 +68,11 @@ class ExportService /** * Convert a page to a PDF file. - * @param Page $page - * @return mixed|string - * @throws \Throwable + * @throws Throwable */ public function pageToPdf(Page $page) { - $this->entityRepo->renderPage($page); + $page->html = (new PageContent($page))->render(); $html = view('pages/pdf', [ 'page' => $page ])->render(); @@ -88,32 +81,30 @@ class ExportService /** * Convert a chapter to a PDF file. - * @param \BookStack\Entities\Chapter $chapter - * @return mixed|string - * @throws \Throwable + * @throws Throwable */ public function chapterToPdf(Chapter $chapter) { - $pages = $this->entityRepo->getChapterChildren($chapter); + $pages = $chapter->getVisiblePages(); $pages->each(function ($page) { - $page->html = $this->entityRepo->renderPage($page); + $page->html = (new PageContent($page))->render(); }); + $html = view('chapters/export', [ 'chapter' => $chapter, 'pages' => $pages ])->render(); + return $this->htmlToPdf($html); } /** - * Convert a book to a PDF file - * @param \BookStack\Entities\Book $book - * @return string - * @throws \Throwable + * Convert a book to a PDF file. + * @throws Throwable */ public function bookToPdf(Book $book) { - $bookTree = $this->entityRepo->getBookChildren($book, true, true); + $bookTree = (new BookContents($book))->getTree(false, true); $html = view('books/export', [ 'book' => $book, 'bookChildren' => $bookTree @@ -122,31 +113,27 @@ class ExportService } /** - * Convert normal webpage HTML to a PDF. - * @param $html - * @return string - * @throws \Exception + * Convert normal web-page HTML to a PDF. + * @throws Exception */ - protected function htmlToPdf($html) + protected function htmlToPdf(string $html): string { $containedHtml = $this->containHtml($html); $useWKHTML = config('snappy.pdf.binary') !== false; if ($useWKHTML) { - $pdf = \SnappyPDF::loadHTML($containedHtml); + $pdf = SnappyPDF::loadHTML($containedHtml); $pdf->setOption('print-media-type', true); } else { - $pdf = \DomPDF::loadHTML($containedHtml); + $pdf = DomPDF::loadHTML($containedHtml); } return $pdf->output(); } /** * Bundle of the contents of a html file to be self-contained. - * @param $htmlContent - * @return mixed|string - * @throws \Exception + * @throws Exception */ - protected function containHtml($htmlContent) + protected function containHtml(string $htmlContent): string { $imageTagsOutput = []; preg_match_all("/\/i", $htmlContent, $imageTagsOutput); @@ -188,12 +175,10 @@ class ExportService /** * Converts the page contents into simple plain text. * This method filters any bad looking content to provide a nice final output. - * @param Page $page - * @return mixed */ - public function pageToPlainText(Page $page) + public function pageToPlainText(Page $page): string { - $html = $this->entityRepo->renderPage($page); + $html = (new PageContent($page))->render(); $text = strip_tags($html); // Replace multiple spaces with single spaces $text = preg_replace('/\ {2,}/', ' ', $text); @@ -207,10 +192,8 @@ class ExportService /** * Convert a chapter into a plain text string. - * @param \BookStack\Entities\Chapter $chapter - * @return string */ - public function chapterToPlainText(Chapter $chapter) + public function chapterToPlainText(Chapter $chapter): string { $text = $chapter->name . "\n\n"; $text .= $chapter->description . "\n\n"; @@ -222,12 +205,10 @@ class ExportService /** * Convert a book into a plain text string. - * @param Book $book - * @return string */ - public function bookToPlainText(Book $book) + public function bookToPlainText(Book $book): string { - $bookTree = $this->entityRepo->getBookChildren($book, true, true); + $bookTree = (new BookContents($book))->getTree(false, true); $text = $book->name . "\n\n"; foreach ($bookTree as $bookChild) { if ($bookChild->isA('chapter')) { diff --git a/app/Entities/HasCoverImage.php b/app/Entities/HasCoverImage.php new file mode 100644 index 000000000..31277f4b6 --- /dev/null +++ b/app/Entities/HasCoverImage.php @@ -0,0 +1,20 @@ +book = $book; + } + + /** + * Get the current priority of the last item + * at the top-level of the book. + */ + public function getLastPriority(): int + { + $maxPage = Page::visible()->where('book_id', '=', $this->book->id) + ->where('draft', '=', false) + ->where('chapter_id', '=', 0)->max('priority'); + $maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id) + ->max('priority'); + return max($maxChapter, $maxPage, 1); + } + + /** + * Get the contents as a sorted collection tree. + * TODO - Support $renderPages option + */ + public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection + { + $pages = $this->getPages($showDrafts); + $chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get(); + $all = collect()->concat($pages)->concat($chapters); + $chapterMap = $chapters->keyBy('id'); + $lonePages = collect(); + + $pages->groupBy('chapter_id')->each(function ($pages, $chapter_id) use ($chapterMap, &$lonePages) { + $chapter = $chapterMap->get($chapter_id); + if ($chapter) { + $chapter->setAttribute('pages', collect($pages)->sortBy($this->bookChildSortFunc())); + } else { + $lonePages = $lonePages->concat($pages); + } + }); + + $all->each(function (Entity $entity) { + $entity->setRelation('book', $this->book); + }); + + return collect($chapters)->concat($lonePages)->sortBy($this->bookChildSortFunc()); + } + + /** + * Function for providing a sorting score for an entity in relation to the + * other items within the book. + */ + protected function bookChildSortFunc(): callable + { + return function (Entity $entity) { + if (isset($entity['draft']) && $entity['draft']) { + return -100; + } + return $entity['priority'] ?? 0; + }; + } + + /** + * Get the visible pages within this book. + */ + protected function getPages(bool $showDrafts = false): Collection + { + $query = Page::visible()->where('book_id', '=', $this->book->id); + + if (!$showDrafts) { + $query->where('draft', '=', false); + } + + return $query->get(); + } + + /** + * Sort the books content using the given map. + * The map is a single-dimension collection of objects in the following format: + * { + * +"id": "294" (ID of item) + * +"sort": 1 (Sort order index) + * +"parentChapter": false (ID of parent chapter, as string, or false) + * +"type": "page" (Entity type of item) + * +"book": "1" (Id of book to place item in) + * } + * + * Returns a list of books that were involved in the operation. + * @throws SortOperationException + */ + public function sortUsingMap(Collection $sortMap): Collection + { + // Load models into map + $this->loadModelsIntoSortMap($sortMap); + $booksInvolved = $this->getBooksInvolvedInSort($sortMap); + + // Perform the sort + $sortMap->each(function ($mapItem) { + $this->applySortUpdates($mapItem); + }); + + // Update permissions and activity. + $booksInvolved->each(function (Book $book) { + $book->rebuildPermissions(); + }); + + return $booksInvolved; + } + + /** + * Using the given sort map item, detect changes for the related model + * and update it if required. + */ + protected function applySortUpdates(\stdClass $sortMapItem) + { + /** @var BookChild $model */ + $model = $sortMapItem->model; + + $priorityChanged = intval($model->priority) !== intval($sortMapItem->sort); + $bookChanged = intval($model->book_id) !== intval($sortMapItem->book); + $chapterChanged = ($sortMapItem->type === 'page') && intval($model->chapter_id) !== $sortMapItem->parentChapter; + + if ($bookChanged) { + $model->changeBook($sortMapItem->book); + } + + if ($chapterChanged) { + $model->chapter_id = intval($sortMapItem->parentChapter); + $model->save(); + } + + if ($priorityChanged) { + $model->priority = intval($sortMapItem->sort); + $model->save(); + } + } + + /** + * Load models from the database into the given sort map. + */ + protected function loadModelsIntoSortMap(Collection $sortMap): void + { + $keyMap = $sortMap->keyBy(function (\stdClass $sortMapItem) { + return $sortMapItem->type . ':' . $sortMapItem->id; + }); + $pageIds = $sortMap->where('type', '=', 'page')->pluck('id'); + $chapterIds = $sortMap->where('type', '=', 'chapter')->pluck('id'); + + $pages = Page::visible()->whereIn('id', $pageIds)->get(); + $chapters = Chapter::visible()->whereIn('id', $chapterIds)->get(); + + foreach ($pages as $page) { + $sortItem = $keyMap->get('page:' . $page->id); + $sortItem->model = $page; + } + + foreach ($chapters as $chapter) { + $sortItem = $keyMap->get('chapter:' . $chapter->id); + $sortItem->model = $chapter; + } + } + + /** + * Get the books involved in a sort. + * The given sort map should have its models loaded first. + * @throws SortOperationException + */ + protected function getBooksInvolvedInSort(Collection $sortMap): Collection + { + $bookIdsInvolved = collect([$this->book->id]); + $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('book')); + $bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('model.book_id')); + $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); + + $books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get(); + + if (count($books) !== count($bookIdsInvolved)) { + throw new SortOperationException("Could not find all books requested in sort operation"); + } + + return $books; + } +} diff --git a/app/Entities/EntityContextManager.php b/app/Entities/Managers/EntityContext.php similarity index 53% rename from app/Entities/EntityContextManager.php rename to app/Entities/Managers/EntityContext.php index 20be0de2b..551cd1a10 100644 --- a/app/Entities/EntityContextManager.php +++ b/app/Entities/Managers/EntityContext.php @@ -1,44 +1,38 @@ -session = $session; - $this->entityRepo = $entityRepo; } /** * Get the current bookshelf context for the given book. - * @param Book $book - * @return Bookshelf|null */ - public function getContextualShelfForBook(Book $book) + public function getContextualShelfForBook(Book $book): ?Bookshelf { $contextBookshelfId = $this->session->get($this->KEY_SHELF_CONTEXT_ID, null); - if (is_int($contextBookshelfId)) { - /** @var Bookshelf $shelf */ - $shelf = $this->entityRepo->getById('bookshelf', $contextBookshelfId); - - if ($shelf && $shelf->contains($book)) { - return $shelf; - } + if (!is_int($contextBookshelfId)) { + return null; } - return null; + + $shelf = Bookshelf::visible()->find($contextBookshelfId); + $shelfContainsBook = $shelf && $shelf->contains($book); + + return $shelfContainsBook ? $shelf : null; } /** diff --git a/app/Entities/Managers/PageContent.php b/app/Entities/Managers/PageContent.php new file mode 100644 index 000000000..36bc2445c --- /dev/null +++ b/app/Entities/Managers/PageContent.php @@ -0,0 +1,304 @@ +page = $page; + } + + /** + * Update the content of the page with new provided HTML. + */ + public function setNewHTML(string $html) + { + $this->page->html = $this->formatHtml($html); + $this->page->text = $this->toPlainText(); + } + + /** + * Formats a page's html to be tagged correctly within the system. + */ + protected function formatHtml(string $htmlText): string + { + if ($htmlText == '') { + return $htmlText; + } + + libxml_use_internal_errors(true); + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8')); + + $container = $doc->documentElement; + $body = $container->childNodes->item(0); + $childNodes = $body->childNodes; + + // Set ids on top-level nodes + $idMap = []; + foreach ($childNodes as $index => $childNode) { + $this->setUniqueId($childNode, $idMap); + } + + // Ensure no duplicate ids within child items + $xPath = new DOMXPath($doc); + $idElems = $xPath->query('//body//*//*[@id]'); + foreach ($idElems as $domElem) { + $this->setUniqueId($domElem, $idMap); + } + + // Generate inner html as a string + $html = ''; + foreach ($childNodes as $childNode) { + $html .= $doc->saveHTML($childNode); + } + + return $html; + } + + /** + * Set a unique id on the given DOMElement. + * A map for existing ID's should be passed in to check for current existence. + * @param DOMElement $element + * @param array $idMap + */ + protected function setUniqueId($element, array &$idMap) + { + if (get_class($element) !== 'DOMElement') { + return; + } + + // Overwrite id if not a BookStack custom id + $existingId = $element->getAttribute('id'); + if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) { + $idMap[$existingId] = true; + return; + } + + // Create an unique id for the element + // Uses the content as a basis to ensure output is the same every time + // the same content is passed through. + $contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20); + $newId = urlencode($contentId); + $loopIndex = 0; + + while (isset($idMap[$newId])) { + $newId = urlencode($contentId . '-' . $loopIndex); + $loopIndex++; + } + + $element->setAttribute('id', $newId); + $idMap[$newId] = true; + } + + /** + * Get a plain-text visualisation of this page. + */ + protected function toPlainText(): string + { + $html = $this->render(true); + return strip_tags($html); + } + + /** + * Render the page for viewing + */ + public function render(bool $blankIncludes = false) : string + { + $content = $this->page->html; + + if (!config('app.allow_content_scripts')) { + $content = $this->escapeScripts($content); + } + + if ($blankIncludes) { + $content = $this->blankPageIncludes($content); + } else { + $content = $this->parsePageIncludes($content); + } + + return $content; + } + + /** + * Parse the headers on the page to get a navigation menu + */ + public function getNavigation(string $htmlContent): array + { + if (empty($htmlContent)) { + return []; + } + + libxml_use_internal_errors(true); + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding($htmlContent, 'HTML-ENTITIES', 'UTF-8')); + $xPath = new DOMXPath($doc); + $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); + + return $headers ? $this->headerNodesToLevelList($headers) : []; + } + + /** + * Convert a DOMNodeList into an array of readable header attributes + * with levels normalised to the lower header level. + */ + protected function headerNodesToLevelList(DOMNodeList $nodeList): array + { + $tree = collect($nodeList)->map(function ($header) { + $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue)); + $text = mb_substr($text, 0, 100); + + return [ + 'nodeName' => strtolower($header->nodeName), + 'level' => intval(str_replace('h', '', $header->nodeName)), + 'link' => '#' . $header->getAttribute('id'), + 'text' => $text, + ]; + })->filter(function ($header) { + return mb_strlen($header['text']) > 0; + }); + + // Shift headers if only smaller headers have been used + $levelChange = ($tree->pluck('level')->min() - 1); + $tree = $tree->map(function ($header) use ($levelChange) { + $header['level'] -= ($levelChange); + return $header; + }); + + return $tree->toArray(); + } + + /** + * Remove any page include tags within the given HTML. + */ + protected function blankPageIncludes(string $html) : string + { + return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html); + } + + /** + * Parse any include tags "{{@#section}}" to be part of the page. + */ + protected function parsePageIncludes(string $html) : string + { + $matches = []; + preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches); + + foreach ($matches[1] as $index => $includeId) { + $fullMatch = $matches[0][$index]; + $splitInclude = explode('#', $includeId, 2); + + // Get page id from reference + $pageId = intval($splitInclude[0]); + if (is_nan($pageId)) { + continue; + } + + // Find page and skip this if page not found + $matchedPage = Page::visible()->find($pageId); + if ($matchedPage === null) { + $html = str_replace($fullMatch, '', $html); + continue; + } + + // If we only have page id, just insert all page html and continue. + if (count($splitInclude) === 1) { + $html = str_replace($fullMatch, $matchedPage->html, $html); + continue; + } + + // Create and load HTML into a document + $innerContent = $this->fetchSectionOfPage($matchedPage, $splitInclude[1]); + $html = str_replace($fullMatch, trim($innerContent), $html); + } + + return $html; + } + + + /** + * Fetch the content from a specific section of the given page. + */ + protected function fetchSectionOfPage(Page $page, string $sectionId): string + { + $topLevelTags = ['table', 'ul', 'ol']; + $doc = new DOMDocument(); + libxml_use_internal_errors(true); + $doc->loadHTML(mb_convert_encoding(''.$page->html.'', 'HTML-ENTITIES', 'UTF-8')); + + // Search included content for the id given and blank out if not exists. + $matchingElem = $doc->getElementById($sectionId); + if ($matchingElem === null) { + return ''; + } + + // Otherwise replace the content with the found content + // Checks if the top-level wrapper should be included by matching on tag types + $innerContent = ''; + $isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags); + if ($isTopLevel) { + $innerContent .= $doc->saveHTML($matchingElem); + } else { + foreach ($matchingElem->childNodes as $childNode) { + $innerContent .= $doc->saveHTML($childNode); + } + } + libxml_clear_errors(); + + return $innerContent; + } + + /** + * Escape script tags within HTML content. + */ + protected function escapeScripts(string $html) : string + { + if (empty($html)) { + return $html; + } + + libxml_use_internal_errors(true); + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $xPath = new DOMXPath($doc); + + // Remove standard script tags + $scriptElems = $xPath->query('//script'); + foreach ($scriptElems as $scriptElem) { + $scriptElem->parentNode->removeChild($scriptElem); + } + + // Remove data or JavaScript iFrames + $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]'); + foreach ($badIframes as $badIframe) { + $badIframe->parentNode->removeChild($badIframe); + } + + // Remove 'on*' attributes + $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]'); + foreach ($onAttributes as $attr) { + /** @var \DOMAttr $attr*/ + $attrName = $attr->nodeName; + $attr->parentNode->removeAttribute($attrName); + } + + $html = ''; + $topElems = $doc->documentElement->childNodes->item(0)->childNodes; + foreach ($topElems as $child) { + $html .= $doc->saveHTML($child); + } + + return $html; + } +} diff --git a/app/Entities/Managers/PageEditActivity.php b/app/Entities/Managers/PageEditActivity.php new file mode 100644 index 000000000..cebbf8720 --- /dev/null +++ b/app/Entities/Managers/PageEditActivity.php @@ -0,0 +1,74 @@ +page = $page; + } + + /** + * Check if there's active editing being performed on this page. + * @return bool + */ + public function hasActiveEditing(): bool + { + return $this->activePageEditingQuery(60)->count() > 0; + } + + /** + * Get a notification message concerning the editing activity on the page. + */ + public function activeEditingMessage(): string + { + $pageDraftEdits = $this->activePageEditingQuery(60)->get(); + $count = $pageDraftEdits->count(); + + $userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); + $timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]); + return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]); + } + + /** + * Get the message to show when the user will be editing one of their drafts. + * @param PageRevision $draft + * @return string + */ + public function getEditingActiveDraftMessage(PageRevision $draft): string + { + $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]); + if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) { + return $message; + } + return $message . "\n" . trans('entities.pages_draft_edited_notification'); + } + + /** + * A query to check for active update drafts on a particular page + * within the last given many minutes. + */ + protected function activePageEditingQuery(int $withinMinutes): Builder + { + $checkTime = Carbon::now()->subMinutes($withinMinutes); + $query = PageRevision::query() + ->where('type', '=', 'update_draft') + ->where('page_id', '=', $this->page->id) + ->where('updated_at', '>', $this->page->updated_at) + ->where('created_by', '!=', user()->id) + ->where('updated_at', '>=', $checkTime) + ->with('createdBy'); + + return $query; + } +} diff --git a/app/Entities/Managers/TrashCan.php b/app/Entities/Managers/TrashCan.php new file mode 100644 index 000000000..1a32294fc --- /dev/null +++ b/app/Entities/Managers/TrashCan.php @@ -0,0 +1,109 @@ +destroyCommonRelations($shelf); + $shelf->delete(); + } + + /** + * Remove a book from the system. + * @throws NotifyException + * @throws BindingResolutionException + */ + public function destroyBook(Book $book) + { + foreach ($book->pages as $page) { + $this->destroyPage($page); + } + + foreach ($book->chapters as $chapter) { + $this->destroyChapter($chapter); + } + + $this->destroyCommonRelations($book); + $book->delete(); + } + + /** + * Remove a page from the system. + * @throws NotifyException + */ + public function destroyPage(Page $page) + { + // Check if set as custom homepage & remove setting if not used or throw error if active + $customHome = setting('app-homepage', '0:'); + if (intval($page->id) === intval(explode(':', $customHome)[0])) { + if (setting('app-homepage-type') === 'page') { + throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl()); + } + setting()->remove('app-homepage'); + } + + $this->destroyCommonRelations($page); + + // Delete Attached Files + $attachmentService = app(AttachmentService::class); + foreach ($page->attachments as $attachment) { + $attachmentService->deleteFile($attachment); + } + + $page->delete(); + } + + /** + * Remove a chapter from the system. + * @throws Exception + */ + public function destroyChapter(Chapter $chapter) + { + if (count($chapter->pages) > 0) { + foreach ($chapter->pages as $page) { + $page->chapter_id = 0; + $page->save(); + } + } + + $this->destroyCommonRelations($chapter); + $chapter->delete(); + } + + /** + * Update entity relations to remove or update outstanding connections. + */ + protected function destroyCommonRelations(Entity $entity) + { + Activity::removeEntity($entity); + $entity->views()->delete(); + $entity->permissions()->delete(); + $entity->tags()->delete(); + $entity->comments()->delete(); + $entity->jointPermissions()->delete(); + $entity->searchTerms()->delete(); + + if ($entity instanceof HasCoverImage && $entity->cover) { + $imageService = app()->make(ImageService::class); + $imageService->destroy($entity->cover); + } + } +} diff --git a/app/Entities/Page.php b/app/Entities/Page.php index 752b3c9dd..76dc628fb 100644 --- a/app/Entities/Page.php +++ b/app/Entities/Page.php @@ -1,7 +1,24 @@ chapter_id ? $this->chapter() : $this->book(); + return $this->chapter_id ? $this->chapter : $this->book; } /** * Get the chapter that this page is in, If applicable. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function chapter() { @@ -63,12 +79,12 @@ class Page extends BookChild */ public function revisions() { - return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc'); + return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc')->orderBy('id', 'desc'); } /** * Get the attachments assigned to this page. - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function attachments() { @@ -86,27 +102,17 @@ class Page extends BookChild $midText = $this->draft ? '/draft/' : '/page/'; $idComponent = $this->draft ? $this->id : urlencode($this->slug); + $url = '/books/' . urlencode($bookSlug) . $midText . $idComponent; if ($path !== false) { - return url('/books/' . urlencode($bookSlug) . $midText . $idComponent . '/' . trim($path, '/')); + $url .= '/' . trim($path, '/'); } - return url('/books/' . urlencode($bookSlug) . $midText . $idComponent); - } - - /** - * Return a generalised, common raw query that can be 'unioned' across entities. - * @param bool $withContent - * @return string - */ - public function entityRawQuery($withContent = false) - { - $htmlQuery = $withContent ? 'html' : "'' as html"; - return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at"; + return url($url); } /** * Get the current revision for the page if existing - * @return \BookStack\Entities\PageRevision|null + * @return PageRevision|null */ public function getCurrentRevision() { diff --git a/app/Entities/PageRevision.php b/app/Entities/PageRevision.php index d30147bfc..13dc713ba 100644 --- a/app/Entities/PageRevision.php +++ b/app/Entities/PageRevision.php @@ -2,7 +2,21 @@ use BookStack\Auth\User; use BookStack\Model; +use Carbon\Carbon; +/** + * Class PageRevision + * @property int $page_id + * @property string $slug + * @property string $book_slug + * @property int $created_by + * @property Carbon $created_at + * @property string $type + * @property string $summary + * @property string $markdown + * @property string $html + * @property int $revision_number + */ class PageRevision extends Model { protected $fillable = ['name', 'html', 'text', 'markdown', 'summary']; @@ -41,13 +55,18 @@ class PageRevision extends Model /** * Get the previous revision for the same page if existing - * @return \BookStack\PageRevision|null + * @return \BookStack\Entities\PageRevision|null */ public function getPrevious() { - if ($id = static::where('page_id', '=', $this->page_id)->where('id', '<', $this->id)->max('id')) { - return static::find($id); + $id = static::newQuery()->where('page_id', '=', $this->page_id) + ->where('id', '<', $this->id) + ->max('id'); + + if ($id) { + return static::query()->find($id); } + return null; } diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php new file mode 100644 index 000000000..78ce505b9 --- /dev/null +++ b/app/Entities/Repos/BaseRepo.php @@ -0,0 +1,118 @@ +tagRepo = $tagRepo; + $this->imageRepo = $imageRepo; + } + + /** + * Create a new entity in the system + */ + public function create(Entity $entity, array $input) + { + $entity->fill($input); + $entity->forceFill([ + 'created_by' => user()->id, + 'updated_by' => user()->id, + ]); + $entity->refreshSlug(); + $entity->save(); + + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($entity, $input['tags']); + } + + $entity->rebuildPermissions(); + $entity->indexForSearch(); + } + + /** + * Update the given entity. + */ + public function update(Entity $entity, array $input) + { + $entity->fill($input); + $entity->updated_by = user()->id; + + if ($entity->isDirty('name')) { + $entity->refreshSlug(); + } + + $entity->save(); + + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($entity, $input['tags']); + } + + $entity->rebuildPermissions(); + $entity->indexForSearch(); + } + + /** + * Update the given items' cover image, or clear it. + * @throws ImageUploadException + * @throws \Exception + */ + public function updateCoverImage(HasCoverImage $entity, UploadedFile $coverImage = null, bool $removeImage = false) + { + if ($coverImage) { + $this->imageRepo->destroyImage($entity->cover); + $image = $this->imageRepo->saveNew($coverImage, 'cover_book', $entity->id, 512, 512, true); + $entity->cover()->associate($image); + } + + if ($removeImage) { + $this->imageRepo->destroyImage($entity->cover); + $entity->image_id = 0; + $entity->save(); + } + } + + /** + * Update the permissions of an entity. + */ + public function updatePermissions(Entity $entity, bool $restricted, Collection $permissions = null) + { + $entity->restricted = $restricted; + $entity->permissions()->delete(); + + if (!is_null($permissions)) { + $entityPermissionData = $permissions->flatMap(function ($restrictions, $roleId) { + return collect($restrictions)->keys()->map(function ($action) use ($roleId) { + return [ + 'role_id' => $roleId, + 'action' => strtolower($action), + ] ; + }); + }); + + $entity->permissions()->createMany($entityPermissionData); + } + + $entity->save(); + $entity->rebuildPermissions(); + } +} diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 91bc9a1b4..7fcc80fac 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -1,46 +1,134 @@ -baseRepo = $baseRepo; + $this->tagRepo = $tagRepo; + $this->imageRepo = $imageRepo; + } + + /** + * Get all books in a paginated format. + */ + public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator + { + return Book::visible()->orderBy($sort, $order)->paginate($count); + } + + /** + * Get the books that were most recently viewed by this user. + */ + public function getRecentlyViewed(int $count = 20): Collection + { + return Book::visible()->withLastView() + ->having('last_viewed_at', '>', 0) + ->orderBy('last_viewed_at', 'desc') + ->take($count)->get(); + } + + /** + * Get the most popular books in the system. + */ + public function getPopular(int $count = 20): Collection + { + return Book::visible()->withViewCount() + ->having('view_count', '>', 0) + ->orderBy('view_count', 'desc') + ->take($count)->get(); + } + + /** + * Get the most recently created books from the system. + */ + public function getRecentlyCreated(int $count = 20): Collection + { + return Book::visible()->orderBy('created_at', 'desc') + ->take($count)->get(); + } + + /** + * Get a book by its slug. */ public function getBySlug(string $slug): Book { - /** @var Book $book */ - $book = $this->getEntityBySlug('book', $slug); + $book = Book::visible()->where('slug', '=', $slug)->first(); + + if ($book === null) { + throw new NotFoundException(trans('errors.book_not_found')); + } + return $book; } /** - * Destroy the provided book and all its child entities. - * @param Book $book - * @throws NotifyException - * @throws \Throwable + * Create a new book in the system */ - public function destroyBook(Book $book) + public function create(array $input): Book { - foreach ($book->pages as $page) { - $this->destroyPage($page); - } - - foreach ($book->chapters as $chapter) { - $this->destroyChapter($chapter); - } - - $this->destroyEntityCommonRelations($book); - $book->delete(); + $book = new Book(); + $this->baseRepo->create($book, $input); + return $book; } -} \ No newline at end of file + /** + * Update the given book. + */ + public function update(Book $book, array $input): Book + { + $this->baseRepo->update($book, $input); + return $book; + } + + /** + * Update the given book's cover image, or clear it. + * @throws ImageUploadException + * @throws Exception + */ + public function updateCoverImage(Book $book, UploadedFile $coverImage = null, bool $removeImage = false) + { + $this->baseRepo->updateCoverImage($book, $coverImage, $removeImage); + } + + /** + * Update the permissions of a book. + */ + public function updatePermissions(Book $book, bool $restricted, Collection $permissions = null) + { + $this->baseRepo->updatePermissions($book, $restricted, $permissions); + } + + /** + * Remove a book from the system. + * @throws NotifyException + * @throws BindingResolutionException + */ + public function destroy(Book $book) + { + $trashCan = new TrashCan(); + $trashCan->destroyBook($book); + } +} diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php new file mode 100644 index 000000000..ab4a51805 --- /dev/null +++ b/app/Entities/Repos/BookshelfRepo.php @@ -0,0 +1,173 @@ +baseRepo = $baseRepo; + } + + /** + * Get all bookshelves in a paginated format. + */ + public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator + { + return Bookshelf::visible()->with('visibleBooks') + ->orderBy($sort, $order)->paginate($count); + } + + /** + * Get the bookshelves that were most recently viewed by this user. + */ + public function getRecentlyViewed(int $count = 20): Collection + { + return Bookshelf::visible()->withLastView() + ->having('last_viewed_at', '>', 0) + ->orderBy('last_viewed_at', 'desc') + ->take($count)->get(); + } + + /** + * Get the most popular bookshelves in the system. + */ + public function getPopular(int $count = 20): Collection + { + return Bookshelf::visible()->withViewCount() + ->having('view_count', '>', 0) + ->orderBy('view_count', 'desc') + ->take($count)->get(); + } + + /** + * Get the most recently created bookshelves from the system. + */ + public function getRecentlyCreated(int $count = 20): Collection + { + return Bookshelf::visible()->orderBy('created_at', 'desc') + ->take($count)->get(); + } + + /** + * Get a shelf by its slug. + */ + public function getBySlug(string $slug): Bookshelf + { + $shelf = Bookshelf::visible()->where('slug', '=', $slug)->first(); + + if ($shelf === null) { + throw new NotFoundException(trans('errors.bookshelf_not_found')); + } + + return $shelf; + } + + /** + * Create a new shelf in the system. + */ + public function create(array $input, array $bookIds): Bookshelf + { + $shelf = new Bookshelf(); + $this->baseRepo->create($shelf, $input); + $this->updateBooks($shelf, $bookIds); + return $shelf; + } + + /** + * Create a new shelf in the system. + */ + public function update(Bookshelf $shelf, array $input, array $bookIds): Bookshelf + { + $this->baseRepo->update($shelf, $input); + $this->updateBooks($shelf, $bookIds); + return $shelf; + } + + /** + * Update which books are assigned to this shelf by + * syncing the given book ids. + * Function ensures the books are visible to the current user and existing. + */ + protected function updateBooks(Bookshelf $shelf, array $bookIds) + { + $numericIDs = collect($bookIds)->map(function ($id) { + return intval($id); + }); + + $syncData = Book::visible() + ->whereIn('id', $bookIds) + ->get(['id'])->pluck('id')->mapWithKeys(function ($bookId) use ($numericIDs) { + return [$bookId => ['order' => $numericIDs->search($bookId)]]; + }); + + $shelf->books()->sync($syncData); + } + + /** + * Update the given shelf cover image, or clear it. + * @throws ImageUploadException + * @throws Exception + */ + public function updateCoverImage(Bookshelf $shelf, UploadedFile $coverImage = null, bool $removeImage = false) + { + $this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage); + } + + /** + * Update the permissions of a bookshelf. + */ + public function updatePermissions(Bookshelf $shelf, bool $restricted, Collection $permissions = null) + { + $this->baseRepo->updatePermissions($shelf, $restricted, $permissions); + } + + /** + * Copy down the permissions of the given shelf to all child books. + */ + public function copyDownPermissions(Bookshelf $shelf): int + { + $shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray(); + $shelfBooks = $shelf->books()->get(); + $updatedBookCount = 0; + + /** @var Book $book */ + foreach ($shelfBooks as $book) { + if (!userCan('restrictions-manage', $book)) { + continue; + } + $book->permissions()->delete(); + $book->restricted = $shelf->restricted; + $book->permissions()->createMany($shelfPermissions); + $book->save(); + $book->rebuildPermissions(); + $updatedBookCount++; + } + + return $updatedBookCount; + } + + /** + * Remove a bookshelf from the system. + * @throws Exception + */ + public function destroy(Bookshelf $shelf) + { + $trashCan = new TrashCan(); + $trashCan->destroyShelf($shelf); + } +} diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php new file mode 100644 index 000000000..c6f3a2d2f --- /dev/null +++ b/app/Entities/Repos/ChapterRepo.php @@ -0,0 +1,108 @@ +baseRepo = $baseRepo; + } + + /** + * Get a chapter via the slug. + * @throws NotFoundException + */ + public function getBySlug(string $bookSlug, string $chapterSlug): Chapter + { + $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->first(); + + if ($chapter === null) { + throw new NotFoundException(trans('errors.chapter_not_found')); + } + + return $chapter; + } + + /** + * Create a new chapter in the system. + */ + public function create(array $input, Book $parentBook): Chapter + { + $chapter = new Chapter(); + $chapter->book_id = $parentBook->id; + $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; + $this->baseRepo->create($chapter, $input); + return $chapter; + } + + /** + * Update the given chapter. + */ + public function update(Chapter $chapter, array $input): Chapter + { + $this->baseRepo->update($chapter, $input); + return $chapter; + } + + /** + * Update the permissions of a chapter. + */ + public function updatePermissions(Chapter $chapter, bool $restricted, Collection $permissions = null) + { + $this->baseRepo->updatePermissions($chapter, $restricted, $permissions); + } + + /** + * Remove a chapter from the system. + * @throws Exception + */ + public function destroy(Chapter $chapter) + { + $trashCan = new TrashCan(); + $trashCan->destroyChapter($chapter); + } + + /** + * Move the given chapter into a new parent book. + * The $parentIdentifier must be a string of the following format: + * 'book:' (book:5) + * @throws MoveOperationException + */ + public function move(Chapter $chapter, string $parentIdentifier): Book + { + $stringExploded = explode(':', $parentIdentifier); + $entityType = $stringExploded[0]; + $entityId = intval($stringExploded[1]); + + if ($entityType !== 'book') { + throw new MoveOperationException('Chapters can only be moved into books'); + } + + $parent = Book::visible()->where('id', '=', $entityId)->first(); + if ($parent === null) { + throw new MoveOperationException('Book to move chapter into not found'); + } + + $chapter->changeBook($parent->id); + $chapter->rebuildPermissions(); + return $parent; + } +} diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php deleted file mode 100644 index 13a335ea0..000000000 --- a/app/Entities/Repos/EntityRepo.php +++ /dev/null @@ -1,843 +0,0 @@ -entityProvider = $entityProvider; - $this->viewService = $viewService; - $this->permissionService = $permissionService; - $this->tagRepo = $tagRepo; - $this->searchService = $searchService; - } - - /** - * Base query for searching entities via permission system - * @param string $type - * @param bool $allowDrafts - * @param string $permission - * @return QueryBuilder - */ - protected function entityQuery($type, $allowDrafts = false, $permission = 'view') - { - $q = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type), $permission); - if (strtolower($type) === 'page' && !$allowDrafts) { - $q = $q->where('draft', '=', false); - } - return $q; - } - - /** - * Check if an entity with the given id exists. - * @param $type - * @param $id - * @return bool - */ - public function exists($type, $id) - { - return $this->entityQuery($type)->where('id', '=', $id)->exists(); - } - - /** - * Get an entity by ID - * @param string $type - * @param integer $id - * @param bool $allowDrafts - * @param bool $ignorePermissions - * @return Entity - */ - public function getById($type, $id, $allowDrafts = false, $ignorePermissions = false) - { - $query = $this->entityQuery($type, $allowDrafts); - - if ($ignorePermissions) { - $query = $this->entityProvider->get($type)->newQuery(); - } - - return $query->find($id); - } - - /** - * @param string $type - * @param []int $ids - * @param bool $allowDrafts - * @param bool $ignorePermissions - * @return Builder[]|\Illuminate\Database\Eloquent\Collection|Collection - */ - public function getManyById($type, $ids, $allowDrafts = false, $ignorePermissions = false) - { - $query = $this->entityQuery($type, $allowDrafts); - - if ($ignorePermissions) { - $query = $this->entityProvider->get($type)->newQuery(); - } - - return $query->whereIn('id', $ids)->get(); - } - - /** - * Get an entity by its url slug. - * @param string $type - * @param string $slug - * @param string|null $bookSlug - * @return Entity - * @throws NotFoundException - */ - public function getEntityBySlug(string $type, string $slug, string $bookSlug = null): Entity - { - $type = strtolower($type); - $query = $this->entityQuery($type)->where('slug', '=', $slug); - - if ($type === 'chapter' || $type === 'page') { - $query = $query->where('book_id', '=', function (QueryBuilder $query) use ($bookSlug) { - $query->select('id') - ->from($this->entityProvider->book->getTable()) - ->where('slug', '=', $bookSlug)->limit(1); - }); - } - - $entity = $query->first(); - - if ($entity === null) { - throw new NotFoundException(trans('errors.' . $type . '_not_found')); - } - - return $entity; - } - - - /** - * Get all entities of a type with the given permission, limited by count unless count is false. - * @param string $type - * @param integer|bool $count - * @param string $permission - * @return Collection - */ - public function getAll($type, $count = 20, $permission = 'view') - { - $q = $this->entityQuery($type, false, $permission)->orderBy('name', 'asc'); - if ($count !== false) { - $q = $q->take($count); - } - return $q->get(); - } - - /** - * Get all entities in a paginated format - * @param $type - * @param int $count - * @param string $sort - * @param string $order - * @param null|callable $queryAddition - * @return LengthAwarePaginator - */ - public function getAllPaginated($type, int $count = 10, string $sort = 'name', string $order = 'asc', $queryAddition = null) - { - $query = $this->entityQuery($type); - $query = $this->addSortToQuery($query, $sort, $order); - if ($queryAddition) { - $queryAddition($query); - } - return $query->paginate($count); - } - - /** - * Add sorting operations to an entity query. - * @param Builder $query - * @param string $sort - * @param string $order - * @return Builder - */ - protected function addSortToQuery(Builder $query, string $sort = 'name', string $order = 'asc') - { - $order = ($order === 'asc') ? 'asc' : 'desc'; - $propertySorts = ['name', 'created_at', 'updated_at']; - - if (in_array($sort, $propertySorts)) { - return $query->orderBy($sort, $order); - } - - return $query; - } - - /** - * Get the most recently created entities of the given type. - * @param string $type - * @param int $count - * @param int $page - * @param bool|callable $additionalQuery - * @return Collection - */ - public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false) - { - $query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type)) - ->orderBy('created_at', 'desc'); - if (strtolower($type) === 'page') { - $query = $query->where('draft', '=', false); - } - if ($additionalQuery !== false && is_callable($additionalQuery)) { - $additionalQuery($query); - } - return $query->skip($page * $count)->take($count)->get(); - } - - /** - * Get the most recently updated entities of the given type. - * @param string $type - * @param int $count - * @param int $page - * @param bool|callable $additionalQuery - * @return Collection - */ - public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false) - { - $query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type)) - ->orderBy('updated_at', 'desc'); - if (strtolower($type) === 'page') { - $query = $query->where('draft', '=', false); - } - if ($additionalQuery !== false && is_callable($additionalQuery)) { - $additionalQuery($query); - } - return $query->skip($page * $count)->take($count)->get(); - } - - /** - * Get the most recently viewed entities. - * @param string|bool $type - * @param int $count - * @param int $page - * @return mixed - */ - public function getRecentlyViewed($type, $count = 10, $page = 0) - { - $filter = is_bool($type) ? false : $this->entityProvider->get($type); - return $this->viewService->getUserRecentlyViewed($count, $page, $filter); - } - - /** - * Get the latest pages added to the system with pagination. - * @param string $type - * @param int $count - * @return mixed - */ - public function getRecentlyCreatedPaginated($type, $count = 20) - { - return $this->entityQuery($type)->orderBy('created_at', 'desc')->paginate($count); - } - - /** - * Get the latest pages added to the system with pagination. - * @param string $type - * @param int $count - * @return mixed - */ - public function getRecentlyUpdatedPaginated($type, $count = 20) - { - return $this->entityQuery($type)->orderBy('updated_at', 'desc')->paginate($count); - } - - /** - * Get the most popular entities base on all views. - * @param string $type - * @param int $count - * @param int $page - * @return mixed - */ - public function getPopular(string $type, int $count = 10, int $page = 0) - { - return $this->viewService->getPopular($count, $page, $type); - } - - /** - * Get draft pages owned by the current user. - * @param int $count - * @param int $page - * @return Collection - */ - public function getUserDraftPages($count = 20, $page = 0) - { - return $this->entityProvider->page->where('draft', '=', true) - ->where('created_by', '=', user()->id) - ->orderBy('updated_at', 'desc') - ->skip($count * $page)->take($count)->get(); - } - - /** - * Get the number of entities the given user has created. - * @param string $type - * @param User $user - * @return int - */ - public function getUserTotalCreated(string $type, User $user) - { - return $this->entityProvider->get($type) - ->where('created_by', '=', $user->id)->count(); - } - - /** - * Get the child items for a chapter sorted by priority but - * with draft items floated to the top. - * @param Bookshelf $bookshelf - * @return \Illuminate\Database\Eloquent\Collection|static[] - */ - public function getBookshelfChildren(Bookshelf $bookshelf) - { - return $this->permissionService->enforceEntityRestrictions('book', $bookshelf->books())->get(); - } - - /** - * Get the direct children of a book. - * @param Book $book - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getBookDirectChildren(Book $book) - { - $pages = $this->permissionService->enforceEntityRestrictions('page', $book->directPages())->get(); - $chapters = $this->permissionService->enforceEntityRestrictions('chapters', $book->chapters())->get(); - return collect()->concat($pages)->concat($chapters)->sortBy('priority')->sortByDesc('draft'); - } - - /** - * Get all child objects of a book. - * Returns a sorted collection of Pages and Chapters. - * Loads the book slug onto child elements to prevent access database access for getting the slug. - * @param Book $book - * @param bool $filterDrafts - * @param bool $renderPages - * @return mixed - */ - public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false) - { - $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get(); - $entities = []; - $parents = []; - $tree = []; - - foreach ($q as $index => $rawEntity) { - if ($rawEntity->entity_type === $this->entityProvider->page->getMorphClass()) { - $entities[$index] = $this->entityProvider->page->newFromBuilder($rawEntity); - if ($renderPages) { - $entities[$index]->html = $rawEntity->html; - $entities[$index]->html = $this->renderPage($entities[$index]); - }; - } else if ($rawEntity->entity_type === $this->entityProvider->chapter->getMorphClass()) { - $entities[$index] = $this->entityProvider->chapter->newFromBuilder($rawEntity); - $key = $entities[$index]->entity_type . ':' . $entities[$index]->id; - $parents[$key] = $entities[$index]; - $parents[$key]->setAttribute('pages', collect()); - } - if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') { - $tree[] = $entities[$index]; - } - $entities[$index]->book = $book; - } - - foreach ($entities as $entity) { - if ($entity->chapter_id === 0 || $entity->chapter_id === '0') { - continue; - } - $parentKey = $this->entityProvider->chapter->getMorphClass() . ':' . $entity->chapter_id; - if (!isset($parents[$parentKey])) { - $tree[] = $entity; - continue; - } - $chapter = $parents[$parentKey]; - $chapter->pages->push($entity); - } - - return collect($tree); - } - - /** - * Get the child items for a chapter sorted by priority but - * with draft items floated to the top. - * @param Chapter $chapter - * @return \Illuminate\Database\Eloquent\Collection|static[] - */ - public function getChapterChildren(Chapter $chapter) - { - return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages()) - ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get(); - } - - - /** - * Get the next sequential priority for a new child element in the given book. - * @param Book $book - * @return int - */ - public function getNewBookPriority(Book $book) - { - $lastElem = $this->getBookChildren($book)->pop(); - return $lastElem ? $lastElem->priority + 1 : 0; - } - - /** - * Get a new priority for a new page to be added to the given chapter. - * @param Chapter $chapter - * @return int - */ - public function getNewChapterPriority(Chapter $chapter) - { - $lastPage = $chapter->pages('DESC')->first(); - return $lastPage !== null ? $lastPage->priority + 1 : 0; - } - - /** - * Find a suitable slug for an entity. - * @param string $type - * @param string $name - * @param bool|integer $currentId - * @param bool|integer $bookId Only pass if type is not a book - * @return string - */ - public function findSuitableSlug($type, $name, $currentId = false, $bookId = false) - { - $slug = $this->nameToSlug($name); - while ($this->slugExists($type, $slug, $currentId, $bookId)) { - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3); - } - return $slug; - } - - - /** - * Updates entity restrictions from a request - * @param Request $request - * @param Entity $entity - * @throws Throwable - */ - public function updateEntityPermissionsFromRequest(Request $request, Entity $entity) - { - $entity->restricted = $request->get('restricted', '') === 'true'; - $entity->permissions()->delete(); - - if ($request->filled('restrictions')) { - $entityPermissionData = collect($request->get('restrictions'))->flatMap(function($restrictions, $roleId) { - return collect($restrictions)->keys()->map(function($action) use ($roleId) { - return [ - 'role_id' => $roleId, - 'action' => strtolower($action), - ] ; - }); - }); - - $entity->permissions()->createMany($entityPermissionData); - } - - $entity->save(); - $entity->rebuildPermissions(); - } - - - /** - * Create a new entity from request input. - * Used for books and chapters. - * @param string $type - * @param array $input - * @param Book|null $book - * @return Entity - */ - public function createFromInput(string $type, array $input = [], Book $book = null) - { - $entityModel = $this->entityProvider->get($type)->newInstance($input); - $entityModel->created_by = user()->id; - $entityModel->updated_by = user()->id; - - if ($book) { - $entityModel->book_id = $book->id; - } - - $entityModel->refreshSlug(); - $entityModel->save(); - - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); - } - - $entityModel->rebuildPermissions(); - $this->searchService->indexEntity($entityModel); - return $entityModel; - } - - /** - * Update entity details from request input. - * Used for shelves, books and chapters. - */ - public function updateFromInput(Entity $entityModel, array $input): Entity - { - $entityModel->fill($input); - $entityModel->updated_by = user()->id; - - if ($entityModel->isDirty('name')) { - $entityModel->refreshSlug(); - } - - $entityModel->save(); - - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); - } - - $entityModel->rebuildPermissions(); - $this->searchService->indexEntity($entityModel); - return $entityModel; - } - - /** - * Sync the books assigned to a shelf from a comma-separated list - * of book IDs. - * @param Bookshelf $shelf - * @param string $books - */ - public function updateShelfBooks(Bookshelf $shelf, string $books) - { - $ids = explode(',', $books); - - // Check books exist and match ordering - $bookIds = $this->entityQuery('book')->whereIn('id', $ids)->get(['id'])->pluck('id'); - $syncData = []; - foreach ($ids as $index => $id) { - if ($bookIds->contains($id)) { - $syncData[$id] = ['order' => $index]; - } - } - - $shelf->books()->sync($syncData); - } - - /** - * Change the book that an entity belongs to. - */ - public function changeBook(BookChild $bookChild, int $newBookId): Entity - { - $bookChild->book_id = $newBookId; - $bookChild->refreshSlug(); - $bookChild->save(); - - // Update related activity - $bookChild->activity()->update(['book_id' => $newBookId]); - - // Update all child pages if a chapter - if ($bookChild->isA('chapter')) { - foreach ($bookChild->pages as $page) { - $this->changeBook($page, $newBookId); - } - } - - return $bookChild; - } - - /** - * Render the page for viewing - * @param Page $page - * @param bool $blankIncludes - * @return string - */ - public function renderPage(Page $page, bool $blankIncludes = false) : string - { - $content = $page->html; - - if (!config('app.allow_content_scripts')) { - $content = $this->escapeScripts($content); - } - - if ($blankIncludes) { - $content = $this->blankPageIncludes($content); - } else { - $content = $this->parsePageIncludes($content); - } - - return $content; - } - - /** - * Remove any page include tags within the given HTML. - * @param string $html - * @return string - */ - protected function blankPageIncludes(string $html) : string - { - return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html); - } - - /** - * Parse any include tags "{{@#section}}" to be part of the page. - * @param string $html - * @return mixed|string - */ - protected function parsePageIncludes(string $html) : string - { - $matches = []; - preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches); - - $topLevelTags = ['table', 'ul', 'ol']; - foreach ($matches[1] as $index => $includeId) { - $splitInclude = explode('#', $includeId, 2); - $pageId = intval($splitInclude[0]); - if (is_nan($pageId)) { - continue; - } - - $matchedPage = $this->getById('page', $pageId); - if ($matchedPage === null) { - $html = str_replace($matches[0][$index], '', $html); - continue; - } - - if (count($splitInclude) === 1) { - $html = str_replace($matches[0][$index], $matchedPage->html, $html); - continue; - } - - $doc = new DOMDocument(); - libxml_use_internal_errors(true); - $doc->loadHTML(mb_convert_encoding(''.$matchedPage->html.'', 'HTML-ENTITIES', 'UTF-8')); - $matchingElem = $doc->getElementById($splitInclude[1]); - if ($matchingElem === null) { - $html = str_replace($matches[0][$index], '', $html); - continue; - } - $innerContent = ''; - $isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags); - if ($isTopLevel) { - $innerContent .= $doc->saveHTML($matchingElem); - } else { - foreach ($matchingElem->childNodes as $childNode) { - $innerContent .= $doc->saveHTML($childNode); - } - } - libxml_clear_errors(); - $html = str_replace($matches[0][$index], trim($innerContent), $html); - } - - return $html; - } - - /** - * Escape script tags within HTML content. - * @param string $html - * @return string - */ - protected function escapeScripts(string $html) : string - { - if ($html == '') { - return $html; - } - - libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); - $xPath = new DOMXPath($doc); - - // Remove standard script tags - $scriptElems = $xPath->query('//script'); - foreach ($scriptElems as $scriptElem) { - $scriptElem->parentNode->removeChild($scriptElem); - } - - // Remove data or JavaScript iFrames - $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]'); - foreach ($badIframes as $badIframe) { - $badIframe->parentNode->removeChild($badIframe); - } - - // Remove 'on*' attributes - $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]'); - foreach ($onAttributes as $attr) { - /** @var \DOMAttr $attr*/ - $attrName = $attr->nodeName; - $attr->parentNode->removeAttribute($attrName); - } - - $html = ''; - $topElems = $doc->documentElement->childNodes->item(0)->childNodes; - foreach ($topElems as $child) { - $html .= $doc->saveHTML($child); - } - - return $html; - } - - /** - * Search for image usage within page content. - * @param $imageString - * @return mixed - */ - public function searchForImage($imageString) - { - $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get(['id', 'name', 'slug', 'book_id']); - foreach ($pages as $page) { - $page->url = $page->getUrl(); - $page->html = ''; - $page->text = ''; - } - return count($pages) > 0 ? $pages : false; - } - - /** - * Destroy a bookshelf instance - * @param Bookshelf $shelf - * @throws Throwable - */ - public function destroyBookshelf(Bookshelf $shelf) - { - $this->destroyEntityCommonRelations($shelf); - $shelf->delete(); - } - - /** - * Destroy a chapter and its relations. - * @param Chapter $chapter - * @throws Throwable - */ - public function destroyChapter(Chapter $chapter) - { - if (count($chapter->pages) > 0) { - foreach ($chapter->pages as $page) { - $page->chapter_id = 0; - $page->save(); - } - } - $this->destroyEntityCommonRelations($chapter); - $chapter->delete(); - } - - /** - * Destroy a given page along with its dependencies. - * @param Page $page - * @throws NotifyException - * @throws Throwable - */ - public function destroyPage(Page $page) - { - // Check if set as custom homepage & remove setting if not used or throw error if active - $customHome = setting('app-homepage', '0:'); - if (intval($page->id) === intval(explode(':', $customHome)[0])) { - if (setting('app-homepage-type') === 'page') { - throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl()); - } - setting()->remove('app-homepage'); - } - - $this->destroyEntityCommonRelations($page); - - // Delete Attached Files - $attachmentService = app(AttachmentService::class); - foreach ($page->attachments as $attachment) { - $attachmentService->deleteFile($attachment); - } - - $page->delete(); - } - - /** - * Destroy or handle the common relations connected to an entity. - * @param Entity $entity - * @throws Throwable - */ - protected function destroyEntityCommonRelations(Entity $entity) - { - Activity::removeEntity($entity); - $entity->views()->delete(); - $entity->permissions()->delete(); - $entity->tags()->delete(); - $entity->comments()->delete(); - $this->permissionService->deleteJointPermissionsForEntity($entity); - $this->searchService->deleteEntityTerms($entity); - } - - /** - * Copy the permissions of a bookshelf to all child books. - * Returns the number of books that had permissions updated. - * @param Bookshelf $bookshelf - * @return int - * @throws Throwable - */ - public function copyBookshelfPermissions(Bookshelf $bookshelf) - { - $shelfPermissions = $bookshelf->permissions()->get(['role_id', 'action'])->toArray(); - $shelfBooks = $bookshelf->books()->get(); - $updatedBookCount = 0; - - /** @var Book $book */ - foreach ($shelfBooks as $book) { - if (!userCan('restrictions-manage', $book)) { - continue; - } - $book->permissions()->delete(); - $book->restricted = $bookshelf->restricted; - $book->permissions()->createMany($shelfPermissions); - $book->save(); - $book->rebuildPermissions(); - $updatedBookCount++; - } - - return $updatedBookCount; - } -} diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 0e0585a85..0fc68f953 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -3,91 +3,199 @@ use BookStack\Entities\Book; use BookStack\Entities\Chapter; use BookStack\Entities\Entity; +use BookStack\Entities\Managers\BookContents; +use BookStack\Entities\Managers\PageContent; +use BookStack\Entities\Managers\TrashCan; use BookStack\Entities\Page; use BookStack\Entities\PageRevision; -use Carbon\Carbon; -use DOMDocument; -use DOMElement; -use DOMXPath; +use BookStack\Exceptions\MoveOperationException; +use BookStack\Exceptions\NotFoundException; +use BookStack\Exceptions\NotifyException; +use BookStack\Exceptions\PermissionsException; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; -class PageRepo extends EntityRepo +class PageRepo { + protected $baseRepo; + /** - * Get page by slug. - * @param string $pageSlug - * @param string $bookSlug - * @return Page - * @throws \BookStack\Exceptions\NotFoundException + * PageRepo constructor. */ - public function getBySlug(string $pageSlug, string $bookSlug) + public function __construct(BaseRepo $baseRepo) { - return $this->getEntityBySlug('page', $pageSlug, $bookSlug); + $this->baseRepo = $baseRepo; } /** - * Search through page revisions and retrieve the last page in the - * current book that has a slug equal to the one given. - * @param string $pageSlug - * @param string $bookSlug - * @return null|Page + * Get a page by ID. + * @throws NotFoundException */ - public function getPageByOldSlug(string $pageSlug, string $bookSlug) + public function getById(int $id): Page { - $revision = $this->entityProvider->pageRevision->where('slug', '=', $pageSlug) - ->whereHas('page', function ($query) { - $this->permissionService->enforceEntityRestrictions('page', $query); + $page = Page::visible()->with(['book'])->find($id); + + if (!$page) { + throw new NotFoundException(trans('errors.page_not_found')); + } + + return $page; + } + + /** + * Get a page its book and own slug. + * @throws NotFoundException + */ + public function getBySlug(string $bookSlug, string $pageSlug): Page + { + $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first(); + + if (!$page) { + throw new NotFoundException(trans('errors.page_not_found')); + } + + return $page; + } + + /** + * Get a page by its old slug but checking the revisions table + * for the last revision that matched the given page and book slug. + */ + public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page + { + $revision = PageRevision::query() + ->whereHas('page', function (Builder $query) { + $query->visible(); }) + ->where('slug', '=', $pageSlug) ->where('type', '=', 'version') ->where('book_slug', '=', $bookSlug) ->orderBy('created_at', 'desc') - ->with('page')->first(); - return $revision !== null ? $revision->page : null; + ->with('page') + ->first(); + return $revision ? $revision->page : null; } /** - * Updates a page with any fillable data and saves it into the database. - * @param Page $page - * @param int $book_id - * @param array $input - * @return Page - * @throws \Exception + * Get pages that have been marked as a template. */ - public function updatePage(Page $page, int $book_id, array $input) + public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator + { + $query = Page::visible() + ->where('template', '=', true) + ->orderBy('name', 'asc') + ->skip(($page - 1) * $count) + ->take($count); + + if ($search) { + $query->where('name', 'like', '%' . $search . '%'); + } + + $paginator = $query->paginate($count, ['*'], 'page', $page); + $paginator->withPath('/templates'); + + return $paginator; + } + + /** + * Get a parent item via slugs. + */ + public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity + { + if ($chapterSlug !== null) { + return $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); + } + + return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); + } + + /** + * Get the draft copy of the given page for the current user. + */ + public function getUserDraft(Page $page): ?PageRevision + { + $revision = $this->getUserDraftQuery($page)->first(); + return $revision; + } + + /** + * Get a new draft page belonging to the given parent entity. + */ + public function getNewDraftPage(Entity $parent) + { + $page = (new Page())->forceFill([ + 'name' => trans('entities.pages_initial_name'), + 'created_by' => user()->id, + 'updated_by' => user()->id, + 'draft' => true, + ]); + + if ($parent instanceof Chapter) { + $page->chapter_id = $parent->id; + $page->book_id = $parent->book_id; + } else { + $page->book_id = $parent->id; + } + + $page->save(); + $page->refresh()->rebuildPermissions(); + return $page; + } + + /** + * Publish a draft page to make it a live, non-draft page. + */ + public function publishDraft(Page $draft, array $input): Page + { + $this->baseRepo->update($draft, $input); + if (isset($input['template']) && userCan('templates-manage')) { + $draft->template = ($input['template'] === 'true'); + } + + $pageContent = new PageContent($draft); + $pageContent->setNewHTML($input['html']); + $draft->draft = false; + $draft->revision_count = 1; + $draft->priority = $this->getNewPriority($draft); + $draft->refreshSlug(); + $draft->save(); + + $this->savePageRevision($draft, trans('entities.pages_initial_revision')); + $draft->indexForSearch(); + return $draft->refresh(); + } + + /** + * Update a page in the system. + */ + public function update(Page $page, array $input): Page { // Hold the old details to compare later $oldHtml = $page->html; $oldName = $page->name; - // Save page tags if present - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($page, $input['tags']); - } - if (isset($input['template']) && userCan('templates-manage')) { $page->template = ($input['template'] === 'true'); } + $this->baseRepo->update($page, $input); + // Update with new details - $userId = user()->id; $page->fill($input); - $page->html = $this->formatHtml($input['html']); - $page->text = $this->pageToPlainText($page); - $page->updated_by = $userId; + $pageContent = new PageContent($page); + $pageContent->setNewHTML($input['html']); $page->revision_count++; if (setting('app-editor') !== 'markdown') { $page->markdown = ''; } - if ($page->isDirty('name')) { - $page->refreshSlug(); - } - $page->save(); // Remove all update drafts for this user & page. - $this->userUpdatePageDraftsQuery($page, $userId)->delete(); + $this->getUserDraftQuery($page)->delete(); // Save a revision after updating $summary = $input['summary'] ?? null; @@ -95,24 +203,20 @@ class PageRepo extends EntityRepo $this->savePageRevision($page, $summary); } - $this->searchService->indexEntity($page); - return $page; } /** * Saves a page revision into the system. - * @param Page $page - * @param null|string $summary - * @return PageRevision - * @throws \Exception */ - public function savePageRevision(Page $page, string $summary = null) + protected function savePageRevision(Page $page, string $summary = null) { - $revision = $this->entityProvider->pageRevision->newInstance($page->toArray()); + $revision = new PageRevision($page->toArray()); + if (setting('app-editor') !== 'markdown') { $revision->markdown = ''; } + $revision->page_id = $page->id; $revision->slug = $page->slug; $revision->book_slug = $page->book->slug; @@ -123,163 +227,29 @@ class PageRepo extends EntityRepo $revision->revision_number = $page->revision_count; $revision->save(); - $revisionLimit = config('app.revision_limit'); - if ($revisionLimit !== false) { - $revisionsToDelete = $this->entityProvider->pageRevision->where('page_id', '=', $page->id) - ->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']); - if ($revisionsToDelete->count() > 0) { - $this->entityProvider->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); - } - } - + $this->deleteOldRevisions($page); return $revision; } - /** - * Formats a page's html to be tagged correctly within the system. - * @param string $htmlText - * @return string - */ - protected function formatHtml(string $htmlText) - { - if ($htmlText == '') { - return $htmlText; - } - - libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8')); - - $container = $doc->documentElement; - $body = $container->childNodes->item(0); - $childNodes = $body->childNodes; - - // Set ids on top-level nodes - $idMap = []; - foreach ($childNodes as $index => $childNode) { - $this->setUniqueId($childNode, $idMap); - } - - // Ensure no duplicate ids within child items - $xPath = new DOMXPath($doc); - $idElems = $xPath->query('//body//*//*[@id]'); - foreach ($idElems as $domElem) { - $this->setUniqueId($domElem, $idMap); - } - - // Generate inner html as a string - $html = ''; - foreach ($childNodes as $childNode) { - $html .= $doc->saveHTML($childNode); - } - - return $html; - } - - /** - * Set a unique id on the given DOMElement. - * A map for existing ID's should be passed in to check for current existence. - * @param DOMElement $element - * @param array $idMap - */ - protected function setUniqueId($element, array &$idMap) - { - if (get_class($element) !== 'DOMElement') { - return; - } - - // Overwrite id if not a BookStack custom id - $existingId = $element->getAttribute('id'); - if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) { - $idMap[$existingId] = true; - return; - } - - // Create an unique id for the element - // Uses the content as a basis to ensure output is the same every time - // the same content is passed through. - $contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20); - $newId = urlencode($contentId); - $loopIndex = 0; - - while (isset($idMap[$newId])) { - $newId = urlencode($contentId . '-' . $loopIndex); - $loopIndex++; - } - - $element->setAttribute('id', $newId); - $idMap[$newId] = true; - } - - /** - * Get the plain text version of a page's content. - * @param \BookStack\Entities\Page $page - * @return string - */ - protected function pageToPlainText(Page $page) : string - { - $html = $this->renderPage($page, true); - return strip_tags($html); - } - - /** - * Get a new draft page instance. - * @param Book $book - * @param Chapter|null $chapter - * @return \BookStack\Entities\Page - * @throws \Throwable - */ - public function getDraftPage(Book $book, Chapter $chapter = null) - { - $page = $this->entityProvider->page->newInstance(); - $page->name = trans('entities.pages_initial_name'); - $page->created_by = user()->id; - $page->updated_by = user()->id; - $page->draft = true; - - if ($chapter) { - $page->chapter_id = $chapter->id; - } - - $book->pages()->save($page); - $page->refresh()->rebuildPermissions(); - return $page; - } - /** * Save a page update draft. - * @param Page $page - * @param array $data - * @return PageRevision|Page */ - public function updatePageDraft(Page $page, array $data = []) + public function updatePageDraft(Page $page, array $input) { // If the page itself is a draft simply update that if ($page->draft) { - $page->fill($data); - if (isset($data['html'])) { - $page->text = $this->pageToPlainText($page); + $page->fill($input); + if (isset($input['html'])) { + $content = new PageContent($page); + $content->setNewHTML($input['html']); } $page->save(); return $page; } // Otherwise save the data to a revision - $userId = user()->id; - $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get(); - - if ($drafts->count() > 0) { - $draft = $drafts->first(); - } else { - $draft = $this->entityProvider->pageRevision->newInstance(); - $draft->page_id = $page->id; - $draft->slug = $page->slug; - $draft->book_slug = $page->book->slug; - $draft->created_by = $userId; - $draft->type = 'update_draft'; - } - - $draft->fill($data); + $draft = $this->getPageRevisionToUpdate($page); + $draft->fill($input); if (setting('app-editor') !== 'markdown') { $draft->markdown = ''; } @@ -289,227 +259,76 @@ class PageRepo extends EntityRepo } /** - * Publish a draft page to make it a normal page. - * Sets the slug and updates the content. - * @param Page $draftPage - * @param array $input - * @return Page - * @throws \Exception + * Destroy a page from the system. + * @throws NotifyException */ - public function publishPageDraft(Page $draftPage, array $input) + public function destroy(Page $page) { - $draftPage->fill($input); - - // Save page tags if present - if (isset($input['tags'])) { - $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']); - } - - if (isset($input['template']) && userCan('templates-manage')) { - $draftPage->template = ($input['template'] === 'true'); - } - - $draftPage->html = $this->formatHtml($input['html']); - $draftPage->text = $this->pageToPlainText($draftPage); - $draftPage->draft = false; - $draftPage->revision_count = 1; - $draftPage->refreshSlug(); - $draftPage->save(); - $this->savePageRevision($draftPage, trans('entities.pages_initial_revision')); - $this->searchService->indexEntity($draftPage); - return $draftPage; - } - - /** - * The base query for getting user update drafts. - * @param Page $page - * @param $userId - * @return mixed - */ - protected function userUpdatePageDraftsQuery(Page $page, int $userId) - { - return $this->entityProvider->pageRevision->where('created_by', '=', $userId) - ->where('type', 'update_draft') - ->where('page_id', '=', $page->id) - ->orderBy('created_at', 'desc'); - } - - /** - * Get the latest updated draft revision for a particular page and user. - * @param Page $page - * @param $userId - * @return PageRevision|null - */ - public function getUserPageDraft(Page $page, int $userId) - { - return $this->userUpdatePageDraftsQuery($page, $userId)->first(); - } - - /** - * Get the notification message that informs the user that they are editing a draft page. - * @param PageRevision $draft - * @return string - */ - public function getUserPageDraftMessage(PageRevision $draft) - { - $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]); - if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) { - return $message; - } - return $message . "\n" . trans('entities.pages_draft_edited_notification'); - } - - /** - * A query to check for active update drafts on a particular page. - * @param Page $page - * @param int $minRange - * @return mixed - */ - protected function activePageEditingQuery(Page $page, int $minRange = null) - { - $query = $this->entityProvider->pageRevision->where('type', '=', 'update_draft') - ->where('page_id', '=', $page->id) - ->where('updated_at', '>', $page->updated_at) - ->where('created_by', '!=', user()->id) - ->with('createdBy'); - - if ($minRange !== null) { - $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange)); - } - - return $query; - } - - /** - * Check if a page is being actively editing. - * Checks for edits since last page updated. - * Passing in a minuted range will check for edits - * within the last x minutes. - * @param Page $page - * @param int $minRange - * @return bool - */ - public function isPageEditingActive(Page $page, int $minRange = null) - { - $draftSearch = $this->activePageEditingQuery($page, $minRange); - return $draftSearch->count() > 0; - } - - /** - * Get a notification message concerning the editing activity on a particular page. - * @param Page $page - * @param int $minRange - * @return string - */ - public function getPageEditingActiveMessage(Page $page, int $minRange = null) - { - $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get(); - - $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); - $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]); - return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]); - } - - /** - * Parse the headers on the page to get a navigation menu - * @param string $pageContent - * @return array - */ - public function getPageNav(string $pageContent) - { - if ($pageContent == '') { - return []; - } - libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8')); - $xPath = new DOMXPath($doc); - $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); - - if (is_null($headers)) { - return []; - } - - $tree = collect($headers)->map(function ($header) { - $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue)); - $text = mb_substr($text, 0, 100); - - return [ - 'nodeName' => strtolower($header->nodeName), - 'level' => intval(str_replace('h', '', $header->nodeName)), - 'link' => '#' . $header->getAttribute('id'), - 'text' => $text, - ]; - })->filter(function ($header) { - return mb_strlen($header['text']) > 0; - }); - - // Shift headers if only smaller headers have been used - $levelChange = ($tree->pluck('level')->min() - 1); - $tree = $tree->map(function ($header) use ($levelChange) { - $header['level'] -= ($levelChange); - return $header; - }); - - return $tree->toArray(); + $trashCan = new TrashCan(); + $trashCan->destroyPage($page); } /** * Restores a revision's content back into a page. - * @param Page $page - * @param Book $book - * @param int $revisionId - * @return Page - * @throws \Exception */ - public function restorePageRevision(Page $page, Book $book, int $revisionId) + public function restoreRevision(Page $page, int $revisionId): Page { $page->revision_count++; $this->savePageRevision($page); $revision = $page->revisions()->where('id', '=', $revisionId)->first(); $page->fill($revision->toArray()); - $page->text = $this->pageToPlainText($page); + $content = new PageContent($page); + $content->setNewHTML($page->html); $page->updated_by = user()->id; $page->refreshSlug(); $page->save(); - $this->searchService->indexEntity($page); + $page->indexForSearch(); return $page; } /** - * Change the page's parent to the given entity. - * @param Page $page - * @param Entity $parent + * Move the given page into a new parent book or chapter. + * The $parentIdentifier must be a string of the following format: + * 'book:' (book:5) + * @throws MoveOperationException + * @throws PermissionsException */ - public function changePageParent(Page $page, Entity $parent) + public function move(Page $page, string $parentIdentifier): Book { - $book = $parent->isA('book') ? $parent : $parent->book; - $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0; - $page->save(); - - if ($page->book->id !== $book->id) { - $page = $this->changeBook($page, $book->id); + $parent = $this->findParentByIdentifier($parentIdentifier); + if ($parent === null) { + throw new MoveOperationException('Book or chapter to move page into not found'); } - $page->load('book'); - $book->rebuildPermissions(); + if (!userCan('page-create', $parent)) { + throw new PermissionsException('User does not have permission to create a page within the new parent'); + } + + $page->changeBook($parent instanceof Book ? $parent->id : $parent->book->id); + $page->rebuildPermissions(); + return $parent; } /** - * Create a copy of a page in a new location with a new name. - * @param \BookStack\Entities\Page $page - * @param \BookStack\Entities\Entity $newParent - * @param string $newName - * @return \BookStack\Entities\Page - * @throws \Throwable + * Copy an existing page in the system. + * Optionally providing a new parent via string identifier and a new name. + * @throws MoveOperationException + * @throws PermissionsException */ - public function copyPage(Page $page, Entity $newParent, string $newName = '') + public function copy(Page $page, string $parentIdentifier = null, string $newName = null): Page { - $newBook = $newParent->isA('book') ? $newParent : $newParent->book; - $newChapter = $newParent->isA('chapter') ? $newParent : null; - $copyPage = $this->getDraftPage($newBook, $newChapter); + $parent = $parentIdentifier ? $this->findParentByIdentifier($parentIdentifier) : $page->parent(); + if ($parent === null) { + throw new MoveOperationException('Book or chapter to move page into not found'); + } + + if (!userCan('page-create', $parent)) { + throw new PermissionsException('User does not have permission to create a page within the new parent'); + } + + $copyPage = $this->getNewDraftPage($parent); $pageData = $page->getAttributes(); // Update name @@ -525,38 +344,116 @@ class PageRepo extends EntityRepo } } - // Set priority - if ($newParent->isA('chapter')) { - $pageData['priority'] = $this->getNewChapterPriority($newParent); - } else { - $pageData['priority'] = $this->getNewBookPriority($newParent); - } - - return $this->publishPageDraft($copyPage, $pageData); + return $this->publishDraft($copyPage, $pageData); } /** - * Get pages that have been marked as templates. - * @param int $count - * @param int $page - * @param string $search - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * Find a page parent entity via a identifier string in the format: + * {type}:{id} + * Example: (book:5) + * @throws MoveOperationException */ - public function getPageTemplates(int $count = 10, int $page = 1, string $search = '') + protected function findParentByIdentifier(string $identifier): ?Entity { - $query = $this->entityQuery('page') - ->where('template', '=', true) - ->orderBy('name', 'asc') - ->skip(($page - 1) * $count) - ->take($count); + $stringExploded = explode(':', $identifier); + $entityType = $stringExploded[0]; + $entityId = intval($stringExploded[1]); - if ($search) { - $query->where('name', 'like', '%' . $search . '%'); + if ($entityType !== 'book' && $entityType !== 'chapter') { + throw new MoveOperationException('Pages can only be in books or chapters'); } - $paginator = $query->paginate($count, ['*'], 'page', $page); - $paginator->withPath('/templates'); + $parentClass = $entityType === 'book' ? Book::class : Chapter::class; + return $parentClass::visible()->where('id', '=', $entityId)->first(); + } - return $paginator; + /** + * Update the permissions of a page. + */ + public function updatePermissions(Page $page, bool $restricted, Collection $permissions = null) + { + $this->baseRepo->updatePermissions($page, $restricted, $permissions); + } + + /** + * Change the page's parent to the given entity. + */ + protected function changeParent(Page $page, Entity $parent) + { + $book = ($parent instanceof Book) ? $parent : $parent->book; + $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0; + $page->save(); + + if ($page->book->id !== $book->id) { + $page->changeBook($book->id); + } + + $page->load('book'); + $book->rebuildPermissions(); + } + + /** + * Get a page revision to update for the given page. + * Checks for an existing revisions before providing a fresh one. + */ + protected function getPageRevisionToUpdate(Page $page): PageRevision + { + $drafts = $this->getUserDraftQuery($page)->get(); + if ($drafts->count() > 0) { + return $drafts->first(); + } + + $draft = new PageRevision(); + $draft->page_id = $page->id; + $draft->slug = $page->slug; + $draft->book_slug = $page->book->slug; + $draft->created_by = user()->id; + $draft->type = 'update_draft'; + return $draft; + } + + /** + * Delete old revisions, for the given page, from the system. + */ + protected function deleteOldRevisions(Page $page) + { + $revisionLimit = config('app.revision_limit'); + if ($revisionLimit === false) { + return; + } + + $revisionsToDelete = PageRevision::query() + ->where('page_id', '=', $page->id) + ->orderBy('created_at', 'desc') + ->skip(intval($revisionLimit)) + ->take(10) + ->get(['id']); + if ($revisionsToDelete->count() > 0) { + PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); + } + } + + /** + * Get a new priority for a page + */ + protected function getNewPriority(Page $page): int + { + if ($page->parent() instanceof Chapter) { + $lastPage = $page->parent()->pages('desc')->first(); + return $lastPage ? $lastPage->priority + 1 : 0; + } + + return (new BookContents($page->book))->getLastPriority() + 1; + } + + /** + * Get the query to find the user's draft copies of the given page. + */ + protected function getUserDraftQuery(Page $page) + { + return PageRevision::query()->where('created_by', '=', user()->id) + ->where('type', 'update_draft') + ->where('page_id', '=', $page->id) + ->orderBy('created_at', 'desc'); } } diff --git a/app/Entities/SlugGenerator.php b/app/Entities/SlugGenerator.php index e68e00b06..459a5264a 100644 --- a/app/Entities/SlugGenerator.php +++ b/app/Entities/SlugGenerator.php @@ -59,6 +59,4 @@ class SlugGenerator return $query->count() > 0; } - - -} \ No newline at end of file +} diff --git a/app/Exceptions/MoveOperationException.php b/app/Exceptions/MoveOperationException.php new file mode 100644 index 000000000..c237dfad3 --- /dev/null +++ b/app/Exceptions/MoveOperationException.php @@ -0,0 +1,8 @@ +attachmentService = $attachmentService; $this->attachment = $attachment; - $this->entityRepo = $entityRepo; + $this->pageRepo = $pageRepo; parent::__construct(); } /** * Endpoint at which attachments are uploaded to. - * @param Request $request - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response + * @throws ValidationException + * @throws NotFoundException */ public function upload(Request $request) { @@ -41,7 +41,7 @@ class AttachmentController extends Controller ]); $pageId = $request->get('uploaded_to'); - $page = $this->entityRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $this->checkPermission('attachment-create-all'); $this->checkOwnablePermission('page-update', $page); @@ -59,10 +59,8 @@ class AttachmentController extends Controller /** * Update an uploaded attachment. - * @param Request $request - * @param int $attachmentId - * @return mixed - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException + * @throws NotFoundException */ public function uploadUpdate(Request $request, $attachmentId) { @@ -72,7 +70,7 @@ class AttachmentController extends Controller ]); $pageId = $request->get('uploaded_to'); - $page = $this->entityRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $attachment = $this->attachment->findOrFail($attachmentId); $this->checkOwnablePermission('page-update', $page); @@ -95,10 +93,8 @@ class AttachmentController extends Controller /** * Update the details of an existing file. - * @param Request $request - * @param $attachmentId - * @return Attachment|mixed - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException + * @throws NotFoundException */ public function update(Request $request, $attachmentId) { @@ -109,7 +105,7 @@ class AttachmentController extends Controller ]); $pageId = $request->get('uploaded_to'); - $page = $this->entityRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $attachment = $this->attachment->findOrFail($attachmentId); $this->checkOwnablePermission('page-update', $page); @@ -125,8 +121,8 @@ class AttachmentController extends Controller /** * Attach a link to a page. - * @param Request $request - * @return mixed + * @throws ValidationException + * @throws NotFoundException */ public function attachLink(Request $request) { @@ -137,7 +133,7 @@ class AttachmentController extends Controller ]); $pageId = $request->get('uploaded_to'); - $page = $this->entityRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $this->checkPermission('attachment-create-all'); $this->checkOwnablePermission('page-update', $page); @@ -151,30 +147,26 @@ class AttachmentController extends Controller /** * Get the attachments for a specific page. - * @param $pageId - * @return mixed */ - public function listForPage($pageId) + public function listForPage(int $pageId) { - $page = $this->entityRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-view', $page); return response()->json($page->attachments); } /** * Update the attachment sorting. - * @param Request $request - * @param $pageId - * @return mixed - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException + * @throws NotFoundException */ - public function sortForPage(Request $request, $pageId) + public function sortForPage(Request $request, int $pageId) { $this->validate($request, [ 'files' => 'required|array', 'files.*.id' => 'required|integer', ]); - $page = $this->entityRepo->getById('page', $pageId); + $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $attachments = $request->get('files'); @@ -184,16 +176,15 @@ class AttachmentController extends Controller /** * Get an attachment from storage. - * @param $attachmentId - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * @throws FileNotFoundException * @throws NotFoundException */ - public function get($attachmentId) + public function get(int $attachmentId) { $attachment = $this->attachment->findOrFail($attachmentId); - $page = $this->entityRepo->getById('page', $attachment->uploaded_to); - if ($page === null) { + try { + $page = $this->pageRepo->getById($attachment->uploaded_to); + } catch (NotFoundException $exception) { throw new NotFoundException(trans('errors.attachment_not_found')); } @@ -211,9 +202,9 @@ class AttachmentController extends Controller * Delete a specific attachment in the system. * @param $attachmentId * @return mixed - * @throws \Exception + * @throws Exception */ - public function delete($attachmentId) + public function delete(int $attachmentId) { $attachment = $this->attachment->findOrFail($attachmentId); $this->checkOwnablePermission('attachment-delete', $attachment); diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index 3959fe685..099558eb7 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -65,14 +65,14 @@ class ConfirmEmailController extends Controller $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token); } catch (Exception $exception) { if ($exception instanceof UserTokenNotFoundException) { - $this->showErrorNotification( trans('errors.email_confirmation_invalid')); + $this->showErrorNotification(trans('errors.email_confirmation_invalid')); return redirect('/register'); } if ($exception instanceof UserTokenExpiredException) { $user = $this->userRepo->getById($exception->userId); $this->emailConfirmationService->sendConfirmation($user); - $this->showErrorNotification( trans('errors.email_confirmation_expired')); + $this->showErrorNotification(trans('errors.email_confirmation_expired')); return redirect('/register/confirm'); } @@ -84,7 +84,7 @@ class ConfirmEmailController extends Controller $user->save(); auth()->login($user); - $this->showSuccessNotification( trans('auth.email_confirm_success')); + $this->showSuccessNotification(trans('auth.email_confirm_success')); $this->emailConfirmationService->deleteByUser($user); return redirect('/'); @@ -106,11 +106,11 @@ class ConfirmEmailController extends Controller try { $this->emailConfirmationService->sendConfirmation($user); } catch (Exception $e) { - $this->showErrorNotification( trans('auth.email_confirm_send_error')); + $this->showErrorNotification(trans('auth.email_confirm_send_error')); return redirect('/register/confirm'); } - $this->showSuccessNotification( trans('auth.email_confirm_resent')); + $this->showSuccessNotification(trans('auth.email_confirm_resent')); return redirect('/register/confirm'); } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 4a0a69ae4..a3c0433a5 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -53,7 +53,7 @@ class ForgotPasswordController extends Controller if ($response === Password::RESET_LINK_SENT) { $message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]); - $this->showSuccessNotification( $message); + $this->showSuccessNotification($message); return back()->with('status', trans($response)); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 540d2e679..4d98eca59 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -44,7 +44,7 @@ class ResetPasswordController extends Controller protected function sendResetResponse(Request $request, $response) { $message = trans('auth.reset_password_success'); - $this->showSuccessNotification( $message); + $this->showSuccessNotification($message); return redirect($this->redirectPath()) ->with('status', trans($response)); } diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 313faf5bc..c361b0a9b 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -77,7 +77,7 @@ class UserInviteController extends Controller $user->save(); auth()->login($user); - $this->showSuccessNotification( trans('auth.user_invite_success', ['appName' => setting('app-name')])); + $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')])); $this->inviteService->deleteByUser($user); return redirect('/'); @@ -96,7 +96,7 @@ class UserInviteController extends Controller } if ($exception instanceof UserTokenExpiredException) { - $this->showErrorNotification( trans('errors.invite_token_expired')); + $this->showErrorNotification(trans('errors.invite_token_expired')); return redirect('/password/email'); } diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index a9a24d2ff..e7d788d91 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -1,22 +1,14 @@ bookRepo = $bookRepo; - $this->userRepo = $userRepo; $this->entityContextManager = $entityContextManager; - $this->imageRepo = $imageRepo; parent::__construct(); } /** * Display a listing of the book. - * @return Response */ public function index() { @@ -58,10 +37,10 @@ class BookController extends Controller $sort = setting()->getForCurrentUser('books_sort', 'name'); $order = setting()->getForCurrentUser('books_sort_order', 'asc'); - $books = $this->bookRepo->getAllPaginated('book', 18, $sort, $order); - $recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed('book', 4, 0) : false; - $popular = $this->bookRepo->getPopular('book', 4, 0); - $new = $this->bookRepo->getRecentlyCreated('book', 4, 0); + $books = $this->bookRepo->getAllPaginated(18, $sort, $order); + $recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false; + $popular = $this->bookRepo->getPopular(4); + $new = $this->bookRepo->getRecentlyCreated(4); $this->entityContextManager->clearShelfContext(); @@ -79,19 +58,17 @@ class BookController extends Controller /** * Show the form for creating a new book. - * @param string $shelfSlug - * @return Response - * @throws NotFoundException */ public function create(string $shelfSlug = null) { + $this->checkPermission('book-create-all'); + $bookshelf = null; if ($shelfSlug !== null) { - $bookshelf = $this->bookRepo->getEntityBySlug('bookshelf', $shelfSlug); + $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail(); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } - $this->checkPermission('book-create-all'); $this->setPageTitle(trans('entities.books_create')); return view('books.create', [ 'bookshelf' => $bookshelf @@ -100,11 +77,6 @@ class BookController extends Controller /** * Store a newly created book in storage. - * - * @param Request $request - * @param string $shelfSlug - * @return Response - * @throws NotFoundException * @throws ImageUploadException * @throws ValidationException */ @@ -114,19 +86,17 @@ class BookController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', - 'image' => $this->imageRepo->getImageValidationRules(), + 'image' => $this->getImageValidationRules(), ]); $bookshelf = null; if ($shelfSlug !== null) { - /** @var Bookshelf $bookshelf */ - $bookshelf = $this->bookRepo->getEntityBySlug('bookshelf', $shelfSlug); + $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail(); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } - /** @var Book $book */ - $book = $this->bookRepo->createFromInput('book', $request->all()); - $this->bookUpdateActions($book, $request); + $book = $this->bookRepo->create($request->all()); + $this->bookRepo->updateCoverImage($book, $request->file('image', null)); Activity::add($book, 'book_create', $book->id); if ($bookshelf) { @@ -139,17 +109,11 @@ class BookController extends Controller /** * Display the specified book. - * @param Request $request - * @param string $slug - * @return Response - * @throws NotFoundException */ public function show(Request $request, string $slug) { $book = $this->bookRepo->getBySlug($slug); - $this->checkOwnablePermission('book-view', $book); - - $bookChildren = $this->bookRepo->getBookChildren($book); + $bookChildren = (new BookContents($book))->getTree(true); Views::add($book); if ($request->has('shelf')) { @@ -167,9 +131,6 @@ class BookController extends Controller /** * Show the form for editing the specified book. - * @param string $slug - * @return Response - * @throws NotFoundException */ public function edit(string $slug) { @@ -181,11 +142,7 @@ class BookController extends Controller /** * Update the specified book in storage. - * @param Request $request - * @param string $slug - * @return Response * @throws ImageUploadException - * @throws NotFoundException * @throws ValidationException * @throws Throwable */ @@ -196,22 +153,20 @@ class BookController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', - 'image' => $this->imageRepo->getImageValidationRules(), + 'image' => $this->getImageValidationRules(), ]); - $book = $this->bookRepo->updateFromInput($book, $request->all()); - $this->bookUpdateActions($book, $request); + $book = $this->bookRepo->update($book, $request->all()); + $resetCover = $request->has('image_reset'); + $this->bookRepo->updateCoverImage($book, $request->file('image', null), $resetCover); - Activity::add($book, 'book_update', $book->id); + Activity::add($book, 'book_update', $book->id); - return redirect($book->getUrl()); + return redirect($book->getUrl()); } /** - * Shows the page to confirm deletion - * @param string $bookSlug - * @return View - * @throws NotFoundException + * Shows the page to confirm deletion. */ public function showDelete(string $bookSlug) { @@ -222,115 +177,7 @@ class BookController extends Controller } /** - * Shows the view which allows pages to be re-ordered and sorted. - * @param string $bookSlug - * @return View - * @throws NotFoundException - */ - public function sort(string $bookSlug) - { - $book = $this->bookRepo->getBySlug($bookSlug); - $this->checkOwnablePermission('book-update', $book); - - $bookChildren = $this->bookRepo->getBookChildren($book, true); - - $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()])); - return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); - } - - /** - * Shows the sort box for a single book. - * Used via AJAX when loading in extra books to a sort. - * @param string $bookSlug - * @return Factory|View - * @throws NotFoundException - */ - public function sortItem(string $bookSlug) - { - $book = $this->bookRepo->getBySlug($bookSlug); - $bookChildren = $this->bookRepo->getBookChildren($book); - return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); - } - - /** - * Saves an array of sort mapping to pages and chapters. - * @param Request $request - * @param string $bookSlug - * @return RedirectResponse|Redirector - * @throws NotFoundException - */ - public function saveSort(Request $request, string $bookSlug) - { - $book = $this->bookRepo->getBySlug($bookSlug); - $this->checkOwnablePermission('book-update', $book); - - // Return if no map sent - if (!$request->filled('sort-tree')) { - return redirect($book->getUrl()); - } - - // Sort pages and chapters - $sortMap = collect(json_decode($request->get('sort-tree'))); - $bookIdsInvolved = collect([$book->id]); - - // Load models into map - $sortMap->each(function ($mapItem) use ($bookIdsInvolved) { - $mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter'); - $mapItem->model = $this->bookRepo->getById($mapItem->type, $mapItem->id); - // Store source and target books - $bookIdsInvolved->push(intval($mapItem->model->book_id)); - $bookIdsInvolved->push(intval($mapItem->book)); - }); - - // Get the books involved in the sort - $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); - $booksInvolved = $this->bookRepo->getManyById('book', $bookIdsInvolved, false, true); - - // Throw permission error if invalid ids or inaccessible books given. - if (count($bookIdsInvolved) !== count($booksInvolved)) { - $this->showPermissionError(); - } - - // Check permissions of involved books - $booksInvolved->each(function (Book $book) { - $this->checkOwnablePermission('book-update', $book); - }); - - // Perform the sort - $sortMap->each(function ($mapItem) { - $model = $mapItem->model; - - $priorityChanged = intval($model->priority) !== intval($mapItem->sort); - $bookChanged = intval($model->book_id) !== intval($mapItem->book); - $chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter; - - if ($bookChanged) { - $this->bookRepo->changeBook($model, $mapItem->book); - } - if ($chapterChanged) { - $model->chapter_id = intval($mapItem->parentChapter); - $model->save(); - } - if ($priorityChanged) { - $model->priority = intval($mapItem->sort); - $model->save(); - } - }); - - // Rebuild permissions and add activity for involved books. - $booksInvolved->each(function (Book $book) { - $book->rebuildPermissions(); - Activity::add($book, 'book_sort', $book->id); - }); - - return redirect($book->getUrl()); - } - - /** - * Remove the specified book from storage. - * @param string $bookSlug - * @return Response - * @throws NotFoundException + * Remove the specified book from the system. * @throws Throwable * @throws NotifyException */ @@ -338,72 +185,40 @@ class BookController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); - Activity::addMessage('book_delete', $book->name); - if ($book->cover) { - $this->imageRepo->destroyImage($book->cover); - } - $this->bookRepo->destroyBook($book); + Activity::addMessage('book_delete', $book->name); + $this->bookRepo->destroy($book); return redirect('/books'); } /** - * Show the Restrictions view. - * @param string $bookSlug - * @return Factory|View - * @throws NotFoundException + * Show the permissions view. */ public function showPermissions(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); - $roles = $this->userRepo->getRestrictableRoles(); + return view('books.permissions', [ 'book' => $book, - 'roles' => $roles ]); } /** * Set the restrictions for this book. - * @param Request $request - * @param string $bookSlug - * @return RedirectResponse|Redirector - * @throws NotFoundException * @throws Throwable */ public function permissions(Request $request, string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); - $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); + + $restricted = $request->get('restricted') === 'true'; + $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null; + $this->bookRepo->updatePermissions($book, $restricted, $permissions); + $this->showSuccessNotification(trans('entities.books_permissions_updated')); return redirect($book->getUrl()); } - - /** - * Common actions to run on book update. - * Handles updating the cover image. - * @param Book $book - * @param Request $request - * @throws ImageUploadException - */ - protected function bookUpdateActions(Book $book, Request $request) - { - // Update the cover image if in request - if ($request->has('image')) { - $this->imageRepo->destroyImage($book->cover); - $newImage = $request->file('image'); - $image = $this->imageRepo->saveNew($newImage, 'cover_book', $book->id, 512, 512, true); - $book->image_id = $image->id; - $book->save(); - } - - if ($request->has('image_reset')) { - $this->imageRepo->destroyImage($book->cover); - $book->image_id = 0; - $book->save(); - } - } } diff --git a/app/Http/Controllers/BookExportController.php b/app/Http/Controllers/BookExportController.php index ae3f56b81..cfa3d6a3a 100644 --- a/app/Http/Controllers/BookExportController.php +++ b/app/Http/Controllers/BookExportController.php @@ -4,25 +4,16 @@ namespace BookStack\Http\Controllers; use BookStack\Entities\ExportService; use BookStack\Entities\Repos\BookRepo; -use BookStack\Exceptions\NotFoundException; use Throwable; class BookExportController extends Controller { - /** - * @var BookRepo - */ - protected $bookRepo; - /** - * @var ExportService - */ + protected $bookRepo; protected $exportService; /** * BookExportController constructor. - * @param BookRepo $bookRepo - * @param ExportService $exportService */ public function __construct(BookRepo $bookRepo, ExportService $exportService) { @@ -33,9 +24,6 @@ class BookExportController extends Controller /** * Export a book as a PDF file. - * @param string $bookSlug - * @return mixed - * @throws NotFoundException * @throws Throwable */ public function pdf(string $bookSlug) @@ -47,9 +35,6 @@ class BookExportController extends Controller /** * Export a book as a contained HTML file. - * @param string $bookSlug - * @return mixed - * @throws NotFoundException * @throws Throwable */ public function html(string $bookSlug) @@ -61,9 +46,6 @@ class BookExportController extends Controller /** * Export a book as a plain text file. - * @param $bookSlug - * @return mixed - * @throws NotFoundException */ public function plainText(string $bookSlug) { diff --git a/app/Http/Controllers/BookSortController.php b/app/Http/Controllers/BookSortController.php new file mode 100644 index 000000000..f5fb6f255 --- /dev/null +++ b/app/Http/Controllers/BookSortController.php @@ -0,0 +1,82 @@ +bookRepo = $bookRepo; + parent::__construct(); + } + + /** + * Shows the view which allows pages to be re-ordered and sorted. + */ + public function show(string $bookSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $this->checkOwnablePermission('book-update', $book); + + $bookChildren = (new BookContents($book))->getTree(false); + + $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()])); + return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); + } + + /** + * Shows the sort box for a single book. + * Used via AJAX when loading in extra books to a sort. + */ + public function showItem(string $bookSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $bookChildren = (new BookContents($book))->getTree(); + return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); + } + + /** + * Sorts a book using a given mapping array. + */ + public function update(Request $request, string $bookSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $this->checkOwnablePermission('book-update', $book); + + // Return if no map sent + if (!$request->filled('sort-tree')) { + return redirect($book->getUrl()); + } + + $sortMap = collect(json_decode($request->get('sort-tree'))); + $bookContents = new BookContents($book); + $booksInvolved = collect(); + + try { + $booksInvolved = $bookContents->sortUsingMap($sortMap); + } catch (SortOperationException $exception) { + $this->showPermissionError(); + } + + // Rebuild permissions and add activity for involved books. + $booksInvolved->each(function (Book $book) { + Activity::add($book, 'book_sort', $book->id); + }); + + return redirect($book->getUrl()); + } +} diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index bef96dd01..57e67dc00 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -1,34 +1,30 @@ entityRepo = $entityRepo; - $this->userRepo = $userRepo; + $this->bookshelfRepo = $bookshelfRepo; $this->entityContextManager = $entityContextManager; $this->imageRepo = $imageRepo; parent::__construct(); @@ -36,7 +32,6 @@ class BookshelfController extends Controller /** * Display a listing of the book. - * @return Response */ public function index() { @@ -49,14 +44,10 @@ class BookshelfController extends Controller 'updated_at' => trans('common.sort_updated_at'), ]; - $shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $sort, $order); - foreach ($shelves as $shelf) { - $shelf->books = $this->entityRepo->getBookshelfChildren($shelf); - } - - $recents = $this->isSignedIn() ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false; - $popular = $this->entityRepo->getPopular('bookshelf', 4, 0); - $new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0); + $shelves = $this->bookshelfRepo->getAllPaginated(18, $sort, $order); + $recents = $this->isSignedIn() ? $this->bookshelfRepo->getRecentlyViewed(4) : false; + $popular = $this->bookshelfRepo->getPopular(4); + $new = $this->bookshelfRepo->getRecentlyCreated(4); $this->entityContextManager->clearShelfContext(); $this->setPageTitle(trans('entities.shelves')); @@ -74,21 +65,19 @@ class BookshelfController extends Controller /** * Show the form for creating a new bookshelf. - * @return Response */ public function create() { $this->checkPermission('bookshelf-create-all'); - $books = $this->entityRepo->getAll('book', false, 'update'); + $books = Book::hasPermission('update')->get(); $this->setPageTitle(trans('entities.shelves_create')); return view('shelves.create', ['books' => $books]); } /** * Store a newly created bookshelf in storage. - * @param Request $request - * @return Response - * @throws \BookStack\Exceptions\ImageUploadException + * @throws ValidationException + * @throws ImageUploadException */ public function store(Request $request) { @@ -96,80 +85,63 @@ class BookshelfController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', - 'image' => $this->imageRepo->getImageValidationRules(), + 'image' => $this->getImageValidationRules(), ]); - $shelf = $this->entityRepo->createFromInput('bookshelf', $request->all()); - $this->shelfUpdateActions($shelf, $request); + $bookIds = explode(',', $request->get('books', '')); + $shelf = $this->bookshelfRepo->create($request->all(), $bookIds); + $this->bookshelfRepo->updateCoverImage($shelf); Activity::add($shelf, 'bookshelf_create'); return redirect($shelf->getUrl()); } - /** - * Display the specified bookshelf. - * @param String $slug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException + * Display the bookshelf of the given slug. + * @throws NotFoundException */ public function show(string $slug) { - /** @var Bookshelf $shelf */ - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('book-view', $shelf); - $books = $this->entityRepo->getBookshelfChildren($shelf); Views::add($shelf); $this->entityContextManager->setShelfContext($shelf->id); $this->setPageTitle($shelf->getShortName()); - return view('shelves.show', [ 'shelf' => $shelf, - 'books' => $books, 'activity' => Activity::entityActivity($shelf, 20, 1) ]); } /** * Show the form for editing the specified bookshelf. - * @param $slug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException */ public function edit(string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); - $shelfBooks = $this->entityRepo->getBookshelfChildren($shelf); - $shelfBookIds = $shelfBooks->pluck('id'); - $books = $this->entityRepo->getAll('book', false, 'update'); - $books = $books->filter(function ($book) use ($shelfBookIds) { - return !$shelfBookIds->contains($book->id); - }); + $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); + $books = Book::hasPermission('update')->whereNotIn('id', $shelfBookIds)->get(); $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()])); return view('shelves.edit', [ 'shelf' => $shelf, 'books' => $books, - 'shelfBooks' => $shelfBooks, ]); } - /** * Update the specified bookshelf in storage. - * @param Request $request - * @param string $slug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException - * @throws \BookStack\Exceptions\ImageUploadException + * @throws ValidationException + * @throws ImageUploadException + * @throws NotFoundException */ public function update(Request $request, string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $this->validate($request, [ 'name' => 'required|string|max:255', @@ -177,24 +149,22 @@ class BookshelfController extends Controller 'image' => $this->imageRepo->getImageValidationRules(), ]); - $shelf = $this->entityRepo->updateFromInput($shelf, $request->all()); - $this->shelfUpdateActions($shelf, $request); - Activity::add($shelf, 'bookshelf_update'); + $bookIds = explode(',', $request->get('books', '')); + $shelf = $this->bookshelfRepo->update($shelf, $request->all(), $bookIds); + $resetCover = $request->has('image_reset'); + $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null), $resetCover); + Activity::add($shelf, 'bookshelf_update'); - return redirect($shelf->getUrl()); + return redirect($shelf->getUrl()); } - /** * Shows the page to confirm deletion - * @param $slug - * @return \Illuminate\View\View - * @throws \BookStack\Exceptions\NotFoundException */ public function showDelete(string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -203,101 +173,58 @@ class BookshelfController extends Controller /** * Remove the specified bookshelf from storage. - * @param string $slug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException - * @throws \Throwable + * @throws Exception */ public function destroy(string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); - Activity::addMessage('bookshelf_delete', $shelf->name); - if ($shelf->cover) { - $this->imageRepo->destroyImage($shelf->cover); - } - $this->entityRepo->destroyBookshelf($shelf); + Activity::addMessage('bookshelf_delete', $shelf->name); + $this->bookshelfRepo->destroy($shelf); return redirect('/shelves'); } /** * Show the permissions view. - * @param string $slug - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * @throws \BookStack\Exceptions\NotFoundException */ public function showPermissions(string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); - $roles = $this->userRepo->getRestrictableRoles(); return view('shelves.permissions', [ 'shelf' => $shelf, - 'roles' => $roles ]); } /** * Set the permissions for this bookshelf. - * @param Request $request - * @param string $slug - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \BookStack\Exceptions\NotFoundException - * @throws \Throwable */ public function permissions(Request $request, string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); - $this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf); - $this->showSuccessNotification( trans('entities.shelves_permissions_updated')); + $restricted = $request->get('restricted') === 'true'; + $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null; + $this->bookshelfRepo->updatePermissions($shelf, $restricted, $permissions); + + $this->showSuccessNotification(trans('entities.shelves_permissions_updated')); return redirect($shelf->getUrl()); } /** * Copy the permissions of a bookshelf to the child books. - * @param string $slug - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \BookStack\Exceptions\NotFoundException */ public function copyPermissions(string $slug) { - $shelf = $this->entityRepo->getEntityBySlug('bookshelf', $slug); + $shelf = $this->bookshelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); - $updateCount = $this->entityRepo->copyBookshelfPermissions($shelf); - $this->showSuccessNotification( trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); + $updateCount = $this->bookshelfRepo->copyDownPermissions($shelf); + $this->showSuccessNotification(trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); return redirect($shelf->getUrl()); } - - /** - * Common actions to run on bookshelf update. - * @param Bookshelf $shelf - * @param Request $request - * @throws \BookStack\Exceptions\ImageUploadException - */ - protected function shelfUpdateActions(Bookshelf $shelf, Request $request) - { - // Update the books that the shelf references - $this->entityRepo->updateShelfBooks($shelf, $request->get('books', '')); - - // Update the cover image if in request - if ($request->has('image')) { - $newImage = $request->file('image'); - $this->imageRepo->destroyImage($shelf->cover); - $image = $this->imageRepo->saveNew($newImage, 'cover_shelf', $shelf->id, 512, 512, true); - $shelf->image_id = $image->id; - $shelf->save(); - } - - if ($request->has('image_reset')) { - $this->imageRepo->destroyImage($shelf->cover); - $shelf->image_id = 0; - $shelf->save(); - } - } } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index f728d1313..135597910 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -1,50 +1,45 @@ entityRepo = $entityRepo; - $this->userRepo = $userRepo; + $this->chapterRepo = $chapterRepo; parent::__construct(); } /** * Show the form for creating a new chapter. - * @param $bookSlug - * @return Response */ - public function create($bookSlug) + public function create(string $bookSlug) { - $book = $this->entityRepo->getEntityBySlug('book', $bookSlug); + $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); $this->checkOwnablePermission('chapter-create', $book); + $this->setPageTitle(trans('entities.chapters_create')); return view('chapters.create', ['book' => $book, 'current' => $book]); } /** * Store a newly created chapter in storage. - * @param Request $request - * @param string $bookSlug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException - * @throws \Illuminate\Validation\ValidationException + * @throws ValidationException */ public function store(Request $request, string $bookSlug) { @@ -52,30 +47,28 @@ class ChapterController extends Controller 'name' => 'required|string|max:255' ]); - $book = $this->entityRepo->getEntityBySlug('book', $bookSlug); + $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); $this->checkOwnablePermission('chapter-create', $book); - $input = $request->all(); - $input['priority'] = $this->entityRepo->getNewBookPriority($book); - $chapter = $this->entityRepo->createFromInput('chapter', $input, $book); + $chapter = $this->chapterRepo->create($request->all(), $book); Activity::add($chapter, 'chapter_create', $book->id); + return redirect($chapter->getUrl()); } /** * Display the specified chapter. - * @param $bookSlug - * @param $chapterSlug - * @return Response */ - public function show($bookSlug, $chapterSlug) + public function show(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); - $sidebarTree = $this->entityRepo->getBookChildren($chapter->book); + + $sidebarTree = (new BookContents($chapter->book))->getTree(); + $pages = $chapter->getVisiblePages(); Views::add($chapter); + $this->setPageTitle($chapter->getShortName()); - $pages = $this->entityRepo->getChapterChildren($chapter); return view('chapters.show', [ 'book' => $chapter->book, 'chapter' => $chapter, @@ -87,79 +80,71 @@ class ChapterController extends Controller /** * Show the form for editing the specified chapter. - * @param $bookSlug - * @param $chapterSlug - * @return Response */ - public function edit($bookSlug, $chapterSlug) + public function edit(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); + $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); return view('chapters.edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]); } /** * Update the specified chapter in storage. - * @param Request $request - * @param string $bookSlug - * @param string $chapterSlug - * @return Response - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ public function update(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); - $this->entityRepo->updateFromInput($chapter, $request->all()); + $this->chapterRepo->update($chapter, $request->all()); Activity::add($chapter, 'chapter_update', $chapter->book->id); + return redirect($chapter->getUrl()); } /** * Shows the page to confirm deletion of this chapter. - * @param $bookSlug - * @param $chapterSlug - * @return \Illuminate\View\View + * @throws NotFoundException */ - public function showDelete($bookSlug, $chapterSlug) + public function showDelete(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); + $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); return view('chapters.delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]); } /** * Remove the specified chapter from storage. - * @param $bookSlug - * @param $chapterSlug - * @return Response + * @throws NotFoundException + * @throws Throwable */ - public function destroy($bookSlug, $chapterSlug) + public function destroy(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); - $book = $chapter->book; + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); - Activity::addMessage('chapter_delete', $chapter->name, $book->id); - $this->entityRepo->destroyChapter($chapter); - return redirect($book->getUrl()); + + Activity::addMessage('chapter_delete', $chapter->name, $chapter->book->id); + $this->chapterRepo->destroy($chapter); + + return redirect($chapter->book->getUrl()); } /** * Show the page for moving a chapter. - * @param $bookSlug - * @param $chapterSlug - * @return mixed - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ - public function showMove($bookSlug, $chapterSlug) + public function showMove(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); + return view('chapters.move', [ 'chapter' => $chapter, 'book' => $chapter->book @@ -168,15 +153,11 @@ class ChapterController extends Controller /** * Perform the move action for a chapter. - * @param Request $request - * @param string $bookSlug - * @param string $chapterSlug - * @return mixed - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ public function move(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -185,63 +166,47 @@ class ChapterController extends Controller return redirect($chapter->getUrl()); } - $stringExploded = explode(':', $entitySelection); - $entityType = $stringExploded[0]; - $entityId = intval($stringExploded[1]); - - $parent = false; - - if ($entityType == 'book') { - $parent = $this->entityRepo->getById('book', $entityId); - } - - if ($parent === false || $parent === null) { - $this->showErrorNotification( trans('errors.selected_book_not_found')); + try { + $newBook = $this->chapterRepo->move($chapter, $entitySelection); + } catch (MoveOperationException $exception) { + $this->showErrorNotification(trans('errors.selected_book_not_found')); return redirect()->back(); } - $this->entityRepo->changeBook($chapter, $parent->id); - $chapter->rebuildPermissions(); - - Activity::add($chapter, 'chapter_move', $chapter->book->id); - $this->showSuccessNotification( trans('entities.chapter_move_success', ['bookName' => $parent->name])); + Activity::add($chapter, 'chapter_move', $newBook->id); + $this->showSuccessNotification(trans('entities.chapter_move_success', ['bookName' => $newBook->name])); return redirect($chapter->getUrl()); } /** * Show the Restrictions view. - * @param $bookSlug - * @param $chapterSlug - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * @throws \BookStack\Exceptions\NotFoundException + * @throws NotFoundException */ - public function showPermissions($bookSlug, $chapterSlug) + public function showPermissions(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); - $roles = $this->userRepo->getRestrictableRoles(); + return view('chapters.permissions', [ 'chapter' => $chapter, - 'roles' => $roles ]); } /** * Set the restrictions for this chapter. - * @param Request $request - * @param string $bookSlug - * @param string $chapterSlug - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \BookStack\Exceptions\NotFoundException - * @throws \Throwable + * @throws NotFoundException */ public function permissions(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); - $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter); - $this->showSuccessNotification( trans('entities.chapters_permissions_success')); + + $restricted = $request->get('restricted') === 'true'; + $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null; + $this->chapterRepo->updatePermissions($chapter, $restricted, $permissions); + + $this->showSuccessNotification(trans('entities.chapters_permissions_success')); return redirect($chapter->getUrl()); } } diff --git a/app/Http/Controllers/ChapterExportController.php b/app/Http/Controllers/ChapterExportController.php index 15d67d539..0c86f8548 100644 --- a/app/Http/Controllers/ChapterExportController.php +++ b/app/Http/Controllers/ChapterExportController.php @@ -1,77 +1,57 @@ -entityRepo = $entityRepo; + $this->chapterRepo = $chapterRepo; $this->exportService = $exportService; parent::__construct(); } /** - * Exports a chapter to pdf . - * @param string $bookSlug - * @param string $chapterSlug - * @return Response + * Exports a chapter to pdf. * @throws NotFoundException * @throws Throwable */ public function pdf(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $pdfContent = $this->exportService->chapterToPdf($chapter); return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf'); } /** * Export a chapter to a self-contained HTML file. - * @param string $bookSlug - * @param string $chapterSlug - * @return Response * @throws NotFoundException * @throws Throwable */ public function html(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $containedHtml = $this->exportService->chapterToContainedHtml($chapter); return $this->downloadResponse($containedHtml, $chapterSlug . '.html'); } /** * Export a chapter to a simple plaintext .txt file. - * @param string $bookSlug - * @param string $chapterSlug - * @return Response * @throws NotFoundException */ public function plainText(string $bookSlug, string $chapterSlug) { - $chapter = $this->entityRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $chapterText = $this->exportService->chapterToPlainText($chapter); return $this->downloadResponse($chapterText, $chapterSlug . '.txt'); } diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 860b50762..068358d72 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -2,44 +2,36 @@ use Activity; use BookStack\Actions\CommentRepo; -use BookStack\Entities\Repos\EntityRepo; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use BookStack\Entities\Page; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; class CommentController extends Controller { - protected $entityRepo; protected $commentRepo; /** * CommentController constructor. - * @param \BookStack\Entities\Repos\EntityRepo $entityRepo - * @param \BookStack\Actions\CommentRepo $commentRepo */ - public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo) + public function __construct(CommentRepo $commentRepo) { - $this->entityRepo = $entityRepo; $this->commentRepo = $commentRepo; parent::__construct(); } /** * Save a new comment for a Page - * @param Request $request - * @param integer $pageId - * @param null|integer $commentId - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response + * @throws ValidationException */ - public function savePageComment(Request $request, $pageId, $commentId = null) + public function savePageComment(Request $request, int $pageId, int $commentId = null) { $this->validate($request, [ 'text' => 'required|string', 'html' => 'required|string', ]); - try { - $page = $this->entityRepo->getById('page', $pageId, true); - } catch (ModelNotFoundException $e) { + $page = Page::visible()->find($pageId); + if ($page === null) { return response('Not found', 404); } @@ -59,11 +51,9 @@ class CommentController extends Controller /** * Update an existing comment. - * @param Request $request - * @param integer $commentId - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws ValidationException */ - public function update(Request $request, $commentId) + public function update(Request $request, int $commentId) { $this->validate($request, [ 'text' => 'required|string', @@ -80,13 +70,12 @@ class CommentController extends Controller /** * Delete a comment from the system. - * @param integer $id - * @return \Illuminate\Http\JsonResponse */ - public function destroy($id) + public function destroy(int $id) { $comment = $this->commentRepo->getById($id); $this->checkOwnablePermission('comment-delete', $comment); + $this->commentRepo->delete($comment); return response()->json(['message' => trans('entities.comment_deleted')]); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 034c852de..b9576f2fe 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -59,7 +59,7 @@ abstract class Controller extends BaseController $response = response()->json(['error' => trans('errors.permissionJson')], 403); } else { $response = redirect('/'); - $this->showErrorNotification( trans('errors.permission')); + $this->showErrorNotification(trans('errors.permission')); } throw new HttpResponseException($response); @@ -129,7 +129,7 @@ abstract class Controller extends BaseController */ protected function jsonError($messageText = "", $statusCode = 500) { - return response()->json(['message' => $messageText], $statusCode); + return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode); } /** @@ -189,4 +189,12 @@ abstract class Controller extends BaseController { session()->flash('error', $message); } + + /** + * Get the validation rules for image files. + */ + protected function getImageValidationRules(): string + { + return 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff'; + } } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a37371d3a..260952fd1 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -1,23 +1,16 @@ entityRepo = $entityRepo; - parent::__construct(); - } /** * Display the homepage. @@ -26,10 +19,20 @@ class HomeController extends Controller public function index() { $activity = Activity::latest(10); - $draftPages = $this->isSignedIn() ? $this->entityRepo->getUserDraftPages(6) : []; + $draftPages = []; + + if ($this->isSignedIn()) { + $draftPages = Page::visible()->where('draft', '=', true) + ->where('created_by', '=', user()->id) + ->orderBy('updated_at', 'desc')->take(6)->get(); + } + $recentFactor = count($draftPages) > 0 ? 0.5 : 1; - $recents = $this->isSignedIn() ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor); - $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12); + $recents = $this->isSignedIn() ? + Views::getUserRecentlyViewed(12*$recentFactor, 0) + : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); + $recentlyUpdatedPages = Page::visible()->where('draft', false) + ->orderBy('updated_at', 'desc')->take(12)->get(); $homepageOptions = ['default', 'books', 'bookshelves', 'page']; $homepageOption = setting('app-homepage-type', 'default'); @@ -66,16 +69,18 @@ class HomeController extends Controller } if ($homepageOption === 'bookshelves') { - $shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $commonData['sort'], $commonData['order']); + $shelfRepo = app(BookshelfRepo::class); + $shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['sort'], $commonData['order']); foreach ($shelves as $shelf) { - $shelf->books = $this->entityRepo->getBookshelfChildren($shelf); + $shelf->books = $shelf->visibleBooks; } $data = array_merge($commonData, ['shelves' => $shelves]); return view('common.home-shelves', $data); } if ($homepageOption === 'books') { - $books = $this->entityRepo->getAllPaginated('book', 18, $commonData['sort'], $commonData['order']); + $bookRepo = app(BookRepo::class); + $books = $bookRepo->getAllPaginated(18, $commonData['sort'], $commonData['order']); $data = array_merge($commonData, ['books' => $books]); return view('common.home-book', $data); } @@ -83,8 +88,9 @@ class HomeController extends Controller if ($homepageOption === 'page') { $homepageSetting = setting('app-homepage', '0:'); $id = intval(explode(':', $homepageSetting)[0]); - $customHomepage = $this->entityRepo->getById('page', $id, false, true); - $this->entityRepo->renderPage($customHomepage, true); + $customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id); + $pageContent = new PageContent($customHomepage); + $customHomepage->html = $pageContent->render(true); return view('common.home-custom', array_merge($commonData, ['customHomepage' => $customHomepage])); } diff --git a/app/Http/Controllers/Images/ImageController.php b/app/Http/Controllers/Images/ImageController.php index 79a23df27..9c67704dd 100644 --- a/app/Http/Controllers/Images/ImageController.php +++ b/app/Http/Controllers/Images/ImageController.php @@ -1,6 +1,6 @@ imageRepo->getById($id); $this->checkImagePermission($image); - $pageSearch = $entityRepo->searchForImage($image->url); - return response()->json($pageSearch); + + $pages = Page::visible()->where('html', 'like', '%' . $image->url . '%')->get(['id', 'name', 'slug', 'book_id']); + foreach ($pages as $page) { + $page->url = $page->getUrl(); + $page->html = ''; + $page->text = ''; + } + $result = count($pages) > 0 ? $pages : false; + + return response()->json($result); } /** diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 736fcf4f6..630f888ed 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -1,18 +1,17 @@ pageRepo = $pageRepo; - $this->userRepo = $userRepo; parent::__construct(); } /** * Show the form for creating a new page. - * @param string $bookSlug - * @param string $chapterSlug - * @return Response - * @internal param bool $pageSlug - * @throws NotFoundException + * @throws Throwable */ - public function create($bookSlug, $chapterSlug = null) + public function create(string $bookSlug, string $chapterSlug = null) { - if ($chapterSlug !== null) { - $chapter = $this->pageRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); - $book = $chapter->book; - } else { - $chapter = null; - $book = $this->pageRepo->getEntityBySlug('book', $bookSlug); - } - - $parent = $chapter ? $chapter : $book; + $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('page-create', $parent); // Redirect to draft edit screen if signed in if ($this->isSignedIn()) { - $draft = $this->pageRepo->getDraftPage($book, $chapter); + $draft = $this->pageRepo->getNewDraftPage($parent); return redirect($draft->getUrl()); } @@ -68,51 +51,38 @@ class PageController extends Controller /** * Create a new page as a guest user. - * @param Request $request - * @param string $bookSlug - * @param string|null $chapterSlug - * @return mixed - * @throws NotFoundException + * @throws ValidationException */ - public function createAsGuest(Request $request, $bookSlug, $chapterSlug = null) + public function createAsGuest(Request $request, string $bookSlug, string $chapterSlug = null) { $this->validate($request, [ 'name' => 'required|string|max:255' ]); - if ($chapterSlug !== null) { - $chapter = $this->pageRepo->getEntityBySlug('chapter', $chapterSlug, $bookSlug); - $book = $chapter->book; - } else { - $chapter = null; - $book = $this->pageRepo->getEntityBySlug('book', $bookSlug); - } - - $parent = $chapter ? $chapter : $book; + $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('page-create', $parent); - $page = $this->pageRepo->getDraftPage($book, $chapter); - $this->pageRepo->publishPageDraft($page, [ + $page = $this->pageRepo->getNewDraftPage($parent); + $this->pageRepo->publishDraft($page, [ 'name' => $request->get('name'), 'html' => '' ]); + return redirect($page->getUrl('/edit')); } /** * Show form to continue editing a draft page. - * @param string $bookSlug - * @param int $pageId - * @return Factory|View + * @throws NotFoundException */ - public function editDraft($bookSlug, $pageId) + public function editDraft(string $bookSlug, int $pageId) { - $draft = $this->pageRepo->getById('page', $pageId, true); - $this->checkOwnablePermission('page-create', $draft->parent); + $draft = $this->pageRepo->getById($pageId); + $this->checkOwnablePermission('page-create', $draft->parent()); $this->setPageTitle(trans('entities.pages_edit_draft')); $draftsEnabled = $this->isSignedIn(); - $templates = $this->pageRepo->getPageTemplates(10); + $templates = $this->pageRepo->getTemplates(10); return view('pages.edit', [ 'page' => $draft, @@ -125,63 +95,50 @@ class PageController extends Controller /** * Store a new page by changing a draft into a page. - * @param Request $request - * @param string $bookSlug - * @param int $pageId - * @return Response + * @throws NotFoundException + * @throws ValidationException */ - public function store(Request $request, $bookSlug, $pageId) + public function store(Request $request, string $bookSlug, int $pageId) { $this->validate($request, [ 'name' => 'required|string|max:255' ]); + $draftPage = $this->pageRepo->getById($pageId); + $this->checkOwnablePermission('page-create', $draftPage->parent()); - $input = $request->all(); - $draftPage = $this->pageRepo->getById('page', $pageId, true); - $book = $draftPage->book; + $page = $this->pageRepo->publishDraft($draftPage, $request->all()); + Activity::add($page, 'page_create', $draftPage->book->id); - $parent = $draftPage->parent; - $this->checkOwnablePermission('page-create', $parent); - - if ($parent->isA('chapter')) { - $input['priority'] = $this->pageRepo->getNewChapterPriority($parent); - } else { - $input['priority'] = $this->pageRepo->getNewBookPriority($parent); - } - - $page = $this->pageRepo->publishPageDraft($draftPage, $input); - - Activity::add($page, 'page_create', $book->id); return redirect($page->getUrl()); } /** * Display the specified page. * If the page is not found via the slug the revisions are searched for a match. - * @param string $bookSlug - * @param string $pageSlug - * @return Response * @throws NotFoundException */ - public function show($bookSlug, $pageSlug) + public function show(string $bookSlug, string $pageSlug) { try { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); } catch (NotFoundException $e) { - $page = $this->pageRepo->getPageByOldSlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug); + if ($page === null) { throw $e; } + return redirect($page->getUrl()); } $this->checkOwnablePermission('page-view', $page); - $page->html = $this->pageRepo->renderPage($page); - $sidebarTree = $this->pageRepo->getBookChildren($page->book); - $pageNav = $this->pageRepo->getPageNav($page->html); + $pageContent = (new PageContent($page)); + $page->html = $pageContent->render(); + $sidebarTree = (new BookContents($page->book))->getTree(); + $pageNav = $pageContent->getNavigation($page->html); - // check if the comment's are enabled + // Check if page comments are enabled $commentsEnabled = !setting('app-disable-comments'); if ($commentsEnabled) { $page->load(['comments.createdBy']); @@ -190,7 +147,8 @@ class PageController extends Controller Views::add($page); $this->setPageTitle($page->getShortName()); return view('pages.show', [ - 'page' => $page,'book' => $page->book, + 'page' => $page, + 'book' => $page->book, 'current' => $page, 'sidebarTree' => $sidebarTree, 'commentsEnabled' => $commentsEnabled, @@ -200,52 +158,47 @@ class PageController extends Controller /** * Get page from an ajax request. - * @param int $pageId - * @return JsonResponse + * @throws NotFoundException */ - public function getPageAjax($pageId) + public function getPageAjax(int $pageId) { - $page = $this->pageRepo->getById('page', $pageId); + $page = $this->pageRepo->getById($pageId); return response()->json($page); } /** * Show the form for editing the specified page. - * @param string $bookSlug - * @param string $pageSlug - * @return Response * @throws NotFoundException */ - public function edit($bookSlug, $pageSlug) + public function edit(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); - $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()])); + $page->isDraft = false; + $editActivity = new PageEditActivity($page); // Check for active editing $warnings = []; - if ($this->pageRepo->isPageEditingActive($page, 60)) { - $warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60); + if ($editActivity->hasActiveEditing()) { + $warnings[] = $editActivity->activeEditingMessage(); } // Check for a current draft version for this user - $userPageDraft = $this->pageRepo->getUserPageDraft($page, user()->id); - if ($userPageDraft !== null) { - $page->name = $userPageDraft->name; - $page->html = $userPageDraft->html; - $page->markdown = $userPageDraft->markdown; + $userDraft = $this->pageRepo->getUserDraft($page); + if ($userDraft !== null) { + $page->forceFill($userDraft->only(['name', 'html', 'markdown'])); $page->isDraft = true; - $warnings [] = $this->pageRepo->getUserPageDraftMessage($userPageDraft); + $warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft); } if (count($warnings) > 0) { - $this->showWarningNotification( implode("\n", $warnings)); + $this->showWarningNotification(implode("\n", $warnings)); } + $templates = $this->pageRepo->getTemplates(10); $draftsEnabled = $this->isSignedIn(); - $templates = $this->pageRepo->getPageTemplates(10); - + $this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()])); return view('pages.edit', [ 'page' => $page, 'book' => $page->book, @@ -257,39 +210,34 @@ class PageController extends Controller /** * Update the specified page in storage. - * @param Request $request - * @param string $bookSlug - * @param string $pageSlug - * @return Response + * @throws ValidationException + * @throws NotFoundException */ - public function update(Request $request, $bookSlug, $pageSlug) + public function update(Request $request, string $bookSlug, string $pageSlug) { $this->validate($request, [ 'name' => 'required|string|max:255' ]); - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); - $this->pageRepo->updatePage($page, $page->book->id, $request->all()); + + $this->pageRepo->update($page, $request->all()); Activity::add($page, 'page_update', $page->book->id); + return redirect($page->getUrl()); } /** * Save a draft update as a revision. - * @param Request $request - * @param int $pageId - * @return JsonResponse + * @throws NotFoundException */ - public function saveDraft(Request $request, $pageId) + public function saveDraft(Request $request, int $pageId) { - $page = $this->pageRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); if (!$this->isSignedIn()) { - return response()->json([ - 'status' => 'error', - 'message' => trans('errors.guests_cannot_save_drafts'), - ], 500); + return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500); } $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown'])); @@ -303,211 +251,98 @@ class PageController extends Controller } /** - * Redirect from a special link url which - * uses the page id rather than the name. - * @param int $pageId - * @return RedirectResponse|Redirector + * Redirect from a special link url which uses the page id rather than the name. + * @throws NotFoundException */ - public function redirectFromLink($pageId) + public function redirectFromLink(int $pageId) { - $page = $this->pageRepo->getById('page', $pageId); + $page = $this->pageRepo->getById($pageId); return redirect($page->getUrl()); } /** * Show the deletion page for the specified page. - * @param string $bookSlug - * @param string $pageSlug - * @return View + * @throws NotFoundException */ - public function showDelete($bookSlug, $pageSlug) + public function showDelete(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()])); - return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]); + return view('pages.delete', [ + 'book' => $page->book, + 'page' => $page, + 'current' => $page + ]); } - /** * Show the deletion page for the specified page. - * @param string $bookSlug - * @param int $pageId - * @return View * @throws NotFoundException */ - public function showDeleteDraft($bookSlug, $pageId) + public function showDeleteDraft(string $bookSlug, int $pageId) { - $page = $this->pageRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()])); - return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]); + return view('pages.delete', [ + 'book' => $page->book, + 'page' => $page, + 'current' => $page + ]); } /** * Remove the specified page from storage. - * @param string $bookSlug - * @param string $pageSlug - * @return Response - * @internal param int $id + * @throws NotFoundException + * @throws Throwable + * @throws NotifyException */ - public function destroy($bookSlug, $pageSlug) + public function destroy(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $book = $page->book; + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); - $this->pageRepo->destroyPage($page); + $book = $page->book; + $this->pageRepo->destroy($page); Activity::addMessage('page_delete', $page->name, $book->id); - $this->showSuccessNotification( trans('entities.pages_delete_success')); + + $this->showSuccessNotification(trans('entities.pages_delete_success')); return redirect($book->getUrl()); } /** * Remove the specified draft page from storage. - * @param string $bookSlug - * @param int $pageId - * @return Response * @throws NotFoundException + * @throws NotifyException + * @throws Throwable */ - public function destroyDraft($bookSlug, $pageId) + public function destroyDraft(string $bookSlug, int $pageId) { - $page = $this->pageRepo->getById('page', $pageId, true); + $page = $this->pageRepo->getById($pageId); $book = $page->book; + $chapter = $page->chapter; $this->checkOwnablePermission('page-update', $page); - $this->showSuccessNotification( trans('entities.pages_delete_draft_success')); - $this->pageRepo->destroyPage($page); + + $this->pageRepo->destroy($page); + + $this->showSuccessNotification(trans('entities.pages_delete_draft_success')); + + if ($chapter && userCan('view', $chapter)) { + return redirect($chapter->getUrl()); + } return redirect($book->getUrl()); } /** - * Shows the last revisions for this page. - * @param string $bookSlug - * @param string $pageSlug - * @return View - * @throws NotFoundException - */ - public function showRevisions($bookSlug, $pageSlug) - { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()])); - return view('pages.revisions', ['page' => $page, 'current' => $page]); - } - - /** - * Shows a preview of a single revision - * @param string $bookSlug - * @param string $pageSlug - * @param int $revisionId - * @return View - */ - public function showRevision($bookSlug, $pageSlug, $revisionId) - { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $revision = $page->revisions()->where('id', '=', $revisionId)->first(); - if ($revision === null) { - abort(404); - } - - $page->fill($revision->toArray()); - $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()])); - - return view('pages.revision', [ - 'page' => $page, - 'book' => $page->book, - 'diff' => null, - 'revision' => $revision - ]); - } - - /** - * Shows the changes of a single revision - * @param string $bookSlug - * @param string $pageSlug - * @param int $revisionId - * @return View - */ - public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) - { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $revision = $page->revisions()->where('id', '=', $revisionId)->first(); - if ($revision === null) { - abort(404); - } - - $prev = $revision->getPrevious(); - $prevContent = ($prev === null) ? '' : $prev->html; - $diff = (new Htmldiff)->diff($prevContent, $revision->html); - - $page->fill($revision->toArray()); - $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()])); - - return view('pages.revision', [ - 'page' => $page, - 'book' => $page->book, - 'diff' => $diff, - 'revision' => $revision - ]); - } - - /** - * Restores a page using the content of the specified revision. - * @param string $bookSlug - * @param string $pageSlug - * @param int $revisionId - * @return RedirectResponse|Redirector - */ - public function restoreRevision($bookSlug, $pageSlug, $revisionId) - { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $this->checkOwnablePermission('page-update', $page); - $page = $this->pageRepo->restorePageRevision($page, $page->book, $revisionId); - Activity::add($page, 'page_restore', $page->book->id); - return redirect($page->getUrl()); - } - - - /** - * Deletes a revision using the id of the specified revision. - * @param string $bookSlug - * @param string $pageSlug - * @param int $revId - * @return RedirectResponse|Redirector - *@throws BadRequestException - * @throws NotFoundException - */ - public function destroyRevision($bookSlug, $pageSlug, $revId) - { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $this->checkOwnablePermission('page-delete', $page); - - $revision = $page->revisions()->where('id', '=', $revId)->first(); - if ($revision === null) { - throw new NotFoundException("Revision #{$revId} not found"); - } - - // Get the current revision for the page - $currentRevision = $page->getCurrentRevision(); - - // Check if its the latest revision, cannot delete latest revision. - if (intval($currentRevision->id) === intval($revId)) { - $this->showErrorNotification( trans('entities.revision_cannot_delete_latest')); - return response()->view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400); - } - - $revision->delete(); - $this->showSuccessNotification( trans('entities.revision_delete_success')); - return redirect($page->getUrl('/revisions')); - } - - /** - * Show a listing of recently created pages - * @return Factory|View + * Show a listing of recently created pages. */ public function showRecentlyUpdated() { - // TODO - Still exist? - $pages = $this->pageRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(url('/pages/recently-updated')); + $pages = Page::visible()->orderBy('updated_at', 'desc') + ->paginate(20) + ->setPath(url('/pages/recently-updated')); + return view('pages.detailed-listing', [ 'title' => trans('entities.recently_updated_pages'), 'pages' => $pages @@ -516,14 +351,11 @@ class PageController extends Controller /** * Show the view to choose a new parent to move a page into. - * @param string $bookSlug - * @param string $pageSlug - * @return mixed * @throws NotFoundException */ - public function showMove($bookSlug, $pageSlug) + public function showMove(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); return view('pages.move', [ @@ -533,17 +365,13 @@ class PageController extends Controller } /** - * Does the action of moving the location of a page - * @param Request $request - * @param string $bookSlug - * @param string $pageSlug - * @return mixed + * Does the action of moving the location of a page. * @throws NotFoundException * @throws Throwable */ public function move(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); @@ -552,37 +380,29 @@ class PageController extends Controller return redirect($page->getUrl()); } - $stringExploded = explode(':', $entitySelection); - $entityType = $stringExploded[0]; - $entityId = intval($stringExploded[1]); - - try { - $parent = $this->pageRepo->getById($entityType, $entityId); - } catch (Exception $e) { - session()->flash(trans('entities.selected_book_chapter_not_found')); + $parent = $this->pageRepo->move($page, $entitySelection); + } catch (Exception $exception) { + if ($exception instanceof PermissionsException) { + $this->showPermissionError(); + } + + $this->showErrorNotification(trans('errors.selected_book_chapter_not_found')); return redirect()->back(); } - $this->checkOwnablePermission('page-create', $parent); - - $this->pageRepo->changePageParent($page, $parent); Activity::add($page, 'page_move', $page->book->id); - $this->showSuccessNotification( trans('entities.pages_move_success', ['parentName' => $parent->name])); - + $this->showSuccessNotification(trans('entities.pages_move_success', ['parentName' => $parent->name])); return redirect($page->getUrl()); } /** * Show the view to copy a page. - * @param string $bookSlug - * @param string $pageSlug - * @return mixed * @throws NotFoundException */ - public function showCopy($bookSlug, $pageSlug) + public function showCopy(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-view', $page); session()->flashInput(['name' => $page->name]); return view('pages.copy', [ @@ -591,79 +411,65 @@ class PageController extends Controller ]); } + /** * Create a copy of a page within the requested target destination. - * @param Request $request - * @param string $bookSlug - * @param string $pageSlug - * @return mixed * @throws NotFoundException * @throws Throwable */ public function copy(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-view', $page); - $entitySelection = $request->get('entity_selection', null); - if ($entitySelection === null || $entitySelection === '') { - $parent = $page->chapter ? $page->chapter : $page->book; - } else { - $stringExploded = explode(':', $entitySelection); - $entityType = $stringExploded[0]; - $entityId = intval($stringExploded[1]); + $entitySelection = $request->get('entity_selection', null) ?? null; + $newName = $request->get('name', null); - try { - $parent = $this->pageRepo->getById($entityType, $entityId); - } catch (Exception $e) { - $this->showErrorNotification(trans('entities.selected_book_chapter_not_found')); - return redirect()->back(); + try { + $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName); + } catch (Exception $exception) { + if ($exception instanceof PermissionsException) { + $this->showPermissionError(); } + + $this->showErrorNotification(trans('errors.selected_book_chapter_not_found')); + return redirect()->back(); } - $this->checkOwnablePermission('page-create', $parent); - - $pageCopy = $this->pageRepo->copyPage($page, $parent, $request->get('name', '')); - Activity::add($pageCopy, 'page_create', $pageCopy->book->id); - $this->showSuccessNotification( trans('entities.pages_copy_success')); + $this->showSuccessNotification(trans('entities.pages_copy_success')); return redirect($pageCopy->getUrl()); } /** * Show the Permissions view. - * @param string $bookSlug - * @param string $pageSlug - * @return Factory|View * @throws NotFoundException */ - public function showPermissions($bookSlug, $pageSlug) + public function showPermissions(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('restrictions-manage', $page); - $roles = $this->userRepo->getRestrictableRoles(); return view('pages.permissions', [ 'page' => $page, - 'roles' => $roles ]); } /** * Set the permissions for this page. - * @param string $bookSlug - * @param string $pageSlug - * @param Request $request - * @return RedirectResponse|Redirector * @throws NotFoundException * @throws Throwable */ public function permissions(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('restrictions-manage', $page); - $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); - $this->showSuccessNotification( trans('entities.pages_permissions_success')); + + $restricted = $request->get('restricted') === 'true'; + $permissions = $request->filled('restrictions') ? collect($request->get('restrictions')) : null; + $this->pageRepo->updatePermissions($page, $restricted, $permissions); + + $this->showSuccessNotification(trans('entities.pages_permissions_success')); return redirect($page->getUrl()); } } diff --git a/app/Http/Controllers/PageExportController.php b/app/Http/Controllers/PageExportController.php index addcc5513..3b02ea224 100644 --- a/app/Http/Controllers/PageExportController.php +++ b/app/Http/Controllers/PageExportController.php @@ -3,21 +3,15 @@ namespace BookStack\Http\Controllers; use BookStack\Entities\ExportService; +use BookStack\Entities\Managers\PageContent; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\NotFoundException; -use Illuminate\Http\Response; use Throwable; class PageExportController extends Controller { - /** - * @var PageRepo - */ - protected $pageRepo; - /** - * @var ExportService - */ + protected $pageRepo; protected $exportService; /** @@ -35,46 +29,37 @@ class PageExportController extends Controller /** * Exports a page to a PDF. * https://github.com/barryvdh/laravel-dompdf - * @param string $bookSlug - * @param string $pageSlug - * @return Response * @throws NotFoundException * @throws Throwable */ public function pdf(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $page->html = $this->pageRepo->renderPage($page); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page->html = (new PageContent($page))->render(); $pdfContent = $this->exportService->pageToPdf($page); return $this->downloadResponse($pdfContent, $pageSlug . '.pdf'); } /** * Export a page to a self-contained HTML file. - * @param string $bookSlug - * @param string $pageSlug - * @return Response * @throws NotFoundException * @throws Throwable */ public function html(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); - $page->html = $this->pageRepo->renderPage($page); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page->html = (new PageContent($page))->render(); $containedHtml = $this->exportService->pageToContainedHtml($page); return $this->downloadResponse($containedHtml, $pageSlug . '.html'); } /** * Export a page to a simple plaintext .txt file. - * @param string $bookSlug - * @param string $pageSlug - * @return Response * @throws NotFoundException */ public function plainText(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($pageSlug, $bookSlug); + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $pageText = $this->exportService->pageToPlainText($page); return $this->downloadResponse($pageText, $pageSlug . '.txt'); } diff --git a/app/Http/Controllers/PageRevisionController.php b/app/Http/Controllers/PageRevisionController.php new file mode 100644 index 000000000..3c65b50ac --- /dev/null +++ b/app/Http/Controllers/PageRevisionController.php @@ -0,0 +1,128 @@ +pageRepo = $pageRepo; + parent::__construct(); + } + + /** + * Shows the last revisions for this page. + * @throws NotFoundException + */ + public function index(string $bookSlug, string $pageSlug) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()])); + return view('pages.revisions', [ + 'page' => $page, + 'current' => $page + ]); + } + + /** + * Shows a preview of a single revision. + * @throws NotFoundException + */ + public function show(string $bookSlug, string $pageSlug, int $revisionId) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $revision = $page->revisions()->where('id', '=', $revisionId)->first(); + if ($revision === null) { + throw new NotFoundException(); + } + + $page->fill($revision->toArray()); + + $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()])); + return view('pages.revision', [ + 'page' => $page, + 'book' => $page->book, + 'diff' => null, + 'revision' => $revision + ]); + } + + /** + * Shows the changes of a single revision. + * @throws NotFoundException + */ + public function changes(string $bookSlug, string $pageSlug, int $revisionId) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $revision = $page->revisions()->where('id', '=', $revisionId)->first(); + if ($revision === null) { + throw new NotFoundException(); + } + + $prev = $revision->getPrevious(); + $prevContent = $prev->html ?? ''; + $diff = (new Htmldiff)->diff($prevContent, $revision->html); + + $page->fill($revision->toArray()); + $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()])); + + return view('pages.revision', [ + 'page' => $page, + 'book' => $page->book, + 'diff' => $diff, + 'revision' => $revision + ]); + } + + /** + * Restores a page using the content of the specified revision. + * @throws NotFoundException + */ + public function restore(string $bookSlug, string $pageSlug, int $revisionId) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $this->checkOwnablePermission('page-update', $page); + + $page = $this->pageRepo->restoreRevision($page, $revisionId); + + Activity::add($page, 'page_restore', $page->book->id); + return redirect($page->getUrl()); + } + + /** + * Deletes a revision using the id of the specified revision. + * @throws NotFoundException + */ + public function destroy(string $bookSlug, string $pageSlug, int $revId) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $this->checkOwnablePermission('page-delete', $page); + + $revision = $page->revisions()->where('id', '=', $revId)->first(); + if ($revision === null) { + throw new NotFoundException("Revision #{$revId} not found"); + } + + // Get the current revision for the page + $currentRevision = $page->getCurrentRevision(); + + // Check if its the latest revision, cannot delete latest revision. + if (intval($currentRevision->id) === intval($revId)) { + $this->showErrorNotification(trans('entities.revision_cannot_delete_latest')); + return redirect($page->getUrl('/revisions')); + } + + $revision->delete(); + $this->showSuccessNotification(trans('entities.revision_delete_success')); + return redirect($page->getUrl('/revisions')); + } +} diff --git a/app/Http/Controllers/PageTemplateController.php b/app/Http/Controllers/PageTemplateController.php index b47205a1b..eaa1a8ae2 100644 --- a/app/Http/Controllers/PageTemplateController.php +++ b/app/Http/Controllers/PageTemplateController.php @@ -11,8 +11,7 @@ class PageTemplateController extends Controller protected $pageRepo; /** - * PageTemplateController constructor. - * @param $pageRepo + * PageTemplateController constructor */ public function __construct(PageRepo $pageRepo) { @@ -22,14 +21,12 @@ class PageTemplateController extends Controller /** * Fetch a list of templates from the system. - * @param Request $request - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function list(Request $request) { $page = $request->get('page', 1); $search = $request->get('search', ''); - $templates = $this->pageRepo->getPageTemplates(10, $page, $search); + $templates = $this->pageRepo->getTemplates(10, $page, $search); if ($search) { $templates->appends(['search' => $search]); @@ -42,13 +39,11 @@ class PageTemplateController extends Controller /** * Get the content of a template. - * @param $templateId - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response * @throws NotFoundException */ - public function get($templateId) + public function get(int $templateId) { - $page = $this->pageRepo->getById('page', $templateId); + $page = $this->pageRepo->getById($templateId); if (!$page->template) { throw new NotFoundException(); diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php index b8ca5a646..148ae5cd6 100644 --- a/app/Http/Controllers/PermissionController.php +++ b/app/Http/Controllers/PermissionController.php @@ -53,7 +53,7 @@ class PermissionController extends Controller ]); $this->permissionsRepo->saveNewRole($request->all()); - $this->showSuccessNotification( trans('settings.role_create_success')); + $this->showSuccessNotification(trans('settings.role_create_success')); return redirect('/settings/roles'); } @@ -90,7 +90,7 @@ class PermissionController extends Controller ]); $this->permissionsRepo->updateRole($id, $request->all()); - $this->showSuccessNotification( trans('settings.role_update_success')); + $this->showSuccessNotification(trans('settings.role_update_success')); return redirect('/settings/roles'); } @@ -124,11 +124,11 @@ class PermissionController extends Controller try { $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id')); } catch (PermissionsException $e) { - $this->showErrorNotification( $e->getMessage()); + $this->showErrorNotification($e->getMessage()); return redirect()->back(); } - $this->showSuccessNotification( trans('settings.role_delete_success')); + $this->showSuccessNotification(trans('settings.role_delete_success')); return redirect('/settings/roles'); } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 1691ee9b0..a5cd7ad6b 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -1,35 +1,27 @@ entityRepo = $entityRepo; $this->viewService = $viewService; $this->searchService = $searchService; $this->entityContextManager = $entityContextManager; @@ -38,9 +30,6 @@ class SearchController extends Controller /** * Searches all entities. - * @param Request $request - * @return View - * @internal param string $searchTerm */ public function search(Request $request) { @@ -64,12 +53,8 @@ class SearchController extends Controller /** * Searches all entities within a book. - * @param Request $request - * @param integer $bookId - * @return View - * @internal param string $searchTerm */ - public function searchBook(Request $request, $bookId) + public function searchBook(Request $request, int $bookId) { $term = $request->get('term', ''); $results = $this->searchService->searchBook($bookId, $term); @@ -78,12 +63,8 @@ class SearchController extends Controller /** * Searches all entities within a chapter. - * @param Request $request - * @param integer $chapterId - * @return View - * @internal param string $searchTerm */ - public function searchChapter(Request $request, $chapterId) + public function searchChapter(Request $request, int $chapterId) { $term = $request->get('term', ''); $results = $this->searchService->searchChapter($chapterId, $term); @@ -93,8 +74,6 @@ class SearchController extends Controller /** * Search for a list of entities and return a partial HTML response of matching entities. * Returns the most popular entities if no search is provided. - * @param Request $request - * @return mixed */ public function searchEntitiesAjax(Request $request) { @@ -115,15 +94,13 @@ class SearchController extends Controller /** * Search siblings items in the system. - * @param Request $request - * @return Factory|View|mixed */ public function searchSiblings(Request $request) { $type = $request->get('entity_type', null); $id = $request->get('entity_id', null); - $entity = $this->entityRepo->getById($type, $id); + $entity = Entity::getEntityInstance($type)->newQuery()->visible()->find($id); if (!$entity) { return $this->jsonError(trans('errors.entity_not_found'), 404); } @@ -132,12 +109,12 @@ class SearchController extends Controller // Page in chapter if ($entity->isA('page') && $entity->chapter) { - $entities = $this->entityRepo->getChapterChildren($entity->chapter); + $entities = $entity->chapter->visiblePages(); } // Page in book or chapter if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) { - $entities = $this->entityRepo->getBookDirectChildren($entity->book); + $entities = $entity->book->getDirectChildren(); } // Book @@ -145,15 +122,15 @@ class SearchController extends Controller if ($entity->isA('book')) { $contextShelf = $this->entityContextManager->getContextualShelfForBook($entity); if ($contextShelf) { - $entities = $this->entityRepo->getBookshelfChildren($contextShelf); + $entities = $contextShelf->visibleBooks()->get(); } else { - $entities = $this->entityRepo->getAll('book'); + $entities = Book::visible()->get(); } } // Shelve if ($entity->isA('bookshelf')) { - $entities = $this->entityRepo->getAll('bookshelf'); + $entities = Bookshelf::visible()->get(); } return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']); diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 68687dc95..1146f22c7 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -76,7 +76,7 @@ class SettingController extends Controller setting()->remove('app-logo'); } - $this->showSuccessNotification( trans('settings.settings_save_success')); + $this->showSuccessNotification(trans('settings.settings_save_success')); return redirect('/settings'); } @@ -111,14 +111,14 @@ class SettingController extends Controller $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun); $deleteCount = count($imagesToDelete); if ($deleteCount === 0) { - $this->showWarningNotification( trans('settings.maint_image_cleanup_nothing_found')); + $this->showWarningNotification(trans('settings.maint_image_cleanup_nothing_found')); return redirect('/settings/maintenance')->withInput(); } if ($dryRun) { session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount])); } else { - $this->showSuccessNotification( trans('settings.maint_image_cleanup_success', ['count' => $deleteCount])); + $this->showSuccessNotification(trans('settings.maint_image_cleanup_success', ['count' => $deleteCount])); } return redirect('/settings/maintenance#image-cleanup')->withInput(); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index c787e78ad..b55398d2f 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -202,7 +202,7 @@ class UserController extends Controller } $user->save(); - $this->showSuccessNotification( trans('settings.users_edit_success')); + $this->showSuccessNotification(trans('settings.users_edit_success')); $redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id); return redirect($redirectUrl); @@ -236,17 +236,17 @@ class UserController extends Controller $user = $this->userRepo->getById($id); if ($this->userRepo->isOnlyAdmin($user)) { - $this->showErrorNotification( trans('errors.users_cannot_delete_only_admin')); + $this->showErrorNotification(trans('errors.users_cannot_delete_only_admin')); return redirect($user->getEditUrl()); } if ($user->system_name === 'public') { - $this->showErrorNotification( trans('errors.users_cannot_delete_guest')); + $this->showErrorNotification(trans('errors.users_cannot_delete_guest')); return redirect($user->getEditUrl()); } $this->userRepo->destroy($user); - $this->showSuccessNotification( trans('settings.users_delete_success')); + $this->showSuccessNotification(trans('settings.users_delete_success')); return redirect('/settings/users'); } @@ -261,7 +261,7 @@ class UserController extends Controller $user = $this->userRepo->getById($id); $userActivity = $this->userRepo->getActivity($user); - $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0); + $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5); $assetCounts = $this->userRepo->getAssetCounts($user); return view('users.profile', [ diff --git a/app/Http/Middleware/GlobalViewData.php b/app/Http/Middleware/GlobalViewData.php index 0c2419016..bc132dfc3 100644 --- a/app/Http/Middleware/GlobalViewData.php +++ b/app/Http/Middleware/GlobalViewData.php @@ -24,5 +24,4 @@ class GlobalViewData return $next($request); } - -} \ No newline at end of file +} diff --git a/resources/views/books/export.blade.php b/resources/views/books/export.blade.php index 4a7c45e0b..1cf91046d 100644 --- a/resources/views/books/export.blade.php +++ b/resources/views/books/export.blade.php @@ -58,7 +58,7 @@

{{ $bookChild->name }}

@if($bookChild->isA('chapter')) -

{{ $bookChild->text }}

+

{{ $bookChild->description }}

@if(count($bookChild->pages) > 0) @foreach($bookChild->pages as $page) diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index c1937ff23..9c599307e 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -22,7 +22,7 @@

{{ trans('entities.pages_popular') }}

- @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'page'), 'style' => 'compact']) + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['page']), 'style' => 'compact'])
@@ -30,7 +30,7 @@

{{ trans('entities.books_popular') }}

- @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'book'), 'style' => 'compact']) + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['book']), 'style' => 'compact'])
@@ -38,7 +38,7 @@

{{ trans('entities.chapters_popular') }}

- @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, 'chapter'), 'style' => 'compact']) + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['chapter']), 'style' => 'compact'])
diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php index f27209c48..3581a545b 100644 --- a/resources/views/form/entity-permissions.blade.php +++ b/resources/views/form/entity-permissions.blade.php @@ -19,7 +19,7 @@ {{ trans('common.toggle_all') }} - @foreach($roles as $role) + @foreach(\BookStack\Auth\Role::restrictable() as $role) {{ $role->display_name }} diff --git a/resources/views/shelves/form.blade.php b/resources/views/shelves/form.blade.php index 5125e7e19..19c5bbecd 100644 --- a/resources/views/shelves/form.blade.php +++ b/resources/views/shelves/form.blade.php @@ -14,10 +14,10 @@
+ value="{{ isset($shelf) ? $shelf->visibleBooks->implode('id', ',') : '' }}">
- @if (isset($shelfBooks) && count($shelfBooks) > 0) - @foreach ($shelfBooks as $book) + @if (count($shelf->visibleBooks ?? []) > 0) + @foreach ($shelf->visibleBooks as $book) diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php index 6bfc525a5..2212e1c1e 100644 --- a/resources/views/shelves/show.blade.php +++ b/resources/views/shelves/show.blade.php @@ -12,9 +12,9 @@

{{$shelf->name}}

{!! nl2br(e($shelf->description)) !!}

- @if(count($books) > 0) + @if(count($shelf->visibleBooks) > 0)
- @foreach($books as $book) + @foreach($shelf->visibleBooks as $book) @include('books.list-item', ['book' => $book]) @endforeach
diff --git a/routes/web.php b/routes/web.php index be729f566..5dee447a4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,9 +9,7 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/uploads/images/{path}', 'Images\ImageController@showImage') ->where('path', '.*$'); - Route::group(['prefix' => 'pages'], function() { - Route::get('/recently-updated', 'PageController@showRecentlyUpdated'); - }); + Route::get('/pages/recently-updated', 'PageController@showRecentlyUpdated'); // Shelves Route::get('/create-shelf', 'BookshelfController@create'); @@ -40,13 +38,13 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{slug}/edit', 'BookController@edit'); Route::put('/{slug}', 'BookController@update'); Route::delete('/{id}', 'BookController@destroy'); - Route::get('/{slug}/sort-item', 'BookController@sortItem'); + Route::get('/{slug}/sort-item', 'BookSortController@showItem'); Route::get('/{slug}', 'BookController@show'); Route::get('/{bookSlug}/permissions', 'BookController@showPermissions'); Route::put('/{bookSlug}/permissions', 'BookController@permissions'); Route::get('/{slug}/delete', 'BookController@showDelete'); - Route::get('/{bookSlug}/sort', 'BookController@sort'); - Route::put('/{bookSlug}/sort', 'BookController@saveSort'); + Route::get('/{bookSlug}/sort', 'BookSortController@show'); + Route::put('/{bookSlug}/sort', 'BookSortController@update'); Route::get('/{bookSlug}/export/html', 'BookExportController@html'); Route::get('/{bookSlug}/export/pdf', 'BookExportController@pdf'); Route::get('/{bookSlug}/export/plaintext', 'BookExportController@plainText'); @@ -74,11 +72,11 @@ Route::group(['middleware' => 'auth'], function () { Route::delete('/{bookSlug}/draft/{pageId}', 'PageController@destroyDraft'); // Revisions - Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageController@showRevisions'); - Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision'); - Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageController@showRevisionChanges'); - Route::put('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision'); - Route::delete('/{bookSlug}/page/{pageSlug}/revisions/{revId}/delete', 'PageController@destroyRevision'); + Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageRevisionController@index'); + Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageRevisionController@show'); + Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/changes', 'PageRevisionController@changes'); + Route::put('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageRevisionController@restore'); + Route::delete('/{bookSlug}/page/{pageSlug}/revisions/{revId}/delete', 'PageRevisionController@destroy'); // Chapters Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create'); diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index a88480969..4aef0ed26 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -2,7 +2,6 @@ use BookStack\Auth\Permissions\JointPermission; use BookStack\Entities\Page; -use BookStack\Entities\Repos\EntityRepo; use BookStack\Auth\User; use BookStack\Entities\Repos\PageRepo; @@ -56,7 +55,7 @@ class CommandsTest extends TestCase $this->asEditor(); $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '

new content

', 'summary' => 'page revision testing']); + $pageRepo->update($page, ['name' => 'updated page', 'html' => '

new content

', 'summary' => 'page revision testing']); $pageRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '

new content in draft

', 'summary' => 'page revision testing']); $this->assertDatabaseHas('page_revisions', [ diff --git a/tests/Entity/EntityTest.php b/tests/Entity/EntityTest.php index a3fb1cfe1..b506da2aa 100644 --- a/tests/Entity/EntityTest.php +++ b/tests/Entity/EntityTest.php @@ -3,7 +3,6 @@ use BookStack\Entities\Book; use BookStack\Entities\Chapter; use BookStack\Entities\Page; -use BookStack\Entities\Repos\EntityRepo; use BookStack\Auth\UserRepo; use BookStack\Entities\Repos\PageRepo; use Carbon\Carbon; @@ -192,7 +191,7 @@ class EntityTest extends BrowserKitTest $entities = $this->createEntityChainBelongingToUser($creator, $updater); $this->actingAs($creator); app(UserRepo::class)->destroy($creator); - app(PageRepo::class)->savePageRevision($entities['page']); + app(PageRepo::class)->update($entities['page'], ['html' => '

hello!

>']); $this->checkEntitiesViewable($entities); } @@ -205,7 +204,7 @@ class EntityTest extends BrowserKitTest $entities = $this->createEntityChainBelongingToUser($creator, $updater); $this->actingAs($updater); app(UserRepo::class)->destroy($updater); - app(PageRepo::class)->savePageRevision($entities['page']); + app(PageRepo::class)->update($entities['page'], ['html' => '

Hello there!

']); $this->checkEntitiesViewable($entities); } @@ -273,8 +272,7 @@ class EntityTest extends BrowserKitTest public function test_slug_multi_byte_lower_casing() { - $entityRepo = app(EntityRepo::class); - $book = $entityRepo->createFromInput('book', [ + $book = $this->newBook([ 'name' => 'КНИГА' ]); @@ -284,8 +282,7 @@ class EntityTest extends BrowserKitTest public function test_slug_format() { - $entityRepo = app(EntityRepo::class); - $book = $entityRepo->createFromInput('book', [ + $book = $this->newBook([ 'name' => 'PartA / PartB / PartC' ]); diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index a0fcb5ca8..8a78c8ac0 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -1,8 +1,7 @@ id)->first(); $this->assertEquals(substr_count($updatedPage->html, "bkmrk-test\""), 1); } + + public function test_get_page_nav_sets_correct_properties() + { + $content = '

Hello

There

Donkey

'; + $pageContent = new PageContent(new Page(['html' => $content])); + $navMap = $pageContent->getNavigation($content); + + $this->assertCount(3, $navMap); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h1', + 'link' => '#testa', + 'text' => 'Hello', + 'level' => 1, + ], $navMap[0]); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h2', + 'link' => '#testb', + 'text' => 'There', + 'level' => 2, + ], $navMap[1]); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h3', + 'link' => '#testc', + 'text' => 'Donkey', + 'level' => 3, + ], $navMap[2]); + } + + public function test_get_page_nav_does_not_show_empty_titles() + { + $content = '

Hello

 

'; + $pageContent = new PageContent(new Page(['html' => $content])); + $navMap = $pageContent->getNavigation($content); + + $this->assertCount(1, $navMap); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h1', + 'link' => '#testa', + 'text' => 'Hello' + ], $navMap[0]); + } + + public function test_get_page_nav_shifts_headers_if_only_smaller_ones_are_used() + { + $content = '

Hello

There
Donkey
'; + $pageContent = new PageContent(new Page(['html' => $content])); + $navMap = $pageContent->getNavigation($content); + + $this->assertCount(3, $navMap); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h4', + 'level' => 1, + ], $navMap[0]); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h5', + 'level' => 2, + ], $navMap[1]); + $this->assertArrayMapIncludes([ + 'nodeName' => 'h6', + 'level' => 3, + ], $navMap[2]); + } } diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index e374575f5..e83f78a10 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -1,11 +1,14 @@ getEditor(); $this->actingAs($newUser)->visit('/') - ->visit($book->getUrl() . '/create-page') - ->visit($chapter->getUrl() . '/create-page') + ->visit($book->getUrl('/create-page')) + ->visit($chapter->getUrl('/create-page')) ->visit($book->getUrl()) ->seeInElement('.book-contents', 'New Page'); - + $this->asAdmin() ->visit($book->getUrl()) ->dontSeeInElement('.book-contents', 'New Page') diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 140f67fe8..38193ec1a 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -12,7 +12,7 @@ class PageRevisionTest extends TestCase $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '

new content

', 'summary' => 'page revision testing']); + $pageRepo->update($page, ['name' => 'updated page', 'html' => '

new content

', 'summary' => 'page revision testing']); $pageRevision = $page->revisions->last(); $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); @@ -30,8 +30,8 @@ class PageRevisionTest extends TestCase $pageRepo = app(PageRepo::class); $page = Page::first(); - $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'initial page revision testing']); - $pageRepo->updatePage($page, $page->book_id, ['name' => 'updated page again', 'html' => '

new content

', 'summary' => 'page revision testing']); + $pageRepo->update($page, ['name' => 'updated page abc123', 'html' => '

new contente def456

', 'summary' => 'initial page revision testing']); + $pageRepo->update($page, ['name' => 'updated page again', 'html' => '

new content

', 'summary' => 'page revision testing']); $page = Page::find($page->id); @@ -98,7 +98,7 @@ class PageRevisionTest extends TestCase $beforeRevisionCount = $page->revisions->count(); $currentRevision = $page->getCurrentRevision(); $resp = $this->asEditor()->delete($currentRevision->getUrl('/delete/')); - $resp->assertStatus(400); + $resp->assertRedirect($page->getUrl('/revisions')); $page = Page::find($page->id); $afterRevisionCount = $page->revisions->count(); diff --git a/tests/Entity/SortTest.php b/tests/Entity/SortTest.php index cad6d3c01..3c83d626a 100644 --- a/tests/Entity/SortTest.php +++ b/tests/Entity/SortTest.php @@ -1,6 +1,5 @@ asAdmin(); $pageRepo = app(PageRepo::class); - $draft = $pageRepo->getDraftPage($this->book); + $draft = $pageRepo->getNewDraftPage($this->book); $resp = $this->get($this->book->getUrl()); $resp->assertSee($draft->name); @@ -214,7 +213,6 @@ class SortTest extends TestCase 'entity_selection' => 'book:' . $newBook->id, 'name' => 'My copied test page' ]); - $pageCopy = Page::where('name', '=', 'My copied test page')->first(); $movePageResp->assertRedirect($pageCopy->getUrl()); diff --git a/tests/Permissions/RestrictionsTest.php b/tests/Permissions/RestrictionsTest.php index f6e07c0f1..d899c6396 100644 --- a/tests/Permissions/RestrictionsTest.php +++ b/tests/Permissions/RestrictionsTest.php @@ -5,14 +5,13 @@ use BookStack\Entities\Bookshelf; use BookStack\Entities\Chapter; use BookStack\Entities\Entity; use BookStack\Auth\User; -use BookStack\Entities\Repos\EntityRepo; use BookStack\Entities\Page; class RestrictionsTest extends BrowserKitTest { /** - * @var \BookStack\Auth\User + * @var User */ protected $user; @@ -327,7 +326,7 @@ class RestrictionsTest extends BrowserKitTest public function test_page_view_restriction() { - $page = \BookStack\Entities\Page::first(); + $page = Page::first(); $pageUrl = $page->getUrl(); $this->actingAs($this->user) @@ -367,7 +366,7 @@ class RestrictionsTest extends BrowserKitTest public function test_page_delete_restriction() { - $page = \BookStack\Entities\Page::first(); + $page = Page::first(); $pageUrl = $page->getUrl(); $this->actingAs($this->user) @@ -438,7 +437,7 @@ class RestrictionsTest extends BrowserKitTest public function test_page_restriction_form() { - $page = \BookStack\Entities\Page::first(); + $page = Page::first(); $this->asAdmin()->visit($page->getUrl() . '/permissions') ->see('Page Permissions') ->check('restricted') @@ -665,10 +664,8 @@ class RestrictionsTest extends BrowserKitTest $this->setEntityRestrictions($firstBook, ['view', 'update']); $this->setEntityRestrictions($secondBook, ['view']); - $firstBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter', - ['name' => 'first book chapter'], $firstBook); - $secondBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter', - ['name' => 'second book chapter'], $secondBook); + $firstBookChapter = $this->newChapter(['name' => 'first book chapter'], $firstBook); + $secondBookChapter = $this->newChapter(['name' => 'second book chapter'], $secondBook); // Create request data $reqData = [ diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index 358bf6ee3..3433f3b83 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -1,9 +1,14 @@ users()->first(); + $user = Role::getRole('viewer')->users()->first(); if (!empty($attributes)) $user->forceFill($attributes)->save(); return $user; } @@ -76,7 +83,7 @@ trait SharedTestHelpers /** * Regenerate the permission for an entity. * @param Entity $entity - * @throws \Throwable + * @throws Throwable */ protected function regenEntityPermissions(Entity $entity) { @@ -87,10 +94,10 @@ trait SharedTestHelpers /** * Create and return a new bookshelf. * @param array $input - * @return \BookStack\Entities\Bookshelf + * @return Bookshelf */ public function newShelf($input = ['name' => 'test shelf', 'description' => 'My new test shelf']) { - return app(EntityRepo::class)->createFromInput('bookshelf', $input); + return app(BookshelfRepo::class)->create($input, []); } /** @@ -99,30 +106,30 @@ trait SharedTestHelpers * @return Book */ public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) { - return app(EntityRepo::class)->createFromInput('book', $input); + return app(BookRepo::class)->create($input); } /** * Create and return a new test chapter * @param array $input * @param Book $book - * @return \BookStack\Entities\Chapter + * @return Chapter */ public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) { - return app(EntityRepo::class)->createFromInput('chapter', $input, $book); + return app(ChapterRepo::class)->create($input, $book); } /** * Create and return a new test page * @param array $input * @return Page - * @throws \Throwable + * @throws Throwable */ public function newPage($input = ['name' => 'test page', 'html' => 'My new test page']) { $book = Book::first(); $pageRepo = app(PageRepo::class); - $draftPage = $pageRepo->getDraftPage($book); - return $pageRepo->publishPageDraft($draftPage, $input); + $draftPage = $pageRepo->getNewDraftPage($book); + return $pageRepo->publishDraft($draftPage, $input); } /** @@ -167,10 +174,10 @@ trait SharedTestHelpers /** * Give the given user some permissions. - * @param \BookStack\Auth\User $user + * @param User $user * @param array $permissions */ - protected function giveUserPermissions(\BookStack\Auth\User $user, $permissions = []) + protected function giveUserPermissions(User $user, $permissions = []) { $newRole = $this->createNewRole($permissions); $user->attachRole($newRole); @@ -198,7 +205,7 @@ trait SharedTestHelpers */ protected function mockHttpFetch($returnData, int $times = 1) { - $mockHttp = \Mockery::mock(HttpFetcher::class); + $mockHttp = Mockery::mock(HttpFetcher::class); $this->app[HttpFetcher::class] = $mockHttp; $mockHttp->shouldReceive('fetch') ->times($times) diff --git a/tests/Unit/PageRepoTest.php b/tests/Unit/PageRepoTest.php deleted file mode 100644 index 38ffbf616..000000000 --- a/tests/Unit/PageRepoTest.php +++ /dev/null @@ -1,78 +0,0 @@ -pageRepo = app()->make(PageRepo::class); - } - - public function test_get_page_nav_sets_correct_properties() - { - $content = '

Hello

There

Donkey

'; - $navMap = $this->pageRepo->getPageNav($content); - - $this->assertCount(3, $navMap); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h1', - 'link' => '#testa', - 'text' => 'Hello', - 'level' => 1, - ], $navMap[0]); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h2', - 'link' => '#testb', - 'text' => 'There', - 'level' => 2, - ], $navMap[1]); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h3', - 'link' => '#testc', - 'text' => 'Donkey', - 'level' => 3, - ], $navMap[2]); - } - - public function test_get_page_nav_does_not_show_empty_titles() - { - $content = '

Hello

 

'; - $navMap = $this->pageRepo->getPageNav($content); - - $this->assertCount(1, $navMap); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h1', - 'link' => '#testa', - 'text' => 'Hello' - ], $navMap[0]); - } - - public function test_get_page_nav_shifts_headers_if_only_smaller_ones_are_used() - { - $content = '

Hello

There
Donkey
'; - $navMap = $this->pageRepo->getPageNav($content); - - $this->assertCount(3, $navMap); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h4', - 'level' => 1, - ], $navMap[0]); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h5', - 'level' => 2, - ], $navMap[1]); - $this->assertArrayMapIncludes([ - 'nodeName' => 'h6', - 'level' => 3, - ], $navMap[2]); - } - -} \ No newline at end of file diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 0d51e050f..12b254d00 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -223,7 +223,7 @@ class AttachmentTest extends TestCase { $admin = $this->getAdmin(); $viewer = $this->getViewer(); - $page = Page::first(); + $page = Page::first(); /** @var Page $page */ $this->actingAs($admin); $fileName = 'permission_test.txt'; @@ -233,7 +233,7 @@ class AttachmentTest extends TestCase $page->restricted = true; $page->permissions()->delete(); $page->save(); - $this->app[PermissionService::class]->buildJointPermissionsForEntity($page); + $page->rebuildPermissions(); $page->load('jointPermissions'); $this->actingAs($viewer); diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 4d3e8a498..0615a95ce 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -367,7 +367,7 @@ class ImageTest extends TestCase $image = Image::where('type', '=', 'gallery')->first(); $pageRepo = app(PageRepo::class); - $pageRepo->updatePage($page, $page->book_id, [ + $pageRepo->update($page, [ 'name' => $page->name, 'html' => $page->html . "url}\">", 'summary' => '' @@ -379,7 +379,7 @@ class ImageTest extends TestCase $this->assertCount(0, $toDelete); // Save a revision of our page without the image; - $pageRepo->updatePage($page, $page->book_id, [ + $pageRepo->update($page, [ 'name' => $page->name, 'html' => "

Hello

", 'summary' => '' From ccec991f6c562514e50d081736253ed9fd7b93ae Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 5 Oct 2019 13:21:38 +0100 Subject: [PATCH 041/257] Added zip/unzip to docker dev setup for composer to use --- dev/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile index b34820861..8816615cf 100644 --- a/dev/docker/Dockerfile +++ b/dev/docker/Dockerfile @@ -4,7 +4,7 @@ ENV APACHE_DOCUMENT_ROOT /app/public WORKDIR /app RUN apt-get update -y \ - && apt-get install -y git libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \ + && apt-get install -y git zip unzip libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \ && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \ && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd ldap \ && a2enmod rewrite \ From 61a9139bf08352a03ef62cdb89ae0c11231b0a97 Mon Sep 17 00:00:00 2001 From: Timo Schwarzer Date: Tue, 15 Oct 2019 18:41:08 +0200 Subject: [PATCH 042/257] Add feature to send test e-mails --- app/Http/Controllers/SettingController.php | 17 +++++++++++++++++ app/Notifications/TestEmail.php | 18 ++++++++++++++++++ resources/lang/en/settings.php | 7 +++++++ resources/views/settings/maintenance.blade.php | 16 +++++++++++++++- routes/web.php | 1 + 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 app/Notifications/TestEmail.php diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 1146f22c7..f0a078300 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -1,6 +1,7 @@ withInput(); } + + /** + * Action to send a test e-mail to the current user. + * @param Request $request + * @param User $user + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function sendTestEmail(Request $request) + { + $this->checkPermission('settings-manage'); + + user()->notify(new TestEmail()); + $this->showSuccessNotification(trans('settings.maint_send_test_email_success', ['address' => user()->email])); + + return redirect('/settings/maintenance#image-cleanup')->withInput(); + } } diff --git a/app/Notifications/TestEmail.php b/app/Notifications/TestEmail.php new file mode 100644 index 000000000..7fce1c19c --- /dev/null +++ b/app/Notifications/TestEmail.php @@ -0,0 +1,18 @@ +newMailMessage() + ->subject(trans('settings.maint_send_test_email_mail_subject')) + ->greeting(trans('settings.maint_send_test_email_mail_greeting')) + ->line(trans('settings.maint_send_test_email_mail_text')); + } +} diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index bb542a588..3bcd517ca 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -63,6 +63,13 @@ return [ 'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?', 'maint_image_cleanup_success' => ':count potentially unused images found and deleted!', 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!', + 'maint_send_test_email' => 'Send a Test E-Mail', + 'maint_send_test_email_desc' => 'This sends a test e-mail to your e-mail address specified in your profile.', + 'maint_send_test_email_run' => 'Send test e-mail', + 'maint_send_test_email_success' => 'E-Mail sent to :address', + 'maint_send_test_email_mail_subject' => 'Test E-Mail', + 'maint_send_test_email_mail_greeting' => 'E-Mail delivery seems to work!', + 'maint_send_test_email_mail_text' => 'Congratulations! As you received this e-mail notification, your e-mail settings seem to be configured properly.', // Role Settings 'roles' => 'Roles', diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php index 6be49cdf2..ecd4702a6 100644 --- a/resources/views/settings/maintenance.blade.php +++ b/resources/views/settings/maintenance.blade.php @@ -13,7 +13,6 @@
-

{{ trans('settings.maint_image_cleanup') }}

@@ -44,5 +43,20 @@
+
+

{{ trans('settings.maint_send_test_email') }}

+
+
+

{{ trans('settings.maint_send_test_email_desc') }}

+
+
+ + {!! csrf_field() !!} + + +
+
+
+
@stop diff --git a/routes/web.php b/routes/web.php index 5dee447a4..eafb6a45c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -172,6 +172,7 @@ Route::group(['middleware' => 'auth'], function () { // Maintenance Route::get('/maintenance', 'SettingController@showMaintenance'); Route::delete('/maintenance/cleanup-images', 'SettingController@cleanupImages'); + Route::post('/maintenance/send-test-email', 'SettingController@sendTestEmail'); // Users Route::get('/users', 'UserController@index'); From 76bd0fdfa6c82b4a64ff0278ed3a76fd416939e5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 16 Oct 2019 18:01:35 +0100 Subject: [PATCH 043/257] Added editor instance event hooks As per #1721 --- resources/js/components/markdown-editor.js | 6 +++++ resources/js/components/wysiwyg-editor.js | 3 +++ resources/js/services/code.js | 19 ++++++++------- resources/js/services/events.js | 27 ++++++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index c89a7ad8b..de256a846 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -30,6 +30,12 @@ class MarkdownEditor { this.displayDoc = this.display.contentDocument; this.init(); }); + + window.$events.emitPublic(elem, 'editor-markdown::setup', { + markdownIt: this.markdown, + displayEl: this.display, + codeMirrorInstance: this.cm, + }); } init() { diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js index c03c0d2aa..60a6743ea 100644 --- a/resources/js/components/wysiwyg-editor.js +++ b/resources/js/components/wysiwyg-editor.js @@ -412,6 +412,7 @@ class WysiwygEditor { this.loadPlugins(); this.tinyMceConfig = this.getTinyMceConfig(); + window.$events.emitPublic(elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig}); window.tinymce.init(this.tinyMceConfig); } @@ -654,6 +655,8 @@ class WysiwygEditor { // Paste image-uploads editor.on('paste', event => editorPaste(event, editor, context)); + // Custom handler hook + window.$events.emitPublic(context.elem, 'editor-tinymce::setup', {editor}); } }; } diff --git a/resources/js/services/code.js b/resources/js/services/code.js index 1e0e48289..ca66bdb5a 100644 --- a/resources/js/services/code.js +++ b/resources/js/services/code.js @@ -240,24 +240,27 @@ function setContent(cmInstance, codeContent) { } /** - * Get a CodeMirror instace to use for the markdown editor. + * Get a CodeMirror instance to use for the markdown editor. * @param {HTMLElement} elem * @returns {*} */ function markdownEditor(elem) { - let content = elem.textContent; - - return CodeMirror(function (elt) { - elem.parentNode.insertBefore(elt, elem); - elem.style.display = 'none'; - }, { + const content = elem.textContent; + const config = { value: content, mode: "markdown", lineNumbers: true, theme: getTheme(), lineWrapping: true, scrollPastEnd: true, - }); + }; + + window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config}); + + return CodeMirror(function (elt) { + elem.parentNode.insertBefore(elt, elem); + elem.style.display = 'none'; + }, config); } /** diff --git a/resources/js/services/events.js b/resources/js/services/events.js index 1f97d0cb8..fa3ed7fdf 100644 --- a/resources/js/services/events.js +++ b/resources/js/services/events.js @@ -7,6 +7,12 @@ class Events { this.stack = []; } + /** + * Emit a custom event for any handlers to pick-up. + * @param {String} eventName + * @param {*} eventData + * @returns {Events} + */ emit(eventName, eventData) { this.stack.push({name: eventName, data: eventData}); if (typeof this.listeners[eventName] === 'undefined') return this; @@ -18,11 +24,32 @@ class Events { return this; } + /** + * Listen to a custom event and run the given callback when that event occurs. + * @param {String} eventName + * @param {Function} callback + * @returns {Events} + */ listen(eventName, callback) { if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = []; this.listeners[eventName].push(callback); return this; } + + /** + * Emit an event for public use. + * Sends the event via the native DOM event handling system. + * @param {Element} targetElement + * @param {String} eventName + * @param {Object} eventData + */ + emitPublic(targetElement, eventName, eventData) { + const event = new CustomEvent(eventName, { + detail: eventData, + bubbles: true + }); + targetElement.dispatchEvent(event); + } } export default Events; \ No newline at end of file From df98deb59da6906e53e6894f604e707907db8afa Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 17 Oct 2019 14:01:19 +0100 Subject: [PATCH 044/257] Added Turkish to locale system --- app/Config/app.php | 2 +- app/Http/Middleware/Localization.php | 1 + resources/lang/en/settings.php | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Config/app.php b/app/Config/app.php index 50e3bdb6a..b5bf891b2 100755 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -52,7 +52,7 @@ return [ 'locale' => env('APP_LANG', 'en'), // Locales available - 'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'], + 'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'hu', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW', 'tr'], // Application Fallback Locale 'fallback_locale' => 'en', diff --git a/app/Http/Middleware/Localization.php b/app/Http/Middleware/Localization.php index b5e702781..8fe4ca990 100644 --- a/app/Http/Middleware/Localization.php +++ b/app/Http/Middleware/Localization.php @@ -37,6 +37,7 @@ class Localization 'uk' => 'uk_UA', 'zh_CN' => 'zh_CN', 'zh_TW' => 'zh_TW', + 'tr' => 'tr_TR', ]; /** diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index bb542a588..026308a34 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -159,8 +159,9 @@ return [ 'ru' => 'Русский', 'uk' => 'Українська', 'zh_CN' => '简体中文', - 'zh_TW' => '繁體中文', - 'hu' => 'Magyar' + 'zh_TW' => '繁體中文', + 'hu' => 'Magyar', + 'tr' => 'Türkçe', ] //!//////////////////////////////// ]; From e48d7d59cc9a5f9f18021fcdeb14f1e7505de387 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 17 Oct 2019 14:19:35 +0100 Subject: [PATCH 045/257] Removed tabindexes where found to be not required --- resources/views/auth/forms/login/ldap.blade.php | 6 +++--- resources/views/auth/forms/login/standard.blade.php | 4 ++-- resources/views/auth/login.blade.php | 3 +-- resources/views/components/custom-checkbox.blade.php | 4 +--- resources/views/components/toggle-switch.blade.php | 3 +-- resources/views/form/password.blade.php | 1 - resources/views/form/text.blade.php | 5 ++--- 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/resources/views/auth/forms/login/ldap.blade.php b/resources/views/auth/forms/login/ldap.blade.php index f7903e18c..2699fda00 100644 --- a/resources/views/auth/forms/login/ldap.blade.php +++ b/resources/views/auth/forms/login/ldap.blade.php @@ -1,12 +1,12 @@
- @include('form.text', ['name' => 'username', 'tabindex' => 1]) + @include('form.text', ['name' => 'username', 'autofocus' => true])
@if(session('request-email', false) === true)
- @include('form.text', ['name' => 'email', 'tabindex' => 1]) + @include('form.text', ['name' => 'email']) {{ trans('auth.ldap_email_hint') }} @@ -15,5 +15,5 @@
- @include('form.password', ['name' => 'password', 'tabindex' => 1]) + @include('form.password', ['name' => 'password'])
\ No newline at end of file diff --git a/resources/views/auth/forms/login/standard.blade.php b/resources/views/auth/forms/login/standard.blade.php index bfe437a8c..52fae3750 100644 --- a/resources/views/auth/forms/login/standard.blade.php +++ b/resources/views/auth/forms/login/standard.blade.php @@ -1,11 +1,11 @@
- @include('form.text', ['name' => 'email', 'tabindex' => 1, 'focus' => 1]) + @include('form.text', ['name' => 'email', 'autofocus' => true])
- @include('form.password', ['name' => 'password', 'tabindex' => 1]) + @include('form.password', ['name' => 'password']) {{ trans('auth.forgot_password') }} diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 2f3cc1f73..fbf540d71 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -22,13 +22,12 @@ 'name' => 'remember', 'checked' => false, 'value' => 'on', - 'tabindex' => 1, 'label' => trans('auth.remember_me'), ])
- +
diff --git a/resources/views/components/custom-checkbox.blade.php b/resources/views/components/custom-checkbox.blade.php index 6ba2f457f..fa3da02e5 100644 --- a/resources/views/components/custom-checkbox.blade.php +++ b/resources/views/components/custom-checkbox.blade.php @@ -3,12 +3,10 @@ $name $value $checked $label -$tabindex --}}