Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom SEO #154

Merged
merged 15 commits into from
Aug 30, 2023
3 changes: 3 additions & 0 deletions app/Http/Requests/UserFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ public function rules()
// Security & Privacy
'can_be_indexed' => 'boolean',
'password' => 'sometimes|nullable',

// Custom SEO
'seo_meta' => 'nullable|array'
];
}

Expand Down
3 changes: 2 additions & 1 deletion app/Http/Resources/FormResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public function toArray($request)
'slack_webhook_url' => $this->slack_webhook_url,
'discord_webhook_url' => $this->discord_webhook_url,
'removed_properties' => $this->removed_properties,
'last_edited_human' => $this->updated_at?->diffForHumans()
'last_edited_human' => $this->updated_at?->diffForHumans(),
'seo_meta' => $this->seo_meta
] : [];

$baseData = $this->getFilteredFormData(parent::toArray($request), $this->userIsFormOwner());
Expand Down
8 changes: 6 additions & 2 deletions app/Models/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,19 @@ class Form extends Model

// Security & Privacy
'can_be_indexed',
'password'
'password',

// Custom SEO
'seo_meta'
];

protected $casts = [
'properties' => 'array',
'database_fields_update' => 'array',
'closes_at' => 'datetime',
'tags' => 'array',
'removed_properties' => 'array'
'removed_properties' => 'array',
'seo_meta' => 'object'
];

protected $appends = [
Expand Down
24 changes: 24 additions & 0 deletions app/Service/Forms/FormCleaner.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class FormCleaner

private array $data;

// For remove keys those have empty value
private array $customKeys = ['seo_meta'];

private array $formDefaults = [
'notifies' => false,
'no_branding' => false,
Expand All @@ -32,6 +35,7 @@ class FormCleaner
'discord_webhook_url' => null,
'editable_submissions' => false,
'custom_code' => null,
'seo_meta' => []
];

private array $fieldDefaults = [
Expand All @@ -49,6 +53,7 @@ class FormCleaner
'discord_webhook_url' => "Discord webhook disabled.",
'editable_submissions' => 'Users will not be able to edit their submissions.',
'custom_code' => 'Custom code was disabled',
'seo_meta' => 'Custom code was disabled',

// For fields
'file_upload' => "Link field is not a file upload.",
Expand Down Expand Up @@ -202,6 +207,9 @@ private function clean(array &$data, array $defaults, $simulation = false): void
// Get value from form
$formVal = Arr::get($data, $key);

// Transform customkeys values
$formVal = $this->cleanCustomKeys($key, $formVal);

// Transform boolean values
$formVal = (($formVal === 0 || $formVal === "0") ? false : $formVal);
$formVal = (($formVal === 1 || $formVal === "1") ? true : $formVal);
Expand Down Expand Up @@ -242,4 +250,20 @@ private function cleanField(array &$data, array $defaults, $simulation = false):
}*/
}

// Remove keys those have empty value
private function cleanCustomKeys($key, $formVal)
{
if (in_array($key, $this->customKeys) && $formVal !== null) {
$newVal = [];
foreach ($formVal as $k => $val) {
if ($val) {
$newVal[$k] = $val;
}
}
return $newVal;
}

return $formVal;
}

}
20 changes: 15 additions & 5 deletions app/Service/SeoMetaResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,25 @@ private function getFormShowMeta(): array
{
$form = Form::whereSlug($this->patternData['slug'])->firstOrFail();

$meta = [
'title' => $form->title . $this->titleSuffix(),
];
if($form->description){
$meta = [];
if ($form->is_pro && $form->seo_meta->page_title) {
$meta['title'] = $form->seo_meta->page_title;
} else {
$meta['title'] = $form->title . $this->titleSuffix();
}

if ($form->is_pro && $form->seo_meta->page_description) {
$meta['description'] = $form->seo_meta->page_description;
} else if ($form->description) {
$meta['description'] = Str::of($form->description)->limit(160);
}
if($form->cover_picture){

if ($form->is_pro && $form->seo_meta->page_thumbnail) {
$meta['image'] = $form->seo_meta->page_thumbnail;
} else if ($form->cover_picture) {
$meta['image'] = $form->cover_picture;
}

return $meta;
}

Expand Down
3 changes: 2 additions & 1 deletion database/factories/FormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ public function definition()
'tags' => [],
'slack_webhook_url' => null,
'editable_submissions_button_text' => 'Edit submission',
'confetti_on_submission' => false
'confetti_on_submission' => false,
'seo_meta' => [],
];
}

