Lint PHP code psr-12, add GH action
This commit is contained in:
parent
e85e4df7fe
commit
62971a2ef4
|
@ -6,6 +6,47 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
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:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
@ -155,7 +196,7 @@ jobs:
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
production-deploy:
|
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'
|
if: success() && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Triggers Deployment (Vapor & Amplify)
|
name: Triggers Deployment (Vapor & Amplify)
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\Forms\FormStatistic;
|
use App\Models\Forms\FormStatistic;
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Models\Forms\FormView;
|
|
||||||
use App\Models\Forms\FormSubmission;
|
use App\Models\Forms\FormSubmission;
|
||||||
|
use App\Models\Forms\FormView;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class CleanDatabase extends Command
|
class CleanDatabase extends Command
|
||||||
|
@ -51,13 +51,13 @@ class CleanDatabase extends Command
|
||||||
->orderBy('date')
|
->orderBy('date')
|
||||||
->groupBy('form_id', 'date')
|
->groupBy('form_id', 'date')
|
||||||
->get()->each(function ($row) use (&$finalData) {
|
->get()->each(function ($row) use (&$finalData) {
|
||||||
$finalData[$row->form_id."-".$row->date] = [
|
$finalData[$row->form_id.'-'.$row->date] = [
|
||||||
'form_id' => $row->form_id,
|
'form_id' => $row->form_id,
|
||||||
'date' => $row->date,
|
'date' => $row->date,
|
||||||
'data' => [
|
'data' => [
|
||||||
'views' => $row->views,
|
'views' => $row->views,
|
||||||
'submissions' => 0
|
'submissions' => 0,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class CleanDatabase extends Command
|
||||||
->orderBy('date')
|
->orderBy('date')
|
||||||
->groupBy('form_id', 'date')
|
->groupBy('form_id', 'date')
|
||||||
->get()->each(function ($row) use (&$finalData) {
|
->get()->each(function ($row) use (&$finalData) {
|
||||||
$key = $row->form_id."-".$row->date;
|
$key = $row->form_id.'-'.$row->date;
|
||||||
if (isset($finalData[$key])) {
|
if (isset($finalData[$key])) {
|
||||||
$finalData[$key]['data']['submissions'] = $row->submissions;
|
$finalData[$key]['data']['submissions'] = $row->submissions;
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,8 +77,8 @@ class CleanDatabase extends Command
|
||||||
'date' => $row->date,
|
'date' => $row->date,
|
||||||
'data' => [
|
'data' => [
|
||||||
'views' => 0,
|
'views' => 0,
|
||||||
'submissions' => $row->submissions
|
'submissions' => $row->submissions,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,9 @@ class GenerateTemplate extends Command
|
||||||
*/
|
*/
|
||||||
protected $description = 'Generates a new form template from a prompt';
|
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.
|
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:
|
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.
|
Do not ask me for more information about required properties or types, only suggest me a form structure.
|
||||||
EOD;
|
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.
|
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]".
|
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.
|
Each paragraph (except for the first one) MUST start with with a h2 tag containing a title for this paragraph.
|
||||||
EOD;
|
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.
|
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.
|
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;
|
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.
|
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).
|
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"]}
|
Ex: { "industries": ["banking_forms","customer_service_forms"]}
|
||||||
EOD;
|
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.
|
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).
|
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"]}
|
Ex: { "types": ["consent_forms","award_forms"]}
|
||||||
EOD;
|
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.).
|
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).
|
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".
|
Reply only with a valid JSON, being an array of object containing the keys "question" and "answer".
|
||||||
EOD;
|
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.
|
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;
|
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:
|
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
|
```json
|
||||||
{
|
{
|
||||||
|
@ -227,14 +227,14 @@ class GenerateTemplate extends Command
|
||||||
->useStreaming()
|
->useStreaming()
|
||||||
->setSystemMessage('You are an assistant helping to generate forms.');
|
->setSystemMessage('You are an assistant helping to generate forms.');
|
||||||
$completer->expectsJson()->completeChat([
|
$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();
|
$formData = $completer->getArray();
|
||||||
|
|
||||||
$completer->doesNotExpectJson();
|
$completer->doesNotExpectJson();
|
||||||
$formDescriptionPrompt = Str::of(self::FORM_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString();
|
$formDescriptionPrompt = Str::of(self::FORM_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString();
|
||||||
$formShortDescription = $completer->completeChat([
|
$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();
|
])->getString();
|
||||||
// If description is between quotes, remove quotes
|
// If description is between quotes, remove quotes
|
||||||
$formShortDescription = Str::of($formShortDescription)->replaceMatches('/^"(.*)"$/', '$1')->toString();
|
$formShortDescription = Str::of($formShortDescription)->replaceMatches('/^"(.*)"$/', '$1')->toString();
|
||||||
|
@ -250,30 +250,29 @@ class GenerateTemplate extends Command
|
||||||
// Now get description and QAs
|
// Now get description and QAs
|
||||||
$completer->doesNotExpectJson();
|
$completer->doesNotExpectJson();
|
||||||
$formDescription = $completer->completeChat([
|
$formDescription = $completer->completeChat([
|
||||||
["role" => "user", "content" => $formDescriptionPrompt]
|
['role' => 'user', 'content' => $formDescriptionPrompt],
|
||||||
])->getHtml();
|
])->getHtml();
|
||||||
|
|
||||||
$completer->expectsJson();
|
$completer->expectsJson();
|
||||||
$formCoverKeywords = $completer->completeChat([
|
$formCoverKeywords = $completer->completeChat([
|
||||||
["role" => "user", "content" => $formDescriptionPrompt],
|
['role' => 'user', 'content' => $formDescriptionPrompt],
|
||||||
["role" => "assistant", "content" => $formDescription],
|
['role' => 'assistant', 'content' => $formDescription],
|
||||||
["role" => "user", "content" => self::FORM_IMG_KEYWORDS_PROMPT]
|
['role' => 'user', 'content' => self::FORM_IMG_KEYWORDS_PROMPT],
|
||||||
])->getArray();
|
])->getArray();
|
||||||
$imageUrl = $this->getImageCoverUrl($formCoverKeywords['search_query']);
|
$imageUrl = $this->getImageCoverUrl($formCoverKeywords['search_query']);
|
||||||
|
|
||||||
$formQAs = $completer->completeChat([
|
$formQAs = $completer->completeChat([
|
||||||
["role" => "user", "content" => $formDescriptionPrompt],
|
['role' => 'user', 'content' => $formDescriptionPrompt],
|
||||||
["role" => "assistant", "content" => $formDescription],
|
['role' => 'assistant', 'content' => $formDescription],
|
||||||
["role" => "user", "content" => self::FORM_QAS_PROMPT]
|
['role' => 'user', 'content' => self::FORM_QAS_PROMPT],
|
||||||
])->getArray();
|
])->getArray();
|
||||||
$completer->doesNotExpectJson();
|
$completer->doesNotExpectJson();
|
||||||
$formTitle = $completer->completeChat([
|
$formTitle = $completer->completeChat([
|
||||||
["role" => "user", "content" => $formDescriptionPrompt],
|
['role' => 'user', 'content' => $formDescriptionPrompt],
|
||||||
["role" => "assistant", "content" => $formDescription],
|
['role' => 'assistant', 'content' => $formDescription],
|
||||||
["role" => "user", "content" => self::FORM_TITLE_PROMPT]
|
['role' => 'user', 'content' => self::FORM_TITLE_PROMPT],
|
||||||
])->getString();
|
])->getString();
|
||||||
|
|
||||||
|
|
||||||
$template = $this->createFormTemplate(
|
$template = $this->createFormTemplate(
|
||||||
$formData,
|
$formData,
|
||||||
$formTitle,
|
$formTitle,
|
||||||
|
@ -285,7 +284,7 @@ class GenerateTemplate extends Command
|
||||||
$types,
|
$types,
|
||||||
$relatedTemplates
|
$relatedTemplates
|
||||||
);
|
);
|
||||||
$this->info('/form-templates/' . $template->slug);
|
$this->info('/form-templates/'.$template->slug);
|
||||||
|
|
||||||
// Set reverse related Templates
|
// Set reverse related Templates
|
||||||
$this->setReverseRelatedTemplates($template);
|
$this->setReverseRelatedTemplates($template);
|
||||||
|
@ -298,34 +297,37 @@ class GenerateTemplate extends Command
|
||||||
*/
|
*/
|
||||||
private function getImageCoverUrl($searchQuery): ?string
|
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();
|
$response = Http::get($url)->json();
|
||||||
$photoIndex = rand(0, max(count($response['results']) - 1, 10));
|
$photoIndex = rand(0, max(count($response['results']) - 1, 10));
|
||||||
if (isset($response['results'][$photoIndex]['urls']['regular'])) {
|
if (isset($response['results'][$photoIndex]['urls']['regular'])) {
|
||||||
return Str::of($response['results'][$photoIndex]['urls']['regular'])->replace('w=1080', 'w=600')->toString();
|
return Str::of($response['results'][$photoIndex]['urls']['regular'])->replace('w=1080', 'w=600')->toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getIndustries(GptCompleter $completer, string $formPrompt): array
|
private function getIndustries(GptCompleter $completer, string $formPrompt): array
|
||||||
{
|
{
|
||||||
$industriesString = Template::getAllIndustries()->pluck('slug')->join(', ');
|
$industriesString = Template::getAllIndustries()->pluck('slug')->join(', ');
|
||||||
|
|
||||||
return $completer->completeChat([
|
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('[REPLACE]', $formPrompt)
|
||||||
->replace('[INDUSTRIES]', $industriesString)
|
->replace('[INDUSTRIES]', $industriesString)
|
||||||
->toString()]
|
->toString()],
|
||||||
])->getArray()['industries'];
|
])->getArray()['industries'];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTypes(GptCompleter $completer, string $formPrompt): array
|
private function getTypes(GptCompleter $completer, string $formPrompt): array
|
||||||
{
|
{
|
||||||
$typesString = Template::getAllTypes()->pluck('slug')->join(', ');
|
$typesString = Template::getAllTypes()->pluck('slug')->join(', ');
|
||||||
|
|
||||||
return $completer->completeChat([
|
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('[REPLACE]', $formPrompt)
|
||||||
->replace('[TYPES]', $typesString)
|
->replace('[TYPES]', $typesString)
|
||||||
->toString()]
|
->toString()],
|
||||||
])->getArray()['types'];
|
])->getArray()['types'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,21 +346,21 @@ class GenerateTemplate extends Command
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
arsort($templateScore); // Sort by Score
|
arsort($templateScore); // Sort by Score
|
||||||
|
|
||||||
return array_slice(array_keys($templateScore), 0, self::MAX_RELATED_TEMPLATES);
|
return array_slice(array_keys($templateScore), 0, self::MAX_RELATED_TEMPLATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormTemplate(
|
private function createFormTemplate(
|
||||||
array $formData,
|
array $formData,
|
||||||
string $formTitle,
|
string $formTitle,
|
||||||
string $formDescription,
|
string $formDescription,
|
||||||
string $formShortDescription,
|
string $formShortDescription,
|
||||||
array $formQAs,
|
array $formQAs,
|
||||||
?string $imageUrl,
|
?string $imageUrl,
|
||||||
array $industry,
|
array $industry,
|
||||||
array $types,
|
array $types,
|
||||||
array $relatedTemplates
|
array $relatedTemplates
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
// Add property uuids, improve form with options
|
// Add property uuids, improve form with options
|
||||||
foreach ($formData['properties'] as &$property) {
|
foreach ($formData['properties'] as &$property) {
|
||||||
$property['id'] = Str::uuid()->toString(); // Column ID
|
$property['id'] = Str::uuid()->toString(); // Column ID
|
||||||
|
@ -387,13 +389,15 @@ class GenerateTemplate extends Command
|
||||||
'publicly_listed' => true,
|
'publicly_listed' => true,
|
||||||
'industries' => $industry,
|
'industries' => $industry,
|
||||||
'types' => $types,
|
'types' => $types,
|
||||||
'related_templates' => $relatedTemplates
|
'related_templates' => $relatedTemplates,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setReverseRelatedTemplates(Template $newTemplate)
|
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();
|
$templates = Template::whereIn('slug', $newTemplate->related_templates)->get();
|
||||||
foreach ($templates as $template) {
|
foreach ($templates as $template) {
|
||||||
|
|
|
@ -27,34 +27,34 @@ class GenerateTaxExport extends Command
|
||||||
*/
|
*/
|
||||||
protected $description = 'Compute Stripe VAT per country';
|
protected $description = 'Compute Stripe VAT per country';
|
||||||
|
|
||||||
const EU_TAX_RATES = [
|
public const EU_TAX_RATES = [
|
||||||
"AT" => 20,
|
'AT' => 20,
|
||||||
"BE" => 21,
|
'BE' => 21,
|
||||||
"BG" => 20,
|
'BG' => 20,
|
||||||
"HR" => 25,
|
'HR' => 25,
|
||||||
"CY" => 19,
|
'CY' => 19,
|
||||||
"CZ" => 21,
|
'CZ' => 21,
|
||||||
"DK" => 25,
|
'DK' => 25,
|
||||||
"EE" => 20,
|
'EE' => 20,
|
||||||
"FI" => 24,
|
'FI' => 24,
|
||||||
"FR" => 20,
|
'FR' => 20,
|
||||||
"DE" => 19,
|
'DE' => 19,
|
||||||
"GR" => 24,
|
'GR' => 24,
|
||||||
"HU" => 27,
|
'HU' => 27,
|
||||||
"IE" => 23,
|
'IE' => 23,
|
||||||
"IT" => 22,
|
'IT' => 22,
|
||||||
"LV" => 21,
|
'LV' => 21,
|
||||||
"LT" => 21,
|
'LT' => 21,
|
||||||
"LU" => 17,
|
'LU' => 17,
|
||||||
"MT" => 18,
|
'MT' => 18,
|
||||||
"NL" => 21,
|
'NL' => 21,
|
||||||
"PL" => 23,
|
'PL' => 23,
|
||||||
"PT" => 23,
|
'PT' => 23,
|
||||||
"RO" => 19,
|
'RO' => 19,
|
||||||
"SK" => 20,
|
'SK' => 20,
|
||||||
"SI" => 22,
|
'SI' => 22,
|
||||||
"ES" => 21,
|
'ES' => 21,
|
||||||
"SE" => 25
|
'SE' => 25,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,20 +69,22 @@ class GenerateTaxExport extends Command
|
||||||
$endDate = $this->option('end-date');
|
$endDate = $this->option('end-date');
|
||||||
|
|
||||||
// Validate the date format
|
// 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.');
|
$this->error('Invalid start date format. Use YYYY-MM-DD.');
|
||||||
|
|
||||||
return Command::FAILURE;
|
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.');
|
$this->error('Invalid end date format. Use YYYY-MM-DD.');
|
||||||
|
|
||||||
return Command::FAILURE;
|
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');
|
$endDate = Carbon::parse($startDate)->endOfMonth()->endOfDay()->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info('Start date: ' . $startDate);
|
$this->info('Start date: '.$startDate);
|
||||||
$this->info('End date: ' . $endDate);
|
$this->info('End date: '.$endDate);
|
||||||
|
|
||||||
$processedInvoices = [];
|
$processedInvoices = [];
|
||||||
|
|
||||||
|
@ -111,6 +113,7 @@ class GenerateTaxExport extends Command
|
||||||
// Ignore if payment was refunded
|
// Ignore if payment was refunded
|
||||||
if (($invoice->payment_intent->status ?? null) !== 'succeeded') {
|
if (($invoice->payment_intent->status ?? null) !== 'succeeded') {
|
||||||
$paymentNotSuccessfulCount++;
|
$paymentNotSuccessfulCount++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,14 +135,14 @@ class GenerateTaxExport extends Command
|
||||||
|
|
||||||
$aggregatedReport = $this->aggregateReport($processedInvoices);
|
$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);
|
$this->exportAsXlsx($processedInvoices, $filePath);
|
||||||
|
|
||||||
$aggregatedReportFilePath = 'opnform-tax-export-aggregated_' . $startDate . '_' . $endDate . '.xlsx';
|
$aggregatedReportFilePath = 'opnform-tax-export-aggregated_'.$startDate.'_'.$endDate.'.xlsx';
|
||||||
$this->exportAsXlsx($aggregatedReport, $aggregatedReportFilePath);
|
$this->exportAsXlsx($aggregatedReport, $aggregatedReportFilePath);
|
||||||
|
|
||||||
// Display the results
|
// 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;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -151,7 +154,7 @@ class GenerateTaxExport extends Command
|
||||||
foreach ($invoices as $invoice) {
|
foreach ($invoices as $invoice) {
|
||||||
$country = $invoice['cust_country'];
|
$country = $invoice['cust_country'];
|
||||||
$customerType = is_null($invoice['cust_vat_id']) && $this->isEuropeanCountry($country) ? 'individual' : 'business';
|
$customerType = is_null($invoice['cust_vat_id']) && $this->isEuropeanCountry($country) ? 'individual' : 'business';
|
||||||
if (!isset($aggregatedReport[$country])) {
|
if (! isset($aggregatedReport[$country])) {
|
||||||
$defaultVal = [
|
$defaultVal = [
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'total_usd' => 0,
|
'total_usd' => 0,
|
||||||
|
@ -163,7 +166,7 @@ class GenerateTaxExport extends Command
|
||||||
];
|
];
|
||||||
$aggregatedReport[$country] = [
|
$aggregatedReport[$country] = [
|
||||||
'individual' => $defaultVal,
|
'individual' => $defaultVal,
|
||||||
'business' => $defaultVal
|
'business' => $defaultVal,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$aggregatedReport[$country][$customerType]['count']++;
|
$aggregatedReport[$country][$customerType]['count']++;
|
||||||
|
@ -181,10 +184,11 @@ class GenerateTaxExport extends Command
|
||||||
$finalReport[] = [
|
$finalReport[] = [
|
||||||
'country' => $country,
|
'country' => $country,
|
||||||
'customer_type' => $customerType,
|
'customer_type' => $customerType,
|
||||||
...$aggData
|
...$aggData,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $finalReport;
|
return $finalReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +230,9 @@ class GenerateTaxExport extends Command
|
||||||
|
|
||||||
if ($taxRate = (self::EU_TAX_RATES[$countryCode] ?? null)) {
|
if ($taxRate = (self::EU_TAX_RATES[$countryCode] ?? null)) {
|
||||||
// If VAT ID is provided, then TAX is 0%
|
// If VAT ID is provided, then TAX is 0%
|
||||||
if (!$vatId) return $taxRate;
|
if (! $vatId) {
|
||||||
|
return $taxRate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -241,12 +247,11 @@ class GenerateTaxExport extends Command
|
||||||
{
|
{
|
||||||
if (count($data) == 0) {
|
if (count($data) == 0) {
|
||||||
$this->info('Empty data. No file generated.');
|
$this->info('Empty data. No file generated.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(new ArrayExport($data))->store($filename, 'local', \Maatwebsite\Excel\Excel::XLSX);
|
(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ class Kernel extends ConsoleKernel
|
||||||
/**
|
/**
|
||||||
* Define the application's command schedule.
|
* Define the application's command schedule.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule)
|
protected function schedule(Schedule $schedule)
|
||||||
|
|
|
@ -3,19 +3,18 @@
|
||||||
namespace App\Events\Forms;
|
namespace App\Events\Forms;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class FormSubmitted
|
class FormSubmitted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithSockets;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
public $form;
|
public $form;
|
||||||
|
|
||||||
public $data;
|
public $data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,17 +3,15 @@
|
||||||
namespace App\Events\Models;
|
namespace App\Events\Models;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PresenceChannel;
|
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class FormCreated
|
class FormCreated
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithSockets;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
|
@ -21,5 +19,6 @@ class FormCreated
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(public Form $form)
|
public function __construct(public Form $form)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class Handler extends ExceptionHandler
|
||||||
|
|
||||||
public function report(Throwable $exception)
|
public function report(Throwable $exception)
|
||||||
{
|
{
|
||||||
if ($this->shouldReport($exception) ) {
|
if ($this->shouldReport($exception)) {
|
||||||
if (app()->bound('sentry') && $this->sentryShouldReport($exception)) {
|
if (app()->bound('sentry') && $this->sentryShouldReport($exception)) {
|
||||||
app('sentry')->captureException($exception);
|
app('sentry')->captureException($exception);
|
||||||
Log::debug('Un-handled Exception: '.$exception->getMessage(), [
|
Log::debug('Un-handled Exception: '.$exception->getMessage(), [
|
||||||
|
@ -64,7 +64,7 @@ class Handler extends ExceptionHandler
|
||||||
|
|
||||||
public function render($request, Throwable $e)
|
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);
|
Log::channel('slack')->error($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ class Handler extends ExceptionHandler
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Illuminate\Validation\ValidationException;
|
||||||
class VerifyEmailException extends ValidationException
|
class VerifyEmailException extends ValidationException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param \App\User $user
|
* @param \App\User $user
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function forUser($user)
|
public static function forUser($user)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class WorkspaceAlreadyExisting extends Exception
|
||||||
public function getErrorMessage()
|
public function getErrorMessage()
|
||||||
{
|
{
|
||||||
$owner = $this->workspace->users()->first();
|
$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
|
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
|
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.';
|
with an Enterprise subscription, or contact us to get help.';
|
||||||
|
|
|
@ -7,7 +7,6 @@ use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||||
|
|
||||||
class FormSubmissionExport implements FromArray, WithHeadingRow
|
class FormSubmissionExport implements FromArray, WithHeadingRow
|
||||||
{
|
{
|
||||||
|
|
||||||
protected array $submissionData;
|
protected array $submissionData;
|
||||||
|
|
||||||
public function __construct(array $submissionData)
|
public function __construct(array $submissionData)
|
||||||
|
@ -15,7 +14,7 @@ class FormSubmissionExport implements FromArray, WithHeadingRow
|
||||||
$headingRow = [];
|
$headingRow = [];
|
||||||
$contentRow = [];
|
$contentRow = [];
|
||||||
foreach ($submissionData as $i => $row) {
|
foreach ($submissionData as $i => $row) {
|
||||||
if($i==0){
|
if ($i == 0) {
|
||||||
$headingRow[] = $this->cleanColumnNames(array_keys($row));
|
$headingRow[] = $this->cleanColumnNames(array_keys($row));
|
||||||
}
|
}
|
||||||
$contentRow[] = array_values($row);
|
$contentRow[] = array_values($row);
|
||||||
|
@ -23,7 +22,7 @@ class FormSubmissionExport implements FromArray, WithHeadingRow
|
||||||
|
|
||||||
$this->submissionData = [
|
$this->submissionData = [
|
||||||
$headingRow,
|
$headingRow,
|
||||||
$contentRow
|
$contentRow,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,4 +24,3 @@ class ArrayExport implements FromArray, WithHeadings
|
||||||
return array_keys($this->data[0]);
|
return array_keys($this->data[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ namespace App\Http\Controllers\Admin;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class ImpersonationController extends Controller
|
class ImpersonationController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -15,7 +13,8 @@ class ImpersonationController extends Controller
|
||||||
$this->middleware('moderator');
|
$this->middleware('moderator');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function impersonate($identifier) {
|
public function impersonate($identifier)
|
||||||
|
{
|
||||||
$user = null;
|
$user = null;
|
||||||
if (is_numeric($identifier)) {
|
if (is_numeric($identifier)) {
|
||||||
$user = User::find($identifier);
|
$user = User::find($identifier);
|
||||||
|
@ -29,17 +28,17 @@ class ImpersonationController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user) {
|
if (! $user) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message'=> 'User not found.'
|
'message' => 'User not found.',
|
||||||
]);
|
]);
|
||||||
} else if ($user->admin) {
|
} elseif ($user->admin) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'You cannot impersonate an admin.',
|
'message' => 'You cannot impersonate an admin.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Log::warning('Impersonation started',[
|
\Log::warning('Impersonation started', [
|
||||||
'from_id' => auth()->id(),
|
'from_id' => auth()->id(),
|
||||||
'from_email' => auth()->user()->email,
|
'from_email' => auth()->user()->email,
|
||||||
'target_id' => $user->id,
|
'target_id' => $user->id,
|
||||||
|
@ -52,7 +51,7 @@ class ImpersonationController extends Controller
|
||||||
])->login($user);
|
])->login($user);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'token' => $token
|
'token' => $token,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,16 @@ class AppSumoAuthController extends Controller
|
||||||
|
|
||||||
public function handleCallback(Request $request)
|
public function handleCallback(Request $request)
|
||||||
{
|
{
|
||||||
if (!$code = $request->code) {
|
if (! $code = $request->code) {
|
||||||
return response()->json(['message' => 'Healthy'], 200);
|
return response()->json(['message' => 'Healthy'], 200);
|
||||||
}
|
}
|
||||||
$accessToken = $this->retrieveAccessToken($code);
|
$accessToken = $this->retrieveAccessToken($code);
|
||||||
$license = $this->fetchOrCreateLicense($accessToken);
|
$license = $this->fetchOrCreateLicense($accessToken);
|
||||||
|
|
||||||
// If user connected, attach license
|
// 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
|
// otherwise start login flow by passing the encrypted license key id
|
||||||
if (is_null($license->user_id)) {
|
if (is_null($license->user_id)) {
|
||||||
|
@ -37,7 +39,7 @@ class AppSumoAuthController extends Controller
|
||||||
private function retrieveAccessToken(string $requestCode): string
|
private function retrieveAccessToken(string $requestCode): string
|
||||||
{
|
{
|
||||||
return Http::withHeaders([
|
return Http::withHeaders([
|
||||||
'Content-type' => 'application/json'
|
'Content-type' => 'application/json',
|
||||||
])->post('https://appsumo.com/openid/token/', [
|
])->post('https://appsumo.com/openid/token/', [
|
||||||
'grant_type' => 'authorization_code',
|
'grant_type' => 'authorization_code',
|
||||||
'code' => $requestCode,
|
'code' => $requestCode,
|
||||||
|
@ -50,13 +52,13 @@ class AppSumoAuthController extends Controller
|
||||||
private function fetchOrCreateLicense(string $accessToken): License
|
private function fetchOrCreateLicense(string $accessToken): License
|
||||||
{
|
{
|
||||||
// Fetch license from API
|
// 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()
|
->throw()
|
||||||
->json('license_key');
|
->json('license_key');
|
||||||
|
|
||||||
// Fetch or create license model
|
// Fetch or create license model
|
||||||
$license = License::where('license_provider','appsumo')->where('license_key',$licenseKey)->first();
|
$license = License::where('license_provider', 'appsumo')->where('license_key', $licenseKey)->first();
|
||||||
if (!$license) {
|
if (! $license) {
|
||||||
$licenseData = Http::withHeaders([
|
$licenseData = Http::withHeaders([
|
||||||
'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
|
'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
|
||||||
])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
|
])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
|
||||||
|
@ -73,8 +75,9 @@ class AppSumoAuthController extends Controller
|
||||||
return $license;
|
return $license;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function attachLicense(License $license) {
|
private function attachLicense(License $license)
|
||||||
if (!Auth::check()) {
|
{
|
||||||
|
if (! Auth::check()) {
|
||||||
throw new AuthenticationException('User not authenticated');
|
throw new AuthenticationException('User not authenticated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +85,7 @@ class AppSumoAuthController extends Controller
|
||||||
if (is_null($license->user_id)) {
|
if (is_null($license->user_id)) {
|
||||||
$license->user_id = Auth::id();
|
$license->user_id = Auth::id();
|
||||||
$license->save();
|
$license->save();
|
||||||
|
|
||||||
return redirect(front_url('/home?appsumo_connect=1'));
|
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
|
* @return string|null
|
||||||
*
|
*
|
||||||
* Returns null if no license found
|
* Returns null if no license found
|
||||||
|
@ -100,7 +102,7 @@ class AppSumoAuthController extends Controller
|
||||||
*/
|
*/
|
||||||
public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
|
public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
|
||||||
{
|
{
|
||||||
if (!$licenseHash) {
|
if (! $licenseHash) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$licenseId = decrypt($licenseHash);
|
$licenseId = decrypt($licenseHash);
|
||||||
|
@ -109,6 +111,7 @@ class AppSumoAuthController extends Controller
|
||||||
if ($license && is_null($license->user_id)) {
|
if ($license && is_null($license->user_id)) {
|
||||||
$license->user_id = $user->id;
|
$license->user_id = $user->id;
|
||||||
$license->save();
|
$license->save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ class ForgotPasswordController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the response for a successful password reset link.
|
* Get the response for a successful password reset link.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $response
|
* @param string $response
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +34,6 @@ class ForgotPasswordController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the response for a failed password reset link.
|
* Get the response for a failed password reset link.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $response
|
* @param string $response
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -26,7 +26,6 @@ class LoginController extends Controller
|
||||||
/**
|
/**
|
||||||
* Attempt to log the user into the application.
|
* Attempt to log the user into the application.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function attemptLogin(Request $request)
|
protected function attemptLogin(Request $request)
|
||||||
|
@ -50,7 +49,6 @@ class LoginController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the needed authorization credentials from the request.
|
* Get the needed authorization credentials from the request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function credentials(Request $request)
|
protected function credentials(Request $request)
|
||||||
|
@ -64,7 +62,6 @@ class LoginController extends Controller
|
||||||
/**
|
/**
|
||||||
* Send the response after the user was authenticated.
|
* Send the response after the user was authenticated.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
protected function sendLoginResponse(Request $request)
|
protected function sendLoginResponse(Request $request)
|
||||||
|
@ -84,7 +81,6 @@ class LoginController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the failed login response instance.
|
* Get the failed login response instance.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
@ -104,7 +100,6 @@ class LoginController extends Controller
|
||||||
/**
|
/**
|
||||||
* Log the user out of the application.
|
* Log the user out of the application.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class OAuthController extends Controller
|
||||||
/**
|
/**
|
||||||
* Redirect the user to the provider authentication page.
|
* Redirect the user to the provider authentication page.
|
||||||
*
|
*
|
||||||
* @param string $provider
|
* @param string $provider
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function redirect($provider)
|
public function redirect($provider)
|
||||||
|
@ -41,7 +41,7 @@ class OAuthController extends Controller
|
||||||
/**
|
/**
|
||||||
* Obtain the user information from the provider.
|
* Obtain the user information from the provider.
|
||||||
*
|
*
|
||||||
* @param string $driver
|
* @param string $driver
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function handleCallback($provider)
|
public function handleCallback($provider)
|
||||||
|
@ -61,8 +61,8 @@ class OAuthController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $provider
|
* @param string $provider
|
||||||
* @param \Laravel\Socialite\Contracts\User $sUser
|
* @param \Laravel\Socialite\Contracts\User $sUser
|
||||||
* @return \App\Models\User
|
* @return \App\Models\User
|
||||||
*/
|
*/
|
||||||
protected function findOrCreateUser($provider, $user)
|
protected function findOrCreateUser($provider, $user)
|
||||||
|
@ -81,15 +81,15 @@ class OAuthController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if (User::where('email', $user->getEmail())->exists()) {
|
if (User::where('email', $user->getEmail())->exists()) {
|
||||||
throw new EmailTakenException;
|
throw new EmailTakenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->createUser($provider, $user);
|
return $this->createUser($provider, $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $provider
|
* @param string $provider
|
||||||
* @param \Laravel\Socialite\Contracts\User $sUser
|
* @param \Laravel\Socialite\Contracts\User $sUser
|
||||||
* @return \App\Models\User
|
* @return \App\Models\User
|
||||||
*/
|
*/
|
||||||
protected function createUser($provider, $sUser)
|
protected function createUser($provider, $sUser)
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\UserResource;
|
use App\Http\Resources\UserResource;
|
||||||
use App\Models\Workspace;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\Workspace;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
@ -31,8 +31,7 @@ class RegisterController extends Controller
|
||||||
/**
|
/**
|
||||||
* The user has been registered.
|
* The user has been registered.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \App\User $user
|
||||||
* @param \App\User $user
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
protected function registered(Request $request, User $user)
|
protected function registered(Request $request, User $user)
|
||||||
|
@ -45,13 +44,13 @@ class RegisterController extends Controller
|
||||||
(new UserResource($user))->toArray($request),
|
(new UserResource($user))->toArray($request),
|
||||||
[
|
[
|
||||||
'appsumo_license' => $this->appsumoLicense,
|
'appsumo_license' => $this->appsumoLicense,
|
||||||
]));
|
]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a validator for an incoming registration request.
|
* Get a validator for an incoming registration request.
|
||||||
*
|
*
|
||||||
* @param array $data
|
|
||||||
* @return \Illuminate\Contracts\Validation\Validator
|
* @return \Illuminate\Contracts\Validation\Validator
|
||||||
*/
|
*/
|
||||||
protected function validator(array $data)
|
protected function validator(array $data)
|
||||||
|
@ -64,14 +63,13 @@ class RegisterController extends Controller
|
||||||
'agree_terms' => ['required', Rule::in([true])],
|
'agree_terms' => ['required', Rule::in([true])],
|
||||||
'appsumo_license' => ['nullable'],
|
'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.
|
* Create a new user instance after a valid registration.
|
||||||
*
|
*
|
||||||
* @param array $data
|
|
||||||
* @return \App\User
|
* @return \App\User
|
||||||
*/
|
*/
|
||||||
protected function create(array $data)
|
protected function create(array $data)
|
||||||
|
@ -85,14 +83,14 @@ class RegisterController extends Controller
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'email' => strtolower($data['email']),
|
'email' => strtolower($data['email']),
|
||||||
'password' => bcrypt($data['password']),
|
'password' => bcrypt($data['password']),
|
||||||
'hear_about_us' => $data['hear_about_us']
|
'hear_about_us' => $data['hear_about_us'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add relation with user
|
// Add relation with user
|
||||||
$user->workspaces()->sync([
|
$user->workspaces()->sync([
|
||||||
$workspace->id => [
|
$workspace->id => [
|
||||||
'role' => 'admin'
|
'role' => 'admin',
|
||||||
]
|
],
|
||||||
], false);
|
], false);
|
||||||
|
|
||||||
$this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);
|
$this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);
|
||||||
|
|
|
@ -23,7 +23,6 @@ class ResetPasswordController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the response for a successful password reset.
|
* Get the response for a successful password reset.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $response
|
* @param string $response
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +34,6 @@ class ResetPasswordController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get the response for a failed password reset.
|
* Get the response for a failed password reset.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param string $response
|
* @param string $response
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -11,23 +11,24 @@ class UserController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get authenticated user.
|
* Get authenticated user.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function current(Request $request)
|
public function current(Request $request)
|
||||||
{
|
{
|
||||||
return new UserResource($request->user());
|
return new UserResource($request->user());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAccount() {
|
public function deleteAccount()
|
||||||
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
if (Auth::user()->admin) {
|
if (Auth::user()->admin) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'Cannot delete an admin. Stay with us 🙏'
|
'message' => 'Cannot delete an admin. Stay with us 🙏',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
Auth::user()->delete();
|
Auth::user()->delete();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'User deleted.'
|
'message' => 'User deleted.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,7 @@ class VerificationController extends Controller
|
||||||
/**
|
/**
|
||||||
* Mark the user's email address as verified.
|
* 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
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function verify(Request $request, User $user)
|
public function verify(Request $request, User $user)
|
||||||
|
@ -54,7 +53,6 @@ class VerificationController extends Controller
|
||||||
/**
|
/**
|
||||||
* Resend the email verification notification.
|
* Resend the email verification notification.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function resend(Request $request)
|
public function resend(Request $request)
|
||||||
|
|
|
@ -15,29 +15,30 @@ class CaddyController extends Controller
|
||||||
]);
|
]);
|
||||||
// make sure domain is valid
|
// make sure domain is valid
|
||||||
$domain = $request->input('domain');
|
$domain = $request->input('domain');
|
||||||
if (!preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $domain)) {
|
if (! preg_match(CustomDomainRequest::CUSTOM_DOMAINS_REGEX, $domain)) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Invalid domain',
|
'message' => 'Invalid domain',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Log::info('Caddy request received',[
|
\Log::info('Caddy request received', [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($workspace = Workspace::whereJsonContains('custom_domains',$domain)->first()) {
|
if ($workspace = Workspace::whereJsonContains('custom_domains', $domain)->first()) {
|
||||||
\Log::info('Caddy request successful',[
|
\Log::info('Caddy request successful', [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'workspace' => $workspace->id,
|
'workspace' => $workspace->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'OK',
|
'message' => 'OK',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Log::info('Caddy request failed',[
|
\Log::info('Caddy request failed', [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'workspace' => $workspace?->id,
|
'workspace' => $workspace?->id,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -3,19 +3,19 @@
|
||||||
namespace App\Http\Controllers\Content;
|
namespace App\Http\Controllers\Content;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class ChangelogController extends Controller
|
class ChangelogController extends Controller
|
||||||
{
|
{
|
||||||
const CANNY_ENDPOINT = 'https://canny.io/api/v1/';
|
public const CANNY_ENDPOINT = 'https://canny.io/api/v1/';
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return \Cache::remember('changelog_entries', now()->addHour(), function () {
|
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'),
|
'apiKey' => config('services.canny.api_key'),
|
||||||
'limit' => 3,
|
'limit' => 3,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $response->json('entries');
|
return $response->json('entries');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ class FileUploadController extends Controller
|
||||||
/**
|
/**
|
||||||
* Upload file to local temp
|
* Upload file to local temp
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function upload(Request $request)
|
public function upload(Request $request)
|
||||||
|
@ -23,7 +22,7 @@ class FileUploadController extends Controller
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'key' => $path
|
'key' => $path,
|
||||||
], 201);
|
], 201);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,15 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Content;
|
namespace App\Http\Controllers\Content;
|
||||||
|
|
||||||
use App\Models\User;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Vapor\Http\Controllers\SignedStorageUrlController as Controller;
|
use Laravel\Vapor\Http\Controllers\SignedStorageUrlController as Controller;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class SignedStorageUrlController extends Controller
|
class SignedStorageUrlController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new signed URL.
|
* Create a new signed URL.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
|
|
|
@ -9,19 +9,21 @@ use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
use AuthorizesRequests;
|
||||||
|
use DispatchesJobs;
|
||||||
|
use ValidatesRequests;
|
||||||
|
|
||||||
public function success($data = [])
|
public function success($data = [])
|
||||||
{
|
{
|
||||||
return response()->json(array_merge([
|
return response()->json(array_merge([
|
||||||
'type' => 'success'
|
'type' => 'success',
|
||||||
], $data));
|
], $data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function error($data = [], $statusCode = 400)
|
public function error($data = [], $statusCode = 400)
|
||||||
{
|
{
|
||||||
return response()->json(array_merge([
|
return response()->json(array_merge([
|
||||||
'type' => 'error'
|
'type' => 'error',
|
||||||
], $data), $statusCode);
|
], $data), $statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ namespace App\Http\Controllers\Forms;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\AiGenerateFormRequest;
|
use App\Http\Requests\AiGenerateFormRequest;
|
||||||
use App\Models\Forms\AI\AiFormCompletion;
|
use App\Models\Forms\AI\AiFormCompletion;
|
||||||
use App\Service\OpenAi\GptCompleter;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class AiFormController extends Controller
|
class AiFormController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -18,8 +16,8 @@ class AiFormController extends Controller
|
||||||
'message' => 'We\'re working on your form, please wait ~1 min.',
|
'message' => 'We\'re working on your form, please wait ~1 min.',
|
||||||
'ai_form_completion_id' => AiFormCompletion::create([
|
'ai_form_completion_id' => AiFormCompletion::create([
|
||||||
'form_prompt' => $request->input('form_prompt'),
|
'form_prompt' => $request->input('form_prompt'),
|
||||||
'ip' => $request->ip()
|
'ip' => $request->ip(),
|
||||||
])->id
|
])->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +28,7 @@ class AiFormController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'ai_form_completion' => $aiFormCompletion
|
'ai_form_completion' => $aiFormCompletion,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use Illuminate\Support\Str;
|
||||||
|
|
||||||
class FormController extends Controller
|
class FormController extends Controller
|
||||||
{
|
{
|
||||||
const ASSETS_UPLOAD_PATH = 'assets/forms';
|
public const ASSETS_UPLOAD_PATH = 'assets/forms';
|
||||||
|
|
||||||
private FormCleaner $formCleaner;
|
private FormCleaner $formCleaner;
|
||||||
|
|
||||||
|
@ -36,26 +36,28 @@ class FormController extends Controller
|
||||||
$workspaceIsPro = $workspace->is_pro;
|
$workspaceIsPro = $workspace->is_pro;
|
||||||
$forms = $workspace->forms()
|
$forms = $workspace->forms()
|
||||||
->orderByDesc('updated_at')
|
->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
|
// Add attributes for faster loading
|
||||||
$form->extra = (object) [
|
$form->extra = (object) [
|
||||||
'loadedWorkspace' => $workspace,
|
'loadedWorkspace' => $workspace,
|
||||||
'workspaceIsPro' => $workspaceIsPro,
|
'workspaceIsPro' => $workspaceIsPro,
|
||||||
'userIsOwner' => true,
|
'userIsOwner' => true,
|
||||||
'cleanings' => $this->formCleaner
|
'cleanings' => $this->formCleaner
|
||||||
->processForm(request(), $form)
|
->processForm(request(), $form)
|
||||||
->simulateCleaning($workspace)
|
->simulateCleaning($workspace)
|
||||||
->getPerformedCleanings()
|
->getPerformedCleanings(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
});
|
||||||
|
|
||||||
return $form;
|
|
||||||
});
|
|
||||||
return FormResource::collection($forms);
|
return FormResource::collection($forms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all user forms, used for zapier
|
* Return all user forms, used for zapier
|
||||||
|
*
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function indexAll()
|
public function indexAll()
|
||||||
|
@ -66,18 +68,20 @@ class FormController extends Controller
|
||||||
$this->authorize('viewAny', Form::class);
|
$this->authorize('viewAny', Form::class);
|
||||||
|
|
||||||
$workspaceIsPro = $workspace->is_pro;
|
$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
|
// Add attributes for faster loading
|
||||||
$form->extra = (object) [
|
$form->extra = (object) [
|
||||||
'loadedWorkspace' => $workspace,
|
'loadedWorkspace' => $workspace,
|
||||||
'workspaceIsPro' => $workspaceIsPro,
|
'workspaceIsPro' => $workspaceIsPro,
|
||||||
'userIsOwner' => true,
|
'userIsOwner' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
});
|
});
|
||||||
|
|
||||||
$forms = $forms->merge($newForms);
|
$forms = $forms->merge($newForms);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FormResource::collection($forms);
|
return FormResource::collection($forms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +98,13 @@ class FormController extends Controller
|
||||||
->getData();
|
->getData();
|
||||||
|
|
||||||
$form = Form::create(array_merge($formData, [
|
$form = Form::create(array_merge($formData, [
|
||||||
'creator_id' => $request->user()->id
|
'creator_id' => $request->user()->id,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return $this->success([
|
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()),
|
'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
|
// Set Removed Properties
|
||||||
$formData['removed_properties'] = array_merge($form->removed_properties, collect($form->properties)->filter(function ($field) use ($formData) {
|
$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());
|
})->toArray());
|
||||||
|
|
||||||
$form->update($formData);
|
$form->update($formData);
|
||||||
|
|
||||||
return $this->success([
|
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()),
|
'form' => (new FormResource($form))->setCleanings($this->formCleaner->getPerformedCleanings()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -133,8 +137,9 @@ class FormController extends Controller
|
||||||
$this->authorize('delete', $form);
|
$this->authorize('delete', $form);
|
||||||
|
|
||||||
$form->delete();
|
$form->delete();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Form was deleted.'
|
'message' => 'Form was deleted.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +155,7 @@ class FormController extends Controller
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Form successfully duplicated.',
|
'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);
|
$form = Form::findOrFail($id);
|
||||||
$this->authorize('update', $form);
|
$this->authorize('update', $form);
|
||||||
|
|
||||||
if ( $option == 'slug') {
|
if ($option == 'slug') {
|
||||||
$form->generateSlug();
|
$form->generateSlug();
|
||||||
} elseif ($option == 'uuid') {
|
} elseif ($option == 'uuid') {
|
||||||
$form->slug = Str::uuid();
|
$form->slug = Str::uuid();
|
||||||
|
@ -168,7 +173,7 @@ class FormController extends Controller
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Form url successfully updated. Your new form url now is: '.$form->share_url.'.',
|
'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);
|
$fileNameParser = StorageFileNameParser::parse($request->url);
|
||||||
|
|
||||||
// Make sure we retrieve the file in tmp storage, move it to persistent
|
// Make sure we retrieve the file in tmp storage, move it to persistent
|
||||||
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;;
|
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;
|
||||||
if (!Storage::exists($fileName)) {
|
if (! Storage::exists($fileName)) {
|
||||||
// File not found, we skip
|
// File not found, we skip
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +197,7 @@ class FormController extends Controller
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'File uploaded.',
|
'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);
|
$this->authorize('view', $form);
|
||||||
|
|
||||||
$path = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $form->id).'/'.$fileName;
|
$path = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $form->id).'/'.$fileName;
|
||||||
if (!Storage::exists($path)) {
|
if (! Storage::exists($path)) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'File not found.'
|
'message' => 'File not found.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,20 +19,21 @@ class FormStatsController extends Controller
|
||||||
|
|
||||||
$this->authorize('view', $form);
|
$this->authorize('view', $form);
|
||||||
|
|
||||||
$formStats = $form->statistics()->where('date','>',now()->subDays(29)->startOfDay())->get();
|
$formStats = $form->statistics()->where('date', '>', now()->subDays(29)->startOfDay())->get();
|
||||||
$periodStats = ["views" => [], "submissions" => []];
|
$periodStats = ['views' => [], 'submissions' => []];
|
||||||
foreach (CarbonPeriod::create(now()->subDays(29), now()) as $dateObj) {
|
foreach (CarbonPeriod::create(now()->subDays(29), now()) as $dateObj) {
|
||||||
$date = $dateObj->format('d-m-Y');
|
$date = $dateObj->format('d-m-Y');
|
||||||
|
|
||||||
$statisticData = $formStats->where('date', $dateObj->format('Y-m-d'))->first();
|
$statisticData = $formStats->where('date', $dateObj->format('Y-m-d'))->first();
|
||||||
$periodStats["views"][$date] = $statisticData->data["views"] ?? 0;
|
$periodStats['views'][$date] = $statisticData->data['views'] ?? 0;
|
||||||
$periodStats["submissions"][$date] = $statisticData->data["submissions"] ?? 0;
|
$periodStats['submissions'][$date] = $statisticData->data['submissions'] ?? 0;
|
||||||
|
|
||||||
if($dateObj->toDateString() === now()->toDateString()){
|
if ($dateObj->toDateString() === now()->toDateString()) {
|
||||||
$periodStats["views"][$date] += $form->views()->count();
|
$periodStats['views'][$date] += $form->views()->count();
|
||||||
$periodStats["submissions"][$date] += $form->submissions()->whereDate('created_at', '>=', now()->startOfDay())->count();
|
$periodStats['submissions'][$date] += $form->submissions()->whereDate('created_at', '>=', now()->startOfDay())->count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $periodStats;
|
return $periodStats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Forms;
|
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\Exports\FormSubmissionExport;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\AnswerFormRequest;
|
use App\Http\Requests\AnswerFormRequest;
|
||||||
|
use App\Http\Resources\FormSubmissionResource;
|
||||||
use App\Jobs\Form\StoreFormSubmissionJob;
|
use App\Jobs\Form\StoreFormSubmissionJob;
|
||||||
|
use App\Models\Forms\Form;
|
||||||
use App\Models\Forms\FormSubmission;
|
use App\Models\Forms\FormSubmission;
|
||||||
use App\Service\Forms\FormSubmissionFormatter;
|
use App\Service\Forms\FormSubmissionFormatter;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
@ -26,7 +25,7 @@ class FormSubmissionController extends Controller
|
||||||
|
|
||||||
public function submissions(string $id)
|
public function submissions(string $id)
|
||||||
{
|
{
|
||||||
$form = Form::findOrFail((int)$id);
|
$form = Form::findOrFail((int) $id);
|
||||||
$this->authorize('view', $form);
|
$this->authorize('view', $form);
|
||||||
|
|
||||||
return FormSubmissionResource::collection($form->submissions()->paginate(100));
|
return FormSubmissionResource::collection($form->submissions()->paginate(100));
|
||||||
|
@ -40,15 +39,16 @@ class FormSubmissionController extends Controller
|
||||||
$job->setSubmissionId($submissionId)->handle();
|
$job->setSubmissionId($submissionId)->handle();
|
||||||
|
|
||||||
$data = new FormSubmissionResource(FormSubmission::findOrFail($submissionId));
|
$data = new FormSubmissionResource(FormSubmission::findOrFail($submissionId));
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Record successfully updated.',
|
'message' => 'Record successfully updated.',
|
||||||
'data' => $data
|
'data' => $data,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function export(string $id)
|
public function export(string $id)
|
||||||
{
|
{
|
||||||
$form = Form::findOrFail((int)$id);
|
$form = Form::findOrFail((int) $id);
|
||||||
$this->authorize('view', $form);
|
$this->authorize('view', $form);
|
||||||
|
|
||||||
$allRows = [];
|
$allRows = [];
|
||||||
|
@ -61,24 +61,25 @@ class FormSubmissionController extends Controller
|
||||||
->useSignedUrlForFiles();
|
->useSignedUrlForFiles();
|
||||||
$allRows[] = [
|
$allRows[] = [
|
||||||
'id' => Hashids::encode($row['id']),
|
'id' => Hashids::encode($row['id']),
|
||||||
'created_at' => date("Y-m-d H:i", strtotime($row['created_at'])),
|
'created_at' => date('Y-m-d H:i', strtotime($row['created_at'])),
|
||||||
...$formatter->getCleanKeyValue()
|
...$formatter->getCleanKeyValue(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$csvExport = (new FormSubmissionExport($allRows));
|
$csvExport = (new FormSubmissionExport($allRows));
|
||||||
|
|
||||||
return Excel::download(
|
return Excel::download(
|
||||||
$csvExport,
|
$csvExport,
|
||||||
$form->slug . '-submission-data.csv',
|
$form->slug.'-submission-data.csv',
|
||||||
\Maatwebsite\Excel\Excel::CSV
|
\Maatwebsite\Excel\Excel::CSV
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submissionFile($id, $fileName)
|
public function submissionFile($id, $fileName)
|
||||||
{
|
{
|
||||||
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id) . '/'
|
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/'
|
||||||
. urldecode($fileName);
|
.urldecode($fileName);
|
||||||
|
|
||||||
if (!Storage::exists($fileName)) {
|
if (! Storage::exists($fileName)) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'File not found.',
|
'message' => 'File not found.',
|
||||||
], 404);
|
], 404);
|
||||||
|
|
|
@ -5,35 +5,38 @@ namespace App\Http\Controllers\Forms\Integration;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Integration\StoreFormZapierWebhookRequest;
|
use App\Http\Requests\Integration\StoreFormZapierWebhookRequest;
|
||||||
use App\Models\Integration\FormZapierWebhook;
|
use App\Models\Integration\FormZapierWebhook;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Spatie\WebhookServer\WebhookCall;
|
|
||||||
|
|
||||||
class FormZapierWebhookController extends Controller
|
class FormZapierWebhookController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Controller for Zappier webhook subscriptions.
|
* Controller for Zappier webhook subscriptions.
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct()
|
||||||
// $this->middleware('subscribed');
|
{
|
||||||
|
// $this->middleware('subscribed');
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(StoreFormZapierWebhookRequest $request) {
|
public function store(StoreFormZapierWebhookRequest $request)
|
||||||
|
{
|
||||||
$hook = $request->instanciateHook();
|
$hook = $request->instanciateHook();
|
||||||
$this->authorize('store', $hook);
|
$this->authorize('store', $hook);
|
||||||
|
|
||||||
$hook->save();
|
$hook->save();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Webhook created.',
|
'message' => 'Webhook created.',
|
||||||
'hook' => $hook
|
'hook' => $hook,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete($id) {
|
public function delete($id)
|
||||||
|
{
|
||||||
$hook = FormZapierWebhook::findOrFail($id);
|
$hook = FormZapierWebhook::findOrFail($id);
|
||||||
$this->authorize('store', $hook);
|
$this->authorize('store', $hook);
|
||||||
|
|
||||||
$hook->delete();
|
$hook->delete();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Webhook deleted.',
|
'message' => 'Webhook deleted.',
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -11,15 +11,15 @@ use App\Models\Forms\FormSubmission;
|
||||||
use App\Service\Forms\FormCleaner;
|
use App\Service\Forms\FormCleaner;
|
||||||
use App\Service\WorkspaceHelper;
|
use App\Service\WorkspaceHelper;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Vinkla\Hashids\Facades\Hashids;
|
use Vinkla\Hashids\Facades\Hashids;
|
||||||
|
|
||||||
class PublicFormController extends Controller
|
class PublicFormController extends Controller
|
||||||
{
|
{
|
||||||
|
public const FILE_UPLOAD_PATH = 'forms/?/submissions';
|
||||||
|
|
||||||
const FILE_UPLOAD_PATH = 'forms/?/submissions';
|
public const TMP_FILE_UPLOAD_PATH = 'tmp/';
|
||||||
const TMP_FILE_UPLOAD_PATH = 'tmp/';
|
|
||||||
|
|
||||||
public function show(Request $request, string $slug)
|
public function show(Request $request, string $slug)
|
||||||
{
|
{
|
||||||
|
@ -27,21 +27,22 @@ class PublicFormController extends Controller
|
||||||
if ($form->workspace == null) {
|
if ($form->workspace == null) {
|
||||||
// Workspace deleted
|
// Workspace deleted
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'Form not found.'
|
'message' => 'Form not found.',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$formCleaner = new FormCleaner();
|
$formCleaner = new FormCleaner();
|
||||||
|
|
||||||
// Disable pro features if needed
|
// Disable pro features if needed
|
||||||
$form->fill($formCleaner
|
$form->fill(
|
||||||
|
$formCleaner
|
||||||
->processForm($request, $form)
|
->processForm($request, $form)
|
||||||
->performCleaning($form->workspace)
|
->performCleaning($form->workspace)
|
||||||
->getData()
|
->getData()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Increase form view counter if not login
|
// Increase form view counter if not login
|
||||||
if(!Auth::check()){
|
if (! Auth::check()) {
|
||||||
$form->views()->create();
|
$form->views()->create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,22 +54,23 @@ class PublicFormController extends Controller
|
||||||
{
|
{
|
||||||
// Check that form has user field
|
// Check that form has user field
|
||||||
$form = $request->form;
|
$form = $request->form;
|
||||||
if (!$form->has_user_field) {
|
if (! $form->has_user_field) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use serializer
|
// Use serializer
|
||||||
$workspace = $form->workspace;
|
$workspace = $form->workspace;
|
||||||
|
|
||||||
return (new WorkspaceHelper($workspace))->getAllUsers();
|
return (new WorkspaceHelper($workspace))->getAllUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showAsset($assetFileName)
|
public function showAsset($assetFileName)
|
||||||
{
|
{
|
||||||
$path = FormController::ASSETS_UPLOAD_PATH.'/'.$assetFileName;
|
$path = FormController::ASSETS_UPLOAD_PATH.'/'.$assetFileName;
|
||||||
if (!Storage::exists($path)) {
|
if (! Storage::exists($path)) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'File not found.',
|
'message' => 'File not found.',
|
||||||
'file_name' => $assetFileName
|
'file_name' => $assetFileName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,18 +86,18 @@ class PublicFormController extends Controller
|
||||||
$job = new StoreFormSubmissionJob($form, $request->validated());
|
$job = new StoreFormSubmissionJob($form, $request->validated());
|
||||||
$job->handle();
|
$job->handle();
|
||||||
$submissionId = Hashids::encode($job->getSubmissionId());
|
$submissionId = Hashids::encode($job->getSubmissionId());
|
||||||
}else{
|
} else {
|
||||||
StoreFormSubmissionJob::dispatch($form, $request->validated());
|
StoreFormSubmissionJob::dispatch($form, $request->validated());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success(array_merge([
|
return $this->success(array_merge([
|
||||||
'message' => 'Form submission saved.',
|
'message' => 'Form submission saved.',
|
||||||
'submission_id' => $submissionId
|
'submission_id' => $submissionId,
|
||||||
], $request->form->is_pro && $request->form->redirect_url ? [
|
], $request->form->is_pro && $request->form->redirect_url ? [
|
||||||
'redirect' => true,
|
'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 = ($submissionId) ? Hashids::decode($submissionId) : false;
|
||||||
$submissionId = isset($submissionId[0]) ? $submissionId[0] : false;
|
$submissionId = isset($submissionId[0]) ? $submissionId[0] : false;
|
||||||
$form = Form::whereSlug($slug)->whereVisibility('public')->firstOrFail();
|
$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([
|
return $this->error([
|
||||||
'message' => 'Not allowed.',
|
'message' => 'Not allowed.',
|
||||||
]);
|
]);
|
||||||
|
@ -120,5 +122,4 @@ class PublicFormController extends Controller
|
||||||
|
|
||||||
return $this->success(['data' => ($submission) ? $submission->data : []]);
|
return $this->success(['data' => ($submission) ? $submission->data : []]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@ class RecordController extends Controller
|
||||||
$record->delete();
|
$record->delete();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Record successfully removed.'
|
'message' => 'Record successfully removed.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ class PasswordController extends Controller
|
||||||
/**
|
/**
|
||||||
* Update the user's password.
|
* Update the user's password.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
|
|
|
@ -10,7 +10,6 @@ class ProfileController extends Controller
|
||||||
/**
|
/**
|
||||||
* Update the user's profile information.
|
* Update the user's profile information.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
|
|
|
@ -2,18 +2,15 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Spatie\Sitemap\Sitemap;
|
|
||||||
use Spatie\Sitemap\Tags\Url;
|
|
||||||
use App\Models\Template;
|
use App\Models\Template;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class SitemapController extends Controller
|
class SitemapController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
...$this->getTemplatesUrls()
|
...$this->getTemplatesUrls(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,10 +20,11 @@ class SitemapController extends Controller
|
||||||
Template::where('publicly_listed', true)->chunk(100, function ($templates) use (&$urls) {
|
Template::where('publicly_listed', true)->chunk(100, function ($templates) use (&$urls) {
|
||||||
foreach ($templates as $template) {
|
foreach ($templates as $template) {
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => '/templates/' . $template->slug
|
'loc' => '/templates/'.$template->slug,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return $urls;
|
return $urls;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,11 @@ use Laravel\Cashier\Subscription;
|
||||||
|
|
||||||
class SubscriptionController extends Controller
|
class SubscriptionController extends Controller
|
||||||
{
|
{
|
||||||
const SUBSCRIPTION_PLANS = ['monthly', 'yearly'];
|
public const SUBSCRIPTION_PLANS = ['monthly', 'yearly'];
|
||||||
|
|
||||||
const PRO_SUBSCRIPTION_NAME = 'default';
|
public const PRO_SUBSCRIPTION_NAME = 'default';
|
||||||
const SUBSCRIPTION_NAMES = [
|
|
||||||
|
public const SUBSCRIPTION_NAMES = [
|
||||||
self::PRO_SUBSCRIPTION_NAME,
|
self::PRO_SUBSCRIPTION_NAME,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ class SubscriptionController extends Controller
|
||||||
if ($user->subscriptions()->where('stripe_status', 'past_due')->first()) {
|
if ($user->subscriptions()->where('stripe_status', 'past_due')->first()) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
'message' => 'You already have a past due subscription. Please verify your details in the billing page,
|
'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' => [
|
'customer_update' => [
|
||||||
'address' => 'auto',
|
'address' => 'auto',
|
||||||
'name' => 'never',
|
'name' => 'never',
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'checkout_url' => $checkout->url
|
'checkout_url' => $checkout->url,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateStripeDetails(UpdateStripeDetailsRequest $request)
|
public function updateStripeDetails(UpdateStripeDetailsRequest $request)
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
if (!$user->hasStripeId()) {
|
if (! $user->hasStripeId()) {
|
||||||
$user->createAsStripeCustomer();
|
$user->createAsStripeCustomer();
|
||||||
}
|
}
|
||||||
$user->updateStripeCustomer([
|
$user->updateStripeCustomer([
|
||||||
|
@ -78,13 +79,14 @@ class SubscriptionController extends Controller
|
||||||
public function billingPortal()
|
public function billingPortal()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
if (!Auth::user()->has_customer_id) {
|
if (! Auth::user()->has_customer_id) {
|
||||||
return $this->error([
|
return $this->error([
|
||||||
"message" => "Please subscribe before accessing your billing portal."
|
'message' => 'Please subscribe before accessing your billing portal.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'portal_url' => Auth::user()->billingPortalUrl(front_url('/home'))
|
'portal_url' => Auth::user()->billingPortalUrl(front_url('/home')),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Templates\FormTemplateRequest;
|
use App\Http\Requests\Templates\FormTemplateRequest;
|
||||||
use App\Http\Resources\FormTemplateResource;
|
use App\Http\Resources\FormTemplateResource;
|
||||||
use App\Models\Template;
|
use App\Models\Template;
|
||||||
|
@ -13,20 +12,20 @@ class TemplateController extends Controller
|
||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$limit = (int)$request->get('limit', 0);
|
$limit = (int) $request->get('limit', 0);
|
||||||
$onlyMy = (bool)$request->get('onlymy', false);
|
$onlyMy = (bool) $request->get('onlymy', false);
|
||||||
|
|
||||||
$templates = Template::when(Auth::check(), function ($query) use ($onlyMy) {
|
$templates = Template::when(Auth::check(), function ($query) use ($onlyMy) {
|
||||||
if ($onlyMy) {
|
if ($onlyMy) {
|
||||||
$query->where('creator_id', Auth::id());
|
$query->where('creator_id', Auth::id());
|
||||||
} else {
|
} else {
|
||||||
$query->where(function ($query) {
|
$query->where(function ($query) {
|
||||||
$query->where('publicly_listed', true)
|
$query->where('publicly_listed', true)
|
||||||
->orWhere('creator_id', Auth::id());
|
->orWhere('creator_id', Auth::id());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
->when(!Auth::check(), function ($query) {
|
->when(! Auth::check(), function ($query) {
|
||||||
$query->where('publicly_listed', true);
|
$query->where('publicly_listed', true);
|
||||||
})
|
})
|
||||||
->when($limit > 0, function ($query) use ($limit) {
|
->when($limit > 0, function ($query) use ($limit) {
|
||||||
|
@ -49,7 +48,7 @@ class TemplateController extends Controller
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Template was created.',
|
'message' => 'Template was created.',
|
||||||
'template_id' => $template->id,
|
'template_id' => $template->id,
|
||||||
'data' => new FormTemplateResource($template)
|
'data' => new FormTemplateResource($template),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +62,7 @@ class TemplateController extends Controller
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Template was updated.',
|
'message' => 'Template was updated.',
|
||||||
'template_id' => $template->id,
|
'template_id' => $template->id,
|
||||||
'data' => new FormTemplateResource($template)
|
'data' => new FormTemplateResource($template),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace App\Http\Controllers\Webhook;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Validation\UnauthorizedException;
|
use Illuminate\Validation\UnauthorizedException;
|
||||||
|
|
||||||
class AppSumoController extends Controller
|
class AppSumoController extends Controller
|
||||||
|
@ -16,6 +16,7 @@ class AppSumoController extends Controller
|
||||||
|
|
||||||
if ($request->test) {
|
if ($request->test) {
|
||||||
Log::info('[APPSUMO] test request received', $request->toArray());
|
Log::info('[APPSUMO] test request received', $request->toArray());
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Webhook received.',
|
'message' => 'Webhook received.',
|
||||||
'event' => $request->event,
|
'event' => $request->event,
|
||||||
|
@ -69,11 +70,13 @@ class AppSumoController extends Controller
|
||||||
$license->user_id = $licenseData['user_id'] ?? null;
|
$license->user_id = $licenseData['user_id'] ?? null;
|
||||||
$license->save();
|
$license->save();
|
||||||
|
|
||||||
Log::info('[APPSUMO] creating new license',
|
Log::info(
|
||||||
|
'[APPSUMO] creating new license',
|
||||||
[
|
[
|
||||||
'license_key' => $license->license_key,
|
'license_key' => $license->license_key,
|
||||||
'license_id' => $license->id,
|
'license_id' => $license->id,
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
|
|
||||||
return $license;
|
return $license;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,11 @@ namespace App\Http\Controllers\Webhook;
|
||||||
use App\Notifications\Subscription\FailedPaymentNotification;
|
use App\Notifications\Subscription\FailedPaymentNotification;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Laravel\Cashier\Http\Controllers\WebhookController;
|
use Laravel\Cashier\Http\Controllers\WebhookController;
|
||||||
use Stripe\Subscription as StripeSubscription;
|
use Stripe\Subscription as StripeSubscription;
|
||||||
|
|
||||||
class StripeController extends WebhookController
|
class StripeController extends WebhookController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function handleCustomerSubscriptionCreated(array $payload)
|
public function handleCustomerSubscriptionCreated(array $payload)
|
||||||
{
|
{
|
||||||
return parent::handleCustomerSubscriptionCreated($payload);
|
return parent::handleCustomerSubscriptionCreated($payload);
|
||||||
|
@ -19,7 +17,7 @@ class StripeController extends WebhookController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to add a sleep, and to detect plan upgrades
|
* Override to add a sleep, and to detect plan upgrades
|
||||||
* @param array $payload
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response|void
|
* @return \Symfony\Component\HttpFoundation\Response|void
|
||||||
*/
|
*/
|
||||||
protected function handleCustomerSubscriptionUpdated(array $payload)
|
protected function handleCustomerSubscriptionUpdated(array $payload)
|
||||||
|
@ -59,7 +57,7 @@ class StripeController extends WebhookController
|
||||||
if (isset($data['trial_end'])) {
|
if (isset($data['trial_end'])) {
|
||||||
$trialEnd = Carbon::createFromTimestamp($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;
|
$subscription->trial_ends_at = $trialEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +123,7 @@ class StripeController extends WebhookController
|
||||||
return $plan;
|
return $plan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'default';
|
return 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ namespace App\Http\Controllers;
|
||||||
use App\Http\Requests\Workspace\CustomDomainRequest;
|
use App\Http\Requests\Workspace\CustomDomainRequest;
|
||||||
use App\Http\Resources\WorkspaceResource;
|
use App\Http\Resources\WorkspaceResource;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Service\WorkspaceHelper;
|
use App\Service\WorkspaceHelper;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class WorkspaceController extends Controller
|
class WorkspaceController extends Controller
|
||||||
|
@ -19,6 +19,7 @@ class WorkspaceController extends Controller
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', Workspace::class);
|
$this->authorize('viewAny', Workspace::class);
|
||||||
|
|
||||||
return WorkspaceResource::collection(Auth::user()->workspaces);
|
return WorkspaceResource::collection(Auth::user()->workspaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ class WorkspaceController extends Controller
|
||||||
{
|
{
|
||||||
$request->workspace->custom_domains = $request->customDomains;
|
$request->workspace->custom_domains = $request->customDomains;
|
||||||
$request->workspace->save();
|
$request->workspace->save();
|
||||||
|
|
||||||
return new WorkspaceResource($request->workspace);
|
return new WorkspaceResource($request->workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +46,10 @@ class WorkspaceController extends Controller
|
||||||
|
|
||||||
$id = $workspace->id;
|
$id = $workspace->id;
|
||||||
$workspace->delete();
|
$workspace->delete();
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Workspace deleted.',
|
'message' => 'Workspace deleted.',
|
||||||
'workspace_id' => $id
|
'workspace_id' => $id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +58,7 @@ class WorkspaceController extends Controller
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required'
|
'name' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create workspace
|
// Create workspace
|
||||||
|
@ -67,14 +70,14 @@ class WorkspaceController extends Controller
|
||||||
// Add relation with user
|
// Add relation with user
|
||||||
$user->workspaces()->sync([
|
$user->workspaces()->sync([
|
||||||
$workspace->id => [
|
$workspace->id => [
|
||||||
'role' => 'admin'
|
'role' => 'admin',
|
||||||
]
|
],
|
||||||
], false);
|
], false);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'Workspace created.',
|
'message' => 'Workspace created.',
|
||||||
'workspace_id' => $workspace->id,
|
'workspace_id' => $workspace->id,
|
||||||
'workspace' => new WorkspaceResource($workspace)
|
'workspace' => new WorkspaceResource($workspace),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Kernel extends HttpKernel
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $middleware = [
|
protected $middleware = [
|
||||||
// \App\Http\Middleware\TrustHosts::class,
|
// \App\Http\Middleware\TrustHosts::class,
|
||||||
\App\Http\Middleware\TrustProxies::class,
|
\App\Http\Middleware\TrustProxies::class,
|
||||||
\Illuminate\Http\Middleware\HandleCors::class,
|
\Illuminate\Http\Middleware\HandleCors::class,
|
||||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||||
|
@ -32,7 +32,7 @@ class Kernel extends HttpKernel
|
||||||
\App\Http\Middleware\SetLocale::class,
|
\App\Http\Middleware\SetLocale::class,
|
||||||
AuthenticateJWT::class,
|
AuthenticateJWT::class,
|
||||||
CustomDomainRestriction::class,
|
CustomDomainRestriction::class,
|
||||||
AcceptsJsonMiddleware::class
|
AcceptsJsonMiddleware::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,7 +10,6 @@ class AcceptsJsonMiddleware
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* 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
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,7 +8,7 @@ use Tymon\JWTAuth\Exceptions\JWTException;
|
||||||
|
|
||||||
class AuthenticateJWT
|
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
|
* Verifies the JWT token and validates the IP and User Agent
|
||||||
|
@ -33,18 +33,19 @@ class AuthenticateJWT
|
||||||
}
|
}
|
||||||
|
|
||||||
$error = null;
|
$error = null;
|
||||||
if (!\Hash::check($request->ip(), $payload->get('ip'))) {
|
if (! \Hash::check($request->ip(), $payload->get('ip'))) {
|
||||||
$error = 'Origin IP is invalid';
|
$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';
|
$error = 'Origin User Agent is invalid';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($error) {
|
if ($error) {
|
||||||
auth()->invalidate();
|
auth()->invalidate();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => $error
|
'message' => $error,
|
||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ class CaddyRequestMiddleware
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
if (!config('custom-domains.enabled')) {
|
if (! config('custom-domains.enabled')) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Custom domains not enabled',
|
'message' => 'Custom domains not enabled',
|
||||||
], 401);
|
], 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([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Unauthorized IP',
|
'message' => 'Unauthorized IP',
|
||||||
|
@ -27,7 +27,7 @@ class CaddyRequestMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
$secret = $request->route('secret');
|
$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([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Unauthorized',
|
'message' => 'Unauthorized',
|
||||||
|
|
|
@ -6,24 +6,24 @@ use App\Http\Requests\Workspace\CustomDomainRequest;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CustomDomainRestriction
|
class CustomDomainRestriction
|
||||||
{
|
{
|
||||||
const CUSTOM_DOMAIN_HEADER = "x-custom-domain";
|
public const CUSTOM_DOMAIN_HEADER = 'x-custom-domain';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
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);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$customDomain = $request->header(self::CUSTOM_DOMAIN_HEADER);
|
$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([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Invalid domain',
|
'message' => 'Invalid domain',
|
||||||
|
@ -38,7 +38,7 @@ class CustomDomainRestriction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if domain is known
|
// Check if domain is known
|
||||||
if (!$workspaces = Workspace::whereJsonContains('custom_domains',$customDomain)->get()) {
|
if (! $workspaces = Workspace::whereJsonContains('custom_domains', $customDomain)->get()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Unknown domain',
|
'message' => 'Unknown domain',
|
||||||
|
|
|
@ -11,7 +11,6 @@ class ProForm
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* 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
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
@ -22,6 +21,7 @@ class ProForm
|
||||||
$request->merge([
|
$request->merge([
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,27 +9,26 @@ use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class ProtectedForm
|
class ProtectedForm
|
||||||
{
|
{
|
||||||
const PASSWORD_HEADER_NAME = 'form-password';
|
public const PASSWORD_HEADER_NAME = 'form-password';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* 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
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
if (!$request->route('slug')) {
|
if (! $request->route('slug')) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = Form::where('slug',$request->route('slug'))->firstOrFail();
|
$form = Form::where('slug', $request->route('slug'))->firstOrFail();
|
||||||
$request->merge([
|
$request->merge([
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
$userIsFormOwner = Auth::check() && Auth::user()->ownsForm($form);
|
$userIsFormOwner = Auth::check() && Auth::user()->ownsForm($form);
|
||||||
if (!$userIsFormOwner && $this->isProtected($request, $form)) {
|
if (! $userIsFormOwner && $this->isProtected($request, $form)) {
|
||||||
return response([
|
return response([
|
||||||
'status' => 'Unauthorized',
|
'status' => 'Unauthorized',
|
||||||
'message' => 'Form is protected.',
|
'message' => 'Form is protected.',
|
||||||
|
@ -41,11 +40,11 @@ class ProtectedForm
|
||||||
|
|
||||||
public static function isProtected(Request $request, Form $form)
|
public static function isProtected(Request $request, Form $form)
|
||||||
{
|
{
|
||||||
if (!$form->has_password) {
|
if (! $form->has_password) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !self::hasCorrectPassword($request, $form);
|
return ! self::hasCorrectPassword($request, $form);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hasCorrectPassword(Request $request, Form $form)
|
public static function hasCorrectPassword(Request $request, Form $form)
|
||||||
|
|
|
@ -11,16 +11,16 @@ class ResolveFormMiddleware
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* 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
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next, string $routeParamName = "id")
|
public function handle(Request $request, Closure $next, string $routeParamName = 'id')
|
||||||
{
|
{
|
||||||
$form = Form::where($routeParamName,$request->route($routeParamName))->firstOrFail();
|
$form = Form::where($routeParamName, $request->route($routeParamName))->firstOrFail();
|
||||||
$request->merge([
|
$request->merge([
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,15 @@ use Tymon\JWTAuth\Exceptions\JWTException;
|
||||||
class ImpersonationMiddleware
|
class ImpersonationMiddleware
|
||||||
{
|
{
|
||||||
public const ADMIN_LOG_PREFIX = '[admin_action] ';
|
public const ADMIN_LOG_PREFIX = '[admin_action] ';
|
||||||
const LOG_ROUTES = [
|
|
||||||
|
public const LOG_ROUTES = [
|
||||||
'open.forms.store',
|
'open.forms.store',
|
||||||
'open.forms.update',
|
'open.forms.update',
|
||||||
'open.forms.duplicate',
|
'open.forms.duplicate',
|
||||||
'open.forms.regenerate-link',
|
'open.forms.regenerate-link',
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_ROUTES = [
|
public const ALLOWED_ROUTES = [
|
||||||
'logout',
|
'logout',
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
|
@ -59,14 +60,13 @@ class ImpersonationMiddleware
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* 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
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!auth()->check() || !auth()->payload()->get('impersonating')) {
|
if (! auth()->check() || ! auth()->payload()->get('impersonating')) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
} catch (JWTException $e) {
|
} catch (JWTException $e) {
|
||||||
|
@ -75,22 +75,22 @@ class ImpersonationMiddleware
|
||||||
|
|
||||||
// Check that route is allowed
|
// Check that route is allowed
|
||||||
$routeName = $request->route()->getName();
|
$routeName = $request->route()->getName();
|
||||||
if (!in_array($routeName, self::ALLOWED_ROUTES)) {
|
if (! in_array($routeName, self::ALLOWED_ROUTES)) {
|
||||||
return response([
|
return response([
|
||||||
'message' => 'Unauthorized when impersonating',
|
'message' => 'Unauthorized when impersonating',
|
||||||
'route' => $routeName,
|
'route' => $routeName,
|
||||||
'impersonator' => auth()->payload()->get('impersonator_id'),
|
'impersonator' => auth()->payload()->get('impersonator_id'),
|
||||||
'impersonated_account' => auth()->id(),
|
'impersonated_account' => auth()->id(),
|
||||||
'url' => $request->fullUrl(),
|
'url' => $request->fullUrl(),
|
||||||
'payload' => $request->all()
|
'payload' => $request->all(),
|
||||||
], 403);
|
], 403);
|
||||||
} else if (in_array($routeName, self::LOG_ROUTES)) {
|
} elseif (in_array($routeName, self::LOG_ROUTES)) {
|
||||||
\Log::warning(self::ADMIN_LOG_PREFIX . 'Impersonator action', [
|
\Log::warning(self::ADMIN_LOG_PREFIX.'Impersonator action', [
|
||||||
'route' => $routeName,
|
'route' => $routeName,
|
||||||
'url' => $request->fullUrl(),
|
'url' => $request->fullUrl(),
|
||||||
'impersonated_account' => auth()->id(),
|
'impersonated_account' => auth()->id(),
|
||||||
'impersonator' => auth()->payload()->get('impersonator_id'),
|
'impersonator' => auth()->payload()->get('impersonator_id'),
|
||||||
'payload' => $request->all()
|
'payload' => $request->all(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,11 @@ class IsAdmin
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
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...
|
// This user is not a paying customer...
|
||||||
if ($request->expectsJson()) {
|
if ($request->expectsJson()) {
|
||||||
return response([
|
return response([
|
||||||
|
@ -24,6 +22,7 @@ class IsAdmin
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect('home');
|
return redirect('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,11 @@ class IsModerator
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
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...
|
// This user is not a paying customer...
|
||||||
if ($request->expectsJson()) {
|
if ($request->expectsJson()) {
|
||||||
return response([
|
return response([
|
||||||
|
@ -24,6 +22,7 @@ class IsModerator
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect('home');
|
return redirect('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ class IsNotSubscribed
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
|
@ -24,6 +22,7 @@ class IsNotSubscribed
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect('billing');
|
return redirect('billing');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,11 @@ class IsSubscribed
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
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...
|
// This user is not a paying customer...
|
||||||
if ($request->expectsJson()) {
|
if ($request->expectsJson()) {
|
||||||
return response([
|
return response([
|
||||||
|
@ -24,6 +22,7 @@ class IsSubscribed
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect('billing');
|
return redirect('billing');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ class RedirectIfAuthenticated
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @param string|null ...$guards
|
* @param string|null ...$guards
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,7 +10,6 @@ class SetLocale
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
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
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
protected function parseLocale($request)
|
protected function parseLocale($request)
|
||||||
|
|
|
@ -18,10 +18,11 @@ class TrimStrings extends Middleware
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The route name where this shouldn't be applied
|
* The route name where this shouldn't be applied
|
||||||
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $exceptUrls = [
|
protected $exceptUrls = [
|
||||||
'/\/api\/forms\/(.*)\/answer/'
|
'/\/api\/forms\/(.*)\/answer/',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function handle($request, \Closure $next)
|
public function handle($request, \Closure $next)
|
||||||
|
|
|
@ -14,6 +14,6 @@ class VerifyCsrfToken extends Middleware
|
||||||
protected $except = [
|
protected $except = [
|
||||||
'stripe/webhook',
|
'stripe/webhook',
|
||||||
'vapor/signed-storage-url',
|
'vapor/signed-storage-url',
|
||||||
'upload-file'
|
'upload-file',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class AiGenerateFormRequest extends FormRequest
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'form_prompt' => 'required|string|max:1000'
|
'form_prompt' => 'required|string|max:1000',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,22 @@
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
|
|
||||||
use App\Rules\StorageFile;
|
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\ValidHCaptcha;
|
||||||
use App\Rules\ValidPhoneInputRule;
|
use App\Rules\ValidPhoneInputRule;
|
||||||
use App\Rules\ValidUrl;
|
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
|
class AnswerFormRequest extends FormRequest
|
||||||
{
|
{
|
||||||
public Form $form;
|
public Form $form;
|
||||||
|
|
||||||
protected array $requestRules = [];
|
protected array $requestRules = [];
|
||||||
|
|
||||||
protected int $maxFileSize;
|
protected int $maxFileSize;
|
||||||
|
|
||||||
public function __construct(Request $request)
|
public function __construct(Request $request)
|
||||||
|
@ -34,7 +34,7 @@ class AnswerFormRequest extends FormRequest
|
||||||
*/
|
*/
|
||||||
public function authorize()
|
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) {
|
$selectionFields = collect($this->form->properties)->filter(function ($pro) {
|
||||||
return in_array($pro['type'], ['select', 'multi_select']);
|
return in_array($pro['type'], ['select', 'multi_select']);
|
||||||
});
|
});
|
||||||
foreach ($selectionFields as $field){
|
foreach ($selectionFields as $field) {
|
||||||
if(isset($data[$field['id']]) && is_array($data[$field['id']])){
|
if (isset($data[$field['id']]) && is_array($data[$field['id']])) {
|
||||||
$data[$field['id']] = array_map(function ($val) use ($field) {
|
$data[$field['id']] = array_map(function ($val) use ($field) {
|
||||||
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
|
$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']]);
|
}, $data[$field['id']]);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
if (FormLogicPropertyResolver::isRequired($property, $data)) {
|
if (FormLogicPropertyResolver::isRequired($property, $data)) {
|
||||||
$rules[] = 'required';
|
$rules[] = 'required';
|
||||||
|
|
||||||
if ($property['type'] == 'checkbox') {
|
if ($property['type'] == 'checkbox') {
|
||||||
// Required for checkboxes means true
|
// Required for checkboxes means true
|
||||||
$rules[] = 'accepted';
|
$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
|
// For star rating, needs a minimum of 1 star
|
||||||
$rules[] = 'min:1';
|
$rules[] = 'min:1';
|
||||||
}
|
}
|
||||||
|
@ -107,6 +108,7 @@ class AnswerFormRequest extends FormRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renames validated fields (because field names are ids)
|
* Renames validated fields (because field names are ids)
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function attributes()
|
public function attributes()
|
||||||
|
@ -115,6 +117,7 @@ class AnswerFormRequest extends FormRequest
|
||||||
foreach ($this->form->properties as $property) {
|
foreach ($this->form->properties as $property) {
|
||||||
$fields[$property['id']] = $property['name'];
|
$fields[$property['id']] = $property['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,21 +130,21 @@ class AnswerFormRequest extends FormRequest
|
||||||
{
|
{
|
||||||
$messages = [];
|
$messages = [];
|
||||||
foreach ($this->form->properties as $property) {
|
foreach ($this->form->properties as $property) {
|
||||||
if($property['type'] == 'date' && isset($property['date_range']) && $property['date_range']){
|
if ($property['type'] == 'date' && isset($property['date_range']) && $property['date_range']) {
|
||||||
$messages[$property['id'].'.0.required_with'] = "From date is required";
|
$messages[$property['id'].'.0.required_with'] = 'From date is required';
|
||||||
$messages[$property['id'].'.1.required_with'] = "To 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";
|
$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']) {
|
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 $messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return validation rules for a given form property
|
* Return validation rules for a given form property
|
||||||
* @param $property
|
|
||||||
*/
|
*/
|
||||||
private function getPropertyRules($property): array
|
private function getPropertyRules($property): array
|
||||||
{
|
{
|
||||||
|
@ -153,27 +156,32 @@ class AnswerFormRequest extends FormRequest
|
||||||
if ($property['is_rating'] ?? false) {
|
if ($property['is_rating'] ?? false) {
|
||||||
return ['numeric'];
|
return ['numeric'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['numeric'];
|
return ['numeric'];
|
||||||
case 'select':
|
case 'select':
|
||||||
case 'multi_select':
|
case 'multi_select':
|
||||||
if (($property['allow_creation'] ?? false)) {
|
if (($property['allow_creation'] ?? false)) {
|
||||||
return ['string'];
|
return ['string'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [Rule::in($this->getSelectPropertyOptions($property))];
|
return [Rule::in($this->getSelectPropertyOptions($property))];
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
return ['boolean'];
|
return ['boolean'];
|
||||||
case 'url':
|
case 'url':
|
||||||
if (isset($property['file_upload']) && $property['file_upload']) {
|
if (isset($property['file_upload']) && $property['file_upload']) {
|
||||||
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, [], $this->form)];
|
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, [], $this->form)];
|
||||||
|
|
||||||
return ['array'];
|
return ['array'];
|
||||||
}
|
}
|
||||||
return [new ValidUrl];
|
|
||||||
|
return [new ValidUrl()];
|
||||||
case 'files':
|
case 'files':
|
||||||
$allowedFileTypes = [];
|
$allowedFileTypes = [];
|
||||||
if(!empty($property['allowed_file_types'])){
|
if (! empty($property['allowed_file_types'])) {
|
||||||
$allowedFileTypes = explode(",", $property['allowed_file_types']);
|
$allowedFileTypes = explode(',', $property['allowed_file_types']);
|
||||||
}
|
}
|
||||||
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, $allowedFileTypes, $this->form)];
|
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, $allowedFileTypes, $this->form)];
|
||||||
|
|
||||||
return ['array'];
|
return ['array'];
|
||||||
case 'email':
|
case 'email':
|
||||||
return ['email:filter'];
|
return ['email:filter'];
|
||||||
|
@ -182,14 +190,17 @@ class AnswerFormRequest extends FormRequest
|
||||||
$this->requestRules[$property['id'].'.*'] = $this->getRulesForDate($property);
|
$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'].'.0'] = ['required_with:'.$property['id'].'.1', 'before_or_equal:'.$property['id'].'.1'];
|
||||||
$this->requestRules[$property['id'].'.1'] = ['required_with:'.$property['id'].'.0'];
|
$this->requestRules[$property['id'].'.1'] = ['required_with:'.$property['id'].'.0'];
|
||||||
|
|
||||||
return ['array', 'min:2'];
|
return ['array', 'min:2'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getRulesForDate($property);
|
return $this->getRulesForDate($property);
|
||||||
case 'phone_number':
|
case 'phone_number':
|
||||||
if (isset($property['use_simple_text_input']) && $property['use_simple_text_input']) {
|
if (isset($property['use_simple_text_input']) && $property['use_simple_text_input']) {
|
||||||
return ['string'];
|
return ['string'];
|
||||||
}
|
}
|
||||||
return ['string', 'min:6', new ValidPhoneInputRule];
|
|
||||||
|
return ['string', 'min:6', new ValidPhoneInputRule()];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -199,18 +210,20 @@ class AnswerFormRequest extends FormRequest
|
||||||
{
|
{
|
||||||
if (isset($property['disable_past_dates']) && $property['disable_past_dates']) {
|
if (isset($property['disable_past_dates']) && $property['disable_past_dates']) {
|
||||||
return ['date', 'after_or_equal:today'];
|
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', 'before_or_equal:today'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['date'];
|
return ['date'];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSelectPropertyOptions($property): array
|
private function getSelectPropertyOptions($property): array
|
||||||
{
|
{
|
||||||
$type = $property['type'];
|
$type = $property['type'];
|
||||||
if (!isset($property[$type])) {
|
if (! isset($property[$type])) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_column($property[$type]['options'], 'name');
|
return array_column($property[$type]['options'], 'name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,25 +236,26 @@ class AnswerFormRequest extends FormRequest
|
||||||
$receivedValue = $receivedData[$property['id']] ?? null;
|
$receivedValue = $receivedData[$property['id']] ?? null;
|
||||||
|
|
||||||
// Escape all '\' in select options
|
// 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)) {
|
if (is_array($receivedValue)) {
|
||||||
$mergeData[$property['id']] = collect($receivedValue)->map(function ($value) {
|
$mergeData[$property['id']] = collect($receivedValue)->map(function ($value) {
|
||||||
$value = Str::of($value);
|
$value = Str::of($value);
|
||||||
|
|
||||||
return $value->replace(
|
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();
|
)->toString();
|
||||||
})->toArray();
|
})->toArray();
|
||||||
} else {
|
} else {
|
||||||
$receivedValue = Str::of($receivedValue);
|
$receivedValue = Str::of($receivedValue);
|
||||||
$mergeData[$property['id']] = $receivedValue->replace(
|
$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();
|
)->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;
|
$mergeData[$property['id']] = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,12 +17,14 @@ class StoreFormZapierWebhookRequest extends FormRequest
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'form_slug' => 'required|exists:forms,slug',
|
'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();
|
$form = Form::whereSlug($this->form_slug)->firstOrFail();
|
||||||
|
|
||||||
return new FormZapierWebhook([
|
return new FormZapierWebhook([
|
||||||
'form_id' => $form->id,
|
'form_id' => $form->id,
|
||||||
'hook_url' => $this->hook_url,
|
'hook_url' => $this->hook_url,
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class StoreFormRequest extends UserFormRequest
|
class StoreFormRequest extends UserFormRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class FormTemplateRequest extends FormRequest
|
class FormTemplateRequest extends FormRequest
|
||||||
{
|
{
|
||||||
const IGNORED_KEYS = [
|
public const IGNORED_KEYS = [
|
||||||
'id',
|
'id',
|
||||||
'creator',
|
'creator',
|
||||||
'cleanings',
|
'cleanings',
|
||||||
|
@ -49,9 +49,10 @@ class FormTemplateRequest extends FormRequest
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
$slugRule = '';
|
$slugRule = '';
|
||||||
if($this->id){
|
if ($this->id) {
|
||||||
$slugRule = ','.$this->id;
|
$slugRule = ','.$this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'form' => 'required|array',
|
'form' => 'required|array',
|
||||||
'publicly_listed' => 'boolean',
|
'publicly_listed' => 'boolean',
|
||||||
|
@ -88,7 +89,7 @@ class FormTemplateRequest extends FormRequest
|
||||||
'types' => $this->types ?? [],
|
'types' => $this->types ?? [],
|
||||||
'industries' => $this->industries ?? [],
|
'industries' => $this->industries ?? [],
|
||||||
'related_templates' => $this->related_templates ?? [],
|
'related_templates' => $this->related_templates ?? [],
|
||||||
'questions' => $this->questions ?? []
|
'questions' => $this->questions ?? [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,4 @@ namespace App\Http\Requests;
|
||||||
|
|
||||||
class UpdateFormRequest extends UserFormRequest
|
class UpdateFormRequest extends UserFormRequest
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class UploadAssetRequest extends 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.
|
* Get the validation rules that apply to the request.
|
||||||
|
@ -22,7 +22,7 @@ class UploadAssetRequest extends FormRequest
|
||||||
'jpg',
|
'jpg',
|
||||||
'bmp',
|
'bmp',
|
||||||
'gif',
|
'gif',
|
||||||
'svg'
|
'svg',
|
||||||
];
|
];
|
||||||
if ($this->offsetExists('type') && $this->get('type') === 'files') {
|
if ($this->offsetExists('type') && $this->get('type') === 'files') {
|
||||||
$fileTypes = [];
|
$fileTypes = [];
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
|
||||||
use App\Http\Requests\Workspace\CustomDomainRequest;
|
use App\Http\Requests\Workspace\CustomDomainRequest;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
|
use App\Rules\FormPropertyLogicRule;
|
||||||
use App\Rules\OneEmailPerLine;
|
use App\Rules\OneEmailPerLine;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use App\Rules\FormPropertyLogicRule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class to validate create/update forms
|
* Abstract class to validate create/update forms
|
||||||
*
|
*
|
||||||
* Class UserFormRequest
|
* Class UserFormRequest
|
||||||
* @package App\Http\Requests
|
|
||||||
*/
|
*/
|
||||||
abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
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',
|
'title' => 'required|string|max:60',
|
||||||
'description' => 'nullable|string|max:2000',
|
'description' => 'nullable|string|max:2000',
|
||||||
'tags' => 'nullable|array',
|
'tags' => 'nullable|array',
|
||||||
'visibility' => ['required',Rule::in(Form::VISIBILITY)],
|
'visibility' => ['required', Rule::in(Form::VISIBILITY)],
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
'notifies' => 'boolean',
|
'notifies' => 'boolean',
|
||||||
'notification_emails' => ['required_if:notifies,1', new OneEmailPerLine ],
|
'notification_emails' => ['required_if:notifies,1', new OneEmailPerLine()],
|
||||||
'send_submission_confirmation' => 'boolean',
|
'send_submission_confirmation' => 'boolean',
|
||||||
'notification_sender' => 'string|max:64',
|
'notification_sender' => 'string|max:64',
|
||||||
'notification_subject' => 'string|max:200',
|
'notification_subject' => 'string|max:200',
|
||||||
|
@ -47,11 +44,11 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
||||||
'notification_settings' => 'nullable',
|
'notification_settings' => 'nullable',
|
||||||
|
|
||||||
// Customization
|
// Customization
|
||||||
'theme' => ['required',Rule::in(Form::THEMES)],
|
'theme' => ['required', Rule::in(Form::THEMES)],
|
||||||
'width' => ['required',Rule::in(Form::WIDTHS)],
|
'width' => ['required', Rule::in(Form::WIDTHS)],
|
||||||
'cover_picture' => 'url|nullable',
|
'cover_picture' => 'url|nullable',
|
||||||
'logo_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',
|
'color' => 'required|string',
|
||||||
'hide_title' => 'required|boolean',
|
'hide_title' => 'required|boolean',
|
||||||
'uppercase_labels' => 'required|boolean',
|
'uppercase_labels' => 'required|boolean',
|
||||||
|
@ -75,7 +72,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
||||||
'editable_submissions' => 'boolean|nullable',
|
'editable_submissions' => 'boolean|nullable',
|
||||||
'editable_submissions_button_text' => 'string|min:1|max:50',
|
'editable_submissions_button_text' => 'string|min:1|max:50',
|
||||||
'confetti_on_submission' => 'boolean',
|
'confetti_on_submission' => 'boolean',
|
||||||
'auto_save'=> 'boolean',
|
'auto_save' => 'boolean',
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
'properties' => 'required|array',
|
'properties' => 'required|array',
|
||||||
|
@ -90,7 +87,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
||||||
'properties.*.required' => 'boolean|nullable',
|
'properties.*.required' => 'boolean|nullable',
|
||||||
'properties.*.multiple' => 'boolean|nullable',
|
'properties.*.multiple' => 'boolean|nullable',
|
||||||
'properties.*.timezone' => 'sometimes|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.*.align' => ['sometimes', Rule::in(['left', 'center', 'right', 'justify'])],
|
||||||
'properties.*.allowed_file_types' => 'sometimes|nullable',
|
'properties.*.allowed_file_types' => 'sometimes|nullable',
|
||||||
'properties.*.use_toggle_switch' => 'boolean|nullable',
|
'properties.*.use_toggle_switch' => 'boolean|nullable',
|
||||||
|
@ -127,7 +124,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
||||||
|
|
||||||
// Custom SEO
|
// Custom SEO
|
||||||
'seo_meta' => 'nullable|array',
|
'seo_meta' => 'nullable|array',
|
||||||
'custom_domain' => 'sometimes|nullable|regex:'. CustomDomainRequest::CUSTOM_DOMAINS_REGEX,
|
'custom_domain' => 'sometimes|nullable|regex:'.CustomDomainRequest::CUSTOM_DOMAINS_REGEX,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,12 @@ use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CustomDomainRequest extends FormRequest
|
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 Workspace $workspace;
|
||||||
|
|
||||||
public array $customDomains = [];
|
public array $customDomains = [];
|
||||||
|
|
||||||
public function __construct(Request $request, Workspace $workspace)
|
public function __construct(Request $request, Workspace $workspace)
|
||||||
{
|
{
|
||||||
$this->workspace = Workspace::findOrFail($request->workspaceId);
|
$this->workspace = Workspace::findOrFail($request->workspaceId);
|
||||||
|
@ -28,13 +30,13 @@ class CustomDomainRequest extends FormRequest
|
||||||
'custom_domains' => [
|
'custom_domains' => [
|
||||||
'present',
|
'present',
|
||||||
'array',
|
'array',
|
||||||
function($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$domains = collect($value)->filter(function ($domain) {
|
$domains = collect($value)->filter(function ($domain) {
|
||||||
return !empty( trim($domain) );
|
return ! empty(trim($domain));
|
||||||
})->each(function($domain) use (&$errors) {
|
})->each(function ($domain) use (&$errors) {
|
||||||
if (!preg_match(self::CUSTOM_DOMAINS_REGEX, $domain)) {
|
if (! preg_match(self::CUSTOM_DOMAINS_REGEX, $domain)) {
|
||||||
$errors[] = 'Invalid domain: ' . $domain;
|
$errors[] = 'Invalid domain: '.$domain;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,16 +46,17 @@ class CustomDomainRequest extends FormRequest
|
||||||
|
|
||||||
$limit = $this->workspace->custom_domain_count_limit;
|
$limit = $this->workspace->custom_domain_count_limit;
|
||||||
if ($limit && $domains->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();
|
$this->customDomains = $domains->toArray();
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function passedValidation(){
|
protected function passedValidation()
|
||||||
|
{
|
||||||
$this->replace(['custom_domains' => $this->customDomains]);
|
$this->replace(['custom_domains' => $this->customDomains]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use App\Http\Middleware\Form\ProtectedForm;
|
use App\Http\Middleware\Form\ProtectedForm;
|
||||||
use App\Http\Resources\UserResource;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class FormResource extends JsonResource
|
class FormResource extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -20,7 +18,7 @@ class FormResource extends JsonResource
|
||||||
*/
|
*/
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
if(!$this->userIsFormOwner() && ProtectedForm::isProtected($request, $this->resource)){
|
if (! $this->userIsFormOwner() && ProtectedForm::isProtected($request, $this->resource)) {
|
||||||
return $this->getProtectedForm();
|
return $this->getProtectedForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +67,7 @@ class FormResource extends JsonResource
|
||||||
public function setCleanings(array $cleanings)
|
public function setCleanings(array $cleanings)
|
||||||
{
|
{
|
||||||
$this->cleanings = $cleanings;
|
$this->cleanings = $cleanings;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,15 +95,18 @@ class FormResource extends JsonResource
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getWorkspace() {
|
private function getWorkspace()
|
||||||
|
{
|
||||||
return $this->extra?->loadedWorkspace ?? $this->workspace;
|
return $this->extra?->loadedWorkspace ?? $this->workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function workspaceIsPro() {
|
private function workspaceIsPro()
|
||||||
|
{
|
||||||
return $this->extra?->workspaceIsPro ?? $this->getWorkspace()->is_pro ?? $this->is_pro;
|
return $this->extra?->workspaceIsPro ?? $this->getWorkspace()->is_pro ?? $this->is_pro;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function userIsFormOwner() {
|
private function userIsFormOwner()
|
||||||
|
{
|
||||||
return $this->extra?->userIsOwner ??
|
return $this->extra?->userIsOwner ??
|
||||||
(
|
(
|
||||||
Auth::check() && Auth::user()->ownsForm($this->resource)
|
Auth::check() && Auth::user()->ownsForm($this->resource)
|
||||||
|
|
|
@ -6,7 +6,6 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class FormSubmissionResource extends JsonResource
|
class FormSubmissionResource extends JsonResource
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
|
@ -28,13 +27,14 @@ class FormSubmissionResource extends JsonResource
|
||||||
private function addExtraData()
|
private function addExtraData()
|
||||||
{
|
{
|
||||||
$this->data = array_merge($this->data, [
|
$this->data = array_merge($this->data, [
|
||||||
"created_at" => $this->created_at->toDateTimeString(),
|
'created_at' => $this->created_at->toDateTimeString(),
|
||||||
'id' => $this->id
|
'id' => $this->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link to the file (generating signed s3 URL)
|
* Link to the file (generating signed s3 URL)
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function generateFileLinks()
|
private function generateFileLinks()
|
||||||
|
@ -45,7 +45,7 @@ class FormSubmissionResource extends JsonResource
|
||||||
return in_array($field['type'], ['files', 'signature']);
|
return in_array($field['type'], ['files', 'signature']);
|
||||||
});
|
});
|
||||||
foreach ($fileFields as $field) {
|
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) {
|
$data[$field['id']] = collect($data[$field['id']])->filter(function ($file) {
|
||||||
return $file !== null && $file;
|
return $file !== null && $file;
|
||||||
})->map(function ($file) {
|
})->map(function ($file) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ class FormTemplateResource extends JsonResource
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
return array_merge(parent::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)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ class UserResource extends JsonResource
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||||
*/
|
*/
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class WorkspaceResource extends JsonResource
|
class WorkspaceResource extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -12,7 +11,7 @@ class WorkspaceResource extends JsonResource
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
|
|
|
@ -14,7 +14,10 @@ use Illuminate\Support\Str;
|
||||||
|
|
||||||
class GenerateAiForm implements ShouldQueue
|
class GenerateAiForm implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
|
@ -34,7 +37,7 @@ class GenerateAiForm implements ShouldQueue
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->completion->update([
|
$this->completion->update([
|
||||||
'status' => AiFormCompletion::STATUS_PROCESSING
|
'status' => AiFormCompletion::STATUS_PROCESSING,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$completer = (new GptCompleter(config('services.openai.api_key')))
|
$completer = (new GptCompleter(config('services.openai.api_key')))
|
||||||
|
@ -44,13 +47,13 @@ class GenerateAiForm implements ShouldQueue
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$completer->completeChat([
|
$completer->completeChat([
|
||||||
["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT)
|
['role' => 'user', 'content' => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT)
|
||||||
->replace('[REPLACE]', $this->completion->form_prompt)->toString()]
|
->replace('[REPLACE]', $this->completion->form_prompt)->toString()],
|
||||||
], 3000);
|
], 3000);
|
||||||
|
|
||||||
$this->completion->update([
|
$this->completion->update([
|
||||||
'status' => AiFormCompletion::STATUS_COMPLETED,
|
'status' => AiFormCompletion::STATUS_COMPLETED,
|
||||||
'result' => $this->cleanOutput($completer->getArray())
|
'result' => $this->cleanOutput($completer->getArray()),
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->onError($e);
|
$this->onError($e);
|
||||||
|
@ -76,10 +79,11 @@ class GenerateAiForm implements ShouldQueue
|
||||||
$this->onError($exception);
|
$this->onError($exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function onError(\Throwable $e) {
|
private function onError(\Throwable $e)
|
||||||
|
{
|
||||||
$this->completion->update([
|
$this->completion->update([
|
||||||
'status' => AiFormCompletion::STATUS_FAILED,
|
'status' => AiFormCompletion::STATUS_FAILED,
|
||||||
'result' => ['error' => $e->getMessage()]
|
'result' => ['error' => $e->getMessage()],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,14 @@
|
||||||
namespace App\Jobs\Form;
|
namespace App\Jobs\Form;
|
||||||
|
|
||||||
use App\Events\Forms\FormSubmitted;
|
use App\Events\Forms\FormSubmitted;
|
||||||
use App\Http\Controllers\Forms\PublicFormController;
|
|
||||||
use App\Http\Controllers\Forms\FormController;
|
use App\Http\Controllers\Forms\FormController;
|
||||||
|
use App\Http\Controllers\Forms\PublicFormController;
|
||||||
use App\Http\Requests\AnswerFormRequest;
|
use App\Http\Requests\AnswerFormRequest;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use App\Models\Forms\FormSubmission;
|
use App\Models\Forms\FormSubmission;
|
||||||
use App\Service\Forms\FormLogicPropertyResolver;
|
use App\Service\Forms\FormLogicPropertyResolver;
|
||||||
use App\Service\Storage\StorageFileNameParser;
|
use App\Service\Storage\StorageFileNameParser;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
@ -22,7 +21,10 @@ use Vinkla\Hashids\Facades\Hashids;
|
||||||
|
|
||||||
class StoreFormSubmissionJob implements ShouldQueue
|
class StoreFormSubmissionJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
public ?string $submissionId = null;
|
public ?string $submissionId = null;
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
|
|
||||||
$this->storeSubmission($formData);
|
$this->storeSubmission($formData);
|
||||||
|
|
||||||
$formData["submission_id"] = $this->submissionId;
|
$formData['submission_id'] = $this->submissionId;
|
||||||
FormSubmitted::dispatch($this->form, $formData);
|
FormSubmitted::dispatch($this->form, $formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
public function setSubmissionId(int $id)
|
public function setSubmissionId(int $id)
|
||||||
{
|
{
|
||||||
$this->submissionId = $id;
|
$this->submissionId = $id;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +85,13 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
*/
|
*/
|
||||||
private function submissionToUpdate(): ?FormSubmission
|
private function submissionToUpdate(): ?FormSubmission
|
||||||
{
|
{
|
||||||
if($this->submissionId){
|
if ($this->submissionId) {
|
||||||
return $this->form->submissions()->findOrFail($this->submissionId);
|
return $this->form->submissions()->findOrFail($this->submissionId);
|
||||||
}
|
}
|
||||||
if ($this->form->editable_submissions && isset($this->submissionData['submission_id']) && $this->submissionData['submission_id']) {
|
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 = $this->submissionData['submission_id'] ? Hashids::decode($this->submissionData['submission_id']) : false;
|
||||||
$submissionId = $submissionId[0] ?? null;
|
$submissionId = $submissionId[0] ?? null;
|
||||||
|
|
||||||
return $this->form->submissions()->findOrFail($submissionId);
|
return $this->form->submissions()->findOrFail($submissionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +115,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
// Do required transformation per type (e.g. file uploads)
|
// Do required transformation per type (e.g. file uploads)
|
||||||
foreach ($data as $answerKey => $answerValue) {
|
foreach ($data as $answerKey => $answerValue) {
|
||||||
$field = $properties->where('id', $answerKey)->first();
|
$field = $properties->where('id', $answerKey)->first();
|
||||||
if (!$field) {
|
if (! $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +132,10 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($field['type'] == 'text' && isset($field['generates_uuid']) && $field['generates_uuid']) {
|
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 {
|
} else {
|
||||||
if ($field['type'] == 'text' && isset($field['generates_auto_increment_id']) && $field['generates_auto_increment_id']) {
|
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 {
|
} else {
|
||||||
$finalData[$field['id']] = $answerValue;
|
$finalData[$field['id']] = $answerValue;
|
||||||
}
|
}
|
||||||
|
@ -139,12 +143,12 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Singrature
|
// For Singrature
|
||||||
if($this->form->is_pro && $field['type'] == 'signature') {
|
if ($this->form->is_pro && $field['type'] == 'signature') {
|
||||||
$finalData[$field['id']] = $this->storeSignature($answerValue);
|
$finalData[$field['id']] = $this->storeSignature($answerValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Phone
|
// 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);
|
$finalData[$field['id']] = substr($answerValue, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,6 +160,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
private function isSkipForUpload($value)
|
private function isSkipForUpload($value)
|
||||||
{
|
{
|
||||||
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
|
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
|
||||||
|
|
||||||
return Storage::exists($newPath.'/'.$value);
|
return Storage::exists($newPath.'/'.$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,15 +178,16 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
return null;
|
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);
|
$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);
|
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
|
||||||
Storage::move($path, $newPath.'/'.$fileName);
|
Storage::move($path, $newPath.'/'.$fileName);
|
||||||
|
|
||||||
return $fileName;
|
return $fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->isSkipForUpload($value)) {
|
if ($this->isSkipForUpload($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +195,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
|
|
||||||
// Make sure we retrieve the file in tmp storage, move it to persistent
|
// Make sure we retrieve the file in tmp storage, move it to persistent
|
||||||
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;
|
$fileName = PublicFormController::TMP_FILE_UPLOAD_PATH.'/'.$fileNameParser->uuid;
|
||||||
if (!Storage::exists($fileName)) {
|
if (! Storage::exists($fileName)) {
|
||||||
// File not found, we skip
|
// File not found, we skip
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -209,7 +215,7 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
|
|
||||||
private function storeSignature(?string $value)
|
private function storeSignature(?string $value)
|
||||||
{
|
{
|
||||||
if ($value == null || !isset(explode(',', $value)[1])) {
|
if ($value == null || ! isset(explode(',', $value)[1])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +231,6 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
/**
|
/**
|
||||||
* Adds prefill from hidden fields
|
* Adds prefill from hidden fields
|
||||||
*
|
*
|
||||||
* @param array $formData
|
|
||||||
* @param AnswerFormRequest $request
|
* @param AnswerFormRequest $request
|
||||||
*/
|
*/
|
||||||
private function addHiddenPrefills(array &$formData): void
|
private function addHiddenPrefills(array &$formData): void
|
||||||
|
@ -235,9 +240,9 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
return isset($property['hidden'])
|
return isset($property['hidden'])
|
||||||
&& isset($property['prefill'])
|
&& isset($property['prefill'])
|
||||||
&& FormLogicPropertyResolver::isHidden($property, $this->submissionData)
|
&& FormLogicPropertyResolver::isHidden($property, $this->submissionData)
|
||||||
&& !is_null($property['prefill'])
|
&& ! is_null($property['prefill'])
|
||||||
&& !in_array($property['type'], ['files'])
|
&& ! in_array($property['type'], ['files'])
|
||||||
&& !($property['type'] == 'url' && isset($property['file_upload']) && $property['file_upload']);
|
&& ! ($property['type'] == 'url' && isset($property['file_upload']) && $property['file_upload']);
|
||||||
})->each(function (array $property) use (&$formData) {
|
})->each(function (array $property) use (&$formData) {
|
||||||
if ($property['type'] === 'date' && isset($property['prefill_today']) && $property['prefill_today']) {
|
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');
|
$formData[$property['id']] = now()->format((isset($property['with_time']) && $property['with_time']) ? 'Y-m-d H:i' : 'Y-m-d');
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
use App\Notifications\Forms\FailedWebhookNotification;
|
use App\Notifications\Forms\FailedWebhookNotification;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
|
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
|
||||||
|
|
||||||
class FailedWebhookListener
|
class FailedWebhookListener
|
||||||
|
@ -12,7 +10,7 @@ class FailedWebhookListener
|
||||||
/**
|
/**
|
||||||
* Handle the event.
|
* Handle the event.
|
||||||
*
|
*
|
||||||
* @param object $event
|
* @param object $event
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle(WebhookCallFailedEvent $event)
|
public function handle(WebhookCallFailedEvent $event)
|
||||||
|
@ -24,15 +22,16 @@ class FailedWebhookListener
|
||||||
'webhook_url' => $event->webhookUrl,
|
'webhook_url' => $event->webhookUrl,
|
||||||
'exception' => $event->errorType,
|
'exception' => $event->errorType,
|
||||||
'message' => $event->errorMessage,
|
'message' => $event->errorMessage,
|
||||||
'form_id' => $event->meta['form']->id
|
'form_id' => $event->meta['form']->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
\Log::error('Failed webhook', [
|
\Log::error('Failed webhook', [
|
||||||
'webhook_url' => $event->webhookUrl,
|
'webhook_url' => $event->webhookUrl,
|
||||||
'exception' => $event->errorType,
|
'exception' => $event->errorType,
|
||||||
'message' => $event->errorMessage
|
'message' => $event->errorMessage,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,10 @@ namespace App\Listeners\Forms;
|
||||||
use App\Events\Models\FormCreated;
|
use App\Events\Models\FormCreated;
|
||||||
use App\Mail\Forms\FormCreationConfirmationMail;
|
use App\Mail\Forms\FormCreationConfirmationMail;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
class FormCreationConfirmation implements ShouldQueue
|
class FormCreationConfirmation implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the event.
|
* Handle the event.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,16 +2,13 @@
|
||||||
|
|
||||||
namespace App\Listeners\Forms;
|
namespace App\Listeners\Forms;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
|
||||||
use App\Events\Forms\FormSubmitted;
|
use App\Events\Forms\FormSubmitted;
|
||||||
use Illuminate\Support\Facades\Http;
|
use App\Models\Forms\Form;
|
||||||
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\Notifications\Forms\FormSubmissionNotification;
|
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
|
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
|
* Sends an email to each email address in the form's notification_emails field
|
||||||
* @param FormSubmitted $event
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function sendEmailNotifications(FormSubmitted $event)
|
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 (
|
$subscribers = collect(preg_split("/\r\n|\n|\r/", $event->form->notification_emails))->filter(function (
|
||||||
$email
|
$email
|
||||||
|
|
|
@ -12,13 +12,12 @@ use Illuminate\Support\Facades\Mail;
|
||||||
* Sends a confirmation to form respondant that form was submitted
|
* Sends a confirmation to form respondant that form was submitted
|
||||||
*
|
*
|
||||||
* Class SubmissionConfirmation
|
* Class SubmissionConfirmation
|
||||||
* @package App\Listeners\Forms
|
|
||||||
*/
|
*/
|
||||||
class SubmissionConfirmation implements ShouldQueue
|
class SubmissionConfirmation implements ShouldQueue
|
||||||
{
|
{
|
||||||
use InteractsWithQueue;
|
use InteractsWithQueue;
|
||||||
|
|
||||||
const RISKY_USERS_LIMIT = 120;
|
public const RISKY_USERS_LIMIT = 120;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the event.
|
* Handle the event.
|
||||||
|
@ -29,17 +28,19 @@ class SubmissionConfirmation implements ShouldQueue
|
||||||
public function handle(FormSubmitted $event)
|
public function handle(FormSubmitted $event)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
!$event->form->is_pro ||
|
! $event->form->is_pro ||
|
||||||
!$event->form->send_submission_confirmation ||
|
! $event->form->send_submission_confirmation ||
|
||||||
$this->riskLimitReached($event) // To avoid phishing abuse we limit this feature for risky users
|
$this->riskLimitReached($event) // To avoid phishing abuse we limit this feature for risky users
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = $this->getRespondentEmail($event);
|
$email = $this->getRespondentEmail($event);
|
||||||
if (!$email) return;
|
if (! $email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
\Log::info('Sending submission confirmation',[
|
\Log::info('Sending submission confirmation', [
|
||||||
'recipient' => $email,
|
'recipient' => $email,
|
||||||
'form_id' => $event->form->id,
|
'form_id' => $event->form->id,
|
||||||
'form_slug' => $event->form->slug,
|
'form_slug' => $event->form->slug,
|
||||||
|
@ -50,15 +51,20 @@ class SubmissionConfirmation implements ShouldQueue
|
||||||
private function getRespondentEmail(FormSubmitted $event)
|
private function getRespondentEmail(FormSubmitted $event)
|
||||||
{
|
{
|
||||||
// Make sure we only have one email field in the form
|
// Make sure we only have one email field in the form
|
||||||
$emailFields = collect($event->form->properties)->filter(function($field) {
|
$emailFields = collect($event->form->properties)->filter(function ($field) {
|
||||||
$hidden = $field['hidden']?? false;
|
$hidden = $field['hidden'] ?? false;
|
||||||
return !$hidden && $field['type'] == 'email';
|
|
||||||
|
return ! $hidden && $field['type'] == 'email';
|
||||||
});
|
});
|
||||||
if ($emailFields->count() != 1) return null;
|
if ($emailFields->count() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($event->data[$emailFields->first()['id']])) {
|
if (isset($event->data[$emailFields->first()['id']])) {
|
||||||
$email = $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;
|
return null;
|
||||||
|
@ -73,13 +79,16 @@ class SubmissionConfirmation implements ShouldQueue
|
||||||
'form_id' => $event->form->id,
|
'form_id' => $event->form->id,
|
||||||
'workspace_id' => $event->form->workspace->id,
|
'workspace_id' => $event->form->workspace->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function validateEmail($email): bool {
|
public static function validateEmail($email): bool
|
||||||
return (boolean) filter_var($email, FILTER_VALIDATE_EMAIL);
|
{
|
||||||
|
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ use App\Mail\OpenFormMail;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Mail\Mailable;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class FormCreationConfirmationMail extends OpenFormMail implements ShouldQueue
|
class FormCreationConfirmationMail extends OpenFormMail implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Queueable, SerializesModels;
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new message instance.
|
* Create a new message instance.
|
||||||
|
@ -31,8 +31,8 @@ class FormCreationConfirmationMail extends OpenFormMail implements ShouldQueue
|
||||||
public function build()
|
public function build()
|
||||||
{
|
{
|
||||||
return $this
|
return $this
|
||||||
->markdown('mail.form.created',[
|
->markdown('mail.form.created', [
|
||||||
'form' => $this->form,
|
'form' => $this->form,
|
||||||
])->subject('Your form was created!');
|
])->subject('Your form was created!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@ use App\Service\Forms\FormSubmissionFormatter;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Vinkla\Hashids\Facades\Hashids;
|
use Vinkla\Hashids\Facades\Hashids;
|
||||||
|
|
||||||
class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
|
class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Queueable, SerializesModels;
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new message instance.
|
* Create a new message instance.
|
||||||
|
@ -22,7 +23,8 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(private FormSubmitted $event)
|
public function __construct(private FormSubmitted $event)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the message.
|
* Build the message.
|
||||||
|
@ -42,23 +44,25 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
|
||||||
->replyTo($this->getReplyToEmail($form->creator->email))
|
->replyTo($this->getReplyToEmail($form->creator->email))
|
||||||
->from($this->getFromEmail(), $form->notification_sender)
|
->from($this->getFromEmail(), $form->notification_sender)
|
||||||
->subject($form->notification_subject)
|
->subject($form->notification_subject)
|
||||||
->markdown('mail.form.confirmation-submission-notification',[
|
->markdown('mail.form.confirmation-submission-notification', [
|
||||||
'fields' => $formatter->getFieldsWithValue(),
|
'fields' => $formatter->getFieldsWithValue(),
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'noBranding' => $form->no_branding,
|
'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()
|
private function getFromEmail()
|
||||||
{
|
{
|
||||||
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
|
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
|
||||||
return $originalFromAddress->first(). '+' . time() . '@' . $originalFromAddress->last();
|
|
||||||
|
return $originalFromAddress->first().'+'.time().'@'.$originalFromAddress->last();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getReplyToEmail($default)
|
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;
|
return $replyTo ?? $default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
namespace App\Mail;
|
namespace App\Mail;
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Mail\Mailable;
|
use Illuminate\Mail\Mailable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
abstract class OpenFormMail extends Mailable
|
abstract class OpenFormMail extends Mailable
|
||||||
{
|
{
|
||||||
use Queueable, SerializesModels;
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,13 @@ class AiFormCompletion extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
const STATUS_PENDING = 'pending';
|
public const STATUS_PENDING = 'pending';
|
||||||
const STATUS_PROCESSING = 'processing';
|
|
||||||
const STATUS_COMPLETED = 'completed';
|
public const STATUS_PROCESSING = 'processing';
|
||||||
const STATUS_FAILED = 'failed';
|
|
||||||
|
public const STATUS_COMPLETED = 'completed';
|
||||||
|
|
||||||
|
public const STATUS_FAILED = 'failed';
|
||||||
|
|
||||||
protected $table = 'ai_form_completions';
|
protected $table = 'ai_form_completions';
|
||||||
|
|
||||||
|
@ -21,11 +24,11 @@ class AiFormCompletion extends Model
|
||||||
'form_prompt',
|
'form_prompt',
|
||||||
'status',
|
'status',
|
||||||
'result',
|
'result',
|
||||||
'ip'
|
'ip',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'status' => self::STATUS_PENDING
|
'status' => self::STATUS_PENDING,
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
@ -9,26 +9,31 @@ use App\Models\Traits\CachesAttributes;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
use Database\Factories\FormFactory;
|
use Database\Factories\FormFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Sluggable\HasSlug;
|
use Spatie\Sluggable\HasSlug;
|
||||||
use Spatie\Sluggable\SlugOptions;
|
use Spatie\Sluggable\SlugOptions;
|
||||||
use Stevebauman\Purify\Facades\Purify;
|
use Stevebauman\Purify\Facades\Purify;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
||||||
|
|
||||||
class Form extends Model implements CachableAttributes
|
class Form extends Model implements CachableAttributes
|
||||||
{
|
{
|
||||||
use CachesAttributes;
|
use CachesAttributes;
|
||||||
|
|
||||||
const DARK_MODE_VALUES = ['auto', 'light', 'dark'];
|
use HasFactory;
|
||||||
const THEMES = ['default', 'simple', 'notion'];
|
use HasSlug;
|
||||||
const WIDTHS = ['centered', 'full'];
|
use SoftDeletes;
|
||||||
const VISIBILITY = ['public', 'draft', 'closed'];
|
|
||||||
|
|
||||||
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 = [
|
protected $fillable = [
|
||||||
'workspace_id',
|
'workspace_id',
|
||||||
|
@ -94,7 +99,7 @@ class Form extends Model implements CachableAttributes
|
||||||
'password',
|
'password',
|
||||||
|
|
||||||
// Custom SEO
|
// Custom SEO
|
||||||
'seo_meta'
|
'seo_meta',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
@ -104,7 +109,7 @@ class Form extends Model implements CachableAttributes
|
||||||
'tags' => 'array',
|
'tags' => 'array',
|
||||||
'removed_properties' => 'array',
|
'removed_properties' => 'array',
|
||||||
'seo_meta' => 'object',
|
'seo_meta' => 'object',
|
||||||
'notification_settings' => 'object'
|
'notification_settings' => 'object',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $appends = [
|
protected $appends = [
|
||||||
|
@ -127,13 +132,13 @@ class Form extends Model implements CachableAttributes
|
||||||
'password',
|
'password',
|
||||||
'tags',
|
'tags',
|
||||||
'notification_emails',
|
'notification_emails',
|
||||||
'removed_properties'
|
'removed_properties',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $cachableAttributes = [
|
protected $cachableAttributes = [
|
||||||
'is_pro',
|
'is_pro',
|
||||||
'views_count',
|
'views_count',
|
||||||
'max_file_size'
|
'max_file_size',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,14 +160,15 @@ class Form extends Model implements CachableAttributes
|
||||||
public function getShareUrlAttribute()
|
public function getShareUrlAttribute()
|
||||||
{
|
{
|
||||||
if ($this->custom_domain) {
|
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()
|
public function getEditUrlAttribute()
|
||||||
{
|
{
|
||||||
return front_url('/forms/' . $this->slug . '/show');
|
return front_url('/forms/'.$this->slug.'/show');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubmissionsCountAttribute()
|
public function getSubmissionsCountAttribute()
|
||||||
|
@ -174,9 +180,10 @@ class Form extends Model implements CachableAttributes
|
||||||
{
|
{
|
||||||
return $this->remember('views_count', 15 * 60, function (): int {
|
return $this->remember('views_count', 15 * 60, function (): int {
|
||||||
if (env('DB_CONNECTION') == 'mysql') {
|
if (env('DB_CONNECTION') == 'mysql') {
|
||||||
return (int)($this->views()->count() +
|
return (int) ($this->views()->count() +
|
||||||
$this->statistics()->sum(DB::raw("json_extract(data, '$.views')")));
|
$this->statistics()->sum(DB::raw("json_extract(data, '$.views')")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->views()->count() +
|
return $this->views()->count() +
|
||||||
$this->statistics()->sum(DB::raw("cast(data->>'views' as integer)"));
|
$this->statistics()->sum(DB::raw("cast(data->>'views' as integer)"));
|
||||||
});
|
});
|
||||||
|
@ -204,20 +211,21 @@ class Form extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getIsClosedAttribute()
|
public function getIsClosedAttribute()
|
||||||
{
|
{
|
||||||
return ($this->closes_at && now()->gt($this->closes_at));
|
return $this->closes_at && now()->gt($this->closes_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFormPendingSubmissionKeyAttribute()
|
public function getFormPendingSubmissionKeyAttribute()
|
||||||
{
|
{
|
||||||
if ($this->updated_at?->timestamp) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMaxNumberOfSubmissionsReachedAttribute()
|
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)
|
public function setClosedTextAttribute($value)
|
||||||
|
@ -232,12 +240,12 @@ class Form extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getHasPasswordAttribute()
|
public function getHasPasswordAttribute()
|
||||||
{
|
{
|
||||||
return !empty($this->password);
|
return ! empty($this->password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMaxFileSizeAttribute()
|
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;
|
return $this->workspace->max_file_size;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -292,7 +300,7 @@ class Form extends Model implements CachableAttributes
|
||||||
return SlugOptions::create()
|
return SlugOptions::create()
|
||||||
->doNotGenerateSlugsOnUpdate()
|
->doNotGenerateSlugsOnUpdate()
|
||||||
->generateSlugsFrom(function (Form $form) {
|
->generateSlugsFrom(function (Form $form) {
|
||||||
return $form->title . ' ' . Str::random(6);
|
return $form->title.' '.Str::random(6);
|
||||||
})
|
})
|
||||||
->saveSlugsTo('slug');
|
->saveSlugsTo('slug');
|
||||||
}
|
}
|
||||||
|
@ -302,19 +310,18 @@ class Form extends Model implements CachableAttributes
|
||||||
return FormFactory::new();
|
return FormFactory::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getNotifiesWebhookAttribute()
|
public function getNotifiesWebhookAttribute()
|
||||||
{
|
{
|
||||||
return !empty($this->webhook_url);
|
return ! empty($this->webhook_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNotifiesDiscordAttribute()
|
public function getNotifiesDiscordAttribute()
|
||||||
{
|
{
|
||||||
return !empty($this->discord_webhook_url);
|
return ! empty($this->discord_webhook_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNotifiesSlackAttribute()
|
public function getNotifiesSlackAttribute()
|
||||||
{
|
{
|
||||||
return !empty($this->slack_webhook_url);
|
return ! empty($this->slack_webhook_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Models\Forms;
|
namespace App\Models\Forms;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ class FormStatistic extends Model
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'form_id',
|
'form_id',
|
||||||
'data',
|
'data',
|
||||||
'date'
|
'date',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,5 +33,4 @@ class FormStatistic extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Form::class);
|
return $this->belongsTo(Form::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Models\Forms;
|
namespace App\Models\Forms;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
@ -11,17 +10,18 @@ class FormSubmission extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'data'
|
'data',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'data' => 'array'
|
'data' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RelationShips
|
* RelationShips
|
||||||
*/
|
*/
|
||||||
public function form() {
|
public function form()
|
||||||
|
{
|
||||||
return $this->belongsTo(Form::class);
|
return $this->belongsTo(Form::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Models\Forms;
|
namespace App\Models\Forms;
|
||||||
|
|
||||||
use App\Models\Forms\Form;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
@ -13,7 +12,8 @@ class FormView extends Model
|
||||||
/**
|
/**
|
||||||
* RelationShips
|
* RelationShips
|
||||||
*/
|
*/
|
||||||
public function form() {
|
public function form()
|
||||||
|
{
|
||||||
return $this->belongsTo(Form::class);
|
return $this->belongsTo(Form::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ use App\Service\Forms\Webhooks\WebhookHandlerProvider;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Spatie\WebhookServer\WebhookCall;
|
|
||||||
|
|
||||||
class FormZapierWebhook extends Model
|
class FormZapierWebhook extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, SoftDeletes;
|
use HasFactory;
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
protected $table = 'form_zapier_webhooks';
|
protected $table = 'form_zapier_webhooks';
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,17 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class License extends Model
|
class License extends Model
|
||||||
{
|
{
|
||||||
const STATUS_ACTIVE = 'active';
|
|
||||||
const STATUS_INACTIVE = 'inactive';
|
|
||||||
|
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
public const STATUS_ACTIVE = 'active';
|
||||||
|
|
||||||
|
public const STATUS_INACTIVE = 'inactive';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'license_key',
|
'license_key',
|
||||||
'user_id',
|
'user_id',
|
||||||
'license_provider',
|
'license_provider',
|
||||||
'status',
|
'status',
|
||||||
'meta'
|
'meta',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|
|
@ -11,7 +11,8 @@ use Stevebauman\Purify\Facades\Purify;
|
||||||
|
|
||||||
class Template extends Model
|
class Template extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, HasSlug;
|
use HasFactory;
|
||||||
|
use HasSlug;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'creator_id',
|
'creator_id',
|
||||||
|
@ -25,7 +26,7 @@ class Template extends Model
|
||||||
'publicly_listed',
|
'publicly_listed',
|
||||||
'industries',
|
'industries',
|
||||||
'types',
|
'types',
|
||||||
'related_templates'
|
'related_templates',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
@ -93,7 +94,8 @@ class Template extends Model
|
||||||
array_values(
|
array_values(
|
||||||
json_decode(
|
json_decode(
|
||||||
file_get_contents(resource_path('data/forms/templates/types.json')),
|
file_get_contents(resource_path('data/forms/templates/types.json')),
|
||||||
true)
|
true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)->values();
|
)->values();
|
||||||
}
|
}
|
||||||
|
@ -104,7 +106,8 @@ class Template extends Model
|
||||||
array_values(
|
array_values(
|
||||||
json_decode(
|
json_decode(
|
||||||
file_get_contents(resource_path('data/forms/templates/industries.json')),
|
file_get_contents(resource_path('data/forms/templates/industries.json')),
|
||||||
true)
|
true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)->values();
|
)->values();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,6 @@ interface CachableAttributes
|
||||||
/**
|
/**
|
||||||
* Get an item from the cache, or execute the given Closure and store the result.
|
* 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
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
@ -20,8 +17,6 @@ interface CachableAttributes
|
||||||
/**
|
/**
|
||||||
* Get an item from the cache, or execute the given Closure and store the result forever.
|
* Get an item from the cache, or execute the given Closure and store the result forever.
|
||||||
*
|
*
|
||||||
* @param string $key
|
|
||||||
* @param \Closure $callback
|
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
@ -29,17 +24,11 @@ interface CachableAttributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an item from the cache.
|
* Remove an item from the cache.
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function forget(string $key): bool;
|
public function forget(string $key): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all items from the cache.
|
* Remove all items from the cache.
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function flush(): bool;
|
public function flush(): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ trait CachesAttributes
|
||||||
$this->getTable(),
|
$this->getTable(),
|
||||||
$this->getKey(),
|
$this->getKey(),
|
||||||
$attribute,
|
$attribute,
|
||||||
$this->updated_at?->timestamp ?? '0'
|
$this->updated_at?->timestamp ?? '0',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,15 @@ use App\Notifications\ResetPassword;
|
||||||
use App\Notifications\VerifyEmail;
|
use App\Notifications\VerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Cashier\Billable;
|
use Laravel\Cashier\Billable;
|
||||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||||
|
|
||||||
class User extends Authenticatable implements JWTSubject
|
class User extends Authenticatable implements JWTSubject
|
||||||
{
|
{
|
||||||
use Notifiable, HasFactory, Billable;
|
use Billable;
|
||||||
|
use HasFactory;
|
||||||
|
use Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
@ -25,7 +26,7 @@ class User extends Authenticatable implements JWTSubject
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
'hear_about_us'
|
'hear_about_us',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +37,7 @@ class User extends Authenticatable implements JWTSubject
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
'hear_about_us'
|
'hear_about_us',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,12 +60,12 @@ class User extends Authenticatable implements JWTSubject
|
||||||
|
|
||||||
public function ownsForm(Form $form)
|
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)
|
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()
|
return $this->subscribed()
|
||||||
|| in_array($this->email, config('opnform.extra_pro_users_emails'))
|
|| in_array($this->email, config('opnform.extra_pro_users_emails'))
|
||||||
|| !is_null($this->activeLicense());
|
|| ! is_null($this->activeLicense());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHasCustomerIdAttribute()
|
public function getHasCustomerIdAttribute()
|
||||||
{
|
{
|
||||||
return !is_null($this->stripe_id);
|
return ! is_null($this->stripe_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAdminAttribute()
|
public function getAdminAttribute()
|
||||||
|
@ -121,7 +122,7 @@ class User extends Authenticatable implements JWTSubject
|
||||||
/**
|
/**
|
||||||
* Send the password reset notification.
|
* Send the password reset notification.
|
||||||
*
|
*
|
||||||
* @param string $token
|
* @param string $token
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function sendPasswordResetNotification($token)
|
public function sendPasswordResetNotification($token)
|
||||||
|
@ -136,7 +137,7 @@ class User extends Authenticatable implements JWTSubject
|
||||||
*/
|
*/
|
||||||
public function sendEmailVerificationNotification()
|
public function sendEmailVerificationNotification()
|
||||||
{
|
{
|
||||||
$this->notify(new VerifyEmail);
|
$this->notify(new VerifyEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,7 +145,6 @@ class User extends Authenticatable implements JWTSubject
|
||||||
* Relationship
|
* Relationship
|
||||||
* =================================
|
* =================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function workspaces()
|
public function workspaces()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Workspace::class);
|
return $this->belongsToMany(Workspace::class);
|
||||||
|
@ -246,5 +246,4 @@ class User extends Authenticatable implements JWTSubject
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class Workspace extends Model implements CachableAttributes
|
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 = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'icon',
|
'icon',
|
||||||
|
@ -24,7 +28,7 @@ class Workspace extends Model implements CachableAttributes
|
||||||
|
|
||||||
protected $appends = [
|
protected $appends = [
|
||||||
'is_pro',
|
'is_pro',
|
||||||
'is_enterprise'
|
'is_enterprise',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
@ -37,16 +41,16 @@ class Workspace extends Model implements CachableAttributes
|
||||||
'is_risky',
|
'is_risky',
|
||||||
'submissions_count',
|
'submissions_count',
|
||||||
'max_file_size',
|
'max_file_size',
|
||||||
'custom_domain_count'
|
'custom_domain_count',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getMaxFileSizeAttribute()
|
public function getMaxFileSizeAttribute()
|
||||||
{
|
{
|
||||||
if(is_null(config('cashier.key'))){
|
if (is_null(config('cashier.key'))) {
|
||||||
return self::MAX_FILE_SIZE_PRO;
|
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
|
// Return max file size depending on subscription
|
||||||
foreach ($this->owners as $owner) {
|
foreach ($this->owners as $owner) {
|
||||||
if ($owner->is_subscribed) {
|
if ($owner->is_subscribed) {
|
||||||
|
@ -55,6 +59,7 @@ class Workspace extends Model implements CachableAttributes
|
||||||
return $license->max_file_size;
|
return $license->max_file_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::MAX_FILE_SIZE_PRO;
|
return self::MAX_FILE_SIZE_PRO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,17 +69,18 @@ class Workspace extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getCustomDomainCountLimitAttribute()
|
public function getCustomDomainCountLimitAttribute()
|
||||||
{
|
{
|
||||||
if(is_null(config('cashier.key'))){
|
if (is_null(config('cashier.key'))) {
|
||||||
return null;
|
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) {
|
foreach ($this->owners as $owner) {
|
||||||
if ($owner->is_subscribed) {
|
if ($owner->is_subscribed) {
|
||||||
if ($license = $owner->activeLicense()) {
|
if ($license = $owner->activeLicense()) {
|
||||||
// In case of special License
|
// In case of special License
|
||||||
return $license->custom_domain_limit_count;
|
return $license->custom_domain_limit_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::MAX_DOMAIN_PRO;
|
return self::MAX_DOMAIN_PRO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,44 +91,46 @@ class Workspace extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getIsProAttribute()
|
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 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
|
// Make sure at least one owner is pro
|
||||||
foreach ($this->owners as $owner) {
|
foreach ($this->owners as $owner) {
|
||||||
if ($owner->is_subscribed) {
|
if ($owner->is_subscribed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsEnterpriseAttribute()
|
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 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
|
// Make sure at least one owner is pro
|
||||||
foreach ($this->owners as $owner) {
|
foreach ($this->owners as $owner) {
|
||||||
if ($owner->has_enterprise_subscription) {
|
if ($owner->has_enterprise_subscription) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsRiskyAttribute()
|
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
|
// A workspace is risky if all of his users are risky
|
||||||
foreach ($this->owners as $owner) {
|
foreach ($this->owners as $owner) {
|
||||||
if (!$owner->is_risky) {
|
if (! $owner->is_risky) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +141,7 @@ class Workspace extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getSubmissionsCountAttribute()
|
public function getSubmissionsCountAttribute()
|
||||||
{
|
{
|
||||||
return $this->remember('submissions_count', 15 * 60, function(): int {
|
return $this->remember('submissions_count', 15 * 60, function (): int {
|
||||||
$total = 0;
|
$total = 0;
|
||||||
foreach ($this->forms as $form) {
|
foreach ($this->forms as $form) {
|
||||||
$total += $form->submissions_count;
|
$total += $form->submissions_count;
|
||||||
|
@ -146,7 +154,6 @@ class Workspace extends Model implements CachableAttributes
|
||||||
/**
|
/**
|
||||||
* Relationships
|
* Relationships
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(User::class);
|
return $this->belongsToMany(User::class);
|
||||||
|
|
|
@ -6,7 +6,6 @@ use App\Models\Forms\Form;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
|
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
|
||||||
|
|
||||||
class FailedWebhookNotification extends Notification
|
class FailedWebhookNotification extends Notification
|
||||||
|
@ -14,6 +13,7 @@ class FailedWebhookNotification extends Notification
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public Form $form;
|
public Form $form;
|
||||||
|
|
||||||
public string $provider;
|
public string $provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +30,7 @@ class FailedWebhookNotification extends Notification
|
||||||
/**
|
/**
|
||||||
* Get the notification's delivery channels.
|
* Get the notification's delivery channels.
|
||||||
*
|
*
|
||||||
* @param mixed $notifiable
|
* @param mixed $notifiable
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function via($notifiable)
|
public function via($notifiable)
|
||||||
|
@ -41,13 +41,13 @@ class FailedWebhookNotification extends Notification
|
||||||
/**
|
/**
|
||||||
* Get the mail representation of the notification.
|
* Get the mail representation of the notification.
|
||||||
*
|
*
|
||||||
* @param mixed $notifiable
|
* @param mixed $notifiable
|
||||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||||
*/
|
*/
|
||||||
public function toMail($notifiable)
|
public function toMail($notifiable)
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage())
|
||||||
->subject("Notification issue with your NotionForm: '" . $this->form->title . "'")
|
->subject("Notification issue with your NotionForm: '".$this->form->title."'")
|
||||||
->markdown('mail.form.webhook-error', [
|
->markdown('mail.form.webhook-error', [
|
||||||
'provider' => $this->provider,
|
'provider' => $this->provider,
|
||||||
'error' => $this->event->errorMessage,
|
'error' => $this->event->errorMessage,
|
||||||
|
|
|
@ -8,8 +8,8 @@ use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class FormSubmissionNotification extends Notification implements ShouldQueue
|
class FormSubmissionNotification extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
|
||||||
->outputStringsOnly()
|
->outputStringsOnly()
|
||||||
->useSignedUrlForFiles();
|
->useSignedUrlForFiles();
|
||||||
|
|
||||||
return (new MailMessage)
|
return (new MailMessage())
|
||||||
->replyTo($this->getReplyToEmail($notifiable->routes['mail']))
|
->replyTo($this->getReplyToEmail($notifiable->routes['mail']))
|
||||||
->from($this->getFromEmail(), config('app.name'))
|
->from($this->getFromEmail(), config('app.name'))
|
||||||
->subject('New form submission for "'.$this->event->form->title.'"')
|
->subject('New form submission for "'.$this->event->form->title.'"')
|
||||||
|
@ -65,15 +65,17 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
|
||||||
private function getFromEmail()
|
private function getFromEmail()
|
||||||
{
|
{
|
||||||
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
|
$originalFromAddress = Str::of(config('mail.from.address'))->explode('@');
|
||||||
return $originalFromAddress->first(). '+' . time() . '@' . $originalFromAddress->last();
|
|
||||||
|
return $originalFromAddress->first().'+'.time().'@'.$originalFromAddress->last();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getReplyToEmail($default)
|
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)) {
|
if ($replyTo && $this->validateEmail($replyTo)) {
|
||||||
return $replyTo;
|
return $replyTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getRespondentEmail() ?? $default;
|
return $this->getRespondentEmail() ?? $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
|
||||||
$emailFields = collect($this->event->form->properties)->filter(function ($field) {
|
$emailFields = collect($this->event->form->properties)->filter(function ($field) {
|
||||||
$hidden = $field['hidden'] ?? false;
|
$hidden = $field['hidden'] ?? false;
|
||||||
|
|
||||||
return !$hidden && $field['type'] == 'email';
|
return ! $hidden && $field['type'] == 'email';
|
||||||
});
|
});
|
||||||
if ($emailFields->count() != 1) {
|
if ($emailFields->count() != 1) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -101,7 +103,6 @@ class FormSubmissionNotification extends Notification implements ShouldQueue
|
||||||
|
|
||||||
public static function validateEmail($email): bool
|
public static function validateEmail($email): bool
|
||||||
{
|
{
|
||||||
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
|
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ class ResetPassword extends Notification
|
||||||
*/
|
*/
|
||||||
public function toMail($notifiable)
|
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.')
|
->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))
|
->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.');
|
->line('If you did not request a password reset, no further action is required.');
|
||||||
|
|
|
@ -30,7 +30,7 @@ class FailedPaymentNotification extends Notification implements ShouldQueue
|
||||||
*/
|
*/
|
||||||
public function toMail($notifiable)
|
public function toMail($notifiable)
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage())
|
||||||
->subject('Your Payment Failed')
|
->subject('Your Payment Failed')
|
||||||
->greeting(__('We tried to charge your card for your OpenForm subscription but the payment but did not work.'))
|
->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".
|
->line(__('Please go to OpenForm, click on your name on the top right corner, and click on "Billing".
|
||||||
|
|
|
@ -17,7 +17,9 @@ class VerifyEmail extends Notification
|
||||||
protected function verificationUrl($notifiable)
|
protected function verificationUrl($notifiable)
|
||||||
{
|
{
|
||||||
$url = URL::temporarySignedRoute(
|
$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);
|
return str_replace('/api', '', $url);
|
||||||
|
|
|
@ -13,7 +13,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can view any models.
|
* Determine whether the user can view any models.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function viewAny(User $user)
|
public function viewAny(User $user)
|
||||||
|
@ -24,8 +23,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can view the model.
|
* Determine whether the user can view the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Forms\Form $form
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function view(User $user, Form $form)
|
public function view(User $user, Form $form)
|
||||||
|
@ -36,7 +33,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can create models.
|
* Determine whether the user can create models.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function create(User $user)
|
public function create(User $user)
|
||||||
|
@ -47,8 +43,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can update the model.
|
* Determine whether the user can update the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Forms\Form $form
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function update(User $user, Form $form)
|
public function update(User $user, Form $form)
|
||||||
|
@ -59,8 +53,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Forms\Form $form
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function delete(User $user, Form $form)
|
public function delete(User $user, Form $form)
|
||||||
|
@ -71,8 +63,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can restore the model.
|
* Determine whether the user can restore the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Forms\Form $form
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function restore(User $user, Form $form)
|
public function restore(User $user, Form $form)
|
||||||
|
@ -83,8 +73,6 @@ class FormPolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can permanently delete the model.
|
* Determine whether the user can permanently delete the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Forms\Form $form
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function forceDelete(User $user, Form $form)
|
public function forceDelete(User $user, Form $form)
|
||||||
|
|
|
@ -13,7 +13,6 @@ class TemplatePolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can create models.
|
* Determine whether the user can create models.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function create(User $user)
|
public function create(User $user)
|
||||||
|
@ -24,8 +23,6 @@ class TemplatePolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can update the model.
|
* Determine whether the user can update the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Template $template
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function update(User $user, Template $template)
|
public function update(User $user, Template $template)
|
||||||
|
@ -36,8 +33,6 @@ class TemplatePolicy
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $user
|
|
||||||
* @param \App\Models\Template $template
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function delete(User $user, Template $template)
|
public function delete(User $user, Template $template)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue