Skip to content

Commit

Permalink
Merge pull request #11 from phpform-dev/webhooks
Browse files Browse the repository at this point in the history
Webhooks
  • Loading branch information
sdkfuyr committed Mar 17, 2024
2 parents 3e827f5 + 0f7794a commit f8d29f0
Show file tree
Hide file tree
Showing 17 changed files with 597 additions and 54 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
0.3.0
==
17 March 2024

**Added:**
* Webhooks support

**Fixed:**
* Export to Excel/CSV

0.2.3
==
27 Frb 2024
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# PHPForm - Lightweight Headless Form Builder and API
# PHPForm - Open Source Headless Form Management Server

Welcome to PHPForm, a fully open-source, headless form builder designed with simplicity, efficiency, and privacy in mind.
Built to be easily installed on any budget-friendly hosting solution or free cloud tiers, PHPForm is the perfect choice for
developers and businesses looking for a reliable and GDPR-compliant form management solution.

<img src="./public/images/screen.png" width="100%">
<img src="./public/images/screen.png" width="100%" alt="Screenshot">

# Features

Expand Down
16 changes: 15 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
{
"type": "project",
"license": "proprietary",
"name": "phpform-dev/phpform-server",
"description": "PHPForm - Open Source Headless Form Management Server",
"keywords": [
"phpform",
"form",
"server",
"headless",
"api",
"symfony",
"php"
],
"homepage": "http://phpform.dev",
"license": "MIT",
"version": "0.3.0",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
Expand All @@ -13,6 +26,7 @@
"doctrine/doctrine-bundle": "^2.11",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^2.17",
"guzzlehttp/guzzle": "^7.0",
"minishlink/web-push": "^8.0",
"phpoffice/phpspreadsheet": "^1.29",
"symfony/console": "7.0.*",
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions src/Admin/Controller/FormWebhooksController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
namespace App\Admin\Controller;

use App\Admin\Form\FormWebhookType;
use App\Entity\Form;
use App\Entity\FormWebhook;
use App\Service\FormMenuCounterService;
use App\Service\FormWebhookService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class FormWebhooksController extends AbstractController
{
public function __construct(
private readonly FormMenuCounterService $formMenuCounterService,
private readonly FormWebhookService $formWebhookService,
)
{
}

#[Route('/admin/forms/{id}/webhooks', name: 'admin_forms_webhooks', methods: ['GET', 'POST'])]
public function index(Request $request, Form $formEntity): Response
{
$form = $this->createForm(FormWebhookType::class, new FormWebhook());
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$webhook = $form->getData();
$webhook->setForm($formEntity);
$this->formWebhookService->save($webhook);

$this->addFlash('primary', 'Webhook added successfully.');

return $this->redirectToRoute('admin_forms_webhooks', ['id' => $formEntity->getId()]);
}

$webhooks = $this->formWebhookService->getAllByFormId($formEntity->getId());

return $this->render('@Admin/form-webhooks/index.html.twig', [
'formEntity' => $formEntity,
'form' => $form->createView(),
'menuCounts' => $this->formMenuCounterService->getAllCountsByFormId($formEntity->getId()),
'webhooks' => $webhooks,
]);
}

#[Route('/admin/forms/{formId}/webhooks/{webhookId}/delete', name: 'admin_forms_webhook_delete', methods: ['GET'])]
public function delete(int $formId, int $webhookId): Response
{
$webhook = $this->formWebhookService->getOneByIdAndFormId($webhookId, $formId);
if (!$webhook) {
throw $this->createNotFoundException('Webhook not found.');
}

$this->formWebhookService->delete($webhook);

$this->addFlash('primary', 'Webhook deleted successfully.');

return $this->redirectToRoute('admin_forms_webhooks', ['id' => $formId]);
}
}
31 changes: 31 additions & 0 deletions src/Admin/Form/FormWebhookHeaderType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
namespace App\Admin\Form;

use App\Entity\FormWebhookHeader;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class FormWebhookHeaderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Header Name',
'attr' => ['placeholder' => 'Content-Type'],
])
->add('value', TextType::class, [
'label' => 'Header Value',
'attr' => ['placeholder' => 'application/json'],
]);
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => FormWebhookHeader::class,
]);
}
}
41 changes: 41 additions & 0 deletions src/Admin/Form/FormWebhookType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
namespace App\Admin\Form;

use App\Entity\FormWebhookHeader;
use App\Entity\FormWebhook;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class FormWebhookType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('url', UrlType::class, [
'label' => 'Webhook URL',
'attr' => ['placeholder' => 'https://example.com/webhook'],
])
->add('headers', CollectionType::class, [
'entry_type' => FormWebhookHeaderType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false,
'prototype' => true,
'attr' => [
'class' => 'header-collection',
],
]);
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => FormWebhook::class,
]);
}
}
99 changes: 99 additions & 0 deletions src/Admin/Resources/templates/form-webhooks/index.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{% extends '@Admin/forms/page.html.twig' %}

{% block title %}Webhooks / {{ formEntity.name }} / Forms{% endblock %}

