Lint PHP code psr-12, add GH action

This commit is contained in:
Julien Nahum 2024-02-23 11:54:12 +01:00
parent e85e4df7fe
commit 62971a2ef4
226 changed files with 2338 additions and 2144 deletions

View File

@ -6,6 +6,47 @@ on:
pull_request:
jobs:
code_lint:
runs-on: ubuntu-latest
name: Run code linters
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get Composer cache directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: dom, curl, mbstring, pdo, sqlite, pdo_sqlite
tools: composer:v2
coverage: none
- name: Prepare the environment
run: cp .env.example .env
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress --ignore-platform-reqs --optimize-autoloader
- name: Directory permissions
run: chmod -R 777 storage bootstrap/cache
- name: Run PHP lint check
run: ./vendor/bin/pint --test
tests:
runs-on: ubuntu-latest
@ -155,7 +196,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
production-deploy:
needs: [ tests, build-nuxt-app ]
needs: [ code_lint, tests, build-nuxt-app ]
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
name: Triggers Deployment (Vapor & Amplify)

View File

@ -3,9 +3,9 @@
namespace App\Console\Commands;
use App\Models\Forms\FormStatistic;
use Illuminate\Console\Command;
use App\Models\Forms\FormView;
use App\Models\Forms\FormSubmission;
use App\Models\Forms\FormView;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class CleanDatabase extends Command
@ -51,13 +51,13 @@ class CleanDatabase extends Command
->orderBy('date')
->groupBy('form_id', 'date')
->get()->each(function ($row) use (&$finalData) {
$finalData[$row->form_id."-".$row->date] = [
$finalData[$row->form_id.'-'.$row->date] = [
'form_id' => $row->form_id,
'date' => $row->date,
'data' => [
'views' => $row->views,
'submissions' => 0
]
'submissions' => 0,
],
];
});
@ -68,7 +68,7 @@ class CleanDatabase extends Command
->orderBy('date')
->groupBy('form_id', 'date')
->get()->each(function ($row) use (&$finalData) {
$key = $row->form_id."-".$row->date;
$key = $row->form_id.'-'.$row->date;
if (isset($finalData[$key])) {
$finalData[$key]['data']['submissions'] = $row->submissions;
} else {
@ -77,8 +77,8 @@ class CleanDatabase extends Command
'date' => $row->date,
'data' => [
'views' => 0,
'submissions' => $row->submissions
]
'submissions' => $row->submissions,
],
];
}

View File

