浏览代码

Merge branch 'development' into main

Dennis 2 年之前
父节点
当前提交
3810b487cd
共有 100 个文件被更改,包括 4854 次插入2455 次删除
  1. 3 0
      .env.example
  2. 69 57
      app/Classes/PterodactylClient.php
  3. 0 47
      app/Classes/Settings/Invoices.php
  4. 0 56
      app/Classes/Settings/Language.php
  5. 0 107
      app/Classes/Settings/Misc.php
  6. 0 58
      app/Classes/Settings/Payments.php
  7. 0 154
      app/Classes/Settings/System.php
  8. 7 7
      app/Console/Commands/MakeUserCommand.php
  9. 146 0
      app/Extensions/PaymentGateways/Mollie/MollieExtension.php
  10. 41 0
      app/Extensions/PaymentGateways/Mollie/MollieSettings.php
  11. 18 0
      app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php
  12. 22 0
      app/Extensions/PaymentGateways/Mollie/web_routes.php
  13. 197 0
      app/Extensions/PaymentGateways/PayPal/PayPalExtension.php
  14. 67 0
      app/Extensions/PaymentGateways/PayPal/PayPalSettings.php
  15. 0 13
      app/Extensions/PaymentGateways/PayPal/config.php
  16. 0 186
      app/Extensions/PaymentGateways/PayPal/index.php
  17. 100 0
      app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php
  18. 3 4
      app/Extensions/PaymentGateways/PayPal/web_routes.php
  19. 390 0
      app/Extensions/PaymentGateways/Stripe/StripeExtension.php
  20. 63 0
      app/Extensions/PaymentGateways/Stripe/StripeSettings.php
  21. 0 15
      app/Extensions/PaymentGateways/Stripe/config.php
  22. 0 373
      app/Extensions/PaymentGateways/Stripe/index.php
  23. 98 0
      app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php
  24. 4 4
      app/Extensions/PaymentGateways/Stripe/web_routes.php
  25. 9 0
      app/Helpers/AbstractExtension.php
  26. 165 35
      app/Helpers/ExtensionHelper.php
  27. 5 2
      app/Http/Controllers/Admin/ApplicationApiController.php
  28. 2 2
      app/Http/Controllers/Admin/InvoiceController.php
  29. 19 9
      app/Http/Controllers/Admin/OverViewController.php
  30. 11 7
      app/Http/Controllers/Admin/PartnerController.php
  31. 10 9
      app/Http/Controllers/Admin/PaymentController.php
  32. 28 21
      app/Http/Controllers/Admin/ProductController.php
  33. 19 9
      app/Http/Controllers/Admin/ServerController.php
  34. 95 19
      app/Http/Controllers/Admin/SettingsController.php
  35. 11 13
      app/Http/Controllers/Admin/ShopProductController.php
  36. 5 2
      app/Http/Controllers/Admin/UsefulLinkController.php
  37. 30 14
      app/Http/Controllers/Admin/UserController.php
  38. 19 9
      app/Http/Controllers/Admin/VoucherController.php
  39. 7 2
      app/Http/Controllers/Api/NotificationController.php
  40. 21 11
      app/Http/Controllers/Api/UserController.php
  41. 3 2
      app/Http/Controllers/Auth/ForgotPasswordController.php
  42. 3 2
      app/Http/Controllers/Auth/LoginController.php
  43. 47 15
      app/Http/Controllers/Auth/RegisterController.php
  44. 10 8
      app/Http/Controllers/Auth/SocialiteController.php
  45. 7 2
      app/Http/Controllers/HomeController.php
  46. 15 9
      app/Http/Controllers/Moderation/TicketsController.php
  47. 15 7
      app/Http/Controllers/ProductController.php
  48. 28 13
      app/Http/Controllers/ProfileController.php
  49. 56 39
      app/Http/Controllers/ServerController.php
  50. 8 13
      app/Http/Controllers/StoreController.php
  51. 36 25
      app/Http/Controllers/TicketsController.php
  52. 0 2
      app/Http/Middleware/GlobalNames.php
  53. 12 5
      app/Http/Middleware/SetLocale.php
  54. 17 3
      app/Listeners/CreateInvoice.php
  55. 14 1
      app/Listeners/UnsuspendServers.php
  56. 35 10
      app/Listeners/UserPayment.php
  57. 11 4
      app/Listeners/Verified.php
  58. 2 2
      app/Models/PartnerDiscount.php
  59. 2 0
      app/Models/Product.php
  60. 7 5
      app/Models/Pterodactyl/Egg.php
  61. 4 3
      app/Models/Pterodactyl/Location.php
  62. 4 3
      app/Models/Pterodactyl/Nest.php
  63. 5 3
      app/Models/Pterodactyl/Node.php
  64. 21 10
      app/Models/Server.php
  65. 0 40
      app/Models/Settings.php
  66. 15 24
      app/Models/User.php
  67. 7 3
      app/Notifications/ReferralNotification.php
  68. 26 9
      app/Notifications/WelcomeMessage.php
  69. 14 89
      app/Providers/AppServiceProvider.php
  70. 87 0
      app/Settings/DiscordSettings.php
  71. 138 0
      app/Settings/GeneralSettings.php
  72. 92 0
      app/Settings/InvoiceSettings.php
  73. 73 0
      app/Settings/LocaleSettings.php
  74. 119 0
      app/Settings/MailSettings.php
  75. 82 0
      app/Settings/PterodactylSettings.php
  76. 87 0
      app/Settings/ReferralSettings.php
  77. 64 0
      app/Settings/ServerSettings.php
  78. 56 0
      app/Settings/TicketSettings.php
  79. 120 0
      app/Settings/UserSettings.php
  80. 103 0
      app/Settings/WebsiteSettings.php
  81. 14 12
      app/Traits/Invoiceable.php
  82. 29 27
      composer.json
  83. 367 157
      composer.lock
  84. 1 2
      config/app.php
  85. 1 1
      config/mail.php
  86. 111 0
      config/settings.php
  87. 45 0
      database/migrations/2023_02_01_155730_create_new_settings_table.php
  88. 34 0
      database/migrations/2023_03_15_150022_delete_old_settings_table.php
  89. 32 0
      database/migrations/2023_04_03_231829_update_users_table.php
  90. 2 4
      database/seeders/DatabaseSeeder.php
  91. 0 656
      database/seeders/Seeds/SettingsSeeder.php
  92. 139 0
      database/settings/2023_02_01_164731_create_general_settings.php
  93. 98 0
      database/settings/2023_02_01_181334_create_pterodactyl_settings.php
  94. 136 0
      database/settings/2023_02_01_181453_create_mail_settings.php
  95. 163 0
      database/settings/2023_02_01_181925_create_user_settings.php
  96. 96 0
      database/settings/2023_02_01_181950_create_server_settings.php
  97. 128 0
      database/settings/2023_02_01_182021_create_invoice_settings.php
  98. 113 0
      database/settings/2023_02_01_182043_create_discord_settings.php
  99. 104 0
      database/settings/2023_02_01_182108_create_locale_settings.php
  100. 112 0
      database/settings/2023_02_01_182135_create_referral_settings.php

+ 3 - 0
.env.example

@@ -62,3 +62,6 @@ PUSHER_APP_CLUSTER=mt1
 
 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+
+# Settings Cache
+SETTINGS_CACHE_ENABLED=true

+ 69 - 57
app/Classes/Pterodactyl.php → app/Classes/PterodactylClient.php

@@ -2,46 +2,68 @@
 
 namespace App\Classes;
 
-use App\Models\Egg;
-use App\Models\Nest;
-use App\Models\Node;
+use App\Models\Pterodactyl\Egg;
+use App\Models\Pterodactyl\Nest;
+use App\Models\Pterodactyl\Node;
 use App\Models\Product;
 use App\Models\Server;
-use App\Models\User;
 use Exception;
 use Illuminate\Http\Client\PendingRequest;
 use Illuminate\Http\Client\Response;
 use Illuminate\Support\Facades\Http;
+use App\Settings\PterodactylSettings;
+use App\Settings\ServerSettings;
 
-class Pterodactyl
+class PterodactylClient
 {
     //TODO: Extend error handling (maybe logger for more errors when debugging)
 
+    private int $per_page_limit = 200;
+
+    private int $allocation_limit = 200;
+
+    public PendingRequest $client;
+
+    public PendingRequest $application;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $server_settings = new ServerSettings();
+
+        try {
+            $this->client = $this->client($ptero_settings);
+            $this->application = $this->clientAdmin($ptero_settings);
+            $this->per_page_limit = $ptero_settings->per_page_limit;
+            $this->allocation_limit = $server_settings->allocation_limit;
+        } catch (Exception $exception) {
+            logger('Failed to construct Pterodactyl client, Settings table not available?', ['exception' => $exception]);
+        }
+    }
     /**
      * @return PendingRequest
      */
-    public static function client()
+    public function client(PterodactylSettings $ptero_settings)
     {
         return Http::withHeaders([
-            'Authorization' => 'Bearer ' . config('SETTINGS::SYSTEM:PTERODACTYL:TOKEN'),
+            'Authorization' => 'Bearer ' . $ptero_settings->user_token,
             'Content-type' => 'application/json',
             'Accept' => 'Application/vnd.pterodactyl.v1+json',
-        ])->baseUrl(config('SETTINGS::SYSTEM:PTERODACTYL:URL') . '/api');
+        ])->baseUrl($ptero_settings->getUrl() . 'api' . '/');
     }
 
-    public static function clientAdmin()
+    public function clientAdmin(PterodactylSettings $ptero_settings)
     {
         return Http::withHeaders([
-            'Authorization' => 'Bearer ' . config('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN'),
+            'Authorization' => 'Bearer ' . $ptero_settings->admin_token,
             'Content-type' => 'application/json',
             'Accept' => 'Application/vnd.pterodactyl.v1+json',
-        ])->baseUrl(config('SETTINGS::SYSTEM:PTERODACTYL:URL') . '/api');
+        ])->baseUrl($ptero_settings->getUrl() . 'api' . '/');
     }
 
     /**
      * @return Exception
      */