Expand Down
32 changes: 32 additions & 0 deletions database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('forms', function (Blueprint $table) {
$table->json('seo_meta')->default('{}');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('forms', function (Blueprint $table) {
$table->dropColumn('seo_meta');
});
}
};
2 changes: 1 addition & 1 deletion resources/js/components/open/forms/OpenForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export default {

const formData = clonedeep(this.dataForm ? this.dataForm.data() : {})
let urlPrefill = null
if (this.isPublicFormPage && this.form.is_pro) {
if (this.isPublicFormPage) {
urlPrefill = new URLSearchParams(window.location.search)
}

Expand Down
5 changes: 4 additions & 1 deletion resources/js/components/open/forms/components/FormEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<form-about-submission/>
<form-notifications/>
<form-security-privacy/>
<form-custom-seo />
<form-custom-code/>
<form-integrations/>
</div>
Expand Down Expand Up @@ -66,6 +67,7 @@ import FormNotifications from './form-components/FormNotifications.vue'
import FormIntegrations from './form-components/FormIntegrations.vue'
import FormEditorPreview from './form-components/FormEditorPreview.vue'
import FormSecurityPrivacy from './form-components/FormSecurityPrivacy.vue'
import FormCustomSeo from './form-components/FormCustomSeo.vue'
import saveUpdateAlert from '../../../../mixins/forms/saveUpdateAlert.js'

export default {
Expand All @@ -80,7 +82,8 @@ export default {
FormStructure,
FormInformation,
FormErrorModal,
FormSecurityPrivacy
FormSecurityPrivacy,
FormCustomSeo
},
mixins: [saveUpdateAlert],
props: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
<collapse class="p-5 w-full border-b" :default-value="false">
<template #title>
<h3 id="v-step-2" class="font-semibold text-lg">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="h-5 w-5 inline text-gray-500 mr-2 -mt-1"
>
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>

Link Settings - SEO
<pro-tag />
</h3>
</template>
<p class="mt-4 text-gray-500 text-sm">
Customize the image and text that appear when you share your form on other sites (Open Graph).
</p>
<text-input v-model="form.seo_meta.page_title" name="page_title" class="mt-4"
label="Page Title" help="Under or approximately 60 characters"
/>
<text-area-input v-model="form.seo_meta.page_description" name="page_description" class="mt-4"
label="Page Description" help="Between 150 and 160 characters"
/>
<image-input v-model="form.seo_meta.page_thumbnail" name="page_thumbnail" class="mt-4"
label="Page Thumbnail Image" help="Also know as og:image - 1200 X 630"
/>
</collapse>
</template>

<script>
import Collapse from '../../../../common/Collapse.vue'
import ProTag from '../../../../common/ProTag.vue'

export default {
components: { Collapse, ProTag },
props: {},
data () {
return {}
},
computed: {
form: {
get () {
return this.$store.state['open/working_form'].content
},
/* We add a setter */
set (value) {
this.$store.commit('open/working_form/set', value)
}
}
},
watch: {},
mounted () {
['page_title', 'page_description', 'page_thumbnail'].forEach((keyname) => {
if (this.form.seo_meta[keyname] === undefined) {
this.form.seo_meta[keyname] = null
}
})
},
methods: {}
}
</script>
5 changes: 4 additions & 1 deletion resources/js/mixins/form_editor/initForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export default {
confetti_on_submission: false,

// Security & Privacy
can_be_indexed: true
can_be_indexed: true,

// Custom SEO
seo_meta: {}
})
},
}
Expand Down
3 changes: 2 additions & 1 deletion resources/js/mixins/seo-meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ export default {
const title = this.metaTitle ?? 'OpnForm'
const description = this.metaDescription ?? "Create beautiful forms for free. Unlimited fields, unlimited submissions. It's free and it takes less than 1 minute to create your first form."
const image = this.metaImage ?? this.asset('img/social-preview.jpg')
const metaTemplate = this.metaTemplate ?? '%s · OpnForm'

return {
title: title,
titleTemplate: '%s · OpnForm',
titleTemplate: metaTemplate,
meta: [
...(this.metaTags ?? []),
{ vmid: 'og:title', property: 'og:title', content: title },
Expand Down
16 changes: 16 additions & 0 deletions resources/js/pages/forms/show-public.vue
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,28 @@ export default {
return window.location !== window.parent.location || window.frameElement
},
metaTitle () {
if(this.form && this.form.is_pro && this.form.seo_meta.page_title) {
return this.form.seo_meta.page_title
}
return this.form ? this.form.title : 'Create beautiful forms'
},
metaTemplate () {
if (this.form && this.form.is_pro && this.form.seo_meta.page_title) {
// Disable template if custom SEO title
return '%s'
}
return null
},
metaDescription () {
if (this.form && this.form.is_pro && this.form.seo_meta.page_description) {
return this.form.seo_meta.page_description
}
return (this.form && this.form.description) ? this.form.description.substring(0, 160) : null
},
metaImage () {
if (this.form && this.form.is_pro && this.form.seo_meta.page_thumbnail) {
return this.form.seo_meta.page_thumbnail
}
return (this.form && this.form.cover_picture) ? this.form.cover_picture : null
},
metaTags () {
Expand Down
Loading