@ -24,9 +24,9 @@ class GenerateTemplate extends Command
*/
protected $description = 'Generates a new form template from a prompt';
const MAX_RELATED_TEMPLATES = 8;
public const MAX_RELATED_TEMPLATES = 8;
const FORM_STRUCTURE_PROMPT = <<<EOD
public const FORM_STRUCTURE_PROMPT = <<<'EOD'
You are an AI assistant for OpnForm, a form builder and your job is to build a form for our user.
Forms are represented as Json objects. Here's an example form:
@ -157,7 +157,7 @@ class GenerateTemplate extends Command
Do not ask me for more information about required properties or types, only suggest me a form structure.
EOD;
const FORM_DESCRIPTION_PROMPT = <<<EOD
public const FORM_DESCRIPTION_PROMPT = <<<'EOD'
You are an AI assistant for OpnForm, a form builder and your job is to help us build form templates for our users.
Give me some valid html code (using only h2, p, ul, li html tags) for the following form template page: "[REPLACE]".
@ -169,12 +169,12 @@ class GenerateTemplate extends Command
Each paragraph (except for the first one) MUST start with with a h2 tag containing a title for this paragraph.
EOD;
const FORM_SHORT_DESCRIPTION_PROMPT = <<<EOD
public const FORM_SHORT_DESCRIPTION_PROMPT = <<<'EOD'
I own a form builder online named OpnForm. It's free to use.
Give me a 1 sentence description for the following form template page: "[REPLACE]". It should be short and concise, but still explain what the form is about.
EOD;
const FORM_INDUSTRY_PROMPT = <<<EOD
public const FORM_INDUSTRY_PROMPT = <<<'EOD'
You are an AI assistant for OpnForm, a form builder and your job is to help us build form templates for our users.
I am creating a form template: "[REPLACE]". You must assign the template to industries. Return a list of industries (minimum 1, maximum 3 but only if very relevant) and order them by relevance (most relevant first).
@ -185,7 +185,7 @@ class GenerateTemplate extends Command
Ex: { "industries": ["banking_forms","customer_service_forms"]}
EOD;
const FORM_TYPES_PROMPT = <<<EOD
public const FORM_TYPES_PROMPT = <<<'EOD'
You are an AI assistant for OpnForm, a form builder and your job is to help us build form templates for our users.
I am creating a form template: "[REPLACE]". You must assign the template to one or more types. Return a list of types (minimum 1, maximum 3 but only if very accurate) and order them by relevance (most relevant first).
@ -196,17 +196,17 @@ class GenerateTemplate extends Command
Ex: { "types": ["consent_forms","award_forms"]}
EOD;
const FORM_QAS_PROMPT = <<<EOD
public const FORM_QAS_PROMPT = <<<'EOD'
Now give me 4 to 6 question and answers to put on the form template page. The questions should be about the reasons for this template (when to use, why, target audience, goal etc.).
The questions should also explain why OpnForm is the best option to create this form (open-source, free to use, integrations etc).
Reply only with a valid JSON, being an array of object containing the keys "question" and "answer".
EOD;
const FORM_TITLE_PROMPT = <<<EOD
public const FORM_TITLE_PROMPT = <<<'EOD'
Finally give me a title for the template. It must contain or end with "template". It should be short and to the point, without any quotes.
EOD;
const FORM_IMG_KEYWORDS_PROMPT = <<<EOD
public const FORM_IMG_KEYWORDS_PROMPT = <<<'EOD'
I want to add an image to illustrate this form template page. Give me a relevant search query for unsplash. Reply only with a valid JSON like this:
```json
{
@ -227,14 +227,14 @@ class GenerateTemplate extends Command
->useStreaming()
->setSystemMessage('You are an assistant helping to generate forms.');
$completer->expectsJson()->completeChat([
["role" => "user", "content" => Str::of(self::FORM_STRUCTURE_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()]
['role' => 'user', 'content' => Str::of(self::FORM_STRUCTURE_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()],
]);
$formData = $completer->getArray();
$completer->doesNotExpectJson();
$formDescriptionPrompt = Str::of(self::FORM_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString();
$formShortDescription = $completer->completeChat([
["role" => "user", "content" => Str::of(self::FORM_SHORT_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()]
['role' => 'user', 'content' => Str::of(self::FORM_SHORT_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()],
])->getString();
// If description is between quotes, remove quotes
$formShortDescription = Str::of($formShortDescription)->replaceMatches('/^"(.*)"$/', '$1')->toString();
@ -250,30 +250,29 @@ class GenerateTemplate extends Command
// Now get description and QAs
$completer->doesNotExpectJson();
$formDescription = $completer->completeChat([
["role" => "user", "content" => $formDescriptionPrompt]
['role' => 'user', 'content' => $formDescriptionPrompt],
])->getHtml();
$completer->expectsJson();
$formCoverKeywords = $completer->completeChat([
["role" => "user", "content" => $formDescriptionPrompt],
["role" => "assistant", "content" => $formDescription],
["role" => "user", "content" => self::FORM_IMG_KEYWORDS_PROMPT]
['role' => 'user', 'content' => $formDescriptionPrompt],
['role' => 'assistant', 'content' => $formDescription],
['role' => 'user', 'content' => self::FORM_IMG_KEYWORDS_PROMPT],
])->getArray();
$imageUrl = $this->getImageCoverUrl($formCoverKeywords['search_query']);
$formQAs = $completer->completeChat([
["role" => "user", "content" => $formDescriptionPrompt],
["role" => "assistant", "content" => $formDescription],
["role" => "user", "content" => self::FORM_QAS_PROMPT]
['role' => 'user', 'content' => $formDescriptionPrompt],
['role' => 'assistant', 'content' => $formDescription],
['role' => 'user', 'content' => self::FORM_QAS_PROMPT],
])->getArray();
$completer->doesNotExpectJson();
$formTitle = $completer->completeChat([
["role" => "user", "content" => $formDescriptionPrompt],
["role" => "assistant", "content" => $formDescription],
["role" => "user", "content" => self::FORM_TITLE_PROMPT]
['role' => 'user', 'content' => $formDescriptionPrompt],
['role' => 'assistant', 'content' => $formDescription],
['role' => 'user', 'content' => self::FORM_TITLE_PROMPT],
])->getString();
$template = $this->createFormTemplate(
$formData,
$formTitle,
@ -285,7 +284,7 @@ class GenerateTemplate extends Command
$types,
$relatedTemplates
);
$this->info('/form-templates/' . $template->slug);
$this->info('/form-templates/'.$template->slug);
// Set reverse related Templates
$this->setReverseRelatedTemplates($template);
@ -298,34 +297,37 @@ class GenerateTemplate extends Command
*/
private function getImageCoverUrl($searchQuery): ?string
{
$url = 'https://api.unsplash.com/search/photos?query=' . urlencode($searchQuery) . '&client_id=' . config('services.unsplash.access_key');
$url = 'https://api.unsplash.com/search/photos?query='.urlencode($searchQuery).'&client_id='.config('services.unsplash.access_key');
$response = Http::get($url)->json();
$photoIndex = rand(0, max(count($response['results']) - 1, 10));
if (isset($response['results'][$photoIndex]['urls']['regular'])) {
return Str::of($response['results'][$photoIndex]['urls']['regular'])->replace('w=1080', 'w=600')->toString();
}
return null;
}
private function getIndustries(GptCompleter $completer, string $formPrompt): array
{
$industriesString = Template::getAllIndustries()->pluck('slug')->join(', ');
return $completer->completeChat([
["role" => "user", "content" => Str::of(self::FORM_INDUSTRY_PROMPT)
['role' => 'user', 'content' => Str::of(self::FORM_INDUSTRY_PROMPT)
->replace('[REPLACE]', $formPrompt)
->replace('[INDUSTRIES]', $industriesString)
->toString()]
->toString()],
])->getArray()['industries'];
}
private function getTypes(GptCompleter $completer, string $formPrompt): array
{
$typesString = Template::getAllTypes()->pluck('slug')->join(', ');
return $completer->completeChat([
["role" => "user", "content" => Str::of(self::FORM_TYPES_PROMPT)
['role' => 'user', 'content' => Str::of(self::FORM_TYPES_PROMPT)
->replace('[REPLACE]', $formPrompt)
->replace('[TYPES]', $typesString)
->toString()]
->toString()],
])->getArray()['types'];
}
@ -344,21 +346,21 @@ class GenerateTemplate extends Command
}
});
arsort($templateScore); // Sort by Score
return array_slice(array_keys($templateScore), 0, self::MAX_RELATED_TEMPLATES);
}
private function createFormTemplate(
array $formData,
string $formTitle,
string $formDescription,
string $formShortDescription,
array $formQAs,
array $formData,
string $formTitle,
string $formDescription,
string $formShortDescription,
array $formQAs,
?string $imageUrl,
array $industry,
array $types,
array $relatedTemplates
)
{
array $industry,
array $types,
array $relatedTemplates
) {
// Add property uuids, improve form with options
foreach ($formData['properties'] as &$property) {
$property['id'] = Str::uuid()->toString(); // Column ID
@ -387,13 +389,15 @@ class GenerateTemplate extends Command
'publicly_listed' => true,
'industries' => $industry,
'types' => $types,
'related_templates' => $relatedTemplates
'related_templates' => $relatedTemplates,
]);
}
private function setReverseRelatedTemplates(Template $newTemplate)
{
if (!$newTemplate || count($newTemplate->related_templates) === 0) return;
if (! $newTemplate || count($newTemplate->related_templates) === 0) {
return;
}
$templates = Template::whereIn('slug', $newTemplate->related_templates)->get();
foreach ($templates as $template) {

View File

@ -27,34 +27,34 @@ class GenerateTaxExport extends Command
*/
protected $description = 'Compute Stripe VAT per country';
const EU_TAX_RATES = [
"AT" => 20,
"BE" => 21,
"BG" => 20,
"HR" => 25,
"CY" => 19,
"CZ" => 21,
"DK" => 25,
"EE" => 20,
"FI" => 24,
"FR" => 20,
"DE" => 19,
"GR" => 24,
"HU" => 27,
"IE" => 23,
"IT" => 22,
"LV" => 21,
"LT" => 21,
"LU" => 17,
"MT" => 18,
"NL" => 21,
"PL" => 23,
"PT" => 23,
"RO" => 19,
"SK" => 20,
"SI" => 22,
"ES" => 21,
"SE" => 25
public const EU_TAX_RATES = [
'AT' => 20,
'BE' => 21,
'BG' => 20,
'HR' => 25,
'CY' => 19,
'CZ' => 21,
'DK' => 25,
'EE' => 20,
'FI' => 24,
'FR' => 20,
'DE' => 19,
'GR' => 24,
'HU' => 27,
'IE' => 23,
'IT' => 22,
'LV' => 21,
'LT' => 21,
'LU' => 17,
'MT' => 18,
'NL' => 21,
'PL' => 23,
'PT' => 23,
'RO' => 19,
'SK' => 20,
'SI' => 22,
'ES' => 21,
'SE' => 25,
];
/**
@ -69,20 +69,22 @@ class GenerateTaxExport extends Command
$endDate = $this->option('end-date');
// Validate the date format
if ($startDate && !Carbon::createFromFormat('Y-m-d', $startDate)) {
if ($startDate && ! Carbon::createFromFormat('Y-m-d', $startDate)) {
$this->error('Invalid start date format. Use YYYY-MM-DD.');
return Command::FAILURE;
}
if ($endDate && !Carbon::createFromFormat('Y-m-d', $endDate)) {
if ($endDate && ! Carbon::createFromFormat('Y-m-d', $endDate)) {
$this->error('Invalid end date format. Use YYYY-MM-DD.');
return Command::FAILURE;
} else if (!$endDate && $this->option('full-month')) {
} elseif (! $endDate && $this->option('full-month')) {
$endDate = Carbon::parse($startDate)->endOfMonth()->endOfDay()->format('Y-m-d');
}
$this->info('Start date: ' . $startDate);
$this->info('End date: ' . $endDate);
$this->info('Start date: '.$startDate);
$this->info('End date: '.$endDate);
$processedInvoices = [];
@ -111,6 +113,7 @@ class GenerateTaxExport extends Command
// Ignore if payment was refunded
if (($invoice->payment_intent->status ?? null) !== 'succeeded') {
$paymentNotSuccessfulCount++;
continue;
}
@ -132,14 +135,14 @@ class GenerateTaxExport extends Command
$aggregatedReport = $this->aggregateReport($processedInvoices);
$filePath = 'opnform-tax-export-per-invoice_' . $startDate . '_' . $endDate . '.xlsx';
$filePath = 'opnform-tax-export-per-invoice_'.$startDate.'_'.$endDate.'.xlsx';
$this->exportAsXlsx($processedInvoices, $filePath);
$aggregatedReportFilePath = 'opnform-tax-export-aggregated_' . $startDate . '_' . $endDate . '.xlsx';
$aggregatedReportFilePath = 'opnform-tax-export-aggregated_'.$startDate.'_'.$endDate.'.xlsx';
$this->exportAsXlsx($aggregatedReport, $aggregatedReportFilePath);
// Display the results
$this->info('Total invoices: ' . $totalInvoice . ' (with ' . $paymentNotSuccessfulCount . ' payment not successful or trial free invoice)');
$this->info('Total invoices: '.$totalInvoice.' (with '.$paymentNotSuccessfulCount.' payment not successful or trial free invoice)');
return Command::SUCCESS;
}
@ -151,7 +154,7 @@ class GenerateTaxExport extends Command
foreach ($invoices as $invoice) {
$country = $invoice['cust_country'];
$customerType = is_null($invoice['cust_vat_id']) && $this->isEuropeanCountry($country) ? 'individual' : 'business';
if (!isset($aggregatedReport[$country])) {
if (! isset($aggregatedReport[$country])) {
$defaultVal = [
'count' => 0,
'total_usd' => 0,
@ -163,7 +166,7 @@ class GenerateTaxExport extends Command
];
$aggregatedReport[$country] = [
'individual' => $defaultVal,
'business' => $defaultVal
'business' => $defaultVal,
];
}
$aggregatedReport[$country][$customerType]['count']++;
@ -181,10 +184,11 @@ class GenerateTaxExport extends Command
$finalReport[] = [
'country' => $country,
'customer_type' => $customerType,
...$aggData
...$aggData,
];
}
}
return $finalReport;
}
@ -226,7 +230,9 @@ class GenerateTaxExport extends Command
if ($taxRate = (self::EU_TAX_RATES[$countryCode] ?? null)) {
// If VAT ID is provided, then TAX is 0%
if (!$vatId) return $taxRate;
if (! $vatId) {
return $taxRate;
}
}
return 0;
@ -241,12 +247,11 @@ class GenerateTaxExport extends Command
{
if (count($data) == 0) {
$this->info('Empty data. No file generated.');
return;
}
(new ArrayExport($data))->store($filename, 'local', \Maatwebsite\Excel\Excel::XLSX);
$this->line('File generated: ' . storage_path('app/' . $filename));
$this->line('File generated: '.storage_path('app/'.$filename));
}
}

View File

@ -19,7 +19,6 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)

View File

@ -3,19 +3,18 @@
namespace App\Events\Forms;
use App\Models\Forms\Form;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class FormSubmitted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public $form;
public $data;
/**

View File

@ -3,17 +3,15 @@
namespace App\Events\Models;
use App\Models\Forms\Form;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class FormCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
/**
* Create a new event instance.
@ -21,5 +19,6 @@ class FormCreated
* @return void
*/
public function __construct(public Form $form)
{}
{
}
}

View File

@ -47,7 +47,7 @@ class Handler extends ExceptionHandler
public function report(Throwable $exception)
{
if ($this->shouldReport($exception) ) {
if ($this->shouldReport($exception)) {
if (app()->bound('sentry') && $this->sentryShouldReport($exception)) {
app('sentry')->captureException($exception);
Log::debug('Un-handled Exception: '.$exception->getMessage(), [
@ -64,7 +64,7 @@ class Handler extends ExceptionHandler
public function render($request, Throwable $e)
{
if ($this->shouldReport($e) && !in_array(\App::environment(),['testing']) && config('logging.channels.slack.enabled')) {
if ($this->shouldReport($e) && ! in_array(\App::environment(), ['testing']) && config('logging.channels.slack.enabled')) {
Log::channel('slack')->error($e);
}
@ -78,6 +78,7 @@ class Handler extends ExceptionHandler
return false;
}
}
return true;
}
}

View File

@ -7,7 +7,7 @@ use Illuminate\Validation\ValidationException;
class VerifyEmailException extends ValidationException
{
/**
* @param \App\User $user
* @param \App\User $user
* @return static
*/
public static function forUser($user)

View File

@ -14,7 +14,7 @@ class WorkspaceAlreadyExisting extends Exception
public function getErrorMessage()
{
$owner = $this->workspace->users()->first();
if (!$owner) {
if (! $owner) {
return 'A user already connected that workspace to another NotionForms account. You or the current workspace
owner must have a NotionForms Enterprise subscription for you to add this Notion workspace. Please upgrade
with an Enterprise subscription, or contact us to get help.';

View File

@ -7,7 +7,6 @@ use Maatwebsite\Excel\Concerns\WithHeadingRow;
class FormSubmissionExport implements FromArray, WithHeadingRow
{
protected array $submissionData;
public function __construct(array $submissionData)
@ -15,7 +14,7 @@ class FormSubmissionExport implements FromArray, WithHeadingRow
$headingRow = [];
$contentRow = [];
foreach ($submissionData as $i => $row) {
if($i==0){
if ($i == 0) {
$headingRow[] = $this->cleanColumnNames(array_keys($row));
}
$contentRow[] = array_values($row);
@ -23,7 +22,7 @@ class FormSubmissionExport implements FromArray, WithHeadingRow
$this->submissionData = [
$headingRow,
$contentRow
$contentRow,
];
}

View File

@ -24,4 +24,3 @@ class ArrayExport implements FromArray, WithHeadings
return array_keys($this->data[0]);
}
}

View File

@ -5,8 +5,6 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Forms\Form;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ImpersonationController extends Controller
{
@ -15,7 +13,8 @@ class ImpersonationController extends Controller
$this->middleware('moderator');
}
public function impersonate($identifier) {
public function impersonate($identifier)
{
$user = null;
if (is_numeric($identifier)) {
$user = User::find($identifier);
@ -29,17 +28,17 @@ class ImpersonationController extends Controller
}
}
if (!$user) {
if (! $user) {
return $this->error([
'message'=> 'User not found.'
'message' => 'User not found.',
]);
} else if ($user->admin) {
} elseif ($user->admin) {
return $this->error([
'message' => 'You cannot impersonate an admin.',
]);
}
\Log::warning('Impersonation started',[
\Log::warning('Impersonation started', [
'from_id' => auth()->id(),
'from_email' => auth()->user()->email,
'target_id' => $user->id,
@ -52,7 +51,7 @@ class ImpersonationController extends Controller
])->login($user);
return $this->success([
'token' => $token
'token' => $token,
]);
}
}

View File

@ -17,14 +17,16 @@ class AppSumoAuthController extends Controller
public function handleCallback(Request $request)
{
if (!$code = $request->code) {
if (! $code = $request->code) {
return response()->json(['message' => 'Healthy'], 200);
}
$accessToken = $this->retrieveAccessToken($code);
$license = $this->fetchOrCreateLicense($accessToken);
// If user connected, attach license
if (Auth::check()) return $this->attachLicense($license);
if (Auth::check()) {
return $this->attachLicense($license);
}
// otherwise start login flow by passing the encrypted license key id
if (is_null($license->user_id)) {
@ -37,7 +39,7 @@ class AppSumoAuthController extends Controller
private function retrieveAccessToken(string $requestCode): string
{
return Http::withHeaders([
'Content-type' => 'application/json'
'Content-type' => 'application/json',
])->post('https://appsumo.com/openid/token/', [
'grant_type' => 'authorization_code',
'code' => $requestCode,
@ -50,13 +52,13 @@ class AppSumoAuthController extends Controller
private function fetchOrCreateLicense(string $accessToken): License
{
// Fetch license from API
$licenseKey = Http::get('https://appsumo.com/openid/license_key/?access_token=' . $accessToken)
$licenseKey = Http::get('https://appsumo.com/openid/license_key/?access_token='.$accessToken)
->throw()
->json('license_key');
// Fetch or create license model
$license = License::where('license_provider','appsumo')->where('license_key',$licenseKey)->first();
if (!$license) {
$license = License::where('license_provider', 'appsumo')->where('license_key', $licenseKey)->first();
if (! $license) {
$licenseData = Http::withHeaders([
'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
@ -73,8 +75,9 @@ class AppSumoAuthController extends Controller
return $license;
}
private function attachLicense(License $license) {
if (!Auth::check()) {
private function attachLicense(License $license)
{
if (! Auth::check()) {
throw new AuthenticationException('User not authenticated');
}
@ -82,6 +85,7 @@ class AppSumoAuthController extends Controller
if (is_null($license->user_id)) {
$license->user_id = Auth::id();
$license->save();
return redirect(front_url('/home?appsumo_connect=1'));
}
@ -90,8 +94,6 @@ class AppSumoAuthController extends Controller
}
/**
* @param User $user
* @param string|null $licenseHash
* @return string|null
*
* Returns null if no license found
@ -100,7 +102,7 @@ class AppSumoAuthController extends Controller
*/
public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
{
if (!$licenseHash) {
if (! $licenseHash) {
return null;
}
$licenseId = decrypt($licenseHash);
@ -109,6 +111,7 @@ class AppSumoAuthController extends Controller
if ($license && is_null($license->user_id)) {
$license->user_id = $user->id;
$license->save();
return true;
}

View File

@ -23,7 +23,6 @@ class ForgotPasswordController extends Controller
/**
* Get the response for a successful password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
@ -35,7 +34,6 @@ class ForgotPasswordController extends Controller
/**
* Get the response for a failed password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/

View File

@ -26,7 +26,6 @@ class LoginController extends Controller
/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function attemptLogin(Request $request)
@ -50,7 +49,6 @@ class LoginController extends Controller
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
@ -64,7 +62,6 @@ class LoginController extends Controller
/**
* Send the response after the user was authenticated.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
protected function sendLoginResponse(Request $request)
@ -84,7 +81,6 @@ class LoginController extends Controller
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
@ -104,7 +100,6 @@ class LoginController extends Controller
/**
* Log the user out of the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function logout(Request $request)

View File

@ -28,7 +28,7 @@ class OAuthController extends Controller
/**
* Redirect the user to the provider authentication page.
*
* @param string $provider
* @param string $provider
* @return \Illuminate\Http\RedirectResponse
*/
public function redirect($provider)
@ -41,7 +41,7 @@ class OAuthController extends Controller
/**
* Obtain the user information from the provider.
*
* @param string $driver
* @param string $driver
* @return \Illuminate\Http\Response
*/
public function handleCallback($provider)
@ -61,8 +61,8 @@ class OAuthController extends Controller
}
/**
* @param string $provider
* @param \Laravel\Socialite\Contracts\User $sUser
* @param string $provider
* @param \Laravel\Socialite\Contracts\User $sUser
* @return \App\Models\User
*/
protected function findOrCreateUser($provider, $user)
@ -81,15 +81,15 @@ class OAuthController extends Controller
}
if (User::where('email', $user->getEmail())->exists()) {
throw new EmailTakenException;
throw new EmailTakenException();
}
return $this->createUser($provider, $user);
}
/**
* @param string $provider
* @param \Laravel\Socialite\Contracts\User $sUser
* @param string $provider
* @param \Laravel\Socialite\Contracts\User $sUser
* @return \App\Models\User
*/
protected function createUser($provider, $sUser)

View File

@ -4,8 +4,8 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use App\Models\Workspace;
use App\Models\User;
use App\Models\Workspace;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
@ -31,8 +31,7 @@ class RegisterController extends Controller
/**
* The user has been registered.
*
* @param \Illuminate\Http\Request $request
* @param \App\User $user
* @param \App\User $user
* @return \Illuminate\Http\JsonResponse
*/
protected function registered(Request $request, User $user)
@ -45,13 +44,13 @@ class RegisterController extends Controller
(new UserResource($user))->toArray($request),
[
'appsumo_license' => $this->appsumoLicense,
]));
]
));
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
@ -64,14 +63,13 @@ class RegisterController extends Controller
'agree_terms' => ['required', Rule::in([true])],
'appsumo_license' => ['nullable'],
], [
'agree_terms' => 'Please agree with the terms and conditions.'
'agree_terms' => 'Please agree with the terms and conditions.',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
@ -85,14 +83,14 @@ class RegisterController extends Controller
'name' => $data['name'],
'email' => strtolower($data['email']),
'password' => bcrypt($data['password']),
'hear_about_us' => $data['hear_about_us']
'hear_about_us' => $data['hear_about_us'],
]);
// Add relation with user
$user->workspaces()->sync([
$workspace->id => [
'role' => 'admin'
]
'role' => 'admin',
],
], false);
$this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);

View File

@ -23,7 +23,6 @@ class ResetPasswordController extends Controller
/**
* Get the response for a successful password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
@ -35,7 +34,6 @@ class ResetPasswordController extends Controller
/**
* Get the response for a failed password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/

View File

@ -11,23 +11,24 @@ class UserController extends Controller
{
/**
* Get authenticated user.
*
*/
public function current(Request $request)
{
return new UserResource($request->user());
}
public function deleteAccount() {
public function deleteAccount()
{
$this->middleware('auth');
if (Auth::user()->admin) {
return $this->error([
'message' => 'Cannot delete an admin. Stay with us 🙏'
'message' => 'Cannot delete an admin. Stay with us 🙏',
]);
}
Auth::user()->delete();
return $this->success([
'message' => 'User deleted.'
'message' => 'User deleted.',
]);
}
}

View File

@ -24,8 +24,7 @@ class VerificationController extends Controller
/**
* Mark the user's email address as verified.
*
* @param \Illuminate\Http\Request $request
* @param \App\User $user
* @param \App\User $user
* @return \Illuminate\Http\JsonResponse
*/
public function verify(Request $request, User $user)
@ -54,7 +53,6 @@ class VerificationController extends Controller
/**
* Resend the email verification notification.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function resend(Request $request)

View File

@ -15,29 +15,30 @@ class CaddyController extends Controller
]);
// make sure domain is valid
$domain = $request->input('domain');
if (!preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $domain)) {
if (! preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $domain)) {
return $this->error([
'success' => false,
'message' => 'Invalid domain',
]);
}
\Log::info('Caddy request received',[
\Log::info('Caddy request received', [
'domain' => $domain,
]);
if ($workspace = Workspace::whereJsonContains('custom_domains',$domain)->first()) {
\Log::info('Caddy request successful',[
if ($workspace = Workspace::whereJsonContains('custom_domains', $domain)->first()) {
\Log::info('Caddy request successful', [
'domain' => $domain,
'workspace' => $workspace->id,
]);
return $this->success([
'success' => true,
'message' => 'OK',
]);
}
\Log::info('Caddy request failed',[
\Log::info('Caddy request failed', [
'domain' => $domain,
'workspace' => $workspace?->id,
]);

View File

@ -3,19 +3,19 @@
namespace App\Http\Controllers\Content;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ChangelogController extends Controller
{
const CANNY_ENDPOINT = 'https://canny.io/api/v1/';
public const CANNY_ENDPOINT = 'https://canny.io/api/v1/';
public function index()
{
return \Cache::remember('changelog_entries', now()->addHour(), function () {
$response = \Http:: post(self::CANNY_ENDPOINT.'entries/list',[
$response = \Http::post(self::CANNY_ENDPOINT.'entries/list', [
'apiKey' => config('services.canny.api_key'),
'limit' => 3,
]);
return $response->json('entries');
});
}

View File

@ -12,7 +12,6 @@ class FileUploadController extends Controller
/**
* Upload file to local temp
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function upload(Request $request)
@ -23,7 +22,7 @@ class FileUploadController extends Controller
return response()->json([
'uuid' => $uuid,
'key' => $path
'key' => $path,
], 201);
}
}

View File

@ -2,19 +2,15 @@
namespace App\Http\Controllers\Content;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Vapor\Http\Controllers\SignedStorageUrlController as Controller;
use Illuminate\Http\Request;
class SignedStorageUrlController extends Controller
{
/**
* Create a new signed URL.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)

View File

@ -9,19 +9,21 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
public function success($data = [])
{
return response()->json(array_merge([
'type' => 'success'
'type' => 'success',
], $data));
}
public function error($data = [], $statusCode = 400)
{
return response()->json(array_merge([
'type' => 'error'
'type' => 'error',
], $data), $statusCode);
}
}

View File

@ -5,8 +5,6 @@ namespace App\Http\Controllers\Forms;
use App\Http\Controllers\Controller;
use App\Http\Requests\AiGenerateFormRequest;
use App\Models\Forms\AI\AiFormCompletion;
use App\Service\OpenAi\GptCompleter;
use Illuminate\Support\Str;
class AiFormController extends Controller
{
@ -18,8 +16,8 @@ class AiFormController extends Controller
'message' => 'We\'re working on your form, please wait ~1 min.',
'ai_form_completion_id' => AiFormCompletion::create([
'form_prompt' => $request->input('form_prompt'),
'ip' => $request->ip()
])->id
'ip' => $request->ip(),
])->id,
]);
}
@ -30,7 +28,7 @@ class AiFormController extends Controller
}
return $this->success([
'ai_form_completion' => $aiFormCompletion
'ai_form_completion' => $aiFormCompletion,
]);
}
}

View File

@ -17,7 +17,7 @@ use Illuminate\Support\Str;
class FormController extends Controller
{
const ASSETS_UPLOAD_PATH = 'assets/forms';
public const ASSETS_UPLOAD_PATH = 'assets/forms';
private FormCleaner $formCleaner;
@ -36,26 +36,28 @@ class FormController extends Controller
$workspaceIsPro = $workspace->is_pro;
$forms = $workspace->forms()
->orderByDesc('updated_at')
->paginate(10)->through(function (Form $form) use ($workspace, $workspaceIsPro){
->paginate(10)->through(function (Form $form) use ($workspace, $workspaceIsPro) {
// Add attributes for faster loading
$form->extra = (object) [
'loadedWorkspace' => $workspace,
'workspaceIsPro' => $workspaceIsPro,
'userIsOwner' => true,
'cleanings' => $this->formCleaner
->processForm(request(), $form)
->simulateCleaning($workspace)
->getPerformedCleanings()
];
// Add attributes for faster loading
$form->extra = (object) [
'loadedWorkspace' => $workspace,
'workspaceIsPro' => $workspaceIsPro,
'userIsOwner' => true,
'cleanings' => $this->formCleaner
->processForm(request(), $form)
->simulateCleaning($workspace)
->getPerformedCleanings(),
];
return $form;
});
return $form;
});
return FormResource::collection($forms);
}
/**
* Return all user forms, used for zapier
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function indexAll()
@ -66,18 +68,20 @@ class FormController extends Controller
$this->authorize('viewAny', Form::class);
$workspaceIsPro = $workspace->is_pro;
$newForms = $workspace->forms()->get()->map(function (Form $form) use ($workspace, $workspaceIsPro){
$newForms = $workspace->forms()->get()->map(function (Form $form) use ($workspace, $workspaceIsPro) {
// Add attributes for faster loading
$form->extra = (object) [
'loadedWorkspace' => $workspace,
'workspaceIsPro' => $workspaceIsPro,
'userIsOwner' => true,
];
return $form;
});
$forms = $forms->merge($newForms);
}
return FormResource::collection($forms);
}
@ -94,13 +98,13 @@ class FormController extends Controller
->getData();
$form = Form::create(array_merge($formData, [
'creator_id' => $request->user()->id
'creator_id' => $request->user()->id,
]));
return $this->success([
'message' => $this->formCleaner->hasCleaned() ? 'Form successfully created, but the Pro features you used will be disabled when sharing your form:' : 'Form created.' . ($form->visibility == 'draft' ? ' But other people won\'t be able to see the form since it\'s currently in draft mode' : ''),
'message' => $this->formCleaner->hasCleaned() ? 'Form successfully created, but the Pro features you used will be disabled when sharing your form:' : 'Form created.'.($form->visibility == 'draft' ? ' But other people won\'t be able to see the form since it\'s currently in draft mode' : ''),
'form' => (new FormResource($form))->setCleanings($this->formCleaner->getPerformedCleanings()),
'users_first_form' => $request->user()->forms()->count() == 1
'users_first_form' => $request->user()->forms()->count() == 1,
]);
}
@ -116,13 +120,13 @@ class FormController extends Controller
// Set Removed Properties
$formData['removed_properties'] = array_merge($form->removed_properties, collect($form->properties)->filter(function ($field) use ($formData) {
return (!Str::of($field['type'])->startsWith('nf-') && !in_array($field['id'], collect($formData['properties'])->pluck("id")->toArray()));
return ! Str::of($field['type'])->startsWith('nf-') && ! in_array($field['id'], collect($formData['properties'])->pluck('id')->toArray());
})->toArray());
$form->update($formData);
return $this->success([
'message' => $this->formCleaner->hasCleaned() ? 'Form successfully updated, but the Pro features you used will be disabled when sharing your form:' : 'Form updated.' . ($form->visibility == 'draft' ? ' But other people won\'t be able to see the form since it\'s currently in draft mode' : ''),
'message' => $this->formCleaner->hasCleaned() ? 'Form successfully updated, but the Pro features you used will be disabled when sharing your form:' : 'Form updated.'.($form->visibility == 'draft' ? ' But other people won\'t be able to see the form since it\'s currently in draft mode' : ''),
'form' => (new FormResource($form))->setCleanings($this->formCleaner->getPerformedCleanings()),
]);
}
@ -133,8 +137,9 @@ class FormController extends Controller
$this->authorize('delete', $form);
$form->delete();
return $this->success([
'message' => 'Form was deleted.'
'message' => 'Form was deleted.',
]);
}
@ -150,7 +155,7 @@ class FormController extends Controller
return $this->success([
'message' => 'Form successfully duplicated.',
'new_form' => new FormResource($formCopy)
'new_form' => new FormResource($formCopy),
]);
}
@ -159,7 +164,7 @@ class FormController extends Controller
$form = Form::findOrFail($id);
$this->authorize('update', $form);
if ( $option == 'slug') {
if ($option == 'slug') {
$form->generateSlug();
} elseif ($option == 'uuid') {
$form->slug = Str::uuid();
@ -168,7 +173,7 @@ class FormController extends Controller
return $this->success([
'message' => 'Form url successfully updated. Your new form url now is: '.$form->share_url.'.',
'form' => new FormResource($form)
'form' => new FormResource($form),
]);
}
@ -182,8 +187,8 @@ class FormController extends Controller
$fileNameParser = StorageFileNameParser::parse($request->url);
// Make sure we retrieve the file in tmp storage, move it to persistent
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;;
if (!Storage::exists($fileName)) {
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;
if (! Storage::exists($fileName)) {
// File not found, we skip
return null;
}
@ -192,7 +197,7 @@ class FormController extends Controller
return $this->success([
'message' => 'File uploaded.',
'url' => route("forms.assets.show", [$fileNameParser->getMovedFileName()])
'url' => route('forms.assets.show', [$fileNameParser->getMovedFileName()]),
]);
}
@ -205,9 +210,9 @@ class FormController extends Controller
$this->authorize('view', $form);
$path = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $form->id).'/'.$fileName;
if (!Storage::exists($path)) {
if (! Storage::exists($path)) {
return $this->error([
'message' => 'File not found.'
'message' => 'File not found.',
]);
}

View File

@ -19,20 +19,21 @@ class FormStatsController extends Controller
$this->authorize('view', $form);
$formStats = $form->statistics()->where('date','>',now()->subDays(29)->startOfDay())->get();
$periodStats = ["views" => [], "submissions" => []];
$formStats = $form->statistics()->where('date', '>', now()->subDays(29)->startOfDay())->get();
$periodStats = ['views' => [], 'submissions' => []];
foreach (CarbonPeriod::create(now()->subDays(29), now()) as $dateObj) {
$date = $dateObj->format('d-m-Y');
$statisticData = $formStats->where('date', $dateObj->format('Y-m-d'))->first();
$periodStats["views"][$date] = $statisticData->data["views"] ?? 0;
$periodStats["submissions"][$date] = $statisticData->data["submissions"] ?? 0;
$periodStats['views'][$date] = $statisticData->data['views'] ?? 0;
$periodStats['submissions'][$date] = $statisticData->data['submissions'] ?? 0;
if($dateObj->toDateString() === now()->toDateString()){
$periodStats["views"][$date] += $form->views()->count();
$periodStats["submissions"][$date] += $form->submissions()->whereDate('created_at', '>=', now()->startOfDay())->count();
if ($dateObj->toDateString() === now()->toDateString()) {
$periodStats['views'][$date] += $form->views()->count();
$periodStats['submissions'][$date] += $form->submissions()->whereDate('created_at', '>=', now()->startOfDay())->count();
}
}
return $periodStats;
}
}

View File

@ -2,15 +2,14 @@
namespace App\Http\Controllers\Forms;
use App\Http\Controllers\Controller;
use App\Http\Resources\FormSubmissionResource;
use App\Models\Forms\Form;
use App\Exports\FormSubmissionExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\AnswerFormRequest;
use App\Http\Resources\FormSubmissionResource;
use App\Jobs\Form\StoreFormSubmissionJob;
use App\Models\Forms\Form;
use App\Models\Forms\FormSubmission;
use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
@ -26,7 +25,7 @@ class FormSubmissionController extends Controller
public function submissions(string $id)
{
$form = Form::findOrFail((int)$id);
$form = Form::findOrFail((int) $id);
$this->authorize('view', $form);
return FormSubmissionResource::collection($form->submissions()->paginate(100));
@ -40,15 +39,16 @@ class FormSubmissionController extends Controller
$job->setSubmissionId($submissionId)->handle();
$data = new FormSubmissionResource(FormSubmission::findOrFail($submissionId));
return $this->success([
'message' => 'Record successfully updated.',
'data' => $data
'data' => $data,
]);
}
public function export(string $id)
{
$form = Form::findOrFail((int)$id);
$form = Form::findOrFail((int) $id);
$this->authorize('view', $form);
$allRows = [];
@ -61,24 +61,25 @@ class FormSubmissionController extends Controller
->useSignedUrlForFiles();
$allRows[] = [
'id' => Hashids::encode($row['id']),
'created_at' => date("Y-m-d H:i", strtotime($row['created_at'])),
...$formatter->getCleanKeyValue()
'created_at' => date('Y-m-d H:i', strtotime($row['created_at'])),
...$formatter->getCleanKeyValue(),
];
}
$csvExport = (new FormSubmissionExport($allRows));
return Excel::download(
$csvExport,
$form->slug . '-submission-data.csv',
$form->slug.'-submission-data.csv',
\Maatwebsite\Excel\Excel::CSV
);
}
public function submissionFile($id, $fileName)
{
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id) . '/'
. urldecode($fileName);
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/'
.urldecode($fileName);
if (!Storage::exists($fileName)) {
if (! Storage::exists($fileName)) {
return $this->error([
'message' => 'File not found.',
], 404);

View File

@ -5,35 +5,38 @@ namespace App\Http\Controllers\Forms\Integration;
use App\Http\Controllers\Controller;
use App\Http\Requests\Integration\StoreFormZapierWebhookRequest;
use App\Models\Integration\FormZapierWebhook;
use Illuminate\Http\Request;
use Spatie\WebhookServer\WebhookCall;
class FormZapierWebhookController extends Controller
{
/**
* Controller for Zappier webhook subscriptions.
*/
public function __construct() {
// $this->middleware('subscribed');
public function __construct()
{
// $this->middleware('subscribed');
$this->middleware('auth');
}
public function store(StoreFormZapierWebhookRequest $request) {
public function store(StoreFormZapierWebhookRequest $request)
{
$hook = $request->instanciateHook();
$this->authorize('store', $hook);
$hook->save();
return $this->success([
'message' => 'Webhook created.',
'hook' => $hook
'hook' => $hook,
]);
}
public function delete($id) {
public function delete($id)
{
$hook = FormZapierWebhook::findOrFail($id);
$this->authorize('store', $hook);
$hook->delete();
return $this->success([
'message' => 'Webhook deleted.',
]);

View File

@ -11,15 +11,15 @@ use App\Models\Forms\FormSubmission;
use App\Service\Forms\FormCleaner;
use App\Service\WorkspaceHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Vinkla\Hashids\Facades\Hashids;
class PublicFormController extends Controller
{
public const FILE_UPLOAD_PATH = 'forms/?/submissions';
const FILE_UPLOAD_PATH = 'forms/?/submissions';
const TMP_FILE_UPLOAD_PATH = 'tmp/';
public const TMP_FILE_UPLOAD_PATH = 'tmp/';
public function show(Request $request, string $slug)
{
@ -27,21 +27,22 @@ class PublicFormController extends Controller
if ($form->workspace == null) {
// Workspace deleted
return $this->error([
'message' => 'Form not found.'
'message' => 'Form not found.',
], 404);
}
$formCleaner = new FormCleaner();
// Disable pro features if needed
$form->fill($formCleaner
$form->fill(
$formCleaner
->processForm($request, $form)
->performCleaning($form->workspace)
->getData()
);
// Increase form view counter if not login
if(!Auth::check()){
if (! Auth::check()) {
$form->views()->create();
}
@ -53,25 +54,26 @@ class PublicFormController extends Controller
{
// Check that form has user field
$form = $request->form;
if (!$form->has_user_field) {
if (! $form->has_user_field) {
return [];
}
// Use serializer
$workspace = $form->workspace;
return (new WorkspaceHelper($workspace))->getAllUsers();
}
public function showAsset($assetFileName)
{
$path = FormController::ASSETS_UPLOAD_PATH.'/'.$assetFileName;
if (!Storage::exists($path)) {
if (! Storage::exists($path)) {
return $this->error([
'message' => 'File not found.',
'file_name' => $assetFileName
'file_name' => $assetFileName,
]);
}
return redirect()->to(Storage::temporaryUrl($path, now()->addMinutes(5)));
}
@ -84,18 +86,18 @@ class PublicFormController extends Controller
$job = new StoreFormSubmissionJob($form, $request->validated());
$job->handle();
$submissionId = Hashids::encode($job->getSubmissionId());
}else{
} else {
StoreFormSubmissionJob::dispatch($form, $request->validated());
}
return $this->success(array_merge([
'message' => 'Form submission saved.',
'submission_id' => $submissionId
'submission_id' => $submissionId,
], $request->form->is_pro && $request->form->redirect_url ? [
'redirect' => true,
'redirect_url' => $request->form->redirect_url
'redirect_url' => $request->form->redirect_url,
] : [
'redirect' => false
'redirect' => false,
]));
}
@ -104,7 +106,7 @@ class PublicFormController extends Controller
$submissionId = ($submissionId) ? Hashids::decode($submissionId) : false;
$submissionId = isset($submissionId[0]) ? $submissionId[0] : false;
$form = Form::whereSlug($slug)->whereVisibility('public')->firstOrFail();
if ($form->workspace == null || !$form->editable_submissions || !$submissionId) {
if ($form->workspace == null || ! $form->editable_submissions || ! $submissionId) {
return $this->error([
'message' => 'Not allowed.',
]);
@ -120,5 +122,4 @@ class PublicFormController extends Controller
return $this->success(['data' => ($submission) ? $submission->data : []]);
}
}

View File

@ -17,8 +17,7 @@ class RecordController extends Controller
$record->delete();
return $this->success([
'message' => 'Record successfully removed.'
'message' => 'Record successfully removed.',
]);
}
}

View File

@ -10,7 +10,6 @@ class PasswordController extends Controller
/**
* Update the user's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function update(Request $request)

View File

@ -10,7 +10,6 @@ class ProfileController extends Controller
/**
* Update the user's profile information.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function update(Request $request)

View File

@ -2,18 +2,15 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
use App\Models\Template;
use Illuminate\Http\Request;
class SitemapController extends Controller
{
public function index(Request $request)
{
return [
...$this->getTemplatesUrls()
...$this->getTemplatesUrls(),
];
}
@ -23,10 +20,11 @@ class SitemapController extends Controller
Template::where('publicly_listed', true)->chunk(100, function ($templates) use (&$urls) {
foreach ($templates as $template) {
$urls[] = [
'loc' => '/templates/' . $template->slug
'loc' => '/templates/'.$template->slug,
];
}
});
return $urls;
}
}

View File

@ -9,10 +9,11 @@ use Laravel\Cashier\Subscription;
class SubscriptionController extends Controller
{
const SUBSCRIPTION_PLANS = ['monthly', 'yearly'];
public const SUBSCRIPTION_PLANS = ['monthly', 'yearly'];
const PRO_SUBSCRIPTION_NAME = 'default';
const SUBSCRIPTION_NAMES = [
public const PRO_SUBSCRIPTION_NAME = 'default';
public const SUBSCRIPTION_NAMES = [
self::PRO_SUBSCRIPTION_NAME,
];
@ -30,7 +31,7 @@ class SubscriptionController extends Controller
if ($user->subscriptions()->where('stripe_status', 'past_due')->first()) {
return $this->error([
'message' => 'You already have a past due subscription. Please verify your details in the billing page,
and contact us if the issue persists.'
and contact us if the issue persists.',
]);
}
@ -51,18 +52,18 @@ class SubscriptionController extends Controller
'customer_update' => [
'address' => 'auto',
'name' => 'never',
]
],
]);
return $this->success([
'checkout_url' => $checkout->url
'checkout_url' => $checkout->url,
]);
}
public function updateStripeDetails(UpdateStripeDetailsRequest $request)
{
$user = Auth::user();
if (!$user->hasStripeId()) {
if (! $user->hasStripeId()) {
$user->createAsStripeCustomer();
}
$user->updateStripeCustomer([
@ -78,13 +79,14 @@ class SubscriptionController extends Controller
public function billingPortal()
{
$this->middleware('auth');
if (!Auth::user()->has_customer_id) {
if (! Auth::user()->has_customer_id) {
return $this->error([
"message" => "Please subscribe before accessing your billing portal."
'message' => 'Please subscribe before accessing your billing portal.',
]);
}
return $this->success([
'portal_url' => Auth::user()->billingPortalUrl(front_url('/home'))
'portal_url' => Auth::user()->billingPortalUrl(front_url('/home')),
]);
}

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Templates\FormTemplateRequest;
use App\Http\Resources\FormTemplateResource;
use App\Models\Template;
@ -13,20 +12,20 @@ class TemplateController extends Controller
{
public function index(Request $request)
{
$limit = (int)$request->get('limit', 0);
$onlyMy = (bool)$request->get('onlymy', false);
$limit = (int) $request->get('limit', 0);
$onlyMy = (bool) $request->get('onlymy', false);
$templates = Template::when(Auth::check(), function ($query) use ($onlyMy) {
if ($onlyMy) {
$query->where('creator_id', Auth::id());
} else {
$query->where(function ($query) {
$query->where('publicly_listed', true)
->orWhere('creator_id', Auth::id());
});
}
})
->when(!Auth::check(), function ($query) {
if ($onlyMy) {
$query->where('creator_id', Auth::id());
} else {
$query->where(function ($query) {
$query->where('publicly_listed', true)
->orWhere('creator_id', Auth::id());
});
}
})
->when(! Auth::check(), function ($query) {
$query->where('publicly_listed', true);
})
->when($limit > 0, function ($query) use ($limit) {
@ -49,7 +48,7 @@ class TemplateController extends Controller
return $this->success([
'message' => 'Template was created.',
'template_id' => $template->id,
'data' => new FormTemplateResource($template)
'data' => new FormTemplateResource($template),
]);
}
@ -63,7 +62,7 @@ class TemplateController extends Controller
return $this->success([
'message' => 'Template was updated.',
'template_id' => $template->id,
'data' => new FormTemplateResource($template)
'data' => new FormTemplateResource($template),
]);
}

View File

@ -4,8 +4,8 @@ namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\License;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\UnauthorizedException;
class AppSumoController extends Controller
@ -16,6 +16,7 @@ class AppSumoController extends Controller
if ($request->test) {
Log::info('[APPSUMO] test request received', $request->toArray());
return $this->success([
'message' => 'Webhook received.',
'event' => $request->event,
@ -69,11 +70,13 @@ class AppSumoController extends Controller
$license->user_id = $licenseData['user_id'] ?? null;
$license->save();
Log::info('[APPSUMO] creating new license',
Log::info(
'[APPSUMO] creating new license',
[
'license_key' => $license->license_key,
'license_id' => $license->id,
]);
]
);
return $license;
}

View File

@ -5,13 +5,11 @@ namespace App\Http\Controllers\Webhook;
use App\Notifications\Subscription\FailedPaymentNotification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Http\Controllers\WebhookController;
use Stripe\Subscription as StripeSubscription;
class StripeController extends WebhookController
{
public function handleCustomerSubscriptionCreated(array $payload)
{
return parent::handleCustomerSubscriptionCreated($payload);
@ -19,7 +17,7 @@ class StripeController extends WebhookController
/**
* Override to add a sleep, and to detect plan upgrades
* @param array $payload
*
* @return \Symfony\Component\HttpFoundation\Response|void
*/
protected function handleCustomerSubscriptionUpdated(array $payload)
@ -59,7 +57,7 @@ class StripeController extends WebhookController
if (isset($data['trial_end'])) {
$trialEnd = Carbon::createFromTimestamp($data['trial_end']);
if (!$subscription->trial_ends_at || $subscription->trial_ends_at->ne($trialEnd)) {
if (! $subscription->trial_ends_at || $subscription->trial_ends_at->ne($trialEnd)) {
$subscription->trial_ends_at = $trialEnd;
}
}
@ -125,7 +123,7 @@ class StripeController extends WebhookController
return $plan;
}
}
return 'default';
}
}

View File

@ -5,8 +5,8 @@ namespace App\Http\Controllers;
use App\Http\Requests\Workspace\CustomDomainRequest;
use App\Http\Resources\WorkspaceResource;
use App\Models\Workspace;
use Illuminate\Http\Request;
use App\Service\WorkspaceHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class WorkspaceController extends Controller
@ -19,6 +19,7 @@ class WorkspaceController extends Controller
public function index()
{
$this->authorize('viewAny', Workspace::class);
return WorkspaceResource::collection(Auth::user()->workspaces);
}
@ -34,6 +35,7 @@ class WorkspaceController extends Controller
{
$request->workspace->custom_domains = $request->customDomains;
$request->workspace->save();
return new WorkspaceResource($request->workspace);
}
@ -44,9 +46,10 @@ class WorkspaceController extends Controller
$id = $workspace->id;
$workspace->delete();
return $this->success([
'message' => 'Workspace deleted.',
'workspace_id' => $id
'workspace_id' => $id,
]);
}
@ -55,7 +58,7 @@ class WorkspaceController extends Controller
$user = $request->user();
$this->validate($request, [
'name' => 'required'
'name' => 'required',
]);
// Create workspace
@ -67,14 +70,14 @@ class WorkspaceController extends Controller
// Add relation with user
$user->workspaces()->sync([
$workspace->id => [
'role' => 'admin'
]
'role' => 'admin',
],
], false);
return $this->success([
'message' => 'Workspace created.',
'workspace_id' => $workspace->id,
'workspace' => new WorkspaceResource($workspace)
'workspace' => new WorkspaceResource($workspace),
]);
}
}

View File

@ -22,7 +22,7 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
@ -32,7 +32,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\SetLocale::class,
AuthenticateJWT::class,
CustomDomainRestriction::class,
AcceptsJsonMiddleware::class
AcceptsJsonMiddleware::class,
];
/**

View File

@ -10,7 +10,6 @@ class AcceptsJsonMiddleware
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/

View File

@ -8,7 +8,7 @@ use Tymon\JWTAuth\Exceptions\JWTException;
class AuthenticateJWT
{
const API_SERVER_SECRET_HEADER_NAME = 'x-api-secret';
public const API_SERVER_SECRET_HEADER_NAME = 'x-api-secret';
/**
* Verifies the JWT token and validates the IP and User Agent
@ -33,18 +33,19 @@ class AuthenticateJWT
}
$error = null;
if (!\Hash::check($request->ip(), $payload->get('ip'))) {
if (! \Hash::check($request->ip(), $payload->get('ip'))) {
$error = 'Origin IP is invalid';
}
if (!\Hash::check($request->userAgent(), $payload->get('ua'))) {
if (! \Hash::check($request->userAgent(), $payload->get('ua'))) {
$error = 'Origin User Agent is invalid';
}
if ($error) {
auth()->invalidate();
return response()->json([
'message' => $error
'message' => $error,
], 403);
}
}

View File

@ -12,14 +12,14 @@ class CaddyRequestMiddleware
*/
public function handle(Request $request, Closure $next)
{
if (!config('custom-domains.enabled')) {
if (! config('custom-domains.enabled')) {
return response()->json([
'success' => false,
'message' => 'Custom domains not enabled',
], 401);
}
if (config('custom-domains.enabled') && !in_array($request->ip(), config('custom-domains.authorized_ips'))) {
if (config('custom-domains.enabled') && ! in_array($request->ip(), config('custom-domains.authorized_ips'))) {
return response()->json([
'success' => false,
'message' => 'Unauthorized IP',
@ -27,7 +27,7 @@ class CaddyRequestMiddleware
}
$secret = $request->route('secret');
if (config('custom-domains.caddy_secret') && (!$secret || $secret !== config('custom-domains.caddy_secret'))) {
if (config('custom-domains.caddy_secret') && (! $secret || $secret !== config('custom-domains.caddy_secret'))) {
return response()->json([
'success' => false,
'message' => 'Unauthorized',

View File

@ -6,24 +6,24 @@ use App\Http\Requests\Workspace\CustomDomainRequest;
use App\Models\Forms\Form;
use App\Models\Workspace;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class CustomDomainRestriction
{
const CUSTOM_DOMAIN_HEADER = "x-custom-domain";
public const CUSTOM_DOMAIN_HEADER = 'x-custom-domain';
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next)
{
if (!$request->hasHeader(self::CUSTOM_DOMAIN_HEADER) || !config('custom-domains.enabled')) {
if (! $request->hasHeader(self::CUSTOM_DOMAIN_HEADER) || ! config('custom-domains.enabled')) {
return $next($request);
}
$customDomain = $request->header(self::CUSTOM_DOMAIN_HEADER);
if (!preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $customDomain)) {
if (! preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $customDomain)) {
return response()->json([
'success' => false,
'message' => 'Invalid domain',
@ -38,7 +38,7 @@ class CustomDomainRestriction
}
// Check if domain is known
if (!$workspaces = Workspace::whereJsonContains('custom_domains',$customDomain)->get()) {
if (! $workspaces = Workspace::whereJsonContains('custom_domains', $customDomain)->get()) {
return response()->json([
'success' => false,
'message' => 'Unknown domain',

View File

@ -11,7 +11,6 @@ class ProForm
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
@ -22,6 +21,7 @@ class ProForm
$request->merge([
'form' => $form,
]);
return $next($request);
}
}

View File

@ -9,27 +9,26 @@ use Illuminate\Support\Facades\Auth;
class ProtectedForm
{
const PASSWORD_HEADER_NAME = 'form-password';
public const PASSWORD_HEADER_NAME = 'form-password';
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (!$request->route('slug')) {
if (! $request->route('slug')) {
return $next($request);
}
$form = Form::where('slug',$request->route('slug'))->firstOrFail();
$form = Form::where('slug', $request->route('slug'))->firstOrFail();
$request->merge([
'form' => $form,
]);
$userIsFormOwner = Auth::check() && Auth::user()->ownsForm($form);
if (!$userIsFormOwner && $this->isProtected($request, $form)) {
if (! $userIsFormOwner && $this->isProtected($request, $form)) {
return response([
'status' => 'Unauthorized',
'message' => 'Form is protected.',
@ -41,11 +40,11 @@ class ProtectedForm
public static function isProtected(Request $request, Form $form)
{
if (!$form->has_password) {
if (! $form->has_password) {
return false;
}
return !self::hasCorrectPassword($request, $form);
return ! self::hasCorrectPassword($request, $form);
}
public static function hasCorrectPassword(Request $request, Form $form)

View File

@ -11,16 +11,16 @@ class ResolveFormMiddleware
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, string $routeParamName = "id")
{
$form = Form::where($routeParamName,$request->route($routeParamName))->firstOrFail();
public function handle(Request $request, Closure $next, string $routeParamName = 'id')
{
$form = Form::where($routeParamName, $request->route($routeParamName))->firstOrFail();
$request->merge([
'form' => $form,
]);
return $next($request);
}
}

View File

@ -9,14 +9,15 @@ use Tymon\JWTAuth\Exceptions\JWTException;
class ImpersonationMiddleware
{
public const ADMIN_LOG_PREFIX = '[admin_action] ';
const LOG_ROUTES = [
public const LOG_ROUTES = [
'open.forms.store',
'open.forms.update',
'open.forms.duplicate',
'open.forms.regenerate-link',
];
const ALLOWED_ROUTES = [
public const ALLOWED_ROUTES = [
'logout',
// Forms
@ -59,14 +60,13 @@ class ImpersonationMiddleware
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
try {
if (!auth()->check() || !auth()->payload()->get('impersonating')) {
if (! auth()->check() || ! auth()->payload()->get('impersonating')) {
return $next($request);
}
} catch (JWTException $e) {
@ -75,22 +75,22 @@ class ImpersonationMiddleware
// Check that route is allowed
$routeName = $request->route()->getName();
if (!in_array($routeName, self::ALLOWED_ROUTES)) {
if (! in_array($routeName, self::ALLOWED_ROUTES)) {
return response([
'message' => 'Unauthorized when impersonating',
'route' => $routeName,
'impersonator' => auth()->payload()->get('impersonator_id'),
'impersonated_account' => auth()->id(),
'url' => $request->fullUrl(),
'payload' => $request->all()
'payload' => $request->all(),
], 403);
} else if (in_array($routeName, self::LOG_ROUTES)) {
\Log::warning(self::ADMIN_LOG_PREFIX . 'Impersonator action', [
} elseif (in_array($routeName, self::LOG_ROUTES)) {
\Log::warning(self::ADMIN_LOG_PREFIX.'Impersonator action', [
'route' => $routeName,
'url' => $request->fullUrl(),
'impersonated_account' => auth()->id(),
'impersonator' => auth()->payload()->get('impersonator_id'),
'payload' => $request->all()
'payload' => $request->all(),
]);
}

View File

@ -10,13 +10,11 @@ class IsAdmin
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->user() && !$request->user()->admin) {
if ($request->user() && ! $request->user()->admin) {
// This user is not a paying customer...
if ($request->expectsJson()) {
return response([
@ -24,6 +22,7 @@ class IsAdmin
'type' => 'error',
], 403);
}
return redirect('home');
}

View File

@ -10,13 +10,11 @@ class IsModerator
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->user() && !$request->user()->moderator) {
if ($request->user() && ! $request->user()->moderator) {
// This user is not a paying customer...
if ($request->expectsJson()) {
return response([
@ -24,6 +22,7 @@ class IsModerator
'type' => 'error',
], 403);
}
return redirect('home');
}

View File

@ -10,8 +10,6 @@ class IsNotSubscribed
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
@ -24,6 +22,7 @@ class IsNotSubscribed
'type' => 'error',
], 401);
}
return redirect('billing');
}

View File

@ -10,13 +10,11 @@ class IsSubscribed
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->user() && !$request->user()->subscribed()) {
if ($request->user() && ! $request->user()->subscribed()) {
// This user is not a paying customer...
if ($request->expectsJson()) {
return response([
@ -24,6 +22,7 @@ class IsSubscribed
'type' => 'error',
], 401);
}
return redirect('billing');
}

View File

@ -12,8 +12,6 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null ...$guards
* @return mixed
*/

View File

@ -10,7 +10,6 @@ class SetLocale
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
@ -23,7 +22,7 @@ class SetLocale
}
/**
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function parseLocale($request)

View File

@ -18,10 +18,11 @@ class TrimStrings extends Middleware
/**
* The route name where this shouldn't be applied
*
* @var string[]
*/
protected $exceptUrls = [
'/\/api\/forms\/(.*)\/answer/'
'/\/api\/forms\/(.*)\/answer/',
];
public function handle($request, \Closure $next)

View File

@ -14,6 +14,6 @@ class VerifyCsrfToken extends Middleware
protected $except = [
'stripe/webhook',
'vapor/signed-storage-url',
'upload-file'
'upload-file',
];
}

View File

@ -14,7 +14,7 @@ class AiGenerateFormRequest extends FormRequest
public function rules()
{
return [
'form_prompt' => 'required|string|max:1000'
'form_prompt' => 'required|string|max:1000',
];
}
}

View File

@ -3,22 +3,22 @@
namespace App\Http\Requests;
use App\Models\Forms\Form;
use App\Rules\StorageFile;
use App\Service\Forms\FormLogicPropertyResolver;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
use App\Rules\ValidHCaptcha;
use App\Rules\ValidPhoneInputRule;
use App\Rules\ValidUrl;
use App\Service\Forms\FormLogicPropertyResolver;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class AnswerFormRequest extends FormRequest
{
public Form $form;
protected array $requestRules = [];
protected int $maxFileSize;
public function __construct(Request $request)
@ -34,7 +34,7 @@ class AnswerFormRequest extends FormRequest
*/
public function authorize()
{
return !$this->form->is_closed && !$this->form->max_number_of_submissions_reached && $this->form->visibility === 'public';
return ! $this->form->is_closed && ! $this->form->max_number_of_submissions_reached && $this->form->visibility === 'public';
}
/**
@ -56,23 +56,24 @@ class AnswerFormRequest extends FormRequest
$selectionFields = collect($this->form->properties)->filter(function ($pro) {
return in_array($pro['type'], ['select', 'multi_select']);
});
foreach ($selectionFields as $field){
if(isset($data[$field['id']]) && is_array($data[$field['id']])){
foreach ($selectionFields as $field) {
if (isset($data[$field['id']]) && is_array($data[$field['id']])) {
$data[$field['id']] = array_map(function ($val) use ($field) {
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
return ($op['id'] ?? $op['value'] === $val);
return $op['id'] ?? $op['value'] === $val;
});
return isset($tmpop['name']) ? $tmpop['name'] : "";
return isset($tmpop['name']) ? $tmpop['name'] : '';
}, $data[$field['id']]);
}
};
}
if (FormLogicPropertyResolver::isRequired($property, $data)) {
$rules[] = 'required';
if ($property['type'] == 'checkbox') {
// Required for checkboxes means true
$rules[] = 'accepted';
} else if ($property['type'] == 'number' && isset($property['is_rating']) && $property['is_rating']) {
} elseif ($property['type'] == 'number' && isset($property['is_rating']) && $property['is_rating']) {
// For star rating, needs a minimum of 1 star
$rules[] = 'min:1';
}
@ -107,6 +108,7 @@ class AnswerFormRequest extends FormRequest
/**
* Renames validated fields (because field names are ids)
*
* @return array
*/
public function attributes()
@ -115,6 +117,7 @@ class AnswerFormRequest extends FormRequest
foreach ($this->form->properties as $property) {
$fields[$property['id']] = $property['name'];
}
return $fields;
}
@ -127,21 +130,21 @@ class AnswerFormRequest extends FormRequest
{
$messages = [];
foreach ($this->form->properties as $property) {
if($property['type'] == 'date' && isset($property['date_range']) && $property['date_range']){
$messages[$property['id'].'.0.required_with'] = "From date is required";
$messages[$property['id'].'.1.required_with'] = "To date is required";
$messages[$property['id'].'.0.before_or_equal'] = "From date must be before or equal To date";
if ($property['type'] == 'date' && isset($property['date_range']) && $property['date_range']) {
$messages[$property['id'].'.0.required_with'] = 'From date is required';
$messages[$property['id'].'.1.required_with'] = 'To date is required';
$messages[$property['id'].'.0.before_or_equal'] = 'From date must be before or equal To date';
}
if ($property['type'] == 'number' && isset($property['is_rating']) && $property['is_rating']) {
$messages[$property['id'] . '.min'] = "A rating must be selected";
$messages[$property['id'].'.min'] = 'A rating must be selected';
}
}
return $messages;
}
/**
* Return validation rules for a given form property
* @param $property
*/
private function getPropertyRules($property): array
{
@ -153,27 +156,32 @@ class AnswerFormRequest extends FormRequest
if ($property['is_rating'] ?? false) {
return ['numeric'];
}
return ['numeric'];
case 'select':
case 'multi_select':
if (($property['allow_creation'] ?? false)) {
return ['string'];
}
return [Rule::in($this->getSelectPropertyOptions($property))];
case 'checkbox':
return ['boolean'];
case 'url':
if (isset($property['file_upload']) && $property['file_upload']) {
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, [], $this->form)];
return ['array'];
}
return [new ValidUrl];
return [new ValidUrl()];
case 'files':
$allowedFileTypes = [];
if(!empty($property['allowed_file_types'])){
$allowedFileTypes = explode(",", $property['allowed_file_types']);
if (! empty($property['allowed_file_types'])) {
$allowedFileTypes = explode(',', $property['allowed_file_types']);
}
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, $allowedFileTypes, $this->form)];
return ['array'];
case 'email':
return ['email:filter'];
@ -182,14 +190,17 @@ class AnswerFormRequest extends FormRequest
$this->requestRules[$property['id'].'.*'] = $this->getRulesForDate($property);
$this->requestRules[$property['id'].'.0'] = ['required_with:'.$property['id'].'.1', 'before_or_equal:'.$property['id'].'.1'];
$this->requestRules[$property['id'].'.1'] = ['required_with:'.$property['id'].'.0'];
return ['array', 'min:2'];
}
return $this->getRulesForDate($property);
case 'phone_number':
if (isset($property['use_simple_text_input']) && $property['use_simple_text_input']) {
return ['string'];
}
return ['string', 'min:6', new ValidPhoneInputRule];
return ['string', 'min:6', new ValidPhoneInputRule()];
default:
return [];
}
@ -199,18 +210,20 @@ class AnswerFormRequest extends FormRequest
{
if (isset($property['disable_past_dates']) && $property['disable_past_dates']) {
return ['date', 'after_or_equal:today'];
}else if (isset($property['disable_future_dates']) && $property['disable_future_dates']) {
} elseif (isset($property['disable_future_dates']) && $property['disable_future_dates']) {
return ['date', 'before_or_equal:today'];
}
return ['date'];
}
private function getSelectPropertyOptions($property): array
{
$type = $property['type'];
if (!isset($property[$type])) {
if (! isset($property[$type])) {
return [];
}
return array_column($property[$type]['options'], 'name');
}
@ -223,25 +236,26 @@ class AnswerFormRequest extends FormRequest
$receivedValue = $receivedData[$property['id']] ?? null;
// Escape all '\' in select options
if(in_array($property['type'], ['select', 'multi_select']) && !is_null($receivedValue)){
if (in_array($property['type'], ['select', 'multi_select']) && ! is_null($receivedValue)) {
if (is_array($receivedValue)) {
$mergeData[$property['id']] = collect($receivedValue)->map(function ($value) {
$value = Str::of($value);
return $value->replace(
["\e", "\f", "\n", "\r", "\t", "\v", "\\"],
["\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\"]
["\e", "\f", "\n", "\r", "\t", "\v", '\\'],
['\\e', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\\']
)->toString();
})->toArray();
} else {
$receivedValue = Str::of($receivedValue);
$mergeData[$property['id']] = $receivedValue->replace(
["\e", "\f", "\n", "\r", "\t", "\v", "\\"],
["\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\"]
["\e", "\f", "\n", "\r", "\t", "\v", '\\'],
['\\e', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\\']
)->toString();
}
}
if($property['type'] === 'phone_number' && (!isset($property['use_simple_text_input']) || !$property['use_simple_text_input']) && $receivedValue && in_array($receivedValue, $countryCodeMapper)){
if ($property['type'] === 'phone_number' && (! isset($property['use_simple_text_input']) || ! $property['use_simple_text_input']) && $receivedValue && in_array($receivedValue, $countryCodeMapper)) {
$mergeData[$property['id']] = null;
}
});

View File

@ -17,12 +17,14 @@ class StoreFormZapierWebhookRequest extends FormRequest
{
return [
'form_slug' => 'required|exists:forms,slug',
'hook_url' => 'required|string|url'
'hook_url' => 'required|string|url',
];
}
public function instanciateHook() {
public function instanciateHook()
{
$form = Form::whereSlug($this->form_slug)->firstOrFail();
return new FormZapierWebhook([
'form_id' => $form->id,
'hook_url' => $this->hook_url,

View File

@ -2,9 +2,6 @@
namespace App\Http\Requests;
use App\Models\Forms\Form;
use Illuminate\Validation\Rule;
class StoreFormRequest extends UserFormRequest
{
/**

View File

@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
class FormTemplateRequest extends FormRequest
{
const IGNORED_KEYS = [
public const IGNORED_KEYS = [
'id',
'creator',
'cleanings',
@ -49,9 +49,10 @@ class FormTemplateRequest extends FormRequest
public function rules()
{
$slugRule = '';
if($this->id){
if ($this->id) {
$slugRule = ','.$this->id;
}
return [
'form' => 'required|array',
'publicly_listed' => 'boolean',
@ -88,7 +89,7 @@ class FormTemplateRequest extends FormRequest
'types' => $this->types ?? [],
'industries' => $this->industries ?? [],
'related_templates' => $this->related_templates ?? [],
'questions' => $this->questions ?? []
'questions' => $this->questions ?? [],
]);
}
}

View File

@ -4,5 +4,4 @@ namespace App\Http\Requests;
class UpdateFormRequest extends UserFormRequest
{
}

View File

@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
class UploadAssetRequest extends FormRequest
{
const FORM_ASSET_MAX_SIZE = 5000000;
public const FORM_ASSET_MAX_SIZE = 5000000;
/**
* Get the validation rules that apply to the request.
@ -22,7 +22,7 @@ class UploadAssetRequest extends FormRequest
'jpg',
'bmp',
'gif',
'svg'
'svg',
];
if ($this->offsetExists('type') && $this->get('type') === 'files') {
$fileTypes = [];

View File

@ -1,20 +1,17 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Workspace\CustomDomainRequest;
use App\Models\Forms\Form;
use App\Rules\FormPropertyLogicRule;
use App\Rules\OneEmailPerLine;
use Illuminate\Validation\Rule;
use App\Rules\FormPropertyLogicRule;
/**
* Abstract class to validate create/update forms
*
* Class UserFormRequest
* @package App\Http\Requests
*/
abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
{
@ -30,11 +27,11 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
'title' => 'required|string|max:60',
'description' => 'nullable|string|max:2000',
'tags' => 'nullable|array',
'visibility' => ['required',Rule::in(Form::VISIBILITY)],
'visibility' => ['required', Rule::in(Form::VISIBILITY)],
// Notifications
'notifies' => 'boolean',
'notification_emails' => ['required_if:notifies,1', new OneEmailPerLine ],
'notification_emails' => ['required_if:notifies,1', new OneEmailPerLine()],
'send_submission_confirmation' => 'boolean',
'notification_sender' => 'string|max:64',
'notification_subject' => 'string|max:200',
@ -47,11 +44,11 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
'notification_settings' => 'nullable',
// Customization
'theme' => ['required',Rule::in(Form::THEMES)],
'width' => ['required',Rule::in(Form::WIDTHS)],
'theme' => ['required', Rule::in(Form::THEMES)],
'width' => ['required', Rule::in(Form::WIDTHS)],
'cover_picture' => 'url|nullable',
'logo_picture' => 'url|nullable',
'dark_mode' => ['required',Rule::in(Form::DARK_MODE_VALUES)],
'dark_mode' => ['required', Rule::in(Form::DARK_MODE_VALUES)],
'color' => 'required|string',
'hide_title' => 'required|boolean',
'uppercase_labels' => 'required|boolean',
@ -75,7 +72,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
'editable_submissions' => 'boolean|nullable',
'editable_submissions_button_text' => 'string|min:1|max:50',
'confetti_on_submission' => 'boolean',
'auto_save'=> 'boolean',
'auto_save' => 'boolean',
// Properties
'properties' => 'required|array',
@ -90,7 +87,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
'properties.*.required' => 'boolean|nullable',
'properties.*.multiple' => 'boolean|nullable',
'properties.*.timezone' => 'sometimes|nullable',
'properties.*.width' => ['sometimes', Rule::in(['full','1/2','1/3','2/3','1/3','3/4','1/4'])],
'properties.*.width' => ['sometimes', Rule::in(['full', '1/2', '1/3', '2/3', '1/3', '3/4', '1/4'])],
'properties.*.align' => ['sometimes', Rule::in(['left', 'center', 'right', 'justify'])],
'properties.*.allowed_file_types' => 'sometimes|nullable',
'properties.*.use_toggle_switch' => 'boolean|nullable',
@ -127,7 +124,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
// Custom SEO
'seo_meta' => 'nullable|array',
'custom_domain' => 'sometimes|nullable|regex:'. CustomDomainRequest::CUSTOM_DOMAINS_REGEX,
'custom_domain' => 'sometimes|nullable|regex:'.CustomDomainRequest::CUSTOM_DOMAINS_REGEX,
];
}

View File

@ -8,10 +8,12 @@ use Illuminate\Http\Request;
class CustomDomainRequest extends FormRequest
{
const CUSTOM_DOMAINS_REGEX = '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,20}$/';
public const CUSTOM_DOMAINS_REGEX = '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,20}$/';
public Workspace $workspace;
public array $customDomains = [];
public function __construct(Request $request, Workspace $workspace)
{
$this->workspace = Workspace::findOrFail($request->workspaceId);
@ -28,13 +30,13 @@ class CustomDomainRequest extends FormRequest
'custom_domains' => [
'present',
'array',
function($attribute, $value, $fail) {
function ($attribute, $value, $fail) {
$errors = [];
$domains = collect($value)->filter(function ($domain) {
return !empty( trim($domain) );
})->each(function($domain) use (&$errors) {
if (!preg_match(self::CUSTOM_DOMAINS_REGEX, $domain)) {
$errors[] = 'Invalid domain: ' . $domain;
return ! empty(trim($domain));
})->each(function ($domain) use (&$errors) {
if (! preg_match(self::CUSTOM_DOMAINS_REGEX, $domain)) {
$errors[] = 'Invalid domain: '.$domain;
}
});
@ -44,16 +46,17 @@ class CustomDomainRequest extends FormRequest
$limit = $this->workspace->custom_domain_count_limit;
if ($limit && $domains->count() > $limit) {
$fail('You can only add ' . $limit . ' domain(s).');
$fail('You can only add '.$limit.' domain(s).');
}
$this->customDomains = $domains->toArray();
}
]
},
],
];
}
protected function passedValidation(){
protected function passedValidation()
{
$this->replace(['custom_domains' => $this->customDomains]);
}
}

View File

@ -3,10 +3,8 @@
namespace App\Http\Resources;
use App\Http\Middleware\Form\ProtectedForm;
use App\Http\Resources\UserResource;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class FormResource extends JsonResource
{
@ -20,7 +18,7 @@ class FormResource extends JsonResource
*/
public function toArray($request)
{
if(!$this->userIsFormOwner() && ProtectedForm::isProtected($request, $this->resource)){
if (! $this->userIsFormOwner() && ProtectedForm::isProtected($request, $this->resource)) {
return $this->getProtectedForm();
}
@ -69,6 +67,7 @@ class FormResource extends JsonResource
public function setCleanings(array $cleanings)
{
$this->cleanings = $cleanings;
return $this;
}
@ -96,15 +95,18 @@ class FormResource extends JsonResource
];
}
private function getWorkspace() {
private function getWorkspace()
{
return $this->extra?->loadedWorkspace ?? $this->workspace;
}
private function workspaceIsPro() {
private function workspaceIsPro()
{
return $this->extra?->workspaceIsPro ?? $this->getWorkspace()->is_pro ?? $this->is_pro;
}
private function userIsFormOwner() {
private function userIsFormOwner()
{
return $this->extra?->userIsOwner ??
(
Auth::check() && Auth::user()->ownsForm($this->resource)

View File

@ -6,7 +6,6 @@ use Illuminate\Http\Resources\Json\JsonResource;
class FormSubmissionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
@ -28,13 +27,14 @@ class FormSubmissionResource extends JsonResource
private function addExtraData()
{
$this->data = array_merge($this->data, [
"created_at" => $this->created_at->toDateTimeString(),
'id' => $this->id
'created_at' => $this->created_at->toDateTimeString(),
'id' => $this->id,
]);
}
/**
* Link to the file (generating signed s3 URL)
*
* @return void
*/
private function generateFileLinks()
@ -45,7 +45,7 @@ class FormSubmissionResource extends JsonResource
return in_array($field['type'], ['files', 'signature']);
});
foreach ($fileFields as $field) {
if (isset($data[$field['id']]) && !empty($data[$field['id']])) {
if (isset($data[$field['id']]) && ! empty($data[$field['id']])) {
$data[$field['id']] = collect($data[$field['id']])->filter(function ($file) {
return $file !== null && $file;
})->map(function ($file) {

View File

@ -15,7 +15,7 @@ class FormTemplateResource extends JsonResource
public function toArray($request)
{
return array_merge(parent::toArray($request), [
'is_new' => $this->created_at->isAfter(now()->subDays(7))
'is_new' => $this->created_at->isAfter(now()->subDays(7)),
]);
}
}

View File

@ -9,7 +9,7 @@ class UserResource extends JsonResource
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)

View File

@ -3,7 +3,6 @@
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
class WorkspaceResource extends JsonResource
{
@ -12,7 +11,7 @@ class WorkspaceResource extends JsonResource
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)

View File

@ -14,7 +14,10 @@ use Illuminate\Support\Str;
class GenerateAiForm implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* Create a new job instance.
@ -34,7 +37,7 @@ class GenerateAiForm implements ShouldQueue
public function handle()
{
$this->completion->update([
'status' => AiFormCompletion::STATUS_PROCESSING
'status' => AiFormCompletion::STATUS_PROCESSING,
]);
$completer = (new GptCompleter(config('services.openai.api_key')))
@ -44,13 +47,13 @@ class GenerateAiForm implements ShouldQueue
try {
$completer->completeChat([
["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT)
->replace('[REPLACE]', $this->completion->form_prompt)->toString()]
['role' => 'user', 'content' => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT)
->replace('[REPLACE]', $this->completion->form_prompt)->toString()],
], 3000);
$this->completion->update([
'status' => AiFormCompletion::STATUS_COMPLETED,
'result' => $this->cleanOutput($completer->getArray())
'result' => $this->cleanOutput($completer->getArray()),
]);
} catch (\Exception $e) {
$this->onError($e);
@ -76,10 +79,11 @@ class GenerateAiForm implements ShouldQueue
$this->onError($exception);
}
private function onError(\Throwable $e) {
private function onError(\Throwable $e)
{
$this->completion->update([
'status' => AiFormCompletion::STATUS_FAILED,
'result' => ['error' => $e->getMessage()]
'result' => ['error' => $e->getMessage()],
]);
}
}

View File

@ -3,15 +3,14 @@
namespace App\Jobs\Form;
use App\Events\Forms\FormSubmitted;
use App\Http\Controllers\Forms\PublicFormController;
use App\Http\Controllers\Forms\FormController;
use App\Http\Controllers\Forms\PublicFormController;
use App\Http\Requests\AnswerFormRequest;
use App\Models\Forms\Form;
use App\Models\Forms\FormSubmission;
use App\Service\Forms\FormLogicPropertyResolver;
use App\Service\Storage\StorageFileNameParser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
@ -22,7 +21,10 @@ use Vinkla\Hashids\Facades\Hashids;
class StoreFormSubmissionJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public ?string $submissionId = null;
@ -47,7 +49,7 @@ class StoreFormSubmissionJob implements ShouldQueue
$this->storeSubmission($formData);
$formData["submission_id"] = $this->submissionId;
$formData['submission_id'] = $this->submissionId;
FormSubmitted::dispatch($this->form, $formData);
}
@ -59,6 +61,7 @@ class StoreFormSubmissionJob implements ShouldQueue
public function setSubmissionId(int $id)
{
$this->submissionId = $id;
return $this;
}
@ -82,12 +85,13 @@ class StoreFormSubmissionJob implements ShouldQueue
*/
private function submissionToUpdate(): ?FormSubmission
{
if($this->submissionId){
if ($this->submissionId) {
return $this->form->submissions()->findOrFail($this->submissionId);
}
if ($this->form->editable_submissions && isset($this->submissionData['submission_id']) && $this->submissionData['submission_id']) {
$submissionId = $this->submissionData['submission_id'] ? Hashids::decode($this->submissionData['submission_id']) : false;
$submissionId = $submissionId[0] ?? null;
return $this->form->submissions()->findOrFail($submissionId);
}
@ -111,7 +115,7 @@ class StoreFormSubmissionJob implements ShouldQueue
// Do required transformation per type (e.g. file uploads)
foreach ($data as $answerKey => $answerValue) {
$field = $properties->where('id', $answerKey)->first();
if (!$field) {
if (! $field) {
continue;
}
@ -128,10 +132,10 @@ class StoreFormSubmissionJob implements ShouldQueue
}
} else {
if ($field['type'] == 'text' && isset($field['generates_uuid']) && $field['generates_uuid']) {
$finalData[$field['id']] = ($this->form->is_pro) ? Str::uuid() : "Please upgrade your OpenForm subscription to use our ID generation features";
$finalData[$field['id']] = ($this->form->is_pro) ? Str::uuid() : 'Please upgrade your OpenForm subscription to use our ID generation features';
} else {
if ($field['type'] == 'text' && isset($field['generates_auto_increment_id']) && $field['generates_auto_increment_id']) {
$finalData[$field['id']] = ($this->form->is_pro) ? (string) ($this->form->submissions_count + 1) : "Please upgrade your OpenForm subscription to use our ID generation features";
$finalData[$field['id']] = ($this->form->is_pro) ? (string) ($this->form->submissions_count + 1) : 'Please upgrade your OpenForm subscription to use our ID generation features';
} else {
$finalData[$field['id']] = $answerValue;
}
@ -139,12 +143,12 @@ class StoreFormSubmissionJob implements ShouldQueue
}
// For Singrature
if($this->form->is_pro && $field['type'] == 'signature') {
if ($this->form->is_pro && $field['type'] == 'signature') {
$finalData[$field['id']] = $this->storeSignature($answerValue);
}
// For Phone
if($field['type'] == 'phone_number' && $answerValue && ctype_alpha(substr($answerValue, 0, 2)) && (!isset($field['use_simple_text_input']) || !$field['use_simple_text_input'])) {
if ($field['type'] == 'phone_number' && $answerValue && ctype_alpha(substr($answerValue, 0, 2)) && (! isset($field['use_simple_text_input']) || ! $field['use_simple_text_input'])) {
$finalData[$field['id']] = substr($answerValue, 2);
}
}
@ -156,6 +160,7 @@ class StoreFormSubmissionJob implements ShouldQueue
private function isSkipForUpload($value)
{
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
return Storage::exists($newPath.'/'.$value);
}
@ -173,15 +178,16 @@ class StoreFormSubmissionJob implements ShouldQueue
return null;
}
if(filter_var($value, FILTER_VALIDATE_URL) !== false && str_contains($value, parse_url(config('app.url'))['host'])) { // In case of prefill we have full url so convert to s3
if (filter_var($value, FILTER_VALIDATE_URL) !== false && str_contains($value, parse_url(config('app.url'))['host'])) { // In case of prefill we have full url so convert to s3
$fileName = basename($value);
$path = FormController::ASSETS_UPLOAD_PATH . '/' . $fileName;
$path = FormController::ASSETS_UPLOAD_PATH.'/'.$fileName;
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
Storage::move($path, $newPath.'/'.$fileName);
return $fileName;
}
if($this->isSkipForUpload($value)) {
if ($this->isSkipForUpload($value)) {
return $value;
}
@ -189,7 +195,7 @@ class StoreFormSubmissionJob implements ShouldQueue
// Make sure we retrieve the file in tmp storage, move it to persistent
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;
if (!Storage::exists($fileName)) {
if (! Storage::exists($fileName)) {
// File not found, we skip
return null;
}
@ -209,7 +215,7 @@ class StoreFormSubmissionJob implements ShouldQueue
private function storeSignature(?string $value)
{
if ($value == null || !isset(explode(',', $value)[1])) {
if ($value == null || ! isset(explode(',', $value)[1])) {
return null;
}
@ -225,7 +231,6 @@ class StoreFormSubmissionJob implements ShouldQueue
/**
* Adds prefill from hidden fields
*
* @param array $formData
* @param AnswerFormRequest $request
*/
private function addHiddenPrefills(array &$formData): void
@ -235,9 +240,9 @@ class StoreFormSubmissionJob implements ShouldQueue
return isset($property['hidden'])
&& isset($property['prefill'])
&& FormLogicPropertyResolver::isHidden($property, $this->submissionData)
&& !is_null($property['prefill'])
&& !in_array($property['type'], ['files'])
&& !($property['type'] == 'url' && isset($property['file_upload']) && $property['file_upload']);
&& ! is_null($property['prefill'])
&& ! in_array($property['type'], ['files'])
&& ! ($property['type'] == 'url' && isset($property['file_upload']) && $property['file_upload']);
})->each(function (array $property) use (&$formData) {
if ($property['type'] === 'date' && isset($property['prefill_today']) && $property['prefill_today']) {
$formData[$property['id']] = now()->format((isset($property['with_time']) && $property['with_time']) ? 'Y-m-d H:i' : 'Y-m-d');

View File

@ -3,8 +3,6 @@
namespace App\Listeners;
use App\Notifications\Forms\FailedWebhookNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
class FailedWebhookListener
@ -12,7 +10,7 @@ class FailedWebhookListener
/**
* Handle the event.
*
* @param object $event
* @param object $event
* @return void
*/
public function handle(WebhookCallFailedEvent $event)
@ -24,15 +22,16 @@ class FailedWebhookListener
'webhook_url' => $event->webhookUrl,
'exception' => $event->errorType,
'message' => $event->errorMessage,
'form_id' => $event->meta['form']->id
'form_id' => $event->meta['form']->id,
]);
return;
}
\Log::error('Failed webhook', [
'webhook_url' => $event->webhookUrl,
'exception' => $event->errorType,
'message' => $event->errorMessage
'message' => $event->errorMessage,
]);
}
}

View File

@ -5,12 +5,10 @@ namespace App\Listeners\Forms;
use App\Events\Models\FormCreated;
use App\Mail\Forms\FormCreationConfirmationMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
class FormCreationConfirmation implements ShouldQueue
{
/**
* Handle the event.
*

View File

@ -2,16 +2,13 @@
namespace App\Listeners\Forms;
use App\Models\Forms\Form;
use App\Events\Forms\FormSubmitted;
use Illuminate\Support\Facades\Http;
use Spatie\WebhookServer\WebhookCall;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Notification;
use App\Service\Forms\FormSubmissionFormatter;
use App\Service\Forms\Webhooks\WebhookHandlerProvider;
use App\Models\Forms\Form;
use App\Notifications\Forms\FormSubmissionNotification;
use App\Service\Forms\Webhooks\WebhookHandlerProvider;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class NotifyFormSubmission implements ShouldQueue
{
@ -46,12 +43,14 @@ class NotifyFormSubmission implements ShouldQueue
/**
* Sends an email to each email address in the form's notification_emails field
* @param FormSubmitted $event
*
* @return void
*/
private function sendEmailNotifications(FormSubmitted $event)
{
if (!$event->form->is_pro || !$event->form->notifies) return;
if (! $event->form->is_pro || ! $event->form->notifies) {
return;
}
$subscribers = collect(preg_split("/\r\n|\n|\r/", $event->form->notification_emails))->filter(function (
$email

View File

@ -12,13 +12,12 @@ use Illuminate\Support\Facades\Mail;
* Sends a confirmation to form respondant that form was submitted
*
* Class SubmissionConfirmation
* @package App\Listeners\Forms
*/
class SubmissionConfirmation implements ShouldQueue
{
use InteractsWithQueue;
const RISKY_USERS_LIMIT = 120;
public const RISKY_USERS_LIMIT = 120;
/**
* Handle the event.
@ -29,17 +28,19 @@ class SubmissionConfirmation implements ShouldQueue
public function handle(FormSubmitted $event)
{
if (
!$event->form->is_pro ||
!$event->form->send_submission_confirmation ||
! $event->form->is_pro ||
! $event->form->send_submission_confirmation ||
$this->riskLimitReached($event) // To avoid phishing abuse we limit this feature for risky users
) {
return;
}
$email = $this->getRespondentEmail($event);
if (!$email) return;
if (! $email) {
return;
}
\Log::info('Sending submission confirmation',[
\Log::info('Sending submission confirmation', [
'recipient' => $email,
'form_id' => $event->form->id,
'form_slug' => $event->form->slug,
@ -50,15 +51,20 @@ class SubmissionConfirmation implements ShouldQueue
private function getRespondentEmail(FormSubmitted $event)
{
// Make sure we only have one email field in the form
$emailFields = collect($event->form->properties)->filter(function($field) {
$hidden = $field['hidden']?? false;
return !$hidden && $field['type'] == 'email';
$emailFields = collect($event->form->properties)->filter(function ($field) {
$hidden = $field['hidden'] ?? false;
return ! $hidden && $field['type'] == 'email';
});
if ($emailFields->count() != 1) return null;
if ($emailFields->count() != 1) {
return null;
}
if (isset($event->data[$emailFields->first()['id']])) {
$email = $event->data[$emailFields->first()['id']];
if ($this->validateEmail($email)) return $email;
if ($this->validateEmail($email)) {
return $email;
}
}
return null;
@ -73,13 +79,16 @@ class SubmissionConfirmation implements ShouldQueue
'form_id' => $event->form->id,
'workspace_id' => $event->form->workspace->id,
]);
return true;
}
}
return false;
}
public static function validateEmail($email): bool {
return (boolean) filter_var($email, FILTER_VALIDATE_EMAIL);
public static function validateEmail($email): bool
{
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
}
}

View File

@ -6,12 +6,12 @@ use App\Mail\OpenFormMail;
use App\Models\Forms\Form;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class FormCreationConfirmationMail extends OpenFormMail implements ShouldQueue
{
use Queueable, SerializesModels;
use Queueable;
use SerializesModels;
/**
* Create a new message instance.
@ -31,8 +31,8 @@ class FormCreationConfirmationMail extends OpenFormMail implements ShouldQueue
public function build()
{
return $this
->markdown('mail.form.created',[
'form' => $this->form,
])->subject('Your form was created!');
->markdown('mail.form.created', [
'form' => $this->form,
])->subject('Your form was created!');
}
}

View File

@ -8,13 +8,14 @@ use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Vinkla\Hashids\Facades\Hashids;
class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
{
use Queueable, SerializesModels;
use Queueable;
use SerializesModels;
/**
* Create a new message instance.
@ -22,7 +23,8 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
* @return void
*/
public function __construct(private FormSubmitted $event)
{}
{
}
/**
* Build the message.
@ -42,23 +44,25 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
->replyTo($this->getReplyToEmail($form->creator->email))
->from($this->getFromEmail(), $form->notification_sender)
->subject($form->notification_subject)
->markdown('mail.form.confirmation-submission-notification',[
->markdown('mail.form.confirmation-submission-notification', [
'fields' => $formatter->getFieldsWithValue(),
'form' => $form,
'noBranding' => $form->no_branding,
'submission_id' => (isset($this->event->data['submission_id']) && $this->event->data['submission_id']) ? Hashids::encode($this->event->data['submission_id']) : null
'submission_id' => (isset($this->event->data['submission_id']) && $this->event->data['submission_id']) ? Hashids::encode($this->event->data['submission_id']) : null,
]);
}
private function getFromEmail()
{
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
return $originalFromAddress->first(). '+' . time() . '@' . $originalFromAddress->last();
return $originalFromAddress->first().'+'.time().'@'.$originalFromAddress->last();
}
private function getReplyToEmail($default)
{
$replyTo = Arr::get((array)$this->event->form->notification_settings, 'confirmation_reply_to', null);
$replyTo = Arr::get((array) $this->event->form->notification_settings, 'confirmation_reply_to', null);
return $replyTo ?? $default;
}
}

View File

@ -3,11 +3,11 @@
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
abstract class OpenFormMail extends Mailable
{
use Queueable, SerializesModels;
use Queueable;
use SerializesModels;
}

View File

@ -10,10 +10,13 @@ class AiFormCompletion extends Model
{
use HasFactory;
const STATUS_PENDING = 'pending';
const STATUS_PROCESSING = 'processing';
const STATUS_COMPLETED = 'completed';
const STATUS_FAILED = 'failed';
public const STATUS_PENDING = 'pending';
public const STATUS_PROCESSING = 'processing';
public const STATUS_COMPLETED = 'completed';
public const STATUS_FAILED = 'failed';
protected $table = 'ai_form_completions';
@ -21,11 +24,11 @@ class AiFormCompletion extends Model
'form_prompt',
'status',
'result',
'ip'
'ip',
];
protected $attributes = [
'status' => self::STATUS_PENDING
'status' => self::STATUS_PENDING,
];
protected static function booted()

View File

@ -9,26 +9,31 @@ use App\Models\Traits\CachesAttributes;
use App\Models\User;
use App\Models\Workspace;
use Database\Factories\FormFactory;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Stevebauman\Purify\Facades\Purify;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Form extends Model implements CachableAttributes
{
use CachesAttributes;
const DARK_MODE_VALUES = ['auto', 'light', 'dark'];
const THEMES = ['default', 'simple', 'notion'];
const WIDTHS = ['centered', 'full'];
const VISIBILITY = ['public', 'draft', 'closed'];
use HasFactory;
use HasSlug;
use SoftDeletes;
use HasFactory, HasSlug, SoftDeletes;
public const DARK_MODE_VALUES = ['auto', 'light', 'dark'];
public const THEMES = ['default', 'simple', 'notion'];
public const WIDTHS = ['centered', 'full'];
public const VISIBILITY = ['public', 'draft', 'closed'];
protected $fillable = [
'workspace_id',
@ -94,7 +99,7 @@ class Form extends Model implements CachableAttributes
'password',
// Custom SEO
'seo_meta'
'seo_meta',
];
protected $casts = [
@ -104,7 +109,7 @@ class Form extends Model implements CachableAttributes
'tags' => 'array',
'removed_properties' => 'array',
'seo_meta' => 'object',
'notification_settings' => 'object'
'notification_settings' => 'object',
];
protected $appends = [
@ -127,13 +132,13 @@ class Form extends Model implements CachableAttributes
'password',
'tags',
'notification_emails',
'removed_properties'
'removed_properties',
];
protected $cachableAttributes = [
'is_pro',
'views_count',
'max_file_size'
'max_file_size',
];
/**
@ -155,14 +160,15 @@ class Form extends Model implements CachableAttributes
public function getShareUrlAttribute()
{
if ($this->custom_domain) {
return 'https://' . $this->custom_domain . '/forms/' . $this->slug;
return 'https://'.$this->custom_domain.'/forms/'.$this->slug;
}
return front_url('/forms/' . $this->slug);
return front_url('/forms/'.$this->slug);
}
public function getEditUrlAttribute()
{
return front_url('/forms/' . $this->slug . '/show');
return front_url('/forms/'.$this->slug.'/show');
}
public function getSubmissionsCountAttribute()
@ -174,9 +180,10 @@ class Form extends Model implements CachableAttributes
{
return $this->remember('views_count', 15 * 60, function (): int {
if (env('DB_CONNECTION') == 'mysql') {
return (int)($this->views()->count() +
return (int) ($this->views()->count() +
$this->statistics()->sum(DB::raw("json_extract(data, '$.views')")));
}
return $this->views()->count() +
$this->statistics()->sum(DB::raw("cast(data->>'views' as integer)"));
});
@ -204,20 +211,21 @@ class Form extends Model implements CachableAttributes
public function getIsClosedAttribute()
{
return ($this->closes_at && now()->gt($this->closes_at));
return $this->closes_at && now()->gt($this->closes_at);
}
public function getFormPendingSubmissionKeyAttribute()
{
if ($this->updated_at?->timestamp) {
return "openform-" . $this->id . "-pending-submission-" . substr($this->updated_at?->timestamp, -6);
return 'openform-'.$this->id.'-pending-submission-'.substr($this->updated_at?->timestamp, -6);
}
return null;
}
public function getMaxNumberOfSubmissionsReachedAttribute()
{
return ($this->max_submissions_count && $this->max_submissions_count <= $this->submissions_count);
return $this->max_submissions_count && $this->max_submissions_count <= $this->submissions_count;
}
public function setClosedTextAttribute($value)
@ -232,12 +240,12 @@ class Form extends Model implements CachableAttributes
public function getHasPasswordAttribute()
{
return !empty($this->password);
return ! empty($this->password);
}
public function getMaxFileSizeAttribute()
{
return $this->remember('max_file_size', 15 * 60, function(): int {
return $this->remember('max_file_size', 15 * 60, function (): int {
return $this->workspace->max_file_size;
});
}
@ -292,7 +300,7 @@ class Form extends Model implements CachableAttributes
return SlugOptions::create()
->doNotGenerateSlugsOnUpdate()
->generateSlugsFrom(function (Form $form) {
return $form->title . ' ' . Str::random(6);
return $form->title.' '.Str::random(6);
})
->saveSlugsTo('slug');
}
@ -302,19 +310,18 @@ class Form extends Model implements CachableAttributes
return FormFactory::new();
}
public function getNotifiesWebhookAttribute()
{
return !empty($this->webhook_url);
return ! empty($this->webhook_url);
}
public function getNotifiesDiscordAttribute()
{
return !empty($this->discord_webhook_url);
return ! empty($this->discord_webhook_url);
}
public function getNotifiesSlackAttribute()
{
return !empty($this->slack_webhook_url);
return ! empty($this->slack_webhook_url);
}
}

View File

@ -2,20 +2,19 @@
namespace App\Models\Forms;
use App\Models\Forms\Form;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FormStatistic extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = [
'form_id',
'data',
'date'
'date',
];
/**
@ -34,5 +33,4 @@ class FormStatistic extends Model
{
return $this->belongsTo(Form::class);
}
}

View File

@ -2,7 +2,6 @@
namespace App\Models\Forms;
use App\Models\Forms\Form;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -11,17 +10,18 @@ class FormSubmission extends Model
use HasFactory;
protected $fillable = [
'data'
'data',
];
protected $casts = [
'data' => 'array'
'data' => 'array',
];
/**
* RelationShips
*/
public function form() {
public function form()
{
return $this->belongsTo(Form::class);
}
}

View File

@ -2,7 +2,6 @@
namespace App\Models\Forms;
use App\Models\Forms\Form;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -13,7 +12,8 @@ class FormView extends Model
/**
* RelationShips
*/
public function form() {
public function form()
{
return $this->belongsTo(Form::class);
}
}

View File

@ -7,11 +7,11 @@ use App\Service\Forms\Webhooks\WebhookHandlerProvider;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\WebhookServer\WebhookCall;
class FormZapierWebhook extends Model
{
use HasFactory, SoftDeletes;
use HasFactory;
use SoftDeletes;
protected $table = 'form_zapier_webhooks';
@ -28,7 +28,7 @@ class FormZapierWebhook extends Model
return $this->belongsTo(Form::class);
}
public function triggerHook(array $data)
public function triggerHook(array $data)
{
WebhookHandlerProvider::getProvider(
$this->form,

View File

@ -7,17 +7,17 @@ use Illuminate\Database\Eloquent\Model;
class License extends Model
{
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
use HasFactory;
public const STATUS_ACTIVE = 'active';
public const STATUS_INACTIVE = 'inactive';
protected $fillable = [
'license_key',
'user_id',
'license_provider',
'status',
'meta'
'meta',
];
protected $casts = [

View File

@ -11,7 +11,8 @@ use Stevebauman\Purify\Facades\Purify;
class Template extends Model
{
use HasFactory, HasSlug;
use HasFactory;
use HasSlug;
protected $fillable = [
'creator_id',
@ -25,7 +26,7 @@ class Template extends Model
'publicly_listed',
'industries',
'types',
'related_templates'
'related_templates',
];
protected $casts = [
@ -93,7 +94,8 @@ class Template extends Model
array_values(
json_decode(
file_get_contents(resource_path('data/forms/templates/types.json')),
true)
true
)
)
)->values();
}
@ -104,7 +106,8 @@ class Template extends Model
array_values(
json_decode(
file_get_contents(resource_path('data/forms/templates/industries.json')),
true)
true
)
)
)->values();
}

View File

@ -9,9 +9,6 @@ interface CachableAttributes
/**
* Get an item from the cache, or execute the given Closure and store the result.
*
* @param string $key
* @param int|null $ttl
* @param Closure $callback
*
* @return mixed
*/
@ -20,8 +17,6 @@ interface CachableAttributes
/**
* Get an item from the cache, or execute the given Closure and store the result forever.
*
* @param string $key
* @param \Closure $callback
*
* @return mixed
*/
@ -29,17 +24,11 @@ interface CachableAttributes
/**
* Remove an item from the cache.
*
* @param string $key
*
* @return bool
*/
public function forget(string $key): bool;
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush(): bool;
}

View File

@ -84,7 +84,7 @@ trait CachesAttributes
$this->getTable(),
$this->getKey(),
$attribute,
$this->updated_at?->timestamp ?? '0'
$this->updated_at?->timestamp ?? '0',
]);
}

View File

@ -7,14 +7,15 @@ use App\Notifications\ResetPassword;
use App\Notifications\VerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use Notifiable, HasFactory, Billable;
use Billable;
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.
@ -25,7 +26,7 @@ class User extends Authenticatable implements JWTSubject
'name',
'email',
'password',
'hear_about_us'
'hear_about_us',
];
/**
@ -36,7 +37,7 @@ class User extends Authenticatable implements JWTSubject
protected $hidden = [
'password',
'remember_token',
'hear_about_us'
'hear_about_us',
];
/**
@ -59,12 +60,12 @@ class User extends Authenticatable implements JWTSubject
public function ownsForm(Form $form)
{
return $this->workspaces()->where('workspaces.id',$form->workspace_id)->exists();
return $this->workspaces()->where('workspaces.id', $form->workspace_id)->exists();
}
public function ownsWorkspace(Workspace $workspace)
{
return $this->workspaces()->where('workspaces.id',$workspace->id)->exists();
return $this->workspaces()->where('workspaces.id', $workspace->id)->exists();
}
/**
@ -89,12 +90,12 @@ class User extends Authenticatable implements JWTSubject
{
return $this->subscribed()
|| in_array($this->email, config('opnform.extra_pro_users_emails'))
|| !is_null($this->activeLicense());
|| ! is_null($this->activeLicense());
}
public function getHasCustomerIdAttribute()
{
return !is_null($this->stripe_id);
return ! is_null($this->stripe_id);
}
public function getAdminAttribute()
@ -121,7 +122,7 @@ class User extends Authenticatable implements JWTSubject
/**
* Send the password reset notification.
*
* @param string $token
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
@ -136,7 +137,7 @@ class User extends Authenticatable implements JWTSubject
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
$this->notify(new VerifyEmail());
}
/**
@ -144,7 +145,6 @@ class User extends Authenticatable implements JWTSubject
* Relationship
* =================================
*/
public function workspaces()
{
return $this->belongsToMany(Workspace::class);
@ -246,5 +246,4 @@ class User extends Authenticatable implements JWTSubject
});
});
}
}

View File

@ -10,11 +10,15 @@ use Illuminate\Database\Eloquent\Model;
class Workspace extends Model implements CachableAttributes
{
use HasFactory, CachesAttributes;
use CachesAttributes;
use HasFactory;
public const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
public const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
public const MAX_DOMAIN_PRO = 1;
const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
const MAX_DOMAIN_PRO = 1;
protected $fillable = [
'name',
'icon',
@ -24,7 +28,7 @@ class Workspace extends Model implements CachableAttributes
protected $appends = [
'is_pro',
'is_enterprise'
'is_enterprise',
];
protected $casts = [
@ -37,16 +41,16 @@ class Workspace extends Model implements CachableAttributes
'is_risky',
'submissions_count',
'max_file_size',
'custom_domain_count'
'custom_domain_count',
];
public function getMaxFileSizeAttribute()
{
if(is_null(config('cashier.key'))){
if (is_null(config('cashier.key'))) {
return self::MAX_FILE_SIZE_PRO;
}
return $this->remember('max_file_size', 15 * 60, function(): int {
return $this->remember('max_file_size', 15 * 60, function (): int {
// Return max file size depending on subscription
foreach ($this->owners as $owner) {
if ($owner->is_subscribed) {
@ -55,6 +59,7 @@ class Workspace extends Model implements CachableAttributes
return $license->max_file_size;
}
}
return self::MAX_FILE_SIZE_PRO;
}
@ -64,17 +69,18 @@ class Workspace extends Model implements CachableAttributes
public function getCustomDomainCountLimitAttribute()
{
if(is_null(config('cashier.key'))){
if (is_null(config('cashier.key'))) {
return null;
}
return $this->remember('custom_domain_count', 15 * 60, function(): ?int {
return $this->remember('custom_domain_count', 15 * 60, function (): ?int {
foreach ($this->owners as $owner) {
if ($owner->is_subscribed) {
if ($license = $owner->activeLicense()) {
// In case of special License
return $license->custom_domain_limit_count;
}
return self::MAX_DOMAIN_PRO;
}
}
@ -85,44 +91,46 @@ class Workspace extends Model implements CachableAttributes
public function getIsProAttribute()
{
if(is_null(config('cashier.key'))){
if (is_null(config('cashier.key'))) {
return true; // If no paid plan so TRUE for ALL
}
return $this->remember('is_pro', 15 * 60, function(): bool {
return $this->remember('is_pro', 15 * 60, function (): bool {
// Make sure at least one owner is pro
foreach ($this->owners as $owner) {
if ($owner->is_subscribed) {
return true;
}
}
return false;
});
}
public function getIsEnterpriseAttribute()
{
if(is_null(config('cashier.key'))){
if (is_null(config('cashier.key'))) {
return true; // If no paid plan so TRUE for ALL
}
return $this->remember('is_enterprise', 15 * 60, function(): bool {
return $this->remember('is_enterprise', 15 * 60, function (): bool {
// Make sure at least one owner is pro
foreach ($this->owners as $owner) {
if ($owner->has_enterprise_subscription) {
return true;
}
}
return false;
});
}
public function getIsRiskyAttribute()
{
return $this->remember('is_risky', 15 * 60, function(): bool {
return $this->remember('is_risky', 15 * 60, function (): bool {
// A workspace is risky if all of his users are risky
foreach ($this->owners as $owner) {
if (!$owner->is_risky) {
if (! $owner->is_risky) {
return false;
}
}
@ -133,7 +141,7 @@ class Workspace extends Model implements CachableAttributes
public function getSubmissionsCountAttribute()
{
return $this->remember('submissions_count', 15 * 60, function(): int {
return $this->remember('submissions_count', 15 * 60, function (): int {
$total = 0;
foreach ($this->forms as $form) {
$total += $form->submissions_count;
@ -146,7 +154,6 @@ class Workspace extends Model implements CachableAttributes
/**
* Relationships
*/
public function users()
{
return $this->belongsToMany(User::class);

View File

@ -6,7 +6,6 @@ use App\Models\Forms\Form;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
class FailedWebhookNotification extends Notification
@ -14,6 +13,7 @@ class FailedWebhookNotification extends Notification
use Queueable;
public Form $form;
public string $provider;
/**
@ -30,7 +30,7 @@ class FailedWebhookNotification extends Notification
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
@ -41,13 +41,13 @@ class FailedWebhookNotification extends Notification
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject("Notification issue with your NotionForm: '" . $this->form->title . "'")
return (new MailMessage())
->subject("Notification issue with your NotionForm: '".$this->form->title."'")
->markdown('mail.form.webhook-error', [
'provider' => $this->provider,
'error' => $this->event->errorMessage,

View File

@ -8,8 +8,8 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class FormSubmissionNotification extends Notification implements ShouldQueue
{
@ -52,7 +52,7 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
->outputStringsOnly()
->useSignedUrlForFiles();
return (new MailMessage)
return (new MailMessage())
->replyTo($this->getReplyToEmail($notifiable->routes['mail']))
->from($this->getFromEmail(), config('app.name'))
->subject('New form submission for "'.$this->event->form->title.'"')
@ -65,15 +65,17 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
private function getFromEmail()
{
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
return $originalFromAddress->first(). '+' . time() . '@' . $originalFromAddress->last();
return $originalFromAddress->first().'+'.time().'@'.$originalFromAddress->last();
}
private function getReplyToEmail($default)
{
$replyTo = Arr::get((array)$this->event->form->notification_settings, 'notification_reply_to', null);
$replyTo = Arr::get((array) $this->event->form->notification_settings, 'notification_reply_to', null);
if ($replyTo && $this->validateEmail($replyTo)) {
return $replyTo;
}
return $this->getRespondentEmail() ?? $default;
}
@ -83,7 +85,7 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
$emailFields = collect($this->event->form->properties)->filter(function ($field) {
$hidden = $field['hidden'] ?? false;
return !$hidden && $field['type'] == 'email';
return ! $hidden && $field['type'] == 'email';
});
if ($emailFields->count() != 1) {
return null;
@ -101,7 +103,6 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
public static function validateEmail($email): bool
{
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
}
}

View File

@ -15,7 +15,7 @@ class ResetPassword extends Notification
*/
public function toMail($notifiable)
{
return (new MailMessage)
return (new MailMessage())
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', front_url('password/reset/'.$this->token).'?email='.urlencode($notifiable->email))
->line('If you did not request a password reset, no further action is required.');

View File

@ -30,7 +30,7 @@ class FailedPaymentNotification extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
return (new MailMessage)
return (new MailMessage())
->subject('Your Payment Failed')
->greeting(__('We tried to charge your card for your OpenForm subscription but the payment but did not work.'))
->line(__('Please go to OpenForm, click on your name on the top right corner, and click on "Billing".

View File

@ -17,7 +17,9 @@ class VerifyEmail extends Notification
protected function verificationUrl($notifiable)
{
$url = URL::temporarySignedRoute(
'verification.verify', Carbon::now()->addMinutes(60), ['user' => $notifiable->id]
'verification.verify',
Carbon::now()->addMinutes(60),
['user' => $notifiable->id]
);
return str_replace('/api', '', $url);

View File

@ -13,7 +13,6 @@ class FormPolicy
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
@ -24,8 +23,6 @@ class FormPolicy
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\Forms\Form $form
* @return mixed
*/
public function view(User $user, Form $form)
@ -36,7 +33,6 @@ class FormPolicy
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
@ -47,8 +43,6 @@ class FormPolicy
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Forms\Form $form
* @return mixed
*/
public function update(User $user, Form $form)
@ -59,8 +53,6 @@ class FormPolicy
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Forms\Form $form
* @return mixed
*/
public function delete(User $user, Form $form)
@ -71,8 +63,6 @@ class FormPolicy
/**
* Determine whether the user can restore the model.
*
* @param \App\Models\User $user
* @param \App\Models\Forms\Form $form
* @return mixed
*/
public function restore(User $user, Form $form)
@ -83,8 +73,6 @@ class FormPolicy
/**
* Determine whether the user can permanently delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Forms\Form $form
* @return mixed
*/
public function forceDelete(User $user, Form $form)

View File

@ -13,7 +13,6 @@ class TemplatePolicy
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
@ -24,8 +23,6 @@ class TemplatePolicy
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Template $template
* @return mixed
*/
public function update(User $user, Template $template)
@ -36,8 +33,6 @@ class TemplatePolicy
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Template $template
* @return mixed
*/
public function delete(User $user, Template $template)

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