Initial: ChurchDesk-Terminimport aus loessnitz-kirchspiel.de ausgelagert
- Commands straightsoft:api_events:retrieve und mummertmedia:events-import - Zusatzfelder tl_calendar_events (DCA + deutsche Sprachdatei) - Fix: DBAL-4-kompatibles Löschen entfernter Termine (ArrayParameterType::STRING) - Fix: stabile Aliase (eigener Datensatz wird bei Eindeutigkeitsprüfung ausgenommen) - ChurchDesk-Organisation und Partner-Token über ENV statt im Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
38e23f00ae
11 files changed
+705
No files matched your search
@@ -0,0 +1,3 @@
|
||||
/vendor/
|
||||
composer.lock
|
||||
.DS_Store
|
||||
@@ -0,0 +1,37 @@
|
||||
# mummert/loessnitz-bundle
|
||||
|
||||
Projektspezifische Contao-5.7-Erweiterungen für loessnitz-kirchspiel.de.
|
||||
|
||||
## Konsolenbefehle
|
||||
|
||||
| Befehl | Zweck |
|
||||
|---|---|
|
||||
| `straightsoft:api_events:retrieve` | Lädt alle öffentlichen Termine aus der ChurchDesk-API nach `files/churchdesk-import/events.json` |
|
||||
| `mummertmedia:events-import` | Überträgt die JSON-Datei nach `tl_calendar_events` (Kalender-ID 6), aktualisiert per `externalId` und löscht Termine, die nicht mehr im Feed sind |
|
||||
|
||||
Beide Befehle laufen per Cronjob.
|
||||
|
||||
## Konfiguration (`.env.local` im Projekt)
|
||||
|
||||
```
|
||||
CHURCHDESK_ORGANIZATION_ID=...
|
||||
CHURCHDESK_PARTNER_TOKEN=...
|
||||
```
|
||||
|
||||
## Zusatzfelder `tl_calendar_events`
|
||||
|
||||
`contao/dca/tl_calendar_events.php` legt die vom Import und den Templates
|
||||
genutzten Felder an: `externalId`, `url`, `contributors`, `pastor`, `price`,
|
||||
`summary`, `street`, `street2`, `postal`, `city`, `longitude`, `latitude`,
|
||||
`allDay`, `showEndTime`, `formLink`, `formEmbed`.
|
||||
|
||||
## Installation
|
||||
|
||||
```json
|
||||
"repositories": [
|
||||
{ "type": "vcs", "url": "ssh://git@git.mummert.media:3245/mummert/loessnitz-bundle.git" }
|
||||
],
|
||||
"require": {
|
||||
"mummert/loessnitz-bundle": "^1.0"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "mummert/loessnitz-bundle",
|
||||
"description": "Projektspezifische Erweiterungen für loessnitz-kirchspiel.de (ChurchDesk-Terminimport).",
|
||||
"type": "contao-bundle",
|
||||
"license": "proprietary",
|
||||
"keywords": [
|
||||
"contao",
|
||||
"contao-bundle",
|
||||
"churchdesk"
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"contao/calendar-bundle": "^5.7",
|
||||
"contao/core-bundle": "^5.7",
|
||||
"contao/manager-plugin": "^2.0",
|
||||
"doctrine/dbal": "^4.0",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"symfony/console": "^7.0",
|
||||
"symfony/uid": "^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mummert\\LoessnitzBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"contao-manager-plugin": "Mummert\\LoessnitzBundle\\Contao\\Manager\\Plugin"
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"contao/manager-plugin": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Mummert\LoessnitzBundle\:
|
||||
resource: ../src/
|
||||
exclude:
|
||||
- ../src/DependencyInjection/
|
||||
- ../src/Contao/Manager/
|
||||
- ../src/LoessnitzBundle.php
|
||||
|
||||
mummert_loessnitz.http_client:
|
||||
class: GuzzleHttp\Client
|
||||
|
||||
Mummert\LoessnitzBundle\Command\ImportEventsCommand:
|
||||
arguments:
|
||||
$client: '@mummert_loessnitz.http_client'
|
||||
$filesStorage: '@contao.filesystem.virtual.files'
|
||||
$db: '@database_connection'
|
||||
|
||||
Mummert\LoessnitzBundle\Command\RetrieveApiEventsCommand:
|
||||
arguments:
|
||||
$vfs: '@contao.filesystem.virtual.files'
|
||||
$organizationId: '%env(CHURCHDESK_ORGANIZATION_ID)%'
|
||||
$partnerToken: '%env(CHURCHDESK_PARTNER_TOKEN)%'
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
use Contao\CoreBundle\DataContainer\PaletteManipulator;
|
||||
|
||||
$dca = &$GLOBALS['TL_DCA']['tl_calendar_events'];
|
||||
|
||||
unset($dca['fields']['teaser']['eval']['rte']);
|
||||
|
||||
/**
|
||||
* Palettes
|
||||
*/
|
||||
PaletteManipulator::create()
|
||||
->addField('externalId', 'title')
|
||||
->addField('url', 'location')
|
||||
->addField('contributors', 'location')
|
||||
->addField('pastor', 'location')
|
||||
->addField('price', 'location')
|
||||
->addField('city', 'location')
|
||||
->addField('postal', 'location')
|
||||
->addField('street2', 'location')
|
||||
->addField('street', 'location')
|
||||
->addField('longitude', 'location')
|
||||
->addField('latitude', 'location')
|
||||
->addField('summary', 'teaser')
|
||||
->addField('allDay', 'endDate')
|
||||
->addField('showEndTime', 'endDate')
|
||||
->addField('formLink', 'author')
|
||||
->addField('formEmbed', 'author')
|
||||
->applyToPalette('default', 'tl_calendar_events');
|
||||
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
$dca['fields']['url'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50', 'rgxp' => 'httpurl', 'placeholder' => 'im Format https://...'],
|
||||
'sql' => ['type' => 'string', 'length' => 255, 'default' => ''],
|
||||
];
|
||||
$dca['fields']['formLink'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 512, 'tl_class' => 'w50', 'rgxp' => 'httpurl', 'placeholder' => 'im Format https://...'],
|
||||
'sql' => ['type' => 'string', 'length' => 512, 'default' => ''],
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['formEmbed'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'textarea',
|
||||
'eval' => ['tl_class' => 'clr'],
|
||||
'sql' => ['type' => 'text', 'notnull' => false],
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['contributors'] = [
|
||||
'exclude' => false,
|
||||
'search' => true,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 255, 'default' => ''],
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['pastor'] = [
|
||||
'exclude' => false,
|
||||
'search' => true,
|
||||
'sorting' => true,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 255, 'default' => ''],
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['price'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 255, 'default' => ''],
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['summary'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['tl_class' => 'clr'],
|
||||
'sql' => ['type' => 'text', 'notnull' => false],
|
||||
];
|
||||
|
||||
$dca['fields']['street'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 64, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 64, 'default' => ''],
|
||||
];
|
||||
|
||||
$dca['fields']['street2'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 64, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 64, 'default' => ''],
|
||||
];
|
||||
|
||||
$dca['fields']['postal'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 16, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 16, 'default' => ''],
|
||||
];
|
||||
|
||||
$dca['fields']['city'] = [
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 64, 'tl_class' => 'w50'],
|
||||
'sql' => ['type' => 'string', 'length' => 64, 'default' => ''],
|
||||
];
|
||||
|
||||
$dca['fields']['longitude'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['longitude'],
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'filter' => false,
|
||||
'exclude' => true,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => "varchar(255) NOT NULL default ''",
|
||||
];
|
||||
|
||||
|
||||
|
||||
$dca['fields']['latitude'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['latitude'],
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'filter' => false,
|
||||
'exclude' => true,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => "varchar(255) NOT NULL default ''",
|
||||
];
|
||||
|
||||
$dca['fields']['externalId'] = [
|
||||
'exclude' => false,
|
||||
'search' => true,
|
||||
'sorting' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 64, 'tl_class' => 'w50', 'readonly' => true],
|
||||
'sql' => ['type' => 'string', 'length' => 64, 'default' => ''],
|
||||
];
|
||||
|
||||
$dca['fields']['allDay'] = [
|
||||
'exclude' => true,
|
||||
'inputType' => 'checkbox',
|
||||
'eval' => ['tl_class' => 'w50'],
|
||||
'sql' => "char(1) NOT NULL default ''"
|
||||
];
|
||||
|
||||
$dca['fields']['showEndTime'] = [
|
||||
'exclude' => true,
|
||||
'inputType' => 'checkbox',
|
||||
'eval' => ['tl_class' => 'w50'],
|
||||
'sql' => "char(1) NOT NULL default ''"
|
||||
];
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
$lang = &$GLOBALS['TL_LANG']['tl_calendar_events'];
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
$lang['url'][0] = 'URL';
|
||||
$lang['url'][1] = 'Link zu einer Detailseite mit weiteren Informationen';
|
||||
$lang['contributors'][0] = 'Mitwirkende';
|
||||
$lang['contributors'][1] = 'Künstler, Leiter Kindergottesdienst etc.';
|
||||
$lang['pastor'][0] = 'PfarrerIn';
|
||||
$lang['pastor'][1] = 'leitende(r) PfarrerIn, bei anderen Termin nicht angeben';
|
||||
$lang['price'][0] = 'Preis';
|
||||
$lang['price'][1] = 'Ticketpreise';
|
||||
$lang['summary'][0] = 'Beschreibung';
|
||||
$lang['summary'][1] = 'Langbeschreibung als Ergänzung zum Teasertext';
|
||||
$lang['street'][0] = 'Straße';
|
||||
$lang['street2'][0] = 'Ergänzende Adressangaben';
|
||||
$lang['postal'][0] = 'PLZ';
|
||||
$lang['city'][0] = 'Ort';
|
||||
$lang['longitude'][0] = 'Längengrad';
|
||||
$lang['longitude'][1] = 'in Deutschland irgendwas mit 13.xxxx';
|
||||
$lang['latitude'][0] = 'Breitengrad';
|
||||
$lang['latitude'][1] = 'in Deutschland irgendwas mit 52.xxxx';
|
||||
$lang['externalId'][0] = 'Externe ID';
|
||||
$lang['externalId'][1] = 'ID der externen Schnittstelle zum Datenabgleich';
|
||||
$lang['parishes'][0] = 'Gemeinden';
|
||||
$lang['allDay'][0] = 'Ganztägig';
|
||||
$lang['showEndTime'][0] = 'Endzeit anzeigen';
|
||||
$lang['formLink'][0] = 'A link to the form attached to the event';
|
||||
$lang['formEmbed'][0] = 'A code to embed the form';
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\LoessnitzBundle\Command;
|
||||
|
||||
use Contao\CoreBundle\Framework\ContaoFramework;
|
||||
use Contao\StringUtil;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use GuzzleHttp\Client;
|
||||
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'mummertmedia:events-import',
|
||||
description: 'Import events from JSON file into tl_calendar_events.'
|
||||
)]
|
||||
class ImportEventsCommand extends Command
|
||||
{
|
||||
private const JSON_FILE_UUID = 'ea220a59-6f82-11ef-950a-96000156e216';
|
||||
private const TARGET_CALENDAR_PID = 6; // Dein Kalender
|
||||
|
||||
private array $categoryMapping = [
|
||||
163928 => 12, 163927 => 10, 163930 => 11, 163929 => 9, 85900 => 13,
|
||||
86192 => 14, 86778 => 15, 86189 => 16, 85891 => 2, 85893 => 17,
|
||||
85899 => 18, 130629 => 19, 86191 => 20, 85892 => 21, 85895 => 22,
|
||||
85897 => 3, 146195 => 23, 86455 => 24, 89723 => 25, 86188 => 26,
|
||||
86187 => 27, 174938 => 28, 177613 => 29, 85898 => 30, 86196 => 4,
|
||||
];
|
||||
|
||||
private array $parishCategoryMapping = [
|
||||
33000 => 5, 32996 => 6, 33001 => 8, 33220 => 7,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ContaoFramework $framework,
|
||||
private readonly Connection $db,
|
||||
private readonly Client $client,
|
||||
private readonly VirtualFilesystemInterface $filesStorage
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$this->framework->initialize();
|
||||
|
||||
$stringUtil = $this->framework->getAdapter(StringUtil::class);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// JSON einlesen
|
||||
// -------------------------------------------------------------
|
||||
|
||||
$jsonFileUuid = new Uuid(self::JSON_FILE_UUID);
|
||||
|
||||
if (!$this->filesStorage->fileExists($jsonFileUuid)) {
|
||||
$io->error('JSON-Datei existiert nicht.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$json = $this->filesStorage->read($jsonFileUuid);
|
||||
$events = json_decode($json, true);
|
||||
|
||||
if (!$events) {
|
||||
$io->error('JSON leer oder ungültig.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$importedIds = [];
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Events durchlaufen
|
||||
// -------------------------------------------------------------
|
||||
foreach ($events as $event) {
|
||||
|
||||
$externalId = $event['id'] ?? '';
|
||||
|
||||
$startTime = strtotime($event['startDate'] ?? '');
|
||||
$endTime = strtotime($event['endDate'] ?? '');
|
||||
$hasEnd = !empty($event['showEndtime']) ? $endTime : $startTime;
|
||||
|
||||
$location = $event['location'] ?? '';
|
||||
$address = $event['locationObj']['address'] ?? '';
|
||||
$postal = $event['locationObj']['zipcode'] ?? '';
|
||||
$city = $event['locationObj']['city'] ?? '';
|
||||
$longitude = $event['locationObj']['longitude'] ?? '';
|
||||
$latitude = $event['locationObj']['latitude'] ?? '';
|
||||
$price = $event['price'] ?? '';
|
||||
$contributor = $event['contributor'] ?? '';
|
||||
$teaser = strip_tags($event['description'] ?? '', '<a><img><p><br>');
|
||||
$summary = strip_tags($event['summary'] ?? '');
|
||||
$description = $summary;
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Bildverarbeitung
|
||||
// -------------------------------------------------------------
|
||||
|
||||
$singleSRC = null;
|
||||
$addImage = 0;
|
||||
|
||||
if (!empty($event['image']['span12_16-9'])) {
|
||||
|
||||
$imageUrl = $event['image']['span12_16-9'];
|
||||
$pathInfo = parse_url($imageUrl, PHP_URL_PATH);
|
||||
$ext = pathinfo($pathInfo, PATHINFO_EXTENSION);
|
||||
|
||||
$year = date('Y', $startTime);
|
||||
|
||||
$targetName = sprintf(
|
||||
'churchdesk-import/images/%s/%s.%s',
|
||||
$year,
|
||||
$event['id'],
|
||||
$ext
|
||||
);
|
||||
|
||||
// Datei existiert?
|
||||
if ($this->filesStorage->fileExists($targetName)) {
|
||||
$file = $this->filesStorage->get($targetName);
|
||||
$singleSRC = $stringUtil->uuidToBin($file->getUuid());
|
||||
} else {
|
||||
$response = $this->client->get($imageUrl);
|
||||
$this->filesStorage->write($targetName, $response->getBody()->getContents());
|
||||
$file = $this->filesStorage->get($targetName);
|
||||
$singleSRC = $stringUtil->uuidToBin($file->getUuid());
|
||||
}
|
||||
|
||||
$addImage = 1;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Alias
|
||||
// -------------------------------------------------------------
|
||||
|
||||
$aliasBase = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $event['title']), '-'))
|
||||
. '-' . date('Y-m-d', $startTime);
|
||||
|
||||
$existing = $this->db->fetchAssociative(
|
||||
"SELECT id, alias FROM tl_calendar_events WHERE externalId=?",
|
||||
[$externalId]
|
||||
);
|
||||
|
||||
// Bestehenden Alias behalten, solange Titel und Datum gleich sind (stabile URLs)
|
||||
if ($existing && preg_match('/^' . preg_quote($aliasBase, '/') . '(-\d+)?$/', $existing['alias'])) {
|
||||
$alias = $existing['alias'];
|
||||
} else {
|
||||
$alias = $aliasBase;
|
||||
$counter = 1;
|
||||
|
||||
// eigenen Datensatz bei der Eindeutigkeitsprüfung ausnehmen
|
||||
while ($this->db->fetchOne(
|
||||
"SELECT id FROM tl_calendar_events WHERE alias=? AND id<>?",
|
||||
[$alias, $existing['id'] ?? 0]
|
||||
)) {
|
||||
$alias = $aliasBase . '-' . $counter++;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Kategorien
|
||||
// -------------------------------------------------------------
|
||||
|
||||
$categoryIds = [];
|
||||
|
||||
if (!empty($event['categories'])) {
|
||||
foreach ($event['categories'] as $cat) {
|
||||
if (isset($this->categoryMapping[$cat['id']])) {
|
||||
$categoryIds[] = $this->categoryMapping[$cat['id']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($event['parishes'])) {
|
||||
foreach ($event['parishes'] as $p) {
|
||||
if (isset($this->parishCategoryMapping[$p['id']])) {
|
||||
$categoryIds[] = $this->parishCategoryMapping[$p['id']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Daten für tl_calendar_events
|
||||
// -------------------------------------------------------------
|
||||
|
||||
$row = [
|
||||
'pid' => self::TARGET_CALENDAR_PID,
|
||||
'tstamp' => time(),
|
||||
'title' => $event['title'],
|
||||
'alias' => $alias,
|
||||
'author' => 1,
|
||||
'published' => 1,
|
||||
'addTime' => !empty($event['allDay']) ? 0 : 1,
|
||||
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $hasEnd,
|
||||
'startDate' => $startTime,
|
||||
'endDate' => $hasEnd,
|
||||
|
||||
'location' => $location,
|
||||
'address' => $address,
|
||||
'postal' => $postal,
|
||||
'city' => $city,
|
||||
'longitude' => $longitude,
|
||||
'latitude' => $latitude,
|
||||
'price' => $price,
|
||||
'contributors'=> $contributor,
|
||||
|
||||
'teaser' => $teaser,
|
||||
'summary' => $summary,
|
||||
'description' => $description,
|
||||
|
||||
'addImage' => $addImage,
|
||||
'singleSRC' => $singleSRC,
|
||||
|
||||
'externalId' => $externalId,
|
||||
'categories' => serialize($categoryIds),
|
||||
];
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Insert / Update
|
||||
// -------------------------------------------------------------
|
||||
|
||||
if ($existing) {
|
||||
$this->db->update('tl_calendar_events', $row, ['id' => $existing['id']]);
|
||||
} else {
|
||||
$this->db->insert('tl_calendar_events', $row);
|
||||
}
|
||||
|
||||
$importedIds[] = $externalId;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// DELETE removed events
|
||||
// -------------------------------------------------------------
|
||||
if ($importedIds) {
|
||||
$this->db->executeStatement(
|
||||
"DELETE FROM tl_calendar_events WHERE externalId IS NOT NULL AND externalId NOT IN (?)",
|
||||
[$importedIds],
|
||||
[ArrayParameterType::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
$io->success('Alle Events wurden erfolgreich importiert.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\LoessnitzBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Command\LockableTrait;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'straightsoft:api_events:retrieve',
|
||||
description: 'Retrieve API events from ChurchDesk API and store JSON file.'
|
||||
)]
|
||||
class RetrieveApiEventsCommand extends Command
|
||||
{
|
||||
use LockableTrait;
|
||||
|
||||
|
||||
|
||||
const EVENT_URL = 'https://api2.churchdesk.com/api/v3.0.0/events?itemsNumber=100&imageFormat=span12_16-9';
|
||||
const IMPORT_FOLDER_UUID = 'ea220a63-6f82-11ef-950a-96000156e216';
|
||||
|
||||
public function __construct(
|
||||
private readonly VirtualFilesystemInterface $vfs,
|
||||
private readonly string $organizationId,
|
||||
private readonly string $partnerToken
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// prevent concurrent execution
|
||||
if (!$this->lock()) {
|
||||
$output->writeln('The command is already running.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$client = new Client();
|
||||
|
||||
$events = [];
|
||||
$page = 0;
|
||||
|
||||
do {
|
||||
$io->text('Retrieving page ' . ($page + 1) . '...');
|
||||
|
||||
$response = $client->get(
|
||||
RetrieveApiEventsCommand::EVENT_URL
|
||||
. '&organizationId=' . urlencode($this->organizationId)
|
||||
. '&partnerToken=' . urlencode($this->partnerToken)
|
||||
. '&pageMarker=' . $page
|
||||
);
|
||||
|
||||
$page++;
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!empty($data)) {
|
||||
$events = array_merge($events, $data);
|
||||
$io->success(count($data) . ' events found.');
|
||||
} else {
|
||||
$io->success('0 events found. Finishing.');
|
||||
}
|
||||
|
||||
} while (!empty($data));
|
||||
|
||||
// write JSON file via VFS (Contao 5.6)
|
||||
$path = 'churchdesk-import/events.json';
|
||||
$this->vfs->write($path, json_encode($events, JSON_PRETTY_PRINT));
|
||||
|
||||
$io->success('Events saved: ' . $path);
|
||||
|
||||
$this->release();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\LoessnitzBundle\Contao\Manager;
|
||||
|
||||
use Contao\CalendarBundle\ContaoCalendarBundle;
|
||||
use Contao\CoreBundle\ContaoCoreBundle;
|
||||
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
|
||||
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
|
||||
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
|
||||
use Mummert\LoessnitzBundle\LoessnitzBundle;
|
||||
|
||||
class Plugin implements BundlePluginInterface
|
||||
{
|
||||
public function getBundles(ParserInterface $parser): iterable
|
||||
{
|
||||
return [
|
||||
BundleConfig::create(LoessnitzBundle::class)
|
||||
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\LoessnitzBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
class LoessnitzExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../config'));
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\LoessnitzBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class LoessnitzBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user