From b70cc335aa67399c26a5865bb3e37ca2be617978 Mon Sep 17 00:00:00 2001 From: Nick Adams Date: Mon, 19 Oct 2026 20:59:45 -0700 Subject: [PATCH] service_body shortcode updates support new service body shortcodes to match formatting from BMLT while not impacting existing use cases: * update service_body_X menu item to suffix with (UPPER CASE) * add service_body_X_name to match the name as written in BMLT * add service_body_x_description from BMLT * add service_body_x_helpline from BMLT * add helper functions to lookup service body fields from BMLT * update test fixtures * add tests for new functions * add tests for new shortcodes --- admin/tinymce/front_page_button/plugin.min.js | 100 +++++++++++++++- includes/class-bread-bmlt.php | 10 ++ public/class-bread-content-generator.php | 34 +++++- tests/BreadContentGeneratorTest.php | 110 ++++++++++++++++++ tests/bootstrap.php | 1 + tests/configurations/berlin-booklet.json | 3 + tests/mock/class-bread-bmlt.php | 11 ++ .../serviceBodies/berlin-service-bodies.json | 102 ++++++++++++++++ 8 files changed, 360 insertions(+), 11 deletions(-) create mode 100644 tests/BreadContentGeneratorTest.php create mode 100644 tests/serviceBodies/berlin-service-bodies.json diff --git a/admin/tinymce/front_page_button/plugin.min.js b/admin/tinymce/front_page_button/plugin.min.js index 55cf33b3..666b7851 100644 --- a/admin/tinymce/front_page_button/plugin.min.js +++ b/admin/tinymce/front_page_button/plugin.min.js @@ -102,34 +102,124 @@ text: 'Service Body', menu: [ { - text: 'Service Body 1', + text: 'Service Body 1 (UPPER CASE)', onclick: function () { editor.insertContent('[service_body_1]'); } }, { - text: 'Service Body 2', + text: 'Service Body 1 (as stored)', + onclick: function () { + editor.insertContent('[service_body_1_name]'); + } + }, + { + text: 'Service Body 1 Description', + onclick: function () { + editor.insertContent('[service_body_1_description]'); + } + }, + { + text: 'Service Body 1 Helpline', + onclick: function () { + editor.insertContent('[service_body_1_helpline]'); + } + }, + { + text: 'Service Body 2 (UPPER CASE)', onclick: function () { editor.insertContent('[service_body_2]'); } }, { - text: 'Service Body 3', + text: 'Service Body 2 (as stored)', + onclick: function () { + editor.insertContent('[service_body_2_name]'); + } + }, + { + text: 'Service Body 2 Description', + onclick: function () { + editor.insertContent('[service_body_2_description]'); + } + }, + { + text: 'Service Body 2 Helpline', + onclick: function () { + editor.insertContent('[service_body_2_helpline]'); + } + }, + { + text: 'Service Body 3 (UPPER CASE)', onclick: function () { editor.insertContent('[service_body_3]'); } }, { - text: 'Service Body 4', + text: 'Service Body 3 (as stored)', + onclick: function () { + editor.insertContent('[service_body_3_name]'); + } + }, + { + text: 'Service Body 3 Description', + onclick: function () { + editor.insertContent('[service_body_3_description]'); + } + }, + { + text: 'Service Body 3 Helpline', + onclick: function () { + editor.insertContent('[service_body_3_helpline]'); + } + }, + { + text: 'Service Body 4 (UPPER CASE)', onclick: function () { editor.insertContent('[service_body_4]'); } }, { - text: 'Service Body 5', + text: 'Service Body 4 (as stored)', + onclick: function () { + editor.insertContent('[service_body_4_name]'); + } + }, + { + text: 'Service Body 4 Description', + onclick: function () { + editor.insertContent('[service_body_4_description]'); + } + }, + { + text: 'Service Body 4 Helpline', + onclick: function () { + editor.insertContent('[service_body_4_helpline]'); + } + }, + { + text: 'Service Body 5 (UPPER CASE)', onclick: function () { editor.insertContent('[service_body_5]'); } + }, + { + text: 'Service Body 5 (as stored)', + onclick: function () { + editor.insertContent('[service_body_5_name]'); + } + }, + { + text: 'Service Body 5 Description', + onclick: function () { + editor.insertContent('[service_body_5_description]'); + } + }, + { + text: 'Service Body 5 Helpline', + onclick: function () { + editor.insertContent('[service_body_5_helpline]'); + } } ] }, diff --git a/includes/class-bread-bmlt.php b/includes/class-bread-bmlt.php index 5bd4991d..2bd8e764 100644 --- a/includes/class-bread-bmlt.php +++ b/includes/class-bread-bmlt.php @@ -7,6 +7,7 @@ class Bread_Bmlt public $connection_error; private $bmlt_server_lang = ''; private array $unique_areas; + private array $service_bodies_by_id = []; private Bread $bread; private ?array $preloaded = null; @@ -72,6 +73,7 @@ public function get_areas() $this->unique_areas = array(); foreach ($result as $value) { + $this->service_bodies_by_id[$value['id']] = $value; $parent_name = 'Parent ID'; foreach ($result as $parent) { if ($value['parent_id'] == $parent['id']) { @@ -86,6 +88,14 @@ public function get_areas() return $this->unique_areas; } + /** + * Returns the GetServiceBodies entry for $id, or null. + */ + public function get_service_body(string $id): ?array + { + $this->get_areas(); + return $this->service_bodies_by_id[$id] ?? null; + } public function generateMainQuery($json = 'json') { $sort_keys = 'weekday_tinyint,start_time,meeting_name'; diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php index 618aefbd..3bcef1f9 100644 --- a/public/class-bread-content-generator.php +++ b/public/class-bread-content-generator.php @@ -133,14 +133,9 @@ function __construct(Mpdf $mpdf, Bread $bread, array $result_meetings, Bread_For "[month]" => strtoupper(gmdate("F")), "[day]" => strtoupper(gmdate("j")), "[year]" => strtoupper(gmdate("Y")), - "[service_body]" => (count($this->options['service_bodies']) > 0) ? explode(',', strtoupper($this->options['service_bodies'][0]))[0] : 'Not Used', - "[service_body_1]" => (count($this->options['service_bodies']) > 0) ?explode(',', strtoupper($this->options['service_bodies'][0]))[0] : 'Not Used', - "[service_body_2]" => (count($this->options['service_bodies']) > 1) ? explode(',', strtoupper($this->options['service_bodies'][1]))[0] : 'Not Used', - "[service_body_3]" => (count($this->options['service_bodies']) > 2) ? explode(',', strtoupper($this->options['service_bodies'][2]))[0] : 'Not Used', - "[service_body_4]" => (count($this->options['service_bodies']) > 3) ? explode(',', strtoupper($this->options['service_bodies'][3]))[0] : 'Not Used', - "[service_body_5]" => (count($this->options['service_bodies']) > 4) ? explode(',', strtoupper($this->options['service_bodies'][4]))[0] : 'Not Used', ); + $this->addServiceBodyShortcodes(); $this->shortcodes = apply_filters("Bread_Section_Shortcodes", $this->shortcodes, $this->bread->bmlt()->get_areas(), $formatsManager->getFormatsUsed()); if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { @$this->mpdf->DefHTMLFooterByName('MyFooter', '
' . $this->options['nonmeeting_footer'] . '
'); @@ -176,6 +171,33 @@ function __construct(Mpdf $mpdf, Bread $bread, array $result_meetings, Bread_For 'line_break' => '
', ); } + /** + * Adds [service_body_N] (upper case), [service_body_N_name], [service_body_N_description] and [service_body_N_helpline]. + */ + private function addServiceBodyShortcodes(): void + { + for ($i = 0; $i < 5; $i++) { + $name = 'Not Used'; + $description = 'Not Used'; + $helpline = 'Not Used'; + if (isset($this->options['service_bodies'][$i])) { + $area_data = explode(',', $this->options['service_bodies'][$i]); + $name = $area_data[0]; + $service_body = isset($area_data[1]) ? $this->bread->bmlt()->get_service_body($area_data[1]) : null; + $description = $service_body['description'] ?? ''; + $helpline = $service_body['helpline'] ?? ''; + } + $n = $i + 1; + $this->shortcodes["[service_body_$n]"] = $name == 'Not Used' ? $name : mb_strtoupper($name, 'UTF-8'); + $this->shortcodes["[service_body_{$n}_name]"] = $name; + $this->shortcodes["[service_body_{$n}_description]"] = $description; + $this->shortcodes["[service_body_{$n}_helpline]"] = $helpline; + } + $this->shortcodes['[service_body]'] = $this->shortcodes['[service_body_1]']; + $this->shortcodes['[service_body_name]'] = $this->shortcodes['[service_body_1_name]']; + $this->shortcodes['[service_body_description]'] = $this->shortcodes['[service_body_1_description]']; + $this->shortcodes['[service_body_helpline]'] = $this->shortcodes['[service_body_1_helpline]']; + } /** * Generates the contents of the meeting list. * diff --git a/tests/BreadContentGeneratorTest.php b/tests/BreadContentGeneratorTest.php new file mode 100644 index 00000000..7295a4ef --- /dev/null +++ b/tests/BreadContentGeneratorTest.php @@ -0,0 +1,110 @@ +doTest( + 'berlin-booklet', + [], + 'berlin-service-bodies', + [ + '[service_body]' => 'GEBIET BERLIN', + '[service_body_name]' => 'Gebiet Berlin', + '[service_body_description]' => 'Zuständig für: Berlin und Umland', + '[service_body_helpline]' => '+49 30 12345678', + '[service_body_1]' => 'GEBIET BERLIN', + '[service_body_1_name]' => 'Gebiet Berlin', + '[service_body_1_description]' => 'Zuständig für: Berlin und Umland', + '[service_body_1_helpline]' => '+49 30 12345678', + '[service_body_2]' => 'Not Used', + '[service_body_2_name]' => 'Not Used', + '[service_body_2_description]' => 'Not Used', + '[service_body_2_helpline]' => 'Not Used', + '[service_body_5]' => 'Not Used', + '[service_body_5_name]' => 'Not Used', + '[service_body_5_description]' => 'Not Used', + '[service_body_5_helpline]' => 'Not Used', + ] + ); + } + public function testServiceBodyShortcodesMultiple() + { + $this->doTest( + 'berlin-booklet', + ['service_bodies' => [ + 'Gebiet Süd,7,1,Deutschsprachige Region', + 'Gebiet Österreich,10,1,Deutschsprachige Region', + 'Online Gebiet,12,1,Deutschsprachige Region', + ]], + 'berlin-service-bodies', + [ + '[service_body_1]' => 'GEBIET SÜD', + '[service_body_1_name]' => 'Gebiet Süd', + '[service_body_1_description]' => 'Zuständig für: Stuttgart, Bayern, Österreich', + '[service_body_1_helpline]' => '0800 4453362', + '[service_body_2]' => 'GEBIET ÖSTERREICH', + '[service_body_2_name]' => 'Gebiet Österreich', + '[service_body_2_description]' => 'Zuständig für: Österreich', + '[service_body_2_helpline]' => '', + '[service_body_3]' => 'ONLINE GEBIET', + '[service_body_3_name]' => 'Online Gebiet', + '[service_body_3_description]' => '', + '[service_body_3_helpline]' => '', + '[service_body_4]' => 'Not Used', + ] + ); + } + public function testServiceBodyMissingFromBmlt() + { + $this->doTest( + 'berlin-booklet', + ['service_bodies' => ['Gebiet Gelöscht,99,1,Deutschsprachige Region']], + 'berlin-service-bodies', + [ + '[service_body_1]' => 'GEBIET GELÖSCHT', + '[service_body_1_name]' => 'Gebiet Gelöscht', + '[service_body_1_description]' => '', + '[service_body_1_helpline]' => '', + ] + ); + } + public function doTest($config, $changes, $serviceBodiesJson, $expected): void + { + $options = $this->getConfiguration($config); + foreach ($changes as $key => $value) { + $options[$key] = $value; + } + $bread = new Bread($options); + $bread->bmlt()->setFormatBase('german-formats'); + $bread->bmlt()->set_service_bodies($this->getServiceBodies($serviceBodiesJson)); + $formats = $this->getFormats('berlin-formats-de'); + $formatMgr = new Bread_FormatsManager($formats, 'de', $bread->bmlt()); + + $generator = new Bread_ContentGenerator($this->createMock(Mpdf::class), $bread, [], $formatMgr); + $shortcodes = (new ReflectionProperty($generator, 'shortcodes'))->getValue($generator); + foreach ($expected as $shortcode => $value) { + assertEquals($value, $shortcodes[$shortcode], $shortcode); + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 779b0245..4cf2186b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,6 +6,7 @@ require_once __DIR__.'/../public/class-bread-meetingslist-structure.php'; require_once __DIR__.'/../public/class-bread-meeting-enhancer.php'; require_once __DIR__.'/../public/class-bread-format-manager.php'; +require_once __DIR__.'/../public/class-bread-content-generator.php'; // Mock WordPress functions used for testing. //phpcs:ignore function apply_filters($hook_name, $value, $args) diff --git a/tests/configurations/berlin-booklet.json b/tests/configurations/berlin-booklet.json index 3e72197e..15516aaf 100644 --- a/tests/configurations/berlin-booklet.json +++ b/tests/configurations/berlin-booklet.json @@ -3,6 +3,9 @@ "service_body_1": "Gebiet Berlin,3,1,Deutschsprachige Region", "service_body_2": "Not Used", "service_body_3": "Not Used", + "service_bodies": [ + "Gebiet Berlin,3,1,Deutschsprachige Region" + ], "header_font_size": 8.199999999999999289457264239899814128875732421875, "front_page_content": "

\"\"<\/p>\r\n


[month_lower] [year]

<\/strong><\/span><\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n\r\n\r\n\r\n
\u00a0<\/td>\r\n\"\"<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

\"\"<\/p>\r\n

[page_break] [start_page_numbers]<\/p>\r\n

NA - Eine Gemeinschaft genesender S\u00fcchtiger

<\/strong><\/span><\/p>\r\n

Narcotics Anonymous ist eine weltweite Gemeinschaft von genesenden S\u00fcchtigen. Unser Hauptziel ist es, S\u00fcchtigen durch die Anwendung des Zw\u00f6lf-Schritte-Programms zu helfen, mit dem Drogennehmen aufzuh\u00f6ren.

<\/span><\/p>\r\n

Alle Menschen, die ein Drogenproblem haben, k\u00f6nnen die kostenfreien Gruppentreffen (Meetings) besuchen, unabh\u00e4ngig davon, welche Droge oder Kombination von Drogen sie konsumiert haben. Angeh\u00f6rige oder Menschen, die sich f\u00fcr NA interessieren, aber selbst kein Suchtproblem haben, k\u00f6nnen gerne als \u201eoffen\u201c gekennzeichnete Meetings besuchen.

<\/strong><\/span><\/p>\r\n

Einer der Gr\u00fcnde f\u00fcr den Erfolg von Narcotics Anonymous ist der therapeutische Wert, der darin liegt, dass S\u00fcchtige einander helfen. Mitglieder teilen miteinander ihre Erfolge und Schwierigkeiten beim Erreichen von Abstinenz und im drogenfreien Leben. Sie lernen durch die Zw\u00f6lf Schritte und Zw\u00f6lf Traditionen von NA ein drogenfreies produktives Leben zu f\u00fchren. Die darin enthaltenen Prinzipien sind der Kern des Genesungsprogramms von NA.

<\/span><\/p>\r\n

NA ist keine religi\u00f6se Gemeinschaft und setzt kein bestimmtes Glaubenssystem voraus. Die Mitglieder werden aber ermutigt, ihr eigenes Verst\u00e4ndnis der oben genannten spirituellen Prinzipien zu entwickeln. Dieses Verst\u00e4ndnis kann religi\u00f6s sein, muss es aber nicht. Es geht darum, diese Prinzipien auf den Alltag anzuwenden.

<\/span><\/p>\r\n

NA<\/span>\u00a0ist auf\u00a0lokaler<\/span>\u00a0Ebene<\/span>\u00a0organisiert<\/span>\u00a0und\u00a0h\u00e4lt<\/span>\u00a0<\/i>ca.\u00a076.000 Meetings pro Woche in 143 L\u00e4ndern ab (Stand 2024). In Berlin und Umgebung finden zur Zeit w\u00f6chentlich\u00a0mehr<\/span>\u00a0als<\/span>\u00a0100 Meetings statt, darunter Meetings in Deutsch, Farsi, Franz\u00f6sisch, Englisch, Russisch, Ukrainisch, Serbo-Kroatisch und Polnisch. Es gibt Pr\u00e4senz- und Online-Meetings, Frauen-, M\u00e4nner- und LGBTQ+-Meetings. In diesem Booklet findet Ihr die Zeiten und Ortsangaben der aktuellen Treffen.<\/i>

<\/span><\/p>\r\n

Alle Meetings sind Nichtraucher-Meetings. Wenn nicht anders angegeben, finden alle Meetings auch an Feiertagen statt. Weitere Angaben findet Ihr in den jeweiligen Meetingsinformationen.<\/span> <\/strong><\/span>[page_break]<\/span><\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

 <\/p>\r\n

Nur f\u00fcr heute <\/strong><\/span><\/p>\r\n


Sage Dir selbst:
<\/span>
Nur f\u00fcr heute<\/strong>
<\/span>werden meine Gedanken bei der Genesung sein,
beim Leben und bei der <\/span>Freude am Leben ohne Drogen.
<\/span>
Nur f\u00fcr heute<\/strong>
werde ich einem Mitglied von NA vertrauen,
das an mich glaubt und mir in <\/span>meiner Genesung helfen will.
<\/span>
Nur f\u00fcr heute<\/strong>
werde ich ein Programm haben.
Ich werde versuchen, ihm so gut wie <\/span>m\u00f6glich zu folgen.
<\/span>
Nur f\u00fcr heute<\/strong>
werde ich durch NA versuchen, ein besseres Verh\u00e4ltnis zu meinem Leben zu <\/span>gewinnen.
<\/span>
Nur f\u00fcr heute<\/strong>
werde ich nicht \u00e4ngstlich sein, meine Gedanken werden
bei meinen neuen <\/span>Bekannten sein,
bei Leuten, die keine Drogen nehmen und die einen neuen Lebensweg <\/span>gefunden haben.
<\/span>

Solange ich diesem Weg folge,
brauche ich nichts zu bef\u00fcrchten.<\/span><\/p>\r\n

[page_break]<\/p>", "front_line_height": "1.4", diff --git a/tests/mock/class-bread-bmlt.php b/tests/mock/class-bread-bmlt.php index 568565fb..2768bda5 100644 --- a/tests/mock/class-bread-bmlt.php +++ b/tests/mock/class-bread-bmlt.php @@ -2,6 +2,7 @@ class Bread_Bmlt { private array $areas = array(); + private array $service_bodies = array(); private string $format_base; function __construct($bread) { @@ -18,6 +19,16 @@ public function get_areas() { return $this->areas; } + public function set_service_bodies(array $service_bodies) + { + foreach ($service_bodies as $service_body) { + $this->service_bodies[$service_body['id']] = $service_body; + } + } + public function get_service_body(string $id): ?array + { + return $this->service_bodies[$id] ?? null; + } public static function parse_field($text) { if ($text != '') { diff --git a/tests/serviceBodies/berlin-service-bodies.json b/tests/serviceBodies/berlin-service-bodies.json new file mode 100644 index 00000000..4790a47b --- /dev/null +++ b/tests/serviceBodies/berlin-service-bodies.json @@ -0,0 +1,102 @@ +[ + { + "id": "1", + "parent_id": "0", + "name": "Deutschsprachige Region", + "description": "Zuständig für: Deutschland, Österreich", + "type": "RS", + "url": "http://www.narcotics-anonymous.de", + "helpline": "", + "world_id": "RG240" + }, + { + "id": "3", + "parent_id": "1", + "name": "Gebiet Berlin", + "description": "Zuständig für: Berlin und Umland", + "type": "AS", + "url": "http://www.na-berlin.de", + "helpline": "+49 30 12345678", + "world_id": "AR24004" + }, + { + "id": "4", + "parent_id": "1", + "name": "Gebiet Nord", + "description": "Zuständig für: Schleswig-Holstein, Hamburg, Bremen, Niedersachsen", + "type": "AS", + "url": "http://www.na-nord.de/", + "helpline": "", + "world_id": "AR24005" + }, + { + "id": "5", + "parent_id": "1", + "name": "Gebiet West", + "description": "Zuständig für: Nordrhein-Westfalen", + "type": "AS", + "url": "http://www.na-west.de", + "helpline": "", + "world_id": "AR24009" + }, + { + "id": "6", + "parent_id": "1", + "name": "Gebiet Mitte", + "description": "Zuständig für: Hessen, Rheinland-Pfalz", + "type": "AS", + "url": "https://narcotics-anonymous.de/mitte", + "helpline": "", + "world_id": "AR24010" + }, + { + "id": "7", + "parent_id": "1", + "name": "Gebiet Süd", + "description": "Zuständig für: Stuttgart, Bayern, Österreich", + "type": "AS", + "url": "https://narcotics-anonymous.de/sued", + "helpline": "0800 4453362", + "world_id": "AR24020" + }, + { + "id": "8", + "parent_id": "1", + "name": "Gebiet Südwest", + "description": "Zuständig für: Baden-Württemberg, Saarland, Rheinland-Pfalz", + "type": "AS", + "url": "https://narcotics-anonymous.de/suedwest", + "helpline": "", + "world_id": "AR24025" + }, + { + "id": "10", + "parent_id": "1", + "name": "Gebiet Österreich", + "description": "Zuständig für: Österreich", + "type": "AS", + "url": "http://www.narcotics-anonymous.at", + "helpline": "", + "world_id": "AR24002" + }, + { + "id": "11", + "parent_id": "1", + "name": "Gebiet Ost", + "description": "Zuständig für: Brandenburg, Mecklenburg-Vorpommern, Sachsen, Sachsen-Anhalt, Thüringen", + "type": "AS", + "url": "http://www.na-ost.de", + "helpline": "", + "world_id": "AR24011" + }, + { + "id": "12", + "parent_id": "1", + "name": "Online Gebiet", + "description": "", + "type": "AS", + "url": "https://narcotics-anonymous.de/online-gebiet/", + "helpline": "", + "world_id": "AR24030" + } +]