Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d06f24b1fa | |||
| ed774a5a37 | |||
| 6664a7c71e | |||
| 4be33834d4 | |||
| b445295959 | |||
| 20ba17c684 | |||
| 323e668ac9 | |||
| 0d384a8fa3 | |||
| b14a0c23f6 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,4 +22,5 @@
|
|||||||
/assets/vendor/
|
/assets/vendor/
|
||||||
###< symfony/asset-mapper ###
|
###< symfony/asset-mapper ###
|
||||||
|
|
||||||
/references/
|
/references/
|
||||||
|
composer.lock
|
||||||
20
Dockerfile
20
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM php:8.4-apache
|
FROM php:8.5-apache
|
||||||
|
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt upgrade -y && \
|
apt upgrade -y && \
|
||||||
@@ -14,8 +14,13 @@ RUN apt update && \
|
|||||||
sqlite3 \
|
sqlite3 \
|
||||||
curl \
|
curl \
|
||||||
git \
|
git \
|
||||||
|
cron \
|
||||||
|
logrotate \
|
||||||
nano
|
nano
|
||||||
|
|
||||||
|
RUN service start cron
|
||||||
|
RUN service enable cron
|
||||||
|
|
||||||
RUN docker-php-ext-configure gd --with-jpeg
|
RUN docker-php-ext-configure gd --with-jpeg
|
||||||
RUN docker-php-ext-configure zip
|
RUN docker-php-ext-configure zip
|
||||||
|
|
||||||
@@ -46,6 +51,19 @@ RUN rm -rf /var/www/html/vendor
|
|||||||
RUN rm -rf /var/www/html/tests
|
RUN rm -rf /var/www/html/tests
|
||||||
RUN rm -rf /var/www/html/translations
|
RUN rm -rf /var/www/html/translations
|
||||||
|
|
||||||
|
RUN echo "20 1 * * 6 root cd /var/www/html && /usr/local/bin/php bin/console app:get-audio > /var/log/sermon-notes.log 2>&1" > /etc/cron.d/get-audio
|
||||||
|
RUN chmod 644 /etc/cron.d/get-audio
|
||||||
|
|
||||||
|
RUN echo "/var/log/sermon-notes.log {
|
||||||
|
monthly
|
||||||
|
rotate 12
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
create 644 root root
|
||||||
|
}" > /etc/logrotate.d/sermon-notes
|
||||||
|
|
||||||
RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --no-scripts --no-dev --optimize-autoloader
|
RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --no-scripts --no-dev --optimize-autoloader
|
||||||
RUN mkdir /data
|
RUN mkdir /data
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ print "Updating migrations and setting permissions for data folder".PHP_EOL;
|
|||||||
// import reference material
|
// import reference material
|
||||||
|
|
||||||
print "Importing Bible and Eccumenical Creeds".PHP_EOL;
|
print "Importing Bible and Eccumenical Creeds".PHP_EOL;
|
||||||
`symfony console app:ingest-bible /var/www/html/reference/esv-bible`;
|
`symfony console app:ingest-bible /var/www/html/references/esv-bible`;
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Apostles 'Apostles Creed' creed apc`;
|
`symfony console app:import-ref /var/www/html/references/creeds/Apostles 'Apostles Creed' creed apc`;
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Athanasian 'Athanasian Creed' creed ath`;
|
`symfony console app:import-ref /var/www/html/references/creeds/Athanasian 'Athanasian Creed' creed ath`;
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Chalcedon 'Definition of Chalcedon' creed dc`;
|
`symfony console app:import-ref /var/www/html/references/creeds/Chalcedon 'Definition of Chalcedon' creed dc`;
|
||||||
@@ -50,13 +50,13 @@ if ($westminsterStandards) {
|
|||||||
`symfony console app:import-wlc /var/www/html/references/wlc 'Westminster Larger' wlc WLC{\$ndx}`;
|
`symfony console app:import-wlc /var/www/html/references/wlc 'Westminster Larger' wlc WLC{\$ndx}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
$helviticConfessions = (
|
$helveticConfessions = (
|
||||||
strtolower(
|
strtolower(
|
||||||
readline("Do you want to import the Helvetic Confessions (1st & 2nd) (y/n)? ")
|
readline("Do you want to import the Helvetic Confessions (1st & 2nd) (y/n)? ")
|
||||||
) == 'y'
|
) == 'y'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($helviticConfessions) {
|
if ($helveticConfessions) {
|
||||||
print "Importing Helvitic standards".PHP_EOL;
|
print "Importing Helvitic standards".PHP_EOL;
|
||||||
`symfony console app:import-ref /var/www/html/references/fhc 'First Helvetic Confession' 1hc 1HC{\$ndx}`;
|
`symfony console app:import-ref /var/www/html/references/fhc 'First Helvetic Confession' 1hc 1HC{\$ndx}`;
|
||||||
`symfony console app:import-ref /var/www/html/references/shc 'Second Helvetic Confession' 2hc 2HC{\$ndx}`;
|
`symfony console app:import-ref /var/www/html/references/shc 'Second Helvetic Confession' 2hc 2HC{\$ndx}`;
|
||||||
|
|||||||
39
migrations/Version20260114224910.php
Normal file
39
migrations/Version20260114224910.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20260114224910 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE user ADD COLUMN home_church_rss VARCHAR(255) DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password, name, meta_data FROM user');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('CREATE TABLE user (id BLOB NOT NULL --(DC2Type:uuid)
|
||||||
|
, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||||
|
, password VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, meta_data CLOB DEFAULT NULL --(DC2Type:json)
|
||||||
|
, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('INSERT INTO user (id, email, roles, password, name, meta_data) SELECT id, email, roles, password, name, meta_data FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)');
|
||||||
|
}
|
||||||
|
}
|
||||||
282
src/Command/GetAudioCommand.php
Normal file
282
src/Command/GetAudioCommand.php
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Note;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:get-audio',
|
||||||
|
description: 'Finds Notes with missing recordings and matches them to RSS feed by Date and Title.',
|
||||||
|
)]
|
||||||
|
class GetAudioCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private HttpClientInterface $httpClient
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'No DB changes.');
|
||||||
|
// No specific --debug flag needed, we will output verbose logs by default for now
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$isDryRun = $input->getOption('dry-run');
|
||||||
|
$noteRepository = $this->entityManager->getRepository(Note::class);
|
||||||
|
|
||||||
|
$io->title("Starting Audio Matcher");
|
||||||
|
|
||||||
|
// 1. Fetch Notes
|
||||||
|
$qb = $noteRepository->createQueryBuilder('n')
|
||||||
|
->leftJoin('n.user', 'u')
|
||||||
|
->addSelect('u')
|
||||||
|
->where('n.recording IS NULL OR n.recording = :empty')
|
||||||
|
->andWhere('u.homeChurchRSS IS NOT NULL')
|
||||||
|
->orderBy('n.date', 'DESC') // <--- Added Sort Here
|
||||||
|
->setParameter('empty', '');
|
||||||
|
//$query = $qb->getQuery();
|
||||||
|
|
||||||
|
//print ($query->getSql());
|
||||||
|
|
||||||
|
$notesMissingAudio = $qb->getQuery()->getResult();
|
||||||
|
$count = count($notesMissingAudio);
|
||||||
|
$io->text("Found $count notes in database missing audio.");
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Group by User
|
||||||
|
$notesByUser = [];
|
||||||
|
foreach ($notesMissingAudio as $note) {
|
||||||
|
$userId = (string) $note->getUser()->getId();
|
||||||
|
$notesByUser[$userId]['user'] = $note->getUser();
|
||||||
|
$notesByUser[$userId]['notes'][] = $note;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Process Per User
|
||||||
|
foreach ($notesByUser as $userId => $data) {
|
||||||
|
$user = $data['user'];
|
||||||
|
$userNotes = $data['notes'];
|
||||||
|
$rssUrl = $user->getHomeChurchRSS();
|
||||||
|
|
||||||
|
$io->section("User: {$user->getEmail()} (Notes: " . count($userNotes) . ")");
|
||||||
|
$io->text("Fetching RSS: $rssUrl");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Pass $io to helper for debug output
|
||||||
|
$rssItems = $this->fetchRssItems($rssUrl, $io);
|
||||||
|
|
||||||
|
if (empty($rssItems)) {
|
||||||
|
$io->warning("RSS feed was empty or failed to parse.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matchCount = 0;
|
||||||
|
|
||||||
|
foreach ($userNotes as $note) {
|
||||||
|
if (!$note->getDate()) {
|
||||||
|
$io->text(" > Note ID {$note->getId()} skipped (No Date)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$noteDateString = $note->getDate()->format('Y-m-d');
|
||||||
|
$noteTitle = $note->getTitle();
|
||||||
|
$io->text("---------------------------------------------------");
|
||||||
|
$io->text("Checking Note: [$noteDateString] '$noteTitle'");
|
||||||
|
|
||||||
|
$bestMatch = null;
|
||||||
|
$highestConfidence = 0;
|
||||||
|
|
||||||
|
foreach ($rssItems as $item) {
|
||||||
|
// DEBUG: Show Date Comparison
|
||||||
|
if ($item['date_string'] !== $noteDateString) {
|
||||||
|
// Uncomment the line below if you want to see EVERY failed date comparison (can be noisy)
|
||||||
|
// $io->text(" - REJECTED: Date mismatch (RSS: {$item['date_string']})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG: Show Score Calculation
|
||||||
|
$confidence = $this->calculateConfidence($note, $item);
|
||||||
|
$io->text(sprintf(
|
||||||
|
" - DATE MATCHED. Score: %d%%. RSS Title: '%s'",
|
||||||
|
$confidence,
|
||||||
|
$item['title']
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($confidence >= 80 && $confidence > $highestConfidence) {
|
||||||
|
$highestConfidence = $confidence;
|
||||||
|
$bestMatch = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bestMatch) {
|
||||||
|
$matchCount++;
|
||||||
|
$io->success("Match Found! ($highestConfidence%)");
|
||||||
|
if (!$isDryRun) {
|
||||||
|
$note->setRecording($bestMatch['url']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$io->text(" > No match found for this note.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isDryRun) {
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matchCount > 0) {
|
||||||
|
$io->success("Found $matchCount matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->error("Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively fetches RSS items if pagination links are present.
|
||||||
|
*/
|
||||||
|
private function fetchRssItems(string $startUrl, SymfonyStyle $io): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
$nextUrl = $startUrl;
|
||||||
|
$pageCount = 0;
|
||||||
|
$maxPages = 20; // Safety brake to prevent infinite loops
|
||||||
|
|
||||||
|
do {
|
||||||
|
$pageCount++;
|
||||||
|
$io->text(" > Fetching Feed Page $pageCount: $nextUrl");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->request('GET', $nextUrl);
|
||||||
|
$content = $response->getContent();
|
||||||
|
|
||||||
|
// Suppress warnings for malformed XML
|
||||||
|
$xml = @simplexml_load_string($content);
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
$io->warning("XML Parsing Failed on page $pageCount");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->warning("HTTP Request Failed on page $pageCount: " . $e->getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Parse Items on this page
|
||||||
|
$pageItemsCount = 0;
|
||||||
|
foreach ($xml->channel->item as $item) {
|
||||||
|
$namespaces = $item->getNamespaces(true);
|
||||||
|
$speaker = '';
|
||||||
|
|
||||||
|
// Speaker Logic
|
||||||
|
if (isset($namespaces['itunes'])) {
|
||||||
|
$itunes = $item->children($namespaces['itunes']);
|
||||||
|
$speaker = (string) ($itunes->author ?? '');
|
||||||
|
}
|
||||||
|
if (empty($speaker) && isset($namespaces['dc'])) {
|
||||||
|
$dc = $item->children($namespaces['dc']);
|
||||||
|
$speaker = (string) ($dc->creator ?? '');
|
||||||
|
}
|
||||||
|
if (empty($speaker)) {
|
||||||
|
$speaker = (string) ($item->author ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date Parsing
|
||||||
|
$dateString = null;
|
||||||
|
if (isset($item->pubDate)) {
|
||||||
|
try {
|
||||||
|
$dt = new \DateTimeImmutable((string)$item->pubDate);
|
||||||
|
$dateString = $dt->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// ignore bad date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = [
|
||||||
|
'title' => (string) $item->title,
|
||||||
|
'speaker' => $speaker,
|
||||||
|
'url' => (string) ($item->enclosure['url'] ?? ''),
|
||||||
|
'date_string' => $dateString,
|
||||||
|
];
|
||||||
|
$pageItemsCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->text(" Found $pageItemsCount items on this page.");
|
||||||
|
|
||||||
|
// 2. Look for "Next Page" link (RFC 5005 / Atom)
|
||||||
|
$nextUrl = null;
|
||||||
|
|
||||||
|
// Get namespaces on the <channel> element
|
||||||
|
$namespaces = $xml->channel->getNamespaces(true);
|
||||||
|
|
||||||
|
if (isset($namespaces['atom'])) {
|
||||||
|
$atom = $xml->channel->children($namespaces['atom']);
|
||||||
|
foreach ($atom->link as $link) {
|
||||||
|
// We are looking for <atom:link rel="next" href="..." />
|
||||||
|
$attributes = $link->attributes();
|
||||||
|
if (isset($attributes['rel']) && (string)$attributes['rel'] === 'next') {
|
||||||
|
$nextUrl = (string)$attributes['href'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check for raw <link rel="next"> if atom ns missing (rare but happens)
|
||||||
|
if (!$nextUrl && property_exists($xml->channel, 'link')) {
|
||||||
|
foreach ($xml->channel->link as $link) {
|
||||||
|
$attributes = $link->attributes();
|
||||||
|
if (isset($attributes['rel']) && (string)$attributes['rel'] === 'next') {
|
||||||
|
$nextUrl = (string)$attributes['href'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while ($nextUrl && $pageCount < $maxPages);
|
||||||
|
|
||||||
|
$io->success(sprintf("Finished fetching. Total items: %d (across %d pages)", count($items), $pageCount));
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateConfidence(Note $note, array $rssItem): float
|
||||||
|
{
|
||||||
|
$noteTitle = $this->normalize($note->getTitle());
|
||||||
|
$rssTitle = $this->normalize($rssItem['title']);
|
||||||
|
|
||||||
|
$noteSpeaker = $this->normalize($note->getSpeaker()->getName() ?? '');
|
||||||
|
$rssSpeaker = $this->normalize($rssItem['speaker']);
|
||||||
|
|
||||||
|
similar_text($noteTitle, $rssTitle, $titlePercent);
|
||||||
|
|
||||||
|
if (!empty($noteSpeaker) && !empty($rssSpeaker)) {
|
||||||
|
similar_text($noteSpeaker, $rssSpeaker, $speakerPercent);
|
||||||
|
return ($titlePercent + $speakerPercent) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $titlePercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalize(string $input): string
|
||||||
|
{
|
||||||
|
return strtolower(trim($input));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ use Exception;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\Mailer\MailerInterface;
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
use Symfony\Component\Mime\Address;
|
use Symfony\Component\Mime\Address;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@@ -453,6 +454,47 @@ class AjaxController extends AbstractController
|
|||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/save-profile', name: 'app_save_profile', methods: ['POST'])]
|
||||||
|
public function saveProfile(Request $req, EntityManagerInterface $emi): Response
|
||||||
|
{
|
||||||
|
$data = json_decode($req->getContent());
|
||||||
|
/** @var App\Entity\User $user */
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return new JsonResponse(['msg' => 'No User']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data->passChange) {
|
||||||
|
if(!$data->password) {
|
||||||
|
return new JsonResponse(['msg' => 'Blank password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo check that password matches current password
|
||||||
|
if ($data->password != $user->getPassword()) {
|
||||||
|
return new JsonResponse(['msg' => 'Invalid password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data->newPassword != $data->confPassword) {
|
||||||
|
return new JsonResponse(['msg' => 'Passwords don\'t match']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setName($data->name);
|
||||||
|
$user->setEmail($data->email);
|
||||||
|
$user->setHomeChurchRSS($data->homeChurch);
|
||||||
|
|
||||||
|
$emi->persist($user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$emi->flush();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return new JsonResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(['msg' => 'Updated']);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/save-settings', name: 'app_save_settings', methods: ['POST'])]
|
#[Route('/save-settings', name: 'app_save_settings', methods: ['POST'])]
|
||||||
public function saveSettings(Request $req, EntityManagerInterface $emi): Response
|
public function saveSettings(Request $req, EntityManagerInterface $emi): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSer
|
|||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?array $metaData = null;
|
private ?array $metaData = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $homeChurchRSS = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->series = new ArrayCollection();
|
$this->series = new ArrayCollection();
|
||||||
@@ -323,4 +326,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSer
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHomeChurchRSS(): ?string
|
||||||
|
{
|
||||||
|
return $this->homeChurchRSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHomeChurchRSS(?string $homeChurchRSS): static
|
||||||
|
{
|
||||||
|
$this->homeChurchRSS = $homeChurchRSS;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,58 @@ class Utils
|
|||||||
// error message or try to resend the message
|
// error message or try to resend the message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function filePerms($file): string
|
||||||
|
{
|
||||||
|
$perms = fileperms($file);
|
||||||
|
|
||||||
|
switch ($perms & 0xF000) {
|
||||||
|
case 0xC000: // socket
|
||||||
|
$info = 's';
|
||||||
|
break;
|
||||||
|
case 0xA000: // symbolic link
|
||||||
|
$info = 'l';
|
||||||
|
break;
|
||||||
|
case 0x8000: // regular
|
||||||
|
$info = 'r';
|
||||||
|
break;
|
||||||
|
case 0x6000: // block special
|
||||||
|
$info = 'b';
|
||||||
|
break;
|
||||||
|
case 0x4000: // directory
|
||||||
|
$info = 'd';
|
||||||
|
break;
|
||||||
|
case 0x2000: // character special
|
||||||
|
$info = 'c';
|
||||||
|
break;
|
||||||
|
case 0x1000: // FIFO pipe
|
||||||
|
$info = 'p';
|
||||||
|
break;
|
||||||
|
default: // unknown
|
||||||
|
$info = 'u';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner
|
||||||
|
$info .= (($perms & 0x0100) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0080) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0040) ?
|
||||||
|
(($perms & 0x0800) ? 's' : 'x' ) :
|
||||||
|
(($perms & 0x0800) ? 'S' : '-'));
|
||||||
|
|
||||||
|
// Group
|
||||||
|
$info .= (($perms & 0x0020) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0010) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0008) ?
|
||||||
|
(($perms & 0x0400) ? 's' : 'x' ) :
|
||||||
|
(($perms & 0x0400) ? 'S' : '-'));
|
||||||
|
|
||||||
|
// World
|
||||||
|
$info .= (($perms & 0x0004) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0002) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0001) ?
|
||||||
|
(($perms & 0x0200) ? 't' : 'x' ) :
|
||||||
|
(($perms & 0x0200) ? 'T' : '-'));
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<link href="{{ asset('css/main.css') }}" rel="stylesheet" />
|
<link href="{{ asset('css/main.css') }}" rel="stylesheet" />
|
||||||
<link href="{{ asset('css/jquery-ui.theme.css') }}" rel='stylesheet' />
|
<link href="{{ asset('css/jquery-ui.theme.css') }}" rel='stylesheet' />
|
||||||
<link href="{{ asset('css/jquery-ui.structure.css') }}" rel='stylesheet' />
|
<link href="{{ asset('css/jquery-ui.structure.css') }}" rel='stylesheet' />
|
||||||
<link href="{{ asset('css/style.css') }}" rel='stylesheet' />
|
<link href="{{ asset('styles/style.css') }}" rel='stylesheet' />
|
||||||
<link href='//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css' rel='stylesheet' />
|
<link href='//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css' rel='stylesheet' />
|
||||||
<style>
|
<style>
|
||||||
.flex-container {
|
.flex-container {
|
||||||
@@ -80,6 +80,49 @@ $(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function saveProfile() {
|
||||||
|
const name = $('#name');
|
||||||
|
const email = $('#email');
|
||||||
|
const homeChurch = $('#home-church');
|
||||||
|
const password = $('#password');
|
||||||
|
const newPassword = $('#new-password');
|
||||||
|
const confPassword = $('#conf-password');
|
||||||
|
let passChange = false;
|
||||||
|
|
||||||
|
if (newPassword.val().length > 0) {
|
||||||
|
if (password.val().length == 0) {
|
||||||
|
alert('If you want to change your password you need to put in the current password as well');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.val() != confPassword.val()) {
|
||||||
|
alert('New password and confirm passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
passChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/save-profile', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'name': name.val(),
|
||||||
|
'email': email.val(),
|
||||||
|
'homeChurch': homeChurch.val(),
|
||||||
|
'passChange': passChange,
|
||||||
|
'password': password.val(),
|
||||||
|
'newPassword': newPassword.val(),
|
||||||
|
'confPassword': confPassword.val()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
alert(results.msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
var saveInterval = $('#save-interval');
|
var saveInterval = $('#save-interval');
|
||||||
var saveReferences = $('#save-references');
|
var saveReferences = $('#save-references');
|
||||||
@@ -128,6 +171,9 @@ function rollUp(cont) {
|
|||||||
<label for='email'>Email: </label>
|
<label for='email'>Email: </label>
|
||||||
<input type='email' id='email' name='email' value='{{ app.user.email }}' /><br />
|
<input type='email' id='email' name='email' value='{{ app.user.email }}' /><br />
|
||||||
|
|
||||||
|
<label for='home-church'>Home Church RSS Feed: </label>
|
||||||
|
<input type='text' id='home-church' name='home-church' value='{{ app.user.homeChurchRSS }}' /><br />
|
||||||
|
|
||||||
<label for='password'>Password: </label>
|
<label for='password'>Password: </label>
|
||||||
<input type='password' id='password' name='password' /><br/>
|
<input type='password' id='password' name='password' /><br/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user