From 45a91513a14c2991e4fc570ac3433a540d0b0697 Mon Sep 17 00:00:00 2001 From: Xander Schuurman <44030544+keeama13@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:58:50 +0100 Subject: [PATCH] feat: block account route (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: block account route * feat: messages * fix: add nullable (#163) * feat: set null for searchitem (#166) * fix: fallback to null for SearchItem * style: formatting * fix: only add column when it doesn't exist (#167) * fix: only add column when it doesn't exist * style: formatting * feat: dd removed * feat: add admin e-mail to config * feat: publish css file * feat: update block template * feat: templates * fix: english language * feat: template for succes * style: formatting --------- Co-authored-by: René --- config/siteboss.php | 11 + lang/en/auth.php | 6 + lang/nl/auth.php | 9 +- resources/css/siteboss.css | 698 ++++++++++++++++++ resources/views/components/info.blade.php | 50 ++ .../views/pages/messaage-button.blade.php | 10 + resources/views/pages/message.blade.php | 5 + routes/api.php | 14 +- src/Auth/Notifications/VerifyEmail.php | 1 + src/FrameworkServiceProvider.php | 1 + .../Auth/VerifyEmailController.php | 56 +- src/Services/Assets/AbstractAssetService.php | 2 +- 12 files changed, 852 insertions(+), 11 deletions(-) create mode 100644 resources/css/siteboss.css create mode 100644 resources/views/components/info.blade.php create mode 100644 resources/views/pages/messaage-button.blade.php create mode 100644 resources/views/pages/message.blade.php diff --git a/config/siteboss.php b/config/siteboss.php index ec55bdad..ec04184e 100644 --- a/config/siteboss.php +++ b/config/siteboss.php @@ -49,4 +49,15 @@ 'export_retain_ids' => env('SB_EXPORT_RETAIN_IDS', false), + /* + |-------------------------------------------------------------------------- + | Admin email + |-------------------------------------------------------------------------- + | + | Email address to send admin notifications to. + | + */ + + 'admin_email' => env('SB_ADMIN_EMAIL', null), + ]; diff --git a/lang/en/auth.php b/lang/en/auth.php index fd25c37b..e0d56ad1 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -22,6 +22,12 @@ 'verify_email_button' => 'Verify email', 'verify_email_resend' => 'Resend verification email', 'verify_email_link_sent' => 'A fresh verification link has been sent to your email address.', + 'verify_email_success' => 'Account successfully verified.', 'verify_wrong_email' => 'Click here to block this attempt if you were not the one who tried to login.', + 'block_account_title' => 'Account blocked', 'block_account_message' => 'The account has been blocked.', + 'verify_block_account_title' => 'Block account?', + 'verify_block_account_message' => 'Are you sure you want to block this account? You will not be able to login to SiteBoss. An administrator can unblock your account.', + 'verify_block_account_button' => 'Block account', + ]; diff --git a/lang/nl/auth.php b/lang/nl/auth.php index 1c76b973..72f04238 100644 --- a/lang/nl/auth.php +++ b/lang/nl/auth.php @@ -21,6 +21,13 @@ 'verify_email_link' => 'Klik op onderstaande link om je e-mailadres te bevestigen.', 'verify_email_resend' => 'Verificatiemail opnieuw verzenden', 'verify_email_link_sent' => 'Er is een nieuwe verificatielink naar jouw e-mailadres verzonden.', + 'verify_email_success' => 'Account succesvol geverifieerd.', + 'verify_wrong_email' => 'Heb je zelf niet geprobeerd in te loggen? Klik dan hier om deze poging te blokkeren.', - 'block_account_message' => 'Account succesvol geblokkeerd.', + + 'verify_block_account_title' => 'Account blokkeren?', + 'verify_block_account_message' => 'Wil je voor de zekerheid jouw account blokkeren? Je kunt dan niet meer inloggen op SiteBoss. Een administrator kan jouw account weer deblokkeren.', + 'verify_block_account_button' => 'Blokkeer account', + 'block_account_title' => 'Account geblokkeerd', + 'block_account_message' => 'Vraag een beheerder om je account te herstellen.', ]; diff --git a/resources/css/siteboss.css b/resources/css/siteboss.css new file mode 100644 index 00000000..788d069c --- /dev/null +++ b/resources/css/siteboss.css @@ -0,0 +1,698 @@ +/* Global variables. */ +:root, +::backdrop { + /* Set sans-serif & mono fonts */ + --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, + "Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica, + "Helvetica Neue", sans-serif; + --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + --standard-border-radius: 5px; + + /* Default (light) theme */ + --bg: #fff; + --accent-bg: #f5f7ff; + --text: #212121; + --text-light: #585858; + --border: #898EA4; + --accent: #0d47a1; + --accent-hover: #1266e2; + --accent-text: var(--bg); + --code: #d81b60; + --preformatted: #444; + --marked: #ffdd33; + --disabled: #efefef; +} + +/* Dark theme */ +@media (prefers-color-scheme: dark) { + :root, + ::backdrop { + color-scheme: dark; + --bg: #212121; + --accent-bg: #2b2b2b; + --text: #dcdcdc; + --text-light: #ababab; + --accent: #ffb300; + --accent-hover: #ffe099; + --accent-text: var(--bg); + --code: #f06292; + --preformatted: #ccc; + --disabled: #111; + } + /* Add a bit of transparency so light media isn't so glaring in dark mode */ + img, + video { + opacity: 0.8; + } +} + +/* Reset box-sizing */ +*, *::before, *::after { + box-sizing: border-box; +} + +/* Reset default appearance */ +textarea, +select, +input, +progress { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} + +html { + /* Set the font globally */ + font-family: var(--sans-font); + scroll-behavior: smooth; +} + +/* Make the body a nice central block */ +body { + color: var(--text); + background-color: var(--bg); + font-size: 1.15rem; + line-height: 1.5; + display: grid; + grid-template-columns: 1fr min(45rem, 90%) 1fr; + margin: 0; +} +body > * { + grid-column: 2; +} + +/* Make the header bg full width, but the content inline with body */ +body > header { + background-color: var(--accent-bg); + border-bottom: 1px solid var(--border); + text-align: center; + padding: 0 0.5rem 2rem 0.5rem; + grid-column: 1 / -1; +} + +body > header > *:only-child { + margin-block-start: 2rem; +} + +body > header h1 { + max-width: 1200px; + margin: 1rem auto; +} + +body > header p { + max-width: 40rem; + margin: 1rem auto; +} + +/* Add a little padding to ensure spacing is correct between content and header > nav */ +main { + padding-top: 1.5rem; +} + +body > footer { + margin-top: 4rem; + padding: 2rem 1rem 1.5rem 1rem; + color: var(--text-light); + font-size: 0.9rem; + text-align: center; + border-top: 1px solid var(--border); +} + +/* Format headers */ +h1 { + font-size: 3rem; +} + +h2 { + font-size: 2.6rem; + margin-top: 3rem; +} + +h3 { + font-size: 2rem; + margin-top: 3rem; +} + +h4 { + font-size: 1.44rem; +} + +h5 { + font-size: 1.15rem; +} + +h6 { + font-size: 0.96rem; +} + +p { + margin: 1.5rem 0; +} + +/* Prevent long strings from overflowing container */ +p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; +} + +/* Fix line height when title wraps */ +h1, +h2, +h3 { + line-height: 1.1; +} + +/* Reduce header size on mobile */ +@media only screen and (max-width: 720px) { + h1 { + font-size: 2.5rem; + } + + h2 { + font-size: 2.1rem; + } + + h3 { + font-size: 1.75rem; + } + + h4 { + font-size: 1.25rem; + } +} + +/* Format links & buttons */ +a, +a:visited { + color: var(--accent); +} + +a:hover { + text-decoration: none; +} + +button, +.button, +a.button, /* extra specificity to override a */ +input[type="submit"], +input[type="reset"], +input[type="button"], +label[type="button"] { + border: 1px solid var(--accent); + background-color: var(--accent); + color: var(--accent-text); + padding: 0.5rem 0.9rem; + text-decoration: none; + line-height: normal; +} + +.button[aria-disabled="true"], +input:disabled, +textarea:disabled, +select:disabled, +button[disabled] { + cursor: not-allowed; + background-color: var(--disabled); + border-color: var(--disabled); + color: var(--text-light); +} + +input[type="range"] { + padding: 0; +} + +/* Set the cursor to '?' on an abbreviation and style the abbreviation to show that there is more information underneath */ +abbr[title] { + cursor: help; + text-decoration-line: underline; + text-decoration-style: dotted; +} + +button:enabled:hover, +.button:not([aria-disabled="true"]):hover, +input[type="submit"]:enabled:hover, +input[type="reset"]:enabled:hover, +input[type="button"]:enabled:hover, +label[type="button"]:hover { + background-color: var(--accent-hover); + border-color: var(--accent-hover); + cursor: pointer; +} + +.button:focus-visible, +button:focus-visible:where(:enabled), +input:enabled:focus-visible:where( + [type="submit"], + [type="reset"], + [type="button"] +) { + outline: 2px solid var(--accent); + outline-offset: 1px; +} + +/* Format navigation */ +header > nav { + font-size: 1rem; + line-height: 2; + padding: 1rem 0 0 0; +} + +/* Use flexbox to allow items to wrap, as needed */ +header > nav ul, +header > nav ol { + align-content: space-around; + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + list-style-type: none; + margin: 0; + padding: 0; +} + +/* List items are inline elements, make them behave more like blocks */ +header > nav ul li, +header > nav ol li { + display: inline-block; +} + +header > nav a, +header > nav a:visited { + margin: 0 0.5rem 1rem 0.5rem; + border: 1px solid var(--border); + border-radius: var(--standard-border-radius); + color: var(--text); + display: inline-block; + padding: 0.1rem 1rem; + text-decoration: none; +} + +header > nav a:hover, +header > nav a.current, +header > nav a[aria-current="page"] { + border-color: var(--accent); + color: var(--accent); + cursor: pointer; +} + +/* Reduce nav side on mobile */ +@media only screen and (max-width: 720px) { + header > nav a { + border: none; + padding: 0; + text-decoration: underline; + line-height: 1; + } +} + +/* Consolidate box styling */ +aside, details, pre, progress { + background-color: var(--accent-bg); + border: 1px solid var(--border); + border-radius: var(--standard-border-radius); + margin-bottom: 1rem; +} + +aside { + font-size: 1rem; + width: 30%; + padding: 0 15px; + margin-inline-start: 15px; + float: right; +} +*[dir="rtl"] aside { + float: left; +} + +/* Make aside full-width on mobile */ +@media only screen and (max-width: 720px) { + aside { + width: 100%; + float: none; + margin-inline-start: 0; + } +} + +article, fieldset, dialog { + border: 1px solid var(--border); + padding: 1rem; + border-radius: var(--standard-border-radius); + margin-bottom: 1rem; +} + +article h2:first-child, +section h2:first-child { + margin-top: 1rem; +} + +section { + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 2rem 1rem; + margin: 3rem 0; +} + +/* Don't double separators when chaining sections */ +section + section, +section:first-child { + border-top: 0; + padding-top: 0; +} + +section:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +details { + padding: 0.7rem 1rem; +} + +summary { + cursor: pointer; + font-weight: bold; + padding: 0.7rem 1rem; + margin: -0.7rem -1rem; + word-break: break-all; +} + +details[open] > summary + * { + margin-top: 0; +} + +details[open] > summary { + margin-bottom: 0.5rem; +} + +details[open] > :last-child { + margin-bottom: 0; +} + +/* Format tables */ +table { + border-collapse: collapse; + margin: 1.5rem 0; +} + +figure > table { + width: max-content; +} + +td, +th { + border: 1px solid var(--border); + text-align: start; + padding: 0.5rem; +} + +th { + background-color: var(--accent-bg); + font-weight: bold; +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background-color: var(--accent-bg); +} + +table caption { + font-weight: bold; + margin-bottom: 0.5rem; +} + +/* Format forms */ +textarea, +select, +input, +button, +.button { + font-size: inherit; + font-family: inherit; + padding: 0.5rem; + margin-bottom: 0.5rem; + border-radius: var(--standard-border-radius); + box-shadow: none; + max-width: 100%; + display: inline-block; +} +textarea, +select, +input { + color: var(--text); + background-color: var(--bg); + border: 1px solid var(--border); +} +label { + display: block; +} +textarea:not([cols]) { + width: 100%; +} + +/* Add arrow to drop-down */ +select:not([multiple]) { + background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%), + linear-gradient(135deg, var(--text) 51%, transparent 49%); + background-position: calc(100% - 15px), calc(100% - 10px); + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; + padding-inline-end: 25px; +} +*[dir="rtl"] select:not([multiple]) { + background-position: 10px, 15px; +} + +/* checkbox and radio button style */ +input[type="checkbox"], +input[type="radio"] { + vertical-align: middle; + position: relative; + width: min-content; +} + +input[type="checkbox"] + label, +input[type="radio"] + label { + display: inline-block; +} + +input[type="radio"] { + border-radius: 100%; +} + +input[type="checkbox"]:checked, +input[type="radio"]:checked { + background-color: var(--accent); +} + +input[type="checkbox"]:checked::after { + /* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */ + content: " "; + width: 0.18em; + height: 0.32em; + border-radius: 0; + position: absolute; + top: 0.05em; + left: 0.17em; + background-color: transparent; + border-right: solid var(--bg) 0.08em; + border-bottom: solid var(--bg) 0.08em; + font-size: 1.8em; + transform: rotate(45deg); +} +input[type="radio"]:checked::after { + /* creates a colored circle for the checked radio button */ + content: " "; + width: 0.25em; + height: 0.25em; + border-radius: 100%; + position: absolute; + top: 0.125em; + background-color: var(--bg); + left: 0.125em; + font-size: 32px; +} + +/* Makes input fields wider on smaller screens */ +@media only screen and (max-width: 720px) { + textarea, + select, + input { + width: 100%; + } +} + +/* Set a height for color input */ +input[type="color"] { + height: 2.5rem; + padding: 0.2rem; +} + +/* do not show border around file selector button */ +input[type="file"] { + border: 0; +} + +/* Misc body elements */ +hr { + border: none; + height: 1px; + background: var(--border); + margin: 1rem auto; +} + +mark { + padding: 2px 5px; + border-radius: var(--standard-border-radius); + background-color: var(--marked); + color: black; +} + +mark a { + color: #0d47a1; +} + +img, +video { + max-width: 100%; + height: auto; + border-radius: var(--standard-border-radius); +} + +figure { + margin: 0; + display: block; + overflow-x: auto; +} + +figcaption { + text-align: center; + font-size: 0.9rem; + color: var(--text-light); + margin-bottom: 1rem; +} + +blockquote { + margin-inline-start: 2rem; + margin-inline-end: 0; + margin-block: 2rem; + padding: 0.4rem 0.8rem; + border-inline-start: 0.35rem solid var(--accent); + color: var(--text-light); + font-style: italic; +} + +cite { + font-size: 0.9rem; + color: var(--text-light); + font-style: normal; +} + +dt { + color: var(--text-light); +} + +/* Use mono font for code elements */ +code, +pre, +pre span, +kbd, +samp { + font-family: var(--mono-font); + color: var(--code); +} + +kbd { + color: var(--preformatted); + border: 1px solid var(--preformatted); + border-bottom: 3px solid var(--preformatted); + border-radius: var(--standard-border-radius); + padding: 0.1rem 0.4rem; +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; + color: var(--preformatted); +} + +/* Fix embedded code within pre */ +pre code { + color: var(--preformatted); + background: none; + margin: 0; + padding: 0; +} + +/* Progress bars */ +/* Declarations are repeated because you */ +/* cannot combine vendor-specific selectors */ +progress { + width: 100%; +} + +progress:indeterminate { + background-color: var(--accent-bg); +} + +progress::-webkit-progress-bar { + border-radius: var(--standard-border-radius); + background-color: var(--accent-bg); +} + +progress::-webkit-progress-value { + border-radius: var(--standard-border-radius); + background-color: var(--accent); +} + +progress::-moz-progress-bar { + border-radius: var(--standard-border-radius); + background-color: var(--accent); + transition-property: width; + transition-duration: 0.3s; +} + +progress:indeterminate::-moz-progress-bar { + background-color: var(--accent-bg); +} + +dialog { + max-width: 40rem; + margin: auto; +} + +dialog::backdrop { + background-color: var(--bg); + opacity: 0.8; +} + +@media only screen and (max-width: 720px) { + dialog { + max-width: 100%; + margin: auto 1em; + } +} + +/* Superscript & Subscript */ +/* Prevent scripts from affecting line-height. */ +sup, sub { + vertical-align: baseline; + position: relative; +} + +sup { + top: -0.4em; +} + +sub { + top: 0.3em; +} + +/* Classes for notices */ +.notice { + background: var(--accent-bg); + border: 2px solid var(--border); + border-radius: var(--standard-border-radius); + padding: 1.5rem; + margin: 2rem 0; +} diff --git a/resources/views/components/info.blade.php b/resources/views/components/info.blade.php new file mode 100644 index 00000000..58bc0fca --- /dev/null +++ b/resources/views/components/info.blade.php @@ -0,0 +1,50 @@ + + + + + {{ $title }} + + + + + + + + + +
+ SiteBoss +
+
+

{{ $title }}

+ {{ $slot }} +
+ + + + diff --git a/resources/views/pages/messaage-button.blade.php b/resources/views/pages/messaage-button.blade.php new file mode 100644 index 00000000..dbda49cd --- /dev/null +++ b/resources/views/pages/messaage-button.blade.php @@ -0,0 +1,10 @@ + +

+ {{ $message }} +

+ + {{ $buttonText }} +
diff --git a/resources/views/pages/message.blade.php b/resources/views/pages/message.blade.php new file mode 100644 index 00000000..cc850ecf --- /dev/null +++ b/resources/views/pages/message.blade.php @@ -0,0 +1,5 @@ + +

+ {{ $message }} +

+
diff --git a/routes/api.php b/routes/api.php index b4c6b514..d3728b51 100644 --- a/routes/api.php +++ b/routes/api.php @@ -28,11 +28,21 @@ | */ Route::prefix(config('siteboss.api_prefix'))->group(function () { - Route::prefix('api')->group(function () { + + // Routes account management + Route::group(['prefix' => '/{locale}', 'middleware' => [ValidateSignature::class, 'throttle:6,1', 'set-forget-locale']], function () { + + // Verify email address Route::get('email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke']) - ->middleware([ValidateSignature::class, 'throttle:6,1']) ->name('siteboss.verification.verify'); + // Routes for blocking your own account + Route::get('email/verify/block/{id}/{hash}', [VerifyEmailController::class, 'block']) + ->name('siteboss.verification.block'); + }); + + Route::prefix('api')->group(function () { + // Unauthenticated routes Route::namespace('Forms')->group(function () { Route::post('forms/{form:id}/{langurl}', [DataController::class, 'create'])->middleware(ProtectAgainstSpam::class)->name('formbuilder.post'); diff --git a/src/Auth/Notifications/VerifyEmail.php b/src/Auth/Notifications/VerifyEmail.php index b41ac5cf..902bba00 100644 --- a/src/Auth/Notifications/VerifyEmail.php +++ b/src/Auth/Notifications/VerifyEmail.php @@ -85,6 +85,7 @@ protected function verificationUrl($notifiable) Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)), [ 'id' => $notifiable->getKey(), + 'locale' => app()->getLocale(), 'hash' => sha1($notifiable->getEmailForVerification()), ] ); diff --git a/src/FrameworkServiceProvider.php b/src/FrameworkServiceProvider.php index 91925468..f6d4935d 100644 --- a/src/FrameworkServiceProvider.php +++ b/src/FrameworkServiceProvider.php @@ -35,6 +35,7 @@ public function boot(): void __DIR__.'/../config/database.php' => config_path('database.php'), __DIR__.'/../config/indexer.php' => config_path('indexer.php'), __DIR__.'/../config/laravellocalization.php' => config_path('laravellocalization.php'), + __DIR__.'/../resources/css/siteboss.css' => public_path('assets/static/siteboss.css'), __DIR__.'/Providers/AuthServiceProvider.php' => app_path('Providers/AuthServiceProvider.php'), ], 'siteboss-framework'); diff --git a/src/Http/Controllers/Auth/VerifyEmailController.php b/src/Http/Controllers/Auth/VerifyEmailController.php index d263e74b..7cffb5c3 100644 --- a/src/Http/Controllers/Auth/VerifyEmailController.php +++ b/src/Http/Controllers/Auth/VerifyEmailController.php @@ -5,7 +5,10 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\Events\Verified; use Illuminate\Http\Request; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\URL; use NotFound\Framework\Http\Controllers\Controller; use NotFound\Framework\Mail\Admin\AccountBlocked; use NotFound\Framework\Models\CmsUser; @@ -22,13 +25,22 @@ public function __invoke(Request $request) $user = CmsUser::find($request->route('id')); if ($request->query('block')) { - $user->enabled = 0; - $user->email_verified_at = null; - $user->save(); + $link = URL::temporarySignedRoute( + 'siteboss.verification.block', + Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)), + [ + 'locale' => app()->getLocale(), + 'id' => $request->route('id'), + 'hash' => $request->route('hash'), + ]); - Mail::to(env('SB_ADMIN_EMAIL'))->send(new AccountBlocked($user)); - - return ['status' => 'ok', 'message' => __('siteboss::auth.block_account_message')]; + return view('siteboss::pages.message-button', [ + 'result' => 'error', + 'title' => __('siteboss::auth.verify_block_account_title'), + 'message' => __('siteboss::auth.verify_block_account_message'), + 'buttonText' => __('siteboss::auth.verify_block_account_button'), + 'link' => $link, + ]); } if (! $user) { @@ -43,6 +55,36 @@ public function __invoke(Request $request) event(new Verified($user)); } - return redirect('/siteboss')->with('verified', true); + return view('siteboss::pages.message', + [ + 'title' => __('siteboss::auth.verify_email_success'), + 'message' => __('siteboss::auth.verify_email_success'), + ] + ); + } + + public function block(Request $request) + { + $user = CmsUser::find($request->route('id')); + + if (! $user) { + throw new AuthorizationException; + } + + if (! hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) { + throw new AuthorizationException; + } + + $user->enabled = 0; + $user->email_verified_at = null; + $user->save(); + + Mail::to(config('siteboss.admin_email'))->send(new AccountBlocked($user)); + + return view('siteboss::pages.message', + [ + 'title' => __('siteboss::auth.block_account_title'), + 'message' => __('siteboss::auth.block_account_message'), + ]); } } diff --git a/src/Services/Assets/AbstractAssetService.php b/src/Services/Assets/AbstractAssetService.php index f343b4eb..76b9f609 100644 --- a/src/Services/Assets/AbstractAssetService.php +++ b/src/Services/Assets/AbstractAssetService.php @@ -57,7 +57,7 @@ public function getLang(): Lang /** * Loops through all the table items and return them with the appropriate Input Class */ - public function getFieldComponents(?int $recordId = null): Collection + public function getFieldComponents(int $recordId = null): Collection { $items = $this->assetModel->items()->where('enabled', 1)->orderBy('order')->get();