From cda76c89664ecaf1adabe5aeae2218bffff14d19 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Tue, 26 May 2026 23:19:11 +0200 Subject: [PATCH 01/34] Create AJAX mechanism --- includes/class-bread-bmlt.php | 97 ++++++++++++++++---- includes/class-bread.php | 6 +- public/class-bread-content-generator.php | 12 +-- public/class-bread-public.php | 96 +++++++++++++------- public/css/bread-public.css | 4 - public/js/bread-public.js | 67 +++++++------- public/js/fetch-jsonp.js | 111 +++++++++++++++++++++++ 7 files changed, 295 insertions(+), 98 deletions(-) delete mode 100644 public/css/bread-public.css create mode 100644 public/js/fetch-jsonp.js diff --git a/includes/class-bread-bmlt.php b/includes/class-bread-bmlt.php index 90ca05e1..90235be7 100644 --- a/includes/class-bread-bmlt.php +++ b/includes/class-bread-bmlt.php @@ -14,9 +14,9 @@ function __construct($bread) $this->bread = $bread; } - public function get_configured_root_server_request($url, $raw = false) + private function get_configured_root_server_request($url, $raw = false) { - $results = $this->bread->bmlt()->get($this->bread->getOption('root_server') . "/" . $url); + $results = $this->get($this->bread->getOption('root_server') . "/$url"); if ($raw) { return $results; } @@ -24,7 +24,7 @@ public function get_configured_root_server_request($url, $raw = false) } public function get_formats_by_language(string $lang) { - return $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetFormats&lang_enum=$lang"); + return $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats&lang_enum=$lang"); } /** * Undocumented function @@ -57,10 +57,10 @@ private function get(string $url): WP_Error | array */ public function get_all_meetings(): array { - $lang = $this->bread->bmlt()->get_bmlt_server_lang(); - $result = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults&data_field_key=weekday_tinyint,start_time,service_body_bigint,id_bigint,meeting_name,location_text,email_contact&sort_keys=meeting_name,service_body_bigint,weekday_tinyint,start_time"); + $lang = $this->get_bmlt_server_lang(); + $result = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults&data_field_key=weekday_tinyint,start_time,service_body_bigint,id_bigint,meeting_name,location_text,email_contact&sort_keys=meeting_name,service_body_bigint,weekday_tinyint,start_time"); - $unique_areas = $this->bread->bmlt()->get_areas(); + $unique_areas = $this->get_areas(); $all_meetings = array(); foreach ($result as $value) { foreach ($unique_areas as $unique_area) { @@ -79,7 +79,7 @@ public function get_all_meetings(): array } public function get_fieldkeys() { - $ret = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetFieldKeys"); + $ret = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFieldKeys"); return is_null($ret) ? array() : $ret; } private $standard_keys = array( @@ -108,7 +108,7 @@ public function get_fieldkeys() ); public function get_nonstandard_fieldkeys() { - $all_fks = $this->bread->bmlt()->get_fieldkeys(); + $all_fks = $this->get_fieldkeys(); $ret = array(); foreach ($all_fks as $fk) { if (!in_array($fk['key'], $this->standard_keys)) { @@ -131,7 +131,7 @@ public function get_areas() if (!empty($this->unique_areas)) { return $this->unique_areas; } - $result = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetServiceBodies"); + $result = $this->get_configured_root_server_request("client_interface/json/?switcher=GetServiceBodies"); $this->unique_areas = array(); foreach ($result as $value) { @@ -149,15 +149,80 @@ public function get_areas() return $this->unique_areas; } + public function generateMainQuery($json = 'json') + { + $sort_keys = 'weekday_tinyint,start_time,meeting_name'; + $get_used_formats = '&get_used_formats'; + $select_language = ''; + if ($this->bread->getOption('weekday_language') != $this->get_bmlt_server_lang()) { + $select_language = '&lang_enum=' . substr($this->bread->getOption('weekday_language'), 0, 2); + } + $services = $this->generateDefaultQuery(); + if (isset($_GET['custom_query'])) { + $services = $_GET['custom_query']; + } elseif ($this->bread->getOption('custom_query') !== '') { + $services = $this->bread->getOption('custom_query'); + } + if ($this->bread->getOption('used_format_1') == '') { + return "client_interface/$json/?switcher=GetSearchResults$services&sort_keys=$sort_keys$get_used_formats$select_language"; + } else { + return "client_interface/$json/?switcher=GetSearchResults$services&sort_keys=$sort_keys&get_used_formats&formats[]=" . $this->bread->getOption('used_format_1') . $select_language; + } + } + public function doMainQuery() + { + return $this->get_configured_root_server_request($this->generateMainQuery()); + } + public function generateExtraMeetingQuery($json = 'json') + { + if (empty($this->bread->getOption('extra_meetings'))) { + return null; + } + $sort_keys = 'weekday_tinyint,start_time,meeting_name'; + $get_used_formats = '&get_used_formats'; + $select_language = ''; + $extras = ""; + foreach ((array)$this->bread->getOption('extra_meetings') as $value) { + $data = array(" [", "]"); + $value = str_replace($data, "", $value); + $extras .= "&meeting_ids[]=" . $value; + } + + return "client_interface/$json/?switcher=GetSearchResults&sort_keys=" . $sort_keys . "" . $extras . "" . $get_used_formats . $select_language; + } + public function doExtraMeetingQuery() + { + return $this->get_configured_root_server_request($this->generateExtraMeetingQuery()); + } + public function generateAdditionalListQuery($json = 'json') + { + if (!empty($this->options['additional_list_custom_query'])) { + $sort_order = $this->bread->getOption('additional_list_sort_order'); + if ($sort_order == 'same') { + $sort_order = 'weekday_tinyint,start_time'; + } + $services = $this->bread->getOption('additional_list_custom_query'); + return "client_interface/$json/?switcher=GetSearchResults$services&sort_keys=$sort_order"; + } + return null; + } + public function doAdditionalListQuery() + { + $url = $this->generateAdditionalListQuery(); + if ($url == null) { + return []; + } + return $this->get_configured_root_server_request($url); + } /** * Gets the default language of the root server. * * @return string 2 character string ISO standard for the language. */ - public function get_bmlt_server_lang(): string + private function get_bmlt_server_lang(): string { if ($this->bmlt_server_lang == '') { - $result = $this->bread->bmlt()->testRootServer(); + $result = $this->testRootServer(); if (!($result && is_array($result) && is_array($result[0]))) { return 'en'; } @@ -174,9 +239,9 @@ public function get_bmlt_server_lang(): string public function testRootServer(string $override_root_server = null): array|bool { if ($override_root_server == null) { - $results = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetServerInfo", true); + $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetServerInfo", true); } else { - $results = $this->bread->bmlt()->get($override_root_server . "/client_interface/json/?switcher=GetServerInfo", true); + $results = $this->get($override_root_server . "client_interface/json/?switcher=GetServerInfo", true); } if ($results instanceof WP_Error) { $this->connection_error = $results->get_error_message(); @@ -199,8 +264,8 @@ public function testRootServer(string $override_root_server = null): array|bool public function getFormatsForSelect(bool $all = false): array { if ($all) { - $results = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetFormats"); - $this->bread->bmlt()->sortBySubkey($results, 'key_string'); + $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats"); + $this->sortBySubkey($results, 'key_string'); return $results; } $area_data = explode(',', $this->bread->getOption('service_body_1')); @@ -215,7 +280,7 @@ public function getFormatsForSelect(bool $all = false): array } else { $queryUrl = "client_interface/json/?switcher=GetSearchResults$services&get_formats_only"; } - $results = $this->bread->bmlt()->get_configured_root_server_request($queryUrl); + $results = $this->get_configured_root_server_request($queryUrl); $results = empty($service_body_id) ? $results : $results['formats']; $this->sortBySubkey($results, 'key_string'); return $results; diff --git a/includes/class-bread.php b/includes/class-bread.php index 94da3290..0ddf8250 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -305,7 +305,7 @@ public function &getConfigurationForSettingId($current_setting) if ($current_setting < 1) { $current_setting = is_admin() ? 1 : $this->requested_setting; } - if (is_array($this->allSettings[$current_setting])) { + if ($this->allSettings && !empty($this->allSettings[$current_setting]) && is_array($this->allSettings[$current_setting])) { $this->options = $this->allSettings[$current_setting]; } else { $this->optionsName = $this->generateOptionName($current_setting); @@ -529,6 +529,8 @@ private function define_public_hooks() $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles'); $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts'); $this->loader->add_action('plugins_loaded', $plugin_public, 'bmlt_meeting_list'); + $this->loader->add_action('wp_ajax_bread_preload_action', $plugin_public, 'bread_preload'); + $this->loader->add_action('wp_ajax_nopriv_bread_preload_action', $plugin_public, 'bread_preload'); } /** @@ -721,7 +723,7 @@ private function upgrade_settings(): void || $this->options['meeting_sort'] === 'weekday_city' || $this->options['meeting_sort'] === 'weekday_county' || $this->options['meeting_sort'] === 'day')) { - $this->options['weekday_language'] = $this->bmlt()->get_bmlt_server_lang(); + $this->options['weekday_language'] = 'en'; } if ($this->options['page_fold'] == 'half') { if ($this->options['page_size'] == 'A5') { diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php index 7dc8d125..6d6596b3 100644 --- a/public/class-bread-content-generator.php +++ b/public/class-bread-content-generator.php @@ -580,17 +580,7 @@ function write_additional_meetinglist() * If we are selecting the meetings in the second list based on some format, we don't need another BMLT query. */ if (empty($this->options['additional_list_format_key'])) { - $additional_list_query = true; - $sort_order = $this->options['additional_list_sort_order']; - if ($sort_order == 'same') { - $sort_order = 'weekday_tinyint,start_time'; - } - $services = $this->bread->bmlt()->generateDefaultQuery(); - if (!empty($this->options['additional_list_custom_query'])) { - $services = $this->options['additional_list_custom_query']; - } - $additional_list_query = "client_interface/json/?switcher=GetSearchResults$services&sort_keys=$sort_order"; - $additional_meetinglist_result = $this->bread->bmlt()->get_configured_root_server_request($additional_list_query); + $additional_meetinglist_result = $this->bread->bmlt()->doAdditionalListQuery(); $this->adjust_timezone($additional_meetinglist_result, $this->target_timezone); } if ($additional_list_query || $this->options['weekday_language'] != $this->options['additional_list_language']) { diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 4f3e9cd6..a990e014 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -67,6 +67,10 @@ public function __construct($plugin_name, $version, $bread) $this->version = $version; $this->bread = $bread; $this->options = $bread->getOptions(); + add_shortcode('bread_button', array( + &$this, + "doBreadButton" + )); } /** @@ -76,7 +80,6 @@ public function __construct($plugin_name, $version, $bread) */ public function enqueue_styles() { - wp_enqueue_style($this->plugin_name, plugin_dir_url(__FILE__) . 'css/bread-public.css', array(), $this->version, 'all'); } /** @@ -86,9 +89,64 @@ public function enqueue_styles() */ public function enqueue_scripts() { - wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/bread-public.js', array('jquery'), $this->version, false); + if (($current = $this->doPreloading()) > 0) { + wp_enqueue_script('fetch-jsonp', plugin_dir_url(__FILE__) . 'js/fetch-jsonp.js', array(), $this->version, true); + wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/bread-public.js', array('jquery','fetch-jsonp'), $this->version, true); + wp_localize_script($this->plugin_name, 'bread_ajax_obj', [ + 'ajaxurl' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('bread-ajax-nonce'), + 'config' => $this->generatePreloadConfiguration($current) + ]); + } + } + /** + * Check if the shortcode is being used on this page. + * + * @return boolean + */ + private function doPreloading(): int + { + $post_to_check = get_post(get_the_ID()); + $post_content = $post_to_check->post_content ?? ''; + $tags = ['bread_button']; + preg_match_all('/' . get_shortcode_regex($tags) . '/', $post_content, $matches, PREG_SET_ORDER); + if (empty($matches)) { + return -1; + } + foreach ($matches as $shortcode) { + switch ($shortcode[2]) { + case 'bread_button': + $atts = shortcode_parse_atts($shortcode[3]); + return intval($atts['current_meeting_list']); + } + } + return -1; + } + private function generatePreloadConfiguration(int $id): array + { + $options = $this->bread->getConfigurationForSettingId($id); + return [ + 'root_server' => $options['root_server'], + 'main_query' => $this->bread->bmlt()->generateMainQuery('jsonp'), + 'extra_meetings_query' => $this->bread->bmlt()->generateExtraMeetingQuery('jsonp'), + 'additional_list_query' => $this->bread->bmlt()->generateAdditionalListQuery('jsonp'), + 'weekday_language' => $options['weekday_language'], + 'additional_list_language' => $options['additional_list_language'], + ]; + } + public function doBreadButton($atts) + { + $label = $atts['label'] ?? 'Generate PDF'; + return ''; + } + public function bread_preload() + { + if (! wp_verify_nonce($_POST['nonce'], 'bread-ajax-nonce')) { + die; + } + $preload = json_decode(stripslashes($_POST['preload'])); + wp_send_json_success('Success!'); } - public function bmlt_meeting_list($atts = null, $content = null) { if (!$this->bread->generatingMeetingList()) { @@ -175,23 +233,7 @@ public function bmlt_meeting_list($atts = null, $content = null) ) { $this->drawLinesSeperatingColumns($mode, $mpdf_init_options['format'], $default_font); } - $sort_keys = 'weekday_tinyint,start_time,meeting_name'; - $get_used_formats = '&get_used_formats'; - $select_language = ''; - if ($this->options['weekday_language'] != $this->bread->bmlt()->get_bmlt_server_lang()) { - $select_language = '&lang_enum=' . $this->getSingleLanguage($this->options['weekday_language']); - } - $services = $this->bread->bmlt()->generateDefaultQuery(); - if (isset($_GET['custom_query'])) { - $services = $_GET['custom_query']; - } elseif ($this->options['custom_query'] !== '') { - $services = $this->options['custom_query']; - } - if ($this->options['used_format_1'] == '') { - $result = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=$sort_keys$get_used_formats$select_language"); - } elseif ($this->options['used_format_1'] != '') { - $result = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=$sort_keys&get_used_formats&formats[]=" . $this->options['used_format_1'] . $select_language); - } + $result = $this->bread->bmlt()->doMainQuery(); if ($result == null) { echo "
@@ -23,49 +31,30 @@ function Bread_bmlt_server_setup_page_render(Bread_AdminDisplay $breadAdmin)

- -

- isConnected()) { - echo wp_kses($breadAdmin->getServerVersion(), 'post'); - echo ''; - if ($bread->getOption('sslverify') == '1') { ?> -

- - - emptyOption('root_server')) { - echo "

".esc_html(__('ERROR: Please enter a BMLT Server', 'bread')).""; - echo ''; - if ($bread->getOption('sslverify') == '1') { ?> -

- - -

- -

bmlt()->connection_error); ?> + + -

- - -

-

- getOption('sslverify') ? 'checked' : ''; ?> /> - -

- + +

-
    select_service_bodies(); ?>
+

+ + +

- getOption('recurse_service_bodies') == 1 ? 'checked' : '') ?> /> + getOption('recurse_service_bodies') == 1 ? 'checked' : '') ?> />
@@ -98,20 +87,12 @@ function Bread_bmlt_server_setup_page_render(Bread_AdminDisplay $breadAdmin)

(?)

isConnected() && $bread->getOption('extra_meetings_enabled') == 1) {?> -

- emptyOption('extra_meetings_enabled') && $bread->getOption('extra_meetings_enabled') == 1 ? 'checked' : '') ?> /> + emptyOption('extra_meetings_enabled') && $bread->getOption('extra_meetings_enabled') == 1 ? 'checked' : '') ?> />
diff --git a/admin/partials/_meetings_setup.php b/admin/partials/_meetings_setup.php index 15223b89..8099750a 100644 --- a/admin/partials/_meetings_setup.php +++ b/admin/partials/_meetings_setup.php @@ -109,16 +109,6 @@ function Bread_meetings_setup_page_render(Bread_AdminDisplay $breadAdmin) - bmlt()->get_nonstandard_fieldkeys(); - foreach ($fks as $fk) { - $selected = ''; - if ($fk['key'] == $bread->getOption('main_grouping')) { - $selected = 'selected'; - } - echo ''; - } - ?>

@@ -282,22 +263,6 @@ function Bread_meetings_setup_page_render(Bread_AdminDisplay $breadAdmin)
@@ -312,19 +277,8 @@ function Bread_meetings_setup_page_render(Bread_AdminDisplay $breadAdmin)