-    private static function getException(string $message = '', int $status = 0): Exception
+    private function getException(string $message = '', int $status = 0): Exception
     {
         if ($status == 404) {
             return new Exception('Ressource does not exist on pterodactyl - ' . $message, 404);
@@ -68,10 +90,10 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getEggs(Nest $nest)
+    public function getEggs(Nest $nest)
     {
         try {
-            $response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables&per_page=" . config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
+            $response = $this->application->get("application/nests/{$nest->id}/eggs?include=nest,variables&per_page=" . $this->per_page_limit);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -87,10 +109,10 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getNodes()
+    public function getNodes()
     {
         try {
-            $response = self::client()->get('/application/nodes?per_page=' . config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
+            $response = $this->application->get('application/nodes?per_page=' . $this->per_page_limit);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -107,10 +129,10 @@ class Pterodactyl
      * @throws Exception
      * @description Returns the infos of a single node
      */
-    public static function getNode($id)
+    public function getNode($id)
     {
         try {
-            $response = self::client()->get('/application/nodes/' . $id);
+            $response = $this->application->get('application/nodes/' . $id);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -121,10 +143,10 @@ class Pterodactyl
         return $response->json()['attributes'];
     }
 
-    public static function getServers()
+    public function getServers()
     {
         try {
-            $response = self::client()->get('/application/servers?per_page=' . config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
+            $response = $this->application->get('application/servers?per_page=' . $this->per_page_limit);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -140,10 +162,10 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getNests()
+    public function getNests()
     {
         try {
-            $response = self::client()->get('/application/nests?per_page=' . config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
+            $response = $this->application->get('application/nests?per_page=' . $this->per_page_limit);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -159,10 +181,10 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getLocations()
+    public function getLocations()
     {
         try {
-            $response = self::client()->get('/application/locations?per_page=' . config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
+            $response = $this->application->get('application/locations?per_page=' . $this->per_page_limit);
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -179,7 +201,7 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getFreeAllocationId(Node $node)
+    public function getFreeAllocationId(Node $node)
     {
         return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
     }
@@ -190,7 +212,7 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getFreeAllocations(Node $node)
+    public function getFreeAllocations(Node $node)
     {
         $response = self::getAllocations($node);
         $freeAllocations = [];
@@ -214,11 +236,10 @@ class Pterodactyl
      *
      * @throws Exception
      */
-    public static function getAllocations(Node $node)
+    public function getAllocations(Node $node)
     {
-        $per_page = config('SETTINGS::SERVER:ALLOCATION_LIMIT', 200);
         try {
-            $response = self::client()->get("/application/nodes/{$node->id}/allocations?per_page={$per_page}");
+            $response = $this->application->get("application/nodes/{$node->id}/allocations?per_page={$this->allocation_limit}");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -229,24 +250,15 @@ class Pterodactyl
         return $response->json();
     }
 
-    /**
-     * @param  string  $route
-     * @return string
-     */
-    public static function url(string $route): string
-    {
-        return config('SETTINGS::SYSTEM:PTERODACTYL:URL') . $route;
-    }
-
     /**
      * @param  Server  $server
      * @param  Egg  $egg
      * @param  int  $allocationId
      * @return Response
      */
-    public static function createServer(Server $server, Egg $egg, int $allocationId)
+    public function createServer(Server $server, Egg $egg, int $allocationId)
     {
-        return self::client()->post('/application/servers', [
+        return $this->application->post('application/servers', [
             'name' => $server->name,
             'external_id' => $server->id,
             'user' => $server->user->pterodactyl_id,
@@ -272,10 +284,10 @@ class Pterodactyl
         ]);
     }
 
-    public static function suspendServer(Server $server)
+    public function suspendServer(Server $server)
     {
         try {
-            $response = self::client()->post("/application/servers/$server->pterodactyl_id/suspend");
+            $response = $this->application->post("application/servers/$server->pterodactyl_id/suspend");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -286,10 +298,10 @@ class Pterodactyl
         return $response;
     }
 
-    public static function unSuspendServer(Server $server)
+    public function unSuspendServer(Server $server)
     {
         try {
-            $response = self::client()->post("/application/servers/$server->pterodactyl_id/unsuspend");
+            $response = $this->application->post("application/servers/$server->pterodactyl_id/unsuspend");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -309,7 +321,7 @@ class Pterodactyl
     public function getUser(int $pterodactylId)
     {
         try {
-            $response = self::client()->get("/application/users/{$pterodactylId}");
+            $response = $this->application->get("application/users/{$pterodactylId}");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -326,10 +338,10 @@ class Pterodactyl
      * @param  int  $pterodactylId
      * @return mixed
      */
-    public static function getServerAttributes(int $pterodactylId, bool $deleteOn404 = false)
+    public function getServerAttributes(int $pterodactylId, bool $deleteOn404 = false)
     {
         try {
-            $response = self::client()->get("/application/servers/{$pterodactylId}?include=egg,node,nest,location");
+            $response = $this->application->get("application/servers/{$pterodactylId}?include=egg,node,nest,location");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }
@@ -356,9 +368,9 @@ class Pterodactyl
      * @param  Product  $product
      * @return Response
      */
-    public static function updateServer(Server $server, Product $product)
+    public function updateServer(Server $server, Product $product)
     {
-        return self::client()->patch("/application/servers/{$server->pterodactyl_id}/build", [
+        return $this->application->patch("application/servers/{$server->pterodactyl_id}/build", [
             'allocation' => $server->allocation,
             'memory' => $product->memory,
             'swap' => $product->swap,
@@ -381,9 +393,9 @@ class Pterodactyl
      * @param  Server  $server
      * @return mixed
      */
-    public static function updateServerOwner(Server $server, int $userId)
+    public function updateServerOwner(Server $server, int $userId)
     {
-        return self::client()->patch("/application/servers/{$server->pterodactyl_id}/details", [
+        return $this->application->patch("application/servers/{$server->pterodactyl_id}/details", [
             'name' => $server->name,
             'user' => $userId,
         ]);
@@ -396,9 +408,9 @@ class Pterodactyl
      * @param  string  $action
      * @return Response
      */
-    public static function powerAction(Server $server, $action)
+    public function powerAction(Server $server, $action)
     {
-        return self::clientAdmin()->post("/client/servers/{$server->identifier}/power", [
+        return $this->client->post("client/servers/{$server->identifier}/power", [
             'signal' => $action,
         ]);
     }
@@ -406,9 +418,9 @@ class Pterodactyl
     /**
      * Get info about user
      */
-    public static function getClientUser()
+    public function getClientUser()
     {
-        return self::clientAdmin()->get('/client/account');
+        return $this->client->get('client/account');
     }
 
     /**
@@ -419,10 +431,10 @@ class Pterodactyl
      * @param  int  $requireDisk
      * @return bool
      */
-    public static function checkNodeResources(Node $node, int $requireMemory, int $requireDisk)
+    public function checkNodeResources(Node $node, int $requireMemory, int $requireDisk)
     {
         try {
-            $response = self::client()->get("/application/nodes/{$node->id}");
+            $response = $this->application->get("application/nodes/{$node->id}");
         } catch (Exception $e) {
             throw self::getException($e->getMessage());
         }

+ 0 - 47
app/Classes/Settings/Invoices.php

@@ -1,47 +0,0 @@
-<?php
-
-namespace App\Classes\Settings;
-
-use App\Models\Settings;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-
-class Invoices
-{
-    public function __construct()
-    {
-
-    }
-
-    public function updateSettings(Request $request)
-    {
-        $request->validate([
-            'logo' => 'nullable|max:10000|mimes:jpg,png,jpeg',
-        ]);
-
-        $values = [
-            //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form)
-            'SETTINGS::INVOICE:COMPANY_NAME' => 'company-name',
-            'SETTINGS::INVOICE:COMPANY_ADDRESS' => 'company-address',
-            'SETTINGS::INVOICE:COMPANY_PHONE' => 'company-phone',
-            'SETTINGS::INVOICE:COMPANY_MAIL' => 'company-mail',
-            'SETTINGS::INVOICE:COMPANY_VAT' => 'company-vat',
-            'SETTINGS::INVOICE:COMPANY_WEBSITE' => 'company-web',
-            'SETTINGS::INVOICE:PREFIX' => 'invoice-prefix',
-            'SETTINGS::INVOICE:ENABLED' => 'enable-invoices',
-        ];
-
-        foreach ($values as $key => $value) {
-            $param = $request->get($value);
-
-            Settings::where('key', $key)->updateOrCreate(['key' => $key], ['value' => $param]);
-            Cache::forget('setting'.':'.$key);
-        }
-
-        if ($request->hasFile('logo')) {
-            $request->file('logo')->storeAs('public', 'logo.png');
-        }
-
-        return redirect(route('admin.settings.index').'#invoices')->with('success', __('Invoice settings updated!'));
-    }
-}

+ 0 - 56
app/Classes/Settings/Language.php

@@ -1,56 +0,0 @@
-<?php
-
-namespace App\Classes\Settings;
-
-use App\Models\Settings;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Session;
-use Illuminate\Support\Facades\Validator;
-
-class Language
-{
-    public function __construct()
-    {
-
-    }
-
-    public function updateSettings(Request $request)
-    {
-        $validator = Validator::make($request->all(), [
-            'autotranslate' => 'string',
-            'canClientChangeLanguage' => 'string',
-            'defaultLanguage' => 'required|string',
-            'languages' => 'required|array',
-            'languages.*' => 'required|string',
-            'datatable-language' => 'required|string',
-        ]);
-
-        if ($validator->fails()) {
-            return redirect(route('admin.settings.index').'#language')->with('error', __('Language settings have not been updated!'))->withErrors($validator);
-        }
-
-        $values = [
-            //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form)
-            'SETTINGS::LOCALE:DEFAULT' => 'defaultLanguage',
-            'SETTINGS::LOCALE:DYNAMIC' => 'autotranslate',
-            'SETTINGS::LOCALE:CLIENTS_CAN_CHANGE' => 'canClientChangeLanguage',
-            'SETTINGS::LOCALE:AVAILABLE' => 'languages',
-            'SETTINGS::LOCALE:DATATABLES' => 'datatable-language',
-        ];
-
-        foreach ($values as $key => $value) {
-            $param = $request->get($value);
-
-            if (is_array($param)) {
-                $param = implode(',', $param);
-            }
-
-            Settings::where('key', $key)->updateOrCreate(['key' => $key], ['value' => $param]);
-            Cache::forget('setting'.':'.$key);
-            Session::remove('locale');
-        }
-
-        return redirect(route('admin.settings.index').'#language')->with('success', __('Language settings updated!'));
-    }
-}

+ 0 - 107
app/Classes/Settings/Misc.php

@@ -1,107 +0,0 @@
-<?php
-
-namespace App\Classes\Settings;
-
-use App\Models\Settings;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Validator;
-
-class Misc
-{
-    public function __construct()
-    {
-
-    }
-
-    public function updateSettings(Request $request)
-    {
-        $validator = Validator::make($request->all(), [
-            'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg',
-            'favicon' => 'nullable|max:10000|mimes:ico',
-            'discord-bot-token' => 'nullable|string',
-            'discord-client-id' => 'nullable|string',
-            'discord-client-secret' => 'nullable|string',
-            'discord-guild-id' => 'nullable|string',
-            'discord-invite-url' => 'nullable|string',
-            'discord-role-id' => 'nullable|string',
-            'recaptcha-site-key' => 'nullable|string',
-            'recaptcha-secret-key' => 'nullable|string',
-            'enable-recaptcha' => 'nullable|string',
-            'mailservice' => 'nullable|string',
-            'mailhost' => 'nullable|string',
-            'mailport' => 'nullable|string',
-            'mailusername' => 'nullable|string',
-            'mailpassword' => 'nullable|string',
-            'mailencryption' => 'nullable|string',
-            'mailfromadress' => 'nullable|string',
-            'mailfromname' => 'nullable|string',
-            'enable_referral' => 'nullable|string',
-            'referral_reward' => 'nullable|numeric',
-            'referral_allowed' => 'nullable|string',
-            'always_give_commission' => 'nullable|string',
-            'referral_percentage' => 'nullable|numeric',
-            'referral_mode' => 'nullable|string',
-            'ticket_enabled' => 'nullable|string',
-            'ticket_notify' => 'string',
-        ]);
-
-        $validator->after(function ($validator) use ($request) {
-            // if enable-recaptcha is true then recaptcha-site-key and recaptcha-secret-key must be set
-            if ($request->get('enable-recaptcha') == 'true' && (! $request->get('recaptcha-site-key') || ! $request->get('recaptcha-secret-key'))) {
-                $validator->errors()->add('recaptcha-site-key', 'The site key is required if recaptcha is enabled.');
-                $validator->errors()->add('recaptcha-secret-key', 'The secret key is required if recaptcha is enabled.');
-            }
-        });
-
-        if ($validator->fails()) {
-            return redirect(route('admin.settings.index').'#misc')->with('error', __('Misc settings have not been updated!'))->withErrors($validator)
-                ->withInput();
-        }
-
-        if ($request->hasFile('icon')) {
-            $request->file('icon')->storeAs('public', 'icon.png');
-        }
-        if ($request->hasFile('favicon')) {
-            $request->file('favicon')->storeAs('public', 'favicon.ico');
-        }
-
-        $values = [
-            'SETTINGS::DISCORD:BOT_TOKEN' => 'discord-bot-token',
-            'SETTINGS::DISCORD:CLIENT_ID' => 'discord-client-id',
-            'SETTINGS::DISCORD:CLIENT_SECRET' => 'discord-client-secret',
-            'SETTINGS::DISCORD:GUILD_ID' => 'discord-guild-id',
-            'SETTINGS::DISCORD:INVITE_URL' => 'discord-invite-url',
-            'SETTINGS::DISCORD:ROLE_ID' => 'discord-role-id',
-            'SETTINGS::RECAPTCHA:SITE_KEY' => 'recaptcha-site-key',
-            'SETTINGS::RECAPTCHA:SECRET_KEY' => 'recaptcha-secret-key',
-            'SETTINGS::RECAPTCHA:ENABLED' => 'enable-recaptcha',
-            'SETTINGS::MAIL:MAILER' => 'mailservice',
-            'SETTINGS::MAIL:HOST' => 'mailhost',
-            'SETTINGS::MAIL:PORT' => 'mailport',
-            'SETTINGS::MAIL:USERNAME' => 'mailusername',
-            'SETTINGS::MAIL:PASSWORD' => 'mailpassword',
-            'SETTINGS::MAIL:ENCRYPTION' => 'mailencryption',
-            'SETTINGS::MAIL:FROM_ADDRESS' => 'mailfromadress',
-            'SETTINGS::MAIL:FROM_NAME' => 'mailfromname',
-            'SETTINGS::REFERRAL::ENABLED' => 'enable_referral',
-            'SETTINGS::REFERRAL::REWARD' => 'referral_reward',
-            'SETTINGS::REFERRAL::ALLOWED' => 'referral_allowed',
-            'SETTINGS::REFERRAL:MODE' => 'referral_mode',
-            'SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION' => 'always_give_commission',
-            'SETTINGS::REFERRAL:PERCENTAGE' => 'referral_percentage',
-            'SETTINGS::TICKET:ENABLED' => 'ticket_enabled',
-            'SETTINGS::TICKET:NOTIFY' => 'ticket_notify',
-
-        ];
-
-        foreach ($values as $key => $value) {
-            $param = $request->get($value);
-
-            Settings::where('key', $key)->updateOrCreate(['key' => $key], ['value' => $param]);
-            Cache::forget('setting'.':'.$key);
-        }
-
-        return redirect(route('admin.settings.index').'#misc')->with('success', __('Misc settings updated!'));
-    }
-}

+ 0 - 58
app/Classes/Settings/Payments.php

@@ -1,58 +0,0 @@
-<?php
-
-namespace App\Classes\Settings;
-
-use App\Models\Settings;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Validator;
-
-class Payments
-{
-    public function __construct()
-    {
-
-    }
-
-    public function updateSettings(Request $request)
-    {
-        $validator = Validator::make($request->all(), [
-            'paypal-client_id' => 'nullable|string',
-            'paypal-client-secret' => 'nullable|string',
-            'paypal-sandbox-secret' => 'nullable|string',
-            'stripe-secret-key' => 'nullable|string',
-            'stripe-endpoint-secret' => 'nullable|string',
-            'stripe-test-secret-key' => 'nullable|string',
-            'stripe-test-endpoint-secret' => 'nullable|string',
-            'stripe-methods' => 'nullable|string',
-            'sales-tax' => 'nullable|numeric',
-        ]);
-        if ($validator->fails()) {
-            return redirect(route('admin.settings.index').'#payment')->with('error', __('Payment settings have not been updated!'))->withErrors($validator)
-                ->withInput();
-        }
-
-        $values = [
-            //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form)
-            'SETTINGS::PAYMENTS:PAYPAL:SECRET' => 'paypal-client-secret',
-            'SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID' => 'paypal-client-id',
-            'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET' => 'paypal-sandbox-secret',
-            'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID' => 'paypal-sandbox-id',
-            'SETTINGS::PAYMENTS:STRIPE:SECRET' => 'stripe-secret',
-            'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET' => 'stripe-endpoint-secret',
-            'SETTINGS::PAYMENTS:STRIPE:TEST_SECRET' => 'stripe-test-secret',
-            'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET' => 'stripe-endpoint-test-secret',
-            'SETTINGS::PAYMENTS:STRIPE:METHODS' => 'stripe-methods',
-            'SETTINGS::PAYMENTS:SALES_TAX' => 'sales-tax',
-        ];
-
-        foreach ($values as $key => $value) {
-            $param = $request->get($value);
-
-            Settings::where('key', $key)->updateOrCreate(['key' => $key], ['value' => $param]);
-            Cache::forget('setting'.':'.$key);
-        }
-
-        return redirect(route('admin.settings.index').'#payment')->with('success', __('Payment settings updated!'));
-    }
-}

+ 0 - 154
app/Classes/Settings/System.php

@@ -1,154 +0,0 @@
-<?php
-
-namespace App\Classes\Settings;
-
-use App\Classes\Pterodactyl;
-use App\Models\Settings;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Validator;
-use Qirolab\Theme\Theme;
-
-
-class System
-{
-    public function __construct()
-    {
-
-    }
-
-    public function checkPteroClientkey()
-    {
-        $response = Pterodactyl::getClientUser();
-
-        if ($response->failed()) {
-            return redirect()->back()->with('error', __('Your Key or URL is not correct'));
-        }
-
-        return redirect()->back()->with('success', __('Everything is good!'));
-    }
-
-    public function updateSettings(Request $request)
-    {
-        $validator = Validator::make($request->all(), [
-            'register-ip-check' => 'string',
-            'server-create-charge-first-hour' => 'string',
-            'credits-display-name' => 'required|string',
-            'allocation-limit' => 'required|min:0|integer',
-            'force-email-verification' => 'string',
-            'force-discord-verification' => 'string',
-            'initial-credits' => 'required|min:0|integer',
-            'initial-server-limit' => 'required|min:0|integer',
-            'credits-reward-amount-discord' => 'required|min:0|integer',
-            'credits-reward-amount-email' => 'required|min:0|integer',
-            'server-limit-discord' => 'required|min:0|integer',
-            'server-limit-email' => 'required|min:0|integer',
-            'server-limit-purchase' => 'required|min:0|integer',
-            'pterodactyl-api-key' => 'required|string',
-            'pterodactyl-url' => 'required|string',
-            'per-page-limit' => 'required|min:0|integer',
-            'pterodactyl-admin-api-key' => 'required|string',
-            'enable-upgrades' => 'string',
-            'enable-disable-servers' => 'string',
-            'enable-disable-new-users' => 'string',
-            'show-imprint' => 'string',
-            'show-privacy' => 'string',
-            'show-tos' => 'string',
-            'alert-enabled' => 'string',
-            'alter-type' => 'string',
-            'alert-message' => 'string|nullable',
-            'motd-enabled' => 'string',
-            'usefullinks-enabled' => 'string',
-            'motd-message' => 'string|nullable',
-            'seo-title' => 'string|nullable',
-            'seo-description' => 'string|nullable',
-        ]);
-
-        $validator->after(function ($validator) use ($request) {
-            // if enable-recaptcha is true then recaptcha-site-key and recaptcha-secret-key must be set
-            if ($request->get('enable-upgrades') == 'true' && (! $request->get('pterodactyl-admin-api-key'))) {
-                $validator->errors()->add('pterodactyl-admin-api-key', 'The admin api key is required when upgrades are enabled.');
-            }
-        });
-
-        if ($validator->fails()) {
-            return redirect(route('admin.settings.index').'#system')->with('error', __('System settings have not been updated!'))->withErrors($validator)
-                ->withInput();
-        }
-
-        // update Icons from request
-        $this->updateIcons($request);
-
-        $values = [
-
-            "SETTINGS::SYSTEM:REGISTER_IP_CHECK" => "register-ip-check",
-            "SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR" => "server-create-charge-first-hour",
-            "SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME" => "credits-display-name",
-            "SETTINGS::SERVER:ALLOCATION_LIMIT" => "allocation-limit",
-            "SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER" => "minimum-credits",
-            "SETTINGS::USER:FORCE_DISCORD_VERIFICATION" => "force-discord-verification",
-            "SETTINGS::USER:FORCE_EMAIL_VERIFICATION" => "force-email-verification",
-            "SETTINGS::USER:INITIAL_CREDITS" => "initial-credits",
-            "SETTINGS::USER:INITIAL_SERVER_LIMIT" => "initial-server-limit",
-            "SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD" => "credits-reward-amount-discord",
-            "SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL" => "credits-reward-amount-email",
-            "SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD" => "server-limit-discord",
-            "SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL" => "server-limit-email",
-            "SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE" => "server-limit-purchase",
-            "SETTINGS::MISC:PHPMYADMIN:URL" => "phpmyadmin-url",
-            "SETTINGS::SYSTEM:PTERODACTYL:URL" => "pterodactyl-url",
-            'SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT' => "per-page-limit",
-            "SETTINGS::SYSTEM:PTERODACTYL:TOKEN" => "pterodactyl-api-key",
-            "SETTINGS::SYSTEM:ENABLE_LOGIN_LOGO" => "enable-login-logo",
-            "SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN" => "pterodactyl-admin-api-key",
-            "SETTINGS::SYSTEM:ENABLE_UPGRADE" => "enable-upgrade",
-            "SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS" => "enable-disable-servers",
-            "SETTINGS::SYSTEM:CREATION_OF_NEW_USERS" => "enable-disable-new-users",
-            "SETTINGS::SYSTEM:SHOW_IMPRINT" => "show-imprint",
-            "SETTINGS::SYSTEM:SHOW_PRIVACY" => "show-privacy",
-            "SETTINGS::SYSTEM:SHOW_TOS" => "show-tos",
-            "SETTINGS::SYSTEM:ALERT_ENABLED" => "alert-enabled",
-            "SETTINGS::SYSTEM:ALERT_TYPE" => "alert-type",
-            "SETTINGS::SYSTEM:ALERT_MESSAGE" => "alert-message",
-            "SETTINGS::SYSTEM:THEME" => "theme",
-            "SETTINGS::SYSTEM:MOTD_ENABLED" => "motd-enabled",
-            "SETTINGS::SYSTEM:MOTD_MESSAGE" => "motd-message",
-            "SETTINGS::SYSTEM:USEFULLINKS_ENABLED" => "usefullinks-enabled",
-            "SETTINGS::SYSTEM:SEO_TITLE" => "seo-title",
-            "SETTINGS::SYSTEM:SEO_DESCRIPTION" => "seo-description",
-        ];
-
-        foreach ($values as $key => $value) {
-            $param = $request->get($value);
-
-            Settings::where('key', $key)->updateOrCreate(['key' => $key], ['value' => $param]);
-            Cache::forget('setting'.':'.$key);
-        }
-
-        //SET THEME
-        $theme = $request->get('theme');
-        Theme::set($theme);
-
-
-        return redirect(route('admin.settings.index').'#system')->with('success', __('System settings updated!'));
-    }
-
-    private function updateIcons(Request $request)
-    {
-        $request->validate([
-            'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg',
-            'logo' => 'nullable|max:10000|mimes:jpg,png,jpeg',
-            'favicon' => 'nullable|max:10000|mimes:ico',
-        ]);
-
-        if ($request->hasFile('icon')) {
-            $request->file('icon')->storeAs('public', 'icon.png');
-        }
-        if ($request->hasFile('logo')) {
-            $request->file('logo')->storeAs('public', 'logo.png');
-        }
-        if ($request->hasFile('favicon')) {
-            $request->file('favicon')->storeAs('public', 'favicon.ico');
-        }
-    }
-}

+ 7 - 7
app/Console/Commands/MakeUserCommand.php

@@ -2,18 +2,20 @@
 
 namespace App\Console\Commands;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
 use App\Models\User;
+use App\Settings\PterodactylSettings;
 use App\Traits\Referral;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Validator;
-use Illuminate\Support\Str;
 
 class MakeUserCommand extends Command
 {
     use Referral;
 
+    private $pterodactyl;
+
     /**
      * The name and signature of the console command.
      *
@@ -28,17 +30,14 @@ class MakeUserCommand extends Command
      */
     protected $description = 'Create an admin account with the Artisan Console';
 
-    private Pterodactyl $pterodactyl;
-
     /**
      * Create a new command instance.
      *
      * @return void
      */
-    public function __construct(Pterodactyl $pterodactyl)
+    public function __construct()
     {
         parent::__construct();
-        $this->pterodactyl = $pterodactyl;
     }
 
 
@@ -47,8 +46,9 @@ class MakeUserCommand extends Command
      *
      * @return int
      */
-    public function handle()
+    public function handle(PterodactylSettings $ptero_settings)
     {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
         $ptero_id = $this->option('ptero_id') ?? $this->ask('Please specify your Pterodactyl ID.');
         $password = $this->secret('password') ?? $this->ask('Please specify your password.');
 

+ 146 - 0
app/Extensions/PaymentGateways/Mollie/MollieExtension.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\Mollie;
+
+use App\Helpers\AbstractExtension;
+use App\Events\PaymentEvent;
+use App\Events\UserUpdateCreditsEvent;
+use App\Models\PartnerDiscount;
+use App\Models\Payment;
+use App\Models\ShopProduct;
+use App\Models\User;
+use Exception;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Redirect;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Http;
+
+/**
+ * Summary of PayPalExtension
+ */
+class MollieExtension extends AbstractExtension
+{
+    public static function getConfig(): array
+    {
+        return [
+            "name" => "Mollie",
+            "RoutesIgnoreCsrf" => [
+                "payment/MollieWebhook"
+            ],
+        ];
+    }
+
+    static function pay(Request $request): void
+    {
+        $url = 'https://api.mollie.com/v2/payments';
+        $settings = new MollieSettings();
+
+        $user = Auth::user();
+        $shopProduct = ShopProduct::findOrFail($request->shopProduct);
+        $discount = PartnerDiscount::getDiscount();
+
+        // create a new payment
+        $payment = Payment::create([
+            'user_id' => $user->id,
+            'payment_id' => null,
+            'payment_method' => 'mollie',
+            'type' => $shopProduct->type,
+            'status' => 'open',
+            'amount' => $shopProduct->quantity,
+            'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
+            'tax_value' => $shopProduct->getTaxValue(),
+            'tax_percent' => $shopProduct->getTaxPercent(),
+            'total_price' => $shopProduct->getTotalPrice(),
+            'currency_code' => $shopProduct->currency_code,
+            'shop_item_product_id' => $shopProduct->id,
+        ]);
+
+        try {
+            $response = Http::withHeaders([
+                'Content-Type' => 'application/json',
+                'Authorization' => 'Bearer ' . $settings->api_key,
+            ])->post($url, [
+                'amount' => [
+                    'currency' => $shopProduct->currency_code,
+                    'value' => number_format($shopProduct->getTotalPrice(), 2, '.', ''),
+                ],
+                'description' => "Order #{$payment->id} - " . $shopProduct->name,
+                'redirectUrl' => route('payment.MollieSuccess'),
+                'cancelUrl' => route('payment.Cancel'),
+                'webhookUrl' => url('/extensions/payment/MollieWebhook'),
+                'metadata' => [
+                    'payment_id' => $payment->id,
+                ],
+            ]);
+
+            if ($response->status() != 201) {
+                Log::error('Mollie Payment: ' . $response->body());
+                $payment->delete();
+
+                Redirect::route('store.index')->with('error', __('Payment failed'))->send();
+                return;
+            }
+
+            $payment->update([
+                'payment_id' => $response->json()['id'],
+            ]);
+
+            Redirect::away($response->json()['_links']['checkout']['href'])->send();
+            return;
+        } catch (Exception $ex) {
+            Log::error('Mollie Payment: ' . $ex->getMessage());
+            $payment->delete();
+
+            Redirect::route('store.index')->with('error', __('Payment failed'))->send();
+            return;
+        }
+    }
+
+    static function success(Request $request): void
+    {
+        $payment = Payment::findOrFail($request->input('payment'));
+        $payment->status = 'pending';
+
+        Redirect::route('home')->with('success', 'Your payment is being processed')->send();
+        return;
+    }
+
+    static function webhook(Request $request): JsonResponse
+    {
+        $url = 'https://api.mollie.com/v2/payments/' . $request->id;
+        $settings = new MollieSettings();
+
+        try {
+            $response = Http::withHeaders([
+                'Content-Type' => 'application/json',
+                'Authorization' => 'Bearer ' . $settings->api_key,
+            ])->get($url);
+            if ($response->status() != 200) {
+                Log::error('Mollie Payment Webhook: ' . $response->json()['title']);
+                return response()->json(['success' => false]);
+            }
+
+            $payment = Payment::findOrFail($response->json()['metadata']['payment_id']);
+            $payment->status->update([
+                'status' => $response->json()['status'],
+            ]);
+
+            $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
+            event(new PaymentEvent($payment, $payment, $shopProduct));
+
+            if ($response->json()['status'] == 'paid') {
+                $user = User::findOrFail($payment->user_id);
+                event(new UserUpdateCreditsEvent($user));
+            }
+        } catch (Exception $ex) {
+            Log::error('Mollie Payment Webhook: ' . $ex->getMessage());
+            return response()->json(['success' => false]);
+        }
+
+        // return a 200 status code
+        return response()->json(['success' => true]);
+    }
+}

+ 41 - 0
app/Extensions/PaymentGateways/Mollie/MollieSettings.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\Mollie;
+
+use Spatie\LaravelSettings\Settings;
+
+class MollieSettings extends Settings
+{
+
+    public bool $enabled = false;
+    public ?string $api_key;
+
+    public static function group(): string
+    {
+        return 'mollie';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            'api_key',
+        ];
+    }
+
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-dollar-sign',
+            'api_key' => [
+                'type' => 'string',
+                'label' => 'API Key',
+                'description' => 'The API Key of your Mollie App',
+            ],
+            'enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enabled',
+                'description' => 'Enable or disable this payment gateway',
+            ],
+        ];
+    }
+}

+ 18 - 0
app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php

@@ -0,0 +1,18 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+
+class CreateMollieSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $this->migrator->addEncrypted('mollie.api_key', null);
+        $this->migrator->add('mollie.enabled', false);
+    }
+
+    public function down(): void
+    {
+        $this->migrator->delete('mollie.api_key');
+        $this->migrator->delete('mollie.enabled');
+    }
+}

+ 22 - 0
app/Extensions/PaymentGateways/Mollie/web_routes.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Extensions\PaymentGateways\Mollie\MollieExtension;
+
+Route::middleware(['web', 'auth'])->group(function () {
+    Route::get('payment/MolliePay/{shopProduct}', function () {
+        MollieExtension::pay(request());
+    })->name('payment.MolliePay');
+
+    Route::get(
+        'payment/MollieSuccess',
+        function () {
+            MollieExtension::success(request());
+        }
+    )->name('payment.MollieSuccess');
+});
+
+
+Route::post('payment/MollieWebhook', function () {
+    MollieExtension::webhook(request());
+})->name('payment.MollieWebhook');

+ 197 - 0
app/Extensions/PaymentGateways/PayPal/PayPalExtension.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\PayPal;
+
+use App\Helpers\AbstractExtension;
+use App\Events\PaymentEvent;
+use App\Events\UserUpdateCreditsEvent;
+use App\Extensions\PaymentGateways\PayPal\PayPalSettings;
+use App\Models\PartnerDiscount;
+use App\Models\Payment;
+use App\Models\ShopProduct;
+use App\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Redirect;
+use Illuminate\Support\Facades\Log;
+use PayPalCheckoutSdk\Core\PayPalHttpClient;
+use PayPalCheckoutSdk\Core\ProductionEnvironment;
+use PayPalCheckoutSdk\Core\SandboxEnvironment;
+use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
+use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
+use PayPalHttp\HttpException;
+
+
+/**
+ * Summary of PayPalExtension
+ */
+class PayPalExtension extends AbstractExtension
+{
+    public static function getConfig(): array
+    {
+        return [
+            "name" => "PayPal",
+            "RoutesIgnoreCsrf" => [],
+        ];
+    }
+
+    static function PaypalPay(Request $request): void
+    {
+        /** @var User $user */
+        $user = Auth::user();
+        $shopProduct = ShopProduct::findOrFail($request->shopProduct);
+        $discount = PartnerDiscount::getDiscount();
+
+        // create a new payment
+        $payment = Payment::create([
+            'user_id' => $user->id,
+            'payment_id' => null,
+            'payment_method' => 'paypal',
+            'type' => $shopProduct->type,
+            'status' => 'open',
+            'amount' => $shopProduct->quantity,
+            'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
+            'tax_value' => $shopProduct->getTaxValue(),
+            'tax_percent' => $shopProduct->getTaxPercent(),
+            'total_price' => $shopProduct->getTotalPrice(),
+            'currency_code' => $shopProduct->currency_code,
+            'shop_item_product_id' => $shopProduct->id,
+        ]);
+
+        $request = new OrdersCreateRequest();
+        $request->prefer('return=representation');
+        $request->body = [
+            "intent" => "CAPTURE",
+            "purchase_units" => [
+                [
+                    "reference_id" => uniqid(),
+                    "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""),
+                    "amount"       => [
+                        "value"         => $shopProduct->getTotalPrice(),
+                        'currency_code' => strtoupper($shopProduct->currency_code),
+                        'breakdown' => [
+                            'item_total' =>
+                            [
+                                'currency_code' => strtoupper($shopProduct->currency_code),
+                                'value' => $shopProduct->getPriceAfterDiscount(),
+                            ],
+                            'tax_total' =>
+                            [
+                                'currency_code' => strtoupper($shopProduct->currency_code),
+                                'value' => $shopProduct->getTaxValue(),
+                            ]
+                        ]
+                    ]
+                ]
+            ],
+            "application_context" => [
+                "cancel_url" => route('payment.Cancel'),
+                "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]),
+                'brand_name' =>  config('app.name', 'Controlpanel.GG'),
+                'shipping_preference'  => 'NO_SHIPPING'
+            ]
+
+
+        ];
+
+        try {
+            // Call API with your client and get a response for your call
+            $response = self::getPayPalClient()->execute($request);
+
+            // check for any errors in the response
+            if ($response->statusCode != 201) {
+                throw new \Exception($response->statusCode);
+            }
+
+            // make sure the link is not empty
+            if (empty($response->result->links[1]->href)) {
+                throw new \Exception('No redirect link found');
+            }
+
+            Redirect::away($response->result->links[1]->href)->send();
+            return;
+        } catch (HttpException $ex) {
+            Log::error('PayPal Payment: ' . $ex->getMessage());
+            $payment->delete();
+
+            Redirect::route('store.index')->with('error', __('Payment failed'))->send();
+            return;
+        }
+    }
+
+    static function PaypalSuccess(Request $laravelRequest): void
+    {
+        $user = Auth::user();
+        $user = User::findOrFail($user->id);
+
+        $payment = Payment::findOrFail($laravelRequest->payment);
+        $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
+
+        $request = new OrdersCaptureRequest($laravelRequest->input('token'));
+        $request->prefer('return=representation');
+
+        try {
+            // Call API with your client and get a response for your call
+            $response = self::getPayPalClient()->execute($request);
+            if ($response->statusCode == 201 || $response->statusCode == 200) {
+                //update payment
+                $payment->update([
+                    'status' => 'paid',
+                    'payment_id' => $response->result->id,
+                ]);
+
+                event(new UserUpdateCreditsEvent($user));
+                event(new PaymentEvent($user, $payment, $shopProduct));
+
+                // redirect to the payment success page with success message
+                Redirect::route('home')->with('success', 'Payment successful')->send();
+            } elseif (env('APP_ENV') == 'local') {
+                // If call returns body in response, you can get the deserialized version from the result attribute of the response
+                $payment->delete();
+                dd($response);
+            } else {
+                $payment->update([
+                    'status' => 'cancelled',
+                    'payment_id' => $response->result->id,
+                ]);
+                abort(500);
+            }
+        } catch (HttpException $ex) {
+            if (env('APP_ENV') == 'local') {
+                echo $ex->statusCode;
+                $payment->delete();
+                dd($ex->getMessage());
+            } else {
+                $payment->update([
+                    'status' => 'cancelled',
+                    'payment_id' => $response->result->id,
+                ]);
+                abort(422);
+            }
+        }
+    }
+
+    static function getPayPalClient(): PayPalHttpClient
+    {
+        $environment = env('APP_ENV') == 'local'
+            ? new SandboxEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret())
+            : new ProductionEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret());
+        return new PayPalHttpClient($environment);
+    }
+    /**
+     * @return string
+     */
+    static function getPaypalClientId(): string
+    {
+        $settings = new PayPalSettings();
+        return env('APP_ENV') == 'local' ?  $settings->sandbox_client_id : $settings->client_id;
+    }
+    /**
+     * @return string
+     */
+    static function getPaypalClientSecret(): string
+    {
+        $settings = new PayPalSettings();
+        return env('APP_ENV') == 'local' ? $settings->sandbox_client_secret : $settings->client_secret;
+    }
+}

+ 67 - 0
app/Extensions/PaymentGateways/PayPal/PayPalSettings.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\PayPal;
+
+use Spatie\LaravelSettings\Settings;
+
+class PayPalSettings extends Settings
+{
+    public bool $enabled = false;
+    public ?string $client_id;
+    public ?string $client_secret;
+    public ?string $sandbox_client_id;
+    public ?string $sandbox_client_secret;
+
+    public static function group(): string
+    {
+        return 'paypal';
+    }
+
+
+    public static function encrypted(): array
+    {
+        return [
+            'client_id',
+            'client_secret',
+            'sandbox_client_id',
+            'sandbox_client_secret'
+        ];
+    }
+
+    /**
+     * Summary of optionInputData array
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-dollar-sign',
+            'client_id' => [
+                'type' => 'string',
+                'label' => 'Client ID',
+                'description' => 'The Client ID of your PayPal App',
+            ],
+            'client_secret' => [
+                'type' => 'string',
+                'label' => 'Client Secret',
+                'description' => 'The Client Secret of your PayPal App',
+            ],
+            'enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enabled',
+                'description' => 'Enable this payment gateway',
+            ],
+            'sandbox_client_id' => [
+                'type' => 'string',
+                'label' => 'Sandbox Client ID',
+                'description' => 'The Sandbox Client ID  used when app_env = local',
+            ],
+            'sandbox_client_secret' => [
+                'type' => 'string',
+                'label' => 'Sandbox Client Secret',
+                'description' => 'The Sandbox Client Secret  used when app_env = local',
+            ],
+        ];
+    }
+}

+ 0 - 13
app/Extensions/PaymentGateways/PayPal/config.php

@@ -1,13 +0,0 @@
-<?php
-
-namespace App\Extensions\PaymentGateways\PayPal;
-
-function getConfig()
-{
-    return [
-        "name" => "PayPal",
-        "description" => "PayPal payment gateway",
-        "RoutesIgnoreCsrf" => [],
-        "enabled" => (config('SETTINGS::PAYMENTS:PAYPAL:SECRET') && config('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID')) || (config('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') && config('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') && env("APP_ENV") === "local"),
-    ];
-}

+ 0 - 186
app/Extensions/PaymentGateways/PayPal/index.php

@@ -1,186 +0,0 @@
-<?php
-
-use App\Events\PaymentEvent;
-use App\Events\UserUpdateCreditsEvent;
-use App\Models\PartnerDiscount;
-use App\Models\Payment;
-use App\Models\ShopProduct;
-use App\Models\User;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Redirect;
-use Illuminate\Support\Facades\Log;
-use PayPalCheckoutSdk\Core\PayPalHttpClient;
-use PayPalCheckoutSdk\Core\ProductionEnvironment;
-use PayPalCheckoutSdk\Core\SandboxEnvironment;
-use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
-use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
-use PayPalHttp\HttpException;
-
-
-
-/**
- * @param Request $request
- * @param ShopProduct $shopProduct
- */
-function PaypalPay(Request $request)
-{
-    /** @var User $user */
-    $user = Auth::user();
-    $shopProduct = ShopProduct::findOrFail($request->shopProduct);
-    $discount = PartnerDiscount::getDiscount();
-
-    // create a new payment
-    $payment = Payment::create([
-        'user_id' => $user->id,
-        'payment_id' => null,
-        'payment_method' => 'paypal',
-        'type' => $shopProduct->type,
-        'status' => 'open',
-        'amount' => $shopProduct->quantity,
-        'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
-        'tax_value' => $shopProduct->getTaxValue(),
-        'tax_percent' => $shopProduct->getTaxPercent(),
-        'total_price' => $shopProduct->getTotalPrice(),
-        'currency_code' => $shopProduct->currency_code,
-        'shop_item_product_id' => $shopProduct->id,
-    ]);
-
-    $request = new OrdersCreateRequest();
-    $request->prefer('return=representation');
-    $request->body = [
-        "intent" => "CAPTURE",
-        "purchase_units" => [
-            [
-                "reference_id" => uniqid(),
-                "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""),
-                "amount"       => [
-                    "value"         => $shopProduct->getTotalPrice(),
-                    'currency_code' => strtoupper($shopProduct->currency_code),
-                    'breakdown' => [
-                        'item_total' =>
-                        [
-                            'currency_code' => strtoupper($shopProduct->currency_code),
-                            'value' => $shopProduct->getPriceAfterDiscount(),
-                        ],
-                        'tax_total' =>
-                        [
-                            'currency_code' => strtoupper($shopProduct->currency_code),
-                            'value' => $shopProduct->getTaxValue(),
-                        ]
-                    ]
-                ]
-            ]
-        ],
-        "application_context" => [
-            "cancel_url" => route('payment.Cancel'),
-            "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]),
-            'brand_name' =>  config('app.name', 'Controlpanel.GG'),
-            'shipping_preference'  => 'NO_SHIPPING'
-        ]
-
-
-    ];
-
-    try {
-        // Call API with your client and get a response for your call
-        $response = getPayPalClient()->execute($request);
-
-        // check for any errors in the response
-        if ($response->statusCode != 201) {
-            throw new \Exception($response->statusCode);
-        }
-
-        // make sure the link is not empty
-        if (empty($response->result->links[1]->href)) {
-            throw new \Exception('No redirect link found');
-        }
-
-        Redirect::away($response->result->links[1]->href)->send();
-        return;
-    } catch (HttpException $ex) {
-        Log::error('PayPal Payment: ' . $ex->getMessage());
-        $payment->delete();
-
-        Redirect::route('store.index')->with('error', __('Payment failed'))->send();
-        return;
-    }
-}
-/**
- * @param Request $laravelRequest
- */
-function PaypalSuccess(Request $laravelRequest)
-{
-    $user = Auth::user();
-    $user = User::findOrFail($user->id);
-
-    $payment = Payment::findOrFail($laravelRequest->payment);
-    $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
-
-    $request = new OrdersCaptureRequest($laravelRequest->input('token'));
-    $request->prefer('return=representation');
-
-    try {
-        // Call API with your client and get a response for your call
-        $response = getPayPalClient()->execute($request);
-        if ($response->statusCode == 201 || $response->statusCode == 200) {
-            //update payment
-            $payment->update([
-                'status' => 'paid',
-                'payment_id' => $response->result->id,
-            ]);
-
-            event(new UserUpdateCreditsEvent($user));
-            event(new PaymentEvent($user, $payment, $shopProduct));
-
-            // redirect to the payment success page with success message
-            Redirect::route('home')->with('success', 'Payment successful')->send();
-        } elseif (env('APP_ENV') == 'local') {
-            // If call returns body in response, you can get the deserialized version from the result attribute of the response
-            $payment->delete();
-            dd($response);
-        } else {
-            $payment->update([
-                'status' => 'cancelled',
-                'payment_id' => $response->result->id,
-            ]);
-            abort(500);
-        }
-    } catch (HttpException $ex) {
-        if (env('APP_ENV') == 'local') {
-            echo $ex->statusCode;
-            $payment->delete();
-            dd($ex->getMessage());
-        } else {
-            $payment->update([
-                'status' => 'cancelled',
-                'payment_id' => $response->result->id,
-            ]);
-            abort(422);
-        }
-    }
-}
-/**
- * @return PayPalHttpClient
- */
-function getPayPalClient()
-{
-    $environment = env('APP_ENV') == 'local'
-        ? new SandboxEnvironment(getPaypalClientId(), getPaypalClientSecret())
-        : new ProductionEnvironment(getPaypalClientId(), getPaypalClientSecret());
-    return new PayPalHttpClient($environment);
-}
-/**
- * @return string
- */
-function getPaypalClientId()
-{
-    return env('APP_ENV') == 'local' ?  config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID") : config("SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID");
-}
-/**
- * @return string
- */
-function getPaypalClientSecret()
-{
-    return env('APP_ENV') == 'local' ? config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET") : config("SETTINGS::PAYMENTS:PAYPAL:SECRET");
-}

+ 100 - 0
app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php

@@ -0,0 +1,100 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+
+class CreatePayPalSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+
+        $this->migrator->addEncrypted('paypal.client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') : null);
+        $this->migrator->addEncrypted('paypal.client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SECRET') : null);
+        $this->migrator->addEncrypted('paypal.sandbox_client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : null);
+        $this->migrator->addEncrypted('paypal.sandbox_client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : null);
+        $this->migrator->add('paypal.enabled', false);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID',
+                'value' => $this->getNewValue('client_id'),
+                'type' => 'string',
+                'description' => 'The Client ID of your PayPal App'
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:PAYPAL:SECRET',
+                'value' => $this->getNewValue('client_secret'),
+                'type' => 'string',
+                'description' => 'The Client Secret of your PayPal App'
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID',
+                'value' => $this->getNewValue('sandbox_client_id'),
+                'type' => 'string',
+                'description' => 'The Sandbox Client ID of your PayPal App'
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET',
+                'value' => $this->getNewValue('sandbox_client_secret'),
+                'type' => 'string',
+                'description' => 'The Sandbox Client Secret of your PayPal App'
+            ]
+        ]);
+
+
+        $this->migrator->delete('paypal.client_id');
+        $this->migrator->delete('paypal.client_secret');
+        $this->migrator->delete('paypal.enabled');
+        $this->migrator->delete('paypal.sandbox_client_id');
+        $this->migrator->delete('paypal.sandbox_client_secret');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'paypal'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 3 - 4
app/Extensions/PaymentGateways/PayPal/web_routes.php

@@ -1,18 +1,17 @@
 <?php
 
 use Illuminate\Support\Facades\Route;
-
-include_once(__DIR__ . '/index.php');
+use App\Extensions\PaymentGateways\PayPal\PayPalExtension;
 
 Route::middleware(['web', 'auth'])->group(function () {
     Route::get('payment/PayPalPay/{shopProduct}', function () {
-        PaypalPay(request());
+        PayPalExtension::PaypalPay(request());
     })->name('payment.PayPalPay');
 
     Route::get(
         'payment/PayPalSuccess',
         function () {
-            PaypalSuccess(request());
+            PayPalExtension::PaypalSuccess(request());
         }
     )->name('payment.PayPalSuccess');
 });

+ 390 - 0
app/Extensions/PaymentGateways/Stripe/StripeExtension.php

@@ -0,0 +1,390 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\Stripe;
+
+use App\Helpers\AbstractExtension;
+use App\Events\PaymentEvent;
+use App\Events\UserUpdateCreditsEvent;
+use App\Extensions\PaymentGateways\Stripe\StripeSettings;
+use App\Models\PartnerDiscount;
+use App\Models\Payment;
+use App\Models\ShopProduct;
+use App\Models\User;
+use App\Notifications\ConfirmPaymentNotification;
+use Exception;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Redirect;
+use Stripe\Exception\SignatureVerificationException;
+use Stripe\Stripe;
+use Stripe\StripeClient;
+
+class StripeExtension extends AbstractExtension
+{
+    public static function getConfig(): array
+    {
+        return [
+            "name" => "Stripe",
+            "RoutesIgnoreCsrf" => [
+                "payment/StripeWebhooks",
+            ],
+        ];
+    }
+
+    /**
+     * @param  Request  $request
+     * @param  ShopProduct  $shopProduct
+     */
+    public static function StripePay(Request $request)
+    {
+        $user = Auth::user();
+        $shopProduct = ShopProduct::findOrFail($request->shopProduct);
+
+        // check if the price is valid for stripe
+        if (!self::checkPriceAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), 'stripe')) {
+            Redirect::route('home')->with('error', __('The product you chose can\'t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'))->send();
+            return;
+        }
+
+        $discount = PartnerDiscount::getDiscount();
+
+
+        // create payment
+        $payment = Payment::create([
+            'user_id' => $user->id,
+            'payment_id' => null,
+            'payment_method' => 'stripe',
+            'type' => $shopProduct->type,
+            'status' => 'open',
+            'amount' => $shopProduct->quantity,
+            'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
+            'tax_value' => $shopProduct->getTaxValue(),
+            'total_price' => $shopProduct->getTotalPrice(),
+            'tax_percent' => $shopProduct->getTaxPercent(),
+            'currency_code' => $shopProduct->currency_code,
+            'shop_item_product_id' => $shopProduct->id,
+        ]);
+
+        $stripeClient = self::getStripeClient();
+        $request = $stripeClient->checkout->sessions->create([
+            'line_items' => [
+                [
+                    'price_data' => [
+                        'currency' => $shopProduct->currency_code,
+                        'product_data' => [
+                            'name' => $shopProduct->display . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''),
+                            'description' => $shopProduct->description,
+                        ],
+                        'unit_amount_decimal' => round($shopProduct->getPriceAfterDiscount() * 100, 2),
+                    ],
+                    'quantity' => 1,
+                ],
+                [
+                    'price_data' => [
+                        'currency' => $shopProduct->currency_code,
+                        'product_data' => [
+                            'name' => __('Tax'),
+                            'description' => $shopProduct->getTaxPercent() . '%',
+                        ],
+                        'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2) * 100,
+                    ],
+                    'quantity' => 1,
+                ],
+            ],
+
+            'mode' => 'payment',
+            'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}',
+            'cancel_url' => route('payment.Cancel'),
+            'payment_intent_data' => [
+                'metadata' => [
+                    'payment_id' => $payment->id,
+                ],
+            ],
+        ]);
+
+        Redirect::to($request->url)->send();
+    }
+
+    /**
+     * @param  Request  $request
+     */
+    public static function StripeSuccess(Request $request)
+    {
+        $user = Auth::user();
+        $user = User::findOrFail($user->id);
+        $payment = Payment::findOrFail($request->input('payment'));
+        $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
+
+
+        Redirect::route('home')->with('success', 'Please wait for success')->send();
+
+        $stripeClient = self::getStripeClient();
+        try {
+            //get stripe data
+            $paymentSession = $stripeClient->checkout->sessions->retrieve($request->input('session_id'));
+            $paymentIntent = $stripeClient->paymentIntents->retrieve($paymentSession->payment_intent);
+
+            //get DB entry of this payment ID if existing
+            $paymentDbEntry = Payment::where('payment_id', $paymentSession->payment_intent)->count();
+
+            // check if payment is 100% completed and payment does not exist in db already
+            if ($paymentSession->status == 'complete' && $paymentIntent->status == 'succeeded' && $paymentDbEntry == 0) {
+
+                //update payment
+                $payment->update([
+                    'payment_id' => $paymentSession->payment_intent,
+                    'status' => 'paid',
+                ]);
+
+                //payment notification
+                $user->notify(new ConfirmPaymentNotification($payment));
+
+                event(new UserUpdateCreditsEvent($user));
+                event(new PaymentEvent($user, $payment, $shopProduct));
+
+                //redirect back to home
+                Redirect::route('home')->with('success', 'Payment successful')->send();
+            } else {
+                if ($paymentIntent->status == 'processing') {
+
+                    //update payment
+                    $payment->update([
+                        'payment_id' => $paymentSession->payment_intent,
+                        'status' => 'processing',
+                    ]);
+
+                    event(new PaymentEvent($user, $payment, $shopProduct));
+
+                    Redirect::route('home')->with('success', 'Your payment is being processed')->send();
+                }
+
+                if ($paymentDbEntry == 0 && $paymentIntent->status != 'processing') {
+                    $stripeClient->paymentIntents->cancel($paymentIntent->id);
+
+                    //redirect back to home
+                    Redirect::route('home')->with('info', __('Your payment has been canceled!'))->send();
+                } else {
+                    abort(402);
+                }
+            }
+        } catch (Exception $e) {
+            if (env('APP_ENV') == 'local') {
+                dd($e->getMessage());
+            } else {
+                abort(422);
+            }
+        }
+    }
+
+    /**
+     * @param  Request  $request
+     */
+    public static function handleStripePaymentSuccessHook($paymentIntent)
+    {
+        try {
+            $payment = Payment::where('id', $paymentIntent->metadata->payment_id)->with('user')->first();
+            $user = User::where('id', $payment->user_id)->first();
+            $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
+
+            if ($paymentIntent->status == 'succeeded' && $payment->status == 'processing') {
+
+                //update payment db entry status
+                $payment->update([
+                    'payment_id' => $payment->payment_id ?? $paymentIntent->id,
+                    'status' => 'paid'
+                ]);
+
+                //payment notification
+                $user->notify(new ConfirmPaymentNotification($payment));
+                event(new UserUpdateCreditsEvent($user));
+                event(new PaymentEvent($user, $payment, $shopProduct));
+            }
+
+            // return 200
+            return response()->json(['success' => true], 200);
+        } catch (Exception $ex) {
+            abort(422);
+        }
+    }
+
+    /**
+     * @param  Request  $request
+     */
+    public static function StripeWebhooks(Request $request)
+    {
+        Stripe::setApiKey(self::getStripeSecret());
+
+        try {
+            $payload = @file_get_contents('php://input');
+            $sig_header = $request->header('Stripe-Signature');
+            $event = null;
+            $event = \Stripe\Webhook::constructEvent(
+                $payload,
+                $sig_header,
+                self::getStripeEndpointSecret()
+            );
+        } catch (\UnexpectedValueException $e) {
+            // Invalid payload
+
+            abort(400);
+        } catch (SignatureVerificationException $e) {
+            // Invalid signature
+
+            abort(400);
+        }
+
+        // Handle the event
+        switch ($event->type) {
+            case 'payment_intent.succeeded':
+                $paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
+                self::handleStripePaymentSuccessHook($paymentIntent);
+                break;
+            default:
+                echo 'Received unknown event type ' . $event->type;
+        }
+    }
+
+    /**
+     * @return \Stripe\StripeClient
+     */
+    public static function getStripeClient()
+    {
+        return new StripeClient(self::getStripeSecret());
+    }
+
+    /**
+     * @return string
+     */
+    public static function getStripeSecret()
+    {
+        $settings = new StripeSettings();
+
+        return env('APP_ENV') == 'local'
+            ? $settings->test_secret_key
+            : $settings->secret_key;
+    }
+
+    /**
+     * @return string
+     */
+    public static function getStripeEndpointSecret()
+    {
+        $settings = new StripeSettings();
+        return env('APP_ENV') == 'local'
+            ? $settings->test_endpoint_secret
+            : $settings->endpoint_secret;
+    }
+    /**
+     * @param  $amount
+     * @param  $currencyCode
+     * @param  $payment_method
+     * @return bool
+     * @description check if the amount is higher than the minimum amount for the stripe gateway
+     */
+    public static function checkPriceAmount($amount, $currencyCode, $payment_method)
+    {
+        $minimums = [
+            "USD" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "AED" => [
+                "paypal" => 0,
+                "stripe" => 2
+            ],
+            "AUD" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "BGN" => [
+                "paypal" => 0,
+                "stripe" => 1
+            ],
+            "BRL" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "CAD" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "CHF" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "CZK" => [
+                "paypal" => 0,
+                "stripe" => 15
+            ],
+            "DKK" => [
+                "paypal" => 0,
+                "stripe" => 2.5
+            ],
+            "EUR" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "GBP" => [
+                "paypal" => 0,
+                "stripe" => 0.3
+            ],
+            "HKD" => [
+                "paypal" => 0,
+                "stripe" => 4
+            ],
+            "HRK" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "HUF" => [
+                "paypal" => 0,
+                "stripe" => 175
+            ],
+            "INR" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "JPY" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "MXN" => [
+                "paypal" => 0,
+                "stripe" => 10
+            ],
+            "MYR" => [
+                "paypal" => 0,
+                "stripe" => 2
+            ],
+            "NOK" => [
+                "paypal" => 0,
+                "stripe" => 3
+            ],
+            "NZD" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "PLN" => [
+                "paypal" => 0,
+                "stripe" => 2
+            ],
+            "RON" => [
+                "paypal" => 0,
+                "stripe" => 2
+            ],
+            "SEK" => [
+                "paypal" => 0,
+                "stripe" => 3
+            ],
+            "SGD" => [
+                "paypal" => 0,
+                "stripe" => 0.5
+            ],
+            "THB" => [
+                "paypal" => 0,
+                "stripe" => 10
+            ]
+        ];
+        return $amount >= $minimums[$currencyCode][$payment_method];
+    }
+}

+ 63 - 0
app/Extensions/PaymentGateways/Stripe/StripeSettings.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Extensions\PaymentGateways\Stripe;
+
+use Spatie\LaravelSettings\Settings;
+
+class StripeSettings extends Settings
+{
+
+    public bool $enabled = false;
+    public ?string $secret_key;
+    public ?string $endpoint_secret;
+    public ?string $test_secret_key;
+    public ?string $test_endpoint_secret;
+
+
+    public static function group(): string
+    {
+        return 'stripe';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            "secret_key",
+            "endpoint_secret",
+            "test_secret_key",
+            "test_endpoint_secret"
+        ];
+    }
+
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-dollar-sign',
+            'secret_key' => [
+                'type' => 'string',
+                'label' => 'Secret Key',
+                'description' => 'The Secret Key of your Stripe App',
+            ],
+            'endpoint_secret' => [
+                'type' => 'string',
+                'label' => 'Endpoint Secret',
+                'description' => 'The Endpoint Secret of your Stripe App',
+            ],
+            'test_secret_key' => [
+                'type' => 'string',
+                'label' => 'Test Secret Key',
+                'description' => 'The Test Secret Key used when app_env = local',
+            ],
+            'test_endpoint_secret' => [
+                'type' => 'string',
+                'label' => 'Test Endpoint Secret',
+                'description' => 'The Test Endpoint Secret used when app_env = local',
+            ],
+            'enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enabled',
+                'description' => 'Enable this payment gateway',
+            ]
+        ];
+    }
+}

+ 0 - 15
app/Extensions/PaymentGateways/Stripe/config.php

@@ -1,15 +0,0 @@
-<?php
-
-namespace App\Extensions\PaymentGateways\Stripe;
-
-function getConfig()
-{
-    return [
-        "name" => "Stripe",
-        "description" => "Stripe payment gateway",
-        "RoutesIgnoreCsrf" => [
-            "payment/StripeWebhooks",
-        ],
-        "enabled" => config('SETTINGS::PAYMENTS:STRIPE:SECRET') && config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') || config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET') && config('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') && env("APP_ENV") === "local",
-    ];
-}

+ 0 - 373
app/Extensions/PaymentGateways/Stripe/index.php

@@ -1,373 +0,0 @@
-<?php
-
-use App\Events\PaymentEvent;
-use App\Events\UserUpdateCreditsEvent;
-use App\Models\PartnerDiscount;
-use App\Models\Payment;
-use App\Models\ShopProduct;
-use App\Models\User;
-use App\Notifications\ConfirmPaymentNotification;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Redirect;
-use Stripe\Exception\SignatureVerificationException;
-use Stripe\Stripe;
-use Stripe\StripeClient;
-
-
-
-
-/**
- * @param  Request  $request
- * @param  ShopProduct  $shopProduct
- */
-function StripePay(Request $request)
-{
-    $user = Auth::user();
-    $shopProduct = ShopProduct::findOrFail($request->shopProduct);
-
-    // check if the price is valid for stripe
-    if (!checkPriceAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), 'stripe')) {
-        Redirect::route('home')->with('error', __('The product you chose can\'t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'))->send();
-        return;
-    }
-
-    $discount = PartnerDiscount::getDiscount();
-
-
-    // create payment
-    $payment = Payment::create([
-        'user_id' => $user->id,
-        'payment_id' => null,
-        'payment_method' => 'stripe',
-        'type' => $shopProduct->type,
-        'status' => 'open',
-        'amount' => $shopProduct->quantity,
-        'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
-        'tax_value' => $shopProduct->getTaxValue(),
-        'total_price' => $shopProduct->getTotalPrice(),
-        'tax_percent' => $shopProduct->getTaxPercent(),
-        'currency_code' => $shopProduct->currency_code,
-        'shop_item_product_id' => $shopProduct->id,
-    ]);
-
-    $stripeClient = getStripeClient();
-    $request = $stripeClient->checkout->sessions->create([
-        'line_items' => [
-            [
-                'price_data' => [
-                    'currency' => $shopProduct->currency_code,
-                    'product_data' => [
-                        'name' => $shopProduct->display . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''),
-                        'description' => $shopProduct->description,
-                    ],
-                    'unit_amount_decimal' => round($shopProduct->getPriceAfterDiscount() * 100, 2),
-                ],
-                'quantity' => 1,
-            ],
-            [
-                'price_data' => [
-                    'currency' => $shopProduct->currency_code,
-                    'product_data' => [
-                        'name' => __('Tax'),
-                        'description' => $shopProduct->getTaxPercent() . '%',
-                    ],
-                    'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2) * 100,
-                ],
-                'quantity' => 1,
-            ],
-        ],
-
-        'mode' => 'payment',
-        'payment_method_types' => str_getcsv(config('SETTINGS::PAYMENTS:STRIPE:METHODS')),
-        'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}',
-        'cancel_url' => route('payment.Cancel'),
-        'payment_intent_data' => [
-            'metadata' => [
-                'payment_id' => $payment->id,
-            ],
-        ],
-    ]);
-
-    Redirect::to($request->url)->send();
-}
-
-/**
- * @param  Request  $request
- */
-function StripeSuccess(Request $request)
-{
-    $user = Auth::user();
-    $user = User::findOrFail($user->id);
-    $payment = Payment::findOrFail($request->input('payment'));
-    $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
-
-
-    Redirect::route('home')->with('success', 'Please wait for success')->send();
-
-    $stripeClient = getStripeClient();
-    try {
-        //get stripe data
-        $paymentSession = $stripeClient->checkout->sessions->retrieve($request->input('session_id'));
-        $paymentIntent = $stripeClient->paymentIntents->retrieve($paymentSession->payment_intent);
-
-        //get DB entry of this payment ID if existing
-        $paymentDbEntry = Payment::where('payment_id', $paymentSession->payment_intent)->count();
-
-        // check if payment is 100% completed and payment does not exist in db already
-        if ($paymentSession->status == 'complete' && $paymentIntent->status == 'succeeded' && $paymentDbEntry == 0) {
-
-            //update payment
-            $payment->update([
-                'payment_id' => $paymentSession->payment_intent,
-                'status' => 'paid',
-            ]);
-
-            //payment notification
-            $user->notify(new ConfirmPaymentNotification($payment));
-
-            event(new UserUpdateCreditsEvent($user));
-            event(new PaymentEvent($user, $payment, $shopProduct));
-
-            //redirect back to home
-            Redirect::route('home')->with('success', 'Payment successful')->send();
-        } else {
-            if ($paymentIntent->status == 'processing') {
-
-                //update payment
-                $payment->update([
-                    'payment_id' => $paymentSession->payment_intent,
-                    'status' => 'processing',
-                ]);
-
-                event(new PaymentEvent($user, $payment, $shopProduct));
-
-                Redirect::route('home')->with('success', 'Your payment is being processed')->send();
-            }
-
-            if ($paymentDbEntry == 0 && $paymentIntent->status != 'processing') {
-                $stripeClient->paymentIntents->cancel($paymentIntent->id);
-
-                //redirect back to home
-                Redirect::route('home')->with('info', __('Your payment has been canceled!'))->send();
-            } else {
-                abort(402);
-            }
-        }
-    } catch (Exception $e) {
-        if (env('APP_ENV') == 'local') {
-            dd($e->getMessage());
-        } else {
-            abort(422);
-        }
-    }
-}
-
-/**
- * @param  Request  $request
- */
-function handleStripePaymentSuccessHook($paymentIntent)
-{
-    try {
-        $payment = Payment::where('id', $paymentIntent->metadata->payment_id)->with('user')->first();
-        $user = User::where('id', $payment->user_id)->first();
-        $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
-
-        if ($paymentIntent->status == 'succeeded' && $payment->status == 'processing') {
-
-            //update payment db entry status
-            $payment->update([
-                'payment_id' => $payment->payment_id ?? $paymentIntent->id,
-                'status' => 'paid'
-            ]);
-
-            //payment notification
-            $user->notify(new ConfirmPaymentNotification($payment));
-            event(new UserUpdateCreditsEvent($user));
-            event(new PaymentEvent($user, $payment, $shopProduct));
-        }
-
-        // return 200
-        return response()->json(['success' => true], 200);
-    } catch (Exception $ex) {
-        abort(422);
-    }
-}
-
-/**
- * @param  Request  $request
- */
-function StripeWebhooks(Request $request)
-{
-    Stripe::setApiKey(getStripeSecret());
-
-    try {
-        $payload = @file_get_contents('php://input');
-        $sig_header = $request->header('Stripe-Signature');
-        $event = null;
-        $event = \Stripe\Webhook::constructEvent(
-            $payload,
-            $sig_header,
-            getStripeEndpointSecret()
-        );
-    } catch (\UnexpectedValueException $e) {
-        // Invalid payload
-
-        abort(400);
-    } catch (SignatureVerificationException $e) {
-        // Invalid signature
-
-        abort(400);
-    }
-
-    // Handle the event
-    switch ($event->type) {
-        case 'payment_intent.succeeded':
-            $paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
-            handleStripePaymentSuccessHook($paymentIntent);
-            break;
-        default:
-            echo 'Received unknown event type ' . $event->type;
-    }
-}
-
-/**
- * @return \Stripe\StripeClient
- */
-function getStripeClient()
-{
-    return new StripeClient(getStripeSecret());
-}
-
-/**
- * @return string
- */
-function getStripeSecret()
-{
-    return env('APP_ENV') == 'local'
-        ? config('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET')
-        : config('SETTINGS::PAYMENTS:STRIPE:SECRET');
-}
-
-/**
- * @return string
- */
-function getStripeEndpointSecret()
-{
-    return env('APP_ENV') == 'local'
-        ? config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET')
-        : config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET');
-}
-/**
- * @param  $amount
- * @param  $currencyCode
- * @param  $payment_method
- * @return bool
- * @description check if the amount is higher than the minimum amount for the stripe gateway
- */
-function checkPriceAmount($amount, $currencyCode, $payment_method)
-{
-    $minimums = [
-        "USD" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "AED" => [
-            "paypal" => 0,
-            "stripe" => 2
-        ],
-        "AUD" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "BGN" => [
-            "paypal" => 0,
-            "stripe" => 1
-        ],
-        "BRL" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "CAD" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "CHF" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "CZK" => [
-            "paypal" => 0,
-            "stripe" => 15
-        ],
-        "DKK" => [
-            "paypal" => 0,
-            "stripe" => 2.5
-        ],
-        "EUR" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "GBP" => [
-            "paypal" => 0,
-            "stripe" => 0.3
-        ],
-        "HKD" => [
-            "paypal" => 0,
-            "stripe" => 4
-        ],
-        "HRK" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "HUF" => [
-            "paypal" => 0,
-            "stripe" => 175
-        ],
-        "INR" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "JPY" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "MXN" => [
-            "paypal" => 0,
-            "stripe" => 10
-        ],
-        "MYR" => [
-            "paypal" => 0,
-            "stripe" => 2
-        ],
-        "NOK" => [
-            "paypal" => 0,
-            "stripe" => 3
-        ],
-        "NZD" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "PLN" => [
-            "paypal" => 0,
-            "stripe" => 2
-        ],
-        "RON" => [
-            "paypal" => 0,
-            "stripe" => 2
-        ],
-        "SEK" => [
-            "paypal" => 0,
-            "stripe" => 3
-        ],
-        "SGD" => [
-            "paypal" => 0,
-            "stripe" => 0.5
-        ],
-        "THB" => [
-            "paypal" => 0,
-            "stripe" => 10
-        ]
-    ];
-    return $amount >= $minimums[$currencyCode][$payment_method];
-}

+ 98 - 0
app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php

@@ -0,0 +1,98 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateStripeSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        $this->migrator->addEncrypted('stripe.secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:SECRET') : null);
+        $this->migrator->addEncrypted('stripe.endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') : null);
+        $this->migrator->addEncrypted('stripe.test_secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') : null);
+        $this->migrator->addEncrypted('stripe.test_endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET') : null);
+        $this->migrator->add('stripe.enabled', false);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::PAYMENTS:STRIPE:SECRET',
+                'value' => $this->getNewValue('secret_key'),
+                'type' => 'string',
+                'description' => 'The Secret Key of your Stripe App'
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET',
+                'value' => $this->getNewValue('endpoint_secret'),
+                'type' => 'string',
+                'description' => 'The Endpoint Secret of your Stripe App'
+
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:STRIPE:TEST_SECRET',
+                'value' => $this->getNewValue('test_secret_key'),
+                'type' => 'string',
+                'description' => 'The Test Secret Key of your Stripe App'
+            ],
+            [
+                'key' => 'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET',
+                'value' => $this->getNewValue('test_endpoint_secret'),
+                'type' => 'string',
+                'description' => 'The Test Endpoint Secret of your Stripe App'
+            ]
+        ]);
+
+        $this->migrator->delete('stripe.secret_key');
+        $this->migrator->delete('stripe.endpoint_secret');
+        $this->migrator->delete('stripe.enabled');
+        $this->migrator->delete('stripe.test_secret_key');
+        $this->migrator->delete('stripe.test_endpoint_secret');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'stripe'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 4 - 4
app/Extensions/PaymentGateways/Stripe/web_routes.php

@@ -1,17 +1,17 @@
 <?php
 
 use Illuminate\Support\Facades\Route;
+use App\Extensions\PaymentGateways\Stripe\StripeExtension;
 
-include_once(__DIR__ . '/index.php');
 Route::middleware(['web', 'auth'])->group(function () {
     Route::get('payment/StripePay/{shopProduct}', function () {
-        StripePay(request());
+        StripeExtension::StripePay(request());
     })->name('payment.StripePay');
 
     Route::get(
         'payment/StripeSuccess',
         function () {
-            StripeSuccess(request());
+            StripeExtension::StripeSuccess(request());
         }
     )->name('payment.StripeSuccess');
 });
@@ -19,5 +19,5 @@ Route::middleware(['web', 'auth'])->group(function () {
 
 // Stripe WebhookRoute -> validation in Route Handler
 Route::post('payment/StripeWebhooks', function () {
-    StripeWebhooks(request());
+    StripeExtension::StripeWebhooks(request());
 })->name('payment.StripeWebhooks');

+ 9 - 0
app/Helpers/AbstractExtension.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace App\Helpers;
+
+// create a abstract class for the extension that will contain all the methods that will be used in the extension
+abstract class AbstractExtension
+{
+    abstract public static function getConfig(): array;
+}

+ 165 - 35
app/Helpers/ExtensionHelper.php

@@ -2,81 +2,211 @@
 
 namespace App\Helpers;
 
+/**
+ * Summary of ExtensionHelper
+ */
 class ExtensionHelper
 {
     /**
-     * Get a config of an extension by its name
-     * @param string $extensionName
-     * @param string $configname
+     * Get all extensions
+     * @return array array of all extensions e.g. ["App\Extensions\PayPal", "App\Extensions\Stripe"]
      */
-    public static function getExtensionConfig(string $extensionName, string $configname)
+    public static function getAllExtensions()
+    {
+        $extensionNamespaces = glob(app_path() . '/Extensions/*', GLOB_ONLYDIR);
+        $extensions = [];
+        foreach ($extensionNamespaces as $extensionNamespace) {
+            $extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
+        }
+        // remove base path from every extension but keep app/Extensions/...
+        $extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions);
+
+        return $extensions;
+    }
+
+    /**
+     * Get all extensions by namespace
+     * @param string $namespace case sensitive namespace of the extension e.g. PaymentGateways
+     * @return array array of all extensions e.g. ["App\Extensions\PayPal", "App\Extensions\Stripe"]
+     */
+    public static function getAllExtensionsByNamespace(string $namespace)
+    {
+        $extensions = glob(app_path() . '/Extensions/' . $namespace . '/*', GLOB_ONLYDIR);
+        // remove base path from every extension but keep app/Extensions/...
+        $extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions);
+
+        return $extensions;
+    }
+
+    /**
+     * Get an extension by its name
+     * @param string $extensionName case sensitive name of the extension e.g. PayPal
+     * @return string|null the path of the extension e.g. App\Extensions\PayPal
+     */
+    public static function getExtension(string $extensionName)
     {
-        $extensions = ExtensionHelper::getAllExtensions();
+        $extensions = self::getAllExtensions();
+        // filter the extensions by the extension name
+        $extensions = array_filter($extensions, fn ($item) => basename($item) == $extensionName);
+
+        // return the only extension
+        return array_shift($extensions);
+    }
+
+    /**
+     * Get all extension classes
+     * @return array array of all extension classes e.g. ["App\Extensions\PayPal\PayPalExtension", "App\Extensions\Stripe\StripeExtension"]
+     */
+    public static function getAllExtensionClasses()
+    {
+        $extensions = self::getAllExtensions();
+        // add the ExtensionClass to the end of the namespace 
+        $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions);
+        // filter out non existing extension classes
+        $extensions = array_filter($extensions, fn ($item) => class_exists($item));
+
+        return $extensions;
+    }
+
+    /**
+     * Get all extension classes by namespace
+     * @param string $namespace case sensitive namespace of the extension e.g. PaymentGateways
+     * @return array array of all extension classes e.g. ["App\Extensions\PayPal\PayPalExtension", "App\Extensions\Stripe\StripeExtension"]
+     */
+    public static function getAllExtensionClassesByNamespace(string $namespace)
+    {
+        $extensions = self::getAllExtensionsByNamespace($namespace);
+        // add the ExtensionClass to the end of the namespace
+        $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions);
+        // filter out non existing extension classes
+        $extensions = array_filter($extensions, fn ($item) => class_exists($item));
+
+        return $extensions;
+    }
+
+    /**
+     * Get the class of an extension by its name
+     * @param string $extensionName case sensitive name of the extension e.g. PayPal
+     * @return string|null the class name of the extension e.g. App\Extensions\PayPal\PayPalExtension
+     */
+    public static function getExtensionClass(string $extensionName)
+    {
+        $extensions = self::getAllExtensions();
 
-        // call the getConfig function of the config file of the extension like that
-        // call_user_func("App\\Extensions\\PaymentGateways\\Stripe" . "\\getConfig");
         foreach ($extensions as $extension) {
             if (!(basename($extension) ==  $extensionName)) {
                 continue;
             }
 
-            $configFile = $extension . '/config.php';
-            if (file_exists($configFile)) {
-                include_once $configFile;
-                $config = call_user_func('App\\Extensions\\' . basename(dirname($extension)) . '\\' . basename($extension) . "\\getConfig");
-            }
+            $extensionClass = $extension . '\\' . $extensionName . 'Extension';
+            return $extensionClass;
+        }
+    }
 
 
-            if (isset($config[$configname])) {
-                return $config[$configname];
-            }
+
+
+    /**
+     * Get a config of an extension by its name
+     * @param string $extensionName
+     * @param string $configname
+     */
+    public static function getExtensionConfig(string $extensionName, string $configname)
+    {
+
+        $extension = self::getExtensionClass($extensionName);
+
+        $config = $extension::getConfig();
+
+
+
+        if (isset($config[$configname])) {
+            return $config[$configname];
         }
 
+
         return null;
     }
 
     public static function getAllCsrfIgnoredRoutes()
     {
-        $extensions = ExtensionHelper::getAllExtensions();
+        $extensions = self::getAllExtensionClasses();
 
         $routes = [];
+
         foreach ($extensions as $extension) {
-            $configFile = $extension . '/config.php';
-            if (file_exists($configFile)) {
-                include_once $configFile;
-                $config = call_user_func('App\\Extensions\\' . basename(dirname($extension)) . '\\' . basename($extension) . "\\getConfig");
-            }
+            $config = $extension::getConfig();
 
             if (isset($config['RoutesIgnoreCsrf'])) {
                 $routes = array_merge($routes, $config['RoutesIgnoreCsrf']);
             }
-
-            // map over the routes and add the extension name as prefix
-            $result = array_map(fn ($item) => "extensions/{$item}", $routes);
         }
+        // map over the routes and add the extension name as prefix
+        $result = array_map(fn ($item) => "extensions/{$item}", $routes);
 
         return $result;
     }
 
     /**
-     * Get all extensions
-     * @return array
+     * Summary of getAllExtensionMigrations
+     * @return array of all migration paths look like: app/Extensions/ExtensionNamespace/ExtensionName/migrations/
      */
-    public static function getAllExtensions()
+    public static function getAllExtensionMigrations()
     {
-        $extensionNamespaces = glob(app_path() . '/Extensions/*', GLOB_ONLYDIR);
-        $extensions = [];
-        foreach ($extensionNamespaces as $extensionNamespace) {
-            $extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
+        $extensions = self::getAllExtensions();
+        // Transform the extensions to a path
+        $extensions = array_map(fn ($item) => self::extensionNameToPath($item), $extensions);
+
+        // get all migration directories of the extensions and return them as array
+        $migrations = [];
+        foreach ($extensions as $extension) {
+            $migrationDir = $extension . '/migrations';
+            if (file_exists($migrationDir)) {
+                $migrations[] = $migrationDir;
+            }
         }
 
-        return $extensions;
+        return $migrations;
     }
 
-    public static function getAllExtensionsByNamespace(string $namespace)
+    /**
+     * Summary of getAllExtensionSettings
+     * @return array of all setting classes look like: App\Extensions\PaymentGateways\PayPal\PayPalSettings
+     */
+    public static function getAllExtensionSettingsClasses()
     {
-        $extensions = glob(app_path() . '/Extensions/' . $namespace . '/*', GLOB_ONLYDIR);
+        $extensions = self::getAllExtensions();
 
-        return $extensions;
+        $settings = [];
+        foreach ($extensions as $extension) {
+
+            $extensionName = basename($extension);
+            $settingsClass = $extension . '\\' . $extensionName . 'Settings';
+            if (class_exists($settingsClass)) {
+                $settings[] = $settingsClass;
+            }
+        }
+
+        return $settings;
+    }
+
+    public static function getExtensionSettings(string $extensionName)
+    {
+        $extension = self::getExtension($extensionName);
+        $settingClass = $extension . '\\' . $extensionName . 'Settings';
+
+        if (class_exists($settingClass)) {
+            return new $settingClass();
+        }
+    }
+
+    /**
+     * Transforms a extension name to a path
+     * @param string $extensionName e.g. App\Extensions\PaymentGateways\PayPal
+     * @return string e.g. C:\xampp\htdocs\laravel\app/Extensions/PaymentGateways/PayPal
+     */
+    private static function extensionNameToPath(string $extensionName)
+    {
+        return app_path() . '/' . str_replace('\\', '/', str_replace('App\\', '', $extensionName));
     }
 }

+ 5 - 2
app/Http/Controllers/Admin/ApplicationApiController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
 
 use App\Http\Controllers\Controller;
 use App\Models\ApplicationApi;
+use App\Settings\LocaleSettings;
 use Exception;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
@@ -20,9 +21,11 @@ class ApplicationApiController extends Controller
      *
      * @return Application|Factory|View|Response
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        return view('admin.api.index');
+        return view('admin.api.index', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     /**

+ 2 - 2
app/Http/Controllers/Admin/InvoiceController.php

@@ -15,7 +15,7 @@ class InvoiceController extends Controller
         $zip = new ZipArchive;
         $zip_safe_path = storage_path('invoices.zip');
         $res = $zip->open($zip_safe_path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
-        $result = $this::rglob(storage_path('app/invoice/*'));
+        $result = $this->rglob(storage_path('app/invoice/*'));
         if ($res === true) {
             $zip->addFromString('1. Info.txt', __('Created at').' '.now()->format('d.m.Y'));
             foreach ($result as $file) {
@@ -38,7 +38,7 @@ class InvoiceController extends Controller
     {
         $files = glob($pattern, $flags);
         foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
-            $files = array_merge($files, $this::rglob($dir.'/'.basename($pattern), $flags));
+            $files = array_merge($files, $this->rglob($dir.'/'.basename($pattern), $flags));
         }
 
         return $files;

+ 19 - 9
app/Http/Controllers/Admin/OverViewController.php

@@ -2,12 +2,14 @@
 
 namespace App\Http\Controllers\Admin;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
+use App\Settings\PterodactylSettings;
+use App\Settings\GeneralSettings;
 use App\Http\Controllers\Controller;
-use App\Models\Egg;
-use App\Models\Location;
-use App\Models\Nest;
-use App\Models\Node;
+use App\Models\Pterodactyl\Egg;
+use App\Models\Pterodactyl\Location;
+use App\Models\Pterodactyl\Nest;
+use App\Models\Pterodactyl\Node;
 use App\Models\Payment;
 use App\Models\Product;
 use App\Models\Server;
@@ -19,7 +21,14 @@ class OverViewController extends Controller
 {
     public const TTL = 86400;
 
-    public function index()
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+    
+    public function index(GeneralSettings $general_settings)
     {
         //Get counters
         $counters = collect();
@@ -134,7 +143,7 @@ class OverViewController extends Controller
 
         //Get node information and prepare collection
         $pteroNodeIds = [];
-        foreach (Pterodactyl::getNodes() as $pteroNode) {
+        foreach ($this->pterodactyl->getNodes() as $pteroNode) {
             array_push($pteroNodeIds, $pteroNode['attributes']['id']);
         }
         $nodes = collect();
@@ -145,7 +154,7 @@ class OverViewController extends Controller
             } //Check if node exists on pterodactyl too, if not, skip
             $nodes->put($nodeId, collect());
             $nodes[$nodeId]->name = $DBnode['name'];
-            $pteroNode = Pterodactyl::getNode($nodeId);
+            $pteroNode = $this->pterodactyl->getNode($nodeId);
             $nodes[$nodeId]->usagePercent = round(max($pteroNode['allocated_resources']['memory'] / ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100), $pteroNode['allocated_resources']['disk'] / ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100)) * 100, 2);
             $counters['totalUsagePercent'] += $nodes[$nodeId]->usagePercent;
 
@@ -156,7 +165,7 @@ class OverViewController extends Controller
         }
         $counters['totalUsagePercent'] = ($DBnodes->count()) ? round($counters['totalUsagePercent'] / $DBnodes->count(), 2) : 0;
 
-        foreach (Pterodactyl::getServers() as $server) { //gets all servers from Pterodactyl and calculates total of credit usage for each node separately + total
+        foreach ($this->pterodactyl->getServers() as $server) { //gets all servers from Pterodactyl and calculates total of credit usage for each node separately + total
             $nodeId = $server['attributes']['node'];
 
             if ($CPServer = Server::query()->where('pterodactyl_id', $server['attributes']['id'])->first()) {
@@ -207,6 +216,7 @@ class OverViewController extends Controller
             'deletedNodesPresent' => ($DBnodes->count() != count($pteroNodeIds)) ? true : false,
             'perPageLimit' => ($counters['servers']->total != Server::query()->count()) ? true : false,
             'tickets' => $tickets,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 

+ 11 - 7
app/Http/Controllers/Admin/PartnerController.php

@@ -5,13 +5,17 @@ namespace App\Http\Controllers\Admin;
 use App\Http\Controllers\Controller;
 use App\Models\PartnerDiscount;
 use App\Models\User;
+use App\Settings\LocaleSettings;
+use App\Settings\ReferralSettings;
 use Illuminate\Http\Request;
 
 class PartnerController extends Controller
 {
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        return view('admin.partners.index');
+        return view('admin.partners.index', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     /**
@@ -117,19 +121,19 @@ class PartnerController extends Controller
                 ';
             })
             ->addColumn('user', function (PartnerDiscount $partner) {
-                return ($user = User::where('id', $partner->user_id)->first()) ? '<a href="'.route('admin.users.show', $partner->user_id).'">'.$user->name.'</a>' : __('Unknown user');
+                return ($user = User::where('id', $partner->user_id)->first()) ? '<a href="'.route('admin.users.show', $partner->user_id) . '">' . $user->name . '</a>' : __('Unknown user');
             })
             ->editColumn('created_at', function (PartnerDiscount $partner) {
                 return $partner->created_at ? $partner->created_at->diffForHumans() : '';
             })
             ->editColumn('partner_discount', function (PartnerDiscount $partner) {
-                return $partner->partner_discount ? $partner->partner_discount.'%' : '0%';
+                return $partner->partner_discount ? $partner->partner_discount . '%' : '0%';
             })
             ->editColumn('registered_user_discount', function (PartnerDiscount $partner) {
-                return $partner->registered_user_discount ? $partner->registered_user_discount.'%' : '0%';
+                return $partner->registered_user_discount ? $partner->registered_user_discount . '%' : '0%';
             })
-            ->editColumn('referral_system_commission', function (PartnerDiscount $partner) {
-                return $partner->referral_system_commission >= 0 ? $partner->referral_system_commission.'%' : __('Default').' ('.config('SETTINGS::REFERRAL:PERCENTAGE').'%)';
+            ->editColumn('referral_system_commission', function (PartnerDiscount $partner, ReferralSettings $referral_settings) {
+                return $partner->referral_system_commission >= 0 ? $partner->referral_system_commission . '%' : __('Default') . ' ('.$referral_settings->percentage . '%)';
             })
             ->rawColumns(['user', 'actions'])
             ->make();

+ 10 - 9
app/Http/Controllers/Admin/PaymentController.php

@@ -18,17 +18,19 @@ use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use App\Helpers\ExtensionHelper;
-
+use App\Settings\GeneralSettings;
+use App\Settings\LocaleSettings;
 
 class PaymentController extends Controller
 {
     /**
      * @return Application|Factory|View
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
         return view('admin.payments.index')->with([
             'payments' => Payment::paginate(15),
+            'locale_datatables' => $locale_settings->datatables
         ]);
     }
 
@@ -37,7 +39,7 @@ class PaymentController extends Controller
      * @param  ShopProduct  $shopProduct
      * @return Application|Factory|View
      */
-    public function checkOut(ShopProduct $shopProduct)
+    public function checkOut(ShopProduct $shopProduct, GeneralSettings $general_settings)
     {
         $discount = PartnerDiscount::getDiscount();
         $price = $shopProduct->price - ($shopProduct->price * $discount / 100);
@@ -49,7 +51,10 @@ class PaymentController extends Controller
             // build a paymentgateways array that contains the routes for the payment gateways and the image path for the payment gateway which lays in public/images/Extensions/PaymentGateways with the extensionname in lowercase
             foreach ($extensions as $extension) {
                 $extensionName = basename($extension);
-                if (!ExtensionHelper::getExtensionConfig($extensionName, 'enabled')) continue; // skip if not enabled
+
+                $extensionSettings = ExtensionHelper::getExtensionSettings($extensionName);
+                if ($extensionSettings->enabled == false) continue;
+
 
                 $payment = new \stdClass();
                 $payment->name = ExtensionHelper::getExtensionConfig($extensionName, 'name');
@@ -58,11 +63,6 @@ class PaymentController extends Controller
             }
         }
 
-
-
-
-
-
         return view('store.checkout')->with([
             'product' => $shopProduct,
             'discountpercent' => $discount,
@@ -73,6 +73,7 @@ class PaymentController extends Controller
             'total' => $shopProduct->getTotalPrice(),
             'paymentGateways'   => $paymentGateways,
             'productIsFree' => $price <= 0,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 

+ 28 - 21
app/Http/Controllers/Admin/ProductController.php

@@ -3,9 +3,12 @@
 namespace App\Http\Controllers\Admin;
 
 use App\Http\Controllers\Controller;
-use App\Models\Location;
-use App\Models\Nest;
+use App\Models\Pterodactyl\Location;
+use App\Models\Pterodactyl\Nest;
 use App\Models\Product;
+use App\Settings\GeneralSettings;
+use App\Settings\LocaleSettings;
+use App\Settings\UserSettings;
 use Exception;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
@@ -21,9 +24,11 @@ class ProductController extends Controller
      *
      * @return Application|Factory|View
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        return view('admin.products.index');
+        return view('admin.products.index', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     /**
@@ -31,15 +36,16 @@ class ProductController extends Controller
      *
      * @return Application|Factory|View
      */
-    public function create()
+    public function create(GeneralSettings $general_settings)
     {
         return view('admin.products.create', [
             'locations' => Location::with('nodes')->get(),
             'nests' => Nest::with('eggs')->get(),
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
-    public function clone(Request $request, Product $product)
+    public function clone(Product $product)
     {
         return view('admin.products.create', [
             'product' => $product,
@@ -90,11 +96,12 @@ class ProductController extends Controller
      * @param  Product  $product
      * @return Application|Factory|View
      */
-    public function show(Product $product)
+    public function show(Product $product, UserSettings $user_settings, GeneralSettings $general_settings)
     {
         return view('admin.products.show', [
             'product' => $product,
-            'minimum_credits' => config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER'),
+            'minimum_credits' => $user_settings->min_credits_to_make_server,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -104,12 +111,13 @@ class ProductController extends Controller
      * @param  Product  $product
      * @return Application|Factory|View
      */
-    public function edit(Product $product)
+    public function edit(Product $product, GeneralSettings $general_settings)
     {
         return view('admin.products.edit', [
             'product' => $product,
             'locations' => Location::with('nodes')->get(),
             'nests' => Nest::with('eggs')->get(),
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -157,7 +165,7 @@ class ProductController extends Controller
      * @param  Product  $product
      * @return RedirectResponse
      */
-    public function disable(Request $request, Product $product)
+    public function disable(Product $product)
     {
         $product->update(['disabled' => ! $product->disabled]);
 
@@ -190,7 +198,6 @@ class ProductController extends Controller
     public function dataTable()
     {
         $query = Product::with(['servers']);
-
         return datatables($query)
             ->addColumn('actions', function (Product $product) {
                 return '
@@ -219,18 +226,18 @@ class ProductController extends Controller
                 $checked = $product->disabled == false ? 'checked' : '';
 
                 return '
-                                <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.products.disable', $product->id).'">
-                            '.csrf_field().'
-                            '.method_field('PATCH').'
-                            <div class="custom-control custom-switch">
-                            <input '.$checked.' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch'.$product->id.'">
-                            <label class="custom-control-label" for="switch'.$product->id.'"></label>
-                          </div>
-                       </form>
+                    <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.products.disable', $product->id).'">
+                        '.csrf_field().'
+                        '.method_field('PATCH').'
+                        <div class="custom-control custom-switch">
+                        <input '.$checked.' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch'.$product->id.'">
+                        <label class="custom-control-label" for="switch'.$product->id.'"></label>
+                        </div>
+                    </form>
                 ';
             })
-            ->editColumn('minimum_credits', function (Product $product) {
-                return $product->minimum_credits==-1 ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') : $product->minimum_credits;
+            ->editColumn('minimum_credits', function (Product $product, UserSettings $user_settings) {
+                return $product->minimum_credits==-1 ? $user_settings->min_credits_to_make_server : $product->minimum_credits;
             })
             ->editColumn('created_at', function (Product $product) {
                 return $product->created_at ? $product->created_at->diffForHumans() : '';

+ 19 - 9
app/Http/Controllers/Admin/ServerController.php

@@ -2,10 +2,12 @@
 
 namespace App\Http\Controllers\Admin;
 
-use App\Classes\Pterodactyl;
 use App\Http\Controllers\Controller;
 use App\Models\Server;
 use App\Models\User;
+use App\Settings\LocaleSettings;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
 use Exception;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
@@ -18,14 +20,23 @@ use Illuminate\Support\Facades\Log;
 
 class ServerController extends Controller
 {
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     /**
      * Display a listing of the resource.
      *
      * @return Application|Factory|View|Response
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        return view('admin.servers.index');
+        return view('admin.servers.index', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     /**
@@ -65,7 +76,7 @@ class ServerController extends Controller
 
             // try to update the owner on pterodactyl
             try {
-                $response = Pterodactyl::updateServerOwner($server, $user->pterodactyl_id);
+                $response = $this->pterodactyl->updateServerOwner($server, $user->pterodactyl_id);
                 if ($response->getStatusCode() != 200) {
                     return redirect()->back()->with('error', 'Failed to update server owner on pterodactyl');
                 }
@@ -118,7 +129,6 @@ class ServerController extends Controller
 
     public function syncServers()
     {
-        $pteroServers = Pterodactyl::getServers();
         $CPServers = Server::get();
 
         $CPIDArray = [];
@@ -129,7 +139,7 @@ class ServerController extends Controller
             }
         }
 
-        foreach ($pteroServers as $server) { //go thru all ptero servers, if server exists, change value to true in array.
+        foreach ($this->pterodactyl->getServers() as $server) { //go thru all ptero servers, if server exists, change value to true in array.
             if (isset($CPIDArray[$server['attributes']['id']])) {
                 $CPIDArray[$server['attributes']['id']] = true;
 
@@ -149,7 +159,7 @@ class ServerController extends Controller
         }, ARRAY_FILTER_USE_BOTH); //Array of servers, that dont exist on ptero (value == false)
         $deleteCount = 0;
         foreach ($filteredArray as $key => $CPID) { //delete servers that dont exist on ptero anymore
-            if (!Pterodactyl::getServerAttributes($key, true)) {
+            if (!$this->pterodactyl->getServerAttributes($key, true)) {
                 $deleteCount++;
             }
         }
@@ -216,8 +226,8 @@ class ServerController extends Controller
             ->editColumn('suspended', function (Server $server) {
                 return $server->suspended ? $server->suspended->diffForHumans() : '';
             })
-            ->editColumn('name', function (Server $server) {
-                return '<a class="text-info" target="_blank" href="' . config('SETTINGS::SYSTEM:PTERODACTYL:URL') . '/admin/servers/view/' . $server->pterodactyl_id . '">' . strip_tags($server->name) . '</a>';
+            ->editColumn('name', function (Server $server, PterodactylSettings $ptero_settings) {
+                return '<a class="text-info" target="_blank" href="' . $ptero_settings->panel_url . '/admin/servers/view/' . $server->pterodactyl_id . '">' . strip_tags($server->name) . '</a>';
             })
             ->rawColumns(['user', 'actions', 'status', 'name'])
             ->make();

+ 95 - 19
app/Http/Controllers/Admin/SettingsController.php

@@ -2,11 +2,15 @@
 
 namespace App\Http\Controllers\Admin;
 
+use App\Helpers\ExtensionHelper;
 use App\Http\Controllers\Controller;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Contracts\View\View;
 use Illuminate\Http\Response;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redirect;
+use Illuminate\Support\Facades\Validator;
 use Qirolab\Theme\Theme;
 
 class SettingsController extends Controller
@@ -19,37 +23,109 @@ class SettingsController extends Controller
     public function index()
     {
 
+        // get all other settings in app/Settings directory
+        // group items by file name like $categories
+        $settings = collect();
+        $settings_classes = [];
 
-        //Get all tabs as laravel view paths
-        $tabs = [];
-        if(file_exists(Theme::getViewPaths()[0] . '/admin/settings/tabs/')){
-            $tabspath = glob(Theme::getViewPaths()[0] . '/admin/settings/tabs/*.blade.php');
-        }else{
-            $tabspath = glob(Theme::path($path = 'views', $themeName = 'default').'/admin/settings/tabs/*.blade.php');
+        // get all app settings
+        $app_settings = scandir(app_path('Settings'));
+        $app_settings = array_diff($app_settings, ['.', '..']);
+        // append App\Settings to class name
+        foreach ($app_settings as $app_setting) {
+            $settings_classes[] = 'App\\Settings\\' . str_replace('.php', '', $app_setting);
         }
+        // get all extension settings
+        $settings_files = array_merge($settings_classes, ExtensionHelper::getAllExtensionSettingsClasses());
 
-          foreach ($tabspath as $filename) {
-            $tabs[] = 'admin.settings.tabs.'.basename($filename, '.blade.php');
-        }
 
+        foreach ($settings_files as $file) {
+
+            $className = $file;
+            // instantiate the class and call toArray method to get all options
+            $options = (new $className())->toArray();
+
+            // call getOptionInputData method to get all options
+            if (method_exists($className, 'getOptionInputData')) {
+                $optionInputData = $className::getOptionInputData();
+            } else {
+                $optionInputData = [];
+            }
+
+            // collect all option input data
+            $optionsData = [];
+            foreach ($options as $key => $value) {
+                $optionsData[$key] = [
+                    'value' => $value,
+                    'label' => $optionInputData[$key]['label'] ?? ucwords(str_replace('_', ' ', $key)),
+                    'type' => $optionInputData[$key]['type'] ?? 'string',
+                    'description' => $optionInputData[$key]['description'] ?? '',
+                    'options' => $optionInputData[$key]['options'] ?? [],
+                ];
+            }
 
-        //Generate a html list item for each tab based on tabs file basename, set first tab as active
-        $tabListItems = [];
-        foreach ($tabs as $tab) {
-            $tabName = str_replace('admin.settings.tabs.', '', $tab);
-            $tabListItems[] = '<li class="nav-item">
-            <a class="nav-link '.(empty($tabListItems) ? 'active' : '').'" data-toggle="pill" href="#'.$tabName.'">
-            '.__(ucfirst($tabName)).'
-            </a></li>';
+            // collect category icon if available
+            if (isset($optionInputData['category_icon'])) {
+                $optionsData['category_icon'] = $optionInputData['category_icon'];
+            }
+            $optionsData['settings_class'] = $className;
+
+            $settings[str_replace('Settings', '', class_basename($className))] = $optionsData;
         }
 
+        $settings->sort();
+
+
         $themes = array_diff(scandir(base_path('themes')), array('..', '.'));
 
         return view('admin.settings.index', [
-            'tabs' => $tabs,
-            'tabListItems' => $tabListItems,
+            'settings' => $settings->all(),
             'themes' => $themes,
             'active_theme' => Theme::active(),
         ]);
     }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     */
+    public function update(Request $request)
+    {
+        $category = request()->get('category');
+        $settings_class = request()->get('settings_class');
+
+        if (method_exists($settings_class, 'getValidations')) {
+            $validations = $settings_class::getValidations();
+        } else {
+            $validations = [];
+        }
+
+
+        $validator = Validator::make($request->all(), $validations);
+        if ($validator->fails()) {
+            return Redirect::to('admin/settings' . '#' . $category)->withErrors($validator)->withInput();
+        }
+
+        $settingsClass = new $settings_class();
+
+        foreach ($settingsClass->toArray() as $key => $value) {
+            // Get the type of the settingsclass property
+            $rp = new \ReflectionProperty($settingsClass, $key);
+            $rpType = $rp->getType();
+
+            if ($rpType == 'bool') {
+                $settingsClass->$key = $request->has($key);
+                continue;
+            }
+
+            $nullable = $rpType->allowsNull();
+            if ($nullable) $settingsClass->$key = $request->input($key) ?? null;
+            else $settingsClass->$key = $request->input($key);
+        }
+
+        $settingsClass->save();
+
+
+        return Redirect::to('admin/settings' . '#' . $category)->with('success', 'Settings updated successfully.');
+    }
 }

+ 11 - 13
app/Http/Controllers/Admin/ShopProductController.php

@@ -3,6 +3,8 @@
 namespace App\Http\Controllers\Admin;
 
 use App\Models\ShopProduct;
+use App\Settings\GeneralSettings;
+use App\Settings\LocaleSettings;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Contracts\View\View;
@@ -20,20 +22,14 @@ class ShopProductController extends Controller
      *
      * @return Application|Factory|View|Response
      */
-    public function index(Request $request)
+    public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings)
     {
-        $isPaymentSetup = false;
+        $isStoreEnabled = $general_settings->store_enabled;
 
-        if (
-            env('APP_ENV') == 'local' ||
-            config('SETTINGS::PAYMENTS:PAYPAL:SECRET') && config('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') ||
-            config('SETTINGS::PAYMENTS:STRIPE:SECRET') && config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') && config('SETTINGS::PAYMENTS:STRIPE:METHODS')
-        ) {
-            $isPaymentSetup = true;
-        }
 
         return view('admin.store.index', [
-            'isPaymentSetup' => $isPaymentSetup,
+            'isStoreEnabled' => $isStoreEnabled,
+            'locale_datatables' => $locale_settings->datatables
         ]);
     }
 
@@ -42,10 +38,11 @@ class ShopProductController extends Controller
      *
      * @return Application|Factory|View|Response
      */
-    public function create()
+    public function create(GeneralSettings $general_settings)
     {
         return view('admin.store.create', [
             'currencyCodes' => config('currency_codes'),
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -79,11 +76,12 @@ class ShopProductController extends Controller
      * @param  ShopProduct  $shopProduct
      * @return Application|Factory|View|Response
      */
-    public function edit(ShopProduct $shopProduct)
+    public function edit(ShopProduct $shopProduct, GeneralSettings $general_settings)
     {
         return view('admin.store.edit', [
             'currencyCodes' => config('currency_codes'),
             'shopProduct' => $shopProduct,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -117,7 +115,7 @@ class ShopProductController extends Controller
      * @param  ShopProduct  $shopProduct
      * @return RedirectResponse
      */
-    public function disable(Request $request, ShopProduct $shopProduct)
+    public function disable(ShopProduct $shopProduct)
     {
         $shopProduct->update(['disabled' => !$shopProduct->disabled]);
 

+ 5 - 2
app/Http/Controllers/Admin/UsefulLinkController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
 use App\Enums\UsefulLinkLocation;
 use App\Http\Controllers\Controller;
 use App\Models\UsefulLink;
+use App\Settings\LocaleSettings;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Contracts\View\View;
@@ -19,9 +20,11 @@ class UsefulLinkController extends Controller
      *
      * @return Application|Factory|View|Response
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        return view('admin.usefullinks.index');
+        return view('admin.usefullinks.index', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     /**

+ 30 - 14
app/Http/Controllers/Admin/UserController.php

@@ -2,11 +2,14 @@
 
 namespace App\Http\Controllers\Admin;
 
-use App\Classes\Pterodactyl;
 use App\Events\UserUpdateCreditsEvent;
 use App\Http\Controllers\Controller;
 use App\Models\User;
 use App\Notifications\DynamicNotification;
+use App\Settings\LocaleSettings;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\GeneralSettings;
 use Exception;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
@@ -26,12 +29,11 @@ use Spatie\QueryBuilder\QueryBuilder;
 
 class UserController extends Controller
 {
+    private $pterodactyl;
 
-    private Pterodactyl $pterodactyl;
-
-    public function __construct(Pterodactyl $pterodactyl)
+    public function __construct(PterodactylSettings $ptero_settings)
     {
-        $this->pterodactyl = $pterodactyl;
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
     }
 
     /**
@@ -40,9 +42,12 @@ class UserController extends Controller
      * @param  Request  $request
      * @return Application|Factory|View|Response
      */
-    public function index(Request $request)
+    public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings)
     {
-        return view('admin.users.index');
+        return view('admin.users.index', [
+            'locale_datatables' => $locale_settings->datatables,
+            'credits_display_name' => $general_settings->credits_display_name
+        ]);
     }
 
     /**
@@ -51,7 +56,7 @@ class UserController extends Controller
      * @param  User  $user
      * @return Application|Factory|View|Response
      */
-    public function show(User $user)
+    public function show(User $user, LocaleSettings $locale_settings, GeneralSettings $general_settings)
     {
         //QUERY ALL REFERRALS A USER HAS
         //i am not proud of this at all.
@@ -65,6 +70,8 @@ class UserController extends Controller
         return view('admin.users.show')->with([
             'user' => $user,
             'referrals' => $allReferals,
+            'locale_datatables' => $locale_settings->datatables,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -99,10 +106,11 @@ class UserController extends Controller
      * @param  User  $user
      * @return Application|Factory|View|Response
      */
-    public function edit(User $user)
+    public function edit(User $user, GeneralSettings $general_settings)
     {
         return view('admin.users.edit')->with([
             'user' => $user,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -158,6 +166,10 @@ class UserController extends Controller
      */
     public function destroy(User $user)
     {
+        if ($user->role === 'admin' && User::query()->where('role', 'admin')->count() === 1) {
+            return redirect()->back()->with('error', __('You can not delete the last admin!'));
+        }
+
         $user->delete();
 
         return redirect()->back()->with('success', __('user has been removed!'));
@@ -169,7 +181,7 @@ class UserController extends Controller
      * @param  User  $user
      * @return RedirectResponse
      */
-    public function verifyEmail(Request $request, User $user)
+    public function verifyEmail(User $user)
     {
         $user->verifyEmail();
 
@@ -207,7 +219,7 @@ class UserController extends Controller
      * @param  User  $user
      * @return Application|Factory|View|Response
      */
-    public function notifications(User $user)
+    public function notifications()
     {
         return view('admin.users.notifications');
     }
@@ -248,7 +260,11 @@ class UserController extends Controller
         }
         $all = $data['all'] ?? false;
         $users = $all ? User::all() : User::whereIn('id', $data['users'])->get();
-        Notification::send($users, new DynamicNotification($data['via'], $database, $mail));
+        try {
+            Notification::send($users, new DynamicNotification($data['via'], $database, $mail));
+        } catch (Exception $e) {
+            return redirect()->route('admin.users.notifications')->with('error', __('The attempt to send the email failed with the error: ' . $e->getMessage()));
+        }
 
         return redirect()->route('admin.users.notifications')->with('success', __('Notification sent!'));
     }
@@ -333,8 +349,8 @@ class UserController extends Controller
             ->editColumn('last_seen', function (User $user) {
                 return $user->last_seen ? $user->last_seen->diffForHumans() : __('Never');
             })
-            ->editColumn('name', function (User $user) {
-                return '<a class="text-info" target="_blank" href="' . config('SETTINGS::SYSTEM:PTERODACTYL:URL') . '/admin/users/view/' . $user->pterodactyl_id . '">' . strip_tags($user->name) . '</a>';
+            ->editColumn('name', function (User $user, PterodactylSettings $ptero_settings) {
+                return '<a class="text-info" target="_blank" href="' . $ptero_settings->panel_url . '/admin/users/view/' . $user->pterodactyl_id . '">' . strip_tags($user->name) . '</a>';
             })
             ->rawColumns(['avatar', 'name', 'credits', 'role', 'usage',  'actions'])
             ->make();

+ 19 - 9
app/Http/Controllers/Admin/VoucherController.php

@@ -6,6 +6,8 @@ use App\Events\UserUpdateCreditsEvent;
 use App\Http\Controllers\Controller;
 use App\Models\User;
 use App\Models\Voucher;
+use App\Settings\GeneralSettings;
+use App\Settings\LocaleSettings;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Contracts\View\View;
@@ -22,9 +24,12 @@ class VoucherController extends Controller
      *
      * @return Application|Factory|View
      */
-    public function index()
+    public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings)
     {
-        return view('admin.vouchers.index');
+        return view('admin.vouchers.index', [
+            'locale_datatables' => $locale_settings->datatables,
+            'credits_display_name' => $general_settings->credits_display_name
+        ]);
     }
 
     /**
@@ -32,9 +37,11 @@ class VoucherController extends Controller
      *
      * @return Application|Factory|View
      */
-    public function create()
+    public function create(GeneralSettings $general_settings)
     {
-        return view('admin.vouchers.create');
+        return view('admin.vouchers.create', [
+            'credits_display_name' => $general_settings->credits_display_name
+        ]);
     }
 
     /**
@@ -75,10 +82,11 @@ class VoucherController extends Controller
      * @param  Voucher  $voucher
      * @return Application|Factory|View
      */
-    public function edit(Voucher $voucher)
+    public function edit(Voucher $voucher, GeneralSettings $general_settings)
     {
         return view('admin.vouchers.edit', [
             'voucher' => $voucher,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -117,10 +125,12 @@ class VoucherController extends Controller
         return redirect()->back()->with('success', __('voucher has been removed!'));
     }
 
-    public function users(Voucher $voucher)
+    public function users(Voucher $voucher, LocaleSettings $locale_settings, GeneralSettings $general_settings)
     {
         return view('admin.vouchers.users', [
             'voucher' => $voucher,
+            'locale_datatables' => $locale_settings->datatables,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -130,7 +140,7 @@ class VoucherController extends Controller
      *
      * @throws ValidationException
      */
-    public function redeem(Request $request)
+    public function redeem(Request $request, GeneralSettings $general_settings)
     {
         //general validations
         $request->validate([
@@ -161,7 +171,7 @@ class VoucherController extends Controller
 
         if ($request->user()->credits + $voucher->credits >= 99999999) {
             throw ValidationException::withMessages([
-                'code' => "You can't redeem this voucher because you would exceed the  limit of ".CREDITS_DISPLAY_NAME,
+                'code' => "You can't redeem this voucher because you would exceed the  limit of " . $general_settings->credits_display_name,
             ]);
         }
 
@@ -171,7 +181,7 @@ class VoucherController extends Controller
         event(new UserUpdateCreditsEvent($request->user()));
 
         return response()->json([
-            'success' => "{$voucher->credits} ".CREDITS_DISPLAY_NAME.' '.__('have been added to your balance!'),
+            'success' => "{$voucher->credits} ". $general_settings->credits_display_name .' '.__('have been added to your balance!'),
         ]);
     }
 

+ 7 - 2
app/Http/Controllers/Api/NotificationController.php

@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Notification;
 use Illuminate\Support\HtmlString;
 use Illuminate\Validation\ValidationException;
 use Spatie\ValidationRules\Rules\Delimited;
+use Exception;
 
 class NotificationController extends Controller
 {
@@ -104,8 +105,12 @@ class NotificationController extends Controller
                 'users' => ['No users found!'],
             ]);
         }
-
-        Notification::send($users, new DynamicNotification($via, $database, $mail));
+        try {
+            Notification::send($users, new DynamicNotification($via, $database, $mail));
+        }
+        catch (Exception $e) {
+            return response()->json(['message' => 'The attempt to send the email failed with the error: ' . $e->getMessage()], 500);
+        }
 
         return response()->json(['message' => 'Notification successfully sent.', 'user_count' => $users->count()]);
     }

+ 21 - 11
app/Http/Controllers/Api/UserController.php

@@ -2,13 +2,16 @@
 
 namespace App\Http\Controllers\Api;
 
-use App\Classes\Pterodactyl;
 use App\Events\UserUpdateCreditsEvent;
 use App\Http\Controllers\Controller;
 use App\Models\DiscordUser;
 use App\Models\User;
 use App\Notifications\ReferralNotification;
 use App\Traits\Referral;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\ReferralSettings;
+use App\Settings\UserSettings;
 use Carbon\Carbon;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@@ -34,6 +37,13 @@ class UserController extends Controller
 
     const ALLOWED_FILTERS = ['name', 'server_limit', 'email', 'pterodactyl_id', 'role', 'suspended'];
 
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     /**
      * Display a listing of the resource.
      *
@@ -95,7 +105,7 @@ class UserController extends Controller
 
         //Update Users Password on Pterodactyl
         //Username,Mail,First and Lastname are required aswell
-        $response = Pterodactyl::client()->patch('/application/users/' . $user->pterodactyl_id, [
+        $response = $this->pterodactyl->application->patch('/application/users/' . $user->pterodactyl_id, [
             'username' => $request->name,
             'first_name' => $request->name,
             'last_name' => $request->name,
@@ -203,7 +213,7 @@ class UserController extends Controller
      *
      * @throws ValidationException
      */
-    public function suspend(Request $request, int $id)
+    public function suspend(int $id)
     {
         $discordUser = DiscordUser::find($id);
         $user = $discordUser ? $discordUser->user : User::findOrFail($id);
@@ -227,7 +237,7 @@ class UserController extends Controller
      *
      * @throws ValidationException
      */
-    public function unsuspend(Request $request, int $id)
+    public function unsuspend(int $id)
     {
         $discordUser = DiscordUser::find($id);
         $user = $discordUser ? $discordUser->user : User::findOrFail($id);
@@ -246,7 +256,7 @@ class UserController extends Controller
     /**
      * @throws ValidationException
      */
-    public function store(Request $request)
+    public function store(Request $request, UserSettings $user_settings, ReferralSettings $referral_settings)
     {
         $request->validate([
             'name' => ['required', 'string', 'max:30', 'min:4', 'alpha_num', 'unique:users'],
@@ -255,7 +265,7 @@ class UserController extends Controller
         ]);
 
         // Prevent the creation of new users via API if this is enabled.
-        if (!config('SETTINGS::SYSTEM:CREATION_OF_NEW_USERS', 'true')) {
+        if (!$user_settings->creation_enabled) {
             throw ValidationException::withMessages([
                 'error' => 'The creation of new users has been blocked by the system administrator.',
             ]);
@@ -264,13 +274,13 @@ class UserController extends Controller
         $user = User::create([
             'name' => $request->input('name'),
             'email' => $request->input('email'),
-            'credits' => config('SETTINGS::USER:INITIAL_CREDITS', 150),
-            'server_limit' => config('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1),
+            'credits' => $user_settings->initial_credits,
+            'server_limit' => $user_settings->initial_server_limit,
             'password' => Hash::make($request->input('password')),
             'referral_code' => $this->createReferralCode(),
         ]);
 
-        $response = Pterodactyl::client()->post('/application/users', [
+        $response = $this->pterodactyl->application->post('/application/users', [
             'external_id' => App::environment('local') ? Str::random(16) : (string) $user->id,
             'username' => $user->name,
             'email' => $user->email,
@@ -297,8 +307,8 @@ class UserController extends Controller
             $ref_code = $request->input('referral_code');
             $new_user = $user->id;
             if ($ref_user = User::query()->where('referral_code', '=', $ref_code)->first()) {
-                if (config('SETTINGS::REFERRAL:MODE') == 'register' || config('SETTINGS::REFERRAL:MODE') == 'both') {
-                    $ref_user->increment('credits', config('SETTINGS::REFERRAL::REWARD'));
+                if ($referral_settings->mode === 'register' || $referral_settings->mode === 'both') {
+                    $ref_user->increment('credits', $referral_settings->reward);
                     $ref_user->notify(new ReferralNotification($ref_user->id, $new_user));
                 }
                 //INSERT INTO USER_REFERRALS TABLE

+ 3 - 2
app/Http/Controllers/Auth/ForgotPasswordController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Auth;
 
 use App\Http\Controllers\Controller;
+use App\Settings\GeneralSettings;
 use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
 use Illuminate\Http\Request;
 
@@ -31,13 +32,13 @@ class ForgotPasswordController extends Controller
         $this->middleware('guest');
     }
 
-    protected function validateEmail(Request $request)
+    protected function validateEmail(Request $request, GeneralSettings $general_settings)
     {
         $this->validate($request, [
             'email' => ['required', 'string', 'email', 'max:255'],
         ]);
 
-        if (config('SETTINGS::RECAPTCHA:ENABLED') == 'true') {
+        if ($general_settings->recaptcha_enabled) {
             $this->validate($request, [
                 'g-recaptcha-response' => 'required|recaptcha',
             ]);

+ 3 - 2
app/Http/Controllers/Auth/LoginController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
 
 use App\Http\Controllers\Controller;
 use App\Providers\RouteServiceProvider;
+use App\Settings\GeneralSettings;
 use Illuminate\Foundation\Auth\AuthenticatesUsers;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
@@ -53,13 +54,13 @@ class LoginController extends Controller
         return $field;
     }
 
-    public function login(Request $request)
+    public function login(Request $request, GeneralSettings $general_settings)
     {
         $validationRules = [
             $this->username() => 'required|string',
             'password' => 'required|string',
         ];
-        if (config('SETTINGS::RECAPTCHA:ENABLED') == 'true') {
+        if ($general_settings->recaptcha_enabled) {
             $validationRules['g-recaptcha-response'] = ['required', 'recaptcha'];
         }
         $request->validate($validationRules);

+ 47 - 15
app/Http/Controllers/Auth/RegisterController.php

@@ -2,13 +2,18 @@
 
 namespace App\Http\Controllers\Auth;
 
-use App\Classes\Pterodactyl;
 use App\Http\Controllers\Controller;
 use App\Models\User;
 use App\Notifications\ReferralNotification;
 use App\Providers\RouteServiceProvider;
 use App\Traits\Referral;
 use Carbon\Carbon;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\GeneralSettings;
+use App\Settings\ReferralSettings;
+use App\Settings\UserSettings;
+use App\Settings\WebsiteSettings;
 use Illuminate\Foundation\Auth\RegistersUsers;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Facades\DB;
@@ -20,6 +25,24 @@ use Illuminate\Validation\ValidationException;
 
 class RegisterController extends Controller
 {
+    private $pterodactyl;
+
+    private $credits_display_name;
+
+    private $recaptcha_enabled;
+
+    private $website_show_tos;
+
+    private $register_ip_check;
+
+    private $initial_credits;
+
+    private $initial_server_limit;
+
+    private $referral_mode;
+
+    private $referral_reward;
+
     /*
     |--------------------------------------------------------------------------
     | Register Controller
@@ -45,9 +68,18 @@ class RegisterController extends Controller
      *
      * @return void
      */
-    public function __construct()
+    public function __construct(PterodactylSettings $ptero_settings, GeneralSettings $general_settings, WebsiteSettings $website_settings, UserSettings $user_settings, ReferralSettings $referral_settings)
     {
         $this->middleware('guest');
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+        $this->credits_display_name = $general_settings->credits_display_name;
+        $this->recaptcha_enabled = $general_settings->recaptcha_enabled;
+        $this->website_show_tos = $website_settings->show_tos;
+        $this->register_ip_check = $user_settings->register_ip_check;
+        $this->initial_credits = $user_settings->initial_credits;
+        $this->initial_server_limit = $user_settings->initial_server_limit;
+        $this->referral_mode = $referral_settings->mode;
+        $this->referral_reward = $referral_settings->reward;
     }
 
     /**
@@ -63,14 +95,14 @@ class RegisterController extends Controller
             'email' => ['required', 'string', 'email', 'max:64', 'unique:users'],
             'password' => ['required', 'string', 'min:8', 'confirmed'],
         ];
-        if (config('SETTINGS::RECAPTCHA:ENABLED') == 'true') {
+        if ($this->recaptcha_enabled) {
             $validationRules['g-recaptcha-response'] = ['required', 'recaptcha'];
         }
-        if (config('SETTINGS::SYSTEM:SHOW_TOS') == 'true') {
+        if ($this->website_show_tos) {
             $validationRules['terms'] = ['required'];
         }
 
-        if (config('SETTINGS::SYSTEM:REGISTER_IP_CHECK', 'true') == 'true') {
+        if ($this->register_ip_check) {
 
             //check if ip has already made an account
             $data['ip'] = session()->get('ip') ?? request()->ip();
@@ -99,15 +131,16 @@ class RegisterController extends Controller
         $user = User::create([
             'name' => $data['name'],
             'email' => $data['email'],
-            'credits' => config('SETTINGS::USER:INITIAL_CREDITS', 150),
-            'server_limit' => config('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1),
+            'credits' => $this->initial_credits,
+            'server_limit' => $this->initial_server_limit,
             'password' => Hash::make($data['password']),
             'referral_code' => $this->createReferralCode(),
+            'pterodactyl_id' => Str::uuid(),
 
         ]);
 
-        $response = Pterodactyl::client()->post('/application/users', [
-            'external_id' => App::environment('local') ? Str::random(16) : (string) $user->id,
+        $response = $this->pterodactyl->application->post('/application/users', [
+            'external_id' => $user->pterodactyl_id,
             'username' => $user->name,
             'email' => $user->email,
             'first_name' => $user->name,
@@ -125,24 +158,23 @@ class RegisterController extends Controller
             ]);
         }
 
-        $user->update([
-            'pterodactyl_id' => $response->json()['attributes']['id'],
-        ]);
+        // delete activity log for user creation where description = 'created' or 'deleted' and subject_id = user_id
+        DB::table('activity_log')->where('description', 'created')->orWhere('description', 'deleted')->where('subject_id', $user->id)->delete();
 
         //INCREMENT REFERRAL-USER CREDITS
         if (!empty($data['referral_code'])) {
             $ref_code = $data['referral_code'];
             $new_user = $user->id;
             if ($ref_user = User::query()->where('referral_code', '=', $ref_code)->first()) {
-                if (config('SETTINGS::REFERRAL:MODE') == 'sign-up' || config('SETTINGS::REFERRAL:MODE') == 'both') {
-                    $ref_user->increment('credits', config('SETTINGS::REFERRAL::REWARD'));
+                if ($this->referral_mode === 'sign-up' || $this->referral_mode === 'both') {
+                    $ref_user->increment('credits', $this->referral_reward);
                     $ref_user->notify(new ReferralNotification($ref_user->id, $new_user));
 
                     //LOGS REFERRALS IN THE ACTIVITY LOG
                     activity()
                         ->performedOn($user)
                         ->causedBy($ref_user)
-                        ->log('gained ' . config('SETTINGS::REFERRAL::REWARD') . ' ' . config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME') . ' for sign-up-referral of ' . $user->name . ' (ID:' . $user->id . ')');
+                        ->log('gained ' . $this->referral_reward . ' ' . $this->credits_display_name . ' for sign-up-referral of ' . $user->name . ' (ID:' . $user->id . ')');
                 }
                 //INSERT INTO USER_REFERRALS TABLE
                 DB::table('user_referrals')->insert([

+ 10 - 8
app/Http/Controllers/Auth/SocialiteController.php

@@ -5,22 +5,24 @@ namespace App\Http\Controllers\Auth;
 use App\Http\Controllers\Controller;
 use App\Models\DiscordUser;
 use App\Models\User;
+use App\Settings\DiscordSettings;
+use App\Settings\UserSettings;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Http;
 use Laravel\Socialite\Facades\Socialite;
 
 class SocialiteController extends Controller
 {
-    public function redirect()
+    public function redirect(DiscordSettings $discord_settings)
     {
-        $scopes = ! empty(config('SETTINGS::DISCORD:BOT_TOKEN')) && ! empty(config('SETTINGS::DISCORD:GUILD_ID')) ? ['guilds.join'] : [];
+        $scopes = !empty($discord_settings->bot_token) && !empty($discord_settings->guild_id) ? ['guilds.join'] : [];
 
         return Socialite::driver('discord')
             ->scopes($scopes)
             ->redirect();
     }
 
-    public function callback()
+    public function callback(DiscordSettings $discord_settings, UserSettings $user_settings)
     {
         if (Auth::guest()) {
             return abort(500);
@@ -29,9 +31,9 @@ class SocialiteController extends Controller
         /** @var User $user */
         $user = Auth::user();
         $discord = Socialite::driver('discord')->user();
-        $botToken = config('SETTINGS::DISCORD:BOT_TOKEN');
-        $guildId = config('SETTINGS::DISCORD:GUILD_ID');
-        $roleId = config('SETTINGS::DISCORD:ROLE_ID');
+        $botToken = $discord_settings->bot_token;
+        $guildId = $discord_settings->guild_id;
+        $roleId = $discord_settings->role_id;
 
         //save / update discord_users
 
@@ -49,8 +51,8 @@ class SocialiteController extends Controller
             DiscordUser::create(array_merge($discord->user, ['user_id' => Auth::user()->id]));
 
             //update user
-            Auth::user()->increment('credits', config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD'));
-            Auth::user()->increment('server_limit', config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD'));
+            Auth::user()->increment('credits', $user_settings->credits_reward_after_verify_discord);
+            Auth::user()->increment('server_limit', $user_settings->server_limit_after_verify_discord);
             Auth::user()->update(['discord_verified_at' => now()]);
         } else {
             $user->discordUser->update($discord->user);

+ 7 - 2
app/Http/Controllers/HomeController.php

@@ -4,7 +4,9 @@ namespace App\Http\Controllers;
 
 use App\Models\PartnerDiscount;
 use App\Models\UsefulLink;
-use Illuminate\Http\Request;
+use App\Settings\GeneralSettings;
+use App\Settings\WebsiteSettings;
+use App\Settings\ReferralSettings;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Hash;
@@ -89,7 +91,7 @@ class HomeController extends Controller
     }
 
     /** Show the application dashboard. */
-    public function index(Request $request)
+    public function index(GeneralSettings $general_settings, WebsiteSettings $website_settings, ReferralSettings $referral_settings)
     {
         $usage = Auth::user()->creditUsage();
         $credits = Auth::user()->Credits();
@@ -120,6 +122,9 @@ class HomeController extends Controller
             'numberOfReferrals' => DB::table('user_referrals')->where('referral_id', '=', Auth::user()->id)->count(),
             'partnerDiscount' => PartnerDiscount::where('user_id', Auth::user()->id)->first(),
             'myDiscount' => PartnerDiscount::getDiscount(),
+            'general_settings' => $general_settings,
+            'website_settings' => $website_settings,
+            'referral_settings' => $referral_settings
         ]);
     }
 }

+ 15 - 9
app/Http/Controllers/Moderation/TicketsController.php

@@ -10,20 +10,23 @@ use App\Models\TicketCategory;
 use App\Models\TicketComment;
 use App\Models\User;
 use App\Notifications\Ticket\User\ReplyNotification;
+use App\Settings\LocaleSettings;
+use App\Settings\PterodactylSettings;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 
 class TicketsController extends Controller
 {
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        $tickets = Ticket::orderBy('id', 'desc')->paginate(10);
-        $ticketcategories = TicketCategory::all();
-
-        return view('moderator.ticket.index', compact('tickets', 'ticketcategories'));
+        return view('moderator.ticket.index', [
+            'tickets' => Ticket::orderBy('id', 'desc')->paginate(10),
+            'ticketcategories' => TicketCategory::all(),
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
-    public function show($ticket_id)
+    public function show($ticket_id, PterodactylSettings $ptero_settings)
     {
         try {
         $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail();
@@ -34,8 +37,9 @@ class TicketsController extends Controller
         $ticketcomments = $ticket->ticketcomments;
         $ticketcategory = $ticket->ticketcategory;
         $server = Server::where('id', $ticket->server)->first();
+        $pterodactyl_url = $ptero_settings->panel_url;
 
-        return view('moderator.ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server'));
+        return view('moderator.ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server', 'pterodactyl_url'));
     }
 
     public function changeStatus($ticket_id)
@@ -164,9 +168,11 @@ class TicketsController extends Controller
             ->make(true);
     }
 
-    public function blacklist()
+    public function blacklist(LocaleSettings $locale_settings)
     {
-        return view('moderator.ticket.blacklist');
+        return view('moderator.ticket.blacklist', [
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
     public function blacklistAdd(Request $request)

+ 15 - 7
app/Http/Controllers/ProductController.php

@@ -2,18 +2,26 @@
 
 namespace App\Http\Controllers;
 
-use App\Classes\Pterodactyl;
-use App\Models\Egg;
-use App\Models\Location;
-use App\Models\Node;
+use App\Classes\PterodactylClient;
+use App\Models\Pterodactyl\Egg;
+use App\Models\Pterodactyl\Location;
+use App\Models\Pterodactyl\Node;
 use App\Models\Product;
+use App\Settings\PterodactylSettings;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Collection;
 
 class ProductController extends Controller
-{
+{   
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     /**
      * @description get product locations based on selected egg
      *
@@ -60,7 +68,7 @@ class ProductController extends Controller
     {
         $nodes = $this->getNodesBasedOnEgg($request, $egg);
         foreach ($nodes as $key => $node) {
-            $pteroNode = Pterodactyl::getNode($node->id);
+            $pteroNode = $this->pterodactyl->getNode($node->id);
             if ($pteroNode['allocated_resources']['memory'] >= ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) || $pteroNode['allocated_resources']['disk'] >= ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100)) {
                 $nodes->forget($key);
             }
@@ -109,7 +117,7 @@ class ProductController extends Controller
             })
             ->get();
 
-        $pteroNode = Pterodactyl::getNode($node->id);
+        $pteroNode = $this->pterodactyl->getNode($node->id);
         foreach ($products as $key => $product) {
             if ($product->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) {
                 $product->doesNotFit = true;

+ 28 - 13
app/Http/Controllers/ProfileController.php

@@ -2,8 +2,12 @@
 
 namespace App\Http\Controllers;
 
-use App\Classes\Pterodactyl;
 use App\Models\User;
+use App\Settings\UserSettings;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\DiscordSettings;
+use App\Settings\ReferralSettings;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
@@ -12,8 +16,15 @@ use Illuminate\Validation\ValidationException;
 
 class ProfileController extends Controller
 {
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     /** Display a listing of the resource. */
-    public function index()
+    public function index(UserSettings $user_settings, DiscordSettings $discord_settings, ReferralSettings $referral_settings)
     {
         switch (Auth::user()->role) {
             case 'admin':
@@ -32,10 +43,14 @@ class ProfileController extends Controller
 
         return view('profile.index')->with([
             'user' => Auth::user(),
-            'credits_reward_after_verify_discord' => config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD'),
-            'force_email_verification' => config('SETTINGS::USER:FORCE_EMAIL_VERIFICATION'),
-            'force_discord_verification' => config('SETTINGS::USER:FORCE_DISCORD_VERIFICATION'),
+            'credits_reward_after_verify_discord' => $user_settings->credits_reward_after_verify_discord,
+            'force_email_verification' => $user_settings->force_email_verification,
+            'force_discord_verification' => $user_settings->force_discord_verification,
             'badgeColor' => $badgeColor,
+            'discord_client_id' => $discord_settings->client_id,
+            'discord_client_secret' => $discord_settings->client_secret,
+            'referral_enabled' => $referral_settings->enabled,
+            'referral_allowed' => $referral_settings->allowed
         ]);
     }
 
@@ -63,15 +78,15 @@ class ProfileController extends Controller
         $user = User::findOrFail($id);
 
         //update password if necessary
-        if (! is_null($request->input('new_password'))) {
+        if (!is_null($request->input('new_password'))) {
 
             //validate password request
             $request->validate([
                 'current_password' => [
                     'required',
                     function ($attribute, $value, $fail) use ($user) {
-                        if (! Hash::check($value, $user->password)) {
-                            $fail('The '.$attribute.' is invalid.');
+                        if (!Hash::check($value, $user->password)) {
+                            $fail('The ' . $attribute . ' is invalid.');
                         }
                     },
                 ],
@@ -81,7 +96,7 @@ class ProfileController extends Controller
 
             //Update Users Password on Pterodactyl
             //Username,Mail,First and Lastname are required aswell
-            $response = Pterodactyl::client()->patch('/application/users/'.$user->pterodactyl_id, [
+            $response = $this->pterodactyl->application->patch('/application/users/' . $user->pterodactyl_id, [
                 'password' => $request->input('new_password'),
                 'username' => $request->input('name'),
                 'first_name' => $request->input('name'),
@@ -103,13 +118,13 @@ class ProfileController extends Controller
 
         //validate request
         $request->validate([
-            'name' => 'required|min:4|max:30|alpha_num|unique:users,name,'.$id.',id',
-            'email' => 'required|email|max:64|unique:users,email,'.$id.',id',
+            'name' => 'required|min:4|max:30|alpha_num|unique:users,name,' . $id . ',id',
+            'email' => 'required|email|max:64|unique:users,email,' . $id . ',id',
             'avatar' => 'nullable',
         ]);
 
         //update avatar
-        if (! is_null($request->input('avatar'))) {
+        if (!is_null($request->input('avatar'))) {
             $avatar = json_decode($request->input('avatar'));
             if ($avatar->input->size > 3000000) {
                 abort(500);
@@ -125,7 +140,7 @@ class ProfileController extends Controller
         }
 
         //update name and email on Pterodactyl
-        $response = Pterodactyl::client()->patch('/application/users/'.$user->pterodactyl_id, [
+        $response = $this->pterodactyl->application->patch('/application/users/' . $user->pterodactyl_id, [
             'username' => $request->input('name'),
             'first_name' => $request->input('name'),
             'last_name' => $request->input('name'),

+ 56 - 39
app/Http/Controllers/ServerController.php

@@ -2,14 +2,18 @@
 
 namespace App\Http\Controllers;
 
-use App\Classes\Pterodactyl;
-use App\Models\Egg;
-use App\Models\Location;
-use App\Models\Nest;
-use App\Models\Node;
+use App\Models\Pterodactyl\Egg;
+use App\Models\Pterodactyl\Location;
+use App\Models\Pterodactyl\Nest;
+use App\Models\Pterodactyl\Node;
 use App\Models\Product;
 use App\Models\Server;
 use App\Notifications\ServerCreationError;
+use App\Settings\UserSettings;
+use App\Settings\ServerSettings;
+use App\Settings\PterodactylSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\GeneralSettings;
 use Exception;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Http\Client\Response;
@@ -20,8 +24,15 @@ use Illuminate\Support\Facades\Request as FacadesRequest;
 
 class ServerController extends Controller
 {
+    private $pterodactyl;
+
+    public function __construct(PterodactylSettings $ptero_settings)
+    {
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     /** Display a listing of the resource. */
-    public function index()
+    public function index(GeneralSettings $general_settings, PterodactylSettings $ptero_settings)
     {
         $servers = Auth::user()->servers;
 
@@ -29,7 +40,7 @@ class ServerController extends Controller
         foreach ($servers as $server) {
 
             //Get server infos from ptero
-            $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id, true);
+            $serverAttributes = $this->pterodactyl->getServerAttributes($server->pterodactyl_id);
             if (! $serverAttributes) {
                 continue;
             }
@@ -61,14 +72,19 @@ class ServerController extends Controller
 
         return view('servers.index')->with([
             'servers' => $servers,
+            'credits_display_name' => $general_settings->credits_display_name,
+            'pterodactyl_url' => $ptero_settings->panel_url,
+            'phpmyadmin_url' => $general_settings->phpmyadmin_url
         ]);
     }
 
     /** Show the form for creating a new resource. */
-    public function create()
+    public function create(UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $general_settings)
     {
-        if (! is_null($this->validateConfigurationRules())) {
-            return $this->validateConfigurationRules();
+        $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings);
+
+        if (!is_null($validate_configuration)) {
+            return $validate_configuration;
         }
 
         $productCount = Product::query()->where('disabled', '=', false)->count();
@@ -98,13 +114,16 @@ class ServerController extends Controller
             'locations' => $locations,
             'eggs' => $eggs,
             'user' => Auth::user(),
+            'server_creation_enabled' => $server_settings->creation_enabled,
+            'min_credits_to_make_server' => $user_settings->min_credits_to_make_server,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
     /**
      * @return null|RedirectResponse
      */
-    private function validateConfigurationRules()
+    private function validateConfigurationRules(UserSettings $user_settings, ServerSettings $server_settings)
     {
         //limit validation
         if (Auth::user()->servers()->count() >= Auth::user()->server_limit) {
@@ -120,35 +139,31 @@ class ServerController extends Controller
             $nodeName = $node->name;
 
             // Check if node has enough memory and disk space
-            $checkResponse = Pterodactyl::checkNodeResources($node, $product->memory, $product->disk);
+            $checkResponse = $this->pterodactyl->checkNodeResources($node, $product->memory, $product->disk);
             if ($checkResponse == false) {
                 return redirect()->route('servers.index')->with('error', __("The node '".$nodeName."' doesn't have the required memory or disk left to allocate this product."));
             }
 
             // Min. Credits
-            if (
-                Auth::user()->credits <
-                ($product->minimum_credits == -1
-                    ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)
-                    : $product->minimum_credits)
-            ) {
+            if (Auth::user()->credits < ($product->minimum_credits == -1
+                ? $user_settings->min_credits_to_make_server
+                : $product->minimum_credits)) {
                 return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!');
             }
         }
 
         //Required Verification for creating an server
-        if (config('SETTINGS::USER:FORCE_EMAIL_VERIFICATION', 'false') === 'true' && ! Auth::user()->hasVerifiedEmail()) {
+        if ($user_settings->force_email_verification && !Auth::user()->hasVerifiedEmail()) {
             return redirect()->route('profile.index')->with('error', __('You are required to verify your email address before you can create a server.'));
         }
 
         //Required Verification for creating an server
-
-        if (! config('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS', 'true') && Auth::user()->role != 'admin') {
+        if (!$server_settings->creation_enabled && Auth::user()->role != 'admin') {
             return redirect()->route('servers.index')->with('error', __('The system administrator has blocked the creation of new servers.'));
         }
 
         //Required Verification for creating an server
-        if (config('SETTINGS::USER:FORCE_DISCORD_VERIFICATION', 'false') === 'true' && ! Auth::user()->discordUser) {
+        if ($user_settings->force_discord_verification && !Auth::user()->discordUser) {
             return redirect()->route('profile.index')->with('error', __('You are required to link your discord account before you can create a server.'));
         }
 
@@ -156,13 +171,15 @@ class ServerController extends Controller
     }
 
     /** Store a newly created resource in storage. */
-    public function store(Request $request)
+    public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings)
     {
         /** @var Node $node */
         /** @var Egg $egg */
         /** @var Product $product */
-        if (! is_null($this->validateConfigurationRules())) {
-            return $this->validateConfigurationRules();
+        $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings);
+
+        if (!is_null($validate_configuration)) {
+            return $validate_configuration;
         }
 
         $request->validate([
@@ -183,13 +200,13 @@ class ServerController extends Controller
         ]);
 
         //get free allocation ID
-        $allocationId = Pterodactyl::getFreeAllocationId($node);
+        $allocationId = $this->pterodactyl->getFreeAllocationId($node);
         if (! $allocationId) {
             return $this->noAllocationsError($server);
         }
 
         //create server on pterodactyl
-        $response = Pterodactyl::createServer($server, $egg, $allocationId);
+        $response = $this->pterodactyl->createServer($server, $egg, $allocationId);
         if ($response->failed()) {
             return $this->serverCreationFailed($response, $server);
         }
@@ -201,7 +218,7 @@ class ServerController extends Controller
             'identifier' => $serverAttributes['identifier'],
         ]);
 
-        if (config('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') {
+        if ($server_settings->charge_first_hour) {
             if ($request->user()->credits >= $server->product->getHourlyPrice()) {
                 $request->user()->decrement('credits', $server->product->getHourlyPrice());
             }
@@ -252,12 +269,12 @@ class ServerController extends Controller
     }
 
     /** Show Server Settings */
-    public function show(Server $server)
+    public function show(Server $server, ServerSettings $server_settings, GeneralSettings $general_settings)
     {
         if ($server->user_id != Auth::user()->id) {
-            return back()->with('error', __('´This is not your Server!'));
+            return back()->with('error', __('This is not your Server!'));
         }
-        $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id);
+        $serverAttributes = $this->pterodactyl->getServerAttributes($server->pterodactyl_id);
         $serverRelationships = $serverAttributes['relationships'];
         $serverLocationAttributes = $serverRelationships['location']['attributes'];
 
@@ -273,7 +290,7 @@ class ServerController extends Controller
         $server->name = $serverAttributes['name'];
         $server->egg = $serverRelationships['egg']['attributes']['name'];
 
-        $pteroNode = Pterodactyl::getNode($serverRelationships['node']['attributes']['id']);
+        $pteroNode = $this->pterodactyl->getNode($serverRelationships['node']['attributes']['id']);
 
         $products = Product::orderBy('created_at')
         ->whereHas('nodes', function (Builder $builder) use ($serverRelationships) { //Only show products for that node
@@ -292,6 +309,8 @@ class ServerController extends Controller
         return view('servers.settings')->with([
             'server' => $server,
             'products' => $products,
+            'server_enable_upgrade' => $server_settings->enable_upgrade,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 
@@ -306,7 +325,7 @@ class ServerController extends Controller
         $user = Auth::user();
         $oldProduct = Product::where('id', $server->product->id)->first();
         $newProduct = Product::where('id', $request->product_upgrade)->first();
-        $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id);
+        $serverAttributes = $this->pterodactyl->getServerAttributes($server->pterodactyl_id);
         $serverRelationships = $serverAttributes['relationships'];
 
         // Get node resource allocation info
@@ -317,7 +336,7 @@ class ServerController extends Controller
         // Check if node has enough memory and disk space
         $requireMemory = $newProduct->memory - $oldProduct->memory;
         $requiredisk = $newProduct->disk - $oldProduct->disk;
-        $checkResponse = Pterodactyl::checkNodeResources($node, $requireMemory, $requiredisk);
+        $checkResponse = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk);
         if ($checkResponse == false) {
             return redirect()->route('servers.index')->with('error', __("The node '".$nodeName."' doesn't have the required memory or disk left to upgrade the server."));
         }
@@ -331,14 +350,12 @@ class ServerController extends Controller
             $server->product_id = $request->product_upgrade;
             $server->update();
             $server->allocation = $serverAttributes['allocation'];
-            $response = Pterodactyl::updateServer($server, $newProduct);
-            if ($response->failed()) {
-                return $this->serverCreationFailed($response, $server);
-            }
+            $response = $this->pterodactyl->updateServer($server, $newProduct);
+            if ($response->failed()) return redirect()->route('servers.index')->with('error', __("The system was unable to update your server product. Please try again later or contact support."));
             //update user balance
             $user->decrement('credits', $priceupgrade);
             //restart the server
-            $response = Pterodactyl::powerAction($server, 'restart');
+            $response = $this->pterodactyl->powerAction($server, 'restart');
             if ($response->failed()) {
                 return redirect()->route('servers.index')->with('error', $response->json()['errors'][0]['detail']);
             }

+ 8 - 13
app/Http/Controllers/StoreController.php

@@ -3,36 +3,31 @@
 namespace App\Http\Controllers;
 
 use App\Models\ShopProduct;
+use App\Settings\GeneralSettings;
+use App\Settings\UserSettings;
 use Illuminate\Support\Facades\Auth;
 
 class StoreController extends Controller
 {
     /** Display a listing of the resource. */
-    public function index()
+    public function index(UserSettings $user_settings, GeneralSettings $general_settings)
     {
-        $isPaymentSetup = false;
-
-        if (
-            env('APP_ENV') == 'local' ||
-            config('SETTINGS::PAYMENTS:PAYPAL:SECRET') && config('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') ||
-            config('SETTINGS::PAYMENTS:STRIPE:SECRET') && config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') && config('SETTINGS::PAYMENTS:STRIPE:METHODS')
-        ) {
-            $isPaymentSetup = true;
-        }
+        $isStoreEnabled = $general_settings->store_enabled;
 
         //Required Verification for creating an server
-        if (config('SETTINGS::USER:FORCE_EMAIL_VERIFICATION', false) === 'true' && ! Auth::user()->hasVerifiedEmail()) {
+        if ($user_settings->force_email_verification && !Auth::user()->hasVerifiedEmail()) {
             return redirect()->route('profile.index')->with('error', __('You are required to verify your email address before you can purchase credits.'));
         }
 
         //Required Verification for creating an server
-        if (config('SETTINGS::USER:FORCE_DISCORD_VERIFICATION', false) === 'true' && ! Auth::user()->discordUser) {
+        if ($user_settings->force_discord_verification && !Auth::user()->discordUser) {
             return redirect()->route('profile.index')->with('error', __('You are required to link your discord account before you can purchase Credits'));
         }
 
         return view('store.index')->with([
             'products' => ShopProduct::where('disabled', '=', false)->orderBy('type', 'asc')->orderBy('price', 'asc')->get(),
-            'isPaymentSetup' => $isPaymentSetup,
+            'isStoreEnabled' => $isStoreEnabled,
+            'credits_display_name' => $general_settings->credits_display_name
         ]);
     }
 }

+ 36 - 25
app/Http/Controllers/TicketsController.php

@@ -11,6 +11,9 @@ use App\Models\User;
 use App\Notifications\Ticket\Admin\AdminCreateNotification;
 use App\Notifications\Ticket\Admin\AdminReplyNotification;
 use App\Notifications\Ticket\User\CreateNotification;
+use App\Settings\LocaleSettings;
+use App\Settings\PterodactylSettings;
+use App\Settings\TicketSettings;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Notification;
@@ -18,23 +21,28 @@ use Illuminate\Support\Str;
 
 class TicketsController extends Controller
 {
-    public function index()
+    public function index(LocaleSettings $locale_settings)
     {
-        $tickets = Ticket::where('user_id', Auth::user()->id)->paginate(10);
-        $ticketcategories = TicketCategory::all();
-
-        return view('ticket.index', compact('tickets', 'ticketcategories'));
+        return view('ticket.index', [
+            'tickets' => Ticket::where('user_id', Auth::user()->id)->paginate(10),
+            'ticketcategories' => TicketCategory::all(),
+            'locale_datatables' => $locale_settings->datatables
+        ]);
     }
 
-    public function store(Request $request)
+    public function store(Request $request, TicketSettings $ticket_settings)
     {
-        $this->validate($request, [
+        $this->validate(
+            $request,
+            [
                 'title' => 'required',
                 'ticketcategory' => 'required',
                 'priority' => 'required',
-                'message' => 'required',]
+                'message' => 'required',
+            ]
         );
-        $ticket = new Ticket([
+        $ticket = new Ticket(
+            [
                 'title' => $request->input('title'),
                 'user_id' => Auth::user()->id,
                 'ticket_id' => strtoupper(Str::random(8)),
@@ -42,28 +50,28 @@ class TicketsController extends Controller
                 'priority' => $request->input('priority'),
                 'message' => $request->input('message'),
                 'status' => 'Open',
-                'server' => $request->input('server'),]
+                'server' => $request->input('server'),
+            ]
         );
         $ticket->save();
         $user = Auth::user();
-        if (config('SETTINGS::TICKET:NOTIFY') == "all") {
-            $admin = User::where('role', 'admin')->orWhere('role', 'mod')->get();
-        }
-        if (config('SETTINGS::TICKET:NOTIFY') == "admin") {
-            $admin = User::where('role', 'admin')->get();
-        }
-        if (config('SETTINGS::TICKET:NOTIFY') == "moderator") {
-            $admin = User::where('role', 'mod')->get();
+        switch ($ticket_settings->notify) {
+            case 'all':
+                $admin = User::where('role', 'admin')->orWhere('role', 'mod')->get();
+                Notification::send($admin, new AdminCreateNotification($ticket, $user));
+            case 'admin':
+                $admin = User::where('role', 'admin')->get();
+                Notification::send($admin, new AdminCreateNotification($ticket, $user));
+            case 'moderator':
+                $admin = User::where('role', 'mod')->get();
+                Notification::send($admin, new AdminCreateNotification($ticket, $user));
         }
         $user->notify(new CreateNotification($ticket));
-        if (config('SETTINGS::TICKET:NOTIFY') != "none") {
-            Notification::send($admin, new AdminCreateNotification($ticket, $user));
-        }
 
         return redirect()->route('ticket.index')->with('success', __('A ticket has been opened, ID: #') . $ticket->ticket_id);
     }
 
-    public function show($ticket_id)
+    public function show($ticket_id, PterodactylSettings $ptero_settings)
     {
         try {
             $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail();
@@ -73,8 +81,9 @@ class TicketsController extends Controller
         $ticketcomments = $ticket->ticketcomments;
         $ticketcategory = $ticket->ticketcategory;
         $server = Server::where('id', $ticket->server)->first();
+        $pterodactyl_url = $ptero_settings->panel_url;
 
-        return view('ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server'));
+        return view('ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server', 'pterodactyl_url'));
     }
 
     public function reply(Request $request)
@@ -170,8 +179,10 @@ class TicketsController extends Controller
                 return __($tickets->priority);
             })
             ->editColumn('updated_at', function (Ticket $tickets) {
-                return ['display' => $tickets->updated_at ? $tickets->updated_at->diffForHumans() : '',
-                    'raw' => $tickets->updated_at ? strtotime($tickets->updated_at) : ''];
+                return [
+                    'display' => $tickets->updated_at ? $tickets->updated_at->diffForHumans() : '',
+                    'raw' => $tickets->updated_at ? strtotime($tickets->updated_at) : ''
+                ];
             })
             ->addColumn('actions', function (Ticket $tickets) {
                 $statusButtonColor = ($tickets->status == "Closed") ? 'btn-success' : 'btn-warning';

+ 0 - 2
app/Http/Middleware/GlobalNames.php

@@ -16,8 +16,6 @@ class GlobalNames
      */
     public function handle(Request $request, Closure $next)
     {
-        define('CREDITS_DISPLAY_NAME', config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME', 'Credits'));
-
         $unsupported_lang_array = explode(',', config('app.unsupported_locales'));
         $unsupported_lang_array = array_map('strtolower', $unsupported_lang_array);
         define('UNSUPPORTED_LANGS', $unsupported_lang_array);

+ 12 - 5
app/Http/Middleware/SetLocale.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Middleware;
 
+use App\Settings\LocaleSettings;
 use Closure;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\App;
@@ -9,6 +10,12 @@ use Illuminate\Support\Facades\Session;
 
 class SetLocale
 {
+    private $locale_settings;
+
+    public function __construct(LocaleSettings $locale_settings)
+    {
+        $this->locale_settings = $locale_settings;
+    }
     /**
      * Handle an incoming request.
      *
@@ -19,15 +26,15 @@ class SetLocale
     public function handle($request, Closure $next)
     {
         if (Session::has('locale')) {
-            $locale = Session::get('locale', config('SETTINGS::LOCALE:DEFAULT'));
+            $locale = Session::get('locale', $this->locale_settings->default);
         } else {
-            if (config('SETTINGS::LOCALE:DYNAMIC') !== 'true') {
-                $locale = config('SETTINGS::LOCALE:DEFAULT');
+            if (!$this->locale_settings->dynamic) {
+                $locale = $this->locale_settings->default;
             } else {
                 $locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2);
 
-                if (! in_array($locale, explode(',', config('SETTINGS::LOCALE:AVAILABLE')))) {
-                    $locale = config('SETTINGS::LOCALE:DEFAULT');
+                if (! in_array($locale, explode(',', $this->locale_settings->available))) {
+                    $locale = $this->locale_settings->default;
                 }
             }
         }

+ 17 - 3
app/Listeners/CreateInvoice.php

@@ -3,13 +3,27 @@
 namespace App\Listeners;
 
 use App\Events\PaymentEvent;
+use App\Settings\InvoiceSettings;
 use App\Traits\Invoiceable;
 
 class CreateInvoice
 {
-
     use Invoiceable;
 
+    private $invoice_enabled;
+    private $invoice_settings;
+
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct(InvoiceSettings $invoice_settings)
+    {
+        $this->invoice_enabled = $invoice_settings->enabled;
+        $this->invoice_settings = $invoice_settings;
+    }
+
     /**
      * Handle the event.
      *
@@ -18,9 +32,9 @@ class CreateInvoice
      */
     public function handle(PaymentEvent $event)
     {
-        if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
+        if ($this->invoice_enabled) {
             // create invoice using the trait
-            $this->createInvoice($event->payment, $event->shopProduct);
+            $this->createInvoice($event->payment, $event->shopProduct, $this->invoice_settings);
         }
     }
 }

+ 14 - 1
app/Listeners/UnsuspendServers.php

@@ -4,11 +4,24 @@ namespace App\Listeners;
 
 use App\Events\UserUpdateCreditsEvent;
 use App\Models\Server;
+use App\Settings\UserSettings;
 use Exception;
 use Illuminate\Contracts\Queue\ShouldQueue;
 
 class UnsuspendServers implements ShouldQueue
 {
+    private $min_credits_to_make_server;
+
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct(UserSettings $user_settings)
+    {
+        $this->min_credits_to_make_server = $user_settings->min_credits_to_make_server;
+    }
+
     /**
      * Handle the event.
      *
@@ -19,7 +32,7 @@ class UnsuspendServers implements ShouldQueue
      */
     public function handle(UserUpdateCreditsEvent $event)
     {
-        if ($event->user->credits > config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)) {
+        if ($event->user->credits > $this->min_credits_to_make_server) {
             /** @var Server $server */
             foreach ($event->user->servers as $server) {
                 if ($server->isSuspended()) {

+ 35 - 10
app/Listeners/UserPayment.php

@@ -6,11 +6,36 @@ use App\Events\PaymentEvent;
 use App\Models\User;
 use Illuminate\Support\Facades\DB;
 use App\Models\PartnerDiscount;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Queue\InteractsWithQueue;
+use App\Settings\GeneralSettings;
+use App\Settings\ReferralSettings;
+use App\Settings\UserSettings;
 
 class UserPayment
 {
+    private $server_limit_after_irl_purchase;
+
+    private $referral_mode;
+
+    private $referral_percentage;
+
+    private $referral_always_give_commission;
+
+    private $credits_display_name;
+
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct(UserSettings $user_settings, ReferralSettings $referral_settings, GeneralSettings $general_settings)
+    {
+        $this->server_limit_after_irl_purchase = $user_settings->server_limit_after_irl_purchase;
+        $this->referral_mode = $referral_settings->mode;
+        $this->referral_percentage = $referral_settings->percentage;
+        $this->referral_always_give_commission = $referral_settings->always_give_commission;
+        $this->credits_display_name = $general_settings->credits_display_name;
+    }
+    
     /**
      * Handle the event.
      *
@@ -28,8 +53,8 @@ class UserPayment
         }
 
         //update server limit
-        if (config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0 && $user->server_limit < config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
-            $user->update(['server_limit' => config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
+        if ($this->server_limit_after_irl_purchase !== 0 && $user->server_limit < $this->server_limit_after_irl_purchase) {
+            $user->update(['server_limit' => $this->server_limit_after_irl_purchase]);
         }
 
         //update User with bought item
@@ -40,17 +65,17 @@ class UserPayment
         }
 
         //give referral commission always
-        if ((config("SETTINGS::REFERRAL:MODE") == "commission" || config("SETTINGS::REFERRAL:MODE") == "both") && $shopProduct->type == "Credits" && config("SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION") == "true") {
+        if (($this->referral_mode === "commission" || $this->referral_mode === "both") && $shopProduct->type == "Credits" && $this->referral_always_give_commission) {
             if ($ref_user = DB::table("user_referrals")->where('registered_user_id', '=', $user->id)->first()) {
                 $ref_user = User::findOrFail($ref_user->referral_id);
-                $increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, "", "");
+                $increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id, $this->referral_percentage)) / 100, 0, "", "");
                 $ref_user->increment('credits', $increment);
 
                 //LOGS REFERRALS IN THE ACTIVITY LOG
                 activity()
                     ->performedOn($user)
                     ->causedBy($ref_user)
-                    ->log('gained ' . $increment . ' ' . config("SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME") . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
+                    ->log('gained ' . $increment . ' ' . $this->credits_display_name . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
             }
         }
         //update role give Referral-reward
@@ -58,17 +83,17 @@ class UserPayment
             $user->update(['role' => 'client']);
 
             //give referral commission only on first purchase
-            if ((config("SETTINGS::REFERRAL:MODE") == "commission" || config("SETTINGS::REFERRAL:MODE") == "both") && $shopProduct->type == "Credits" && config("SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION") == "false") {
+            if (($this->referral_mode === "commission" || $this->referral_mode === "both") && $shopProduct->type == "Credits" && !$this->referral_always_give_commission) {
                 if ($ref_user = DB::table("user_referrals")->where('registered_user_id', '=', $user->id)->first()) {
                     $ref_user = User::findOrFail($ref_user->referral_id);
-                    $increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, "", "");
+                    $increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id, $this->referral_percentage)) / 100, 0, "", "");
                     $ref_user->increment('credits', $increment);
 
                     //LOGS REFERRALS IN THE ACTIVITY LOG
                     activity()
                         ->performedOn($user)
                         ->causedBy($ref_user)
-                        ->log('gained ' . $increment . ' ' . config("SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME") . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
+                        ->log('gained ' . $increment . ' ' . $this->credits_display_name . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
                 }
             }
         }

+ 11 - 4
app/Listeners/Verified.php

@@ -2,16 +2,23 @@
 
 namespace App\Listeners;
 
+use App\Settings\UserSettings;
+
 class Verified
 {
+    private $server_limit_after_verify_email;
+
+    private $credits_reward_after_verify_email;
+
     /**
      * Create the event listener.
      *
      * @return void
      */
-    public function __construct()
+    public function __construct(UserSettings $user_settings)
     {
-        //
+        $this->server_limit_after_verify_email = $user_settings->server_limit_after_verify_email;
+        $this->credits_reward_after_verify_email = $user_settings->credits_reward_after_verify_email;
     }
 
     /**
@@ -23,8 +30,8 @@ class Verified
     public function handle($event)
     {
         if (! $event->user->email_verified_reward) {
-            $event->user->increment('server_limit', config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL'));
-            $event->user->increment('credits', config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL'));
+            $event->user->increment('server_limit', $this->server_limit_after_verify_email);
+            $event->user->increment('credits', $this->credits_reward_after_verify_email);
         }
     }
 }

+ 2 - 2
app/Models/PartnerDiscount.php

@@ -33,7 +33,7 @@ class PartnerDiscount extends Model
         return 0;
     }
 
-    public static function getCommission($user_id)
+    public static function getCommission($user_id, $percentage)
     {
         if ($partnerDiscount = PartnerDiscount::where('user_id', $user_id)->first()) {
             if ($partnerDiscount->referral_system_commission >= 0) {
@@ -41,6 +41,6 @@ class PartnerDiscount extends Model
             }
         }
 
-        return config('SETTINGS::REFERRAL:PERCENTAGE');
+        return $percentage;
     }
 }

+ 2 - 0
app/Models/Product.php

@@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Spatie\Activitylog\LogOptions;
 use Spatie\Activitylog\Traits\LogsActivity;
+use App\Models\Pterodactyl\Egg;
+use App\Models\Pterodactyl\Node;
 
 class Product extends Model
 {

+ 7 - 5
app/Models/Egg.php → app/Models/Pterodactyl/Egg.php

@@ -1,12 +1,14 @@
 <?php
 
-namespace App\Models;
+namespace App\Models\Pterodactyl;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use App\Models\Pterodactyl\Nest;
+use App\Models\Product;
 
 class Egg extends Model
 {
@@ -37,9 +39,9 @@ class Egg extends Model
     public static function syncEggs()
     {
         Nest::syncNests();
-
-        Nest::all()->each(function (Nest $nest) {
-            $eggs = Pterodactyl::getEggs($nest);
+        $client = app(PterodactylClient::class);
+        Nest::all()->each(function (Nest $nest) use ($client) {
+            $eggs = $client->getEggs($nest);
 
             foreach ($eggs as $egg) {
                 $array = [];

+ 4 - 3
app/Models/Location.php → app/Models/Pterodactyl/Location.php

@@ -1,8 +1,8 @@
 <?php
 
-namespace App\Models;
+namespace App\Models\Pterodactyl;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
 use Exception;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
@@ -33,7 +33,8 @@ class Location extends Model
      */
     public static function syncLocations()
     {
-        $locations = Pterodactyl::getLocations();
+        $client = app(PterodactylClient::class);
+        $locations = $client->getLocations();
 
         //map response
         $locations = array_map(function ($val) {

+ 4 - 3
app/Models/Nest.php → app/Models/Pterodactyl/Nest.php

@@ -1,8 +1,8 @@
 <?php
 
-namespace App\Models;
+namespace App\Models\Pterodactyl;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
@@ -32,7 +32,8 @@ class Nest extends Model
 
     public static function syncNests()
     {
-        $nests = Pterodactyl::getNests();
+        $client = app(PterodactylClient::class);
+        $nests = $client->getNests();
 
         //map response
         $nests = array_map(function ($nest) {

+ 5 - 3
app/Models/Node.php → app/Models/Pterodactyl/Node.php

@@ -1,13 +1,14 @@
 <?php
 
-namespace App\Models;
+namespace App\Models\Pterodactyl;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
 use Exception;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use App\Models\Product;
 
 class Node extends Model
 {
@@ -32,7 +33,8 @@ class Node extends Model
     public static function syncNodes()
     {
         Location::syncLocations();
-        $nodes = Pterodactyl::getNodes();
+        $client = app(PterodactylClient::class);
+        $nodes = $client->getNodes();
 
         //map response
         $nodes = array_map(function ($node) {

+ 21 - 10
app/Models/Server.php

@@ -2,7 +2,8 @@
 
 namespace App\Models;
 
-use App\Classes\Pterodactyl;
+use App\Classes\PterodactylClient;
+use App\Settings\PterodactylSettings;
 use Exception;
 use GuzzleHttp\Promise\PromiseInterface;
 use Hidehalo\Nanoid\Client;
@@ -21,13 +22,17 @@ class Server extends Model
 {
     use HasFactory;
     use LogsActivity;
+
+    private PterodactylClient $pterodactyl;
+
     public function getActivitylogOptions(): LogOptions
     {
         return LogOptions::defaults()
-            -> logOnlyDirty()
-            -> logOnly(['*'])
-            -> dontSubmitEmptyLogs();
+            ->logOnlyDirty()
+            ->logOnly(['*'])
+            ->dontSubmitEmptyLogs();
     }
+
     /**
      * @var bool
      */
@@ -62,6 +67,12 @@ class Server extends Model
         'suspended' => 'datetime',
     ];
 
+    public function __construct()
+    {
+        $ptero_settings = new PterodactylSettings();
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     public static function boot()
     {
         parent::boot();
@@ -73,8 +84,8 @@ class Server extends Model
         });
 
         static::deleting(function (Server $server) {
-            $response = Pterodactyl::client()->delete("/application/servers/{$server->pterodactyl_id}");
-            if ($response->failed() && ! is_null($server->pterodactyl_id)) {
+            $response = $server->pterodactyl->application->delete("/application/servers/{$server->pterodactyl_id}");
+            if ($response->failed() && !is_null($server->pterodactyl_id)) {
                 //only return error when it's not a 404 error
                 if ($response['errors'][0]['status'] != '404') {
                     throw new Exception($response['errors'][0]['code']);
@@ -88,7 +99,7 @@ class Server extends Model
      */
     public function isSuspended()
     {
-        return ! is_null($this->suspended);
+        return !is_null($this->suspended);
     }
 
     /**
@@ -96,7 +107,7 @@ class Server extends Model
      */
     public function getPterodactylServer()
     {
-        return Pterodactyl::client()->get("/application/servers/{$this->pterodactyl_id}");
+        return $this->pterodactyl->application->get("/application/servers/{$this->pterodactyl_id}");
     }
 
     /**
@@ -104,7 +115,7 @@ class Server extends Model
      */
     public function suspend()
     {
-        $response = Pterodactyl::suspendServer($this);
+        $response = $this->pterodactyl->suspendServer($this);
 
         if ($response->successful()) {
             $this->update([
@@ -120,7 +131,7 @@ class Server extends Model
      */
     public function unSuspend()
     {
-        $response = Pterodactyl::unSuspendServer($this);
+        $response = $this->pterodactyl->unSuspendServer($this);
 
         if ($response->successful()) {
             $this->update([

+ 0 - 40
app/Models/Settings.php

@@ -4,48 +4,8 @@ namespace App\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
-use Illuminate\Support\Facades\Cache;
 
 class Settings extends Model
 {
     use HasFactory;
-
-    protected $table = 'settings';
-
-    public const CACHE_TAG = 'setting';
-
-    public $primaryKey = 'key';
-
-    public $incrementing = false;
-
-    protected $keyType = 'string';
-
-    protected $fillable = [
-        'key',
-        'value',
-        'type',
-    ];
-
-    public static function boot()
-    {
-        parent::boot();
-
-        static::updated(function (Settings $settings) {
-            Cache::forget(self::CACHE_TAG.':'.$settings->key);
-        });
-    }
-
-    /**
-     * @param  string  $key
-     * @param $default
-     * @return mixed
-     */
-    public static function getValueByKey(string $key, $default = null)
-    {
-        return Cache::rememberForever(self::CACHE_TAG.':'.$key, function () use ($default, $key) {
-            $settings = self::find($key);
-
-            return $settings ? $settings->value : $default;
-        });
-    }
 }

+ 15 - 24
app/Models/User.php

@@ -2,9 +2,12 @@
 
 namespace App\Models;
 
-use App\Classes\Pterodactyl;
 use App\Notifications\Auth\QueuedVerifyEmail;
 use App\Notifications\WelcomeMessage;
+use App\Settings\GeneralSettings;
+use App\Settings\UserSettings;
+use App\Classes\PterodactylClient;
+use App\Settings\PterodactylSettings;
 use Illuminate\Contracts\Auth\MustVerifyEmail;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -23,6 +26,8 @@ class User extends Authenticatable implements MustVerifyEmail
 {
     use HasFactory, Notifiable, LogsActivity, CausesActivity;
 
+    private PterodactylClient $pterodactyl;
+
     /**
      * @var string[]
      */
@@ -85,12 +90,18 @@ class User extends Authenticatable implements MustVerifyEmail
         'server_limit' => 'float',
     ];
 
+    public function __construct()
+    {
+        $ptero_settings = new PterodactylSettings();
+        $this->pterodactyl = new PterodactylClient($ptero_settings);
+    }
+
     public static function boot()
     {
         parent::boot();
 
-        static::created(function (User $user) {
-            $user->notify(new WelcomeMessage($user));
+        static::created(function (User $user, GeneralSettings $general_settings, UserSettings $user_settings) {
+            $user->notify(new WelcomeMessage($user, $general_settings, $user_settings));
         });
 
         static::deleting(function (User $user) {
@@ -111,7 +122,7 @@ class User extends Authenticatable implements MustVerifyEmail
 
             $user->discordUser()->delete();
 
-            Pterodactyl::client()->delete("/application/users/{$user->pterodactyl_id}");
+            $user->pterodactyl->application->delete("/application/users/{$user->pterodactyl_id}");
         });
     }
 
@@ -184,9 +195,6 @@ class User extends Authenticatable implements MustVerifyEmail
         return $this->suspended;
     }
 
-    /**
-     * @throws Exception
-     */
     public function suspend()
     {
         foreach ($this->servers as $server) {
@@ -200,9 +208,6 @@ class User extends Authenticatable implements MustVerifyEmail
         return $this;
     }
 
-    /**
-     * @throws Exception
-     */
     public function unSuspend()
     {
         foreach ($this->getServersWithProduct() as $server) {
@@ -230,23 +235,9 @@ class User extends Authenticatable implements MustVerifyEmail
      */
     public function getAvatar()
     {
-        //TODO loading the images to confirm they exist is causing to much load time. alternative has to be found :) maybe onerror tag on the <img tags>
-        //        if ($this->discordUser()->exists()) {
-        //            if(@getimagesize($this->discordUser->getAvatar())) {
-        //                $avatar = $this->discordUser->getAvatar();
-        //            } else {
-        //                $avatar = "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
-        //            }
-        //        } else {
-        //            $avatar = "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
-        //        }
-
         return 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($this->email)));
     }
 
-    /**
-     * @return string
-     */
     public function creditUsage()
     {
         $usage = 0;

+ 7 - 3
app/Notifications/ReferralNotification.php

@@ -3,6 +3,8 @@
 namespace App\Notifications;
 
 use App\Models\User;
+use App\Settings\GeneralSettings;
+use App\Settings\ReferralSettings;
 use Illuminate\Bus\Queueable;
 use Illuminate\Notifications\Notification;
 
@@ -15,6 +17,8 @@ class ReferralNotification extends Notification
      */
     private $user;
 
+    private $ref_user;
+
     /**
      * Create a new notification instance.
      *
@@ -43,13 +47,13 @@ class ReferralNotification extends Notification
      * @param  mixed  $notifiable
      * @return array
      */
-    public function toArray($notifiable)
+    public function toArray($notifiable, GeneralSettings $general_settings, ReferralSettings $referral_settings)
     {
         return [
             'title' => __('Someone registered using your Code!'),
             'content' => '
-                <p>You received '.config('SETTINGS::REFERRAL::REWARD').' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').'</p>
-                <p>because '.$this->ref_user->name.' registered with your Referral-Code!</p>
+                <p>You received '. $referral_settings->reward . ' ' . $general_settings->credits_display_name . '</p>
+                <p>because ' . $this->ref_user->name . ' registered with your Referral-Code!</p>
                 <p>Thank you very much for supporting us!.</p>
                 <p>'.config('app.name', 'Laravel').'</p>
             ',

+ 26 - 9
app/Notifications/WelcomeMessage.php

@@ -3,6 +3,8 @@
 namespace App\Notifications;
 
 use App\Models\User;
+use App\Settings\GeneralSettings;
+use App\Settings\UserSettings;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Notification;
@@ -16,14 +18,29 @@ class WelcomeMessage extends Notification implements ShouldQueue
      */
     private $user;
 
+    private $credits_display_name;
+
+    private $credits_reward_after_verify_discord;
+
+    private $credits_reward_after_verify_email;
+
+    private $server_limit_after_verify_discord;
+
+    private $server_limit_after_verify_email;
+
     /**
      * Create a new notification instance.
      *
      * @param  User  $user
      */
-    public function __construct(User $user)
+    public function __construct(User $user, GeneralSettings $general_settings, UserSettings $user_settings)
     {
         $this->user = $user;
+        $this->credits_display_name = $general_settings->credits_display_name;
+        $this->credits_reward_after_verify_discord = $user_settings->credits_reward_after_verify_discord;
+        $this->credits_reward_after_verify_email = $user_settings->credits_reward_after_verify_email;
+        $this->server_limit_after_verify_discord = $user_settings->server_limit_after_verify_discord;
+        $this->server_limit_after_verify_email = $user_settings->server_limit_after_verify_email;
     }
 
     /**
@@ -40,18 +57,18 @@ class WelcomeMessage extends Notification implements ShouldQueue
     public function AdditionalLines()
     {
         $AdditionalLine = '';
-        if (config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL') != 0) {
-            $AdditionalLine .= __('Verifying your e-mail address will grant you ').config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL').' '.__('additional').' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').'. <br />';
+        if ($this->credits_reward_after_verify_email != 0) {
+            $AdditionalLine .= __('Verifying your e-mail address will grant you ').$this->credits_reward_after_verify_email.' '.__('additional').' '.$this->credits_display_name.'. <br />';
         }
-        if (config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') != 0) {
-            $AdditionalLine .= __('Verifying your e-mail will also increase your Server Limit by ').config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL').'. <br />';
+        if ($this->server_limit_after_verify_email != 0) {
+            $AdditionalLine .= __('Verifying your e-mail will also increase your Server Limit by ').$this->server_limit_after_verify_email.'. <br />';
         }
         $AdditionalLine .= '<br />';
-        if (config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD') != 0) {
-            $AdditionalLine .= __('You can also verify your discord account to get another ').config('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD').' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').'. <br />';
+        if ($this->credits_reward_after_verify_discord != 0) {
+            $AdditionalLine .= __('You can also verify your discord account to get another ').$this->credits_reward_after_verify_discord.' '.$this->credits_display_name.'. <br />';
         }
-        if (config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') != 0) {
-            $AdditionalLine .= __('Verifying your Discord account will also increase your Server Limit by ').config('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD').'. <br />';
+        if ($this->server_limit_after_verify_discord != 0) {
+            $AdditionalLine .= __('Verifying your Discord account will also increase your Server Limit by ').$this->server_limit_after_verify_discord.'. <br />';
         }
 
         return $AdditionalLine;

+ 14 - 89
app/Providers/AppServiceProvider.php

@@ -2,16 +2,17 @@
 
 namespace App\Providers;
 
-use App\Models\Settings;
+use App\Extensions\PaymentGateways\PayPal\PayPalSettings;
 use App\Models\UsefulLink;
+use App\Settings\MailSettings;
 use Exception;
 use Illuminate\Pagination\Paginator;
-use Illuminate\Support\Facades\Artisan;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\ServiceProvider;
-use Qirolab\Theme\Theme;
+
 
 class AppServiceProvider extends ServiceProvider
 {
@@ -54,6 +55,14 @@ class AppServiceProvider extends ServiceProvider
             return $ok;
         });
 
+        // Force HTTPS if APP_URL is set to https
+        if (config('app.url') && parse_url(config('app.url'), PHP_URL_SCHEME) === 'https') {
+            URL::forceScheme('https');
+        }
+
+        // Do not run this code if no APP_KEY is set
+        if (config('app.key') == null) return;
+
         try {
             if (Schema::hasColumn('useful_links', 'position')) {
                 $useful_links = UsefulLink::where("position", "like", "%topbar%")->get()->sortby("id");
@@ -63,92 +72,8 @@ class AppServiceProvider extends ServiceProvider
             Log::error("Couldnt find useful_links. Probably the installation is not completet. " . $e);
         }
 
-        //only run if the installer has been executed
-        try {
-            $settings = Settings::all();
-            // Set all configs from database
-            foreach ($settings as $setting) {
-                config([$setting->key => $setting->value]);
-            }
-
-            if (!file_exists(base_path('themes') . "/" . config("SETTINGS::SYSTEM:THEME"))) {
-                config(['SETTINGS::SYSTEM:THEME' => "default"]);
-            }
-
-            if (config('SETTINGS::SYSTEM:THEME') && config('SETTINGS::SYSTEM:THEME') !== config('theme.active')) {
-                Theme::set(config("SETTINGS::SYSTEM:THEME", "default"), "default");
-            } else {
-                Theme::set("default", "default");
-            }
-
-            // Set Mail Config
-            //only update config if mail settings have changed in DB
-            if (
-                config('mail.default') != config('SETTINGS:MAIL:MAILER') ||
-                config('mail.mailers.smtp.host') != config('SETTINGS:MAIL:HOST') ||
-                config('mail.mailers.smtp.port') != config('SETTINGS:MAIL:PORT') ||
-                config('mail.mailers.smtp.username') != config('SETTINGS:MAIL:USERNAME') ||
-                config('mail.mailers.smtp.password') != config('SETTINGS:MAIL:PASSWORD') ||
-                config('mail.mailers.smtp.encryption') != config('SETTINGS:MAIL:ENCRYPTION') ||
-                config('mail.from.address') != config('SETTINGS:MAIL:FROM_ADDRESS') ||
-                config('mail.from.name') != config('SETTINGS:MAIL:FROM_NAME')
-            ) {
-                config(['mail.default' => config('SETTINGS::MAIL:MAILER')]);
-                config(['mail.mailers.smtp' => [
-                    'transport' => 'smtp',
-                    'host' => config('SETTINGS::MAIL:HOST'),
-                    'port' => config('SETTINGS::MAIL:PORT'),
-                    'encryption' => config('SETTINGS::MAIL:ENCRYPTION'),
-                    'username' => config('SETTINGS::MAIL:USERNAME'),
-                    'password' => config('SETTINGS::MAIL:PASSWORD'),
-                    'timeout' => null,
-                    'auth_mode' => null,
-                ]]);
-                config(['mail.from' => ['address' => config('SETTINGS::MAIL:FROM_ADDRESS'), 'name' => config('SETTINGS::MAIL:FROM_NAME')]]);
-
-                Artisan::call('queue:restart');
-            }
 
-            // Set Recaptcha API Config
-            // Load recaptcha package if recaptcha is enabled
-            if (config('SETTINGS::RECAPTCHA:ENABLED') == 'true') {
-                $this->app->register(\Biscolab\ReCaptcha\ReCaptchaServiceProvider::class);
-            }
-
-            //only update config if recaptcha settings have changed in DB
-            if (
-                config('recaptcha.api_site_key') != config('SETTINGS::RECAPTCHA:SITE_KEY') ||
-                config('recaptcha.api_secret_key') != config('SETTINGS::RECAPTCHA:SECRET_KEY')
-            ) {
-                config(['recaptcha.api_site_key' => config('SETTINGS::RECAPTCHA:SITE_KEY')]);
-                config(['recaptcha.api_secret_key' => config('SETTINGS::RECAPTCHA:SECRET_KEY')]);
-
-                Artisan::call('config:clear');
-                Artisan::call('cache:clear');
-            }
-
-            try {
-                $stringfromfile = file(base_path() . '/.git/HEAD');
-
-                $firstLine = $stringfromfile[0]; //get the string from the array
-
-                $explodedstring = explode('/', $firstLine, 3); //seperate out by the "/" in the string
-
-                $branchname = $explodedstring[2]; //get the one that is always the branch name
-            } catch (Exception $e) {
-                $branchname = 'unknown';
-                Log::notice($e);
-            }
-            config(['BRANCHNAME' => $branchname]);
-
-            // Set Discord-API Config
-            config(['services.discord.client_id' => config('SETTINGS::DISCORD:CLIENT_ID')]);
-            config(['services.discord.client_secret' => config('SETTINGS::DISCORD:CLIENT_SECRET')]);
-        } catch (Exception $e) {
-            error_log('Settings Error: Could not load settings from database. The Installation probably is not done yet.');
-            error_log($e);
-            Log::error('Settings Error: Could not load settings from database. The Installation probably is not done yet.');
-            Log::error($e);
-        }
+        $settings = $this->app->make(MailSettings::class);
+        $settings->setConfig();
     }
 }

+ 87 - 0
app/Settings/DiscordSettings.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class DiscordSettings extends Settings
+{
+    public ?string $bot_token;
+    public ?string $client_id;
+    public ?string $client_secret;
+    public ?string $guild_id;
+    public ?string $invite_url;
+    public ?string $role_id;
+
+    public static function group(): string
+    {
+        return 'discord';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            'bot_token',
+            'client_id',
+            'client_secret'
+        ];
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'bot_token' => 'nullable|string',
+            'client_id' => 'nullable|string',
+            'client_secret' => 'nullable|string',
+            'guild_id' => 'nullable|string',
+            'invite_url' => 'nullable|string|url',
+            'role_id' => 'nullable|string',
+        ];
+    }
+
+    /**
+     * Summary of optionInputData array
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-user-friends',
+            'bot_token' => [
+                'label' => 'Bot Token',
+                'type' => 'string',
+                'description' => 'The bot token for your Discord bot.',
+            ],
+            'client_id' => [
+                'label' => 'Client ID',
+                'type' => 'string',
+                'description' => 'The client ID for your Discord bot.',
+            ],
+            'client_secret' => [
+                'label' => 'Client Secret',
+                'type' => 'string',
+                'description' => 'The client secret for your Discord bot.',
+            ],
+            'guild_id' => [
+                'label' => 'Guild ID',
+                'type' => 'string',
+                'description' => 'The guild ID for your Discord server.',
+            ],
+            'invite_url' => [
+                'label' => 'Invite URL',
+                'type' => 'string',
+                'description' => 'The invite URL for your Discord server.',
+            ],
+            'role_id' => [
+                'label' => 'Role ID',
+                'type' => 'string',
+                'description' => 'The role ID for your Discord server.',
+            ],
+        ];
+    }
+}

+ 138 - 0
app/Settings/GeneralSettings.php

@@ -0,0 +1,138 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class GeneralSettings extends Settings
+{
+    public bool $store_enabled;
+    public string $credits_display_name;
+    public bool $recaptcha_enabled;
+    public string $recaptcha_site_key;
+    public string $recaptcha_secret_key;
+    public string $phpmyadmin_url;
+    public bool $alert_enabled;
+    public string $alert_type;
+    public string $alert_message;
+    public string $theme;
+
+    //public int $initial_user_role; wait for Roles & Permissions PR.
+
+    public static function group(): string
+    {
+        return 'general';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            'recaptcha_site_key',
+            'recaptcha_secret_key'
+        ];
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'store_enabled' => 'boolean',
+            'credits_display_name' => 'required|string',
+            'recaptcha_enabled' => 'nullable|boolean',
+            'recaptcha_site_key' => 'nullable|string',
+            'recaptcha_secret_key' => 'nullable|string',
+            'phpmyadmin_url' => 'nullable|string',
+            'alert_enabled' => 'nullable|boolean',
+            'alert_type' => 'required|in:primary,secondary,success,danger,warning,info',
+            'alert_message' => 'required|string',
+            'theme' => 'required|in:default,BlueInfinity' // TODO: themes should be made/loaded dynamically
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => "fas fa-cog",
+            'store_enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enable Store',
+                'description' => 'Enable the store for users to purchase credits.'
+            ],
+            'credits_display_name' => [
+                'type' => 'string',
+                'label' => 'Credits Display Name',
+                'description' => 'The name of the currency used.'
+            ],
+            'initial_user_credits' => [
+                'type' => 'number',
+                'label' => 'Initial User Credits',
+                'description' => 'The amount of credits a user gets when they register.'
+            ],
+            'initial_server_limit' => [
+                'type' => 'number',
+                'label' => 'Initial Server Limit',
+                'description' => 'The amount of servers a user can create when they register.'
+            ],
+            'recaptcha_enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enable reCAPTCHA',
+                'description' => 'Enable reCAPTCHA on the login page.'
+            ],
+            'recaptcha_site_key' => [
+                'type' => 'string',
+                'label' => 'reCAPTCHA Site Key',
+                'description' => 'The site key for reCAPTCHA.'
+            ],
+            'recaptcha_secret_key' => [
+                'type' => 'string',
+                'label' => 'reCAPTCHA Secret Key',
+                'description' => 'The secret key for reCAPTCHA.'
+            ],
+            'phpmyadmin_url' => [
+                'type' => 'string',
+                'label' => 'phpMyAdmin URL',
+                'description' => 'The URL of your phpMyAdmin installation.'
+            ],
+            'alert_enabled' => [
+                'type' => 'boolean',
+                'label' => 'Enable Alert',
+                'description' => 'Enable an alert to be displayed on the home page.'
+            ],
+            'alert_type' => [
+                'type' => 'select',
+                'label' => 'Alert Type',
+                'options' => [
+                    'primary' => 'Blue',
+                    'secondary' => 'Grey',
+                    'success' => 'Green',
+                    'danger' => 'Red',
+                    'warning' => 'Orange',
+                    'info' => 'Cyan',
+                ],
+                'description' => 'The type of alert to display.'
+            ],
+            'alert_message' => [
+                'type' => 'string',
+                'label' => 'Alert Message',
+                'description' => 'The message to display in the alert.'
+            ],
+            'theme' => [
+                'type' => 'select',
+                'label' => 'Theme',
+                'options' => [
+                    'default' => 'Default',
+                    'BlueInfinity' => 'Blue Infinity',
+                ], // TODO: themes should be made/loaded dynamically
+                'description' => 'The theme to use for the site.'
+            ],
+        ];
+    }
+}

+ 92 - 0
app/Settings/InvoiceSettings.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class InvoiceSettings extends Settings
+{
+    public ?string $company_address;
+    public ?string $company_mail;
+    public ?string $company_name;
+    public ?string $company_phone;
+    public ?string $company_vat;
+    public ?string $company_website;
+    public bool $enabled;
+    public ?string $prefix;
+
+    public static function group(): string
+    {
+        return 'invoice';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'company_address' => 'nullable|string',
+            'company_mail' => 'nullable|string',
+            'company_name' => 'nullable|string',
+            'company_phone' => 'nullable|string',
+            'company_vat' => 'nullable|string',
+            'company_website' => 'nullable|string',
+            'enabled' => 'nullable|boolean',
+            'prefix' => 'nullable|string',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-file-invoice-dollar',
+            'company_address' => [
+                'label' => 'Company Address',
+                'type' => 'string',
+                'description' => 'The address of your company.',
+            ],
+            'company_mail' => [
+                'label' => 'Company Mail',
+                'type' => 'string',
+                'description' => 'The mail of your company.',
+            ],
+            'company_name' => [
+                'label' => 'Company Name',
+                'type' => 'string',
+                'description' => 'The name of your company.',
+            ],
+            'company_phone' => [
+                'label' => 'Company Phone',
+                'type' => 'string',
+                'description' => 'The phone of your company.',
+            ],
+            'company_vat' => [
+                'label' => 'Company VAT ID',
+                'type' => 'string',
+                'description' => 'The VAT ID of your company.',
+            ],
+            'company_website' => [
+                'label' => 'Company Website',
+                'type' => 'string',
+                'description' => 'The website of your company.',
+            ],
+            'enabled' => [
+                'label' => 'Enabled',
+                'type' => 'boolean',
+                'description' => 'Enable or disable invoices.',
+            ],
+            'prefix' => [
+                'label' => 'Prefix',
+                'type' => 'string',
+                'description' => 'The prefix of your invoices.',
+            ],
+        ];
+    }
+}

+ 73 - 0
app/Settings/LocaleSettings.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class LocaleSettings extends Settings
+{
+    public ?string $available;
+    public bool $clients_can_change;
+    public ?string $datatables;
+    public string $default;
+    public bool $dynamic;
+
+    public static function group(): string
+    {
+        return 'locale';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'available' => 'nullable|array',
+            'clients_can_change' => 'nullable|boolean',
+            'datatables' => 'nullable|string',
+            'default' => 'required|in:' . implode(',', config('app.available_locales')),
+            'dynamic' => 'nullable|boolean',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-globe',
+            'available' => [
+                'label' => 'Available Locales',
+                'type' => 'multiselect',
+                'description' => 'The locales that are available for the user to choose from.',
+                'options' => config('app.available_locales'),
+            ],
+            'clients_can_change' => [
+                'label' => 'Clients Can Change',
+                'type' => 'boolean',
+                'description' => 'Whether clients can change their locale.',
+            ],
+            'datatables' => [
+                'label' => 'Datatables Locale',
+                'type' => 'string',
+                'description' => 'The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: <a href="https://datatables.net/plug-ins/i18n/">https://datatables.net/plug-ins/i18n/</a>',
+            ],
+            'default' => [
+                'label' => 'Default Locale',
+                'type' => 'select',
+                'description' => 'The default locale to use.',
+                'options' => config('app.available_locales'),
+            ],
+            'dynamic' => [
+                'label' => 'Dynamic Locale',
+                'type' => 'boolean',
+                'description' => 'Whether to use the dynamic locale.',
+            ],
+        ];
+    }
+}

+ 119 - 0
app/Settings/MailSettings.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class MailSettings extends Settings
+{
+    public ?string $mail_host;
+    public ?int $mail_port;
+    public ?string $mail_username;
+    public ?string $mail_password;
+    public ?string $mail_encryption;
+    public ?string $mail_from_address;
+    public ?string $mail_from_name;
+    public ?string $mail_mailer;
+    public bool $mail_enabled;
+
+    public static function group(): string
+    {
+        return 'mail';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            'mail_password'
+        ];
+    }
+
+    public function setConfig()
+    {
+        try {
+            config()->set('mail.mailers.smtp.host', $this->mail_host);
+            config()->set('mail.mailers.smtp.port', $this->mail_port);
+            config()->set('mail.mailers.smtp.encryption', $this->mail_encryption);
+            config()->set('mail.mailers.smtp.username', $this->mail_username);
+            config()->set('mail.mailers.smtp.password', $this->mail_password);
+            config()->set('mail.from.address', $this->mail_from_address);
+            config()->set('mail.from.name', $this->mail_from_name);
+        } catch (\Exception) {
+        }
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'mail_host' => 'nullable|string',
+            'mail_port' => 'nullable|int',
+            'mail_username' => 'nullable|string',
+            'mail_password' => 'nullable|string',
+            'mail_encryption' => 'nullable|string',
+            'mail_from_address' => 'nullable|string',
+            'mail_from_name' => 'nullable|string',
+            'mail_mailer' => 'nullable|string',
+            'mail_enabled' => 'nullable|boolean',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-envelope',
+            'mail_host' => [
+                'label' => 'Mail Host',
+                'type' => 'string',
+                'description' => 'The host of your mail server.',
+            ],
+            'mail_port' => [
+                'label' => 'Mail Port',
+                'type' => 'number',
+                'description' => 'The port of your mail server.',
+            ],
+            'mail_username' => [
+                'label' => 'Mail Username',
+                'type' => 'string',
+                'description' => 'The username of your mail server.',
+            ],
+            'mail_password' => [
+                'label' => 'Mail Password',
+                'type' => 'string',
+                'description' => 'The password of your mail server.',
+            ],
+            'mail_encryption' => [
+                'label' => 'Mail Encryption',
+                'type' => 'string',
+                'description' => 'The encryption of your mail server.',
+            ],
+            'mail_from_address' => [
+                'label' => 'Mail From Address',
+                'type' => 'string',
+                'description' => 'The from address of your mail server.',
+            ],
+            'mail_from_name' => [
+                'label' => 'Mail From Name',
+                'type' => 'string',
+                'description' => 'The from name of your mail server.',
+            ],
+            'mail_mailer' => [
+                'label' => 'Mail Mailer',
+                'type' => 'string',
+                'description' => 'The mailer of your mail server.',
+            ],
+            'mail_enabled' => [
+                'label' => 'Mail Enabled',
+                'type' => 'boolean',
+            ],
+        ];
+    }
+}

+ 82 - 0
app/Settings/PterodactylSettings.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class PterodactylSettings extends Settings
+{
+    public string $admin_token;
+    public string $user_token;
+    public string $panel_url;
+    public int $per_page_limit;
+
+    public static function group(): string
+    {
+        return 'pterodactyl';
+    }
+
+    public static function encrypted(): array
+    {
+        return [
+            'admin_token',
+            'user_token'
+        ];
+    }
+
+    /**
+     * Get url with ensured ending backslash
+     *
+     * @return string
+     */
+    public function getUrl(): string
+    {
+        return str_ends_with($this->panel_url, '/') ? $this->panel_url : $this->panel_url . '/';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'panel_url' => 'required|string|url',
+            'admin_token' => 'required|string',
+            'user_token' => 'required|string',
+            'per_page_limit' => 'required|integer|min:1|max:10000',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-server',
+            'panel_url' => [
+                'label' => 'Panel URL',
+                'type' => 'string',
+                'description' => 'The URL to your Pterodactyl panel.',
+            ],
+            'admin_token' => [
+                'label' => 'Admin Token',
+                'type' => 'string',
+                'description' => 'The admin user token for your Pterodactyl panel.',
+            ],
+            'user_token' => [
+                'label' => 'User Token',
+                'type' => 'string',
+                'description' => 'The user token for your Pterodactyl panel.',
+            ],
+            'per_page_limit' => [
+                'label' => 'Per Page Limit',
+                'type' => 'number',
+                'description' => 'The number of servers to show per page.',
+            ],
+        ];
+    }
+}

+ 87 - 0
app/Settings/ReferralSettings.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class ReferralSettings extends Settings
+{
+    public string $allowed;
+    public bool $always_give_commission;
+    public bool $enabled;
+    public ?float $reward;
+    public string $mode;
+    public ?int $percentage;
+
+    public static function group(): string
+    {
+        return 'referral';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'allowed' => 'required|in:Everyone,Clients',
+            'always_give_commission' => 'nullable|boolean',
+            'enabled' => 'nullable|boolean',
+            'reward' => 'nullable|numeric',
+            'mode' => 'required|in:Commission,Sign-Up,Both',
+            'percentage' => 'nullable|numeric',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-user-friends',
+            'allowed' => [
+                'label' => 'Allowed',
+                'type' => 'select',
+                'description' => 'Who is allowed to see their referral-URL',
+                'options' => [
+                    'everyone' => 'Everyone',
+                    'clients' => 'Clients',
+                ],
+            ],
+            'always_give_commission' => [
+                'label' => 'Always Give Commission',
+                'type' => 'boolean',
+                'description' => 'Always give commission to the referrer.',
+            ],
+            'enabled' => [
+                'label' => 'Enabled',
+                'type' => 'boolean',
+                'description' => 'Enable referral system.',
+            ],
+            'reward' => [
+                'label' => 'Reward',
+                'type' => 'number',
+                'description' => 'Reward for the referrer.',
+            ],
+            'mode' => [
+                'label' => 'Mode',
+                'type' => 'select',
+                'description' => 'Referral mode.',
+                'options' => [
+                    'commission' => 'Commission',
+                    'sign-up' => 'Sign-Up',
+                    'both' => 'Both',
+                ],
+            ],
+            'percentage' => [
+                'label' => 'Percentage',
+                'type' => 'number',
+                'description' => 'If a referred user buys credits, the referral-user will get x% of the Credits the referred user bought.',
+            ],
+        ];
+    }
+}

+ 64 - 0
app/Settings/ServerSettings.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class ServerSettings extends Settings
+{
+    public int $allocation_limit;
+    public bool $creation_enabled;
+    public bool $enable_upgrade;
+    public bool $charge_first_hour;
+
+    public static function group(): string
+    {
+        return 'server';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'allocation_limit' => 'required|integer|min:0',
+            'creation_enabled' => 'nullable|boolean',
+            'enable_upgrade' => 'nullable|boolean',
+            'charge_first_hour' => 'nullable|boolean',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-server',
+            'allocation_limit' => [
+                'label' => 'Allocation Limit',
+                'type' => 'number',
+                'description' => 'The maximum amount of allocations to pull per node for automatic deployment, if more allocations are being used than this limit is set to, no new servers can be created.',
+            ],
+            'creation_enabled' => [
+                'label' => 'Creation Enabled',
+                'type' => 'boolean',
+                'description' => 'Whether or not users can create servers.',
+            ],
+            'enable_upgrade' => [
+                'label' => 'Enable Upgrade',
+                'type' => 'boolean',
+                'description' => 'Whether or not users can upgrade their servers.',
+            ],
+            'charge_first_hour' => [
+                'label' => 'Charge First Hour',
+                'type' => 'boolean',
+                'description' => 'Whether or not the first hour of a server is charged.',
+            ],
+        ];
+    }
+}

+ 56 - 0
app/Settings/TicketSettings.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class TicketSettings extends Settings
+{
+    public bool $enabled;
+    public string $notify;
+
+    public static function group(): string
+    {
+        return 'ticket';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'enabled' => 'nullable|boolean',
+            'notify' => 'nullable|string',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-ticket-alt',
+            'enabled' => [
+                'label' => 'Enabled',
+                'type' => 'boolean',
+                'description' => 'Enable or disable the ticket system.',
+            ],
+            'notify' => [
+                'label' => 'Notify',
+                'type' => 'select',
+                'description' => 'Who will receive an E-Mail when a new Ticket is created.',
+                'options' => [
+                    'admin' => 'Admins',
+                    'moderator' => 'Moderators',
+                    'all' => 'Admins and Moderators',
+                    'none' => 'Nobody',
+                ],
+            ],
+        ];
+    }
+}

+ 120 - 0
app/Settings/UserSettings.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class UserSettings extends Settings
+{
+    public float $credits_reward_after_verify_discord;
+    public float $credits_reward_after_verify_email;
+    public bool $force_discord_verification;
+    public bool $force_email_verification;
+    public float $initial_credits;
+    public int $initial_server_limit;
+    public float $min_credits_to_make_server;
+    public int $server_limit_after_irl_purchase;
+    public int $server_limit_after_verify_discord;
+    public int $server_limit_after_verify_email;
+    public bool $register_ip_check;
+    public bool $creation_enabled;
+
+    public static function group(): string
+    {
+        return 'user';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'credits_reward_after_verify_discord' => 'required|numeric',
+            'credits_reward_after_verify_email' => 'required|numeric',
+            'force_discord_verification' => 'nullable|boolean',
+            'force_email_verification' => 'nullable|boolean',
+            'initial_credits' => 'required|numeric',
+            'initial_server_limit' => 'required|numeric',
+            'min_credits_to_make_server' => 'required|numeric',
+            'server_limit_after_irl_purchase' => 'required|numeric',
+            'server_limit_after_verify_discord' => 'required|numeric',
+            'server_limit_after_verify_email' => 'required|numeric',
+            'register_ip_check' => 'nullable|boolean',
+            'creation_enabled' => 'nullable|boolean',
+        ];
+    }
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|boolean|number|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-user',
+            'credits_reward_after_verify_discord' => [
+                'label' => 'Credits Reward After Verify Discord',
+                'type' => 'number',
+                'description' => 'The amount of credits a user gets after verifying their discord account.',
+            ],
+            'credits_reward_after_verify_email' => [
+                'label' => 'Credits Reward After Verify Email',
+                'type' => 'number',
+                'description' => 'The amount of credits a user gets after verifying their email.',
+            ],
+            'force_discord_verification' => [
+                'label' => 'Force Discord Verification',
+                'type' => 'boolean',
+                'description' => 'Force users to verify their discord account.',
+            ],
+            'force_email_verification' => [
+                'label' => 'Force Email Verification',
+                'type' => 'boolean',
+                'description' => 'Force users to verify their email.',
+            ],
+            'initial_credits' => [
+                'label' => 'Initial Credits',
+                'type' => 'number',
+                'description' => 'The amount of credits a user gets when they register.',
+            ],
+            'initial_server_limit' => [
+                'label' => 'Initial Server Limit',
+                'type' => 'number',
+                'description' => 'The amount of servers a user can create when they register.',
+            ],
+            'min_credits_to_make_server' => [
+                'label' => 'Min Credits To Make Server',
+                'type' => 'number',
+                'description' => 'The minimum amount of credits a user needs to create a server.',
+            ],
+            'server_limit_after_irl_purchase' => [
+                'label' => 'Server Limit After IRL Purchase',
+                'type' => 'number',
+                'description' => 'The amount of servers a user can create after they purchase a server.',
+            ],
+            'server_limit_after_verify_discord' => [
+                'label' => 'Server Limit After Verify Discord',
+                'type' => 'number',
+                'description' => 'The amount of servers a user can create after they verify their discord account.',
+            ],
+            'server_limit_after_verify_email' => [
+                'label' => 'Server Limit After Verify Email',
+                'type' => 'number',
+                'description' => 'The amount of servers a user can create after they verify their email.',
+            ],
+            'register_ip_check' => [
+                'label' => 'Register IP Check',
+                'type' => 'boolean',
+                'description' => 'Check if the IP a user is registering from is already in use.',
+            ],
+            'creation_enabled' => [
+                'label' => 'Creation Enabled',
+                'type' => 'boolean',
+                'description' => 'Whether or not users can create servers.',
+            ],
+        ];
+    }
+}

+ 103 - 0
app/Settings/WebsiteSettings.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace App\Settings;
+
+use Spatie\LaravelSettings\Settings;
+
+class WebsiteSettings extends Settings
+{
+
+
+    public bool $show_imprint;
+    public bool $show_privacy;
+    public bool $show_tos;
+    public bool $useful_links_enabled;
+    public bool $enable_login_logo;
+    public ?string $seo_title;
+    public ?string $seo_description;
+    public bool $motd_enabled;
+
+    public ?string $motd_message;
+
+    public static function group(): string
+    {
+        return 'website';
+    }
+
+    /**
+     * Summary of validations array
+     * @return array<string, string>
+     */
+    public static function getValidations()
+    {
+        return [
+            'motd_enabled' => 'nullable|boolean',
+            'motd_message' => 'nullable|string',
+            'show_imprint' => 'nullable|boolean',
+            'show_privacy' => 'nullable|boolean',
+            'show_tos' => 'nullable|boolean',
+            'useful_links_enabled' => 'nullable|boolean',
+            'enable_login_logo' => 'nullable|boolean',
+            'seo_title' => 'nullable|string',
+            'seo_description' => 'nullable|string',
+        ];
+    }
+
+
+    /**
+     * Summary of optionTypes
+     * Only used for the settings page
+     * @return array<array<'type'|'label'|'description'|'options', string|array<string, string>>>
+     */
+    public static function getOptionInputData()
+    {
+        return [
+            'category_icon' => 'fas fa-globe',
+            'motd_enabled' => [
+                'label' => 'Enable MOTD',
+                'type' => 'boolean',
+                'description' => 'Enable the MOTD (Message of the day) on the dashboard.',
+            ],
+            'motd_message' => [
+                'label' => 'MOTD Message',
+                'type' => 'textarea',
+                'description' => 'The message of the day.',
+            ],
+            'show_imprint' => [
+                'label' => 'Show Imprint',
+                'type' => 'boolean',
+                'description' => 'Show the imprint on the website.',
+            ],
+            'show_privacy' => [
+                'label' => 'Show Privacy',
+                'type' => 'boolean',
+                'description' => 'Show the privacy on the website.',
+            ],
+            'show_tos' => [
+                'label' => 'Show TOS',
+                'type' => 'boolean',
+                'description' => 'Show the TOS on the website.',
+            ],
+            'useful_links_enabled' => [
+                'label' => 'Enable Useful Links',
+                'type' => 'boolean',
+                'description' => 'Enable the useful links on the dashboard.',
+            ],
+            'seo_title' => [
+                'label' => 'SEO Title',
+                'type' => 'string',
+                'description' => 'The title of the website.',
+            ],
+            'seo_description' => [
+                'label' => 'SEO Description',
+                'type' => 'string',
+                'description' => 'The description of the website.',
+            ],
+            'enable_login_logo' => [
+                'label' => 'Enable Login Logo',
+                'type' => 'boolean',
+                'description' => 'Enable the logo on the login page.',
+            ],
+        ];
+    }
+}

+ 14 - 12
app/Traits/Invoiceable.php

@@ -5,32 +5,34 @@ namespace App\Traits;
 use App\Models\PartnerDiscount;
 use App\Models\Payment;
 use App\Models\ShopProduct;
+use App\Models\Invoice;
 use App\Notifications\InvoiceNotification;
+use App\Settings\InvoiceSettings;
 use Illuminate\Support\Facades\Storage;
 use LaravelDaily\Invoices\Classes\Buyer;
 use LaravelDaily\Invoices\Classes\InvoiceItem;
 use LaravelDaily\Invoices\Classes\Party;
-use LaravelDaily\Invoices\Invoice;
+use LaravelDaily\Invoices\Invoice as DailyInvoice;
 use Symfony\Component\Intl\Currencies;
 
 trait Invoiceable
 {
-    public function createInvoice(Payment $payment, ShopProduct $shopProduct)
+    public function createInvoice(Payment $payment, ShopProduct $shopProduct, InvoiceSettings $invoice_settings)
     {
         $user = $payment->user;
         //create invoice
-        $lastInvoiceID = \App\Models\Invoice::where("invoice_name", "like", "%" . now()->format('mY') . "%")->count("id");
+        $lastInvoiceID = Invoice::where("invoice_name", "like", "%" . now()->format('mY') . "%")->count("id");
         $newInvoiceID = $lastInvoiceID + 1;
         $logoPath = storage_path('app/public/logo.png');
 
         $seller = new Party([
-            'name' => config("SETTINGS::INVOICE:COMPANY_NAME"),
-            'phone' => config("SETTINGS::INVOICE:COMPANY_PHONE"),
-            'address' => config("SETTINGS::INVOICE:COMPANY_ADDRESS"),
-            'vat' => config("SETTINGS::INVOICE:COMPANY_VAT"),
+            'name' => $invoice_settings->company_name,
+            'phone' => $invoice_settings->company_phone,
+            'address' => $invoice_settings->company_address,
+            'vat' => $invoice_settings->company_vat,
             'custom_fields' => [
-                'E-Mail' => config("SETTINGS::INVOICE:COMPANY_MAIL"),
-                "Web" => config("SETTINGS::INVOICE:COMPANY_WEBSITE")
+                'E-Mail' => $invoice_settings->company_mail,
+                "Web" => $invoice_settings->company_website
             ],
         ]);
 
@@ -51,7 +53,7 @@ trait Invoiceable
         $notes = implode("<br>", $notes);
 
 
-        $invoice = Invoice::make()
+        $invoice = DailyInvoice::make()
             ->template('controlpanel')
             ->name(__("Invoice"))
             ->buyer($customer)
@@ -64,7 +66,7 @@ trait Invoiceable
             ->series(now()->format('mY'))
             ->delimiter("-")
             ->sequence($newInvoiceID)
-            ->serialNumberFormat(config("SETTINGS::INVOICE:PREFIX") . '{DELIMITER}{SERIES}{SEQUENCE}')
+            ->serialNumberFormat($invoice_settings->prefix . '{DELIMITER}{SERIES}{SEQUENCE}')
             ->currencyCode(strtoupper($payment->currency_code))
             ->currencySymbol(Currencies::getSymbol(strtoupper($payment->currency_code)))
             ->notes($notes);
@@ -78,7 +80,7 @@ trait Invoiceable
         $invoice->render();
         Storage::disk("local")->put("invoice/" . $user->id . "/" . now()->format('Y') . "/" . $invoice->filename, $invoice->output);
 
-        \App\Models\Invoice::create([
+        Invoice::create([
             'invoice_user' => $user->id,
             'invoice_name' => $invoice->getSerialNumber(),
             'payment_id' => $payment->payment_id,

+ 29 - 27
composer.json

@@ -11,36 +11,38 @@
         "php": "^8.1",
         "ext-intl": "*",
         "biscolab/laravel-recaptcha": "^5.4",
-        "doctrine/dbal": "^3.1",
-        "guzzlehttp/guzzle": "^7.2",
-        "hidehalo/nanoid-php": "^1.1",
+        "doctrine/dbal": "^3.5.3",
+        "guzzlehttp/guzzle": "^7.5",
+        "hidehalo/nanoid-php": "^1.1.12",
         "kkomelin/laravel-translatable-string-exporter": "^1.18",
-        "laravel/framework": "^9.46",
-        "laravel/tinker": "^2.7",
-        "laravel/ui": "^3.3",
-        "laraveldaily/laravel-invoices": "^3.0",
-        "league/flysystem-aws-s3-v3": "^3.0",
-        "paypal/paypal-checkout-sdk": "^1.0",
-        "paypal/rest-api-sdk-php": "^1.14",
-        "qirolab/laravel-themer": "^2.0",
-        "socialiteproviders/discord": "^4.1",
-        "spatie/laravel-activitylog": "^4.4",
-        "spatie/laravel-query-builder": "^5.0",
-        "spatie/laravel-validation-rules": "^3.2",
-        "stripe/stripe-php": "^7.107",
-        "symfony/http-client": "^6.2",
-        "symfony/intl": "^6.0",
-        "symfony/mailgun-mailer": "^6.2",
-        "yajra/laravel-datatables-oracle": "^9.19"
+        "laravel/framework": "^9.50.2",
+        "laravel/tinker": "^2.8",
+        "laravel/ui": "^3.4.6",
+        "laraveldaily/laravel-invoices": "^3.0.2",
+        "league/flysystem-aws-s3-v3": "^3.12.2",
+        "paypal/paypal-checkout-sdk": "^1.0.2",
+        "paypal/rest-api-sdk-php": "^1.14.0",
+        "predis/predis": "*",
+        "qirolab/laravel-themer": "^2.0.2",
+        "socialiteproviders/discord": "^4.1.2",
+        "spatie/laravel-activitylog": "^4.7.3",
+        "spatie/laravel-query-builder": "^5.1.2",
+        "spatie/laravel-settings": "^2.7",
+        "spatie/laravel-validation-rules": "^3.2.2",
+        "stripe/stripe-php": "^7.128",
+        "symfony/http-client": "^6.2.6",
+        "symfony/intl": "^6.2.5",
+        "symfony/mailgun-mailer": "^6.2.5",
+        "yajra/laravel-datatables-oracle": "^9.21.2"
     },
     "require-dev": {
-        "barryvdh/laravel-debugbar": "^3.6",
-        "fakerphp/faker": "^1.9.1",
-        "laravel/sail": "^1.15",
-        "mockery/mockery": "^1.4.4",
-        "nunomaduro/collision": "^6.3",
-        "phpunit/phpunit": "^9.5.10",
-        "spatie/laravel-ignition": "^1.4"
+        "barryvdh/laravel-debugbar": "^3.7",
+        "fakerphp/faker": "^1.21",
+        "laravel/sail": "^1.19",
+        "mockery/mockery": "^1.5.1",
+        "nunomaduro/collision": "^6.4",
+        "phpunit/phpunit": "^9.6",
+        "spatie/laravel-ignition": "^1.6"
     },
     "config": {
         "optimize-autoloader": true,

文件差异内容过多而无法显示
+ 367 - 157
composer.lock


+ 1 - 2
config/app.php

@@ -210,9 +210,8 @@ return [
         App\Providers\EventServiceProvider::class,
         App\Providers\RouteServiceProvider::class,
         Yajra\DataTables\DataTablesServiceProvider::class,
-
         KKomelin\TranslatableStringExporter\Providers\ExporterServiceProvider::class,
-
+        Biscolab\ReCaptcha\ReCaptchaServiceProvider::class,
     ],
 
     /*

+ 1 - 1
config/mail.php

@@ -93,7 +93,7 @@ return [
 
     'from' => [
         'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
-        'name' => env('MAIL_FROM_NAME', 'Example'),
+        'name' => env('MAIL_FROM_NAME', 'ControlPanel'),
     ],
 
     /*

+ 111 - 0
config/settings.php

@@ -0,0 +1,111 @@
+<?php
+
+use App\Helpers\ExtensionHelper;
+use App\Settings\GeneralSettings;
+use App\Settings\DiscordSettings;
+use App\Settings\InvoiceSettings;
+use App\Settings\LocaleSettings;
+use App\Settings\MailSettings;
+use App\Settings\PterodactylSettings;
+use App\Settings\ReferralSettings;
+use App\Settings\ServerSettings;
+use App\Settings\UserSettings;
+use App\Settings\WebsiteSettings;
+use App\Settings\TicketSettings;
+
+return [
+
+    /*
+     * Each settings class used in your application must be registered, you can
+     * put them (manually) here.
+     */
+    'settings' => [
+        GeneralSettings::class,
+        DiscordSettings::class,
+        InvoiceSettings::class,
+        LocaleSettings::class,
+        MailSettings::class,
+        PterodactylSettings::class,
+        ReferralSettings::class,
+        ServerSettings::class,
+        UserSettings::class,
+        WebsiteSettings::class,
+        TicketSettings::class,
+    ],
+
+    /*
+     * The path where the settings classes will be created.
+     */
+    'setting_class_path' => app_path('Settings'),
+
+    /*
+     * In these directories settings migrations will be stored and ran when migrating. A settings
+     * migration created via the make:settings-migration command will be stored in the first path or
+     * a custom defined path when running the command.
+     */
+    'migrations_paths' => [
+        database_path('settings'),
+        ...ExtensionHelper::getAllExtensionMigrations()
+
+    ],
+
+    /*
+     * When no repository was set for a settings class the following repository
+     * will be used for loading and saving settings.
+     */
+    'default_repository' => 'database',
+
+    /*
+     * Settings will be stored and loaded from these repositories.
+     */
+    'repositories' => [
+        'database' => [
+            'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class,
+            'model' => null,
+            'table' => null,
+            'connection' => null,
+        ],
+        'redis' => [
+            'type' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class,
+            'connection' => null,
+            'prefix' => null,
+        ],
+    ],
+
+    /*
+     * The contents of settings classes can be cached through your application,
+     * settings will be stored within a provided Laravel store and can have an
+     * additional prefix.
+     */
+    'cache' => [
+        'enabled' => env('SETTINGS_CACHE_ENABLED', true),
+        'store' => 'redis',
+        'prefix' => 'setting',
+        'ttl' => null,
+    ],
+
+    /*
+     * These global casts will be automatically used whenever a property within
+     * your settings class isn't a default PHP type.
+     */
+    'global_casts' => [
+        DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class,
+        DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class,
+        //        Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class,
+        Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class,
+    ],
+
+    /*
+     * The package will look for settings in these paths and automatically
+     * register them.
+     */
+    'auto_discover_settings' => [
+        app()->path(),
+    ],
+
+    /*
+     * Automatically discovered settings classes can be cached so they don't
+     * need to be searched each time the application boots up.
+     */
+    'discovered_settings_cache_path' => storage_path('app/laravel-settings'),
+];

+ 45 - 0
database/migrations/2023_02_01_155730_create_new_settings_table.php

@@ -0,0 +1,45 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        // rename old settings table
+        Schema::table('settings', function (Blueprint $table) {
+            $table->rename('settings_old');
+        });
+
+        // create new settings table
+        Schema::create('settings', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->json('payload')->nullable();
+            $table->string('group')->index();
+            $table->boolean('locked');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('settings');
+
+        Schema::table('settings_old', function (Blueprint $table) {
+            $table->rename("settings");
+        });
+    }
+};

+ 34 - 0
database/migrations/2023_03_15_150022_delete_old_settings_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::dropIfExists('settings_old');
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::create('settings_old', function (Blueprint $table) {
+            $table->string('key', 191)->primary();
+            $table->text('value')->nullable();
+            $table->string('type');
+            $table->longText('description')->nullable();
+            $table->timestamps();
+        });
+    }
+};

+ 32 - 0
database/migrations/2023_04_03_231829_update_users_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->string('pterodactyl_id')->change();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->integer('pterodactyl_id')->nullable->change();
+        });
+    }
+};

+ 2 - 4
database/seeders/DatabaseSeeder.php

@@ -2,8 +2,8 @@
 
 namespace Database\Seeders;
 
-use Database\Seeders\Seeds\SettingsSeeder;
 use Illuminate\Database\Seeder;
+use Illuminate\Support\Facades\Schema;
 
 class DatabaseSeeder extends Seeder
 {
@@ -14,8 +14,6 @@ class DatabaseSeeder extends Seeder
      */
     public function run()
     {
-        $this->call([
-            SettingsSeeder::class,
-        ]);
+        // Schema::dropIfExists('settings_old');
     }
 }

+ 0 - 656
database/seeders/Seeds/SettingsSeeder.php

@@ -1,656 +0,0 @@
-<?php
-
-namespace Database\Seeders\Seeds;
-
-use App\Models\Settings;
-use Illuminate\Database\Seeder;
-
-class SettingsSeeder extends Seeder
-{
-    /**
-     * Run the database seeds.
-     *
-     * @return void
-     */
-    public function run()
-    {
-        //initials
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:INITIAL_CREDITS',
-        ], [
-            'value' => '250',
-            'type' => 'integer',
-            'description' => 'The initial amount of credits the user starts with.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:INITIAL_SERVER_LIMIT',
-        ], [
-            'value' => '1',
-            'type' => 'integer',
-            'description' => 'The initial server limit the user starts with.',
-        ]);
-
-        //verify email event
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL',
-        ], [
-            'value' => '250',
-            'type' => 'integer',
-            'description' => 'Increase in credits after the user has verified their email account.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL',
-        ], [
-            'value' => '2',
-            'type' => 'integer',
-            'description' => 'Increase in server limit after the user has verified their email account.',
-        ]);
-
-        //verify discord event
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD',
-        ], [
-            'value' => '375',
-            'type' => 'integer',
-            'description' => 'Increase in credits after the user has verified their discord account.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD',
-        ], [
-            'value' => '2',
-            'type' => 'integer',
-            'description' => 'Increase in server limit after the user has verified their discord account.',
-        ]);
-
-        //other
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER',
-        ], [
-            'value' => '50',
-            'type' => 'integer',
-            'description' => 'The minimum amount of credits the user would need to make a server.',
-        ]);
-
-        //purchasing
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE',
-        ], [
-            'value' => '10',
-            'type' => 'integer',
-            'description' => 'updates the users server limit to this amount (unless the user already has a higher server limit) after making a purchase with real money, set to 0 to ignore this.',
-        ]);
-
-        //force email and discord verification
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:FORCE_EMAIL_VERIFICATION',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Force an user to verify the email adress before creating a server / buying credits.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:FORCE_DISCORD_VERIFICATION',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Force an user to link an Discord Account before creating a server / buying credits.',
-        ]);
-
-        //disable ip check on register
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:REGISTER_IP_CHECK',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Prevent users from making multiple accounts using the same IP address',
-        ]);
-
-        //per_page on allocations request
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SERVER:ALLOCATION_LIMIT',
-        ], [
-            'value' => '200',
-            'type' => 'integer',
-            'description' => 'The maximum amount of allocations to pull per node for automatic deployment, if more allocations are being used than this limit is set to, no new servers can be created!',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER',
-        ], [
-            'value'       => '0',
-            'type'        => 'integer',
-            'description' => 'The minimum amount of credits user has to have to create a server. Can be overridden by package limits.'
-        ]);
-
-        //credits display name
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME',
-        ], [
-            'value' => 'Credits',
-            'type' => 'string',
-            'description' => 'The display name of your currency.',
-        ]);
-
-        //credits display name
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Charges the first hour worth of credits upon creating a server.',
-        ]);
-        //sales tax
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:SALES_TAX',
-        ], [
-            'value' => '0',
-            'type' => 'integer',
-            'description' => 'The %-value of tax that will be added to the product price on checkout.',
-        ]);
-        //Invoices enabled
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:ENABLED',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Enables or disables the invoice feature for payments.',
-        ]);
-        //Invoice company name
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_NAME',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The name of the Company on the Invoices.',
-        ]);
-        //Invoice company address
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_ADDRESS',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The address of the Company on the Invoices.',
-        ]);
-        //Invoice company phone
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_PHONE',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The phone number of the Company on the Invoices.',
-        ]);
-
-        //Invoice company mail
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_MAIL',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The email address of the Company on the Invoices.',
-        ]);
-
-        //Invoice VAT
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_VAT',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The VAT-Number of the Company on the Invoices.',
-        ]);
-
-        //Invoice Website
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:COMPANY_WEBSITE',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The Website of the Company on the Invoices.',
-        ]);
-
-        //Invoice Website
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::INVOICE:PREFIX',
-        ], [
-            'value' => 'INV',
-            'type' => 'string',
-            'description' => 'The invoice prefix.',
-        ]);
-
-        //Locale
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::LOCALE:DEFAULT',
-        ], [
-            'value' => 'en',
-            'type' => 'string',
-            'description' => 'The default dashboard language.',
-        ]);
-        //Dynamic locale
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::LOCALE:DYNAMIC',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'If this is true, the Language will change to the Clients browserlanguage or default.',
-        ]);
-        //User can change Locale
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::LOCALE:CLIENTS_CAN_CHANGE',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'If this is true, the clients will be able to change their Locale.',
-        ]);
-        //Locale
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::LOCALE:AVAILABLE',
-        ], [
-            'value' => 'en',
-            'type' => 'string',
-            'description' => 'The available languages.',
-        ]);
-        //Locale
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::LOCALE:DATATABLES',
-        ], [
-            'value' => 'en-gb',
-            'type' => 'string',
-            'description' => 'The Language of the Datatables. Grab the Language-Codes from here https://datatables.net/plug-ins/i18n/',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:PAYPAL:SECRET',
-        ], [
-            'value' => env('PAYPAL_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your PayPal Secret-Key (https://developer.paypal.com/docs/integration/direct/rest/).',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID',
-        ], [
-            'value' => env('PAYPAL_CLIENT_ID', ''),
-            'type' => 'string',
-            'description' => 'Your PayPal Client_ID.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET',
-        ], [
-            'value' => env('PAYPAL_SANDBOX_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your PayPal SANDBOX Secret-Key used for testing.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID',
-        ], [
-            'value' => env('PAYPAL_SANDBOX_CLIENT_ID', ''),
-            'type' => 'string',
-            'description' => 'Your PayPal SANDBOX Client-ID used for testing.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:STRIPE:SECRET',
-        ], [
-            'value' => env('STRIPE_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your Stripe Secret-Key (https://dashboard.stripe.com/account/apikeys).',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET',
-        ], [
-            'value' => env('STRIPE_ENDPOINT_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your Stripe endpoint secret-key.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:STRIPE:TEST_SECRET',
-        ], [
-            'value' => env('STRIPE_TEST_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your Stripe test secret-key.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET',
-        ], [
-            'value' => env('STRIPE_ENDPOINT_TEST_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Your Stripe endpoint test secret-key.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::PAYMENTS:STRIPE:METHODS',
-        ], [
-            'value' => env('STRIPE_METHODS', 'card,sepa_debit'),
-            'type' => 'string',
-            'description' => 'Comma seperated list of payment methods that are enabled (https://stripe.com/docs/payments/payment-methods/integration-options).',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:CLIENT_ID',
-        ], [
-            'value' => env('DISCORD_CLIENT_ID', ''),
-            'type' => 'string',
-            'description' => 'Discord API Credentials (https://discordapp.com/developers/applications/).',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:CLIENT_SECRET',
-        ], [
-            'value' => env('DISCORD_CLIENT_SECRET', ''),
-            'type' => 'string',
-            'description' => 'Discord API Credentials (https://discordapp.com/developers/applications/).',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:BOT_TOKEN',
-        ], [
-            'value' => env('DISCORD_BOT_TOKEN', ''),
-            'type' => 'string',
-            'description' => 'Discord API Credentials (https://discordapp.com/developers/applications/).',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:GUILD_ID',
-        ], [
-            'value' => env('DISCORD_GUILD_ID', ''),
-            'type' => 'string',
-            'description' => 'Discord API Credentials (https://discordapp.com/developers/applications/).',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:ROLE_ID',
-        ], [
-            'value' => env('DISCORD_ROLE_ID', ''),
-            'type' => 'string',
-            'description' => 'Discord role that will be assigned to users when they register.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::DISCORD:INVITE_URL',
-        ], [
-            'value' => env('DISCORD_INVITE_URL', ''),
-            'type' => 'string',
-            'description' => 'The invite URL to your Discord Server.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:PTERODACTYL:TOKEN',
-        ], [
-            'value' => env('PTERODACTYL_TOKEN', ''),
-            'type' => 'string',
-            'description' => 'Admin API Token from Pterodactyl Panel - necessary for the Panel to work. The Key needs all read&write permissions!',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:PTERODACTYL:URL',
-        ], [
-            'value' => env('PTERODACTYL_URL', ''),
-            'type' => 'string',
-            'description' => 'The URL to your Pterodactyl Panel. Must not end with a / ',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT',
-        ], [
-            'value' => 200,
-            'type' => 'integer',
-            'description' => 'The Pterodactyl API perPage limit. It is necessary to set it higher than your server count.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MISC:PHPMYADMIN:URL',
-        ], [
-            'value' => env('PHPMYADMIN_URL', ''),
-            'type' => 'string',
-            'description' => 'The URL to your PHPMYADMIN Panel. Must not end with a /, remove to remove database button',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::RECAPTCHA:SITE_KEY',
-        ], [
-            'value' => env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'),
-            'type' => 'string',
-            'description' => 'Google Recaptcha API Credentials (https://www.google.com/recaptcha/admin) - reCaptcha V2 (not v3)',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::RECAPTCHA:SECRET_KEY',
-        ], [
-            'value' => env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'),
-            'type' => 'string',
-            'description' => 'Google Recaptcha API Credentials (https://www.google.com/recaptcha/admin) - reCaptcha V2 (not v3)',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::RECAPTCHA:ENABLED',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Enables or disables the ReCaptcha feature on the registration/login page.',
-
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:MAILER',
-        ], [
-            'value' => env('MAIL_MAILER', 'smtp'),
-            'type' => 'string',
-            'description' => 'Selected Mailer (smtp, mailgun, sendgrid, mailtrap).',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:HOST',
-        ], [
-            'value' => env('MAIL_HOST', 'localhost'),
-            'type' => 'string',
-            'description' => 'Mailer Host Address.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:PORT',
-        ], [
-            'value' => env('MAIL_PORT', '25'),
-            'type' => 'string',
-            'description' => 'Mailer Server Port.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:USERNAME',
-        ], [
-            'value' => env('MAIL_USERNAME', ''),
-            'type' => 'string',
-            'description' => 'Mailer Username.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:PASSWORD',
-        ], [
-            'value' => env('MAIL_PASSWORD', ''),
-            'type' => 'string',
-            'description' => 'Mailer Password.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:ENCRYPTION',
-        ], [
-            'value' => env('MAIL_ENCRYPTION', 'tls'),
-            'type' => 'string',
-            'description' => 'Mailer Encryption (tls, ssl).',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:FROM_ADDRESS',
-        ], [
-            'value' => env('MAIL_FROM_ADDRESS', ''),
-            'type' => 'string',
-            'description' => 'Mailer From Address.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::MAIL:FROM_NAME',
-        ], [
-            'value' => env('APP_NAME', 'Controlpanel'),
-            'type' => 'string',
-            'description' => 'Mailer From Name.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL::ENABLED',
-        ], [
-            'value' => 'false',
-            'type' => 'string',
-            'description' => 'Enable or disable the referral system.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION',
-        ], [
-            'value' => 'false',
-            'type' => 'string',
-            'description' => 'Whether referrals get percentage commission only on first purchase or on every purchase',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL::REWARD',
-        ], [
-            'value' => 100,
-            'type' => 'integer',
-            'description' => 'Credit reward a user should receive when a user registers with his referral code',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL::ALLOWED',
-        ], [
-            'value' => 'client',
-            'type' => 'string',
-            'description' => 'Who should be allowed to to use the referral code. all/client',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL:MODE',
-        ], [
-            'value' => 'sign-up',
-            'type' => 'string',
-            'description' => 'Whether referrals get Credits on User-Registration or if a User buys credits',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::REFERRAL:PERCENTAGE',
-        ], [
-            'value' => 100,
-            'type' => 'integer',
-            'description' => 'The Percentage value a referred user gets.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN',
-        ], [
-            'value' => '',
-            'type' => 'string',
-            'description' => 'The Client API Key of an Pterodactyl Admin Account.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:ENABLE_UPGRADE',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Enables the updgrade/downgrade feature for servers.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Enable creation of new servers',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_USERS',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Enable creation of new users',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SHOW_IMPRINT',
-        ], [
-
-            'value' => "false",
-            'type'  => 'boolean',
-            'description'  => 'Enable imprint in footer.'
-
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SHOW_PRIVACY',
-        ], [
-
-            'value' => "false",
-            'type'  => 'boolean',
-            'description'  => 'Enable privacy policy in footer.'
-
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SHOW_TOS',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Enable Terms of Service in footer.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:ALERT_ENABLED',
-        ], [
-            'value' => 'false',
-            'type' => 'boolean',
-            'description' => 'Enable Alerts on Homepage.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:ALERT_TYPE',
-        ], [
-            'value' => 'dark',
-            'type' => 'text',
-            'description' => 'Changes the Color of the Alert.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:ALERT_MESSAGE',
-        ], [
-            'value' => '',
-            'type' => 'text',
-            'description' => 'Changes the Content the Alert.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:THEME',
-        ], [
-            'value' => 'default',
-            'type' => 'text',
-            'description' => 'Current active theme.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:USEFULLINKS_ENABLED',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Enable Useful Links on Homepage.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:MOTD_ENABLED',
-        ], [
-            'value' => 'true',
-            'type' => 'boolean',
-            'description' => 'Enable MOTD on Homepage.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:MOTD_MESSAGE',
-        ], [
-            'value' => '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://controlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">Controlpanel.gg</span></h1>
- <p><span style="font-size: 18pt;">Thank you for using our Software</span></p>
- <p><span style="font-size: 18pt;">If you have any questions, make sure to join our <a href="https://discord.com/invite/4Y6HjD2uyU" target="_blank" rel="noopener">Discord</a></span></p>
- <p><span style="font-size: 10pt;">(you can change this message in the <a href="admin/settings#system">Settings</a> )</span></p>',
-            'type' => 'text',
-            'description' => 'MOTD Message.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SEO_TITLE',
-        ], [
-            'value' => 'Controlpanel.gg',
-            'type' => 'text',
-            'description' => 'The SEO Title.',
-        ]);
-
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::SYSTEM:SEO_DESCRIPTION',
-        ], [
-            'value' => 'Billing software for Pterodactyl Dashboard!',
-            'type' => 'text',
-            'description' => 'SEO Description.',
-        ]);
-        Settings::firstOrCreate([
-            'key' => 'SETTINGS::TICKET:NOTIFY',
-        ], [
-            'value' => 'all',
-            'type' => 'text',
-            'description' => 'Who will get a Email Notifcation on new Tickets.',
-        ]);
-    }
-}

+ 139 - 0
database/settings/2023_02_01_164731_create_general_settings.php

@@ -0,0 +1,139 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateGeneralSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('general.store_enabled',  true);
+        $this->migrator->add('general.credits_display_name', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME') : 'Credits');
+        $this->migrator->addEncrypted('general.recaptcha_site_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SITE_KEY") : env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'));
+        $this->migrator->addEncrypted('general.recaptcha_secret_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SECRET_KEY") : env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'));
+        $this->migrator->add('general.recaptcha_enabled', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:ENABLED") : true);
+        $this->migrator->add('general.phpmyadmin_url', $table_exists ? $this->getOldValue("SETTINGS::MISC:PHPMYADMIN:URL") : env('PHPMYADMIN_URL', ''));
+        $this->migrator->add('general.alert_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_ENABLED") : false);
+        $this->migrator->add('general.alert_type', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_TYPE") : 'dark');
+        $this->migrator->add('general.alert_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_MESSAGE") : '');
+        $this->migrator->add('general.theme', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:THEME") : 'default');
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME',
+                'value' => $this->getNewValue('credits_display_name'),
+                'type' => 'string',
+                'description' => 'The name of the credits on the panel.'
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:ALERT_ENABLED',
+                'value' => $this->getNewValue('alert_enabled'),
+                'type' => 'boolean',
+                'description' => 'Enable the alert at the top of the panel.'
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:ALERT_TYPE',
+                'value' => $this->getNewValue('alert_type'),
+                'type' => 'string',
+                'description' => 'The type of alert to display.'
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:ALERT_MESSAGE',
+                'value' => $this->getNewValue('alert_message'),
+                'type' => 'text',
+                'description' => 'The message to display in the alert.'
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:THEME',
+                'value' => $this->getNewValue('theme'),
+                'type' => 'string',
+                'description' => 'The theme to use for the panel.'
+
+            ],
+            [
+                'key' => 'SETTINGS::RECAPTCHA:SITE_KEY',
+                'value' => $this->getNewValue('recaptcha_site_key'),
+                'type' => 'string',
+                'description' => 'The site key for reCAPTCHA.'
+            ],
+            [
+                'key' => 'SETTINGS::RECAPTCHA:SECRET_KEY',
+                'value' => $this->getNewValue('recaptcha_secret_key'),
+                'type' => 'string',
+                'description' => 'The secret key for reCAPTCHA.'
+            ],
+            [
+                'key' => 'SETTINGS::RECAPTCHA:ENABLED',
+                'value' => $this->getNewValue('recaptcha_enabled'),
+                'type' => 'boolean',
+                'description' => 'Enable reCAPTCHA on the panel.'
+            ],
+            [
+                'key' => 'SETTINGS::MISC:PHPMYADMIN:URL',
+                'value' => $this->getNewValue('phpmyadmin_url'),
+                'type' => 'string',
+                'description' => 'The URL to your phpMyAdmin installation.'
+            ],
+        ]);
+
+        $this->migrator->delete('general.store_enabled');
+        $this->migrator->delete('general.credits_display_name');
+        $this->migrator->delete('general.recaptcha_site_key');
+        $this->migrator->delete('general.recaptcha_secret_key');
+        $this->migrator->delete('general.recaptcha_enabled');
+        $this->migrator->delete('general.phpmyadmin_url');
+        $this->migrator->delete('general.alert_enabled');
+        $this->migrator->delete('general.alert_type');
+        $this->migrator->delete('general.alert_message');
+        $this->migrator->delete('general.theme');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'general'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 98 - 0
database/settings/2023_02_01_181334_create_pterodactyl_settings.php

@@ -0,0 +1,98 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreatePterodactylSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', ''));
+        $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : '');
+        $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL') : env('PTERODACTYL_URL', ''));
+        $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT') : 200);
+    }
+
+    public function down(): void
+    {
+
+
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::SYSTEM:PTERODACTYL:TOKEN',
+                'value' => $this->getNewValue('admin_token'),
+                'type' => 'string',
+                'description' => 'The admin token for the Pterodactyl panel.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN',
+                'value' => $this->getNewValue('user_token'),
+                'type' => 'string',
+                'description' => 'The user token for the Pterodactyl panel.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:PTERODACTYL:URL',
+                'value' => $this->getNewValue('panel_url'),
+                'type' => 'string',
+                'description' => 'The URL for the Pterodactyl panel.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT',
+                'value' => $this->getNewValue('per_page_limit'),
+                'type' => 'integer',
+                'description' => 'The number of servers to show per page.',
+            ],
+        ]);
+
+        $this->migrator->delete('pterodactyl.admin_token');
+        $this->migrator->delete('pterodactyl.user_token');
+        $this->migrator->delete('pterodactyl.panel_url');
+        $this->migrator->delete('pterodactyl.per_page_limit');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'pterodactyl'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 136 - 0
database/settings/2023_02_01_181453_create_mail_settings.php

@@ -0,0 +1,136 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateMailSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('mail.mail_host', $table_exists ? $this->getOldValue('SETTINGS::MAIL:HOST') : env('MAIL_HOST', 'localhost'));
+        $this->migrator->add('mail.mail_port', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PORT') : env('MAIL_PORT', 25));
+        $this->migrator->add('mail.mail_username', $table_exists ? $this->getOldValue('SETTINGS::MAIL:USERNAME') : env('MAIL_USERNAME', ''));
+        $this->migrator->addEncrypted('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', ''));
+        $this->migrator->add('mail.mail_encryption', $table_exists ? $this->getOldValue('SETTINGS::MAIL:ENCRYPTION') : env('MAIL_ENCRYPTION', 'tls'));
+        $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com'));
+        $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'ControlPanel.gg'));
+        $this->migrator->add('mail.mail_mailer', $table_exists ? $this->getOldValue('SETTINGS::MAIL:MAILER') : env('MAIL_MAILER', 'smtp'));
+        $this->migrator->add('mail.mail_enabled', true);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::MAIL:HOST',
+                'value' => $this->getNewValue('mail_host'),
+                'type' => 'string',
+                'description' => 'The host of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:PORT',
+                'value' => $this->getNewValue('mail_port'),
+                'type' => 'integer',
+                'description' => 'The port of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:USERNAME',
+                'value' => $this->getNewValue('mail_username'),
+                'type' => 'string',
+                'description' => 'The username of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:PASSWORD',
+                'value' => $this->getNewValue('mail_password'),
+                'type' => 'string',
+                'description' => 'The password of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:ENCRYPTION',
+                'value' => $this->getNewValue('mail_encryption'),
+                'type' => 'string',
+                'description' => 'The encryption of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:FROM_ADDRESS',
+                'value' => $this->getNewValue('mail_from_address'),
+                'type' => 'string',
+                'description' => 'The from address of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:FROM_NAME',
+                'value' => $this->getNewValue('mail_from_name'),
+                'type' => 'string',
+                'description' => 'The from name of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:MAILER',
+                'value' => $this->getNewValue('mail_mailer'),
+                'type' => 'string',
+                'description' => 'The mailer of the mail server.',
+            ],
+            [
+                'key' => 'SETTINGS::MAIL:ENABLED',
+                'value' => $this->getNewValue('mail_enabled'),
+                'type' => 'boolean',
+                'description' => 'The enabled state of the mail server.',
+            ],
+        ]);
+
+        $this->migrator->delete('mail.mail_host');
+        $this->migrator->delete('mail.mail_port');
+        $this->migrator->delete('mail.mail_username');
+        $this->migrator->delete('mail.mail_password');
+        $this->migrator->delete('mail.mail_encryption');
+        $this->migrator->delete('mail.mail_from_address');
+        $this->migrator->delete('mail.mail_from_name');
+        $this->migrator->delete('mail.mail_mailer');
+        $this->migrator->delete('mail.mail_enabled');
+    }
+
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'mail'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 163 - 0
database/settings/2023_02_01_181925_create_user_settings.php

@@ -0,0 +1,163 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateUserSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('user.credits_reward_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD') : 250);
+        $this->migrator->add('user.credits_reward_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL') : 250);
+        $this->migrator->add('user.force_discord_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_DISCORD_VERIFICATION') : false);
+        $this->migrator->add('user.force_email_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_EMAIL_VERIFICATION') : false);
+        $this->migrator->add('user.initial_credits', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_CREDITS') : 250);
+        $this->migrator->add('user.initial_server_limit', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_SERVER_LIMIT') : 1);
+        $this->migrator->add('user.min_credits_to_make_server', $table_exists ? $this->getOldValue('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') : 50);
+        $this->migrator->add('user.server_limit_after_irl_purchase', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') : 10);
+        $this->migrator->add('user.server_limit_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') : 2);
+        $this->migrator->add('user.server_limit_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') : 2);
+        $this->migrator->add('user.register_ip_check', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:REGISTER_IP_CHECK") : true);
+        $this->migrator->add('user.creation_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:CREATION_OF_NEW_USERS") : true);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD',
+                'value' => $this->getNewValue('credits_reward_after_verify_discord'),
+                'type' => 'integer',
+                'description' => 'The amount of credits that the user will receive after verifying their Discord account.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL',
+                'value' => $this->getNewValue('credits_reward_after_verify_email'),
+                'type' => 'integer',
+                'description' => 'The amount of credits that the user will receive after verifying their email.',
+
+            ],
+            [
+                'key' => 'SETTINGS::USER:FORCE_DISCORD_VERIFICATION',
+                'value' => $this->getNewValue('force_discord_verification'),
+                'type' => 'boolean',
+                'description' => 'If the user must verify their Discord account to use the panel.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:FORCE_EMAIL_VERIFICATION',
+                'value' => $this->getNewValue('force_email_verification'),
+                'type' => 'boolean',
+                'description' => 'If the user must verify their email to use the panel.',
+
+            ],
+            [
+                'key' => 'SETTINGS::USER:INITIAL_CREDITS',
+                'value' => $this->getNewValue('initial_credits'),
+                'type' => 'integer',
+                'description' => 'The amount of credits that the user will receive when they register.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:INITIAL_SERVER_LIMIT',
+                'value' => $this->getNewValue('initial_server_limit'),
+                'type' => 'integer',
+                'description' => 'The amount of servers that the user will be able to create when they register.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER',
+                'value' => $this->getNewValue('min_credits_to_make_server'),
+                'type' => 'integer',
+                'description' => 'The minimum amount of credits that the user must have to create a server.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE',
+                'value' => $this->getNewValue('server_limit_after_irl_purchase'),
+                'type' => 'integer',
+                'description' => 'The amount of servers that the user will be able to create after making a real purchase.',
+            ],
+            [
+                'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD',
+                'value' => $this->getNewValue('server_limit_after_verify_discord'),
+                'type' => 'integer',
+                'description' => 'The amount of servers that the user will be able to create after verifying their Discord account.',
+
+            ],
+            [
+                'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL',
+                'value' => $this->getNewValue('server_limit_after_verify_email'),
+                'type' => 'integer',
+                'description' => 'The amount of servers that the user will be able to create after verifying their email.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:REGISTER_IP_CHECK',
+                'value' => $this->getNewValue('register_ip_check'),
+                'type' => 'boolean',
+                'description' => 'If the user must verify their IP address to register.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_USERS',
+                'value' => $this->getNewValue('creation_enabled'),
+                'type' => 'boolean',
+                'description' => 'If the user can register.',
+            ],
+        ]);
+
+        $this->migrator->delete('user.credits_reward_after_verify_discord');
+        $this->migrator->delete('user.credits_reward_after_verify_email');
+        $this->migrator->delete('user.force_discord_verification');
+        $this->migrator->delete('user.force_email_verification');
+        $this->migrator->delete('user.initial_credits');
+        $this->migrator->delete('user.initial_server_limit');
+        $this->migrator->delete('user.min_credits_to_make_server');
+        $this->migrator->delete('user.server_limit_after_irl_purchase');
+        $this->migrator->delete('user.server_limit_after_verify_discord');
+        $this->migrator->delete('user.server_limit_after_verify_email');
+        $this->migrator->delete('user.register_ip_check');
+        $this->migrator->delete('user.creation_enabled');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'user'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 96 - 0
database/settings/2023_02_01_181950_create_server_settings.php

@@ -0,0 +1,96 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateServerSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('server.allocation_limit', $table_exists ? $this->getOldValue('SETTINGS::SERVER:ALLOCATION_LIMIT') : 200);
+        $this->migrator->add('server.creation_enabled', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS') : true);
+        $this->migrator->add('server.enable_upgrade', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:ENABLE_UPGRADE') : false);
+        $this->migrator->add('server.charge_first_hour', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR') : false);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::SERVER:ALLOCATION_LIMIT',
+                'value' => $this->getNewValue('allocation_limit'),
+                'type' => 'integer',
+                'description' => 'The number of servers to show per page.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS',
+                'value' => $this->getNewValue('creation_enabled'),
+                'type' => 'boolean',
+                'description' => 'Whether or not users can create new servers.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:ENABLE_UPGRADE',
+                'value' => $this->getNewValue('enable_upgrade'),
+                'type' => 'boolean',
+                'description' => 'Whether or not users can upgrade their servers.',
+            ],
+            [
+                'key' => 'SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR',
+                'value' => $this->getNewValue('charge_first_hour'),
+                'type' => 'boolean',
+                'description' => 'Whether or not to charge the user for the first hour of their server.',
+            ],
+        ]);
+
+        $this->migrator->delete('server.allocation_limit');
+        $this->migrator->delete('server.creation_enabled');
+        $this->migrator->delete('server.enable_upgrade');
+        $this->migrator->delete('server.charge_first_hour');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'server'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 128 - 0
database/settings/2023_02_01_182021_create_invoice_settings.php

@@ -0,0 +1,128 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateInvoiceSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('invoice.company_address', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_ADDRESS') : '');
+        $this->migrator->add('invoice.company_mail', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_MAIL') : '');
+        $this->migrator->add('invoice.company_name', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_NAME') : '');
+        $this->migrator->add('invoice.company_phone', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_PHONE') : '');
+        $this->migrator->add('invoice.company_vat', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_VAT') : '');
+        $this->migrator->add('invoice.company_website', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_WEBSITE') : '');
+        $this->migrator->add('invoice.enabled', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:ENABLED') : true);
+        $this->migrator->add('invoice.prefix', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:PREFIX') : 'INV');
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_ADDRESS',
+                'value' => $this->getNewValue('company_address'),
+                'type' => 'string',
+                'description' => 'The address of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_MAIL',
+                'value' => $this->getNewValue('company_mail'),
+                'type' => 'string',
+                'description' => 'The email address of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_NAME',
+                'value' => $this->getNewValue('company_name'),
+                'type' => 'string',
+                'description' => 'The name of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_PHONE',
+                'value' => $this->getNewValue('company_phone'),
+                'type' => 'string',
+                'description' => 'The phone number of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_VAT',
+                'value' => $this->getNewValue('company_vat'),
+                'type' => 'string',
+                'description' => 'The VAT number of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:COMPANY_WEBSITE',
+                'value' => $this->getNewValue('company_website'),
+                'type' => 'string',
+                'description' => 'The website of the company.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:ENABLED',
+                'value' => $this->getNewValue('enabled'),
+                'type' => 'boolean',
+                'description' => 'Enable or disable the invoice system.',
+            ],
+            [
+                'key' => 'SETTINGS::INVOICE:PREFIX',
+                'value' => $this->getNewValue('prefix'),
+                'type' => 'string',
+                'description' => 'The prefix of the invoice.',
+            ],
+        ]);
+
+        $this->migrator->delete('invoice.company_address');
+        $this->migrator->delete('invoice.company_mail');
+        $this->migrator->delete('invoice.company_name');
+        $this->migrator->delete('invoice.company_phone');
+        $this->migrator->delete('invoice.company_vat');
+        $this->migrator->delete('invoice.company_website');
+        $this->migrator->delete('invoice.enabled');
+        $this->migrator->delete('invoice.prefix');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'invoice'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 113 - 0
database/settings/2023_02_01_182043_create_discord_settings.php

@@ -0,0 +1,113 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateDiscordSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->addEncrypted('discord.bot_token', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:BOT_TOKEN') : '');
+        $this->migrator->addEncrypted('discord.client_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_ID') : '');
+        $this->migrator->addEncrypted('discord.client_secret', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_SECRET') : '');
+        $this->migrator->add('discord.guild_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:GUILD_ID') : '');
+        $this->migrator->add('discord.invite_url', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:INVITE_URL') : '');
+        $this->migrator->add('discord.role_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:ROLE_ID') : '');
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::DISCORD:BOT_TOKEN',
+                'value' => $this->getNewValue('bot_token'),
+                'type' => 'string',
+                'description' => 'The bot token for the Discord bot.',
+            ],
+            [
+                'key' => 'SETTINGS::DISCORD:CLIENT_ID',
+                'value' => $this->getNewValue('client_id'),
+                'type' => 'string',
+                'description' => 'The client ID for the Discord bot.',
+
+            ],
+            [
+                'key' => 'SETTINGS::DISCORD:CLIENT_SECRET',
+                'value' => $this->getNewValue('client_secret'),
+                'type' => 'string',
+                'description' => 'The client secret for the Discord bot.',
+            ],
+            [
+                'key' => 'SETTINGS::DISCORD:GUILD_ID',
+                'value' => $this->getNewValue('guild_id'),
+                'type' => 'string',
+                'description' => 'The guild ID for the Discord bot.',
+            ],
+            [
+                'key' => 'SETTINGS::DISCORD:INVITE_URL',
+                'value' => $this->getNewValue('invite_url'),
+                'type' => 'string',
+                'description' => 'The invite URL for the Discord bot.',
+            ],
+            [
+                'key' => 'SETTINGS::DISCORD:ROLE_ID',
+                'value' => $this->getNewValue('role_id'),
+                'type' => 'string',
+                'description' => 'The role ID for the Discord bot.',
+            ]
+        ]);
+
+        $this->migrator->delete('discord.bot_token');
+        $this->migrator->delete('discord.client_id');
+        $this->migrator->delete('discord.client_secret');
+        $this->migrator->delete('discord.guild_id');
+        $this->migrator->delete('discord.invite_url');
+        $this->migrator->delete('discord.role_id');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'discord'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 104 - 0
database/settings/2023_02_01_182108_create_locale_settings.php

@@ -0,0 +1,104 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateLocaleSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('locale.available', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:AVAILABLE') : '');
+        $this->migrator->add('locale.clients_can_change', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:CLIENTS_CAN_CHANGE') : true);
+        $this->migrator->add('locale.datatables', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DATATABLES') : 'en-gb');
+        $this->migrator->add('locale.default', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DEFAULT') : 'en');
+        $this->migrator->add('locale.dynamic', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DYNAMIC') : false);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::LOCALE:AVAILABLE',
+                'value' => $this->getNewValue('available'),
+                'type' => 'string',
+                'description' => 'The available locales.',
+            ],
+            [
+                'key' => 'SETTINGS::LOCALE:CLIENTS_CAN_CHANGE',
+                'value' => $this->getNewValue('clients_can_change'),
+                'type' => 'boolean',
+                'description' => 'If clients can change their locale.',
+            ],
+            [
+                'key' => 'SETTINGS::LOCALE:DATATABLES',
+                'value' => $this->getNewValue('datatables'),
+                'type' => 'string',
+                'description' => 'The locale for datatables.',
+            ],
+            [
+                'key' => 'SETTINGS::LOCALE:DEFAULT',
+                'value' => $this->getNewValue('default'),
+                'type' => 'string',
+                'description' => 'The default locale.',
+            ],
+            [
+                'key' => 'SETTINGS::LOCALE:DYNAMIC',
+                'value' => $this->getNewValue('dynamic'),
+                'type' => 'boolean',
+                'description' => 'If the locale should be dynamic.',
+            ],
+        ]);
+
+        $this->migrator->delete('locale.available');
+        $this->migrator->delete('locale.clients_can_change');
+        $this->migrator->delete('locale.datatables');
+        $this->migrator->delete('locale.default');
+        $this->migrator->delete('locale.dynamic');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'locale'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

+ 112 - 0
database/settings/2023_02_01_182135_create_referral_settings.php

@@ -0,0 +1,112 @@
+<?php
+
+use Spatie\LaravelSettings\Migrations\SettingsMigration;
+use Illuminate\Support\Facades\DB;
+
+class CreateReferralSettings extends SettingsMigration
+{
+    public function up(): void
+    {
+        $table_exists = DB::table('settings_old')->exists();
+
+        // Get the user-set configuration values from the old table.
+        $this->migrator->add('referral.allowed', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALLOWED') : 'client');
+        $this->migrator->add('referral.always_give_commission', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION') : false);
+        $this->migrator->add('referral.enabled', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ENABLED') : false);
+        $this->migrator->add('referral.reward', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::REWARD') : 100);
+        $this->migrator->add('referral.mode', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:MODE') : 'sign-up');
+        $this->migrator->add('referral.percentage', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:PERCENTAGE') : 100);
+    }
+
+    public function down(): void
+    {
+        DB::table('settings_old')->insert([
+            [
+                'key' => 'SETTINGS::REFERRAL::ALLOWED',
+                'value' => $this->getNewValue('allowed'),
+                'type' => 'string',
+                'description' => 'The allowed referral types.',
+            ],
+            [
+                'key' => 'SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION',
+                'value' => $this->getNewValue('always_give_commission'),
+                'type' => 'boolean',
+                'description' => 'Whether to always give commission to the referrer.',
+            ],
+            [
+                'key' => 'SETTINGS::REFERRAL::ENABLED',
+                'value' => $this->getNewValue('enabled'),
+                'type' => 'boolean',
+                'description' => 'Whether to enable the referral system.',
+            ],
+            [
+                'key' => 'SETTINGS::REFERRAL::REWARD',
+                'value' => $this->getNewValue('reward'),
+                'type' => 'integer',
+                'description' => 'The reward for the referral.',
+            ],
+            [
+                'key' => 'SETTINGS::REFERRAL:MODE',
+                'value' => $this->getNewValue('mode'),
+                'type' => 'string',
+                'description' => 'The referral mode.',
+            ],
+            [
+                'key' => 'SETTINGS::REFERRAL:PERCENTAGE',
+                'value' => $this->getNewValue('percentage'),
+                'type' => 'integer',
+                'description' => 'The referral percentage.',
+            ],
+        ]);
+
+        $this->migrator->delete('referral.allowed');
+        $this->migrator->delete('referral.always_give_commission');
+        $this->migrator->delete('referral.enabled');
+        $this->migrator->delete('referral.reward');
+        $this->migrator->delete('referral.mode');
+        $this->migrator->delete('referral.percentage');
+    }
+
+    public function getNewValue(string $name)
+    {
+        $new_value = DB::table('settings')->where([['group', '=', 'referral'], ['name', '=', $name]])->get(['payload'])->first();
+
+        // Some keys returns '""' as a value.
+        if ($new_value->payload === '""') {
+            return null;
+        }
+
+        // remove the quotes from the string
+        if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') {
+            return substr($new_value->payload, 1, -1);
+        }
+
+        return $new_value->payload;
+    }
+
+    public function getOldValue(string $key)
+    {
+        // Always get the first value of the key.
+        $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first();
+
+        // Handle the old values to return without it being a string in all cases.
+        if ($old_value->type === "string" || $old_value->type === "text") {
+            if (is_null($old_value->value)) {
+                return '';
+            }
+
+            // Some values have the type string, but their values are boolean.
+            if ($old_value->value === "false" || $old_value->value === "true") {
+                return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+            }
+
+            return $old_value->value;
+        }
+
+        if ($old_value->type === "boolean") {
+            return filter_var($old_value->value, FILTER_VALIDATE_BOOL);
+        }
+
+        return filter_var($old_value->value, FILTER_VALIDATE_INT);
+    }
+}

部分文件因为文件数量过多而无法显示