91
common/Settings/DotEnvEditor.php
Executable file
91
common/Settings/DotEnvEditor.php
Executable file
@@ -0,0 +1,91 @@
|
||||
<?php namespace Common\Settings;
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use Dotenv\Repository\RepositoryBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DotEnvEditor
|
||||
{
|
||||
public function __construct(protected string $fileName = '.env')
|
||||
{
|
||||
}
|
||||
|
||||
public function load(): array
|
||||
{
|
||||
$dotEnv = Dotenv::create(
|
||||
RepositoryBuilder::createWithNoAdapters()->make(),
|
||||
[base_path()],
|
||||
$this->fileName,
|
||||
);
|
||||
$values = $dotEnv->load();
|
||||
$lowercaseValues = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (strtolower($value) === 'null') {
|
||||
$lowercaseValues[strtolower($key)] = null;
|
||||
} elseif (strtolower($value) === 'false') {
|
||||
$lowercaseValues[strtolower($key)] = false;
|
||||
} elseif (strtolower($value) === 'true') {
|
||||
$lowercaseValues[strtolower($key)] = true;
|
||||
} elseif (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) {
|
||||
$lowercaseValues[strtolower($key)] = $matches[2];
|
||||
} else {
|
||||
$lowercaseValues[strtolower($key)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $lowercaseValues;
|
||||
}
|
||||
|
||||
public function write(array|Collection $values = []): void
|
||||
{
|
||||
$content = file_get_contents(base_path($this->fileName));
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$value = $this->formatValue($value);
|
||||
$key = strtoupper($key);
|
||||
|
||||
if (Str::contains($content, $key . '=')) {
|
||||
preg_match("/($key=)(.*?)(\n|\Z)/msi", $content, $matches);
|
||||
$content = str_replace(
|
||||
$matches[1] . $matches[2],
|
||||
$matches[1] . $value,
|
||||
$content,
|
||||
);
|
||||
} else {
|
||||
$content .= "\n$key=$value";
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents(base_path($this->fileName), $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format specified value to be compatible with .env file
|
||||
*/
|
||||
private function formatValue(mixed $value = null): string
|
||||
{
|
||||
if ($value === 0 || $value === false) {
|
||||
$value = 'false';
|
||||
}
|
||||
if ($value === 1 || $value === true) {
|
||||
$value = 'true';
|
||||
}
|
||||
if (!$value) {
|
||||
$value = 'null';
|
||||
}
|
||||
$value = trim($value);
|
||||
|
||||
// wrap string in quotes, if it contains whitespace or special characters
|
||||
if (preg_match('/\s/', $value) || Str::contains($value, '#')) {
|
||||
//replace double quotes with single quotes
|
||||
$value = str_replace('"', "'", $value);
|
||||
|
||||
//wrap string in quotes
|
||||
$value = '"' . $value . '"';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
26
common/Settings/Events/SettingsSaved.php
Executable file
26
common/Settings/Events/SettingsSaved.php
Executable file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Events;
|
||||
|
||||
class SettingsSaved
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $dbSettings;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $envSettings;
|
||||
|
||||
/**
|
||||
* @param array $dbSettings
|
||||
* @param array $envSettings
|
||||
*/
|
||||
public function __construct($dbSettings, $envSettings)
|
||||
{
|
||||
$this->dbSettings = $dbSettings;
|
||||
$this->envSettings = $envSettings;
|
||||
}
|
||||
}
|
||||
47
common/Settings/Mail/ConnectGmailAccountController.php
Executable file
47
common/Settings/Mail/ConnectGmailAccountController.php
Executable file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Mail;
|
||||
|
||||
use Common\Auth\Oauth;
|
||||
use Common\Core\BaseController;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
class ConnectGmailAccountController extends BaseController
|
||||
{
|
||||
public function connectGmail()
|
||||
{
|
||||
Session::flash(
|
||||
Oauth::OAUTH_CALLBACK_HANDLER_KEY,
|
||||
HandleConnectGmailOauthCallback::class,
|
||||
);
|
||||
|
||||
$driver = Socialite::driver('google')
|
||||
->scopes([
|
||||
'https://www.googleapis.com/auth/gmail.readonly',
|
||||
'https://www.googleapis.com/auth/gmail.send',
|
||||
])
|
||||
->with([
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'consent select_account',
|
||||
]);
|
||||
|
||||
return $driver->redirect();
|
||||
}
|
||||
|
||||
public static function getConnectedEmail(): ?string
|
||||
{
|
||||
if (!class_exists(GmailClient::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode(File::get(GmailClient::tokenPath()), true);
|
||||
return $data['email'] ?? null;
|
||||
} catch (FileNotFoundException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
common/Settings/Mail/GmailApiMailTransport.php
Executable file
19
common/Settings/Mail/GmailApiMailTransport.php
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Mail;
|
||||
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mailer\Transport\AbstractTransport;
|
||||
|
||||
class GmailApiMailTransport extends AbstractTransport
|
||||
{
|
||||
public function doSend(SentMessage $message): void
|
||||
{
|
||||
(new GmailClient())->sendEmail($message->toString());
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'gmailApi';
|
||||
}
|
||||
}
|
||||
111
common/Settings/Mail/GmailClient.php
Executable file
111
common/Settings/Mail/GmailClient.php
Executable file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Mail;
|
||||
|
||||
use File;
|
||||
use Google\Service\Gmail\Message;
|
||||
use Google\Service\Gmail\WatchRequest;
|
||||
use Google\Service\Gmail\WatchResponse;
|
||||
use Google_Client;
|
||||
use Google_Service_Gmail;
|
||||
|
||||
class GmailClient
|
||||
{
|
||||
private Google_Service_Gmail $gmail;
|
||||
|
||||
private Google_Client $googleClient;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->buildGoogleClient();
|
||||
}
|
||||
|
||||
public static function tokenPath(): string
|
||||
{
|
||||
return storage_path('app/tokens/gmail.json');
|
||||
}
|
||||
|
||||
public static function tokenExists(): bool
|
||||
{
|
||||
return file_exists(self::tokenPath());
|
||||
}
|
||||
|
||||
public function sendEmail(string $rawContent): Message
|
||||
{
|
||||
$encoded = strtr(base64_encode($rawContent), ['+' => '-', '/' => '_']);
|
||||
$msg = new Message();
|
||||
$msg->setRaw($encoded);
|
||||
return $this->gmail->users_messages->send('me', $msg);
|
||||
}
|
||||
|
||||
public function listHistory(int $historyId): array
|
||||
{
|
||||
$response = $this->gmail->users_history->listUsersHistory('me', [
|
||||
'startHistoryId' => $historyId,
|
||||
]);
|
||||
|
||||
$messageIds = collect($response['history'])
|
||||
->map(function ($history) {
|
||||
$msg = $history['messagesAdded'][0]['message'] ?? null;
|
||||
$labels = $msg['labelIds'] ?? [];
|
||||
|
||||
if ($msg && !in_array('SENT', $labels)) {
|
||||
return $msg['id'];
|
||||
}
|
||||
})
|
||||
->filter();
|
||||
|
||||
if ($messageIds->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->googleClient->setUseBatch(true);
|
||||
$batch = $this->gmail->createBatch();
|
||||
|
||||
$messageIds->each(function ($msgId) use ($batch) {
|
||||
$request = $this->gmail->users_messages->get('me', $msgId, [
|
||||
'format' => 'raw',
|
||||
]);
|
||||
$batch->add($request);
|
||||
});
|
||||
|
||||
$this->googleClient->setUseBatch(false);
|
||||
|
||||
return array_values($batch->execute());
|
||||
}
|
||||
|
||||
public function watch(): WatchResponse
|
||||
{
|
||||
$payload = new WatchRequest();
|
||||
$payload->topicName = settings('incoming_email.gmail.topicName');
|
||||
$payload->labelIds = ['UNREAD'];
|
||||
$payload->labelFilterAction = 'include';
|
||||
return $this->gmail->users->watch('me', $payload);
|
||||
}
|
||||
|
||||
private function buildGoogleClient(): void
|
||||
{
|
||||
$this->googleClient = new Google_Client();
|
||||
$this->googleClient->setClientId(config('services.google.client_id'));
|
||||
$this->googleClient->setClientSecret(
|
||||
config('services.google.client_secret'),
|
||||
);
|
||||
|
||||
if (self::tokenExists()) {
|
||||
$tokenJson = file_get_contents(self::tokenPath());
|
||||
$accessToken = json_decode($tokenJson, true);
|
||||
$this->googleClient->setAccessToken($accessToken);
|
||||
}
|
||||
|
||||
if ($this->googleClient->isAccessTokenExpired()) {
|
||||
$newToken = $this->googleClient->fetchAccessTokenWithRefreshToken(
|
||||
$this->googleClient->getRefreshToken(),
|
||||
);
|
||||
$oldToken = json_decode(File::get(self::tokenPath()), true);
|
||||
$mergedToken = array_merge($oldToken, $newToken);
|
||||
File::put(self::tokenPath(), json_encode($mergedToken));
|
||||
}
|
||||
|
||||
$this->gmail = new Google_Service_Gmail($this->googleClient);
|
||||
}
|
||||
}
|
||||
36
common/Settings/Mail/HandleConnectGmailOauthCallback.php
Executable file
36
common/Settings/Mail/HandleConnectGmailOauthCallback.php
Executable file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Mail;
|
||||
|
||||
use Common\Auth\Oauth;
|
||||
use Illuminate\Contracts\View\View as ViewContract;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
class HandleConnectGmailOauthCallback
|
||||
{
|
||||
public function execute(string $provider): ViewContract
|
||||
{
|
||||
$profile = Socialite::with('google')->user();
|
||||
|
||||
File::ensureDirectoryExists(dirname(GmailClient::tokenPath()));
|
||||
File::put(
|
||||
GmailClient::tokenPath(),
|
||||
json_encode([
|
||||
'access_token' => $profile->token,
|
||||
'refresh_token' => $profile->refreshToken,
|
||||
'created' => now()->timestamp,
|
||||
'expires_in' => $profile->expiresIn,
|
||||
'email' => $profile->email,
|
||||
]),
|
||||
);
|
||||
|
||||
if (settings('incoming_email.gmail.enabled')) {
|
||||
(new GmailClient())->watch();
|
||||
}
|
||||
|
||||
return (new Oauth())->getPopupResponse('SUCCESS', [
|
||||
'profile' => $profile,
|
||||
]);
|
||||
}
|
||||
}
|
||||
75
common/Settings/Setting.php
Executable file
75
common/Settings/Setting.php
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php namespace Common\Settings;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
protected $table = 'settings';
|
||||
|
||||
protected $fillable = ['name', 'value'];
|
||||
|
||||
protected $casts = ['private' => 'bool'];
|
||||
|
||||
protected function value(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function ($value) {
|
||||
if (
|
||||
in_array($this->attributes['name'], Settings::$secretKeys)
|
||||
) {
|
||||
try {
|
||||
$value = decrypt($value);
|
||||
} catch (Exception $e) {
|
||||
$value = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($this->attributes['name'], Settings::$jsonKeys)) {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
|
||||
if ($value === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($value === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ctype_digit($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
set: function ($value) {
|
||||
$value = !is_null($value) ? $value : '';
|
||||
|
||||
if (
|
||||
in_array($this->attributes['name'], Settings::$jsonKeys) &&
|
||||
!is_string($value)
|
||||
) {
|
||||
$value = json_encode($value);
|
||||
}
|
||||
|
||||
if ($value === true) {
|
||||
$value = 'true';
|
||||
} elseif ($value === false) {
|
||||
$value = 'false';
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
|
||||
if (
|
||||
in_array($this->attributes['name'], Settings::$secretKeys)
|
||||
) {
|
||||
$value = encrypt($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
221
common/Settings/Settings.php
Executable file
221
common/Settings/Settings.php
Executable file
@@ -0,0 +1,221 @@
|
||||
<?php namespace Common\Settings;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Settings
|
||||
{
|
||||
protected Collection $all;
|
||||
|
||||
/**
|
||||
* Laravel config values that should be included with settings.
|
||||
* (display name for client => laravel config key)
|
||||
*/
|
||||
protected array $configKeys = [
|
||||
'billing.stripe_public_key' => 'services.stripe.key',
|
||||
'billing.paypal.public_key' => 'services.paypal.client_id',
|
||||
'site.demo' => 'common.site.demo',
|
||||
'logging.sentry_public' => 'sentry.dsn',
|
||||
'i18n.default_localization' => 'app.locale',
|
||||
'billing.integrated' => 'common.site.billing_integrated',
|
||||
'workspaces.integrated' => 'common.site.workspaces_integrated',
|
||||
'notifications.integrated' => 'common.site.notifications_integrated',
|
||||
'notif.subs.integrated' => 'common.site.notif_subs_integrated',
|
||||
'api.integrated' => 'common.site.api_integrated',
|
||||
'branding.site_name' => 'app.name',
|
||||
'realtime.pusher_cluster' =>
|
||||
'broadcasting.connections.pusher.options.cluster',
|
||||
'realtime.pusher_key' => 'broadcasting.connections.pusher.key',
|
||||
'site.hide_docs_buttons' => 'common.site.hide_docs_buttons',
|
||||
'site.has_mobile_app' => 'common.site.has_mobile_app',
|
||||
'uploads.public_driver' => 'common.site.public_disk_driver',
|
||||
'uploads.uploads_driver' => 'common.site.uploads_disk_driver',
|
||||
'uploads.disable_tus' => 'common.site.uploads_disable_tus',
|
||||
];
|
||||
|
||||
/**
|
||||
* Settings that are json encoded in database.
|
||||
*/
|
||||
public static array $jsonKeys = [
|
||||
'menus',
|
||||
'homepage.appearance',
|
||||
'uploads.allowed_extensions',
|
||||
'uploads.blocked_extensions',
|
||||
'cookie_notice.button',
|
||||
'registration.policies',
|
||||
'artistPage.tabs',
|
||||
'landing',
|
||||
'hc.newTicket.appearance',
|
||||
'incoming_email',
|
||||
'title_page.sections',
|
||||
'streaming.qualities',
|
||||
'builder.template_categories',
|
||||
'publish.default_credentials',
|
||||
];
|
||||
|
||||
public static array $secretKeys = [
|
||||
'recaptcha.secret_key',
|
||||
'google_safe_browsing_key',
|
||||
'incoming_email',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
public function all(bool $includeSecret = false): array
|
||||
{
|
||||
$all = $this->all;
|
||||
|
||||
// filter out secret (server-only) settings
|
||||
if (!$includeSecret) {
|
||||
$all = $all->filter(function ($value, $key) use ($includeSecret) {
|
||||
return !in_array($key, self::$secretKeys);
|
||||
});
|
||||
}
|
||||
|
||||
return $all->toArray();
|
||||
}
|
||||
|
||||
public function get(string|int $key, mixed $default = null): mixed
|
||||
{
|
||||
$value = Arr::get($this->all, $key) ?? $default;
|
||||
|
||||
return is_string($value) ? trim($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random setting value from fields that
|
||||
* have multiple values separated by newline.
|
||||
*/
|
||||
public function getRandom(string $key, ?string $default = null): mixed
|
||||
{
|
||||
$key = $this->get($key, $default);
|
||||
$parts = explode("\n", $key);
|
||||
return $parts[array_rand($parts)];
|
||||
}
|
||||
|
||||
public function getMenu(string $name)
|
||||
{
|
||||
return Arr::first(
|
||||
$this->get('menus'),
|
||||
fn($menu) => strtolower($menu['name']) === strtolower($name),
|
||||
);
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return !is_null(Arr::get($this->all, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set single setting. Does not persist in database.
|
||||
*/
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$this->all[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist specified settings in database.
|
||||
*/
|
||||
public function save(array $settings): void
|
||||
{
|
||||
$settings = $this->flatten($settings);
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$setting = Setting::firstOrNew(['name' => $key]);
|
||||
$setting->value = $value;
|
||||
$setting->save();
|
||||
$this->set($key, $setting->value);
|
||||
}
|
||||
|
||||
Cache::forget('settings.public');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings parsed from dot notation to assoc array. Also decodes JSON values.
|
||||
*/
|
||||
public function getUnflattened(
|
||||
bool $includeSecret = false,
|
||||
array $settings = null,
|
||||
): array {
|
||||
if (!$settings) {
|
||||
$settings = $this->all($includeSecret);
|
||||
}
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
if (in_array($key, self::$jsonKeys) && is_string($value)) {
|
||||
$settings[$key] = json_decode($value, true);
|
||||
}
|
||||
}
|
||||
|
||||
$dot = dot($settings, true);
|
||||
|
||||
return $dot->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten specified assoc array into dot array. (['billing.enable' => true])
|
||||
*/
|
||||
public function flatten(array $settings): array
|
||||
{
|
||||
// this will find all json keys, encode them and remove decoded version from original array
|
||||
foreach (Settings::$jsonKeys as $key) {
|
||||
if (Arr::has($settings, $key)) {
|
||||
$value = Arr::pull($settings, $key);
|
||||
$settings[$key] = is_array($value)
|
||||
? json_encode($value)
|
||||
: $value;
|
||||
}
|
||||
}
|
||||
|
||||
$dot = dot($settings);
|
||||
|
||||
// remove keys that were added from config files and are not stored in database
|
||||
$dotArray = $dot->delete(array_keys($this->configKeys))->flatten();
|
||||
|
||||
// dot package leaves empty array as value for root element when deleting
|
||||
foreach ($dotArray as $key => $value) {
|
||||
if (is_array($value) && empty($value)) {
|
||||
unset($dotArray[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $dotArray;
|
||||
}
|
||||
|
||||
protected function find(string $key)
|
||||
{
|
||||
return Arr::get($this->all, $key);
|
||||
}
|
||||
|
||||
protected function loadSettings(): void
|
||||
{
|
||||
$value = Cache::get('settings.public');
|
||||
$this->all = collect();
|
||||
|
||||
if ($value && count($value) > 0) {
|
||||
$this->all = $value;
|
||||
} else {
|
||||
try {
|
||||
$value = Setting::select('name', 'value')
|
||||
->get()
|
||||
->pluck('value', 'name');
|
||||
if (!$value->isEmpty()) {
|
||||
$this->all = $value;
|
||||
Cache::set('settings.public', $value, now()->addDay());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
// add config keys that should be included
|
||||
foreach ($this->configKeys as $clientKey => $configKey) {
|
||||
$this->set($clientKey, config()->get($configKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
129
common/Settings/SettingsController.php
Executable file
129
common/Settings/SettingsController.php
Executable file
@@ -0,0 +1,129 @@
|
||||
<?php namespace Common\Settings;
|
||||
|
||||
use Common\Core\AppUrl;
|
||||
use Common\Core\BaseController;
|
||||
use Common\Settings\Events\SettingsSaved;
|
||||
use Common\Settings\Mail\ConnectGmailAccountController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class SettingsController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
protected Request $request,
|
||||
protected Settings $settings,
|
||||
) {
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('index', Setting::class);
|
||||
|
||||
$envSettings = (new DotEnvEditor())->load();
|
||||
$envSettings['newAppUrl'] = app(AppUrl::class)->newAppUrl;
|
||||
$envSettings[
|
||||
'connectedGmailAccount'
|
||||
] = ConnectGmailAccountController::getConnectedEmail();
|
||||
|
||||
// inputs on frontend can't be bound to null
|
||||
foreach ($envSettings as $key => $value) {
|
||||
if ($value === null) {
|
||||
$envSettings[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'server' => $envSettings,
|
||||
'client' => $this->settings->getUnflattened(true),
|
||||
];
|
||||
}
|
||||
|
||||
public function persist()
|
||||
{
|
||||
$this->authorize('update', Setting::class);
|
||||
|
||||
$clientSettings = $this->cleanValues($this->request->get('client'));
|
||||
$serverSettings = $this->cleanValues($this->request->get('server'));
|
||||
|
||||
// need to handle files before validating
|
||||
$this->handleFiles();
|
||||
|
||||
if (
|
||||
$errResponse = $this->validateSettings(
|
||||
$serverSettings,
|
||||
$clientSettings,
|
||||
)
|
||||
) {
|
||||
return $errResponse;
|
||||
}
|
||||
|
||||
if ($serverSettings) {
|
||||
(new DotEnvEditor())->write($serverSettings);
|
||||
}
|
||||
|
||||
if ($clientSettings) {
|
||||
$this->settings->save($clientSettings);
|
||||
}
|
||||
|
||||
Cache::flush();
|
||||
|
||||
event(new SettingsSaved($clientSettings, $serverSettings));
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
private function cleanValues(string|null $config): array
|
||||
{
|
||||
if (!$config) {
|
||||
return [];
|
||||
}
|
||||
$config = json_decode($config, true);
|
||||
foreach ($config as $key => $value) {
|
||||
$config[$key] = is_string($value) ? trim($value) : $value;
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function handleFiles()
|
||||
{
|
||||
$files = $this->request->allFiles();
|
||||
|
||||
// store google analytics certificate file
|
||||
if ($certificateFile = Arr::get($files, 'certificate')) {
|
||||
File::put(
|
||||
storage_path('laravel-analytics/certificate.json'),
|
||||
file_get_contents($certificateFile),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSettings(
|
||||
array $serverSettings,
|
||||
array $clientSettings,
|
||||
) {
|
||||
// flatten "client" and "server" arrays into single array
|
||||
$values = array_merge(
|
||||
$serverSettings ?: [],
|
||||
$clientSettings ?: [],
|
||||
$this->request->allFiles(),
|
||||
);
|
||||
$keys = array_keys($values);
|
||||
$validators = config('common.setting-validators');
|
||||
|
||||
foreach ($validators as $validator) {
|
||||
if (empty(array_intersect($validator::KEYS, $keys))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($messages = app($validator)->fails($values)) {
|
||||
return $this->error(
|
||||
__('Could not persist settings.'),
|
||||
$messages,
|
||||
);
|
||||
}
|
||||
// catch and display any generic error that might occur
|
||||
}
|
||||
}
|
||||
}
|
||||
38
common/Settings/Uploading/DropboxRefreshTokenController.php
Executable file
38
common/Settings/Uploading/DropboxRefreshTokenController.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Uploading;
|
||||
|
||||
use Common\Core\BaseController;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class DropboxRefreshTokenController extends BaseController
|
||||
{
|
||||
public function generate()
|
||||
{
|
||||
$payload = $this->validate(request(), [
|
||||
'app_key' => 'required|string',
|
||||
'app_secret' => 'required|string',
|
||||
'access_code' => 'required|string',
|
||||
]);
|
||||
|
||||
$response = Http::asForm()->post(
|
||||
"https://{$payload['app_key']}:{$payload['app_secret']}@api.dropbox.com/oauth2/token",
|
||||
[
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $payload['access_code'],
|
||||
],
|
||||
);
|
||||
|
||||
if (isset($response['refresh_token'])) {
|
||||
app(DotEnvEditor::class)->write([
|
||||
'STORAGE_DROPBOX_REFRESH_TOKEN' => $response['refresh_token'],
|
||||
]);
|
||||
return $this->success([
|
||||
'refreshToken' => $response['refresh_token'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($response['error_description'] ?? null);
|
||||
}
|
||||
}
|
||||
39
common/Settings/Validators/AnalyticsCredentialsValidator.php
Executable file
39
common/Settings/Validators/AnalyticsCredentialsValidator.php
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Admin\Analytics\Actions\BuildGoogleAnalyticsReport;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class AnalyticsCredentialsValidator
|
||||
{
|
||||
const KEYS = [
|
||||
'analytics_property_id',
|
||||
'analytics.tracking_code',
|
||||
'certificate',
|
||||
];
|
||||
|
||||
public function fails($settings): array|false
|
||||
{
|
||||
$this->setConfigDynamically($settings);
|
||||
|
||||
try {
|
||||
app(BuildGoogleAnalyticsReport::class)->execute([]);
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'analytics_group' => "Invalid credentials: {$e->getMessage()}",
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function setConfigDynamically(array $settings): void
|
||||
{
|
||||
if ($propertyId = Arr::get($settings, 'analytics_property_id')) {
|
||||
Config::set('services.google.analytics_property_id', $propertyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
common/Settings/Validators/CacheConfigValidator.php
Executable file
64
common/Settings/Validators/CacheConfigValidator.php
Executable file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class CacheConfigValidator
|
||||
{
|
||||
const KEYS = ['cache_driver'];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
$this->setConfigDynamically($settings);
|
||||
|
||||
try {
|
||||
$driverName = Arr::get(
|
||||
$settings,
|
||||
'cache_driver',
|
||||
config('cache.default'),
|
||||
);
|
||||
$driver = Cache::driver($driverName);
|
||||
$driver->put('foo', 'bar', 1);
|
||||
if ($driver->get('foo') !== 'bar') {
|
||||
return $this->getDefaultErrorMessage();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
} catch (Throwable $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
app(DotEnvEditor::class)->write(
|
||||
Arr::except($settings, ['cache_driver']),
|
||||
);
|
||||
}
|
||||
|
||||
private function getErrorMessage($e): array
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (Str::contains($message, 'apc_fetch')) {
|
||||
return ['cache_group' => "Could not enable APC. $message"];
|
||||
} elseif (Str::contains($message, 'Memcached')) {
|
||||
return ['cache_group' => "Could not enable Memcached. $message"];
|
||||
} elseif (Str::contains($message, 'Connection refused')) {
|
||||
return ['cache_group' => 'Could not connect to redis server.'];
|
||||
} else {
|
||||
return $this->getDefaultErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultErrorMessage(): array
|
||||
{
|
||||
return ['cache_group' => 'Could not enable this cache method.'];
|
||||
}
|
||||
}
|
||||
84
common/Settings/Validators/FacebookLoginValidator.php
Executable file
84
common/Settings/Validators/FacebookLoginValidator.php
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Auth\Oauth;
|
||||
use Common\Core\HttpClient;
|
||||
use Config;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Socialite;
|
||||
|
||||
class FacebookLoginValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = ['facebook_id', 'facebook_secret'];
|
||||
|
||||
/**
|
||||
* @var Oauth
|
||||
*/
|
||||
private $oauth;
|
||||
|
||||
/**
|
||||
* @var HttpClient
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
public function __construct(Oauth $oauth)
|
||||
{
|
||||
$this->oauth = $oauth;
|
||||
$this->httpClient = new HttpClient([
|
||||
'exceptions' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
try {
|
||||
Socialite::driver('facebook')->getAccessTokenResponse('foo-bar');
|
||||
} catch (ClientException $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
} catch (ServerException $e) {
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
if ($facebookId = Arr::get($settings, 'facebook_id')) {
|
||||
Config::set('services.facebook.client_id', $facebookId);
|
||||
}
|
||||
|
||||
if ($facebookSecret = Arr::get($settings, 'facebook_secret')) {
|
||||
Config::set('services.facebook.client_secret', $facebookSecret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientException $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage(ClientException $e)
|
||||
{
|
||||
$errResponse = json_decode($e->getResponse()->getBody()->getContents(), true);
|
||||
$code = Arr::get($errResponse, 'error.code');
|
||||
|
||||
// there were no credentials related errors, we can assume validation was successful
|
||||
if ($code === 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($code === 191) {
|
||||
return ['facebook_group' => 'Site url is not present in "Valid OAuth Redirect URIs" field on your facebook app.'];
|
||||
}
|
||||
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
|
||||
private function getDefaultError()
|
||||
{
|
||||
return ['facebook_group' => 'These facebook credentials are not valid.'];
|
||||
}
|
||||
}
|
||||
85
common/Settings/Validators/GoogleLoginValidator.php
Executable file
85
common/Settings/Validators/GoogleLoginValidator.php
Executable file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Auth\Oauth;
|
||||
use Common\Core\HttpClient;
|
||||
use Config;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Socialite;
|
||||
|
||||
class GoogleLoginValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = ['google_id', 'google_secret'];
|
||||
|
||||
/**
|
||||
* @var Oauth
|
||||
*/
|
||||
private $oauth;
|
||||
|
||||
/**
|
||||
* @var HttpClient
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
public function __construct(Oauth $oauth)
|
||||
{
|
||||
$this->oauth = $oauth;
|
||||
$this->httpClient = new HttpClient([
|
||||
'exceptions' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
try {
|
||||
Socialite::driver('google')->getAccessTokenResponse('foo-bar');
|
||||
} catch (ClientException $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
if ($googleId = Arr::get($settings, 'google_id')) {
|
||||
Config::set('services.google.client_id', $googleId);
|
||||
}
|
||||
|
||||
if ($googleSecret = Arr::get($settings, 'google_secret')) {
|
||||
Config::set('services.google.client_secret', $googleSecret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientException $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage(ClientException $e)
|
||||
{
|
||||
$errResponse = json_decode(
|
||||
$e
|
||||
->getResponse()
|
||||
->getBody()
|
||||
->getContents(),
|
||||
true,
|
||||
);
|
||||
|
||||
// there were no credentials related errors, we can assume validation was successful
|
||||
if (
|
||||
Arr::get($errResponse, 'error_description') ===
|
||||
'Malformed auth code.'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$msg1 = Arr::get($errResponse, 'error.errors.0.message', '');
|
||||
$msg2 = Arr::get($errResponse, 'error_description', '');
|
||||
$message = strtolower($msg1 ?: $msg2);
|
||||
return [
|
||||
'google_group' => "Could not validate these credentials: $message",
|
||||
];
|
||||
}
|
||||
}
|
||||
29
common/Settings/Validators/LoggingCredentialsValidator.php
Executable file
29
common/Settings/Validators/LoggingCredentialsValidator.php
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Exception;
|
||||
use Sentry\Dsn;
|
||||
|
||||
class LoggingCredentialsValidator
|
||||
{
|
||||
const KEYS = ['sentry_dsn'];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
try {
|
||||
Dsn::createFromString($settings['sentry_dsn']);
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($e)
|
||||
{
|
||||
return ['logging_group' => 'This sentry DSN is not valid.'];
|
||||
}
|
||||
}
|
||||
33
common/Settings/Validators/MailCredentials/MailCredentialsMailable.php
Executable file
33
common/Settings/Validators/MailCredentials/MailCredentialsMailable.php
Executable file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators\MailCredentials;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class MailCredentialsMailable extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->markdown('common::emails.mail-validation')
|
||||
->subject(config('app.name') . ' Mail Set Up Successfully!');
|
||||
}
|
||||
}
|
||||
143
common/Settings/Validators/MailCredentials/OutgoingMailCredentialsValidator.php
Executable file
143
common/Settings/Validators/MailCredentials/OutgoingMailCredentialsValidator.php
Executable file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators\MailCredentials;
|
||||
|
||||
use Arr;
|
||||
use Auth;
|
||||
use Aws\Ses\Exception\SesException;
|
||||
use Common\CommonServiceProvider;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Common\Settings\Validators\SettingsValidator;
|
||||
use Config;
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Mail\MailServiceProvider;
|
||||
use Mail;
|
||||
use Str;
|
||||
|
||||
class OutgoingMailCredentialsValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = [
|
||||
'mail_driver',
|
||||
'mail_host',
|
||||
'mail_username',
|
||||
'mail_password',
|
||||
'mail_port',
|
||||
'mail_encryption', // SMTP
|
||||
'mailgun_domain',
|
||||
'mailgun_secret', // Mailgun
|
||||
'ses_key',
|
||||
'ses_secret', // Amazon SES
|
||||
'sparkpost_secret', // Sparkpost
|
||||
];
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
try {
|
||||
Mail::to(Auth::user()->email)->send(new MailCredentialsMailable());
|
||||
} catch (Exception $e) {
|
||||
app(DotEnvEditor::class)->write(['MAIL_SETUP' => false]);
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
|
||||
app(DotEnvEditor::class)->write(['MAIL_SETUP' => true]);
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
foreach ($settings as $key => $value) {
|
||||
//mail_host => mail.host
|
||||
$key = str_replace('_', '.', $key);
|
||||
|
||||
// "mail.*" credentials go into "mail.php" config
|
||||
// file, other credentials go into "services.php"
|
||||
if ($key === 'mail.driver') {
|
||||
$key = 'mail.default';
|
||||
} elseif ($key === 'mail_from_address') {
|
||||
$key = 'mail.from.address';
|
||||
} elseif (!Str::startsWith($key, 'mail.')) {
|
||||
$key = "services.$key";
|
||||
} else {
|
||||
$key = str_replace('mail.', 'mail.mailers.smtp.', $key);
|
||||
}
|
||||
|
||||
Config::set($key, $value);
|
||||
}
|
||||
|
||||
// make sure laravel uses newly set config
|
||||
(new MailServiceProvider(app()))->register();
|
||||
(new CommonServiceProvider(app()))->registerCustomMailDrivers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception|ClientException $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($e)
|
||||
{
|
||||
$message = null;
|
||||
if (config('mail.driver') === 'smtp') {
|
||||
$message = $this->getSmtpMessage($e);
|
||||
} elseif (config('mail.driver') === 'mailgun') {
|
||||
$message = $this->getMailgunMessage($e);
|
||||
} elseif (config('mail.driver') === 'ses') {
|
||||
$message = $this->getSesMessage($e);
|
||||
}
|
||||
|
||||
return $message ?: $this->getDefaultMessage($e);
|
||||
}
|
||||
|
||||
private function getSesMessage(SesException $e)
|
||||
{
|
||||
return ['mail_group' => $e->getAwsErrorMessage()];
|
||||
}
|
||||
|
||||
private function getMailgunMessage(ClientException $e)
|
||||
{
|
||||
$originalContents = $e
|
||||
->getResponse()
|
||||
->getBody()
|
||||
->getContents();
|
||||
$errResponse = json_decode($originalContents, true);
|
||||
if (is_null($errResponse) && is_string($originalContents)) {
|
||||
$errResponse = $originalContents;
|
||||
}
|
||||
$message = strtolower(Arr::get($errResponse, 'message', $errResponse));
|
||||
|
||||
if (Str::contains($message, 'domain not found')) {
|
||||
return [
|
||||
'server.mailgun_domain' => 'This mailgun domain is not valid.',
|
||||
];
|
||||
} elseif (Str::contains($message, 'forbidden')) {
|
||||
return [
|
||||
'server.mailgun_secret' => 'This mailgun API Key is not valid.',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'mail_group' =>
|
||||
'Could not validate mailgun credentials. Please double check them.',
|
||||
];
|
||||
}
|
||||
|
||||
private function getSmtpMessage(Exception $e): ?array
|
||||
{
|
||||
if (Str::contains($e->getMessage(), 'Connection timed out #110')) {
|
||||
return [
|
||||
'mail_group' =>
|
||||
'Connection to mail server timed out. This usually indicates incorrect mail credentials. Please double check them.',
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getDefaultMessage(Exception $e): array
|
||||
{
|
||||
return [
|
||||
'mail_group' => "Could not validate mail credentials: <br> {$e->getMessage()}",
|
||||
];
|
||||
}
|
||||
}
|
||||
96
common/Settings/Validators/PaypalCredentialsValidator.php
Executable file
96
common/Settings/Validators/PaypalCredentialsValidator.php
Executable file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Billing\Gateways\Paypal\Paypal;
|
||||
use Common\Settings\Settings;
|
||||
use Config;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class PaypalCredentialsValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = [
|
||||
'paypal_client_id',
|
||||
'paypal_secret',
|
||||
'paypal_webhook_id',
|
||||
'billing.paypal_test_mode',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* @param Settings $settings
|
||||
*/
|
||||
public function __construct(Settings $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
// create gateway after setting config dynamically
|
||||
// so gateway uses new configuration
|
||||
|
||||
try {
|
||||
$response = app(Paypal::class)
|
||||
->paypal()
|
||||
->get('payments/billing-plans');
|
||||
if (!$response->successful()) {
|
||||
return $this->getErrorMessage($response->body());
|
||||
}
|
||||
} catch (ClientException $e) {
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
foreach (self::KEYS as $key) {
|
||||
if (!Arr::has($settings, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'billing.paypal_test_mode') {
|
||||
$this->settings->set(
|
||||
'billing.paypal_test_mode',
|
||||
$settings[$key],
|
||||
);
|
||||
} else {
|
||||
// paypal_client_id => client_id
|
||||
$configKey = str_replace('paypal_', '', $key);
|
||||
Config::set("services.paypal.$configKey", $settings[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($data)
|
||||
{
|
||||
$message = Arr::get($data, 'message');
|
||||
if ($data['name'] === 'AUTHENTICATION_FAILURE') {
|
||||
return [
|
||||
'paypal_group' =>
|
||||
'Paypal Client ID or Paypal Secret is invalid.',
|
||||
];
|
||||
} elseif ($message) {
|
||||
$infoLink = Arr::get($data, 'information_link');
|
||||
return ['paypal_group' => "$message. $infoLink"];
|
||||
} else {
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultError()
|
||||
{
|
||||
return ['paypal_group' => 'These paypal credentials are not valid.'];
|
||||
}
|
||||
}
|
||||
52
common/Settings/Validators/QueueCredentialsValidator.php
Executable file
52
common/Settings/Validators/QueueCredentialsValidator.php
Executable file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Config;
|
||||
use Queue;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class QueueCredentialsValidator
|
||||
{
|
||||
const KEYS = [
|
||||
'queue_driver',
|
||||
|
||||
// sqs
|
||||
'SQS_QUEUE_KEY', 'SQS_QUEUE_SECRET', 'SQS_QUEUE_PREFIX', 'SQS_QUEUE_NAME', 'SQS_QUEUE_REGION',
|
||||
];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
$this->setConfigDynamically($settings);
|
||||
|
||||
$driver = Arr::get($settings, 'queue_driver', config('queue.default'));
|
||||
try {
|
||||
Queue::connection($driver)->size();
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage($e, $driver);
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
foreach ($settings as $key => $value) {
|
||||
// SQS_QUEUE_KEY => sqs.queue.key
|
||||
$key = strtolower(str_replace('_', '.', $key));
|
||||
// sqs.queue.key => sqs.key
|
||||
$key = str_replace('queue.', '', $key);
|
||||
$key = str_replace('name', 'queue', $key);
|
||||
Config::set("queue.connections.$key", $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $e
|
||||
* @param string $driver
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($e, $driver)
|
||||
{
|
||||
return ['queue_group' => "Could not change queue driver to <strong>$driver</strong>.<br> {$e->getMessage()}"];
|
||||
}
|
||||
}
|
||||
51
common/Settings/Validators/RealtimeCredentialsValidator.php
Executable file
51
common/Settings/Validators/RealtimeCredentialsValidator.php
Executable file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Config;
|
||||
use Exception;
|
||||
use Arr;
|
||||
use Pusher\Pusher;
|
||||
|
||||
class RealtimeCredentialsValidator
|
||||
{
|
||||
const KEYS = ['pusher_key', 'pusher_secret', 'pusher_app_id', 'pusher_cluster'];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
$this->setConfigDynamically($settings);
|
||||
|
||||
try {
|
||||
$config = Config::get('broadcasting.connections.pusher');
|
||||
$pusher = new Pusher($config['key'], $config['secret'],
|
||||
$config['app_id'], Arr::get($config, 'options', []));
|
||||
if ($pusher->get_channels() === false) {
|
||||
return $this->getErrorMessage();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
foreach (self::KEYS as $key) {
|
||||
if ( ! Arr::has($settings, $key)) continue;
|
||||
if ($key === 'pusher_cluster') {
|
||||
Config::set("broadcasting.connections.pusher.options.cluster", $settings[$key]);
|
||||
} else {
|
||||
$configKey = str_replace('pusher_', '', $key);
|
||||
Config::set("broadcasting.connections.pusher.$configKey", $settings[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($e = null)
|
||||
{
|
||||
return ['pusher_group' => 'These pusher credentials are not valid.'];
|
||||
}
|
||||
}
|
||||
45
common/Settings/Validators/RecaptchaCredentialsValidator.php
Executable file
45
common/Settings/Validators/RecaptchaCredentialsValidator.php
Executable file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class RecaptchaCredentialsValidator
|
||||
{
|
||||
const KEYS = ['recaptcha.site_key', 'recaptcha.secret_key'];
|
||||
|
||||
public function fails($settings): array|false
|
||||
{
|
||||
try {
|
||||
$response = Http::asForm()->post(
|
||||
'https://www.google.com/recaptcha/api/siteverify',
|
||||
[
|
||||
'response' => 'foo-bar',
|
||||
'secret' => Arr::get($settings, 'recaptcha.secret_key'),
|
||||
],
|
||||
);
|
||||
|
||||
if (
|
||||
$response['success'] === false &&
|
||||
$response['error-codes'][0] !== 'invalid-input-response'
|
||||
) {
|
||||
return [
|
||||
'recaptcha_group' =>
|
||||
Arr::get($response, 'error-codes')[0] ??
|
||||
__('These credentials are not valid'),
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getErrorMessage(Exception $e): array
|
||||
{
|
||||
return ['recaptcha_group' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
79
common/Settings/Validators/SearchConfigValidator.php
Executable file
79
common/Settings/Validators/SearchConfigValidator.php
Executable file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use App\Models\User;
|
||||
use Arr;
|
||||
use Exception;
|
||||
use Laravel\Scout\Builder;
|
||||
use Laravel\Scout\EngineManager;
|
||||
use Matchish\ScoutElasticSearch\ElasticSearchServiceProvider;
|
||||
use Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine;
|
||||
use Throwable;
|
||||
|
||||
class SearchConfigValidator
|
||||
{
|
||||
const KEYS = ['scout_driver'];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
$engineName = Arr::get(
|
||||
$settings,
|
||||
'scout_driver',
|
||||
config('scout.driver'),
|
||||
);
|
||||
$manager = app(EngineManager::class);
|
||||
|
||||
if (isset($settings['algolia_app_id'])) {
|
||||
config()->set('scout.algolia.id', $settings['algolia_app_id']);
|
||||
}
|
||||
if (isset($settings['algolia_secret'])) {
|
||||
config()->set('scout.algolia.secret', $settings['algolia_secret']);
|
||||
}
|
||||
|
||||
if (
|
||||
$engineName === 'mysql' &&
|
||||
Arr::get($settings, 'scout_mysql_mode') !== 'fulltext'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// register elastic search provider, if not registered already
|
||||
if (
|
||||
$engineName === ElasticSearchEngine::class &&
|
||||
empty(app()->getProviders(ElasticSearchServiceProvider::class))
|
||||
) {
|
||||
app()->register(ElasticSearchServiceProvider::class);
|
||||
}
|
||||
|
||||
$results = $manager->engine($engineName)->search(
|
||||
app(Builder::class, [
|
||||
'model' => new User(),
|
||||
'query' => 'test',
|
||||
]),
|
||||
);
|
||||
if (!$results) {
|
||||
return $this->getDefaultErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception|Throwable $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage($e)
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
return [
|
||||
'search_group' => "Could not enable this search method: $message",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getDefaultErrorMessage()
|
||||
{
|
||||
return ['search_group' => 'Could not enable this search method.'];
|
||||
}
|
||||
}
|
||||
12
common/Settings/Validators/SettingsValidator.php
Executable file
12
common/Settings/Validators/SettingsValidator.php
Executable file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
interface SettingsValidator
|
||||
{
|
||||
/**
|
||||
* @param array $values
|
||||
* @return null|array
|
||||
*/
|
||||
public function fails($values);
|
||||
}
|
||||
69
common/Settings/Validators/StaticFileDeliveryValidator.php
Executable file
69
common/Settings/Validators/StaticFileDeliveryValidator.php
Executable file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Files\Actions\CreateFileEntry;
|
||||
use Common\Files\Actions\Deletion\PermanentlyDeleteEntries;
|
||||
use Common\Files\Actions\StoreFile;
|
||||
use Common\Files\FileEntryPayload;
|
||||
use Common\Settings\DotEnvEditor;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StaticFileDeliveryValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = ['static_file_delivery'];
|
||||
|
||||
public function fails($values): bool|array
|
||||
{
|
||||
if (!$values['static_file_delivery']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$originalDelivery = config('common.site.static_file_delivery');
|
||||
$originalDriver = config('common.site.uploads_disk_driver');
|
||||
|
||||
app(DotEnvEditor::class)->write([
|
||||
'STATIC_FILE_DELIVERY' => $values['static_file_delivery'],
|
||||
'UPLOADS_DISK_DRIVER' => 'local',
|
||||
]);
|
||||
|
||||
$previewToken = Str::random(10);
|
||||
$contents = Str::random(10);
|
||||
|
||||
$path = base_path('common/resources/lorem.html');
|
||||
$uploadedFile = new UploadedFile(
|
||||
$path,
|
||||
basename($path),
|
||||
'text/html',
|
||||
filesize($path),
|
||||
);
|
||||
$payload = new FileEntryPayload([
|
||||
'file' => $uploadedFile,
|
||||
]);
|
||||
$fileEntry = app(CreateFileEntry::class)->execute($payload);
|
||||
$fileEntry->fill(['preview_token' => $previewToken])->save();
|
||||
app(StoreFile::class)->execute($payload, ['file' => $uploadedFile]);
|
||||
|
||||
$response = Http::get(
|
||||
url($fileEntry->url) . "?preview_token=$previewToken",
|
||||
);
|
||||
app(PermanentlyDeleteEntries::class)->execute([$fileEntry->id]);
|
||||
|
||||
app(DotEnvEditor::class)->write([
|
||||
'STATIC_FILE_DELIVERY' => $originalDelivery,
|
||||
'UPLOADS_DISK_DRIVER' => $originalDriver,
|
||||
]);
|
||||
|
||||
if ($contents !== $response->body()) {
|
||||
return [
|
||||
'static_delivery_group' => __(
|
||||
'Could not validate selected optimization. Is it enabled on the server?',
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
common/Settings/Validators/StorageCredentialsValidator.php
Executable file
161
common/Settings/Validators/StorageCredentialsValidator.php
Executable file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Aws\S3\Exception\S3Exception;
|
||||
use Common\Files\Providers\BackblazeServiceProvider;
|
||||
use Common\Files\Providers\DigitalOceanServiceProvider;
|
||||
use Common\Files\Providers\DropboxServiceProvider;
|
||||
use Config;
|
||||
use Exception;
|
||||
use Spatie\FlysystemDropbox\DropboxAdapter;
|
||||
use Storage;
|
||||
use Str;
|
||||
|
||||
class StorageCredentialsValidator
|
||||
{
|
||||
const KEYS = [
|
||||
'uploads_disk_driver',
|
||||
'public_disk_driver',
|
||||
|
||||
// dropbox
|
||||
'storage_dropbox_access_token',
|
||||
'storage_dropbox_refresh_token',
|
||||
'storage_dropbox_app_key',
|
||||
'storage_dropbox_app_secret',
|
||||
|
||||
// s3
|
||||
'storage_s3_key',
|
||||
'storage_s3_secret',
|
||||
'storage_s3_region',
|
||||
'storage_s3_bucket',
|
||||
|
||||
// ftp
|
||||
'storage_ftp_host',
|
||||
'storage_ftp_username',
|
||||
'storage_ftp_password',
|
||||
'storage_ftp_root',
|
||||
'storage_ftp_port',
|
||||
'storage_ftp_passive',
|
||||
'storage_ftp_ssl',
|
||||
|
||||
// digital ocean
|
||||
'storage_digitalocean_key',
|
||||
'storage_digitalocean_secret',
|
||||
'storage_digitalocean_region',
|
||||
'storage_digitalocean_bucket',
|
||||
|
||||
// rackspace
|
||||
'storage_rackspace_username',
|
||||
'storage_rackspace_key',
|
||||
'storage_rackspace_region',
|
||||
'storage_rackspace_container',
|
||||
|
||||
// backblaze
|
||||
'storage_backblaze_key',
|
||||
'storage_backblaze_secret',
|
||||
'storage_backblaze_bucket',
|
||||
'storage_backblaze_region',
|
||||
];
|
||||
|
||||
public function fails($settings)
|
||||
{
|
||||
$this->setConfigDynamically($settings);
|
||||
$this->registerAdapters();
|
||||
|
||||
$messages = array_merge(
|
||||
is_null(config('common.site.uploads_disk_driver'))
|
||||
? []
|
||||
: $this->validateDisk('uploads'),
|
||||
$this->validateDisk('public'),
|
||||
);
|
||||
|
||||
return empty($messages) ? false : $messages;
|
||||
}
|
||||
|
||||
private function validateDisk(string $diskName): array
|
||||
{
|
||||
$driverName = Config::get("common.site.{$diskName}_disk_driver");
|
||||
|
||||
try {
|
||||
$disk = Storage::disk($diskName);
|
||||
if ($disk->getAdapter() instanceof DropboxAdapter) {
|
||||
// dropbox adapter catches all errors silently
|
||||
// need to use client directly to check for errors
|
||||
$disk
|
||||
->getAdapter()
|
||||
->getClient()
|
||||
->listFolder();
|
||||
} else {
|
||||
$disk->allFiles();
|
||||
}
|
||||
} catch (S3Exception $e) {
|
||||
return $this->getS3Message($e);
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (
|
||||
Str::contains(
|
||||
$message,
|
||||
'ftp_chdir(): Failed to change directory',
|
||||
)
|
||||
) {
|
||||
$message =
|
||||
'Could not open "uploads" directory. You might need to create it manually via any FTP manager.';
|
||||
}
|
||||
return [
|
||||
'storage_group' => "Invalid $driverName credentials.<br>{$message}",
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getS3Message(S3Exception $e): array
|
||||
{
|
||||
return [
|
||||
'storage_group' => "Could not validate credentials. <br> {$e->getAwsErrorMessage()}",
|
||||
];
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings): void
|
||||
{
|
||||
$replacements = [
|
||||
's3',
|
||||
'dropbox',
|
||||
'ftp',
|
||||
'digitalocean',
|
||||
'rackspace',
|
||||
'backblaze',
|
||||
];
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
if ($key === 'uploads_disk_driver') {
|
||||
Config::set('common.site.uploads_disk_driver', $value ?: null);
|
||||
} elseif ($key === 'public_disk_driver') {
|
||||
Config::set('common.site.public_disk_driver', $value ?: null);
|
||||
} else {
|
||||
// uploads_s3_key => services.s3.key
|
||||
$key = str_replace('storage_', '', $key);
|
||||
$key = preg_replace('/_/', '.', $key, 1);
|
||||
$key = "services.$key";
|
||||
foreach ($replacements as $replacement) {
|
||||
$key = str_replace(
|
||||
"{$replacement}_",
|
||||
"{$replacement}.",
|
||||
$key,
|
||||
);
|
||||
}
|
||||
$key = str_replace('digitalocean.', 'digitalocean_s3.', $key);
|
||||
$key = str_replace('backblaze.', 'backblaze_s3.', $key);
|
||||
Config::set($key, $value ?: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function registerAdapters(): void
|
||||
{
|
||||
app()->register(DigitalOceanServiceProvider::class);
|
||||
app()->register(DropboxServiceProvider::class);
|
||||
app()->register(BackblazeServiceProvider::class);
|
||||
}
|
||||
}
|
||||
46
common/Settings/Validators/StripeCredentialsValidator.php
Executable file
46
common/Settings/Validators/StripeCredentialsValidator.php
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Billing\Gateways\Stripe\Stripe;
|
||||
use Config;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class StripeCredentialsValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = ['stripe_key', 'stripe_secret'];
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
// create gateway after setting config dynamically
|
||||
// so gateway uses new configuration
|
||||
$gateway = app(Stripe::class);
|
||||
|
||||
try {
|
||||
$gateway->getAllPlans();
|
||||
} catch (ClientException $e) {
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
foreach (self::KEYS as $key) {
|
||||
if (!Arr::has($settings, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// stripe_key => key
|
||||
$configKey = str_replace('stripe_', '', $key);
|
||||
Config::set("services.stripe.$configKey", $settings[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultError(): array
|
||||
{
|
||||
return ['stripe_group' => 'These stripe credentials are not valid.'];
|
||||
}
|
||||
}
|
||||
73
common/Settings/Validators/TwitterLoginValidator.php
Executable file
73
common/Settings/Validators/TwitterLoginValidator.php
Executable file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Settings\Validators;
|
||||
|
||||
use Common\Auth\Oauth;
|
||||
use Common\Core\HttpClient;
|
||||
use Config;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Socialite;
|
||||
|
||||
class TwitterLoginValidator implements SettingsValidator
|
||||
{
|
||||
const KEYS = ['twitter_id', 'twitter_secret'];
|
||||
|
||||
/**
|
||||
* @var Oauth
|
||||
*/
|
||||
private $oauth;
|
||||
|
||||
/**
|
||||
* @var HttpClient
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
public function __construct(Oauth $oauth)
|
||||
{
|
||||
$this->oauth = $oauth;
|
||||
$this->httpClient = new HttpClient([
|
||||
'exceptions' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function fails($values)
|
||||
{
|
||||
$this->setConfigDynamically($values);
|
||||
|
||||
try {
|
||||
Socialite::driver('twitter')->redirect();
|
||||
} catch (Exception $e) {
|
||||
return $this->getErrorMessage($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function setConfigDynamically($settings)
|
||||
{
|
||||
if ($twitterId = Arr::get($settings, 'twitter_id')) {
|
||||
Config::set('services.twitter.client_id', $twitterId);
|
||||
}
|
||||
|
||||
if ($twitterSecret = Arr::get($settings, 'twitter_secret')) {
|
||||
Config::set('services.twitter.client_secret', $twitterSecret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $e
|
||||
* @return array
|
||||
*/
|
||||
private function getErrorMessage(Exception $e)
|
||||
{
|
||||
if (\Str::contains($e->getMessage(), 'code="415"')) {
|
||||
return ['twitter_group' => 'Site url is not present in "Callback URL" field on your twitter app.'];
|
||||
}
|
||||
|
||||
return $this->getDefaultError();
|
||||
}
|
||||
|
||||
private function getDefaultError()
|
||||
{
|
||||
return ['twitter_group' => 'These twitter credentials are not valid.'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user