diff --git a/admin/partials/bread-admin-display.php b/admin/partials/bread-admin-display.php index e927d3c9..32706c3a 100644 --- a/admin/partials/bread-admin-display.php +++ b/admin/partials/bread-admin-display.php @@ -61,43 +61,6 @@ public function getServerVersion() { return $this->server_version; } - public function select_service_bodies() - { - for ($i = 1; $i <= 5; $i++) { ?> -

  • - -
  • - - unique_areas as $area) { - $area_data = explode(',', $area); - $area_name = $this->bread->arraySafeGet($area_data); - $area_id = $this->bread->arraySafeGet($area_data, 1); - $area_parent = $this->bread->arraySafeGet($area_data, 2); - $area_parent_name = $this->bread->arraySafeGet($area_data, 3); - $descr = $area_name . " (" . $area_id . ") " . $area_parent_name . " (" . $area_parent . ")"; - $selected = ''; - $sb = esc_html($this->bread->getOption("service_body_$i")); - $area_selected = explode(',', $sb); - if ($this->bread->arraySafeGet($area_selected) != "Not Used" && $area_id == $this->bread->arraySafeGet($area_selected, 1)) { - $selected = 'selected'; - } ?> - diff --git a/bmlt-meeting-list.php b/bmlt-meeting-list.php index 3be9b1ae..ef93c6c5 100644 --- a/bmlt-meeting-list.php +++ b/bmlt-meeting-list.php @@ -11,7 +11,7 @@ * Plugin Name: Bread * Plugin URI: https://bmlt.app * Description: Maintains and generates PDF Meeting Lists from BMLT. - * Version: 2.9.15 + * Version: 2.10.0 * Author: bmlt-enabled * Author URI: https://bmlt.app/ * License: GPL-2.0+ @@ -30,7 +30,7 @@ * Start at version 2.8.0 and use SemVer - https://semver.org * Rename this for your plugin and update it as you release new versions. */ -define('BREAD_VERSION', '2.9.13'); +define('BREAD_VERSION', '2.10.0'); /** * The code that runs during plugin activation. diff --git a/includes/class-bread-bmlt.php b/includes/class-bread-bmlt.php index 961a1b9f..6c9acb2c 100644 --- a/includes/class-bread-bmlt.php +++ b/includes/class-bread-bmlt.php @@ -28,7 +28,9 @@ private function get_configured_root_server_request($url, $raw = false) } public function get_formats_by_language(string $lang) { - if ($this->preloaded !== null) return $this->preloaded['allFormats'][substr($lang, 0, 2)]; + if ($this->preloaded !== null) { + return $this->preloaded['allFormats'][substr($lang, 0, 2)]; + } return $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats&lang_enum=$lang"); } /** @@ -55,77 +57,6 @@ private function get(string $url): WP_Error | array } return wp_remote_get($url, $args); } - /** - * Gets all the meetins in the root server as an array with id=>string. Used to select extra meetings. - * - * @return array - */ - public function get_all_meetings(): array - { - $lang = $this->get_bmlt_server_lang(); - $result = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults&data_field_key=weekday_tinyint,start_time,service_body_bigint,id_bigint,meeting_name,location_text,email_contact&sort_keys=meeting_name,service_body_bigint,weekday_tinyint,start_time"); - - $unique_areas = $this->get_areas(); - $all_meetings = array(); - foreach ($result as $value) { - foreach ($unique_areas as $unique_area) { - $area_data = explode(',', $unique_area); - $area_id = $this->bread->arraySafeGet($area_data, 1); - if ($area_id === $value['service_body_bigint']) { - $area_name = $this->bread->arraySafeGet($area_data); - } - } - - $value['start_time'] = gmdate("g:iA", strtotime($value['start_time'])); - $all_meetings[$value['id_bigint']] = wp_strip_all_tags($value['meeting_name'] . ' - ' . $this->bread->getday($value['weekday_tinyint'], true, $lang) . ' ' . $value['start_time'] . ' in ' . $area_name . ' at ' . $value['location_text']); - } - - return $all_meetings; - } - public function get_fieldkeys() - { - $ret = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFieldKeys"); - return is_null($ret) ? array() : $ret; - } - private $standard_keys = array( - "id_bigint", - "worldid_mixed", - "service_body_bigint", - "weekday_tinyint", - "start_time", - "duration_time", - "formats", - "lang_enum", - "longitude", - "latitude", - "meeting_name" . "location_text", - "location_info", - "location_street", - "location_city_subsection", - "location_neighborhood", - "location_municipality", - "location_sub_province", - "location_province", - "location_postal_code_1", - "location_nation", - "comments", - "zone" - ); - public function get_nonstandard_fieldkeys() - { - $all_fks = $this->get_fieldkeys(); - $ret = array(); - foreach ($all_fks as $fk) { - if (!in_array($fk['key'], $this->standard_keys)) { - $ret[] = $fk; - } - } - $ext_fields = apply_filters("Bread_Enrich_Meeting_Data", array(), array()); - foreach ($ext_fields as $key => $value) { - $ret[] = array("key" => $key, "description" => $key); - } - return $ret; - } /** * Generates a list of service bodies to be used in the admin UI's drop downs. * @@ -177,7 +108,9 @@ public function generateMainQuery($json = 'json') } public function doMainQuery() { - if ($this->preloaded !== null) return $this->preloaded['mainResults']; + if ($this->preloaded !== null) { + return $this->preloaded['mainResults']; + } return $this->get_configured_root_server_request($this->generateMainQuery()); } public function generateExtraMeetingQuery($json = 'json') @@ -199,7 +132,9 @@ public function generateExtraMeetingQuery($json = 'json') } public function doExtraMeetingQuery() { - if ($this->preloaded !== null) return $this->preloaded['extraMeetings']; + if ($this->preloaded !== null) { + return $this->preloaded['extraMeetings']; + } return $this->get_configured_root_server_request($this->generateExtraMeetingQuery()); } public function generateAdditionalListQuery($json = 'json') @@ -220,7 +155,9 @@ public function doAdditionalListQuery() if ($url == null) { return []; } - if ($this->preloaded !== null) return $this->preloaded['additionalListMeetings']; + if ($this->preloaded !== null) { + return $this->preloaded['additionalListMeetings']; + } return $this->get_configured_root_server_request($url); } /** @@ -264,36 +201,6 @@ public function testRootServer(string $override_root_server = null): array|bool return json_decode(wp_remote_retrieve_body($results), true); } - /** - * This is used from the AdminUI, not to generate the meeting list. - * - * @param boolean $all should we get all the formats defined in the root server, or only those used in the service body. This respects the option recurse_service_bodies but only the first service body. - * @return array the formats - */ - public function getFormatsForSelect(bool $all = false): array - { - if ($all) { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats"); - $this->sortBySubkey($results, 'key_string'); - return $results; - } - $area_data = explode(',', $this->bread->getOption('service_body_1')); - $service_body_id = $this->bread->arraySafeGet($area_data, 1); - if ($this->bread->getOption('recurse_service_bodies') == 1) { - $services = '&recursive=1&services[]=' . $service_body_id; - } else { - $services = '&services[]=' . $service_body_id; - } - if (empty($service_body_id)) { - $queryUrl = "client_interface/json/?switcher=GetFormats"; - } else { - $queryUrl = "client_interface/json/?switcher=GetSearchResults$services&get_formats_only"; - } - $results = $this->get_configured_root_server_request($queryUrl); - $results = empty($service_body_id) ? $results : $results['formats']; - $this->sortBySubkey($results, 'key_string'); - return $results; - } /** * Convenient front end to array_multisort. Sorts the array in place. * @@ -325,27 +232,14 @@ public function generateDefaultQuery(): string // only the name of the service body. So we cache the value so it only // needs to be called once. if (!$this->default_query) { - $this->default_query = $this->addServiceBody('service_body_1'); - $this->default_query .= $this->addServiceBody('service_body_2'); - $this->default_query .= $this->addServiceBody('service_body_3'); - $this->default_query .= $this->addServiceBody('service_body_4'); - $this->default_query .= $this->addServiceBody('service_body_5'); - } - return $this->default_query; - } - private function addServiceBody($service_body_name) - { - if (false === ($this->bread->getOption($service_body_name) == 'Not Used')) { - $area_data = explode(',', $this->bread->getOption($service_body_name)); - $area = $this->bread->arraySafeGet($area_data); - $this->bread->setOption($service_body_name, ($area == 'NOT USED' ? '' : $area)); - $service_body_id = $this->bread->arraySafeGet($area_data, 1); - if ($this->bread->getOption('recurse_service_bodies') == 1) { - return '&recursive=1&services[]=' . $service_body_id; - } else { - return '&services[]=' . $service_body_id; + $this->default_query = ($this->bread->getOption('recurse_service_bodies') == 1) ? '&recursive=1' : ''; + for ($i=0; $ibread->getOption('service_bodies')); $i++) { + $area_data = explode(',', $this->bread->getOption('service_bodies')[$i]); + $service_body_id = $this->bread->arraySafeGet($area_data, 1); + $this->default_query .= '&services[]=' . $service_body_id; } } + return $this->default_query; } public function parse_field($text) { diff --git a/includes/class-bread.php b/includes/class-bread.php index cd80cc11..0903908f 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -780,7 +780,7 @@ private function upgrade_settings(): void $this->renamed_option('asm_language', 'additional_list_language'); $this->renamed_option('asm_custom_query', 'additional_list_custom_query'); $this->renamed_option('asm_template_content', 'additional_list_template_content'); - if (!isset($this->options['bread_version']) || $this->options['bread_version'] < '2.8') { + if ($this->versionLessThan('2.8')) { if (($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') && trim($this->options['last_page_content']) !== '') { $this->options['custom_section_content'] = $this->options['last_page_content']; $this->options['custom_section_font_size'] = $this->options['last_page_font_size']; @@ -790,8 +790,51 @@ private function upgrade_settings(): void unset($this->options['last_page_font_size']); } } + if ($this->versionLessThan('2.10')) { + $this->options['service_bodies'] = []; + if (!empty($this->options['service_body_1']) && $this->options['service_body_1'] != 'Not Used') { + $this->options['service_bodies'][] = $this->options['service_body_1']; + } + if (!empty($this->options['service_body_2']) && $this->options['service_body_2'] != 'Not Used') { + $this->options['service_bodies'][] = $this->options['service_body_2']; + } + if (!empty($this->options['service_body_3']) && $this->options['service_body_3'] != 'Not Used') { + $this->options['service_bodies'][] = $this->options['service_body_3']; + } + if (!empty($this->options['service_body_4']) && $this->options['service_body_4'] != 'Not Used') { + $this->options['service_bodies'][] = $this->options['service_body_4']; + } + if (!empty($this->options['service_body_5']) && $this->options['service_body_5'] != 'Not Used') { + $this->options['service_bodies'][] = $this->options['service_body_5']; + } + unset($this->options['service_body_1']); + unset($this->options['service_body_2']); + unset($this->options['service_body_3']); + unset($this->options['service_body_4']); + unset($this->options['service_body_5']); + } $this->options['bread_version'] = BREAD_VERSION; } + private function versionLessThan($version): bool + { + if (empty($this->options['bread_version'])) { + return true; + } + $current = explode('.', $this->options['bread_version']); + $test = explode('.', $version); + for ($i=0; $i= count($test)) { + return false; + } + if (intval($current[$i]) > intval($test[$i])) { + return false; + } + if (intval($current[$i]) < intval($test[$i])) { + return true; + } + } + return false; + } private function renamed_option(string $old, string $new) { if (!empty($this->options[$old])) { diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php index 6d6596b3..3c597224 100644 --- a/public/class-bread-content-generator.php +++ b/public/class-bread-content-generator.php @@ -122,7 +122,7 @@ function __construct(Mpdf $mpdf, Bread $bread, array $result_meetings, Bread_For '

    [page_break]

    ' => '', '[page_break]' => '', '' => '', - "[area]" => strtoupper($this->options['service_body_1']), + "[area]" => strtoupper($this->options['service_bodies'][0]), '
    [new_column]
    ' => '', '

    [new_column]

    ' => '', '[new_column]' => '', @@ -133,12 +133,12 @@ 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]" => strtoupper($this->options['service_body_1']), - "[service_body_1]" => strtoupper($this->options['service_body_1']), - "[service_body_2]" => strtoupper($this->options['service_body_2']), - "[service_body_3]" => strtoupper($this->options['service_body_3']), - "[service_body_4]" => strtoupper($this->options['service_body_4']), - "[service_body_5]" => strtoupper($this->options['service_body_5']), + "[service_body]" => strtoupper($this->options['service_bodies'][0]), + "[service_body_1]" => strtoupper($this->options['service_bodies'][0]), + "[service_body_2]" => (count($this->options['service_bodies']) > 1) ? strtoupper($this->options['service_bodies'][1]) : 'Not Used', + "[service_body_3]" => (count($this->options['service_bodies']) > 2) ? strtoupper($this->options['service_bodies'][2]) : 'Not Used', + "[service_body_4]" => (count($this->options['service_bodies']) > 3) ? strtoupper($this->options['service_bodies'][3]) : 'Not Used', + "[service_body_5]" => (count($this->options['service_bodies']) > 4) ? strtoupper($this->options['service_bodies'][4]) : 'Not Used', ); $this->shortcodes = apply_filters("Bread_Section_Shortcodes", $this->shortcodes, $this->bread->bmlt()->get_areas(), $formatsManager->getFormatsUsed()); diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 4e06b87b..cf9c4e4a 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -163,7 +163,7 @@ public function bmlt_meeting_list() echo '

    bread Error: BMLT Server missing.

    Please go to Settings -> bread and verify BMLT Server

    '; exit; } - if ($this->options['service_body_1'] == 'Not Used' && true === ($this->options['custom_query'] == '')) { + if (count($this->options['service_bodies']) == 0 && true === ($this->options['custom_query'] == '')) { echo '

    bread Error: Service Body 1 missing from configuration.

    Please go to Settings -> bread and verify Service Body


    Contact the bread administrator and report this problem!

    '; exit; } @@ -301,7 +301,7 @@ public function bmlt_meeting_list() $transient_key = $this->bread->get_TransientKey($this->bread->getRequestedSetting()); set_transient($transient_key, $content, intval($this->options['cache_time']) * HOUR_IN_SECONDS); } - $FilePath = apply_filters("Bread_Download_Name", $this->get_FilePath(), $this->options['service_body_1'], $this->bread->getSettingName($this->bread->getRequestedSetting())); + $FilePath = apply_filters("Bread_Download_Name", $this->get_FilePath(), $this->options['service_bodies'][0], $this->bread->getSettingName($this->bread->getRequestedSetting())); $this->mpdf->Output($FilePath, 'I'); } foreach ($import_streams as $FilePath => $stream) { From 49317099f0e86cee604d53968f9cec085e757633 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sat, 30 May 2026 17:12:12 +0200 Subject: [PATCH 04/34] live extra-meetings --- .github/workflows/pull-requests.yml | 2 +- admin/js/bmlt_meeting_list.js | 1026 ++++++++++++++----------- admin/partials/_bmlt_server_setup.php | 7 +- 3 files changed, 565 insertions(+), 470 deletions(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 20f8d809..32cf7166 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -32,7 +32,7 @@ jobs: unzip "${BUILD_DIR}/${ZIP_FILENAME}" -d "./bread" - name: Run plugin check - uses: wordpress/plugin-check-action@v1 + uses: wordpress/plugin-check-action@v1.0.6 with: build-dir: "./bread" exclude-directories: 'vendor' diff --git a/admin/js/bmlt_meeting_list.js b/admin/js/bmlt_meeting_list.js index a0134626..0b39bd26 100644 --- a/admin/js/bmlt_meeting_list.js +++ b/admin/js/bmlt_meeting_list.js @@ -1,506 +1,600 @@ - -jQuery(document).ready( - function($) { - $(".connecting").hide(); - $(".saving").hide(); - $(".bmlt-accordion").accordion( - { - heightStyle: "content", - active: false, - collapsible: true - } - ); - $(".bmlt_color").spectrum({preferredFormat: 'hex', showInput: true, showPalette: false}); - $("#col_color").on("click", () => $("#triggerSet").spectrum("set", this.val())); - $("#bmlt_meeting_list_options").on( - "keypress", function(event) { - if(event.which == 13 && !event.shiftKey) { - event.preventDefault(); - return false; - } - } - ); - $(".gears-working").click( - function(e) { - $(".saving").show(); - } - ); - $('input[name="submit_import_file"]').on( - 'click', function(e) { - e.preventDefault(); - var import_file_val = $('input[name=import_file]').val(); - if(import_file_val == false) { - $('#nofileModal').dialog('open'); - } else { - $('#basicModal').dialog('open'); - } - } - ); - $("#nofileModal").dialog( - { - autoOpen: false, - modal: true, - buttons: { - Ok: function() { - $(this).dialog("close"); - } - } - } - ); - $('#basicModal').dialog({ - autoOpen: false, - width: 'auto', - title: "Are you sure?", - modal: true, - buttons: { - "Confirm": function(e) { - $(this).dialog('close'); - $(".saving").show(); - $('#form_import_file').submit(); - }, - "Cancel": function() { - $(this).dialog('close'); - } - } - }); - $('.my-tooltip').each(function(i, e) { - $(e).tooltipster( - { - contentAsHTML: true, - theme: 'tooltipster-noir', - trigger: 'click' - } - ) - }); - $("#meeting-list-tabs").tabs( - { - active: 0 - } - ); - $('#meeting-list-tabs').tabs().addClass('ui-tabs-vertical ui-helper-clearfix'); - $("#container").removeClass('hide'); - var meeting_sort_val = $("#meeting_sort").val(); - $('.borough_by_suffix').hide(); - $('.county_by_suffix').hide(); - if(meeting_sort_val === 'borough_county') { - $('.borough_by_suffix').show(); - $('.county_by_suffix').show(); - } else if(meeting_sort_val === 'borough') { - $('.borough_by_suffix').show(); - } else if(meeting_sort_val === 'county') { - $('.county_by_suffix').show(); - } - $('.neighborhood_by_suffix').hide(); - $('.city_by_suffix').hide(); - if(meeting_sort_val === 'neighborhood_city') { - $('.neighborhood_by_suffix').show(); - $('.city_by_suffix').show(); - } - var user_defined_sub = false; - $('.user_defined_headings').hide(); - if(meeting_sort_val === 'user_defined') { - $('.user_defined_headings').show(); - if($("#subgrouping").val() != '') { - user_defined_sub = true; - } - } - if(meeting_sort_val == 'weekday_area' - || meeting_sort_val == 'weekday_city' - || meeting_sort_val == 'weekday_county' - || meeting_sort_val == 'state' - || user_defined_sub - ) { - $('.show_subheader').show(); - } else { - $('.show_subheader').hide(); - } - $("#suppress_heading").click( - function() { - var val = $("#suppress_heading:checked").val(); - if(val == 1) { - $("#header_options_div").hide(); - } else { - $("#header_options_div").show(); - } - } - ); - $("#meeting_sort").change( - function() { - var meeting_sort_val = $("#meeting_sort").val(); - $('.borough_by_suffix').hide(); - $('.county_by_suffix').hide(); - $('.neighborhood_by_suffix').hide(); - $('.city_by_suffix').hide(); - if(meeting_sort_val === 'borough_county') { - $('.borough_by_suffix').show(); - $('.county_by_suffix').show(); - } else if(meeting_sort_val === 'borough') { - $('.borough_by_suffix').show(); - } else if(meeting_sort_val === 'county') { - $('.county_by_suffix').show(); - } - if(meeting_sort_val === 'neighborhood_city') { - $('.neighborhood_by_suffix').show(); - $('.city_by_suffix').show(); - } - var user_defined_sub = false; - $('.user_defined_headings').hide(); - if(meeting_sort_val === 'user_defined') { - $('.user_defined_headings').show(); - if($("#subgrouping").val() != '') { - user_defined_sub = true; - } - } - if(meeting_sort_val == 'weekday_area' - || meeting_sort_val == 'weekday_city' - || meeting_sort_val == 'weekday_county' - || meeting_sort_val == 'state' - || user_defined_sub - ) { - $('.show_subheader').show(); - } else { - $('.show_subheader').hide(); - } - } - ); - $("#subgrouping").click( - function() { - $('.user_defined_headings').hide(); - if($("#meeting_sort").val() === 'user_defined') { - $('.user_defined_headings').show(); - if($("#subgrouping").val() != '') { - $('.show_subheader').show(); - } else { - $('.show_subheader').hide(); - } - } - } - ); - function calcTimeDisplay(hasEndTime) { - let clock = $('input[name=time_clock]:checked').val(); - let removeSpaces = $('input[name=remove_space]:checked').val() - let startTime = '8:00 PM'; - let endTime = '9:00 PM'; - if(clock == '24') { - startTime = '20:00'; - endTime = '21:00'; - } else if(clock == '24fr') { - startTime = '20h00'; - endTime = '21h00'; - } - if(hasEndTime == 2) { - startTime += ' - ' + endTime; - } - if(hasEndTime == 3) { - startTime = clock == '12' ? '8 - 9' : ''; - } - if(removeSpaces != "0") { - startTime = startTime.replaceAll(' ', ''); - } - return startTime; - } - function setTimeOptionText() { - $('label[for=option1]').html(calcTimeDisplay(1)); - $('label[for=option2]').html(calcTimeDisplay(2)); - $('label[for=option3]').html(calcTimeDisplay(3)); - if($('input[name=time_clock]:checked').val() != '12') { - if($('input[name=time_clock]:checked').val() == 3) - $('#option2').prop('checked', true); - $('#option3').hide(); - } else $('#option3').show(); - }; - setTimeOptionText(); - $('.recalcTimeLabel').on('click', setTimeOptionText); - function bookletControlsShowHide() { - $('#half').prop('checked') && $('#landscape').prop("checked", true); - $('#full').prop('checked') && $('#portrait').prop("checked", true); - $('.booklet').show(); - $('.single-page').hide(); - $('#half').prop('checked') && $('#A6').hide(); - $('#half').prop('checked') && $('#A6').hide(); - $('#half').prop('checked') && $("label[for=A6]").hide(); - } - function singlePageControlsShowHide() { - $('.booklet').hide(); - $('.single-page').show(); - } - $('.single-page-check').on('click', singlePageControlsShowHide); - $('.booklet-check').on('click', bookletControlsShowHide); - $('input[name=page_fold]:checked').hasClass('booklet-check') && bookletControlsShowHide(); - $('input[name=page_fold]:checked').hasClass('single-page-check') && singlePageControlsShowHide(); - $("#service_bodies").select2(); - $('#extra_meetings').select2( - { - placeholder: "Select extra meetings", - } - ); - $("#bread_author_select").select2( - { - placeholder: "Select authors", - } - ); - $('.bread-select').on("change", function (e) { - const self = $(this); - const field = self.parent().find('.select2-search--inline'); - field.css('display', (self.val().length == 0 ? 'contents' : 'block')); - field.find('input').css('width', (self.val().length == 0 ? '100%' : 'auto')); - }); - $("#meeting-list-tabs").tabs( - { - active: 0 - } - ); - $("#meeting-list-tabs-wrapper").removeClass('hide'); - // Define friendly index name - var index = 'key'; - // Define friendly data store name - var dataStore = window.sessionStorage; - // Start magic! - try { - // getter: Fetch previous value - var oldIndex = dataStore.getItem(index); - } catch(e) { - // getter: Always default to first tab in error state - var oldIndex = 0; - } - $('#meeting-list-tabs').tabs( - { - // The zero-based index of the panel that is active (open) - active: oldIndex, - // Triggered after a tab has been activated - activate: function(event, ui) { - // Get future value - var newIndex = ui.newTab.parent().children().index(ui.newTab); - // Set future value - dataStore.setItem(index, newIndex) - } - } - ); - var aggregator = "https://aggregator.bmltenabled.org/main_server"; - $(window).on( - "load", function() { - if($('#use_aggregator').is(':checked')) { - $("#root_server").prop("readonly", true); - } - } - ); - $('#use_aggregator').click( - function() { - if($(this).is(':checked')) { - $("#root_server").val(aggregator); - $("#root_server").prop("readonly", true); - } else { - $("#root_server").val(""); - $("#root_server").prop("readonly", false); - } - } - ); - var rootServerValue = $('#root_server').val(); - if(~rootServerValue.indexOf(aggregator)) { - $("#use_aggregator").prop("checked", true); - } - $(".service_body_select").select2({ - width: "40%" +jQuery(document).ready(function ($) { + $(".connecting").hide(); + $(".saving").hide(); + $(".bmlt-accordion").accordion({ + heightStyle: "content", + active: false, + collapsible: true, + }); + $(".bmlt_color").spectrum({ + preferredFormat: "hex", + showInput: true, + showPalette: false, + }); + $("#col_color").on("click", () => + $("#triggerSet").spectrum("set", this.val()), + ); + $("#bmlt_meeting_list_options").on("keypress", function (event) { + if (event.which == 13 && !event.shiftKey) { + event.preventDefault(); + return false; + } + }); + $(".gears-working").click(function (e) { + $(".saving").show(); + }); + $('input[name="submit_import_file"]').on("click", function (e) { + e.preventDefault(); + var import_file_val = $("input[name=import_file]").val(); + if (import_file_val == false) { + $("#nofileModal").dialog("open"); + } else { + $("#basicModal").dialog("open"); + } + }); + $("#nofileModal").dialog({ + autoOpen: false, + modal: true, + buttons: { + Ok: function () { + $(this).dialog("close"); + }, + }, + }); + $("#basicModal").dialog({ + autoOpen: false, + width: "auto", + title: "Are you sure?", + modal: true, + buttons: { + Confirm: function (e) { + $(this).dialog("close"); + $(".saving").show(); + $("#form_import_file").submit(); + }, + Cancel: function () { + $(this).dialog("close"); + }, + }, + }); + $(".my-tooltip").each(function (i, e) { + $(e).tooltipster({ + contentAsHTML: true, + theme: "tooltipster-noir", + trigger: "click", + }); + }); + $("#meeting-list-tabs").tabs({ + active: 0, + }); + $("#meeting-list-tabs") + .tabs() + .addClass("ui-tabs-vertical ui-helper-clearfix"); + $("#container").removeClass("hide"); + var meeting_sort_val = $("#meeting_sort").val(); + $(".borough_by_suffix").hide(); + $(".county_by_suffix").hide(); + if (meeting_sort_val === "borough_county") { + $(".borough_by_suffix").show(); + $(".county_by_suffix").show(); + } else if (meeting_sort_val === "borough") { + $(".borough_by_suffix").show(); + } else if (meeting_sort_val === "county") { + $(".county_by_suffix").show(); + } + $(".neighborhood_by_suffix").hide(); + $(".city_by_suffix").hide(); + if (meeting_sort_val === "neighborhood_city") { + $(".neighborhood_by_suffix").show(); + $(".city_by_suffix").show(); + } + var user_defined_sub = false; + $(".user_defined_headings").hide(); + if (meeting_sort_val === "user_defined") { + $(".user_defined_headings").show(); + if ($("#subgrouping").val() != "") { + user_defined_sub = true; + } + } + if ( + meeting_sort_val == "weekday_area" || + meeting_sort_val == "weekday_city" || + meeting_sort_val == "weekday_county" || + meeting_sort_val == "state" || + user_defined_sub + ) { + $(".show_subheader").show(); + } else { + $(".show_subheader").hide(); + } + $("#suppress_heading").click(function () { + var val = $("#suppress_heading:checked").val(); + if (val == 1) { + $("#header_options_div").hide(); + } else { + $("#header_options_div").show(); + } + }); + $("#meeting_sort").change(function () { + var meeting_sort_val = $("#meeting_sort").val(); + $(".borough_by_suffix").hide(); + $(".county_by_suffix").hide(); + $(".neighborhood_by_suffix").hide(); + $(".city_by_suffix").hide(); + if (meeting_sort_val === "borough_county") { + $(".borough_by_suffix").show(); + $(".county_by_suffix").show(); + } else if (meeting_sort_val === "borough") { + $(".borough_by_suffix").show(); + } else if (meeting_sort_val === "county") { + $(".county_by_suffix").show(); + } + if (meeting_sort_val === "neighborhood_city") { + $(".neighborhood_by_suffix").show(); + $(".city_by_suffix").show(); + } + var user_defined_sub = false; + $(".user_defined_headings").hide(); + if (meeting_sort_val === "user_defined") { + $(".user_defined_headings").show(); + if ($("#subgrouping").val() != "") { + user_defined_sub = true; + } + } + if ( + meeting_sort_val == "weekday_area" || + meeting_sort_val == "weekday_city" || + meeting_sort_val == "weekday_county" || + meeting_sort_val == "state" || + user_defined_sub + ) { + $(".show_subheader").show(); + } else { + $(".show_subheader").hide(); + } + }); + $("#subgrouping").click(function () { + $(".user_defined_headings").hide(); + if ($("#meeting_sort").val() === "user_defined") { + $(".user_defined_headings").show(); + if ($("#subgrouping").val() != "") { + $(".show_subheader").show(); + } else { + $(".show_subheader").hide(); + } + } + }); + function calcTimeDisplay(hasEndTime) { + let clock = $("input[name=time_clock]:checked").val(); + let removeSpaces = $("input[name=remove_space]:checked").val(); + let startTime = "8:00 PM"; + let endTime = "9:00 PM"; + if (clock == "24") { + startTime = "20:00"; + endTime = "21:00"; + } else if (clock == "24fr") { + startTime = "20h00"; + endTime = "21h00"; + } + if (hasEndTime == 2) { + startTime += " - " + endTime; + } + if (hasEndTime == 3) { + startTime = clock == "12" ? "8 - 9" : ""; + } + if (removeSpaces != "0") { + startTime = startTime.replaceAll(" ", ""); + } + return startTime; + } + function setTimeOptionText() { + $("label[for=option1]").html(calcTimeDisplay(1)); + $("label[for=option2]").html(calcTimeDisplay(2)); + $("label[for=option3]").html(calcTimeDisplay(3)); + if ($("input[name=time_clock]:checked").val() != "12") { + if ($("input[name=time_clock]:checked").val() == 3) + $("#option2").prop("checked", true); + $("#option3").hide(); + } else $("#option3").show(); + } + setTimeOptionText(); + $(".recalcTimeLabel").on("click", setTimeOptionText); + function bookletControlsShowHide() { + $("#half").prop("checked") && $("#landscape").prop("checked", true); + $("#full").prop("checked") && $("#portrait").prop("checked", true); + $(".booklet").show(); + $(".single-page").hide(); + $("#half").prop("checked") && $("#A6").hide(); + $("#half").prop("checked") && $("#A6").hide(); + $("#half").prop("checked") && $("label[for=A6]").hide(); + } + function singlePageControlsShowHide() { + $(".booklet").hide(); + $(".single-page").show(); + } + $(".single-page-check").on("click", singlePageControlsShowHide); + $(".booklet-check").on("click", bookletControlsShowHide); + $("input[name=page_fold]:checked").hasClass("booklet-check") && + bookletControlsShowHide(); + $("input[name=page_fold]:checked").hasClass("single-page-check") && + singlePageControlsShowHide(); + $("#service_bodies").select2(); + $("#extra_meetings").select2({ + placeholder: "Select extra meetings", + }); + $("#bread_author_select").select2({ + placeholder: "Select authors", + }); + $(".bread-select").on("change", function (e) { + const self = $(this); + const field = self.parent().find(".select2-search--inline"); + field.css("display", self.val().length == 0 ? "contents" : "block"); + field + .find("input") + .css("width", self.val().length == 0 ? "100%" : "auto"); + }); + $("#meeting-list-tabs").tabs({ + active: 0, + }); + $("#meeting-list-tabs-wrapper").removeClass("hide"); + // Define friendly index name + var index = "key"; + // Define friendly data store name + var dataStore = window.sessionStorage; + // Start magic! + try { + // getter: Fetch previous value + var oldIndex = dataStore.getItem(index); + } catch (e) { + // getter: Always default to first tab in error state + var oldIndex = 0; + } + $("#meeting-list-tabs").tabs({ + // The zero-based index of the panel that is active (open) + active: oldIndex, + // Triggered after a tab has been activated + activate: function (event, ui) { + // Get future value + var newIndex = ui.newTab.parent().children().index(ui.newTab); + // Set future value + dataStore.setItem(index, newIndex); + }, + }); + var aggregator = "https://aggregator.bmltenabled.org/main_server"; + $(window).on("load", function () { + if ($("#use_aggregator").is(":checked")) { + $("#root_server").prop("readonly", true); + } + }); + $("#use_aggregator").click(function () { + if ($(this).is(":checked")) { + $("#root_server").val(aggregator); + $("#root_server").prop("readonly", true); + } else { + $("#root_server").val(""); + $("#root_server").prop("readonly", false); + } + }); + var rootServerValue = $("#root_server").val(); + if (~rootServerValue.indexOf(aggregator)) { + $("#use_aggregator").prop("checked", true); + } + $(".service_body_select").select2({ + width: "40%", }); $(".theme_select").select2({ - width: "20%" + width: "20%", }); $("#select_filters").select2({ - width: "60%" + width: "60%", }); $("#extra_meetings").select2({ width: "60%", placeholder: "Select Extra Meetings", }); - handle_error = function(context, error) { + handle_error = function (context, error) { console.log(error); - } - ask_bmlt = function(context, query, success, fail) { - const url = $('#'+context.root_server).val()+"/client_interface/jsonp/?"+query; - fetchJsonp(url) - .then((response) => { - if (response.ok) { - return response.json(); - } - return Promise.reject(response); // 2. reject instead of throw - }) - .then((json) => { - success(context, json); - return json; - }) - .catch((response) => { - fail(context, response) - return false; - }) - } - test_root_server = function() { - const context = { - root_server: 'root_server', - service_bodies: 'service_bodies', - service_bodies_selected: bread_admin.service_bodies_selected - } - if(!$('#root_server').val()) { - $('#connected_message').hide(); - $('#disconnected_message').hide(); - fill_service_bodies(context, []); - fill_extra_meetings(context, []); + }; + ask_bmlt = function (context, query, success, fail) { + const url = + $("#" + context.root_server).val() + + "/client_interface/jsonp/?" + + query; + fetchJsonp(url) + .then((response) => { + if (response.ok) { + return response.json(); + } + return Promise.reject(response); // 2. reject instead of throw + }) + .then((json) => { + success(context, json); + return json; + }) + .catch((response) => { + fail(context, response); + return false; + }); + }; + const admin_context = { + root_server: "root_server", + service_bodies: "service_bodies", + service_bodies_selected: bread_admin.service_bodies_selected, + }; + test_root_server = function () { + if (!$("#root_server").val()) { + $("#connected_message").hide(); + $("#disconnected_message").hide(); + fill_service_bodies(admin_context, []); + fill_extra_meetings(admin_context, []); return; } - ask_bmlt(context, "switcher=GetServerInfo", + ask_bmlt( + admin_context, + "switcher=GetServerInfo", (context, info) => { - $('#server_version').html(info[0].version); - $('#connected_message').show(); - $('#disconnected_message').hide(); - ask_bmlt(context, 'switcher=GetServiceBodies', fill_service_bodies, handle_error); - if($('#extra_meetings_enabled').is(':checked')) { - $("#extra_meetings_select").hide(); - $("#fetching_meetings").show(); - ask_bmlt(context, 'switcher=GetSearchResults', fill_extra_meetings, handle_error); - } else { - fill_extra_meetings([], []); - } + $("#server_version").html(info[0].version); + $("#connected_message").show(); + $("#disconnected_message").hide(); + ask_bmlt( + context, + "switcher=GetServiceBodies", + fill_service_bodies, + handle_error, + ); + query_extra_meetings(context); }, (context, error) => { - console.log(error) - $('#connected_message').hide(); - $('#disconnected_message').show(); + console.log(error); + $("#connected_message").hide(); + $("#disconnected_message").show(); fill_service_bodies(context, []); fill_extra_meetings(context, []); - } + }, ); - } - test_root_server(); - write_service_body_with_childern = function(context, options, sb, parents, my_parent, level) { - let prefix = ''; - for(i = 0;i < level;i++) prefix += '-'; - const sbVal = [sb.name, sb.id, sb.parent_id, my_parent].join(','); - const option = '' + }; + query_extra_meetings = function (context) { + if ($("#extra_meetings_enabled").is(":checked")) { + $("#extra_meetings_select").hide(); + $("#fetching_meetings").show(); + ask_bmlt( + context, + "switcher=GetSearchResults", + fill_extra_meetings, + handle_error, + ); + } else { + fill_extra_meetings(context, []); + } + }; + test_root_server(); + $("#extra_meetings_enabled").on("change", function () { + query_extra_meetings(admin_context); + }); + write_service_body_with_childern = function ( + context, + options, + sb, + parents, + my_parent, + level, + ) { + let prefix = ""; + for (i = 0; i < level; i++) prefix += "-"; + const sbVal = [sb.name, sb.id, sb.parent_id, my_parent].join(","); + const option = + '"; options.push(option); found = parents.find((p) => p.id == sb.id); - if(typeof found !== 'undefined') - found.children.forEach((child) => - options = write_service_body_with_childern(context, options, child, parents, sb.name, level + 1)); + if (typeof found !== "undefined") + found.children.forEach( + (child) => + (options = write_service_body_with_childern( + context, + options, + child, + parents, + sb.name, + level + 1, + )), + ); return options; - } - fill_service_bodies = function(context, service_bodies) { - service_bodies = service_bodies.sort((a, b) => a.name.localeCompare(b.name)); - const roots = service_bodies.filter((sb) => sb.parent_id == '0'); + }; + fill_service_bodies = function (context, service_bodies) { + service_bodies = service_bodies.sort((a, b) => + a.name.localeCompare(b.name), + ); + const roots = service_bodies.filter((sb) => sb.parent_id == "0"); const parents = service_bodies.reduce((carry, item) => { const found = carry.find((p) => p.id == item.parent_id); - if(found) { + if (found) { found.children.push(item); } else { - carry.push({id: item.parent_id, children: [item]}) + carry.push({ id: item.parent_id, children: [item] }); } return carry; }, []); let options = []; roots.forEach((sb) => { - options = write_service_body_with_childern(context, options, sb, parents, 'ROOT', 0); + options = write_service_body_with_childern( + context, + options, + sb, + parents, + "ROOT", + 0, + ); }); - $('#'+context.service_bodies).html(options.join('')); + $("#" + context.service_bodies).html(options.join("")); - query_used_formats(context) - } - root_server_keypress = function(event) { - if(event.code == 'Enter') { + query_used_formats(context); + }; + root_server_keypress = function (event) { + if (event.code == "Enter") { this.test_root_server(); event.preventDefault(); } return true; - } - fill_extra_meetings = function(context, extra_meetings_array) { + }; + fill_extra_meetings = function (context, extra_meetings_array) { $("#fetching_meetings").hide(); - if($('#extra_meetings_enabled').is(':checked')) { - $("#extra_meetings_select").show(); - + if ($("#extra_meetings_enabled").is(":checked")) { + $("#extra_meetings").next(".select2-container").show(); + $("#extra_meetings_hint").show(); } else { - $("#extra_meetings_select").hide(); + $("#extra_meetings").next(".select2-container").hide(); + $("#extra_meetings_hint").hide(); + } + const options = extra_meetings_array.map( + (extra_meeting) => + '", + ); + $("#extra_meetings").html(options.join("")); + }; + fill_formats = function (context, formats) { + const select = (key) => + bread_admin.used_format == key ? "selected" : ""; + const options = formats.formats.reduce( + (carry, item) => { + carry.push( + '", + ); + return carry; + }, + [''], + ); + $("#used_format_1").html(options.join("")); + fill_additional_list_formats(context, formats); + }; + fill_additional_list_formats = function (context, formats) { + const select = (key) => + bread_admin.additional_list_format_key == key ? "selected" : ""; + const options = formats.formats.reduce( + (carry, item) => { + carry.push( + '", + ); + return carry; + }, + [ + '', + '', + '', + ], + ); + $("#additional_list_format_key").html(options.join("")); + }; + query_used_formats = function (context) { + const serviceBodies = $("#service_bodies") + .val() + .map((s) => s.split(",")[1]); + if (serviceBodies.length == 0) { + fill_formats(context, []); + return; } - const options = extra_meetings_array.map((extra_meeting) => - ''); - $('#extra_meetings').html(options.join('')); - } - fill_formats = function(context, formats) { - const select = (key) => (bread_admin.used_format==key) ? 'selected' : '' - const options = formats.formats.reduce((carry,item) => { - carry.push(''); - return carry; - }, ['']); - $('#used_format_1').html(options.join('')); - fill_additional_list_formats(context, formats) - } - fill_additional_list_formats = function(context, formats) { - const select = (key) => (bread_admin.additional_list_format_key==key) ? 'selected' : '' - const options = formats.formats.reduce((carry,item) => { - carry.push(''); - return carry; - }, ['', - '', - '', - ]); - $('#additional_list_format_key').html(options.join('')); - } - query_used_formats = function(context) { - const serviceBodies = $('#service_bodies').val().map((s)=>s.split(',')[1]) - if (serviceBodies.length == 0) { - fill_formats(context, []); - return; - } - const query = serviceBodies.reduce((carry, item) => { - return carry + "&services[]=" + item - }, $("#recurse_service_bodies").is(':checked') ? '&recursive=1' : '') - ask_bmlt(context, "switcher=GetSearchResults&get_formats_only"+query, fill_formats, handle_error); - } -/** - * Get Tab Key - */ -function getTabKey(href) { - return href.replace('#', ''); -} -/** - * Hide all tabs - */ -function hideAllTabs() { - tabs.each(function() { - var href = getTabKey(jQuery(this).attr('href')); - jQuery('#' + href).hide(); - }); -} -/** - * Activate Tab - */ -function activateTab(tab) { - var href = getTabKey(tab.attr('href')); - tabs.removeClass('nav-tab-active'); - tab.addClass('nav-tab-active'); - jQuery('#' + href).show(); -} - var activeTab, firstTab; - // First load, activate first tab or tab with nav-tab-active class - firstTab = false; - activeTab = false; - tabs = $('a.nav-tab'); - hideAllTabs(); - tabs.each(function() { - var href = $(this).attr('href').replace('#', ''); - if(!firstTab) { - firstTab = $(this); - } - if($(this).hasClass('nav-tab-active')) { - activeTab = $(this); - } - }); - if(!activeTab) { - activeTab = firstTab; - } - activateTab(activeTab); - //Click tab - tabs.click(function(e) { - e.preventDefault(); - hideAllTabs(); - activateTab($(this)); - }); + const query = serviceBodies.reduce( + (carry, item) => { + return carry + "&services[]=" + item; + }, + $("#recurse_service_bodies").is(":checked") ? "&recursive=1" : "", + ); + ask_bmlt( + context, + "switcher=GetSearchResults&get_formats_only" + query, + fill_formats, + handle_error, + ); + }; + /** + * Get Tab Key + */ + function getTabKey(href) { + return href.replace("#", ""); + } + /** + * Hide all tabs + */ + function hideAllTabs() { + tabs.each(function () { + var href = getTabKey(jQuery(this).attr("href")); + jQuery("#" + href).hide(); + }); + } + /** + * Activate Tab + */ + function activateTab(tab) { + var href = getTabKey(tab.attr("href")); + tabs.removeClass("nav-tab-active"); + tab.addClass("nav-tab-active"); + jQuery("#" + href).show(); + } + var activeTab, firstTab; + // First load, activate first tab or tab with nav-tab-active class + firstTab = false; + activeTab = false; + tabs = $("a.nav-tab"); + hideAllTabs(); + tabs.each(function () { + var href = $(this).attr("href").replace("#", ""); + if (!firstTab) { + firstTab = $(this); + } + if ($(this).hasClass("nav-tab-active")) { + activeTab = $(this); + } + }); + if (!activeTab) { + activeTab = firstTab; + } + activateTab(activeTab); + //Click tab + tabs.click(function (e) { + e.preventDefault(); + hideAllTabs(); + activateTab($(this)); + }); }); diff --git a/admin/partials/_bmlt_server_setup.php b/admin/partials/_bmlt_server_setup.php index 311113b3..a82c522c 100644 --- a/admin/partials/_bmlt_server_setup.php +++ b/admin/partials/_bmlt_server_setup.php @@ -86,11 +86,12 @@ function Bread_bmlt_server_setup_page_render(Bread_AdminDisplay $breadAdmin)

    (?)

    - isConnected() && $bread->getOption('extra_meetings_enabled') == 1) {?> +
    + Fetching meetings from server... +
    -

    - +

    emptyOption('extra_meetings_enabled') && $bread->getOption('extra_meetings_enabled') == 1 ? 'checked' : '') ?> />
    From c38c90dbf064059dd721c43494e1900eaed53b6b Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sat, 30 May 2026 17:39:36 +0200 Subject: [PATCH 05/34] Trying to get plugin checker to run --- .github/workflows/pull-requests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 32cf7166..1f86e598 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -6,7 +6,7 @@ on: jobs: lint: - runs-on: ubuntu-22.04 + runs-on: ubuntu24/20260518.149 env: PLUGIN: bread BUILD_DIR: build @@ -32,7 +32,7 @@ jobs: unzip "${BUILD_DIR}/${ZIP_FILENAME}" -d "./bread" - name: Run plugin check - uses: wordpress/plugin-check-action@v1.0.6 + uses: wordpress/plugin-check-action@v1 with: build-dir: "./bread" exclude-directories: 'vendor' From c260422bc38ce7e28eb125ae43f8b6c10360d950 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sat, 30 May 2026 21:00:57 +0200 Subject: [PATCH 06/34] Removed plugin-checker until bug is fixed --- .github/workflows/pull-requests.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 1f86e598..86e9e4d1 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -6,7 +6,7 @@ on: jobs: lint: - runs-on: ubuntu24/20260518.149 + runs-on: ubuntu-22.04 env: PLUGIN: bread BUILD_DIR: build @@ -30,11 +30,10 @@ jobs: echo "ZIP_FILENAME=${ZIP_FILENAME}" >> $GITHUB_ENV PROD=1 make build unzip "${BUILD_DIR}/${ZIP_FILENAME}" -d "./bread" - - - name: Run plugin check - uses: wordpress/plugin-check-action@v1 - with: - build-dir: "./bread" - exclude-directories: 'vendor' - categories: 'plugin_repo' - ignore-warnings: true +# Removed until https://github.com/WordPress/plugin-check-action/issues/579 is fixed +# - name: Run plugin check +# uses: wordpress/plugin-check-action@v1 +# with: +# exclude-directories: 'vendor' +# categories: 'plugin_repo' +# ignore-warnings: true From 585d48eb8443f3b159cd2b078bd28adc729e22fc Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sun, 31 May 2026 20:11:56 +0200 Subject: [PATCH 07/34] Avoid namespace conflicts with wizard --- admin/js/bmlt_meeting_list.js | 62 +++++++-------- admin/js/bread-wizard.js | 125 ++++++++++++++++++++----------- admin/partials/_bread_wizard.php | 12 +-- 3 files changed, 114 insertions(+), 85 deletions(-) diff --git a/admin/js/bmlt_meeting_list.js b/admin/js/bmlt_meeting_list.js index 0b39bd26..b6f68ef3 100644 --- a/admin/js/bmlt_meeting_list.js +++ b/admin/js/bmlt_meeting_list.js @@ -292,12 +292,12 @@ width: "60%", placeholder: "Select Extra Meetings", }); - handle_error = function (context, error) { + handle_error = function (error) { console.log(error); }; - ask_bmlt = function (context, query, success, fail) { + ask_bmlt = function (query, success, fail) { const url = - $("#" + context.root_server).val() + + $("#root_server").val() + "/client_interface/jsonp/?" + query; fetchJsonp(url) @@ -308,71 +308,62 @@ return Promise.reject(response); // 2. reject instead of throw }) .then((json) => { - success(context, json); + success(json); return json; }) .catch((response) => { - fail(context, response); + fail(response); return false; }); }; - const admin_context = { - root_server: "root_server", - service_bodies: "service_bodies", - service_bodies_selected: bread_admin.service_bodies_selected, - }; test_root_server = function () { if (!$("#root_server").val()) { $("#connected_message").hide(); $("#disconnected_message").hide(); - fill_service_bodies(admin_context, []); - fill_extra_meetings(admin_context, []); + fill_service_bodies([]); + fill_extra_meetings([]); return; } ask_bmlt( - admin_context, "switcher=GetServerInfo", - (context, info) => { + (info) => { $("#server_version").html(info[0].version); $("#connected_message").show(); $("#disconnected_message").hide(); ask_bmlt( - context, "switcher=GetServiceBodies", fill_service_bodies, handle_error, ); - query_extra_meetings(context); + query_extra_meetings(); }, - (context, error) => { + (error) => { console.log(error); $("#connected_message").hide(); $("#disconnected_message").show(); - fill_service_bodies(context, []); - fill_extra_meetings(context, []); + fill_service_bodies([]); + fill_extra_meetings([]); }, ); }; - query_extra_meetings = function (context) { + query_extra_meetings = function () { if ($("#extra_meetings_enabled").is(":checked")) { $("#extra_meetings_select").hide(); $("#fetching_meetings").show(); ask_bmlt( - context, "switcher=GetSearchResults", fill_extra_meetings, handle_error, ); } else { - fill_extra_meetings(context, []); + fill_extra_meetings([]); } }; test_root_server(); $("#extra_meetings_enabled").on("change", function () { - query_extra_meetings(admin_context); + query_extra_meetings(); }); write_service_body_with_childern = function ( - context, options, sb, parents, @@ -386,7 +377,7 @@ ''], ); $("#used_format_1").html(options.join("")); - fill_additional_list_formats(context, formats); + fill_additional_list_formats(formats); }; - fill_additional_list_formats = function (context, formats) { + fill_additional_list_formats = function (formats) { const select = (key) => bread_admin.additional_list_format_key == key ? "selected" : ""; const options = formats.formats.reduce( @@ -527,12 +516,12 @@ ); $("#additional_list_format_key").html(options.join("")); }; - query_used_formats = function (context) { + query_used_formats = function () { const serviceBodies = $("#service_bodies") .val() .map((s) => s.split(",")[1]); if (serviceBodies.length == 0) { - fill_formats(context, []); + fill_formats({formats: []}); return; } const query = serviceBodies.reduce( @@ -542,7 +531,6 @@ $("#recurse_service_bodies").is(":checked") ? "&recursive=1" : "", ); ask_bmlt( - context, "switcher=GetSearchResults&get_formats_only" + query, fill_formats, handle_error, diff --git a/admin/js/bread-wizard.js b/admin/js/bread-wizard.js index c9f9422d..f137c056 100644 --- a/admin/js/bread-wizard.js +++ b/admin/js/bread-wizard.js @@ -1,6 +1,24 @@ jQuery(document).ready(function($){ - var BreadWizard = function() { - BreadWizard.prototype.ajax_submit = function() { + window.BreadWizard = {}; + BreadWizard.ask_bmlt = function(query, success, fail) { + const url = $("#wizard_root_server").val()+"/client_interface/jsonp/?"+query; + fetchJsonp(url) + .then((response) => { + if (response.ok) { + return response.json(); + } + return Promise.reject(response); // 2. reject instead of throw + }) + .then((json) => { + success(json); + return json; + }) + .catch((response) => { + fail(response) + return false; + }) + } + BreadWizard.ajax_submit = function() { $(".saving").show(); var myform = document.getElementById("wizard_form"); var formData = new FormData(myform); @@ -15,16 +33,14 @@ jQuery(document).ready(function($){ return response.json(); }) .then((resp) => { - finalInstructions(resp); + BreadWizard.finalInstructions(resp); }) .catch((error) => { // Handle error console.log("error ", error); }); } - var href; - var setting; - finalInstructions = function(response) { + BreadWizard.finalInstructions = function(response) { $(".saving").hide(); $('#wizard-before-create').hide(); $('#wizard-after-create').show(); @@ -34,37 +50,32 @@ jQuery(document).ready(function($){ const tag = '
    '+href+'
    '; $('#wizard-show-link').html(tag); } - BreadWizard.prototype.generate_meeting_list = function() { + BreadWizard.generate_meeting_list = function() { window.open(href, '_blank').focus(); } - BreadWizard.prototype.redo_layout = function() { + BreadWizard.redo_layout = function() { $('#wizard_setting_id').val(setting); $('#bread-wizard').smartWizard("goToStep", 2); } - BreadWizard.prototype.test_root_server = function() { - const context = { - root_server: 'wizard_root_server', - service_bodies: 'wizard_service_bodies', - service_bodies_selected: [] - } - ask_bmlt(context, "switcher=GetServerInfo", - (context, info) => { + BreadWizard.test_root_server = function() { + BreadWizard.ask_bmlt("switcher=GetServerInfo", + (info) => { $('#wizard_root_server_result').removeClass('invalid-feedback dashicons-dismiss') .addClass('valid-feedback dashicons-before dashicons-yes-alt').html($('#wizard_connected_message').html()+info[0].version); }, - (context, error) => { + (error) => { $('#wizard_root_server_result').removeClass('valid-feedback dashicons-yes-alt') .addClass('invalid-feedback dashicons-before dashicons-dismiss').html($('#wizard_disconnected_message').html()); }); } - BreadWizard.prototype.root_server_keypress = function(event) { + BreadWizard.root_server_keypress = function(event) { if (event.code == 'Enter') this.test_root_server(); } - BreadWizard.prototype.root_server_changed = function() { + BreadWizard.root_server_changed = function() { $('#wizard_root_server_result').removeClass('valid-feedback').addClass('invalid-feedback dashicons-before dashicons-dismiss') .html($('#wizard_testnow_message').html()); } - BreadWizard.prototype.finish = function() { + BreadWizard.finish = function() { $('#bread-wizard').smartWizard("reset"); var form = document.createElement("form"); form.method = "POST"; @@ -77,8 +88,44 @@ jQuery(document).ready(function($){ document.body.appendChild(form); form.submit(); } - var hasVirtualMeetings = false; - layout_options = function(meetings) { + BreadWizard.write_service_body_with_childern = function(options, sb, parents, my_parent, level) { + let prefix = ''; + for (i=0; i'+prefix+sb.name+''); + found = parents.find((p) => p.id == sb.id); + if (typeof found !== 'undefined') + found.children.forEach((child) => + options = write_service_body_with_childern(options, child, parents, sb.name, level+1)); + return options; + } + BreadWizard.fill_service_bodies = function(service_bodies) { + service_bodies = service_bodies.sort((a,b) => a.name.localeCompare(b.name)); + const roots = service_bodies.filter((sb) => sb.parent_id=='0'); + const parents = service_bodies.reduce((carry,item) => { + const found = carry.find((p) => p.id == item.parent_id); + if (found) { + found.children.push(item); + } else { + carry.push({id: item.parent_id, children:[item]}) + } + return carry; + }, []); + let options = []; + roots.forEach((sb) => { + options = BreadWizard.write_service_body_with_childern(options, sb, parents, 'ROOT', 0); + }); + $('#wizard_service_bodies').html(options.join('')); + } + BreadWizard.fill_formats = function(formats) { + const options = formats.reduce((carry,item) => { + carry.push(''); + return carry; + }, ['']); + $('#wizard_format_filter').html(options.join('')); + } + BreadWizard.hasVirtualMeetings = false; + BreadWizard.layout_options = function(meetings) { const meeting_count = meetings.length; hasVirtualMeetings = meetings.some((m) => m.formats.split(',').some(format => ['VM','HY'].includes(format))); $('#wizard_meeting_count').html(meeting_count); @@ -87,7 +134,7 @@ jQuery(document).ready(function($){ const name = (group.maxSize == '99999') ? 'Very Large Fellowships' : 'Approx. '+group.maxSize+' meetings'; carry.push(''); group.configurations.reduce((carryGroup, item, idx) => { - carryGroup.push(...getOptionsFromFilename(group.maxSize, item, idx, layouts)); + carryGroup.push(...BreadWizard.getOptionsFromFilename(group.maxSize, item, idx, layouts)); return carryGroup; }, carry); carry.push(''); @@ -95,7 +142,7 @@ jQuery(document).ready(function($){ }, []); $('#wizard_layout').html(options.join('')); } - getOptionsFromFilename = function(size, filename, idx, layouts) { + BreadWizard.getOptionsFromFilename = function(size, filename, idx, layouts) { const type = filename.split('-'); const fold = type[0]; const orientation = type[1]; @@ -107,7 +154,7 @@ jQuery(document).ready(function($){ return carry; },[]); } - lang_options = function() { + BreadWizard.lang_options = function() { const options = breadTranslations.reduce((carry,item)=>{ const selected = (item.key==='en') ? ' selected' : ''; carry.push('') @@ -119,20 +166,15 @@ jQuery(document).ready(function($){ $('#wizard-virtual-meeting-section').hide(); } } - wizard_handle_error = function(error) { + BreadWizard.handle_error = function(error) { console.log(error); $('#bread-wizard').smartWizard("goToStep", 0); } - BreadWizard.prototype.getContent = function(idx, stepDirection, stepPosition, selStep, callback) { - const context = { - root_server: 'wizard_root_server', - service_bodies: 'wizard_service_bodies', - service_bodies_selected: [] - } + BreadWizard.getContent = function(idx, stepDirection, stepPosition, selStep, callback) { switch(idx) { case 1: - ask_bmlt(context, 'switcher=GetServiceBodies', fill_service_bodies, wizard_handle_error); - ask_bmlt(context, 'switcher=GetFormats', fill_formats, wizard_handle_error); + BreadWizard.ask_bmlt('switcher=GetServiceBodies', BreadWizard.fill_service_bodies, BreadWizard.handle_error); + BreadWizard.ask_bmlt('switcher=GetFormats', BreadWizard.fill_formats, BreadWizard.handle_error); break; case 2: const services = $('#wizard_service_bodies').val().reduce((carry,item) => { @@ -141,12 +183,12 @@ jQuery(document).ready(function($){ }, '&recursive=1'); const formats = (Number($('#wizard_format_filter').val()) > 0) ? '&formats='+$('#wizard_format_filter').val() : ''; - ask_bmlt(context, 'switcher=GetSearchResults'+services+formats, layout_options, wizard_handle_error); + BreadWizard.ask_bmlt('switcher=GetSearchResults'+services+formats, BreadWizard.layout_options, BreadWizard.handle_error); break; case 3: - lang_options(); + BreadWizard.lang_options(); case 4: - if ($("wizard_layout").val()=='') wizard_handle_error('Layout not defined'); + if ($("wizard_layout").val()=='') handle_error('Layout not defined'); $('#wizard-before-create').show(); $('#wizard-after-create').hide(); default: @@ -154,8 +196,7 @@ jQuery(document).ready(function($){ } callback(); } - }; - window.breadWizard = new BreadWizard(); + // SmartWizard initialize $('#bread-wizard').smartWizard( @@ -169,9 +210,9 @@ jQuery(document).ready(function($){ anchor: { enableNavigation: false, }, - getContent: breadWizard.getContent + getContent: BreadWizard.getContent } - ); + ) // Initialize the leaveStep event $("#bread-wizard").on("leaveStep", function(e, anchorObject, currentStepIndex, nextStepIndex, stepDirection) { switch(currentStepIndex) { @@ -194,4 +235,4 @@ jQuery(document).ready(function($){ max_selected_options:5, width: "62%" }); -}); \ No newline at end of file +}) diff --git a/admin/partials/_bread_wizard.php b/admin/partials/_bread_wizard.php index 0daa1a13..fdf835d4 100644 --- a/admin/partials/_bread_wizard.php +++ b/admin/partials/_bread_wizard.php @@ -53,8 +53,8 @@

    Do I have the BMLT? to find your BMLT server', 'bread')) ?>
    - + onKeypress="BreadWizard.root_server_keypress(event)" onChange="BreadWizard.root_server_changed()" /> + @@ -150,19 +150,19 @@

    - +

    - +

    -

    +

    - +
    From dfbc919b322d2e7a0dcdd856be1934ccbdd71fc8 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:05:36 +0200 Subject: [PATCH 08/34] small fixes --- admin/class-bread-admin.php | 1 - admin/js/bread-wizard.js | 18 +++++++++--------- includes/class-bread.php | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index 944c18a5..e8da09b0 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -78,7 +78,6 @@ public function enqueue_styles($hook) wp_enqueue_style("select2", plugin_dir_url(__FILE__) . "css/select2.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("smartWizard-dots", plugin_dir_url(__FILE__) . "css/smart_wizard_dots.css", false, BREAD_VERSION, 'all'); } - /** * Register the JavaScript for the admin area. * diff --git a/admin/js/bread-wizard.js b/admin/js/bread-wizard.js index f137c056..476c45eb 100644 --- a/admin/js/bread-wizard.js +++ b/admin/js/bread-wizard.js @@ -44,17 +44,17 @@ jQuery(document).ready(function($){ $(".saving").hide(); $('#wizard-before-create').hide(); $('#wizard-after-create').show(); - href = window.location.href.substring(0, window.location.href.indexOf('/wp-admin')); - setting = response.result.setting; - href = href+"?current-meeting-list="+setting; - const tag = '
    '+href+'
    '; + BreadWizard.href = window.location.href.substring(0, window.location.href.indexOf('/wp-admin')); + BreadWizard.setting = response.result.setting; + BreadWizard.href = href+"?current-meeting-list="+BreadWizard.setting; + const tag = '
    '+BreadWizard.href+'
    '; $('#wizard-show-link').html(tag); } BreadWizard.generate_meeting_list = function() { - window.open(href, '_blank').focus(); + window.open(BreadWizard.href, '_blank').focus(); } BreadWizard.redo_layout = function() { - $('#wizard_setting_id').val(setting); + $('#wizard_setting_id').val(BreadWizard.setting); $('#bread-wizard').smartWizard("goToStep", 2); } BreadWizard.test_root_server = function() { @@ -83,7 +83,7 @@ jQuery(document).ready(function($){ var input = document.createElement("input"); input.type = "hidden"; input.name = "current-meeting-list"; - input.value = setting; + input.value = BreadWizard.setting; form.appendChild(input); document.body.appendChild(form); form.submit(); @@ -96,7 +96,7 @@ jQuery(document).ready(function($){ found = parents.find((p) => p.id == sb.id); if (typeof found !== 'undefined') found.children.forEach((child) => - options = write_service_body_with_childern(options, child, parents, sb.name, level+1)); + options = BreadWizard.write_service_body_with_childern(options, child, parents, sb.name, level+1)); return options; } BreadWizard.fill_service_bodies = function(service_bodies) { @@ -127,7 +127,7 @@ jQuery(document).ready(function($){ BreadWizard.hasVirtualMeetings = false; BreadWizard.layout_options = function(meetings) { const meeting_count = meetings.length; - hasVirtualMeetings = meetings.some((m) => m.formats.split(',').some(format => ['VM','HY'].includes(format))); + BreadWizard.hasVirtualMeetings = meetings.some((m) => m.formats.split(',').some(format => ['VM','HY'].includes(format))); $('#wizard_meeting_count').html(meeting_count); const layouts = breadLayouts.find((layouts) => meeting_count <= Number(layouts.maxSize)); const options = breadLayouts.reduce((carry,group) => { diff --git a/includes/class-bread.php b/includes/class-bread.php index 0903908f..4090252a 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -682,6 +682,7 @@ private function fillUnsetOptionsInner() $this->fillUnsetStringOption('used_format_1', ''); $this->fillUnsetOption('base_font', 'dejavusanscondensed'); $this->fillUnsetOption('colorspace', 0); + $this->fillUnsetArrayOption('service_bodies', []); $this->fillUnsetOption('recurse_service_bodies', 1); $this->fillUnsetOption('extra_meetings_enabled', 0); $this->fillUnsetOption('include_protection', 0); From 5863996bdf571ee4bf300d905e92d6804572236d Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:58:14 +0200 Subject: [PATCH 09/34] small fixes --- admin/partials/bread-admin-display.php | 21 --------------------- includes/class-bread-bmlt.php | 5 +---- public/class-bread-content-generator.php | 12 ++++++------ public/class-bread-public.php | 4 ++-- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/admin/partials/bread-admin-display.php b/admin/partials/bread-admin-display.php index 32706c3a..aeedc854 100644 --- a/admin/partials/bread-admin-display.php +++ b/admin/partials/bread-admin-display.php @@ -32,26 +32,6 @@ function __construct($admin) { $this->admin = $admin; $this->bread = $admin->get_bread_instance(); - $this->refresh_status(); - } - private function refresh_status() - { - $serverInfo = $this->bread->bmlt()->testRootServer(); - $this->connected = is_array($serverInfo) && array_key_exists("version", $serverInfo[0]) ? $serverInfo[0]["version"] : ''; - if ($this->connected) { - $this->unique_areas = $this->bread->bmlt()->get_areas(); - asort($this->unique_areas); - if ($serverInfo[0]["aggregator_mode_enabled"] ?? false) { - $this->server_version = "
    ".__('Using Tomato Server', 'bread')."
    "; - } elseif ($this->connected) { - /* translators: string is the version number of the BMLT Server */ - $this->server_version = "
    ".sprintf(__('Your BMLT Server is running %s', 'bread'), esc_html($this->connected)). "
    "; - } - } - } - public function isConnected() - { - return $this->connected; } public function getBreadInstance() { @@ -122,7 +102,6 @@ function admin_options_page($filename = '') - refresh_status(); ?>
    diff --git a/includes/class-bread-bmlt.php b/includes/class-bread-bmlt.php index 6c9acb2c..722f8ac4 100644 --- a/includes/class-bread-bmlt.php +++ b/includes/class-bread-bmlt.php @@ -90,10 +90,7 @@ public function generateMainQuery($json = 'json') { $sort_keys = 'weekday_tinyint,start_time,meeting_name'; $get_used_formats = '&get_used_formats'; - $select_language = ''; - if ($this->bread->getOption('weekday_language') != $this->get_bmlt_server_lang()) { - $select_language = '&lang_enum=' . substr($this->bread->getOption('weekday_language'), 0, 2); - } + $select_language = '&lang_enum=' . substr($this->bread->getOption('weekday_language'), 0, 2); $services = $this->generateDefaultQuery(); if (isset($_GET['custom_query'])) { $services = $_GET['custom_query']; diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php index 3c597224..b6115746 100644 --- a/public/class-bread-content-generator.php +++ b/public/class-bread-content-generator.php @@ -133,12 +133,12 @@ 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]" => strtoupper($this->options['service_bodies'][0]), - "[service_body_1]" => strtoupper($this->options['service_bodies'][0]), - "[service_body_2]" => (count($this->options['service_bodies']) > 1) ? strtoupper($this->options['service_bodies'][1]) : 'Not Used', - "[service_body_3]" => (count($this->options['service_bodies']) > 2) ? strtoupper($this->options['service_bodies'][2]) : 'Not Used', - "[service_body_4]" => (count($this->options['service_bodies']) > 3) ? strtoupper($this->options['service_bodies'][3]) : 'Not Used', - "[service_body_5]" => (count($this->options['service_bodies']) > 4) ? strtoupper($this->options['service_bodies'][4]) : 'Not Used', + "[service_body]" => explode(',', strtoupper($this->options['service_bodies'][0]))[0], + "[service_body_1]" => explode(',', strtoupper($this->options['service_bodies'][0]))[0], + "[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->shortcodes = apply_filters("Bread_Section_Shortcodes", $this->shortcodes, $this->bread->bmlt()->get_areas(), $formatsManager->getFormatsUsed()); diff --git a/public/class-bread-public.php b/public/class-bread-public.php index cf9c4e4a..4243646e 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -135,7 +135,7 @@ private function generatePreloadConfiguration(int $id): array public function doBreadButton($atts) { $label = $atts['label'] ?? 'Generate PDF'; - $id = $atts['current-meeting-list'] ?? "1"; + $id = $atts['current_meeting_list'] ?? "1"; return "
    ". "". "". @@ -152,7 +152,7 @@ public function bmlt_meeting_list() if (! wp_verify_nonce($_POST['nonce'], 'bread-button-nonce')) { die; } - $preload = json_decode(stripslashes($_POST['preload']), true); + $preload = json_decode($_POST['preload'], true); $this->bread->bmlt()->preload($preload); } $this->options = $this->bread->getConfigurationForSettingId($this->bread->getRequestedSetting()); From 8a08c12d304a6cb86218bbb2d6b835ed407f0581 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:50:53 +0200 Subject: [PATCH 10/34] Disable extra meetings if aggregator is being used --- admin/js/bmlt_meeting_list.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/admin/js/bmlt_meeting_list.js b/admin/js/bmlt_meeting_list.js index b6f68ef3..faac5fbb 100644 --- a/admin/js/bmlt_meeting_list.js +++ b/admin/js/bmlt_meeting_list.js @@ -347,6 +347,14 @@ ); }; query_extra_meetings = function () { + if ($('#root_server').val() == aggregator) { + $("#extra_meetings_enabled").prop('checked', false) + $("#extra_meetings_enabled").prop("disabled", true) + $("#extra_meetings_select").hide(); + $("#fetching_meetings").hide(); + fill_extra_meetings([]); + return; + } if ($("#extra_meetings_enabled").is(":checked")) { $("#extra_meetings_select").hide(); $("#fetching_meetings").show(); From 6e92f8ac2f59adec631b3a4c4c896cc57cd9d283 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:11:07 +0200 Subject: [PATCH 11/34] Adjust extra meeting display --- admin/class-bread-admin.php | 1 + admin/js/bmlt_meeting_list.js | 23 +++++++++++++---------- admin/js/dayjs.min.js | 1 + public/class-bread-content-generator.php | 4 ++-- 4 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 admin/js/dayjs.min.js diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index e8da09b0..aa2776fc 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -96,6 +96,7 @@ public function enqueue_scripts($hook) wp_enqueue_script("tooltipster", plugin_dir_url(__FILE__) . "js/tooltipster.bundle.min.js", array('jquery'), "1.2", true); wp_enqueue_script("spectrum", plugin_dir_url(__FILE__) . "js/spectrum.min.js", array('jquery'), "1.2", true); wp_enqueue_script("select2", plugin_dir_url(__FILE__) . "js/select2.min.js", array('jquery'), "1.2", true); + wp_enqueue_script("dayjs", plugin_dir_url(__FILE__) . "js/dayjs.min.js", array('jquery'), "1.2", true); wp_enqueue_script("fetch-jsonp", plugin_dir_url(__FILE__) . "js/fetch-jsonp.js", array('jquery'), "1.30", true); wp_enqueue_script("smartWizard", plugin_dir_url(__FILE__) . "js/jquery.smartWizard.js", array('jquery'), "6.0.6", true); wp_enqueue_script("breadWizard", plugin_dir_url(__FILE__) . "js/bread-wizard.js", array('smartWizard'), BREAD_VERSION, true); diff --git a/admin/js/bmlt_meeting_list.js b/admin/js/bmlt_meeting_list.js index faac5fbb..50cec5e6 100644 --- a/admin/js/bmlt_meeting_list.js +++ b/admin/js/bmlt_meeting_list.js @@ -409,10 +409,12 @@ ); return options; }; + var breadServiceBodies ={} ; fill_service_bodies = function (service_bodies) { service_bodies = service_bodies.sort((a, b) => a.name.localeCompare(b.name), ); + breadServiceBodies = service_bodies.reduce((obj, item) => { obj[item.id] = item; return obj; }, {}) const roots = service_bodies.filter((sb) => sb.parent_id == "0"); const parents = service_bodies.reduce((carry, item) => { const found = carry.find((p) => p.id == item.parent_id); @@ -444,6 +446,12 @@ } return true; }; + extraMeetingDayAndTime = function (extra_meeting) { + const meetingDay = dayjs().day(extra_meeting.weekday_tinyint) + .hour(parseInt(extra_meeting.start_time.substring(0, 2))) + .minute(parseInt(extra_meeting.start_time.substring(3, 5))); + return meetingDay.format("ddd h:mm A"); + } fill_extra_meetings = function (extra_meetings_array) { $("#fetching_meetings").hide(); if ($("#extra_meetings_enabled").is(":checked")) { @@ -462,16 +470,11 @@ ? "selected" : "") + ">" + - extra_meeting.meeting_name + - " [" + - extra_meeting.weekday_tinyint + - "][" + - extra_meeting.start_time + - "][" + - extra_meeting.location_municipality + - "][" + - extra_meeting.service_body_bigint + - "]", + extra_meeting.meeting_name + ' - ' + + extraMeetingDayAndTime(extra_meeting) + ' - ' + + extra_meeting.location_municipality + ' - ' + + breadServiceBodies[extra_meeting.service_body_bigint]?.name + + "", ); $("#extra_meetings").html(options.join("")); }; diff --git a/admin/js/dayjs.min.js b/admin/js/dayjs.min.js new file mode 100644 index 00000000..cfcd9845 --- /dev/null +++ b/admin/js/dayjs.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e()}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",c="month",f="quarter",h="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},O=function(t,e){if(S(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},b=v;b.l=w,b.i=S,b.w=function(t,e){return O(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=w(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[p]=!0}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(b.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return b},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=O(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return O(t) strtoupper(gmdate("F")), "[day]" => strtoupper(gmdate("j")), "[year]" => strtoupper(gmdate("Y")), - "[service_body]" => explode(',', strtoupper($this->options['service_bodies'][0]))[0], - "[service_body_1]" => explode(',', strtoupper($this->options['service_bodies'][0]))[0], + "[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', From c538aef43719b580635c26bd64bb545da2b04bee Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:51:19 +0200 Subject: [PATCH 12/34] Retrieve queries over AJAX --- includes/class-bread.php | 2 ++ public/class-bread-public.php | 18 ++++++++++-------- public/js/bread-public.js | 35 ++++++++++++++++++++++------------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/includes/class-bread.php b/includes/class-bread.php index 4090252a..1d63ba50 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -531,6 +531,8 @@ private function define_public_hooks() $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles'); $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts'); + $this->loader->add_action('wp_ajax_bread_generate_queries_action', $plugin_public, 'generate_preload_configuration'); + $this->loader->add_action('wp_ajax_nopriv_bread_generate_queries_action', $plugin_public, 'generate_preload_configuration'); $this->loader->add_action('plugins_loaded', $plugin_public, 'bmlt_meeting_list'); } diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 4243646e..fb818a57 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -92,9 +92,9 @@ public function enqueue_scripts() if (($current = $this->doPreloading()) > 0) { wp_enqueue_script('fetch-jsonp', plugin_dir_url(__FILE__) . 'js/fetch-jsonp.js', array(), $this->version, true); wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/bread-public.js', array('jquery','fetch-jsonp'), $this->version, true); - wp_localize_script($this->plugin_name, 'bread_ajax_obj', [ - 'config' => $this->generatePreloadConfiguration($current) - ]); + wp_localize_script($this->plugin_name, 'ajax_object', [ + 'ajax_url' => admin_url('admin-ajax.php') + ]); } } /** @@ -120,27 +120,29 @@ private function doPreloading(): int } return -1; } - private function generatePreloadConfiguration(int $id): array + public function generate_preload_configuration(): string { + $id = intval($_GET['current-meeting-list'] ?? 1); $options = $this->bread->getConfigurationForSettingId($id); - return [ + wp_send_json([ 'root_server' => $options['root_server'], 'main_query' => $this->bread->bmlt()->generateMainQuery('jsonp'), 'extra_meetings_query' => $this->bread->bmlt()->generateExtraMeetingQuery('jsonp'), 'additional_list_query' => $this->bread->bmlt()->generateAdditionalListQuery('jsonp'), 'weekday_language' => $options['weekday_language'], 'additional_list_language' => $options['additional_list_language'], - ]; + ]); + die(); } public function doBreadButton($atts) { $label = $atts['label'] ?? 'Generate PDF'; $id = $atts['current_meeting_list'] ?? "1"; - return "". + return "". "". "". "". - "". + "". ""; } public function bmlt_meeting_list() diff --git a/public/js/bread-public.js b/public/js/bread-public.js index 21afb0e6..65b6bf68 100644 --- a/public/js/bread-public.js +++ b/public/js/bread-public.js @@ -1,28 +1,37 @@ jQuery(document).ready(function($) { - $('#bread_button_form').submit(async function(event) { + $('.bread_button_form').submit(async function(event) { event.preventDefault(); // Block default submission - const doFetch = async (url) => { + const doFetchP = async (url) => { try { - x = await fetchJsonp(url); + const x = await fetchJsonp(url); return x.json(); } catch (e) {console.log(e)} } - let config = bread_ajax_obj.config; + const doFetch = async (url) => { + try { + const x = await fetch(url); + const json = await x.json(); + console.log(json); + return json; + } catch (e) {console.log(e)} + } + const currentMeetingList = $(event.target).children('input[name="current-meeting-list"]').val(); + let config = await doFetch(ajax_object.ajax_url + '?action=bread_generate_queries_action¤t-meeting-list=' + currentMeetingList); const preload = {}; - preload.mainResults = await doFetch(config.root_server + '/' + config.main_query); - if (config.extra_meetings_query) preload.extraMeetings = await doFetch(config.root_server + '/' + config.extra_meetings_query); - if (config.additional_list_query) preload.additionalListMeetings = await doFetch(config.root_server + '/' + config.additional_list_query); - preload.serviceBodies = await doFetch(config.root_server + '/client_interface/jsonp/?switcher=GetServiceBodies'); + preload.mainResults = await doFetchP(config.root_server + '/' + config.main_query); + if (config.extra_meetings_query) preload.extraMeetings = await doFetchP(config.root_server + '/' + config.extra_meetings_query); + if (config.additional_list_query) preload.additionalListMeetings = await doFetchP(config.root_server + '/' + config.additional_list_query); + preload.serviceBodies = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetServiceBodies'); preload.allFormats = {}; const langs = config.weekday_language.split('_'); for (let i = 0; i < langs.length; i++) { - preload.allFormats[langs[i]] = await doFetch(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + langs[i]); + preload.allFormats[langs[i]] = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + langs[i]); } if (config.additional_list_language && config.additional_list_language != '') - preload.allFormats[config.additional_list_language] = await doFetch(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + config.additional_list_language); - $('#bread_preload_item').val(JSON.stringify(preload)); - $('#bread_button_form').unbind('submit'); - $('#bread_button_form').submit(); + preload.allFormats[config.additional_list_language] = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + config.additional_list_language); + $(event.target).children('input[name="preload"]').val(JSON.stringify(preload)); + $(event.target).unbind('submit'); + $(event.target).submit(); return false; } )}) From 54721cf4bb6eaf9bff8c97e31bb0b64cae892131 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Fri, 5 Jun 2026 19:30:43 +0200 Subject: [PATCH 13/34] Add nonce and working gif Co-authored-by: Copilot --- admin/class-bread-admin.php | 30 ------------------- public/class-bread-public.php | 9 +++++- public/css/bread-public.css | 10 +++++++ public/js/bread-public.js | 54 ++++++++++++++++++++++++++++------- 4 files changed, 62 insertions(+), 41 deletions(-) create mode 100644 public/css/bread-public.css diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index aa2776fc..93febba7 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -421,36 +421,6 @@ function save_admin_options() { $this->bread->updateOptions(); } - public function getLatestRootVersion() - { - $results = $this->get("https://api.github.com/repos/bmlt-enabled/bmlt-root-server/releases/latest"); - $httpcode = wp_remote_retrieve_response_code($results); - $response_message = wp_remote_retrieve_response_message($results); - if ($httpcode != 200 && $httpcode != 302 && $httpcode != 304 && ! empty($response_message)) { - return 'Problem Connecting to Server!'; - }; - $body = wp_remote_retrieve_body($results); - $result = json_decode($body, true); - return $result['name']; - } - function get($url, $cookies = array()) - { - $args = array( - 'timeout' => '120', - 'cookies' => $cookies, - ); - if (isset($this->options['user_agent']) - && $this->bread->getOption('user_agent') != 'None' - ) { - $args['headers'] = array( - 'User-Agent' => $this->bread->getOption('user_agent') - ); - } - if ($this->bread->getOption('sslverify') == '1') { - $args['sslverify'] = false; - } - return wp_remote_get($url, $args); - } /** * @desc Adds the options sub-panel */ diff --git a/public/class-bread-public.php b/public/class-bread-public.php index fb818a57..41bb5731 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -80,6 +80,9 @@ public function __construct($plugin_name, $version, $bread) */ public function enqueue_styles() { + if (($current = $this->doPreloading()) > 0) { + wp_enqueue_style('bread-public', plugin_dir_url(__FILE__) . 'css/bread-public.css', array(), $this->version, 'all'); + } } /** @@ -93,7 +96,8 @@ public function enqueue_scripts() wp_enqueue_script('fetch-jsonp', plugin_dir_url(__FILE__) . 'js/fetch-jsonp.js', array(), $this->version, true); wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/bread-public.js', array('jquery','fetch-jsonp'), $this->version, true); wp_localize_script($this->plugin_name, 'ajax_object', [ - 'ajax_url' => admin_url('admin-ajax.php') + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('bread-ajax-nonce'), // Can use the same even if there are multiple buttons on the page since WP nonces are single use but can be used multiple times within 24 hours ]); } } @@ -122,6 +126,9 @@ private function doPreloading(): int } public function generate_preload_configuration(): string { + if (!wp_verify_nonce($_GET['nonce'], 'bread-ajax-nonce')) { + wp_die('Security check failed'); + } $id = intval($_GET['current-meeting-list'] ?? 1); $options = $this->bread->getConfigurationForSettingId($id); wp_send_json([ diff --git a/public/css/bread-public.css b/public/css/bread-public.css new file mode 100644 index 00000000..b99a08b2 --- /dev/null +++ b/public/css/bread-public.css @@ -0,0 +1,10 @@ +.working { + position: fixed; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + z-index: 9999; + background: url("data:image/gif;base64,R0lGODlhxgDGAPcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0RDQU5MRV5YSmxkT3htUoN1VYl6V4x9WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI5+WI9/WY9/WY9/WY9/WY9/WY9/WY9/WY9/WY9/WY9/WY9/WY9/WZGBXJODX5SEYJWGYpiJZpmLaZuNa52Pbp+ScqGVeqKXf6SagqachqadjaSfnaCfqJ+eqp+eqp+eqp+eqp+eqp+eqp6eqp6eqp6eqp6eqp6eqp6eqp6eqp6eqp6eqp+fq5+fq5+fq5+fq5+fq5+fq5+fq5+fq6CgrKGhrKKiraOjrqSkr6Skr6WlsKamsKinsKmosaqpsauqr62rrK6rq6+sqbCsqLGtp7KtprOvqLSwq7WxrbWysLWytLWzt7W0ure2vLi3vLq4u726u8G9ucXAuMjDusvFu8zHvM3JwM7Kwc/LwtHMw9HNxdPPx9XRydjTytrWzNzXzt7a0d/b1N7c19zb2t3c293c3N/e3eHg3uPi3+Xk4efl4ubl5efm5ujn5ujn5uno5+vp6Ozq6e3s6+7t7O/u7O/u7PDv7fHw7fLx7vLx7/Py8fTz8fX08vb18/b29Pf29Pj39fj39vn49/r5+Pv7+v39/P79/f7+/v7+/v7+/v7+/v7+/v7+/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJBADvACwAAAAAxgDGAAAI/gDfCRxIsKDBgwgTEkwmq1QmNmzOaJmISaHFixgzatzIsaPHjyDflZpIsuSYkChTqlzJsuXFkSVjTnNJs6bNmzVhxiRJC6fPn0CDGtS5U4ur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SZQEiRzJEsbgCp/whRzhfiRZkmexeYywDU6q6JJ2sU4qtoc/ERAAIfkECQQA7QAsAAAAAMYAxgCHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJCgoKCwsLDAwMDQ0NDg4ODw8PEBAQEREREhISExMTFBQUFRUVFhYWFxcXGBgYGRkZGhoaGxsbHBwcHR0dHh4edGhLjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39ZkYFbkoJdk4Nfk4RglIVglIVhlYVhlYZilYZilYZilodjlodjl4hlmYpnmoxqm41rnI5tnpBvoJJyopR1pJd4ppl8qJx/qZ2Bqp6Cq5+DrKCErKGFraKHr6OJsKSKsaaLsaaMsqeNsqiPs6mRs6mUsqqXsamYrKaeqqWiqaWjqKSjo6Gnn5+qn5+qn5+qn5+qn5+qn5+qn5+rn5+rn5+rn5+rn5+rn5+rn5+qn5+qn5+qn5+rn5+roaGtpKSvp6eyqqq0rKy1sLC3tLO5t7W5uLa4ure0u7i3vrm0wLuzw72yxb+1x8K3ysS5zce6z8m70Mq90Mu+0czA0czC0c3D0c3G0c7K0M7Lz83NzszOzs3P0M/R0dDS09LS1dTU1tXW2NfX2tjZ29ra3Nvb3tzb393a4d/a5OHb5+Te6ebf6efi6ejl6ejn6ejo6enp6urq6+rr7Ovr7Ozs7e3t7u3t7+7u7+/t8O/t8fDu8fDv8vHw8vLy8/Py9PTz9fTz9vXz9/b09/b09/f0+Pf1+Pf1+fj2+/r5+/v5/Pv6/f38/v7+/v7+/v7+/v7+/v7+/v7+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////CP4A2wkcSLCgwYMIExK0xerSpTJlnimcSLGixYsYM2rcyLFjO1BxwPwYSfIHK48oU6pcybIlRSolY1LaKMuWy5s4c+qcSCZmSTAb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NJEYuY8XeYjZZJECoWgeGZItgQylcF5iCEzCdJIo2TYS0rAKXkeS7PaSMQmBwSSCAImTOUkWKqmEB+GMPASTP1kYPnmUyvF0RqmUe7FYxId30YgUAQEAIfkECQQA8wAsAAAAAMYAxgCHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJCgoKCwsLDAwMDQ0NDg4ODw8PEBAQEREREhISExMTFBQUFRUVFhYWFxcXGBgYGRkZGhoaGxsbHBwcHR0dHh4eHx8fICAgISEhIiIiIyMjJCQkJSUlJiYmJycnKCgoKSkpKioqKysrLCwsLS0tLi4uLy8vMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk5Ojo6RUM+U09EYltJc2hPgHNUiHlWjHxYjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39akYFckoJdk4RflIVglIVhlYZilYZilYZilYZilYZilYZilYZjlYdjlohkmIlmmYtomoxqnI5tnpFwoZN0o5Z4ppp8p5t+qJyAqZ2CqZ2DqZ+HqqCLrKKPrKORraWUq6OWqaOYp6GbpaCdop+hn56on56qn56qn56qn56qn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+qn5+rn5+rn5+roKCsoqKuo6OupaStq6murauwsa+ztLK0trS2uLa3ube4u7m5vbm3v7q1wbuxw7yxxL6zxcC3xsG6yMS9y8a+zMe+zsm/0MvB0M3H0M3Kz83Nz87Qz8/S0NDT0tHW1dTX19bY2djZ2trd29rd3Nvd3dzd3t3e4N/f4uHh4+Lj5eXl5+bm6ejn6+rp7ezq7+7r8O/t8fDu8vHw8/Lx8/Py9PTz9fTz9vX09vb09vb09vb09/b19/f29/f2+Pf3+Pj3+Pj4+fj4+fn5+fn5+vr7+/v7+/v8/Pz8/Pz9/f3+/v7+/v7+////////////////////////////////////////////////////////////////////////////////////////////////////CP4A5wkcSLCgwYMIExZcprChw4cQI0qcSLGi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gGShIITyiFpYeHvaqE16mBCkV4UBORGFJkcAORAGORG4RgovuJAKsUtWBIoJEZESQW2gUJEW2RDewF2YZH61x4oIoWIhSRIj+ZEUgYUouSSSjnGGRhSTMkkYJ+eGN4l5wbSTvNF7IOmTesGBGDZ5h6gXAQEAIfkECQQA7gAsAAAAAMYAxgCHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJCgoKCwsLDAwMDQ0NDg4ODw8PEBAQEREREhISExMTFBQUFRUVFhYWFxcXGBgYGRkZGhoaGxsbHBwcHR0dHh4eHx8fICAgISEhIiIiIyMjJCQkJSUlJiYmJycnKCgoKSkpKioqKysrLCwsLS0tLi4uLy8vMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk5REI9Uk5DYVpJc2hPgHNUiHlWjHxYjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj4BakIBbkYFckoNek4RglIVhlIVhlYdjl4hlmIpomoxsnI9xnpJ1oZR4o5d8o5h9pJh+pZqApZqBpZuDpZyHpp2KpZ2NpJ2Qo52WoZ2doJ6ln56on5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+roqKto6OvpKSvpqWtqaesrKmpsKymtK6lt7CjubKlvbWmwLmpwLmrv7mvu7e0uba4ubi7urm9u7rAvLzCwL/Cw8HDxsPDyMXDysfDzcrEz8vE0c3G09DJ1tPO2NbV2djZ29rb3Nvd3dze3t7e4N/g4+Hf5ePh5+Xj6ejm7Ovp7ezr7u3s7+7s8O7s8O/s8vDu8/Lx9PTz9fX09vb09vb19/b29/f29/f3+Pj5+fn6+vr7+vr7+/v7/Pz8/v7+/v7+/v7+/v7+/v7+/v7+/v7+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////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lwqzKI4JoQudkH8GsVITIY051AmHyI4T4Uk2aBDL1gnZiI8nIUsUIQ1AloMAaRYBVRGEd5Bn4UkMCR0/2AgR95C2AXSvSJGqoU2ukAqpYFj7GBcBAQAh+QQJBADvACwAAAAAxgDGAIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4mJSKKe1aOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mQgFqRgVyThGCVhmKWh2SZi2ibjWyekHChk3Sjlnekl3ikl3mlmn+mnIalnY6jnZOhnZ2fnqmfnqqfnqqfnqqfnqqfnqqenqqenqqenqqenqqenqqfn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufnqqfn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6uhoKmioaijoqilo6inpamppqirp6asqKSuqJ+wqZuyqZezqpe1rJi3rpq5sJ27s6C+tqW/uKfCuqvBu7DAu7PAu7a+u7i7ubq8uru7ur28ur69vcPBwcfGxcrLys3PztDT0dHU09PX1dfZ2Nrb2tzb2tzc29vc29re3Nrh3tni4Nzk4t7l49/m5N/n5eHp5+Pq6OPs6uXu7Ojw7uvw7+3x8O7y8e/08/L19PL29fP39vP39vT39/T49/X49/X4+Pb4+Pf5+Pj5+Pj7+vr8/Pz8/Pz8/P39/f39/f3+/v7+/v7///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8I/gDfCRxIsKDBgwgTKjR4zAwpU6Zu9VpIsaLF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j/OYEYY0fe6UcNdgjiaEjv+IEqbkjQthjMOYkEJhSuNYigbJjhCJE72YQw+JEEmYCRt5kTThjgcpZF8IhCAZFSLpgRVRcq+ggSf5KKBkJ5ELUZCv0GIvWU/yqBEIeZMoMQyvYHQmtIw8uRjDMHzY1Ar+qBgBAQAh+QQJBADvACwAAAAAxgDGAIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM/PThOST9kXEh0aU6Bc1OIeVaMfViOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mQgFqRgVyRglySgl2Tg16ThGCUhWGVhmKWh2OXiGWYiWaZi2iajGmbjWqbjWucjmycjmydj22dj26ekG+gk3KilXWjlnakl3mmmXynm3+onIGonYOonoWonoeonoionoqmno+knpWinp2hnqOgnqWfnqifnqqfnqqfnqqfnqqfnqqfnqqfn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6qfn6ufn6ufn6ufn6ufn6ugoKyioq2kpK+mpbGpqbStrLWwr7aysLS0srS3tLa5tra7uLe+urfAvbrDv7vGwr7Kxb/Lx8DLyMLLx8TLyMfLycrMy8/PztPR0dXU09fV1dfX1tjZ2Nna2dzb2t3b293d29ze3Nrg3dni39nj4Nvk4d3l49/m5OHn5eLo5uPo5+Tp5+Xq6efs6+ns6+vt7O3u7e7v7+7x8PDz8vH09PL29fL39vP39vT39/T49/T49/T49/X49/b4+Pb5+Pf5+ff5+fj7+/v9/f39/f39/f7+/v7+/v7+/v7+/v7/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////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yigR9YdJApENCnVwGsUKwTBjCjF7d1iNEDGK99d0wwAL6siNwkURy4dn5CgRqMhDJBgRaOhCvfcQNoVm8SgR99hC+YgQW2iLCeEKu9iPEyGKqEcR8/hCBIkQ57B+CCl7y/aQBoFMFjmRKNEMw9d8EuGLDqmRVpENwVCPFBFsGSmSePGPKamSzjdMLekTkmLxejEpk2Ghjjj5dfWUjEcREAAh+QQJBADsACwAAAAAxgDGAIcAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6OjpKR0BbVUZtY017b1KFd1WLe1eOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliOfliPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mPf1mQgFqRgVySgl2ThGCVhmKWh2OWh2SXiGWYiWaZimiai2mbjWudj2+fkXGhlHSilXekl3ilmHqmmXynm4ConISonYWonoinnoymn5Kkn5mhnqGfnqefnqifnqmfnqmfnqmfnqqfnqqfnqqfn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufnqqfnqqfnqqfnqqfn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ufn6ugoKyioq2jo6+mpq+pqLCtq66wra2zrqm3sKW6s6i7tam7tq28t6++uLDBu6/EvK7GvqzGvq3JwbHLw7TMxrfNyLzNycDNycPMycfNy8rOzc3Rz9DU0tPX1tfZ2Nrb2tzc293e3t7g3+Hj4uTm5ebn5+jp6Onr6uvs7Ozt7e3v7u7w7+/x8fDz8vL08/P19fP29vX49/f4+Pf4+Pj6+fn6+vr8/Pz9/f39/f7+/v7+/v7+/v7+/v7/////////////////////////////////////////////////////////////////////////////////////////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4d/b4uDb4+Hc5eLd5uTf6Obi6efk6ujm6+rp7Ovq7ezs7u3t7+7t7+/u8PDv8vHw8vLy8/Pz9PT09fT09vb09/b09/f0+Pf1+Pf2+Pf3+fj4+fn5+vr6+/v8/Pz8/f39/v7+/v7+/v7+/v7+/v7+////////////////////////////////////////////////////////////////////////////CP4A7QkcSLCgwYMIEyp0xqehQz6Abt2qprCixYsYM2rcyLGjx4/IuogcSRLZx5MoU6pcyZLjL5Iwu5hsSbOmzZs4BdqKSTJYzp9Agwo1WIjnSFtDkypdOhCZoW8c+RgViZSp1as1kUntYogjnaldqmpEdgsq1rNYkeWBOTMjWK4bv7Xp0gYX2rtJvxniScfsxWpv+WwERDJPW7yIa+Kaa7QrxpBgBWfExXOW38SYUQJ+e9hgNbK31oKlE8zZRWdT23TOzHrj1ql9Czq7xYfxW558bq0W+E30VLGtgz++3fXbL0C2bys35JPg3rfAhUtXOOf2a+XYSbYxZDrYbTaXp/6LN/gyu/nzXWrfjj6+PW826OPLhwnevX3n8/PHZ39//Gb9AH4XXn/t/RHggVP9QmB/31yH4INzLHifM189aKFIdknYXnkXXtiGaRpOx2GHHSoYYnDPkaiiiScmFcxu+KkoYxcstgiUd+nBmOKMK9oIVDXJ8dEZZTzyWKOPNREWEx8gjlikjCAa9M0tdGSI5EY4NhZMck+q2AZFBDk1UhsDXqnQNxV2GRMeD/EBn4x58IZLmiMVYmZGO6kpEh62BAPmQc78YggeHRpiCJcl3WkRkGrigcufGDljSHVd0qFoRUU9+QeMGSFD6JPNXXpQNQ52yAekKP1C6YySiYpQnv4dshFqS7DKyKmryLz54B9lruTMqiS26upB3xiIoGM5fZNph3PcOqw9RAJ4ZE7LPsjfswb9qt+0P1ULIB5RYqvQf/JxC5S38rFxrbgG1YqelUuVet6p7GJE57xYffNpfH/Ui1GW6LGB6lKoyWepvxahm92sV+14XrgIH4QodsLmC6x5yEZsEMDnOZuUk9nFqfFBDmdXMVoXZzfwyL49jBnI2JmL8Dfy4ZHZN7qaZ+fIYcoHL2IK33YwzzrJtzJaHGfXq79Bv2Vzazln5zGSgUp0CyAOzbcuWvLeJvOdXc/HcGLuYre1mfcCePRdSStHL7YXClewwRDdggsyENtoYf6Ewll48ol+Sxf4lYMHV7iPh7OWeIuLY9Z4iI8jFrmEFg7d2uQSOoPML7YY0lB+wkGG4M7P0hxf3ojBnN/ZhMf39Vkl68c6kvtiHFzL2bHJxx+29N4nMkuDHZ/liZmO3tr1lo0d6mipfhvRTfnMmrH4Qm+P8ecRfxe5tltvT+3mjX2W8sqJr3Hsbhc/sXLB+zs3elMLRf5tT3tvT8rYiYwWo/H9DD36ypkdUJQUH+RF7H3HO0u00FM/+8llPnloX0745zr7CWRL+gGEVXozn/p47xsE1E/GhMLB+YyQZ8hYn3xO+JOwYYcOzFPIOJwxDY+84hW9UEYNEfONWRSKhP4uNI//EKIMXpyiEpU4RUemgcQmjkIXxhjHWZCRNgQBQoIocQbu9MOHpSkjFqNoYhOVwRFeiPGMq8jGVfD3IDrEryMYtFAbxjaOMJ6xibHgyBHv2EQ1WmWBKrIMTaoRwgvNwiB75GMl/IgRJioSifmKGonqspIpqRBBeQhXLB5ZiWFoxIyPVMX4KvULLCLEkl06TC84qcSMJJKPrjgLzvTEHI0Yp5A8qpgzOFmJGMqGl71ACyBJMgdcgA9BfJhFaQD1i1kEUUWdGQcvdYERXfDSGHc5Jh+a4wxJ6umbpCOIKjg5Cmc4Yxi9iMUN17nOXtjxkb4ciujYUAhUte2bCP7ikzdF4sGCWJOXAA2oFO8yKVz0an74zE99vmELb5prGAGNKCd5mLCEPogN4WIosP4mkF1K9KNiHEUvBpoRbFqlaRY1D0YRoqouCAwhxngFSGcqRl0wUoarqMQqSKoUlKb0O1GaBi94ao9fiM8Yr6SpUm2qkGzkFImr2OFSfPpTo6xUIMaA6k0BJVOlevWMQz3INN6JxFFItadVVWmUsupEMh5kHJv8qlxDatKBKIOsTjzrx9KqHDz8ia1nDGZB7jrXworxFX4ELB/NapUt8pUkhfCLMh4Zi4HC1bCYdaIx/klOvQZFdI+NCXDGysppTOOpmU1tRGOplGdWlUV1BP7oKPCq2tryURSe/QloQwuToXHWtsBVal2H4tq0zmQaogiuckHKi6Xslrcw6ZdAkLvc6vKSta21DlWf9FfreveOoiDqZwV0PVuwMUCFAOBIMqbY71o3t0BxLFtkM6gDseEPpRRIEMlEEFC6t7rDHUqxoJMQ4xSiuCRhAx9ssZpq7HMkR/rtf4ErXnmysYEVQSBPftc+hHZBe66YsHJfka+yvdQtb9HIMUfCJIN0VbWqgKIziDrDIo6zts3FCqlI8jqDvIVvkeIJG4YI0dTGIooacYaECxvPoASjOuHEiAs5mhB3RVasmRXFSD8yjmEk9aunqPBS8usasET5IoSi5/7aXixXLYuZI8ZIbmFz3B8XCjBMtgjeZAurizd7ZBz+lSt8p+NC86HkyzMVhVtt4owbf5XE/UHpG0s6V1VstSbjcLRXF+0ehJpSI4j+qCv83JIlg3QVMmwNQmvS3plSMymr/CrEzqmLI176Ls7o3R/axIczpwS1NMVuUkwtUcQagxdsRqJgRTVoRypVFaTGSYhr20pRlfUVusghiIgdUFHcWiiZtu2gW7TnxdJWopxmirNT68lLBRqzrz5LrFOL6kuF+qvhvcs47v3VaC9o3ZhdNlrKndl0+6jVc803YqadWTpf6d2FFfhdEC5XYSOJ4Zj1t1LkXFhVDEPj/VFtHv4zA/FE62LcJ5Jmag1+F4CD1BUBvtQ4jBHXwoJcKZqWaCy+7aqZ11ypFk8MtzkpcX/5XKlFRwzFeTlyomEc3cFxeUCDHrFzA7TJWKHpKKynVOnw+5Fcp6l0nh7RsM907GKHXteFQ/aAmh2kaD+72tMenLYD9O0fjTvc5z5TrF9l7U6nqd+Zko1gW8/uwAyOR0E6imHwXFzzFnVwSl5sJEeM8ndvDbC9GguWXyobvfg6Jz1/FpUb9omPbxHN59r0xCzdqze3j1CtTtPYB2XzCVfU0JXabsRIveKKwrxXq42X3dM06S1afGZ7j5bCq5b0J+I4Zkdh+5sk27DVb4/xaf7q8KsQHLOivNTrv4pyoMRWtcw3k+lTG2asXF/n01BGOp9e/hD9PLP1Zkrk+26QbDijF913JwgXY6vnVfGWFOPHS8SHMCp3CrEwDOHyexKFfDiRgNdENNPwZogXUQf4ExbIShYEKHP1CtmnEftHUzEXgvawfbwUVTgxDu8HUguoggKRDdLnVVtGE4Q1V9BnQSeoVKcweBaRDTE4U1RHgwKRc4+Wgkl2f4KGhAghgUolUqnXf8OAe3KVflBIED84V6cARTyXDceGhXN1hFsoEBtoWKvATrQHb2eYEPsmYsFFgVuYDW0oh3PFhFA4DmSIh5glhNDDgn7oVYz1hvbQhf6DmFnU94bfl4i2tVNbSFqOuFwdaEF8OIn4FlH1pzFOiIkSdQrT4Aw3eEd6aD0WqAqIOIiVVYNK2ER06D3jN2r2MA2tiIeikH7jYGqViIRL14G9MIoi5grftn+7CIUIp4X2kA2d+F1BWBHKIGfFuIVx1kSiUIrOkIa2dQqlSBC0+IpQSF24dRqC6IXbaIgakQ1MlRGgB4wrZ45ogY1zFYDuqBTVZYbzCBTOx0mI5wqu0AvGd49WoXx81HoK8XWbCJAtkYqVUI4CoZDIiJA5IXwKN4TTBJFJsYHReBDLCFUWORRt2IODxUsl2JEZsX7gpUecBIgkyRFSmJEIUWRnJCoKJ7eSQaEMtXZHIGkQ66cKvHCQNMkS03BsISYKHgGAxlCFPxkUI4kVAQEAIfkECQQA8wAsAAAAAMYAxgCHAAAAAQEBAgICAwMDBAQEBQUFBgYGBwcHCAgICQkJCgoKCwsLDAwMDQ0NDg4ODw8PEBAQEREREhISExMTFBQUFRUVFhYWFxcXGBgYGRkZGhoaGxsbHBwcHR0dHh4eHx8fICAgISEhIiIiIyMjJCQkJSUlJiYmJycnKCgoKSkpKioqKysrLCwsLS0tLi4uLy8vMDAwMTExMjIyMzMzNDQ0NTU1NjY2Nzc3ODg4OTk5Ojo6Ozs7PDw8PT09Pj4+Pz8/QEBAQUFBQkJCR0ZEUU5HXVhLaWFPdWpSfnJVhXdWiXtXjH1Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Yjn5Zjn5Zjn5Zjn5Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39Zj39akIBbkYFckoNelYZil4hlmYtonI5sn5FxoZR1pJd4pZh6p5p9qZ2Aqp6CrKCFrqKIrqOJsKWLsaaOsqiRs6qVsKmbq6aep6SjpaKlo6GnoJ+qn5+qn5+qn5+qn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+rn5+roaGtoqKuo6OvpaWwp6exqamzqqq1rKy2rq63sK+4sbC4srG4s7K5t7W6u7m5v7u4wr23xL+0xr+zx8G1x8K4yMO8yMXBycbEysjJy8nKzMrLzszK0M3J0c7I0s/J09DJ1NHK1dLN19TQ2dbS29jV3NrY3t3a4d7a4+Da5OHa5uPc6OXf6ebi6efk6unm7Oro7ezq8O/u8fHx8vHy8vLz8/Pz8/P09PP09fX09/b09/b19/b29/f2+Pf3+Pj4+fj4+fj4+fn5+fn5+vr6+/v7/Pz8/Pz9/f39/v7+////////////////////////////////////////////////////////////////////////////////////////////////////CP4A5wkcSLCgwYMIExpUFgwWrEAQI/JSSLGixYsYM2rcyLGjx3mB0IgcOTLQx5MoU6pcybJiSJIk+7ScSbOmzZp9YMK8ybOnz58HdcJUtjHYRKBIkyodKJSkMY3X7qDRA+va0qtYaTYdCUujIZJ3qmYdO1ZZK45bRZ7FaEyonqNk4/68hkjtxrRoTGLUs7XPU7mAa8KSOhKuxWd49WCElTiw45XPcsK8Q/RiMLxo+gQyBItXZYPPCG+V9bj0x2CiYfaxmlDZw9SYSQaC9Vfgy7SsTevGWDetoYPBEMGOvfUOIl68MCPazfzi7dEDjQknTr06ms/Nsx9si9moZOvg8f7K1E7+YO/w6NOjMVy+PWL18KvfaU+f4Pn4+LeurU//ff7/OpHGX333AWjgfgOSF5WBDIrUxzMJlldgg/lRFmF2yVFIIXsXOjahhgBySNA1sCDY4U8fghjiQSQSJuKJNaWo4ooDtQgWhDDyxNiMM05ko07j5VhThjzOON1WXQm5kTKIYFeQMsMVKeWNSmZ0jWR98JKbQNfwBaIeEUH03ZQkLVflRTuOdEcrOM7zFYN9wBJMmwsF04qXZNZ2ZkILChWIUQb2IQudFikjC5486rVnQmnOGAihG/HynIYPLspnlAwGoudJwSDKoJmWItQohXcEMxOJDd7xYqjz9KlhIP5btqTMmPH14SSrBMmiooA3XTNpeojEiitBxtBKo08yUsfrsIxiqt6qMcJ3K7OgvZkftDYlqxy1FnWKn4lIaYsXpNwahKp6ii7VZXrLlpsQkeDdIWxS3IWnmLsUibsVtj+1kt60+A7kLF7pjuUpdeAGLNBl6AG8FLzV3atwQf6GB6pcBxNHbsDGErcpWRArO/GI6AUJ2DXo/TayQPVax+9S+uok8cijUrfxWAyDN6+7MaumG35hBuLw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39ri4Nzj4d3l49/m5OHn5ePo5uPq6OXs6+ju7evv7u3w8PDy8fLz8vLy8vPy8vPz8/T19fX39vb49/j4+Pj4+Pn5+Pn6+fr7+/v9/f3+/v7+/v7+/v7+/v7///////////////////////////////////////////////////////////////////////////////////////////////////////////////8I/gDjCRxIsKDBgwgTFsxmjNarV6vUKJxIsaLFixgzatzIsWM8aK/UdDlCsuSRZh5TqlzJsqXLicZMyjxCayO0lzhz6twJc6bJNhqhHQmz6ibPo0iTcozpk+QYjW1MqqmptKrVqwKZNj2SDaPQmWFedcVKtqxLrU2BYYzatMsrs3DjctxK0tXFr1vZyN3LdyLdI2kust0qq6/hoyg1/j1iEe9Wo4cjt4SmhqbixSXDqGHzipYxgoObPpVMWiWwkSSpWmyGma6azosLl56tMRubmYkVAnODurXvppBpC5+YbYzPLrkLNlvV+7dzmaOHS0fYrLlM5AWBVX7Ofabs6eAH/hqzDnYsrTDd08sMHn4668VjzqufT7LL5/btVdHf73vVWPzSZYMefwTS1YVaAEoHTIEMbuUfRQwlGNdtDVZo0hjsFURLF9hJWFY2W1go4hH2HURZSV1k6KFSsow4omrxZPPKTGP8t6JVabgoIlXADDiTRDdeRaGOFcqyHWFBVuUGkUy2pWKSL7XY5JTQQckTLVRmadJ3VrpUnZZaAtXlSwK6uEUaaKJpXJhj4jRkgWmoAsyT8TQDjCpr6shlmythWeAYsthYETSu+FghjHxWBMwqyREEDXnptdGoRsDkyOAWCCZqUTaoEdXom+qlcd9Kxhg63xaTaqqQK2CtohZa/ulhmpMrIdKXqaoQQlqfrr+lIaiXeUaKq0V+iqgKUtmE1t2owyYUbIOI8qTsc0A2ixCsDEZ7FKvqpWptPNMSqC1S4fom5rcF8TqfXWWB6hyduEJjKn+BmVVmd+yiS5B8BG4Bb1LvcReGvgfxa+te3HLHLMECZUPrfPXKde9zxzJskMO1KtxXsc4NbPFB+nEXMV/ztjapMSj/2+azvi0sF8f85Wttd9EZljF/en2LbWt78lVueh5bK+Vzv+61IIPo/kxXzYc16LKmljpXsWRR8/d0oupudeth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u7fDvbTFv7PHwLPJw7fLxr7MyMLOysTOy8bPzMjRzsvRz8/S0dHU09PY1tTb2djd29jf3Njg3drh393j4eDl4+Ln5ePn5ubo5+fq6ens6+vt7ezv7+7x8O/y8vL08/T29fX29vX39/f4+Pj5+fr8/Pz9/f3+/v7+/v7+/v7+/v7+/v7///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8I/gDhCRxIsKDBgwgTGkR2TaHDhxAjSpxIsaLFixgJXvvVyhAcLFhaWYw2rGHGkyhTqlxJEVmrPiBjguxjcdXMXMtY6tzJs+dDQzKDYolG8dpHmXdaEfXJtKnTizaFxsxFMZdUkIaGPd3KtetAXFexUrwTFssqr2jT8vxVFotJiMja/lJLt27GuGXnRlzUdqndv4AdLmu7KGK0tncCK15ssC2WiGDLnmVMWbHjo0H7GGKFaxgyzFf1Vh5dF6jj031Jq0bZKidFmKhjC22zurbFazDhuH64rBVo2cAL2x4OcRlm3Q6RmQbOPCYr4tATsg0KR+vBYbCbawd5R3T06NOl/nqHp3y7eZl9kEX8Nf572vChBV7je76+87cH54Ns754r/LC/5PKbfedVhxAyZMXEX39ORUXgg7K1gh88kQW1IIM+XbMHhByi1sdS0SwnlHoYcrVMGx2mWBYcyAwzoExw+FWiU/+paKNsfUw4o08O3ugjasLt2JSGPxbp2IVCsnSikUwKhWSSLNXY5I9PQrmSlFPaSJWVThmXpZGTccnUNQneaMiZhmwIpphO0cfhHq0MI2NBy+SySpkchslmT1hud0cuOjoUDS542kfinjxF86J2qxx6UXkE0oQoT26eZ8huKCGjZn24TKoTXue1UaVFuKBY35yeZlRoc4YEqtIy/ptuF2Sqgv2CqkB9xqZnT9f0qJ2jtCJk2h2L4ETQqsCNqpKvzBkSrEKDCQXHIrhUqJ2yK7VyHrDPDqStitiyxKxss3Y7ELL27dpVpczdGmy0HZbrFZnbdWqutym24aqJfmqEDDLVXjopuudtaZe1wGmGrL1sHtahs4DRy6GkbOa6HaZ2WazdvjNuRHBz8tr1sXbWIYrMuM2VHJjGzDE86aAjI0bZ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1dTZ19Ta2NTb2dfd3Nzg397i4uPl5Obm5ujo5+np6enr6+ru7ezv7uzv7u3x8O7z8vD19PL19fP39vT39vT39/X49/b4+Pf5+fj7+/v8/Pv8/Pz9/f3+/v7+/v7+/v7///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8I/gDdCRxIsKDBgwgTKlwo0JgxhhAjSpxIsaLFixgRTvOly+K0LCD3yPI1LaPJkyhTqqyoqxAekFmYsYRJE8+rhytz6tzJE6GvQnFogpRV8Y9Qob56Kl3KtCKzoEdB1qH4MSpMYU2zat060CrMpBJ1eQXJtaxZnnvG/plo1OvUs3DjZnw1NmbEql73yN3LN6LYsUQh/vUauK/hwwON1X3LsK3XWYgjH66b5ZUsQHugCq2TuS5WyaDlpqVMum7J0KjNFirNOuqb1LC5zmpNm2YhnHdvx94tUdjo2sDxdGRorE6WOLh5Hxb2SqKv38CjZ6kzHKEwzVmqK+f7d8/phMag/ksfj+dzwcE0tW+Hi75OcoLT6I6fL7TQd3errapfzxU9yDj7+YIdfQTGkdQ0gNS1H39N+eJVcwIhSOCER7lE2oIM9mTMgELhMU1xFIZYGx4ZZjUNh0ehKOKKVr1xX4k9icfijNG9ByNPxrxB447AYXijTv7xKOSDP2oV5JBIhlTkVvkl6WQdLy65VJNOCvmGjVIu9VKVQsYRZZZKTbPljnjsYaaZxu2oF5haJbgiHrII82VDurySpoiFsanUkeTpMmdCzMhy54RY6qnSUxQWIlNGz1EIpaE8uUnfHouiJMyY80EI6UrCEPgGWDnJMmGlm6I0aHR//HmSMZhGt2ap/ifx2VqePE1DZXTmwYrRqbX5qNOttb16kKq6uiNrab7uBCxtuTLjiyyZMVbsQby2lixPy7K2R50yZpGroX92Kh2tWzkmYiGlMgMSHoCMZF62pK0Fl5gsxlGqqFFxJp2LcinGIqh6tjohwHDhK6KmbKq7orBxTVPtfCQaeqx0pMo1cXTElihfiOge9vB4BEspMH0V73VxbZDp+WZk0+hIYB2ylLykuCFeqxp9f4QMJoghZmyWg9HVMYvPSxpj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3NrV29nX29rZ29rb3Nvb3Nvc3dzd397f4eHi5OPk5+bl6ejn7Ovp7ezr7+7t8fDu8vHw8/Ly9PPz9fX09vb09/b0+Pf1+Pf1+Pf2+Pj3+Pj4+Pj4+Pj4+Pj4+Pj4+Pj3+Pj3+fj3+fj3+vn4+/v6/v7+/v7+/v7+/v7+/v7+/v7+////////////////////////////////////////////////////////////////////////////////////////////////////////////CP4A5QkcSLCgwYMIEypEaKtOHVWrdCVbSLGixYsYM2rcyLHjxTpIQopE8jCXx5MoU6pcuTHXRI0jYyJpw7KmzZs4K+bSkwaJHo26ZI6sk7Oo0aMek/GM+fLiKqEiiSKdSrXqQF14oP7ECBIqEjxWw4q9mctryKYVe3pdNbatW4/RzPq8WEwu27d481aEIxetQlpyaekdTLigKrlbKWY1q6uw48FB+wrUZWuVwzZyZTbOmKvY488s0ciFwzez3M0XbSFJIxi0a4+LTcv2isdWtIqqReK5/bo3RsCzg0NNo8dzwtwj2xj3zVxhMuHQocKxdRB5zDSom2sviDm69+TUB/5aFxp++/ZiXb+rDwmn8Xio5c33jqZnvf2RseWalN+2c0Vdat0noGlg8TdWZHchFM1hAzYoFxq8GWhVMQHC4ZdAyZTm4IZC7SdhVRRe19pAuQTI4YkhqfKhVdFoGFMdLwGH4oxtRLgiUvV5xVqOM86Y3Y1GldXjkHKpCCRSyZhI5JIjxXdkTk8xKWVMHj6ZE49TSpnGclbixGCWWtrYpU3vDQmHQ2h2h6JUY+JUpoN1rJKLmAQVY4sqajZoZJs3vWlfHbZllAwted73I58r+eldcSflkp59NSJ606Pq4XFhR7oU+t2eBkVzqKQURbYeGlWuJON6fhWzSmmfgpqQpv7Q7ZZTMS56R1QytuChZGKuLqSobAnmRJ99tTLVK0WwBudkUVhuyOmxBv2a2bJGNdtgGtC+qh61R1k7YKnZyiNqdCOGlV+DvIYrj7eyFThWixxiq65AcXkXqVt1cQjusdJ6tW9YUaJ7aa/nzuYuXslGp8fAT6IHkZy6SCRPvdExHFa/mS3c66kCpovwd2isQieixdrHZV4YyyRrthvC4Vg0okH3rKv5NsjtW+yOFq6QDY6M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0cfW0snX08zY1dDZ19Pc2tXg3dfi39vk4t/m5OHo5uPp6OXr6efs6unt7Oru7ezv7u7w7+7x8fHy8vLy8vPz8/P09PT19PT29fT39vT39vX39/X49/b4+Pb5+Pf5+Pj5+fj5+fn6+vr8/Pz9/f39/f39/f7+/v7+/v7///////////////////////////////////////////////////////////////////////////////////////////////8I/gDnCRxIsKDBgwgTJkSFRonDh3H8GFNIsaLFixgzatzIsaPHeXUeinTI6qPJkyhTqlxZceTIkixjypxJU6ZLkTBr6tzJsyfBbTcf8vFJtKhRgtCSbTQW1OHQjduOSp1acVsqJXGiZmTa9GnGZEpIQaNKluy2Vg0dktLItanGbXEeii1Ll2iwtCKDZWTV1KFGVC5Taa1LOCY0P0HRjL3It2/GYEHjTCxMGeXdvn4oGmuVyg/eoIBaBVOqcNvnwIMrq2bb16Erg8FShWyNudXkgoj7mkm9urfF2X1JBwN0mnZrNKR6DXRFO6fv5xR70a6TKq7x6y7RpILcejf07xSt/mMfT974WvDoD0ovz779yMXp4wvcZsa9/fLn5etvfL8/bfj6xUeffwQG5VWA8pFS4IJ5IaifggxGWAdvDvq2DXARMogGaRVaiGGGGnLYYWXJiAdihmjcNiJh0BR3YoTKrdhTMCoadOGLOCqxoWYyntQiVjEWdGOOOO6oXlzO9bhRbg7F0UpqEBJZJIDbuGKiiEpixN1I2o3FnJRETjgPNGi5VEeWb7koV4518OGmmyZmSEqUNyWJpkL8galEHagYA2BByQSDyodEokHhnTaqCWIdvRyqEDSsxImjnYgWBJiUc31kDB9SGlopQtBIycefmhIq56cHlYijGXrF5Ep9/i+eiaqNdDIIiKMoJWMqgajMmlAwsC74mk7b1OqfZL4+ymmBQe5krH23JltRnvY1y9OX95lhrbQJ6Xrftjw9O14dpHK7kH3DHgWIfbiaa5CitAFCFVzupeuuQluWlxVZYLUXx70Vrdteq2RROx6WABcEb18Hzivpdb0mfFC+5JUr1XrlySqxpe3lV9fDxlkM8K7/UYYxeeAm3J7GhA2I38YEtYWyauK2xvLGBl/X7lQUYwfzQDX3dXNh7dWY8LLkRawa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") + 50% 50% no-repeat; +} \ No newline at end of file diff --git a/public/js/bread-public.js b/public/js/bread-public.js index 65b6bf68..b9ae4629 100644 --- a/public/js/bread-public.js +++ b/public/js/bread-public.js @@ -11,27 +11,61 @@ jQuery(document).ready(function($) { try { const x = await fetch(url); const json = await x.json(); - console.log(json); return json; } catch (e) {console.log(e)} } - const currentMeetingList = $(event.target).children('input[name="current-meeting-list"]').val(); - let config = await doFetch(ajax_object.ajax_url + '?action=bread_generate_queries_action¤t-meeting-list=' + currentMeetingList); + const $working = $("
    ", {"class": "working"}); + $("body").append($working); + const form = $(event.target); + const currentMeetingList = form.children('input[name="current-meeting-list"]').val(); + let config = await doFetch(ajax_object.ajax_url + '?action=bread_generate_queries_action¤t-meeting-list=' + currentMeetingList + '&nonce=' + ajax_object.nonce); const preload = {}; preload.mainResults = await doFetchP(config.root_server + '/' + config.main_query); if (config.extra_meetings_query) preload.extraMeetings = await doFetchP(config.root_server + '/' + config.extra_meetings_query); if (config.additional_list_query) preload.additionalListMeetings = await doFetchP(config.root_server + '/' + config.additional_list_query); preload.serviceBodies = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetServiceBodies'); preload.allFormats = {}; - const langs = config.weekday_language.split('_'); - for (let i = 0; i < langs.length; i++) { - preload.allFormats[langs[i]] = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + langs[i]); - } + const lang1 = config.weekday_language.substring(0, 2) + preload.allFormats[lang1] = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + lang1); if (config.additional_list_language && config.additional_list_language != '') preload.allFormats[config.additional_list_language] = await doFetchP(config.root_server + '/client_interface/jsonp/?switcher=GetFormats&lang_enum=' + config.additional_list_language); - $(event.target).children('input[name="preload"]').val(JSON.stringify(preload)); - $(event.target).unbind('submit'); - $(event.target).submit(); + form.children('input[name="preload"]').val(JSON.stringify(preload)); + const url = $(location).attr('href') ; + const formBody = $(event.target).serialize(); + const resp = await fetch(url, { + method: 'POST', + headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, + body: formBody + }); + const blob = await resp.blob(); + + // Ensure the blob is served as a PDF + const respContentType = (resp.headers.get('Content-Type') || '').toLowerCase(); + let pdfBlob = blob; + if (!respContentType.includes('pdf')) { + // Recreate blob with application/pdf type + pdfBlob = new Blob([await blob.arrayBuffer()], {type: 'application/pdf'}); + } + const blobUrl = URL.createObjectURL(pdfBlob); + + // Open a new blank window and embed the PDF for preview. + // If popup is blocked, fall back to triggering a download. + const w = window.open('', '_blank'); + if (w) { + w.document.title = 'PDF Preview'; + w.document.body.style.margin = '0'; + w.document.body.innerHTML = ''; + w.document.close(); + } else { + // Popup blocked — trigger download as fallback + const a = document.createElement('a'); + a.href = blobUrl; + a.download = 'meeting-list.pdf'; + document.body.appendChild(a); + a.click(); + a.remove(); + } + $working.remove(); return false; } )}) From 407eb9256c34d5902029c59b13eb86501d582911 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:29:34 +0200 Subject: [PATCH 14/34] lint --- public/class-bread-public.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 41bb5731..150a63be 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -98,7 +98,7 @@ public function enqueue_scripts() wp_localize_script($this->plugin_name, 'ajax_object', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bread-ajax-nonce'), // Can use the same even if there are multiple buttons on the page since WP nonces are single use but can be used multiple times within 24 hours - ]); + ]); } } /** From e20e734898cf89195885c55db371d98e4dbf507e Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:47:52 +0200 Subject: [PATCH 15/34] clear output buffer before returning because WP is insane --- public/class-bread-public.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 150a63be..52642583 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -131,6 +131,7 @@ public function generate_preload_configuration(): string } $id = intval($_GET['current-meeting-list'] ?? 1); $options = $this->bread->getConfigurationForSettingId($id); + ob_clean(); wp_send_json([ 'root_server' => $options['root_server'], 'main_query' => $this->bread->bmlt()->generateMainQuery('jsonp'), From 76354b4f055146d645ac0c2b41ef071aff4d7e97 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:52:44 +0200 Subject: [PATCH 16/34] Make build smaller 1 --- simplify-mpdf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplify-mpdf.sh b/simplify-mpdf.sh index 7eceb555..de0648f2 100755 --- a/simplify-mpdf.sh +++ b/simplify-mpdf.sh @@ -1 +1 @@ -find vendor/mpdf/mpdf/ttfonts -type f ! -name 'DejaVu*.ttf' -delete +find vendor/mpdf/mpdf/ttfonts -type f ! -name 'DejaVuSansC*.ttf' -delete From 5f2f91f0efcf1ac8aa5422c2580f25ac28447fcc Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:42:47 +0200 Subject: [PATCH 17/34] Robustness --- public/class-bread-public.php | 1 + public/js/bread-public.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 52642583..d2bfb2da 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -524,6 +524,7 @@ private function reorder_booklet_pages($mode) 'restrictColorSpace' => $this->options['colorspace'], ]; $mpdfOptions['format'] = $this->options['page_size'] . "-" . $this->options['page_orientation']; + $mpdfOptions['default_font'] = $this->options['base_font'] == "freesans" ? "dejavusanscondensed" : $this->options['base_font']; $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); $mpdftmp = new mPDF($mpdfOptions); $this->mpdf->shrink_tables_to_fit = 1; diff --git a/public/js/bread-public.js b/public/js/bread-public.js index b9ae4629..0db09f76 100644 --- a/public/js/bread-public.js +++ b/public/js/bread-public.js @@ -1,20 +1,28 @@ jQuery(document).ready(function($) { $('.bread_button_form').submit(async function(event) { event.preventDefault(); // Block default submission + const $working = $("
    ", {"class": "working"}); const doFetchP = async (url) => { try { const x = await fetchJsonp(url); return x.json(); - } catch (e) {console.log(e)} + } catch (e) { + $working.remove(); + alert(e); + console.log(e) + } } const doFetch = async (url) => { try { const x = await fetch(url); const json = await x.json(); return json; - } catch (e) {console.log(e)} + } catch (e) { + $working.remove(); + alert(e); + console.log(e) + } } - const $working = $("
    ", {"class": "working"}); $("body").append($working); const form = $(event.target); const currentMeetingList = form.children('input[name="current-meeting-list"]').val(); From 50c14fbda294f380867774a968ea2bd0972511c0 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:28:15 +0200 Subject: [PATCH 18/34] Allow custom font extensions --- admin/class-bread-admin.php | 38 ++--------- admin/css/admin-fonts.css | 24 ------- admin/partials/_layout_setup.php | 12 ++-- includes/class-bread.php | 10 ++- public/class-bread-public.php | 107 ++++++++++++++++++------------- 5 files changed, 86 insertions(+), 105 deletions(-) delete mode 100644 admin/css/admin-fonts.css diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index 93febba7..dc20d101 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -74,7 +74,6 @@ public function enqueue_styles($hook) wp_enqueue_style("tooltipster", plugin_dir_url(__FILE__) . "css/tooltipster.bundle.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("tooltipster-noir", plugin_dir_url(__FILE__) . "css/tooltipster-sideTip-noir.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("admin", plugin_dir_url(__FILE__) . "css/admin.css", false, BREAD_VERSION, 'all'); - wp_enqueue_style("bread-fonts", plugin_dir_url(__FILE__) . "css/admin-fonts.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("select2", plugin_dir_url(__FILE__) . "css/select2.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("smartWizard-dots", plugin_dir_url(__FILE__) . "css/smart_wizard_dots.css", false, BREAD_VERSION, 'all'); } @@ -204,46 +203,23 @@ function tiny_tweaks($initArray) { if (function_exists('get_current_screen')) { $screen = get_current_screen(); + $fonts = apply_filters('bread_custom_fonts', $this->bread->fonts); if ($screen != null && str_ends_with($screen->id, $this->bmltEnabled_admin->getSlug())) { $initArray['fontsize_formats'] = "5pt 6pt 7pt 8pt 9pt 10pt 11pt 12pt 13pt 14pt 15pt 16pt 17pt 18pt 19pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt 38pt"; $initArray['theme_advanced_blockformats'] = 'h2,h3,h4,p'; $initArray['wordpress_adv_hidden'] = false; - $initArray['font_formats'] = 'Arial (Sans-Serif)=arial;'; - $initArray['font_formats'] .= 'Times (Sans-Serif)=times;'; - $initArray['font_formats'] .= 'Courier (Monospace)=courier;'; - $initArray['font_formats'] .= 'DejaVu (Sans-Serif)=DejaVuSansCondensed;'; + $initArray['font_formats'] = ''; + foreach ($fonts as $font_key => $font_name) { + $initArray['font_formats'] .= "$font_name=$font_key;"; + } $dir = plugin_dir_url(__FILE__); $font = $this->bread->getOption('base_font'); - if ($font == 'dejavusanscondensed') { - $initArray['content_style'] = "@import url('$dir/css/fonts.css'); body { font-family: DejaVuSansCondensed; }"; - } elseif ($font == 'times') { - $initArray['content_style'] = "@import url('$dir/css/fonts.css'); body { font-family: Times; }"; - } elseif ($font == 'courier') { - $initArray['content_style'] = "@import url('$dir/css/fonts.css'); body { font-family: Courier; }"; - } else { - $initArray['content_style'] = "@import url('$dir/css/fonts.css'); body { font-family: Arial; }"; - } - //$initArray['content_style'] = "body { font-family: Arial; }"; + $initArray['content_style'] = apply_filters('bread_content_style', "@import url('$dir/css/fonts.css');"); + $initArray['content_style'] .= "body { font-family: $font; }"; } } return $initArray; } - function is_root_server_missing() - { - if (!function_exists('get_current_screen')) { - return; - } - $screen = get_current_screen(); - if ($screen != null && str_ends_with($screen->id, $this->bmltEnabled_admin->getSlug())) { - $root_server = $this->bread->getOption('root_server'); - if ($root_server == '') { - echo '

    Missing BMLT Server in settings for bread.

    '; - $url = admin_url('options-general.php?page=class-bread-admin.php'); - echo "

    Settings

    "; - echo '
    '; - } - } - } function pwsix_process_rename_settings() { diff --git a/admin/css/admin-fonts.css b/admin/css/admin-fonts.css deleted file mode 100644 index 93686b8f..00000000 --- a/admin/css/admin-fonts.css +++ /dev/null @@ -1,24 +0,0 @@ -@font-face { - font-family:'DejaVuSansCondensed'; - src:url(../../vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed.ttf) format('truetype'); - font-display: block; -} -@font-face { - font-family:'DejaVuSansCondensed'; - src:url(../../vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-Bold.ttf) format('truetype'); - font-display: block; - font-weight: bold; -} -@font-face { - font-family:'DejaVuSansCondensed'; - src:url(../../vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-BoldOblique.ttf) format('truetype'); - font-display: block; - font-weight: bold; - font-style: italic; -} -@font-face { - font-family:'DejaVuSansCondensed'; - src:url(../../vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-Oblique.ttf) format('truetype'); - font-display: block; - font-style: italic; -} \ No newline at end of file diff --git a/admin/partials/_layout_setup.php b/admin/partials/_layout_setup.php index 7f8659b3..f8a8662f 100644 --- a/admin/partials/_layout_setup.php +++ b/admin/partials/_layout_setup.php @@ -2,6 +2,13 @@ if (! defined('ABSPATH')) { exit; } +function echo_font_options($base_font, $bread) +{ + $fonts = apply_filters('bread_custom_fonts', $bread->fonts); + foreach ($fonts as $font_key => $font_name) { + echo ''; + } +} function Bread_layout_setup_page_render(Bread_AdminDisplay $breadAdmin) { $bread = $breadAdmin->getBreadInstance(); @@ -152,10 +159,7 @@ function Bread_layout_setup_page_render(Bread_AdminDisplay $breadAdmin) diff --git a/includes/class-bread.php b/includes/class-bread.php index 1d63ba50..9f327506 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -56,6 +56,7 @@ class Bread private string $optionsName; private $allSettings = array(); private $maxSetting = 1; + public$fonts = []; /** * The setting we are editing, generating or otherwise working with. Generally set with query string "?current-meeting-list". * @@ -385,6 +386,14 @@ public function __construct() $this->load_translations(); $this->define_admin_hooks(); $this->define_public_hooks(); + + $this->fonts = [ + 'dejavusanscondensed' => __('DejaVu Sans Condensed', 'bread'), + 'ccourier' => __('Courier', 'bread'), + 'ctimes' => __('Times', 'bread'), + 'chelvetica' => __('Helvetica', 'bread'), + 'arial' => __('Arial', 'bread'), + ]; } function load_translations() { @@ -501,7 +510,6 @@ private function define_admin_hooks() $this->loader->add_filter('tiny_mce_before_init', $plugin_admin, 'tiny_tweaks'); $this->loader->add_filter('mce_external_plugins', $plugin_admin, 'my_custom_plugins'); $this->loader->add_filter('mce_buttons', $plugin_admin, 'my_register_mce_button'); - //add_action("admin_notices", $plugin_admin, "is_root_server_missing"); $this->loader->add_action("admin_init", $plugin_admin, "my_theme_add_editor_styles"); $this->loader->add_action("wp_default_editor", $plugin_admin, "ml_default_editor"); $this->loader->add_filter('tiny_mce_version', $plugin_admin, 'force_mce_refresh'); diff --git a/public/class-bread-public.php b/public/class-bread-public.php index d2bfb2da..4beab26b 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -3,10 +3,11 @@ exit; } use Mpdf\Mpdf; +use Mpdf\Config\ConfigVariables; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Level; - +define('MPDF_MODE', 's'); // Try to keep the file small by subsetting fonts and only including the characters we need. This is especially important for languages with large character sets like Chinese, Japanese, and Korean. If you have a lot of different characters in your meeting list, you may want to switch to 'utf-8' or 'c' mode which includes more characters but results in larger file sizes. /** * Main class for generating the PDF meeting list. * @@ -19,7 +20,6 @@ */ class Bread_Public { - /** * The ID of this plugin. * @@ -199,13 +199,7 @@ public function bmlt_meeting_list() exit; } } - $page_type_settings = $this->constuct_page_type_settings(); - $default_font = $this->options['base_font'] == "freesans" ? "dejavusanscondensed" : $this->options['base_font']; - $mode = 's'; - $mpdf_init_options = $this->construct_init_options($default_font, $mode, $page_type_settings); - if (isset($this->options['packTabledata']) && $this->options['packTabledata']) { - $mpdf_init_options['packTabledata'] = true; - } + @ob_end_clean(); // We load mPDF only when we need to and as late as possible. This prevents // conflicts with other plugins that use the same PSRs in different versions @@ -214,6 +208,11 @@ public function bmlt_meeting_list() include_once plugin_dir_path(__FILE__) . '../vendor/autoload.php'; require_once __DIR__ . '/class-bread-content-generator.php'; require_once __DIR__ . '/class-bread-format-manager.php'; + $default_font = $this->get_default_font(); + $mpdf_init_options = $this->construct_init_options($default_font); + if (isset($this->options['packTabledata']) && $this->options['packTabledata']) { + $mpdf_init_options['packTabledata'] = true; + } $this->mpdf = new mPDF($mpdf_init_options); if (isset($this->options['logging']) && $this->options['logging']) { $logger = new Logger('bread-log'); @@ -244,7 +243,7 @@ public function bmlt_meeting_list() if ($this->options['column_line'] == 1 && ($this->options['page_fold'] == 'tri' || $this->options['page_fold'] == 'quad') ) { - $this->drawLinesSeperatingColumns($mode, $mpdf_init_options['format'], $default_font); + $this->drawLinesSeperatingColumns($mpdf_init_options['format'], $default_font); } $result = $this->bread->bmlt()->doMainQuery(); @@ -295,7 +294,7 @@ public function bmlt_meeting_list() $generator = new Bread_ContentGenerator($this->mpdf, $this->bread, $result_meetings, $formatsManager); $generator->generate($num_columns); $this->mpdf->SetDisplayMode('fullpage', 'two'); - $this->reorder_booklet_pages($mode); + $this->reorder_booklet_pages(); if ($this->options['include_protection'] == 1) { // 'copy','print','modify','annot-forms','fill-forms','extract','assemble','print-highres' $this->mpdf->SetProtection(array('copy', 'print', 'print-highres'), '', $this->options['protection_password']); @@ -319,7 +318,7 @@ public function bmlt_meeting_list() } $this->bread->removeTempDir(); } - private function constuct_page_type_settings() + private function construct_page_type_settings(): array { $page_type_settings = array(); // TODO: The page number is always 5 from botton...this should be adjustable @@ -377,42 +376,41 @@ private function constuct_page_type_settings() } return $page_type_settings; } - private function construct_init_options($default_font, $mode, $page_type_settings): array + private function construct_init_options(string $default_font): array { - if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { - $mpdf_init_options = [ - + $defaultConfig = (new ConfigVariables())->getDefaults(); + $mpdf_init_options = [ + 'mode' => 's', 'tempDir' => $this->bread->temp_dir(), - 'mode' => $mode, 'default_font_size' => 7, 'default_font' => $default_font, - 'useSubstitutions' => false, 'margin_left' => $this->options['margin_left'], 'margin_right' => $this->options['margin_right'], 'margin_top' => $this->options['margin_top'], 'margin_bottom' => $this->options['margin_bottom'], 'margin_header' => $this->options['margin_header'], - ]; - } else { - $mpdf_init_options = [ - 'mode' => $mode, - 'tempDir' => $this->bread->temp_dir(), - 'default_font_size' => 7, - 'default_font' => $default_font, - 'margin_left' => $this->options['margin_left'], - 'margin_right' => $this->options['margin_right'], - 'margin_top' => $this->options['margin_top'], - 'margin_bottom' => $this->options['margin_bottom'], - 'margin_header' => $this->options['margin_header'], - ]; + 'restrictColorSpace' => $this->options['colorspace'], + 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. + 'fontData' => [ + "dejavusanscondensed" => [ + 'R' => "DejaVuSansCondensed.ttf", + 'B' => "DejaVuSansCondensed-Bold.ttf", + 'I' => "DejaVuSansCondensed-Oblique.ttf", + 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], // Set these, so that life is easier to add custom fonts in filters. + ], + ]; + if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { + $mpdf_init_options['useSubstitutions'] = true; } - $mpdf_init_options['restrictColorSpace'] = $this->options['colorspace']; - $mpdf_init_options = array_merge($mpdf_init_options, $page_type_settings); + $mpdf_init_options = array_merge($mpdf_init_options, $this->construct_page_type_settings()); $mpdf_init_options = apply_filters("Bread_Mpdf_Init_Options", $mpdf_init_options, $this->options); return $mpdf_init_options; } - private function drawLinesSeperatingColumns($mode, $format, $default_font) + private function drawLinesSeperatingColumns($format, $default_font) { $html = ''; if ($this->options['page_fold'] == 'tri') { @@ -440,7 +438,7 @@ private function drawLinesSeperatingColumns($mode, $format, $default_font) } $mpdf_column = new mPDF( [ - 'mode' => $mode, + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'format' => $format, 'default_font_size' => 7, @@ -462,13 +460,13 @@ private function drawLinesSeperatingColumns($mode, $format, $default_font) $tplId = $this->mpdf->importPage($pagecount); $this->mpdf->SetPageTemplate($tplId); } - private function reorder_booklet_pages($mode) + private function reorder_booklet_pages() { if ($this->options['page_fold'] == 'half') { $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_half'); $this->mpdf->Output($FilePath, 'F'); $mpdfOptions = [ - 'mode' => $mode, + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'default_font_size' => '', 'margin_left' => 0, @@ -511,8 +509,9 @@ private function reorder_booklet_pages($mode) } else if ($this->options['page_fold'] == 'full' && $this->options['booklet_pages']) { $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_full'); $this->mpdf->Output($FilePath, 'F'); + $defaultConfig = (new ConfigVariables())->getDefaults(); $mpdfOptions = [ - 'mode' => $mode, + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'default_font_size' => '', 'margin_left' => 0, @@ -522,9 +521,20 @@ private function reorder_booklet_pages($mode) 'margin_footer' => 6, 'orientation' => $this->options['page_orientation'], 'restrictColorSpace' => $this->options['colorspace'], + 'format' => $this->options['page_size'] . "-" . $this->options['page_orientation'], + 'default_font' => $this->get_default_font(), + 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. + 'fontData' => [ + "dejavusanscondensed" => [ + 'R' => "DejaVuSansCondensed.ttf", + 'B' => "DejaVuSansCondensed-Bold.ttf", + 'I' => "DejaVuSansCondensed-Oblique.ttf", + 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], + ], ]; - $mpdfOptions['format'] = $this->options['page_size'] . "-" . $this->options['page_orientation']; - $mpdfOptions['default_font'] = $this->options['base_font'] == "freesans" ? "dejavusanscondensed" : $this->options['base_font']; $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); $mpdftmp = new mPDF($mpdfOptions); $this->mpdf->shrink_tables_to_fit = 1; @@ -546,7 +556,7 @@ private function reorder_booklet_pages($mode) $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_pocket'); $this->mpdf->Output($FilePath, 'F'); $mpdfOptions = [ - 'mode' => $mode, + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'default_font_size' => '', 'margin_left' => 0, @@ -579,7 +589,7 @@ private function reorder_booklet_pages($mode) $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_flyer'); $this->mpdf->Output($FilePath, 'F'); $mpdfOptions = [ - 'mode' => $mode, + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'default_font_size' => '', 'margin_left' => 0, @@ -617,7 +627,14 @@ private function reorder_booklet_pages($mode) $this->mpdf = $mpdftmp; } } - function get_booklet_pages($np, $backcover = true) + private function get_default_font() + { + if ($this->options['base_font'] == "freesans") { + return "dejavusanscondensed"; + } + return $this->options['base_font']; + } + private function get_booklet_pages($np, $backcover = true) { $lastpage = $np; $np = 4 * ceil($np / 4); @@ -639,7 +656,7 @@ function get_booklet_pages($np, $backcover = true) } return $pp; } - function get_FilePath($pos = '') + private function get_FilePath($pos = '') { $site = ''; if (is_multisite()) { @@ -647,7 +664,7 @@ function get_FilePath($pos = '') } return "meetinglist_" . $site . $this->bread->getRequestedSetting() . $pos . '_' . strtolower(gmdate("njYghis")) . ".pdf"; } - function columnSeparators($oh) + private function columnSeparators($oh) { if ($this->options['column_line'] == 1) { return ' From 10c95083ab791165c9905b2e70e31c7148ac9697 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:08:39 +0200 Subject: [PATCH 19/34] Keep more DejaVu fonts as replacements for standard fonts --- includes/class-bread.php | 13 +++++++++---- public/class-bread-public.php | 11 ++--------- readme.txt | 2 ++ simplify-mpdf.sh | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/includes/class-bread.php b/includes/class-bread.php index 9f327506..d49e43cb 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -389,10 +389,11 @@ public function __construct() $this->fonts = [ 'dejavusanscondensed' => __('DejaVu Sans Condensed', 'bread'), - 'ccourier' => __('Courier', 'bread'), - 'ctimes' => __('Times', 'bread'), - 'chelvetica' => __('Helvetica', 'bread'), - 'arial' => __('Arial', 'bread'), + 'times' => __('New Times Roman (DejaVu Serif)', 'bread'), + 'courier' => __('Courier (DejaVu Sans Mono)', 'bread'), + 'ccourier' => __('PDF Core Font - Courier', 'bread'), + 'ctimes' => __('PDF Core Font - New Times Roman', 'bread'), + 'chelvetica' => __('PDF Core Font - Helvetica', 'bread'), ]; } function load_translations() @@ -823,6 +824,10 @@ private function upgrade_settings(): void unset($this->options['service_body_3']); unset($this->options['service_body_4']); unset($this->options['service_body_5']); + + if ($this->options['base_font'] == 'arial') { + $this->options['base_font'] = 'dejabusanscondensed'; + } } $this->options['bread_version'] = BREAD_VERSION; } diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 4beab26b..0165d271 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -208,7 +208,7 @@ public function bmlt_meeting_list() include_once plugin_dir_path(__FILE__) . '../vendor/autoload.php'; require_once __DIR__ . '/class-bread-content-generator.php'; require_once __DIR__ . '/class-bread-format-manager.php'; - $default_font = $this->get_default_font(); + $default_font = $this->options['base_font']; $mpdf_init_options = $this->construct_init_options($default_font); if (isset($this->options['packTabledata']) && $this->options['packTabledata']) { $mpdf_init_options['packTabledata'] = true; @@ -522,7 +522,7 @@ private function reorder_booklet_pages() 'orientation' => $this->options['page_orientation'], 'restrictColorSpace' => $this->options['colorspace'], 'format' => $this->options['page_size'] . "-" . $this->options['page_orientation'], - 'default_font' => $this->get_default_font(), + 'default_font' => $this->options['base_font'], 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. 'fontData' => [ "dejavusanscondensed" => [ @@ -627,13 +627,6 @@ private function reorder_booklet_pages() $this->mpdf = $mpdftmp; } } - private function get_default_font() - { - if ($this->options['base_font'] == "freesans") { - return "dejavusanscondensed"; - } - return $this->options['base_font']; - } private function get_booklet_pages($np, $backcover = true) { $lastpage = $np; diff --git a/readme.txt b/readme.txt index 6665f427..7f29bb7d 100644 --- a/readme.txt +++ b/readme.txt @@ -57,6 +57,8 @@ Follow all these steps, keep in mind that once you start using bread, it's not g = 2.10.0 = * [bread_button] shortcode added that can generate PDFs even when the hosting provider has blocked access to the BMLT server. +* PDF Core Fonts (Helvertica, New Times Roman and Courier) may be selected ++ Extension points added to allow for custom fonts = 2.9.15 = * More commas cleaned up. diff --git a/simplify-mpdf.sh b/simplify-mpdf.sh index de0648f2..5afb4af9 100755 --- a/simplify-mpdf.sh +++ b/simplify-mpdf.sh @@ -1 +1 @@ -find vendor/mpdf/mpdf/ttfonts -type f ! -name 'DejaVuSansC*.ttf' -delete +find vendor/mpdf/mpdf/ttfonts -type f ! \( -name 'DejaVuSansC*.ttf' -o -name 'DejaVuSerifC*.ttf' -o -name 'DejaVuSansMono*.ttf' \) -delete From 908787d1dc8bd22c2c64a6517837fb3ee333a503 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:57:29 +0200 Subject: [PATCH 20/34] lint --- includes/class-bread.php | 2 +- public/class-bread-public.php | 37 ++++++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/includes/class-bread.php b/includes/class-bread.php index d49e43cb..8a27021a 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -56,7 +56,7 @@ class Bread private string $optionsName; private $allSettings = array(); private $maxSetting = 1; - public$fonts = []; + public $fonts = []; /** * The setting we are editing, generating or otherwise working with. Generally set with query string "?current-meeting-list". * diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 0165d271..0584942f 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -7,6 +7,7 @@ use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Level; + define('MPDF_MODE', 's'); // Try to keep the file small by subsetting fonts and only including the characters we need. This is especially important for languages with large character sets like Chinese, Japanese, and Korean. If you have a lot of different characters in your meeting list, you may want to switch to 'utf-8' or 'c' mode which includes more characters but results in larger file sizes. /** * Main class for generating the PDF meeting list. @@ -392,15 +393,15 @@ private function construct_init_options(string $default_font): array 'restrictColorSpace' => $this->options['colorspace'], 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. 'fontData' => [ - "dejavusanscondensed" => [ - 'R' => "DejaVuSansCondensed.ttf", - 'B' => "DejaVuSansCondensed-Bold.ttf", - 'I' => "DejaVuSansCondensed-Oblique.ttf", - 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", - 'useOTL' => 0xFF, - 'useKashida' => 75, - ], // Set these, so that life is easier to add custom fonts in filters. - ], + "dejavusanscondensed" => [ + 'R' => "DejaVuSansCondensed.ttf", + 'B' => "DejaVuSansCondensed-Bold.ttf", + 'I' => "DejaVuSansCondensed-Oblique.ttf", + 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], // Set these, so that life is easier to add custom fonts in filters. + ], ]; if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { $mpdf_init_options['useSubstitutions'] = true; @@ -509,7 +510,7 @@ private function reorder_booklet_pages() } else if ($this->options['page_fold'] == 'full' && $this->options['booklet_pages']) { $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_full'); $this->mpdf->Output($FilePath, 'F'); - $defaultConfig = (new ConfigVariables())->getDefaults(); + $defaultConfig = (new ConfigVariables())->getDefaults(); $mpdfOptions = [ 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), @@ -525,14 +526,14 @@ private function reorder_booklet_pages() 'default_font' => $this->options['base_font'], 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. 'fontData' => [ - "dejavusanscondensed" => [ - 'R' => "DejaVuSansCondensed.ttf", - 'B' => "DejaVuSansCondensed-Bold.ttf", - 'I' => "DejaVuSansCondensed-Oblique.ttf", - 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", - 'useOTL' => 0xFF, - 'useKashida' => 75, - ], + "dejavusanscondensed" => [ + 'R' => "DejaVuSansCondensed.ttf", + 'B' => "DejaVuSansCondensed-Bold.ttf", + 'I' => "DejaVuSansCondensed-Oblique.ttf", + 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], ], ]; $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); From 5ff33fc15d2ab8af67cf472475069bf2fda3601d Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:12:04 +0200 Subject: [PATCH 21/34] Test adding fonts through plugins --- admin/css/fonts.css | 12 +++++++++ public/class-bread-content-generator.php | 8 +++--- public/class-bread-public.php | 31 ++++++------------------ 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/admin/css/fonts.css b/admin/css/fonts.css index cc6106df..5a91b439 100644 --- a/admin/css/fonts.css +++ b/admin/css/fonts.css @@ -21,4 +21,16 @@ src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-Oblique.ttf) format('truetype'); font-display: block; font-style: italic; +} +@font-face { + font-family: ccourier; + src: local('Courier'); +} +@font-face { + font-family: ctimes; + src: local('Times New Roman'); +} +@font-face { + font-family: chelvetica; + src: local('Helvetica'); } \ No newline at end of file diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php index df3fe591..17acf4d7 100644 --- a/public/class-bread-content-generator.php +++ b/public/class-bread-content-generator.php @@ -143,10 +143,10 @@ function __construct(Mpdf $mpdf, Bread $bread, array $result_meetings, Bread_For ); $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'] . '
    '); - $this->mpdf->DefHTMLFooterByName('_default', '
    ' . $this->options['nonmeeting_footer'] . '
    '); - $this->mpdf->DefHTMLFooterByName('Meeting1Footer', '
    ' . $this->options['meeting1_footer'] . '
    '); - $this->mpdf->DefHTMLFooterByName('Meeting2Footer', '
    ' . $this->options['meeting2_footer'] . '
    '); + @$this->mpdf->DefHTMLFooterByName('MyFooter', '
    ' . $this->options['nonmeeting_footer'] . '
    '); + @$this->mpdf->DefHTMLFooterByName('_default', '
    ' . $this->options['nonmeeting_footer'] . '
    '); + @$this->mpdf->DefHTMLFooterByName('Meeting1Footer', '
    ' . $this->options['meeting1_footer'] . '
    '); + @$this->mpdf->DefHTMLFooterByName('Meeting2Footer', '
    ' . $this->options['meeting2_footer'] . '
    '); } if (!empty($this->options['pageheader_content'])) { $data = $this->standard_shortcode_replacement('pageheader'); diff --git a/public/class-bread-public.php b/public/class-bread-public.php index 0584942f..3714eb26 100644 --- a/public/class-bread-public.php +++ b/public/class-bread-public.php @@ -4,11 +4,12 @@ } use Mpdf\Mpdf; use Mpdf\Config\ConfigVariables; +use Mpdf\Config\FontVariables; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Level; -define('MPDF_MODE', 's'); // Try to keep the file small by subsetting fonts and only including the characters we need. This is especially important for languages with large character sets like Chinese, Japanese, and Korean. If you have a lot of different characters in your meeting list, you may want to switch to 'utf-8' or 'c' mode which includes more characters but results in larger file sizes. +define('MPDF_MODE', 'utf-8-s'); // Try to keep the file small by subsetting fonts and only including the characters we need. This is especially important for languages with large character sets like Chinese, Japanese, and Korean. If you have a lot of different characters in your meeting list, you may want to switch to 'utf-8' or 'c' mode which includes more characters but results in larger file sizes. /** * Main class for generating the PDF meeting list. * @@ -214,7 +215,7 @@ public function bmlt_meeting_list() if (isset($this->options['packTabledata']) && $this->options['packTabledata']) { $mpdf_init_options['packTabledata'] = true; } - $this->mpdf = new mPDF($mpdf_init_options); + $this->mpdf = @new mPDF($mpdf_init_options); if (isset($this->options['logging']) && $this->options['logging']) { $logger = new Logger('bread-log'); $site = ''; @@ -380,8 +381,9 @@ private function construct_page_type_settings(): array private function construct_init_options(string $default_font): array { $defaultConfig = (new ConfigVariables())->getDefaults(); + $defaultFontsConfig = (new FontVariables())->getDefaults(); $mpdf_init_options = [ - 'mode' => 's', + 'mode' => MPDF_MODE, 'tempDir' => $this->bread->temp_dir(), 'default_font_size' => 7, 'default_font' => $default_font, @@ -392,16 +394,7 @@ private function construct_init_options(string $default_font): array 'margin_header' => $this->options['margin_header'], 'restrictColorSpace' => $this->options['colorspace'], 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. - 'fontData' => [ - "dejavusanscondensed" => [ - 'R' => "DejaVuSansCondensed.ttf", - 'B' => "DejaVuSansCondensed-Bold.ttf", - 'I' => "DejaVuSansCondensed-Oblique.ttf", - 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", - 'useOTL' => 0xFF, - 'useKashida' => 75, - ], // Set these, so that life is easier to add custom fonts in filters. - ], + 'fontdata' => $defaultFontsConfig ['fontdata'], // Set these, so that life is easier to add custom fonts in filters. ]; if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { $mpdf_init_options['useSubstitutions'] = true; @@ -524,17 +517,7 @@ private function reorder_booklet_pages() 'restrictColorSpace' => $this->options['colorspace'], 'format' => $this->options['page_size'] . "-" . $this->options['page_orientation'], 'default_font' => $this->options['base_font'], - 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. - 'fontData' => [ - "dejavusanscondensed" => [ - 'R' => "DejaVuSansCondensed.ttf", - 'B' => "DejaVuSansCondensed-Bold.ttf", - 'I' => "DejaVuSansCondensed-Oblique.ttf", - 'BI' => "DejaVuSansCondensed-BoldOblique.ttf", - 'useOTL' => 0xFF, - 'useKashida' => 75, - ], - ], + 'fontDir' => $defaultConfig['fontDir'], // Set these, so that life is easier to add custom fonts in filters. ]; $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); $mpdftmp = new mPDF($mpdfOptions); From 1d93db6de98be05686c52d688cfeb46aa639e32a Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:52:38 +0200 Subject: [PATCH 22/34] Load Remote Fonts --- admin/class-bread-admin.php | 43 ++- admin/css/fonts.css | 48 ++++ admin/partials/_custom_fonts_setup.php | 78 ++++++ admin/partials/_layout_setup.php | 11 +- admin/partials/bread-admin-display.php | 9 + bmlt-meeting-list.php | 1 + .../class-bread-loadable-fonts.php | 250 ++++++++++++++++++ includes/class-bread.php | 67 ++++- 8 files changed, 486 insertions(+), 21 deletions(-) create mode 100644 admin/partials/_custom_fonts_setup.php create mode 100644 bread_loadable_fonts/class-bread-loadable-fonts.php diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index dc20d101..74224eaa 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -73,10 +73,16 @@ public function enqueue_styles($hook) wp_enqueue_style("spectrum", plugin_dir_url(__FILE__) . "css/spectrum.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("tooltipster", plugin_dir_url(__FILE__) . "css/tooltipster.bundle.min.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("tooltipster-noir", plugin_dir_url(__FILE__) . "css/tooltipster-sideTip-noir.min.css", false, BREAD_VERSION, 'all'); - wp_enqueue_style("admin", plugin_dir_url(__FILE__) . "css/admin.css", false, BREAD_VERSION, 'all'); + wp_enqueue_style("bread-admin", plugin_dir_url(__FILE__) . "css/admin.css", false, BREAD_VERSION, 'all'); wp_enqueue_style("select2", plugin_dir_url(__FILE__) . "css/select2.min.css", false, BREAD_VERSION, 'all'); + wp_add_inline_style("bread-admin", $this->generate_custom_fonts_script()); wp_enqueue_style("smartWizard-dots", plugin_dir_url(__FILE__) . "css/smart_wizard_dots.css", false, BREAD_VERSION, 'all'); } + public function generate_custom_fonts_script(): string + { + $dir = plugin_dir_url(__FILE__); + return apply_filters('bread_content_style', "@import url('$dir/css/fonts.css');"); + } /** * Register the JavaScript for the admin area. * @@ -197,24 +203,27 @@ function my_custom_plugins() } return $plugins_array; } - // Enable font size & font family selects in the editor function tiny_tweaks($initArray) { if (function_exists('get_current_screen')) { $screen = get_current_screen(); - $fonts = apply_filters('bread_custom_fonts', $this->bread->fonts); + $fonts = $this->bread->getAvailableFonts(); + $active_fonts = $this->bread->getActiveFonts(); if ($screen != null && str_ends_with($screen->id, $this->bmltEnabled_admin->getSlug())) { $initArray['fontsize_formats'] = "5pt 6pt 7pt 8pt 9pt 10pt 11pt 12pt 13pt 14pt 15pt 16pt 17pt 18pt 19pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt 38pt"; $initArray['theme_advanced_blockformats'] = 'h2,h3,h4,p'; $initArray['wordpress_adv_hidden'] = false; $initArray['font_formats'] = ''; - foreach ($fonts as $font_key => $font_name) { + foreach ($fonts as $font_key => $font_info) { + if (!in_array($font_key, $active_fonts)) { + continue; + } + $font_name = $font_info['name']; $initArray['font_formats'] .= "$font_name=$font_key;"; } - $dir = plugin_dir_url(__FILE__); $font = $this->bread->getOption('base_font'); - $initArray['content_style'] = apply_filters('bread_content_style', "@import url('$dir/css/fonts.css');"); + $initArray['content_style'] = $this->generate_custom_fonts_script(); $initArray['content_style'] .= "body { font-family: $font; }"; } } @@ -422,6 +431,27 @@ function admin_submenu_link($parent_slug) function admin_options_page() { $filename = ''; + if (!empty($_GET['fontAction'])) { + $action = sanitize_key($_GET['fontAction']); + if (!wp_verify_nonce($_GET['nonce'], 'bread_font_action')) { + wp_die('Request invalid due to timeout'); + } + $fonts = $this->bread->getAvailableFonts(); + $font = $_GET['font']; + if (!isset($fonts[$font])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font]['actions'])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font]['actions'][$action])) { + wp_die('Request invalid'); + } + call_user_func($fonts[$font]['actions'][$action]['lambda'], $font); + } if (!empty($_POST['pwsix_action']) && (!isset($_POST['bmltmeetinglistsave']) || $_POST['bmltmeetinglistsave'] != 'Save Changes')) { switch ($_POST['pwsix_action']) { case 'settings_admin': @@ -436,6 +466,7 @@ function admin_options_page() case 'import_settings': $filename = $this->pwsix_process_settings_import(); break; + case 'uploadFont': default: break; } diff --git a/admin/css/fonts.css b/admin/css/fonts.css index 5a91b439..5ca85866 100644 --- a/admin/css/fonts.css +++ b/admin/css/fonts.css @@ -22,6 +22,54 @@ font-display: block; font-style: italic; } +@font-face { + font-family:'DejaVuSerifCondensed'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSerifCondensed.ttf) format('truetype'); + font-display: block; +} +@font-face { + font-family:'DejaVuSerifCondensed'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSerifCondensed-Bold.ttf) format('truetype'); + font-display: block; + font-weight: bold; +} +@font-face { + font-family:'DejaVuSerifCondensed'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSerifCondensed-BoldOblique.ttf) format('truetype'); + font-display: block; + font-weight: bold; + font-style: italic; +} +@font-face { + font-family:'DejaVuSerifCondensed'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSerifCondensed-Oblique.ttf) format('truetype'); + font-display: block; + font-style: italic; +} +@font-face { + font-family:'DejaVuSansMono'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSansMono.ttf) format('truetype'); + font-display: block; +} +@font-face { + font-family:'DejaVuSansMono'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSansMono-Bold.ttf) format('truetype'); + font-display: block; + font-weight: bold; +} +@font-face { + font-family:'DejaVuSansMono'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSansMono-BoldOblique.ttf) format('truetype'); + font-display: block; + font-weight: bold; + font-style: italic; +} +@font-face { + font-family:'DejaVuSansMono'; + src:url(../../../vendor/mpdf/mpdf/ttfonts/DejaVuSansMono-Oblique.ttf) format('truetype'); + font-display: block; + font-style: italic; +} @font-face { font-family: ccourier; src: local('Courier'); diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php new file mode 100644 index 00000000..a5065e18 --- /dev/null +++ b/admin/partials/_custom_fonts_setup.php @@ -0,0 +1,78 @@ +bread = $bread; + $this->fonts = $bread->getAvailableFonts(); + $this->nonce = wp_create_nonce("bread_font_action"); + return parent::__construct(); + } + function get_columns() + { + return [ + 'name' => 'Font Family', + 'type' => 'Type', + 'scripts' => 'Character Sets', + 'description' => 'Description', + ]; + } + function column_default($font, $column) + { + switch ($column) { + case 'type': + $stack = $font['stack']; + $array = explode(',', $stack); + return $array[count($array)-1]; + case 'scripts': + return implode(',', $font['scripts']); + default: + if (!isset($font[$column])) { + return ''; + } + return $font[$column]; + } + } + function column_name($font) + { + $actions = []; + if (isset($font['actions'])) { + foreach ($font['actions'] as $key => $action) { + $actions[$key] = sprintf('' . $action['text'] . '', $_REQUEST['page'], $action['action'], $font['slug'], $this->nonce); + } + } + return sprintf('%1$s %2$s', $font['name'], $this->row_actions($actions)); + } + function prepare_items() + { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = array(); + $this->_column_headers = array($columns, $hidden, $sortable); + $this->items = $this->fonts; + foreach ($this->items as $slug => &$info) { + $info['slug'] = $slug; + } + } +} + +function Bread_custom_fonts_setup_page_render(Bread_AdminDisplay $breadAdminDisplay) +{ + $table = new Bread_Custom_Fonts_Table($breadAdminDisplay->getBreadInstance()); + $table->prepare_items(); + $table->display(); +} diff --git a/admin/partials/_layout_setup.php b/admin/partials/_layout_setup.php index f8a8662f..686d92c5 100644 --- a/admin/partials/_layout_setup.php +++ b/admin/partials/_layout_setup.php @@ -2,11 +2,14 @@ if (! defined('ABSPATH')) { exit; } -function echo_font_options($base_font, $bread) +function echo_font_options(string $base_font, Bread $bread) { - $fonts = apply_filters('bread_custom_fonts', $bread->fonts); - foreach ($fonts as $font_key => $font_name) { - echo ''; + $fonts = $bread->getActiveFonts(); + $infos = $bread->getAvailableFonts(); + foreach ($fonts as $font_key) { + if (!isset($infos[$font_key])) continue; + $font_info = $infos[$font_key]; + echo ''; } } function Bread_layout_setup_page_render(Bread_AdminDisplay $breadAdmin) diff --git a/admin/partials/bread-admin-display.php b/admin/partials/bread-admin-display.php index aeedc854..9fb200bf 100644 --- a/admin/partials/bread-admin-display.php +++ b/admin/partials/bread-admin-display.php @@ -19,6 +19,7 @@ include_once '_meetings_setup.php'; include_once '_custom_section_setup.php'; include_once '_backup_restore_setup.php'; +include_once '_custom_fonts_setup.php'; class Bread_AdminDisplay { @@ -37,6 +38,10 @@ public function getBreadInstance() { return $this->bread; } + public function getAdmin() + { + return $this->admin; + } public function getServerVersion() { return $this->server_version; @@ -84,6 +89,7 @@ function admin_options_page($filename = '')
    @@ -126,6 +132,9 @@ function admin_options_page($filename = '')
    +
    + +
    diff --git a/bmlt-meeting-list.php b/bmlt-meeting-list.php index ef93c6c5..3c13e6d4 100644 --- a/bmlt-meeting-list.php +++ b/bmlt-meeting-list.php @@ -61,6 +61,7 @@ function Bread_deactivate() */ require plugin_dir_path(__FILE__) . 'includes/class-bread.php'; require plugin_dir_path(__FILE__) . 'includes/class-bread-bmlt.php'; +require plugin_dir_path(__FILE__) . 'bread_loadable_fonts/class-bread-loadable-fonts.php'; /** * Begins execution of the plugin. * diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php new file mode 100644 index 00000000..4026d756 --- /dev/null +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -0,0 +1,250 @@ + ['name' => 'Roboto', + 'stack' => 'Helvetica, sans-serif', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], + 'description' => 'Roboto has a dual nature. It has a mechanical skeleton and the forms are largely geometric. At the same time, the font features friendly and open curves. While some grotesks distort their letterforms to force a rigid rhythm, Roboto doesn’t compromise, allowing letters to be settled into their natural width. This makes for a more natural reading rhythm more commonly found in humanist and serif types.', + 'remote' => [ + 'directory' => 'https://github.com/google/fonts/raw/refs/heads/main/ofl/roboto/', + 'R' => 'Roboto[wdth,wght].ttf', + 'I' => 'Roboto-Italic[wdth,wght].ttf', + 'type' => 'variable', + ], + 'configuration' => [ + 'useOTL' => '0xFF', + 'kashida' => 75, + ]], + 'rubik' => ['name' => 'Rubik', + 'stack' => 'Helvetica, sans-serif', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'arabic'], + 'remote' => [ + 'directory' => 'https://github.com/google/fonts/raw/refs/heads/main/ofl/rubik/', + 'R' => 'Rubik[wght].ttf', + 'I' => 'Rubik-Italic[wght].ttf', + 'type' => 'variable', + ], + 'configuration' => [ + 'useOTL' => '0xFF', + 'kashida' => 75, + ]], + ]; + public function __construct() + { + foreach ($this->custom_fonts as $key => &$font) { + $font['actions'] = $this->calcActions($key); + } + $this->addAdminHooks(); + } + public function addAdminHooks() + { + add_filter('bread_custom_fonts', [$this, 'bread_custom_fonts'], 1); + add_filter('bread_content_style', [$this, 'bread_content_style'], 1); + add_filter('Bread_active_fonts', [$this, 'bread_active_fonts'], 1); + } + public function addPublicHooks() + { + add_filter("Bread_Mpdf_Init_Options", [$this, 'mpdf_init_options']); + } + public function bread_custom_fonts($fonts) + { + return array_merge($fonts, $this->custom_fonts); + } + private function getUploadDirectory($fontFamily = '', $url = false) + { + $uploads = wp_upload_dir(); + if (!empty($uploads['error']) || empty($uploads['basedir'])) { + return false; + } + $dir = $url ? $uploads['baseurl'] : $uploads['basedir']; + $dir = trailingslashit($dir) . 'bread-uploaded-fonts'; + if (!$url && !wp_mkdir_p($dir)) { + return false; + } + if ($fontFamily != '') { + $dir = trailingslashit($dir) . $fontFamily; + if (!$url && !wp_mkdir_p($dir)) { + return false; + } + } + return $dir; + } + public function bread_active_fonts($fonts) + { + return array_merge($fonts, $this->getUploadedFonts()); + } + private function getUploadedFonts() + { + $ret = []; + $subdirs = (new WP_Filesystem_Direct(null))->dirlist($this->getUploadDirectory(), false, true); + foreach ($subdirs as $subdir) { + if ($subdir['type'] !== 'd') { + continue; + } + if ($this->fontComplete($subdir['name'], array_keys($subdir['files']))) { + $ret[] = $subdir['name']; + } + } + return $ret; + } + private function fontComplete($fontFamily, $fontFiles) + { + if ($fontFamily === '') { + return false; + } + if (!isset($this->custom_fonts[$fontFamily])) { + throw new Exception("$fontFamily not in list of defined fonts"); + } + + $fontInfo = $this->custom_fonts[$fontFamily]; + foreach (['R', 'B', 'I', 'BI'] as $key) { + if (!isset($fontInfo['remote'][$key])) { + continue; + } + if (!in_array($fontInfo['remote'][$key], $fontFiles)) { + return false; + } + } + + return true; + } + public function installFont($fontFamily) + { + if (!current_user_can('manage_options')) { + $this->outputWarning("You must be an administrator to install fonts"); + return; + } + if (!isset($this->custom_fonts[$fontFamily])) { + $this->outputWarning("$fontFamily not in list of defined fonts"); + return; + } + + $fontInfo = $this->custom_fonts[$fontFamily]; + $directory = $fontInfo['remote']['directory']; + $localDir = $this->getUploadDirectory($fontFamily); + foreach (['R', 'B', 'I', 'BI'] as $key) { + if (!isset($fontInfo['remote'][$key])) { + continue; + } + + $file = $fontInfo['remote'][$key]; + $local = trailingslashit($localDir) . $file; + $url = $directory . rawurlencode($file); + $response = wp_remote_get($url, array('timeout' => 15, 'redirection' => 3)); + if (is_wp_error($response)) { + $this->outputWarning("Could not retrieve $url"); + return; + } + $code = wp_remote_retrieve_response_code($response); + $body = wp_remote_retrieve_body($response); + if ($code === 200 && is_string($body) && strlen($body) > 1000) { + $saved = file_put_contents($local, $body); + if (!$saved || $saved < 1000) { + $this->outputWarning("Could not retrieve $url"); + return; + } + } + } + $this->outputSuccess("Font $fontFamily successfully uploaded"); + } + private function outputWarning($str) + { + echo '
    '; + echo '

    '.esc_html($str).'

    '; + echo '
    '; + } + private function outputSuccess($str) + { + echo '
    '; + echo '

    '.esc_html($str).'

    '; + echo '
    '; + } + public function removeFont($fontFamily) + { + if (!current_user_can('manage_options')) { + $this->outputWarning("You must be an administrator to uninstall fonts"); + return; + } + $dirname = trailingslashit($this->getUploadDirectory()) . $fontFamily; + array_map('unlink', glob("$dirname/*.*")); + rmdir($dirname); + $this->outputSuccess("Font $fontFamily removed."); + } + public function bread_content_style($content_style) + { + foreach ($this->getUploadedFonts() as $font) { + $fontInfo = $this->custom_fonts[$font]; + $dir = trailingslashit($this->getUploadDirectory($font, true)); + foreach (['R', 'B', 'I', 'BI'] as $key) { + if (!isset($fontInfo['remote'][$key])) { + continue; + } + $loc = $dir . rawurlencode($fontInfo['remote'][$key]); + $content_style .= "@font-face {"; + $content_style .= "font-family: $font;"; + $content_style .= "src:url($loc) format('truetype');"; + $content_style .= "font-display: block;"; + if (isset($fontInfo['remote']['variable']) && $fontInfo['remote']['variable']) { + $content_style .= "font-weight: 100 1000;"; + } elseif ($key == 'B' || $key == 'BI') { + $content_style .= "font-weight: bold;"; + } + if ($key == 'I' || $key == 'BI') { + $content_style .= "font-style: italic;"; + } + $content_style .= "}"; + } + } + return $content_style; + } + function calcActions($font) + { + if (in_array($font, $this->getUploadedFonts())) { + return [ + 'removefont' => [ + 'text' => __('remove', 'bread'), + 'action' => 'removefont', + 'lambda' => [$this, 'removeFont'] + ]]; + } else { + return [ + 'installfont' => [ + 'text' => __('Install', 'bread'), + 'action' => 'installfont', + 'lambda' => [$this, 'installFont'] + ]]; + } + } + public function mpdf_init_options( $options ) { + $fontDirs = $options['fontDir']??[]; + $fontdata = $options['fontdata']??[]; + foreach($this->getUploadedFonts() as $fontKey) { + $fontDirs[] = $this->getUploadDirectory($fontKey); + $info = $this->custom_fonts[$fontKey]; + $fontdata[$fontKey] = []; + foreach(['R', 'B', 'I', 'BI'] as $style) { + if (isset($info['remote'][$style])) { + $fontdata[$fontKey][$style] = $info['remote'][$style]; + } elseif ($style === 'B') { + $fontdata[$fontKey][$style] = $info['remote']['R']; + } elseif ($style === 'BI') { + $fontdata[$fontKey][$style] = $info['remote']['I']; + } + } + if (isset($info['configuration'])) { + foreach ($info['configuration'] as $key => $value) { + $fontdata[$fontKey][$key] = $value; + } + } + } + $options['fontDir'] = $fontDirs; + $options['fontdata'] = $fontdata; + } +} \ No newline at end of file diff --git a/includes/class-bread.php b/includes/class-bread.php index 8a27021a..3fef367f 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -23,7 +23,15 @@ */ class Bread { - + /** + * The loader that's responsible for maintaining and registering all hooks that power + * the plugin. + * + * @since 2.19.0 + * @access protected + * @var BreadLoadableFonts $fontLoader Maintains and registers all hooks for the plugin. + */ + protected $fontLoader; /** * The loader that's responsible for maintaining and registering all hooks that power * the plugin. @@ -227,6 +235,47 @@ public function loadAllSettings($holder): int } return isset($holder['current-meeting-list']) ? intval($holder['current-meeting-list']) : 1; } + public function getIncludedFonts() + { + $fonts = [ + 'dejavusanscondensed' => ['name' => 'DejaVu Sans Condensed', + 'stack' => 'Helvetica, sans-serif', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese', 'hebrew'], + 'description' => 'A clean and modern sans-serif font with extensive language support. It is a condensed version of the DejaVu Sans font, making it ideal for fitting more text in limited space while maintaining readability.', + 'required' => true], + 'dejavuserifcondensed' => ['name' => 'DejaVu Serif Condensed', + 'stack' => 'Times New Roman, serif', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], + 'description' => 'A clean and modern serif font with extensive language support. It is a condensed version of the DejaVu Serif font, making it ideal for fitting more text in limited space while maintaining readability.', + 'required' => true], + 'dejavusansmono' => ['name' => 'DejaVu Sans Mono', + 'stack' => 'Courier New, monospace', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese'], + 'description' => 'A clean and modern monospace font with extensive language support. It is ideal for displaying code or tabular data, as each character occupies the same amount of horizontal space.', + 'required' => true], + 'chelvetica' => ['name' => 'Helvetica (PDF Core Font)', + 'stack' => 'dejavusanscondensed, sans-serif', + 'scripts' => ['latin'], + 'description' => 'Using PDF Core Fonts results in smaller files.',], + 'ccourier' => ['name' => 'Courier New (PDF Core Font)', + 'stack' => 'Courier New, monospace', + 'scripts' => ['latin'], + 'description' => 'Using PDF Core Fonts results in smaller files.',], + 'ctimes' => ['name' => 'Times New Roman (PDF Core Font)', + 'stack' => 'dejavuserifcondensed, serif', + 'scripts' => ['latin'], + 'description' => 'Using PDF Core Fonts results in smaller files.',], + ]; + return $fonts; + } + public function getAvailableFonts(): array + { + return apply_filters('bread_custom_fonts', $this->getIncludedFonts()); + } + public function getActiveFonts(): array + { + return apply_filters('Bread_active_fonts', array_keys($this->getIncludedFonts())); + } /** * Undocumented function * @@ -386,15 +435,6 @@ public function __construct() $this->load_translations(); $this->define_admin_hooks(); $this->define_public_hooks(); - - $this->fonts = [ - 'dejavusanscondensed' => __('DejaVu Sans Condensed', 'bread'), - 'times' => __('New Times Roman (DejaVu Serif)', 'bread'), - 'courier' => __('Courier (DejaVu Sans Mono)', 'bread'), - 'ccourier' => __('PDF Core Font - Courier', 'bread'), - 'ctimes' => __('PDF Core Font - New Times Roman', 'bread'), - 'chelvetica' => __('PDF Core Font - Helvetica', 'bread'), - ]; } function load_translations() { @@ -471,7 +511,12 @@ private function load_dependencies() * side of the site. */ include_once plugin_dir_path(dirname(__FILE__)) . 'public/class-bread-public.php'; - + /** + * Loading fonts uses the API created to add fonts via plugin. + * We need to instanciate this plugin and let the constructor do its work + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'bread_loadable_fonts/class-bread-loadable-fonts.php'; + $this->fontLoader = new BreadLoadableFonts(); $this->loader = new Bread_Loader(); } From 4292f6aaa00eacca08170bbd28a23327b615648d Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:29:11 +0200 Subject: [PATCH 23/34] Read URLs from GoogleFonts manifest rather than GitHub --- .../class-bread-loadable-fonts.php | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index 4026d756..ae17e21b 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -1,9 +1,4 @@ ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], 'description' => 'Roboto has a dual nature. It has a mechanical skeleton and the forms are largely geometric. At the same time, the font features friendly and open curves. While some grotesks distort their letterforms to force a rigid rhythm, Roboto doesn’t compromise, allowing letters to be settled into their natural width. This makes for a more natural reading rhythm more commonly found in humanist and serif types.', 'remote' => [ - 'directory' => 'https://github.com/google/fonts/raw/refs/heads/main/ofl/roboto/', - 'R' => 'Roboto[wdth,wght].ttf', - 'I' => 'Roboto-Italic[wdth,wght].ttf', - 'type' => 'variable', + 'manifest' => 'https://fonts.google.com/download/list?family=Roboto', + 'R' => 'Roboto_Condensed-Regular.ttf', + 'B' => 'Roboto_Condensed-Bold.ttf', + 'I' => 'Roboto_Condensed-Italic.ttf', + 'BI' => 'Roboto_Condensed-BoldItalic.ttf', ], 'configuration' => [ - 'useOTL' => '0xFF', + 'useOTL' => 0xFF, 'kashida' => 75, ]], 'rubik' => ['name' => 'Rubik', 'stack' => 'Helvetica, sans-serif', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'arabic'], 'remote' => [ - 'directory' => 'https://github.com/google/fonts/raw/refs/heads/main/ofl/rubik/', - 'R' => 'Rubik[wght].ttf', - 'I' => 'Rubik-Italic[wght].ttf', - 'type' => 'variable', + 'manifest' => 'https://fonts.google.com/download/list?family=Rubik', + 'R' => 'Rubik-Regular.ttf', + 'B' => 'Rubik-Bold.ttf', + 'I' => 'Rubik-Italic.ttf', + 'BI' => 'Rubik-BoldItalic.ttf', ], 'configuration' => [ - 'useOTL' => '0xFF', + 'useOTL' => 0xFF, 'kashida' => 75, ]], ]; @@ -42,6 +39,7 @@ public function __construct() $font['actions'] = $this->calcActions($key); } $this->addAdminHooks(); + $this->addPublicHooks(); } public function addAdminHooks() { @@ -115,7 +113,27 @@ private function fontComplete($fontFamily, $fontFiles) return true; } - public function installFont($fontFamily) + private function getURLtoFileMap(string $manifest): array|false + { + $response = wp_remote_get($manifest, array('timeout' => 15, 'redirection' => 3)); + if (is_wp_error($response)) { + $this->outputWarning("Could not retrieve $manifest"); + return false; + } + $code = wp_remote_retrieve_response_code($response); + $body = wp_remote_retrieve_body($response); + if ($code === 200 && is_string($body)) { + $contents = json_decode(substr($body,4), true); + $ret = []; + foreach ($contents['manifest']['fileRefs'] as $item) { + $split = explode('/', $item['filename']); + $ret[end($split)] = $item['url']; + } + return $ret; + } + return false; + } + public function installFont(string $fontFamily) { if (!current_user_can('manage_options')) { $this->outputWarning("You must be an administrator to install fonts"); @@ -127,7 +145,22 @@ public function installFont($fontFamily) } $fontInfo = $this->custom_fonts[$fontFamily]; - $directory = $fontInfo['remote']['directory']; + $fileToUrl = []; + if (isset($fontInfo['remote']['manifest'])) { + $fileToUrl = $this->getURLtoFileMap($fontInfo['remote']['manifest']); + if (!$fileToUrl) { + $this->outputWarning("Could not parse manifest file"); + return; + } + } elseif (isset(($fontInfo['remote']['directory']))) { + $directory = $fontInfo['remote']['directory']; + foreach (['R', 'B', 'I', 'BI'] as $key) { + if (!isset($fontInfo['remote'][$key])) { + continue; + } + $fileToUrl[$fontInfo['remote'][$key]] = $directory . rawurlencode($fontInfo['remote'][$key]); + } + } $localDir = $this->getUploadDirectory($fontFamily); foreach (['R', 'B', 'I', 'BI'] as $key) { if (!isset($fontInfo['remote'][$key])) { @@ -136,7 +169,11 @@ public function installFont($fontFamily) $file = $fontInfo['remote'][$key]; $local = trailingslashit($localDir) . $file; - $url = $directory . rawurlencode($file); + if (!isset($fileToUrl[$fontInfo['remote'][$key]])) { + $this->outputWarning("No URL for " . $fileToUrl[$fontInfo['remote'][$key]]); + return; + } + $url = $fileToUrl[$fontInfo['remote'][$key]]; $response = wp_remote_get($url, array('timeout' => 15, 'redirection' => 3)); if (is_wp_error($response)) { $this->outputWarning("Could not retrieve $url"); @@ -246,5 +283,6 @@ public function mpdf_init_options( $options ) { } $options['fontDir'] = $fontDirs; $options['fontdata'] = $fontdata; + return $options; } } \ No newline at end of file From 787fdabe6f31c8b91bdd437bb59c304cb59c86e5 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:38:13 +0200 Subject: [PATCH 24/34] Added some fonts --- admin/class-bread-admin.php | 52 +++++-- admin/partials/_custom_fonts_setup.php | 12 +- admin/partials/_layout_setup.php | 4 +- .../class-bread-loadable-fonts.php | 144 +++++++++++++----- includes/class-bread.php | 21 ++- readme.txt | 3 +- 6 files changed, 167 insertions(+), 69 deletions(-) diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index 74224eaa..885bac08 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -428,29 +428,51 @@ function admin_submenu_link($parent_slug) 2 ); } + private function outputSuccess(string $str) + { + echo '
    '; + echo '

    '.esc_html($str).'

    '; + echo '
    '; + } + private function outputWarning(string $str) + { + echo '
    '; + echo '

    '.esc_html($str).'

    '; + echo '
    '; + } function admin_options_page() { $filename = ''; - if (!empty($_GET['fontAction'])) { + if ($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['fontAction'])) { $action = sanitize_key($_GET['fontAction']); if (!wp_verify_nonce($_GET['nonce'], 'bread_font_action')) { wp_die('Request invalid due to timeout'); } - $fonts = $this->bread->getAvailableFonts(); - $font = $_GET['font']; - if (!isset($fonts[$font])) { - wp_die('Request invalid'); - } - if (!isset($fonts[$font])) { - wp_die('Request invalid'); - } - if (!isset($fonts[$font]['actions'])) { - wp_die('Request invalid'); - } - if (!isset($fonts[$font]['actions'][$action])) { - wp_die('Request invalid'); + switch ($action) { + case 'success': + $this->outputSuccess(esc_html($_GET['message'])); + break; + case 'warning': + $this->outputWarning(esc_html($_GET['message'])); + break; + default: + $fonts = $this->bread->getAvailableFonts(); + $font = $_GET['font']; + if (!isset($fonts[$font])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font]['actions'])) { + wp_die('Request invalid'); + } + if (!isset($fonts[$font]['actions'][$action])) { + wp_die('Request invalid'); + } + call_user_func($fonts[$font]['actions'][$action]['lambda'], $font); + break; } - call_user_func($fonts[$font]['actions'][$action]['lambda'], $font); } if (!empty($_POST['pwsix_action']) && (!isset($_POST['bmltmeetinglistsave']) || $_POST['bmltmeetinglistsave'] != 'Save Changes')) { switch ($_POST['pwsix_action']) { diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index a5065e18..9e8916bd 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -9,6 +9,7 @@ class Bread_Custom_Fonts_Table extends WP_List_Table { private Bread $bread; private array $fonts; + private array $active; private string $nonce; function getTypeFromStack(string $stack): string { @@ -19,6 +20,7 @@ function __construct(Bread $bread) { $this->bread = $bread; $this->fonts = $bread->getAvailableFonts(); + $this->active = $bread->getActiveFonts(); $this->nonce = wp_create_nonce("bread_font_action"); return parent::__construct(); } @@ -28,7 +30,7 @@ function get_columns() 'name' => 'Font Family', 'type' => 'Type', 'scripts' => 'Character Sets', - 'description' => 'Description', + 'specimen' => 'More Information', ]; } function column_default($font, $column) @@ -52,10 +54,14 @@ function column_name($font) $actions = []; if (isset($font['actions'])) { foreach ($font['actions'] as $key => $action) { - $actions[$key] = sprintf('' . $action['text'] . '', $_REQUEST['page'], $action['action'], $font['slug'], $this->nonce); + $actions[$key] = sprintf('' . $action['text'] . '', $_REQUEST['page'], $action['action'], $font['slug'], $this->nonce); } } - return sprintf('%1$s %2$s', $font['name'], $this->row_actions($actions)); + $name = $font['name']; + if (in_array($font['slug'], $this->active)) { + $name = "" . $name . ""; + } + return sprintf('%1$s %2$s', $name, $this->row_actions($actions)); } function prepare_items() { diff --git a/admin/partials/_layout_setup.php b/admin/partials/_layout_setup.php index 686d92c5..848755a1 100644 --- a/admin/partials/_layout_setup.php +++ b/admin/partials/_layout_setup.php @@ -7,7 +7,9 @@ function echo_font_options(string $base_font, Bread $bread) $fonts = $bread->getActiveFonts(); $infos = $bread->getAvailableFonts(); foreach ($fonts as $font_key) { - if (!isset($infos[$font_key])) continue; + if (!isset($infos[$font_key])) { + continue; + } $font_info = $infos[$font_key]; echo ''; } diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index ae17e21b..d3a34753 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -1,12 +1,15 @@ ['name' => 'Roboto', - 'stack' => 'Helvetica, sans-serif', + 'stack' => 'Sans-Serif - Neo-Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], - 'description' => 'Roboto has a dual nature. It has a mechanical skeleton and the forms are largely geometric. At the same time, the font features friendly and open curves. While some grotesks distort their letterforms to force a rigid rhythm, Roboto doesn’t compromise, allowing letters to be settled into their natural width. This makes for a more natural reading rhythm more commonly found in humanist and serif types.', + 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Roboto', 'R' => 'Roboto_Condensed-Regular.ttf', @@ -19,8 +22,9 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'rubik' => ['name' => 'Rubik', - 'stack' => 'Helvetica, sans-serif', + 'stack' => 'Sans-Serif - Neo-Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'arabic'], + 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Rubik', 'R' => 'Rubik-Regular.ttf', @@ -32,7 +36,76 @@ class BreadLoadableFonts 'useOTL' => 0xFF, 'kashida' => 75, ]], - ]; + 'oswald' => ['name' => 'Oswald', + 'stack' => 'Sans-Serif - Neo-Grotesque', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Oswald', + 'R' => 'Oswald-Regular.ttf', + 'B' => 'Oswald-Bold.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + 'opensans' => ['name' => 'Open Sans Condensed', + 'stack' => 'Sans-Serif - Humanist', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Open%20Sans', + 'R' => 'OpenSans_Condensed-Regular.ttf', + 'B' => 'OpenSans_Condensed-Bold.ttf', + 'I' => 'OpenSans_Condensed-Italic.ttf', + 'BI' => 'OpenSans_Condensed-BoldItalic.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + 'arimo' => ['name' => 'Arimo', + 'stack' => 'Sans-Serif - Arial-like', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'hebrew'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Arimo', + 'R' => 'Arimo-Regular.ttf', + 'B' => 'Arimo-Bold.ttf', + 'I' => 'Arimo-Italic.ttf', + 'BI' => 'Arimo-BoldItalic.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + 'arvo' => ['name' => 'Arvo', + 'stack' => 'Slab-Serif', + 'scripts' => ['latin', 'latin-ext'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Arvo', + 'R' => 'Arvo-Regular.ttf', + 'B' => 'Arvo-Bold.ttf', + 'I' => 'Arvo-Italic.ttf', + 'BI' => 'Arvo-BoldItalic.ttf', + ], + 'configuration' => [ + ]], + 'cairo' => ['name' => 'Cairo', + 'stack' => 'Sans-Serif - Superellipse', + 'scripts' => ['latin', 'latin-ext', 'arabic'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Cairo', + 'R' => 'Cairo-Regular.ttf', + 'B' => 'Cairo-Bold.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + ]; public function __construct() { foreach ($this->custom_fonts as $key => &$font) { @@ -74,7 +147,7 @@ private function getUploadDirectory($fontFamily = '', $url = false) } return $dir; } - public function bread_active_fonts($fonts) + public function bread_active_fonts(array $fonts): array { return array_merge($fonts, $this->getUploadedFonts()); } @@ -98,7 +171,7 @@ private function fontComplete($fontFamily, $fontFiles) return false; } if (!isset($this->custom_fonts[$fontFamily])) { - throw new Exception("$fontFamily not in list of defined fonts"); + return false; } $fontInfo = $this->custom_fonts[$fontFamily]; @@ -123,7 +196,7 @@ private function getURLtoFileMap(string $manifest): array|false $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($code === 200 && is_string($body)) { - $contents = json_decode(substr($body,4), true); + $contents = json_decode(substr($body, 4), true); $ret = []; foreach ($contents['manifest']['fileRefs'] as $item) { $split = explode('/', $item['filename']); @@ -189,19 +262,18 @@ public function installFont(string $fontFamily) } } } + $this->custom_fonts[$fontFamily]['actions'] = $this->calcActions($fontFamily); $this->outputSuccess("Font $fontFamily successfully uploaded"); } private function outputWarning($str) { - echo '
    '; - echo '

    '.esc_html($str).'

    '; - echo '
    '; + wp_redirect(admin_url('admin.php?page=bmlt-enabled-bread&fontAction=warning&message=' . rawurlencode($str) . '&nonce=' . wp_create_nonce('bread_font_action'))); + exit; } private function outputSuccess($str) { - echo '
    '; - echo '

    '.esc_html($str).'

    '; - echo '
    '; + wp_redirect(admin_url('admin.php?page=bmlt-enabled-bread&fontAction=success&message=' . rawurlencode($str) . '&nonce=' . wp_create_nonce('bread_font_action'))); + exit; } public function removeFont($fontFamily) { @@ -211,7 +283,8 @@ public function removeFont($fontFamily) } $dirname = trailingslashit($this->getUploadDirectory()) . $fontFamily; array_map('unlink', glob("$dirname/*.*")); - rmdir($dirname); + (new WP_Filesystem_Direct(null))->rmdir($dirname); + $this->custom_fonts[$fontFamily]['actions'] = $this->calcActions($fontFamily); $this->outputSuccess("Font $fontFamily removed."); } public function bread_content_style($content_style) @@ -259,30 +332,27 @@ function calcActions($font) ]]; } } - public function mpdf_init_options( $options ) { - $fontDirs = $options['fontDir']??[]; - $fontdata = $options['fontdata']??[]; - foreach($this->getUploadedFonts() as $fontKey) { - $fontDirs[] = $this->getUploadDirectory($fontKey); - $info = $this->custom_fonts[$fontKey]; - $fontdata[$fontKey] = []; - foreach(['R', 'B', 'I', 'BI'] as $style) { - if (isset($info['remote'][$style])) { - $fontdata[$fontKey][$style] = $info['remote'][$style]; - } elseif ($style === 'B') { - $fontdata[$fontKey][$style] = $info['remote']['R']; - } elseif ($style === 'BI') { - $fontdata[$fontKey][$style] = $info['remote']['I']; + public function mpdf_init_options($options) + { + $fontDirs = $options['fontDir']??[]; + $fontdata = $options['fontdata']??[]; + foreach ($this->getUploadedFonts() as $fontKey) { + $fontDirs[] = $this->getUploadDirectory($fontKey); + $info = $this->custom_fonts[$fontKey]; + $fontdata[$fontKey] = []; + foreach (['R', 'B', 'I', 'BI'] as $style) { + if (isset($info['remote'][$style])) { + $fontdata[$fontKey][$style] = $info['remote'][$style]; + } } - } - if (isset($info['configuration'])) { - foreach ($info['configuration'] as $key => $value) { - $fontdata[$fontKey][$key] = $value; + if (isset($info['configuration'])) { + foreach ($info['configuration'] as $key => $value) { + $fontdata[$fontKey][$key] = $value; + } } } + $options['fontDir'] = $fontDirs; + $options['fontdata'] = $fontdata; + return $options; } - $options['fontDir'] = $fontDirs; - $options['fontdata'] = $fontdata; - return $options; - } -} \ No newline at end of file +} diff --git a/includes/class-bread.php b/includes/class-bread.php index 3fef367f..05cdc0e9 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -239,32 +239,29 @@ public function getIncludedFonts() { $fonts = [ 'dejavusanscondensed' => ['name' => 'DejaVu Sans Condensed', - 'stack' => 'Helvetica, sans-serif', + 'stack' => 'Sans-Serif', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese', 'hebrew'], - 'description' => 'A clean and modern sans-serif font with extensive language support. It is a condensed version of the DejaVu Sans font, making it ideal for fitting more text in limited space while maintaining readability.', - 'required' => true], + 'specimen' => 'DejaVu Fonts',], 'dejavuserifcondensed' => ['name' => 'DejaVu Serif Condensed', - 'stack' => 'Times New Roman, serif', + 'stack' => 'serif', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], - 'description' => 'A clean and modern serif font with extensive language support. It is a condensed version of the DejaVu Serif font, making it ideal for fitting more text in limited space while maintaining readability.', - 'required' => true], + 'specimen' => 'DejaVu Fonts',], 'dejavusansmono' => ['name' => 'DejaVu Sans Mono', - 'stack' => 'Courier New, monospace', + 'stack' => 'monospace', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese'], - 'description' => 'A clean and modern monospace font with extensive language support. It is ideal for displaying code or tabular data, as each character occupies the same amount of horizontal space.', - 'required' => true], + 'specimen' => 'DejaVu Fonts',], 'chelvetica' => ['name' => 'Helvetica (PDF Core Font)', 'stack' => 'dejavusanscondensed, sans-serif', 'scripts' => ['latin'], - 'description' => 'Using PDF Core Fonts results in smaller files.',], + 'specimen' => 'Using PDF Core Fonts results in smaller files.',], 'ccourier' => ['name' => 'Courier New (PDF Core Font)', 'stack' => 'Courier New, monospace', 'scripts' => ['latin'], - 'description' => 'Using PDF Core Fonts results in smaller files.',], + 'specimen' => 'Using PDF Core Fonts results in smaller files.',], 'ctimes' => ['name' => 'Times New Roman (PDF Core Font)', 'stack' => 'dejavuserifcondensed, serif', 'scripts' => ['latin'], - 'description' => 'Using PDF Core Fonts results in smaller files.',], + 'specimen' => 'Using PDF Core Fonts results in smaller files.',], ]; return $fonts; } diff --git a/readme.txt b/readme.txt index 7f29bb7d..def11d64 100644 --- a/readme.txt +++ b/readme.txt @@ -58,7 +58,8 @@ Follow all these steps, keep in mind that once you start using bread, it's not g = 2.10.0 = * [bread_button] shortcode added that can generate PDFs even when the hosting provider has blocked access to the BMLT server. * PDF Core Fonts (Helvertica, New Times Roman and Courier) may be selected -+ Extension points added to allow for custom fonts +* Extension points added to allow for custom fonts +* A selection of Google Fonts can be downloaded by administrator in the new "Custom Fonts" tab, = 2.9.15 = * More commas cleaned up. From 3dca927b3da5591feac74846c130be1005d1be99 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:06:35 +0200 Subject: [PATCH 25/34] cleanup unused code --- admin/class-bread-admin.php | 3 --- bread_loadable_fonts/class-bread-loadable-fonts.php | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index 885bac08..b0362d10 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -461,9 +461,6 @@ function admin_options_page() if (!isset($fonts[$font])) { wp_die('Request invalid'); } - if (!isset($fonts[$font])) { - wp_die('Request invalid'); - } if (!isset($fonts[$font]['actions'])) { wp_die('Request invalid'); } diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index d3a34753..c52a8cf5 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -301,9 +301,7 @@ public function bread_content_style($content_style) $content_style .= "font-family: $font;"; $content_style .= "src:url($loc) format('truetype');"; $content_style .= "font-display: block;"; - if (isset($fontInfo['remote']['variable']) && $fontInfo['remote']['variable']) { - $content_style .= "font-weight: 100 1000;"; - } elseif ($key == 'B' || $key == 'BI') { + if ($key == 'B' || $key == 'BI') { $content_style .= "font-weight: bold;"; } if ($key == 'I' || $key == 'BI') { From ca579d9a2615bec3912bc1b5e4d019009017c942 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:10:37 +0200 Subject: [PATCH 26/34] Turn plugin check back on --- .github/workflows/pull-requests.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 86e9e4d1..d2693705 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -30,10 +30,9 @@ jobs: echo "ZIP_FILENAME=${ZIP_FILENAME}" >> $GITHUB_ENV PROD=1 make build unzip "${BUILD_DIR}/${ZIP_FILENAME}" -d "./bread" -# Removed until https://github.com/WordPress/plugin-check-action/issues/579 is fixed -# - name: Run plugin check -# uses: wordpress/plugin-check-action@v1 -# with: -# exclude-directories: 'vendor' -# categories: 'plugin_repo' -# ignore-warnings: true + - name: Run plugin check + uses: wordpress/plugin-check-action@v1 + with: + exclude-directories: 'vendor' + categories: 'plugin_repo' + ignore-warnings: true From 1d8427901a6ae9c989880dfe8a3612207ad4b4e7 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:33:47 +0200 Subject: [PATCH 27/34] fix build --- .github/workflows/pull-requests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index d2693705..20f8d809 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -30,9 +30,11 @@ jobs: echo "ZIP_FILENAME=${ZIP_FILENAME}" >> $GITHUB_ENV PROD=1 make build unzip "${BUILD_DIR}/${ZIP_FILENAME}" -d "./bread" + - name: Run plugin check uses: wordpress/plugin-check-action@v1 with: + build-dir: "./bread" exclude-directories: 'vendor' categories: 'plugin_repo' ignore-warnings: true From c6aa289e501cd4a5a923db989704b851cbf16694 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:19:56 +0200 Subject: [PATCH 28/34] Added filters to font table --- admin/js/bmlt_meeting_list.js | 3 + admin/partials/_custom_fonts_setup.php | 62 ++++++++++++++++++- .../class-bread-loadable-fonts.php | 15 +++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/admin/js/bmlt_meeting_list.js b/admin/js/bmlt_meeting_list.js index 50cec5e6..c806032e 100644 --- a/admin/js/bmlt_meeting_list.js +++ b/admin/js/bmlt_meeting_list.js @@ -596,4 +596,7 @@ hideAllTabs(); activateTab($(this)); }); + $('.bread-font-filter').change(function() { + $('#filter-fonts-form').submit(); + }) }); diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index 9e8916bd..8d3c96b3 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -70,15 +70,73 @@ function prepare_items() $sortable = array(); $this->_column_headers = array($columns, $hidden, $sortable); $this->items = $this->fonts; + if (isset($_GET['script']) && $_GET['script'] != '*') { + $this->items = array_filter($this->items, function($font) { return in_array($_GET['script'], $font['scripts']);}); + } + if (isset($_GET['letterform']) && $_GET['letterform'] != '*') { + $this->items = array_filter($this->items, function($font) { return $_GET['letterform'] == $font['scripts'];}); + } foreach ($this->items as $slug => &$info) { $info['slug'] = $slug; } } + private function selected($a, $b): string + { + return ($a == $b) ? 'selected' : ''; + } + private function getAllScripts(): array + { + $ret = []; + foreach($this->fonts as $font) { + foreach($font['scripts'] as $script) { + if (!in_array($script, $ret)) { + $ret[] = $script; + } + } + } + return $ret; + } + private function getAllLetterforms(): array + { + $ret = []; + foreach($this->fonts as $font) { + if (!in_array($font['stack'], $ret)) { + $ret[] = $font['stack']; + } + } + return $ret; + } + protected function extra_tablenav( $which ) + { + $letterform = $_GET['letterform'] ?? '*'; + $script = $_GET['script'] ?? '*'; ?> +
    + + + + + +
    + getBreadInstance()); $table->prepare_items(); $table->display(); -} +} \ No newline at end of file diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index c52a8cf5..027121e8 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -79,6 +79,21 @@ class BreadLoadableFonts 'useOTL' => 0xFF, 'kashida' => 75, ]], + 'ibmplexsans' => ['name' => 'IBM Plex Sans', + 'stack' => 'Sans-Serif - Neo Grotesque', + 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=IBM%20Plex%20Sans%20Condensed', + 'R' => 'IBMPlexSansCondensed-Regular.ttf', + 'B' => 'IBMPlexSansCondensed-Bold.ttf', + 'I' => 'IBMPlexSansCondensed-Italic.ttf', + 'BI' => 'IBMPlexSansCondensed-BoldItalic.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], 'arvo' => ['name' => 'Arvo', 'stack' => 'Slab-Serif', 'scripts' => ['latin', 'latin-ext'], From cb58fc0eac52cf366942a42084728b1443890b0a Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:56:17 +0200 Subject: [PATCH 29/34] cleanup --- admin/partials/_custom_fonts_setup.php | 18 +++++++++--------- .../class-bread-loadable-fonts.php | 16 ++++++++-------- includes/class-bread.php | 12 ++++++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index 8d3c96b3..50d1f816 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -37,7 +37,7 @@ function column_default($font, $column) { switch ($column) { case 'type': - $stack = $font['stack']; + $stack = $font['letterform']; $array = explode(',', $stack); return $array[count($array)-1]; case 'scripts': @@ -74,13 +74,13 @@ function prepare_items() $this->items = array_filter($this->items, function($font) { return in_array($_GET['script'], $font['scripts']);}); } if (isset($_GET['letterform']) && $_GET['letterform'] != '*') { - $this->items = array_filter($this->items, function($font) { return $_GET['letterform'] == $font['scripts'];}); + $this->items = array_filter($this->items, function($font) { return $_GET['letterform'] == $font['letterform'];}); } foreach ($this->items as $slug => &$info) { $info['slug'] = $slug; } } - private function selected($a, $b): string + private function selected($a, $b) { return ($a == $b) ? 'selected' : ''; } @@ -100,8 +100,8 @@ private function getAllLetterforms(): array { $ret = []; foreach($this->fonts as $font) { - if (!in_array($font['stack'], $ret)) { - $ret[] = $font['stack']; + if (!in_array($font['letterform'], $ret)) { + $ret[] = $font['letterform']; } } return $ret; @@ -114,19 +114,19 @@ protected function extra_tablenav( $which ) diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index 027121e8..3061b695 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -7,7 +7,7 @@ class BreadLoadableFonts var $custom_fonts = [ 'roboto' => ['name' => 'Roboto', - 'stack' => 'Sans-Serif - Neo-Grotesque', + 'letterform' => 'Sans-Serif - Neo-Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -22,7 +22,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'rubik' => ['name' => 'Rubik', - 'stack' => 'Sans-Serif - Neo-Grotesque', + 'letterform' => 'Sans-Serif - Neo-Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'arabic'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -37,7 +37,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'oswald' => ['name' => 'Oswald', - 'stack' => 'Sans-Serif - Neo-Grotesque', + 'letterform' => 'Sans-Serif - Neo-Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -50,7 +50,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'opensans' => ['name' => 'Open Sans Condensed', - 'stack' => 'Sans-Serif - Humanist', + 'letterform' => 'Sans-Serif - Humanist', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -65,7 +65,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'arimo' => ['name' => 'Arimo', - 'stack' => 'Sans-Serif - Arial-like', + 'letterform' => 'Sans-Serif - Arial-like', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'hebrew'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -80,7 +80,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'ibmplexsans' => ['name' => 'IBM Plex Sans', - 'stack' => 'Sans-Serif - Neo Grotesque', + 'letterform' => 'Sans-Serif - Neo Grotesque', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -95,7 +95,7 @@ class BreadLoadableFonts 'kashida' => 75, ]], 'arvo' => ['name' => 'Arvo', - 'stack' => 'Slab-Serif', + 'letterform' => 'Slab-Serif', 'scripts' => ['latin', 'latin-ext'], 'specimen' => 'Google Fonts', 'remote' => [ @@ -108,7 +108,7 @@ class BreadLoadableFonts 'configuration' => [ ]], 'cairo' => ['name' => 'Cairo', - 'stack' => 'Sans-Serif - Superellipse', + 'letterform' => 'Sans-Serif - Superellipse', 'scripts' => ['latin', 'latin-ext', 'arabic'], 'specimen' => 'Google Fonts', 'remote' => [ diff --git a/includes/class-bread.php b/includes/class-bread.php index 05cdc0e9..c8391678 100644 --- a/includes/class-bread.php +++ b/includes/class-bread.php @@ -239,27 +239,27 @@ public function getIncludedFonts() { $fonts = [ 'dejavusanscondensed' => ['name' => 'DejaVu Sans Condensed', - 'stack' => 'Sans-Serif', + 'letterform' => 'Sans-Serif', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese', 'hebrew'], 'specimen' => 'DejaVu Fonts',], 'dejavuserifcondensed' => ['name' => 'DejaVu Serif Condensed', - 'stack' => 'serif', + 'letterform' => 'Serif', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], 'specimen' => 'DejaVu Fonts',], 'dejavusansmono' => ['name' => 'DejaVu Sans Mono', - 'stack' => 'monospace', + 'letterform' => 'Monospace', 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext', 'arabic', 'vietnamese'], 'specimen' => 'DejaVu Fonts',], 'chelvetica' => ['name' => 'Helvetica (PDF Core Font)', - 'stack' => 'dejavusanscondensed, sans-serif', + 'letterform' => 'Sans-Serif', 'scripts' => ['latin'], 'specimen' => 'Using PDF Core Fonts results in smaller files.',], 'ccourier' => ['name' => 'Courier New (PDF Core Font)', - 'stack' => 'Courier New, monospace', + 'letterform' => 'Monospace', 'scripts' => ['latin'], 'specimen' => 'Using PDF Core Fonts results in smaller files.',], 'ctimes' => ['name' => 'Times New Roman (PDF Core Font)', - 'stack' => 'dejavuserifcondensed, serif', + 'letterform' => 'Serif', 'scripts' => ['latin'], 'specimen' => 'Using PDF Core Fonts results in smaller files.',], ]; From 4c250a1797a3f93c58c66437c99d2bae67d7ab7a Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:30:25 +0200 Subject: [PATCH 30/34] Corrections to fonts --- admin/class-bread-admin.php | 4 +- admin/partials/_custom_fonts_setup.php | 34 ++++---- .../class-bread-loadable-fonts.php | 83 ++++++++++++++----- 3 files changed, 84 insertions(+), 37 deletions(-) diff --git a/admin/class-bread-admin.php b/admin/class-bread-admin.php index b0362d10..f87b9625 100644 --- a/admin/class-bread-admin.php +++ b/admin/class-bread-admin.php @@ -450,7 +450,9 @@ function admin_options_page() } switch ($action) { case 'success': - $this->outputSuccess(esc_html($_GET['message'])); + if (isset($_GET['message'])) { + $this->outputSuccess(esc_html($_GET['message'])); + } break; case 'warning': $this->outputWarning(esc_html($_GET['message'])); diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index 50d1f816..bde9b6df 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -71,10 +71,14 @@ function prepare_items() $this->_column_headers = array($columns, $hidden, $sortable); $this->items = $this->fonts; if (isset($_GET['script']) && $_GET['script'] != '*') { - $this->items = array_filter($this->items, function($font) { return in_array($_GET['script'], $font['scripts']);}); + $this->items = array_filter($this->items, function ($font) { + return in_array($_GET['script'], $font['scripts']); + }); } if (isset($_GET['letterform']) && $_GET['letterform'] != '*') { - $this->items = array_filter($this->items, function($font) { return $_GET['letterform'] == $font['letterform'];}); + $this->items = array_filter($this->items, function ($font) { + return $_GET['letterform'] == $font['letterform']; + }); } foreach ($this->items as $slug => &$info) { $info['slug'] = $slug; @@ -87,8 +91,8 @@ private function selected($a, $b) private function getAllScripts(): array { $ret = []; - foreach($this->fonts as $font) { - foreach($font['scripts'] as $script) { + foreach ($this->fonts as $font) { + foreach ($font['scripts'] as $script) { if (!in_array($script, $ret)) { $ret[] = $script; } @@ -99,39 +103,39 @@ private function getAllScripts(): array private function getAllLetterforms(): array { $ret = []; - foreach($this->fonts as $font) { + foreach ($this->fonts as $font) { if (!in_array($font['letterform'], $ret)) { $ret[] = $font['letterform']; } } return $ret; } - protected function extra_tablenav( $which ) + protected function extra_tablenav($which) { $letterform = $_GET['letterform'] ?? '*'; $script = $_GET['script'] ?? '*'; ?>
    - - + getAllScripts() as $s) { + foreach ($this->getAllScripts() as $s) { echo '"; } ?> - - + getAllLetterforms() as $s) { + foreach ($this->getAllLetterforms() as $s) { echo '"; } ?>
    - ['name' => 'Roboto', 'letterform' => 'Sans-Serif - Neo-Grotesque', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'greek-ext'], + 'scripts' => ['latin', 'cyrillic', 'greek', 'greek-ext'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Roboto', @@ -23,7 +23,7 @@ class BreadLoadableFonts ]], 'rubik' => ['name' => 'Rubik', 'letterform' => 'Sans-Serif - Neo-Grotesque', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'arabic'], + 'scripts' => ['latin', 'cyrillic', 'arabic', 'hebrew'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Rubik', @@ -36,22 +36,9 @@ class BreadLoadableFonts 'useOTL' => 0xFF, 'kashida' => 75, ]], - 'oswald' => ['name' => 'Oswald', - 'letterform' => 'Sans-Serif - Neo-Grotesque', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext'], - 'specimen' => 'Google Fonts', - 'remote' => [ - 'manifest' => 'https://fonts.google.com/download/list?family=Oswald', - 'R' => 'Oswald-Regular.ttf', - 'B' => 'Oswald-Bold.ttf', - ], - 'configuration' => [ - 'useOTL' => 0xFF, - 'kashida' => 75, - ]], 'opensans' => ['name' => 'Open Sans Condensed', 'letterform' => 'Sans-Serif - Humanist', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], + 'scripts' => ['latin', 'cyrillic', 'greek'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Open%20Sans', @@ -66,7 +53,7 @@ class BreadLoadableFonts ]], 'arimo' => ['name' => 'Arimo', 'letterform' => 'Sans-Serif - Arial-like', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek', 'hebrew'], + 'scripts' => ['latin', 'cyrillic', 'greek', 'hebrew'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Arimo', @@ -79,9 +66,22 @@ class BreadLoadableFonts 'useOTL' => 0xFF, 'kashida' => 75, ]], + 'lato' => ['name' => 'Lato', + 'letterform' => 'Sans-Serif - Humanist', + 'scripts' => ['latin'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=Lato', + 'R' => 'Lato-Regular.ttf', + 'B' => 'Lato-Bold.ttf', + 'I' => 'Lato-Italic.ttf', + 'BI' => 'Lato-BoldItalic.ttf', + ], + 'configuration' => [ + ]], 'ibmplexsans' => ['name' => 'IBM Plex Sans', 'letterform' => 'Sans-Serif - Neo Grotesque', - 'scripts' => ['latin', 'latin-ext', 'cyrillic', 'cyrillic-ext', 'greek'], + 'scripts' => ['latin', 'cyrillic', 'greek'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=IBM%20Plex%20Sans%20Condensed', @@ -94,6 +94,47 @@ class BreadLoadableFonts 'useOTL' => 0xFF, 'kashida' => 75, ]], + 'ibmplexsansarabic' => ['name' => 'IBM Plex Sans Arabic', + 'letterform' => 'Sans-Serif - Neo Grotesque', + 'scripts' => ['latin', 'arabic'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=IBM%20Plex%20Sans%20Arabic', + 'R' => 'IBMPlexSansArabic-Regular.ttf', + 'B' => 'IBMPlexSansArabic-Bold.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + 'ibmplexsanshebrew' => ['name' => 'IBM Plex Sans Hebrew', + 'letterform' => 'Sans-Serif - Neo Grotesque', + 'scripts' => ['latin', 'hebrew'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=IBM%20Plex%20Sans%20Hebrew', + 'R' => 'IBMPlexSansHebrew-Regular.ttf', + 'B' => 'IBMPlexSansHebrew-Bold.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], + 'ibmplexserif' => ['name' => 'IBM Plex Serif', + 'letterform' => 'Serif - Scotch', + 'scripts' => ['latin', 'cyrillic', 'cyrillic-ext'], + 'specimen' => 'Google Fonts', + 'remote' => [ + 'manifest' => 'https://fonts.google.com/download/list?family=IBM%20Plex%20Serif', + 'R' => 'IBMPlexSerif-Regular.ttf', + 'B' => 'IBMPlexSerif-Bold.ttf', + 'I' => 'IBMPlexSerif-Italic.ttf', + 'BI' => 'IBMPlexSerif-BoldItalic.ttf', + ], + 'configuration' => [ + 'useOTL' => 0xFF, + 'kashida' => 75, + ]], 'arvo' => ['name' => 'Arvo', 'letterform' => 'Slab-Serif', 'scripts' => ['latin', 'latin-ext'], @@ -109,7 +150,7 @@ class BreadLoadableFonts ]], 'cairo' => ['name' => 'Cairo', 'letterform' => 'Sans-Serif - Superellipse', - 'scripts' => ['latin', 'latin-ext', 'arabic'], + 'scripts' => ['latin', 'arabic'], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Cairo', @@ -332,14 +373,14 @@ function calcActions($font) if (in_array($font, $this->getUploadedFonts())) { return [ 'removefont' => [ - 'text' => __('remove', 'bread'), + 'text' => 'Remove', 'action' => 'removefont', 'lambda' => [$this, 'removeFont'] ]]; } else { return [ 'installfont' => [ - 'text' => __('Install', 'bread'), + 'text' => 'Install', 'action' => 'installfont', 'lambda' => [$this, 'installFont'] ]]; From d570f925cffa87e0695a64af76ec29f6bf6eef68 Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 24 Jun 2026 00:23:36 +0200 Subject: [PATCH 31/34] lint --- admin/partials/_custom_fonts_setup.php | 10 +++++----- bread_loadable_fonts/class-bread-loadable-fonts.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index bde9b6df..b20ac83a 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -115,22 +115,22 @@ protected function extra_tablenav($which) $letterform = $_GET['letterform'] ?? '*'; $script = $_GET['script'] ?? '*'; ?>
    - + diff --git a/bread_loadable_fonts/class-bread-loadable-fonts.php b/bread_loadable_fonts/class-bread-loadable-fonts.php index 2b6b9380..bc24e0a0 100644 --- a/bread_loadable_fonts/class-bread-loadable-fonts.php +++ b/bread_loadable_fonts/class-bread-loadable-fonts.php @@ -8,7 +8,7 @@ class BreadLoadableFonts var $custom_fonts = [ 'roboto' => ['name' => 'Roboto', 'letterform' => 'Sans-Serif - Neo-Grotesque', - 'scripts' => ['latin', 'cyrillic', 'greek', 'greek-ext'], + 'scripts' => ['latin', 'cyrillic', 'greek',], 'specimen' => 'Google Fonts', 'remote' => [ 'manifest' => 'https://fonts.google.com/download/list?family=Roboto', From 48beb5223689647c4812a8ec13f89076896ce04e Mon Sep 17 00:00:00 2001 From: otrok7 <50595291+otrok7@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:33:31 +0200 Subject: [PATCH 32/34] Table improvements and removed empty i18n class --- admin/css/admin.css | 5 ++- admin/partials/_custom_fonts_setup.php | 33 ++++++++++++++++-- includes/class-bread-i18n.php | 47 -------------------------- includes/class-bread.php | 30 ++-------------- 4 files changed, 38 insertions(+), 77 deletions(-) delete mode 100644 includes/class-bread-i18n.php diff --git a/admin/css/admin.css b/admin/css/admin.css index 375ce0bf..5404521f 100644 --- a/admin/css/admin.css +++ b/admin/css/admin.css @@ -556,4 +556,7 @@ select.bread_service_body_select { .nav-tab-wrapper { width: max-content !important; } -.select2-search--inline { display: contents; } \ No newline at end of file +.select2-search--inline { display: contents; } +#custom-fonts .bulkactions { + display: none; +} \ No newline at end of file diff --git a/admin/partials/_custom_fonts_setup.php b/admin/partials/_custom_fonts_setup.php index b20ac83a..da6c0bcb 100644 --- a/admin/partials/_custom_fonts_setup.php +++ b/admin/partials/_custom_fonts_setup.php @@ -54,7 +54,7 @@ function column_name($font) $actions = []; if (isset($font['actions'])) { foreach ($font['actions'] as $key => $action) { - $actions[$key] = sprintf('' . $action['text'] . '', $_REQUEST['page'], $action['action'], $font['slug'], $this->nonce); + $actions[$key] = sprintf('' . esc_attr($action['text']) . '', esc_attr($_REQUEST['page']), esc_attr($action['action']), esc_attr($font['slug']), esc_attr($this->nonce)); } } $name = $font['name']; @@ -77,12 +77,18 @@ function prepare_items() } if (isset($_GET['letterform']) && $_GET['letterform'] != '*') { $this->items = array_filter($this->items, function ($font) { - return $_GET['letterform'] == $font['letterform']; + return stt_starts_with($font['letterform'], $_GET['letterform']); }); } foreach ($this->items as $slug => &$info) { $info['slug'] = $slug; } + if (isset($_GET['view']) && $_GET['view'] != 'all') { + $this->items = array_filter($this->items, function ($font) { + $bool = in_array($font['slug'], $this->active); + return ($_GET['view'] == 'active') ? $bool : !$bool; + }); + } } private function selected($a, $b) { @@ -110,12 +116,34 @@ private function getAllLetterforms(): array } return $ret; } + protected function get_views(): array + { + $filters = ""; + if (isset($_GET['script'])) { + $filters .= "&script=" . $_GET['script']; + } + if (isset($_GET['letterform'])) { + $filters .= "&letterform=" . $_GET['letterform']; + } + $ret = [ + 'all' => sprintf('All', esc_attr($_REQUEST['page']), $filters), + 'active' => sprintf('Active', esc_attr($_REQUEST['page']), $filters), + 'disabled' => sprintf('Disabled', esc_attr($_REQUEST['page']), $filters), + ]; + if (isset($_GET['view']) && in_array($_GET['view'], array_keys($ret))) { + $ret[$_GET['view']] = "" . $ret[$_GET['view']] . ""; + } else { + $ret['all'] = "" . $ret['all'] . ""; + } + return $ret; + } protected function extra_tablenav($which) { $letterform = $_GET['letterform'] ?? '*'; $script = $_GET['script'] ?? '*'; ?> + - +