{% block section %}
{% if webhooks|length > 0 %}
<div>
{% for webhook in webhooks %}
<div class="box">
<h6 class="has-text-weight-bold">URL:</h6>
<div>
{{ webhook.url }}
</div>
{% if webhook.headers|length > 0 %}
<div class="mt-2">
<h6 class="has-text-weight-bold mb-2">Headers:</h6>
<div>
{% for header in webhook.headers %}
<div class="mb-2">
<span class="tag is-light">
<strong>{{ header.name }}:</strong> {{ header.value }}
</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="mt-3">
<a class="tag is-rounded py-2 has-background-danger-light hoverable" href="{{ path('admin_forms_webhook_delete', { formId: formEntity.id, webhookId: webhook.id }) }}" title="Delete">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width:16px; height: 16px;"><title/><path d="M21,6a1,1,0,0,1-1,1H4A1,1,0,0,1,4,5H9V4.5A1.5,1.5,0,0,1,10.5,3h3A1.5,1.5,0,0,1,15,4.5V5h5A1,1,0,0,1,21,6Z" fill="#464646"/><path d="M5.5,9v9.5A2.5,2.5,0,0,0,8,21h8a2.5,2.5,0,0,0,2.5-2.5V9ZM11,17a1,1,0,0,1-2,0V13a1,1,0,0,1,2,0Zm4,0a1,1,0,0,1-2,0V13a1,1,0,0,1,2,0Z" fill="#464646"/></svg>
</a>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<div class="card mt-4" x-data="webhookForm()">
<header class="card-header">
<p class="card-header-title">
Add new Webhook
</p>
</header>
<div class="card-content">
<div class="content">
{{ form_start(form, {'attr': {'class': 'form'}}) }}
<div class="field">
{{ form_label(form.url, null, {'label_attr': {'class': 'label'}}) }}
<div class="control">
{{ form_widget(form.url, {'attr': {'class': 'input', 'placeholder': 'Webhook URL'}}) }}
</div>
{{ form_errors(form.url, {'attr': {'class': 'help is-danger'}}) }}
</div>

<div class="is-flex is-align-items-center is-justify-content-space-between mb-2">
<h5>Headers</h5>
<button type="button" class="button is-primary is-inverted" @click="addHeader()">Add Header</button>
</div>

<template x-for="(header, index) in headers" :key="index">
<div class="mb-2 is-flex is-align-items-center">
<div class="mr-3">
<input type="text" :name="'form_webhook[headers][' + index + '][name]'" x-model="header.name" class="input" placeholder="Header Name" required minlength="1" maxlength="100">
</div>
<div class="mr-3">
<input type="text" :name="'form_webhook[headers][' + index + '][value]'" x-model="header.value" class="input" placeholder="Header Value" required minlength="1" maxlength="255">
</div>
<div>
<button type="button" class="button is-danger is-inverted" @click="removeHeader(index)">Remove</button>
</div>
</div>
</template>

<div class="field is-grouped mt-4">
<div class="control">
<button type="submit" class="button is-primary">Add Webhook</button>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>

<script>
function webhookForm() {
return {
headers: [{
name: '',
value: ''
}],
addHeader() {
this.headers.push({ name: '', value: '' });
},
removeHeader(index) {
this.headers.splice(index, 1);
}
};
}
</script>
{% endblock %}
42 changes: 27 additions & 15 deletions src/Admin/Resources/templates/forms/page.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@
Settings
</p>
<ul class="menu-list">
{% if app.user.isSuperUser %}
<li>
<a
{% if current_path == 'admin_forms_edit' %}
class="is-active"
{% endif %}
href="{{ path('admin_forms_edit', {'id': formEntity.id}) }}"
>
General
</a>
</li>
<li>
<a
{% if current_path starts with 'admin_forms_fields' %}
class="is-active"
{% endif %}
href="{{ path('admin_forms_fields', {'id': formEntity.id}) }}"
>
Fields ({{ menuCounts.fields }})
</a>
</li>
{% endif %}
<li>
<a
{% if current_path == 'admin_forms_info' %}
Expand All @@ -70,16 +92,6 @@
</a>
</li>
{% if app.user.isSuperUser %}
<li>
<a
{% if current_path == 'admin_forms_edit' %}
class="is-active"
{% endif %}
href="{{ path('admin_forms_edit', {'id': formEntity.id}) }}"
>
Edit
</a>
</li>
<li>
<a
{% if current_path == 'admin_forms_captcha' %}
Expand All @@ -102,12 +114,12 @@
</li>
<li>
<a
{% if current_path starts with 'admin_forms_fields' %}
class="is-active"
{% endif %}
href="{{ path('admin_forms_fields', {'id': formEntity.id}) }}"
{% if current_path == 'admin_forms_webhooks' %}
class="is-active"
{% endif %}
href="{{ path('admin_forms_webhooks', {'id': formEntity.id}) }}"
>
Fields ({{ menuCounts.fields }})
Webhooks ({{ menuCounts.webhooks }})
</a>
</li>
<li>
Expand Down
Loading

0 comments on commit f8d29f0

Please sign in to comment.