Skip to content

Webhooks #11

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

Merged
merged 1 commit into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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