First Commit
This commit is contained in:
+321
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Contact\Controllers;
|
||||
|
||||
use App\Controllers\BaseAdminController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Modules\Contact\Models\Contact;
|
||||
|
||||
class ContactController extends BaseAdminController
|
||||
{
|
||||
/**
|
||||
* Display contact form for public
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$requireCaptcha = $this->getSetting('require_captcha_contact', true);
|
||||
|
||||
$pageContent = null;
|
||||
$metaTitle = null;
|
||||
$metaDescription = null;
|
||||
$title = 'Contact Us';
|
||||
|
||||
if (class_exists('Modules\Configindex\Models\ConfigPage')) {
|
||||
try {
|
||||
$page = \Modules\Configindex\Models\ConfigPage::getByKey('contact', 1);
|
||||
if ($page) {
|
||||
$pageContent = $page->config_index_content;
|
||||
if ($page->config_index_title) {
|
||||
$title = $page->config_index_title;
|
||||
}
|
||||
$metaTitle = $page->config_index_meta_title;
|
||||
$metaDescription = $page->config_index_meta_description;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Ignore if table not found or other error
|
||||
}
|
||||
}
|
||||
|
||||
return new Response($this->view->render('contact/views/index.twig', [
|
||||
'title' => $title,
|
||||
'page_content' => $pageContent,
|
||||
'meta_title' => $metaTitle,
|
||||
'meta_description' => $metaDescription,
|
||||
'require_captcha_contact' => $requireCaptcha
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process contact form submission
|
||||
*/
|
||||
public function submit(Request $request): Response
|
||||
{
|
||||
// Handle JSON request body
|
||||
if ($request->getContentType() === 'json' || 0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
} else {
|
||||
$data = $request->request->all();
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if (empty($data['name']) || empty($data['email']) || empty($data['subject']) || empty($data['message'])) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Please fill in all required fields.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('/contact?error=missing_fields'));
|
||||
}
|
||||
|
||||
// Email format validation
|
||||
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Please provide a valid email address.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('/contact?error=invalid_email'));
|
||||
}
|
||||
|
||||
// Captcha validation
|
||||
$requireCaptcha = $this->getSetting('require_captcha_contact', true);
|
||||
if ($requireCaptcha && empty($data['captcha_valid'])) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Invalid captcha. Please try again.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('/contact?error=invalid_captcha'));
|
||||
}
|
||||
|
||||
// Create contact record
|
||||
$contact = new Contact();
|
||||
$contact->contacts_name = $data['name'];
|
||||
$contact->contacts_email = $data['email'];
|
||||
$contact->contacts_phone = $data['phone'] ?? '';
|
||||
$contact->contacts_subject = apply_filters('contact.subject_sanitize', $data['subject']);
|
||||
$contact->contacts_message = apply_filters('contact.message_sanitize', $data['message']);
|
||||
$contact->contacts_status = 'new';
|
||||
$contact->contacts_ip_address = $request->getClientIp();
|
||||
$contact->contacts_user_agent = $request->headers->get('User-Agent');
|
||||
$contact->save();
|
||||
|
||||
do_action('contact.submitted', $contact);
|
||||
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Your message has been sent successfully. We will get back to you soon!'
|
||||
]);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('/contact?success=true'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display list of contacts for admin
|
||||
*/
|
||||
public function list(Request $request): Response
|
||||
{
|
||||
$status = $request->query->get('status', 'all');
|
||||
$contacts = Contact::query();
|
||||
|
||||
if ($status === 'unread') {
|
||||
$contacts = $contacts->unread();
|
||||
} elseif ($status === 'read') {
|
||||
$contacts = $contacts->read();
|
||||
} elseif ($status !== 'all') {
|
||||
$contacts = $contacts->status($status);
|
||||
}
|
||||
|
||||
$contacts = $contacts->orderBy('created_at', 'desc')->get();
|
||||
|
||||
return $this->renderAdminView('contact/views/admin/index.twig', [
|
||||
'contacts' => $contacts,
|
||||
'current_status' => $status,
|
||||
'title' => 'Contact Messages Management'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display contact details
|
||||
*/
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$contact = Contact::find($id);
|
||||
|
||||
if (!$contact) {
|
||||
return new RedirectResponse(url('admin/contacts?error=not_found'));
|
||||
}
|
||||
|
||||
// Mark as read if it's unread
|
||||
if (!$contact->isRead()) {
|
||||
$contact->markAsRead();
|
||||
}
|
||||
|
||||
return $this->renderAdminView('contact/views/admin/show.twig', [
|
||||
'contact' => $contact,
|
||||
'title' => 'Contact Message Details'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update contact status
|
||||
*/
|
||||
public function updateStatus(Request $request, int $id): Response
|
||||
{
|
||||
$contact = Contact::find($id);
|
||||
|
||||
if (!$contact) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Contact not found.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts?error=not_found'));
|
||||
}
|
||||
|
||||
$status = $request->request->get('status');
|
||||
$validStatuses = ['new', 'in_progress', 'resolved', 'closed'];
|
||||
|
||||
if (!in_array($status, $validStatuses)) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Invalid status.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts?error=invalid_status'));
|
||||
}
|
||||
|
||||
$contact->contacts_status = $status;
|
||||
$contact->save();
|
||||
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Contact status updated successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts/' . $id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update contact response
|
||||
*/
|
||||
public function updateResponse(Request $request, int $id): Response
|
||||
{
|
||||
$contact = Contact::find($id);
|
||||
|
||||
if (!$contact) {
|
||||
return new RedirectResponse(url('admin/contacts?error=not_found'));
|
||||
}
|
||||
|
||||
$response = $request->request->get('response');
|
||||
$contact->contacts_response = $response;
|
||||
|
||||
// Auto update status if it is 'new'
|
||||
if ($contact->contacts_status === 'new' && !empty($response)) {
|
||||
$contact->contacts_status = 'resolved';
|
||||
}
|
||||
|
||||
$contact->save();
|
||||
|
||||
return new RedirectResponse(url('admin/contacts/' . $id . '?success=response_saved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete contact
|
||||
*/
|
||||
public function delete(Request $request, int $id): Response
|
||||
{
|
||||
$contact = Contact::find($id);
|
||||
|
||||
if (!$contact) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Contact not found.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts?error=not_found'));
|
||||
}
|
||||
|
||||
$contact->delete();
|
||||
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Contact deleted successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark contact as read/unread
|
||||
*/
|
||||
public function toggleRead(Request $request, int $id): Response
|
||||
{
|
||||
$contact = Contact::find($id);
|
||||
|
||||
if (!$contact) {
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Contact not found.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts?error=not_found'));
|
||||
}
|
||||
|
||||
if ($contact->isRead()) {
|
||||
$contact->markAsUnread();
|
||||
$message = 'Contact marked as unread.';
|
||||
} else {
|
||||
$contact->markAsRead();
|
||||
$message = 'Contact marked as read.';
|
||||
}
|
||||
|
||||
if ($request->headers->get('Accept') === 'application/json') {
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'is_read' => $contact->isRead()
|
||||
]);
|
||||
}
|
||||
|
||||
return new RedirectResponse(url('admin/contacts/' . $id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contact module setting
|
||||
*/
|
||||
private function getSetting(string $key, $default = null)
|
||||
{
|
||||
try {
|
||||
$module = \App\Models\Module::byName('contact')->first();
|
||||
if ($module) {
|
||||
return $module->getSetting($key, $default);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Contact\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Contact extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'contacts';
|
||||
|
||||
/**
|
||||
* The primary key associated with table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'contacts_id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'contacts_name',
|
||||
'contacts_email',
|
||||
'contacts_phone',
|
||||
'contacts_subject',
|
||||
'contacts_message',
|
||||
'contacts_response',
|
||||
'contacts_status',
|
||||
'contacts_ip_address',
|
||||
'contacts_user_agent',
|
||||
'contacts_read_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'contacts_read_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [
|
||||
'contacts_read_at',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get unread contacts only.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->whereNull('contacts_read_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get read contacts only.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeRead($query)
|
||||
{
|
||||
return $query->whereNotNull('contacts_read_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contacts by status.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeStatus($query, string $status)
|
||||
{
|
||||
return $query->where('contacts_status', $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark contact as read.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function markAsRead(): bool
|
||||
{
|
||||
if ($this->contacts_read_at) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->update(['contacts_read_at' => \Carbon\Carbon::now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark contact as unread.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function markAsUnread(): bool
|
||||
{
|
||||
return $this->update(['contacts_read_at' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if contact is read.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRead(): bool
|
||||
{
|
||||
return !is_null($this->contacts_read_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted created date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormattedCreatedAtAttribute(): string
|
||||
{
|
||||
return $this->created_at->format('d/m/Y H:i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short message (100 characters).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShortMessageAttribute(): string
|
||||
{
|
||||
return strlen($this->contacts_message) > 100
|
||||
? substr($this->contacts_message, 0, 100) . '...'
|
||||
: $this->contacts_message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Contact\Providers;
|
||||
|
||||
use Kernel\Application;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Modules\Contact\Models\Contact;
|
||||
|
||||
class ContactServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$app = Application::getInstance();
|
||||
$app->addPublicPath('/contact*');
|
||||
|
||||
if (function_exists('add_filter')) {
|
||||
add_filter('contact.subject_sanitize', function ($subject) {
|
||||
$subject = is_string($subject) ? $subject : '';
|
||||
$subject = strip_tags($subject);
|
||||
$subject = preg_replace('/\s+/', ' ', trim($subject));
|
||||
return $subject;
|
||||
}, 10);
|
||||
|
||||
add_filter('contact.message_sanitize', function ($message) {
|
||||
$message = is_string($message) ? $message : '';
|
||||
$message = strip_tags($message);
|
||||
$message = trim($message);
|
||||
return $message;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
if (function_exists('add_action')) {
|
||||
add_action('contact.submitted', function (Contact $contact) {
|
||||
$dsn = $_ENV['MAILER_DSN'] ?? '';
|
||||
if (!$dsn) {
|
||||
$transport = Transport::fromDsn(sprintf(
|
||||
'smtp://%s:%s@%s:%s?encryption=%s',
|
||||
$_ENV['MAIL_USERNAME'] ?? '',
|
||||
$_ENV['MAIL_PASSWORD'] ?? '',
|
||||
$_ENV['MAIL_HOST'] ?? 'localhost',
|
||||
$_ENV['MAIL_PORT'] ?? '25',
|
||||
$_ENV['MAIL_ENCRYPTION'] ?? ''
|
||||
));
|
||||
} else {
|
||||
$transport = Transport::fromDsn($dsn);
|
||||
}
|
||||
|
||||
$mailer = new Mailer($transport);
|
||||
$to = $_ENV['MAIL_TO_ADDRESS'] ?? $_ENV['MAIL_FROM_ADDRESS'] ?? null;
|
||||
if (!$to) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email = (new Email())
|
||||
->from($_ENV['MAIL_FROM_ADDRESS'] ?? 'no-reply@localhost')
|
||||
->to($to)
|
||||
->subject('Nova mensagem de contato: ' . ($contact->subject ?? ''))
|
||||
->html(
|
||||
'<h3>Nova mensagem de contato</h3>' .
|
||||
'<p><strong>Nome:</strong> ' . htmlspecialchars($contact->name) . '</p>' .
|
||||
'<p><strong>Email:</strong> ' . htmlspecialchars($contact->email) . '</p>' .
|
||||
'<p><strong>Telefone:</strong> ' . htmlspecialchars((string)$contact->phone) . '</p>' .
|
||||
'<p><strong>Assunto:</strong> ' . htmlspecialchars($contact->subject) . '</p>' .
|
||||
'<p><strong>Mensagem:</strong><br>' . nl2br(htmlspecialchars($contact->message)) . '</p>'
|
||||
);
|
||||
|
||||
try {
|
||||
$mailer->send($email);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user