diff --git a/.gitignore b/.gitignore index b2a06a1..0c213d3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .vscode/* .DS_Store .idea +.phpunit.result.cache vendor/ logs/ build/ diff --git a/.phpcs.xml b/.phpcs.xml index b909cb3..2b905e1 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -35,7 +35,7 @@ custom standard so you don't have to specify the patterns on the command line. --> - mpdf/* + vendor/* +
+
+
+ + \ No newline at end of file diff --git a/admin/partials/_custom_section_setup.php b/admin/partials/_custom_section_setup.php new file mode 100644 index 0000000..de9e4c9 --- /dev/null +++ b/admin/partials/_custom_section_setup.php @@ -0,0 +1,45 @@ + +
+
+
+
+
+
+

The Custom Content can be customized with text, graphics, tables, shortcodes, ect.

+

Default Font Size can be changed for specific text in the editor.

+

Add Media button - upload and add graphics.

+

Meeting List Shortcodes dropdown - insert variable data.

+

The Custom Content will print immediately after the meetings in the meeting list.

+
+
+

Custom Section Content(?)

+
+

Default Font Size:    + Line Height:

+
+ false, + 'editor_height' => 500, + 'resize' => true, + "media_buttons" => true, + "drag_drop_upload" => true, + "editor_css" => "", + "teeny" => false, + 'quicktags' => true, + 'wpautop' => false, + 'textarea_name' => $editor_id, + 'tinymce' => array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'front_page_button') + ); + wp_editor(stripslashes(str_replace("http://", $this->bread->getProtocol(), $this->bread->getOption('custom_section_content'))), $editor_id, $settings); + ?> +
+
+
+
+
+
\ No newline at end of file diff --git a/admin/partials/_front_page_setup.php b/admin/partials/_front_page_setup.php new file mode 100644 index 0000000..866ee2c --- /dev/null +++ b/admin/partials/_front_page_setup.php @@ -0,0 +1,44 @@ + +
+
+
+
+
+
+

The Front Page can be customized with text, graphics, tables, shortcodes, ect.

+

Add Media button - upload and add graphics.

+

Meeting List Shortcodes dropdown - insert custom data.

+

Default Font Size can be changed for specific text.

+
+
+

Front Page Content(?)

+
+

Default Font Size:    + Line Height:

+
+ false, + 'editor_height' => 500, + 'resize' => true, + "media_buttons" => true, + "drag_drop_upload" => true, + "editor_css" => "", + "teeny" => false, + 'quicktags' => true, + 'wpautop' => false, + 'textarea_name' => $editor_id, + 'tinymce' => array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'front_page_button') + ); + wp_editor(stripslashes(str_replace("http://", $this->bread->getProtocol(), $this->bread->getOption('front_page_content'))), $editor_id, $settings); + ?> +
+
+
+
+
+
\ No newline at end of file diff --git a/partials/_helpers.php b/admin/partials/_helpers.php similarity index 100% rename from partials/_helpers.php rename to admin/partials/_helpers.php diff --git a/admin/partials/_layout_setup.php b/admin/partials/_layout_setup.php new file mode 100644 index 0000000..ec84152 --- /dev/null +++ b/admin/partials/_layout_setup.php @@ -0,0 +1,194 @@ + +
+
+
+
+
+ +

Page Layout Defaults

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Meeting List SizePage LayoutOrientationPaper SizePage Height
Smaller AreasTri-FoldLandscapeLetter, A4195, 180
Medium AreaQuad-FoldLandscapeLegal, A4195, 180
Large Area, Region, MetroHalf-FoldLandscapeBooklet, A5250, 260
AnythingFull PagePortrait, LandscapeLetter, Legal, A4None
+

When a layout is clicked defaults are reset for orientation, paper size and page height.

+
+
+

Page Layout(?)

+
+ type="hidden"> +
+
Single Page
+ bread->getOption('page_fold') == 'flyer' ? 'checked' : '') ?>> + bread->getOption('page_fold') == 'tri' ? 'checked' : '') ?>> + bread->getOption('page_fold') == 'quad' ? 'checked' : '') ?>> +
+ bread->getOption('page_orientation') == 'P' ? 'checked' : '') ?>> + bread->getOption('page_orientation') == 'L' ? 'checked' : '') ?>> +
+
Booklets
+ bread->getOption('page_fold') == 'half' ? 'checked' : '') ?>> + bread->getOption('page_fold') == 'full' ? 'checked' : '') ?>> +
+ bread->getOption('booklet_pages') == '1' ? 'checked' : '') ?> /> +
+
+
+
+ Page Size:
+ bread->getOption('page_size') == '5inch' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'letter' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'legal' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'ledger' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'A4' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'A5' ? 'checked' : '') ?>> + bread->getOption('page_size') == 'A6' ? 'checked' : '') ?>> +
+ Page Margin Top:     + Bottom:     + Left:     + Right:     +
+
+
+
+
+
+
+
+
+
+
+

+ Describes things on the page other than the contents. Headers, footers, page numbers. +
What options you see will be dependant on the layout selected. +

+
+
+

Page Decorations(?)

+
+
+ The page header is a title that goes across the entire page above the meetings.
+ + + + Header Margin Top:     +
Header Text:     +
Watermark:     +
+
+ + +
+ + + +
+
+ Page Numbers Font Size: +
+
+ Column Gap Width: +
+
+ + Separator: bread->getOption('column_line') == '1' ? 'checked' : '') ?> /> + +
+
+
+
+
+
+
+
+

Base Fonts and Colors

+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+

Enable PDF Protection.

+

Encrypts and sets the PDF document permissions for the PDF file.

+

PDF can be opened and printed. +

Optional Password to allow editing in a PDF editor. +

Note: PDF is encrypted and cannot be opened in MS Word at all.

+
+
+

Password Protection(?)

+
+
+ + bread->getOption('include_protection') == '1' ? 'checked' : '') ?>>Enable Protection +
+ + +
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/admin/partials/_meeting_list_setup.php b/admin/partials/_meeting_list_setup.php new file mode 100644 index 0000000..6302f4f --- /dev/null +++ b/admin/partials/_meeting_list_setup.php @@ -0,0 +1,72 @@ + +
+
+
+

Start Here: Meeting List Setup Wizard

+ +

Multiple Meeting Lists

+
+

This tool supports multiple meeting lists per site.

+

This feature is configured from the

Backup/Restore
Tab. There, each concurrent meeting list can be given a + name, and the system gives the meeting list a numberic identifier. The meeting list can then be generated using

+ a link of the form http://[host]?current-meeting-list=[id]

+

After switching to another meeting list, any changes in the admin UI impact the currently selected meeting list

+

If you want to give another user access to bread you can give that use the "manage_bread" capability using a custom role editor.

+
+

Reusable Templating

+
+

You can dynamically set some of the options to create a reusable template.

+

In order to change the meeting information you can pass a dynamic custom query using &custom_query=, ensure you are using URL encoding.

+

You can also use any combinations of [querystring_custom_*], where * is any digit. You can then override that specific value using it in querystring as &querystring_custom_1= (for instance).

+

You can use any HTML characters, including line breaks.

+

Here is a video of it in action: https://bmlt.app/reusable-templates-with-bread-1-6-x/

+
+

Extending Bread

+
+

Advanced users can extend the functionality of Bread using the WordPress filter mechanism.

+

The Bread_Enrich_Meeting_Data filter allows the user to expose calculated fields in their the templates to their meetings. + An example for the use of this is to make meetings in other languages easier to find by modifying the name of such a meeting to include a text in the other language. (Normally, other languages are + just noted in the formats, which make the meetings quite hard to find.)

+

This extension has the form:

+
+                add_filter('Bread_Enrich_Meeting_Data', 'enrichMeetingData', 10, 2);
+                function enrichMeetingData($value, $formatsByKey) {...}
+                
+ where $value is an array containing the properties of the meeting, and formatsByKey is a list of the available formats and their properties. +

Similarly, the Bread_Section_Shortcodes filter allows the user to make additional commands available in the Front-Page and Custom-Section parts of their meeting lists. +

This can be used to exposed additional mPdf commands. +

This extension has the form:

+
+                add_filter('Bread_Section_Shortcodes', 'sectionShortcodes', 10, 3);
+                function sectionShortcodes($section_shortcodes, $areas, $formats_used) {...}
+                
+ where $section_shortcodes is an array containing the default shortcodes, areas is a list of the service bodies and their metadata and formats_used is an array of format metadata. + +
+

Support and Help

+
+

File an issue https://github.com/bmlt-enabled/bread/issues

+ Debug Information +
    +
  • Bread Version:
  • +
  • Wordpress Version:
  • +
  • Protocol: bread->getProtocol(); ?>
  • +
  • PHP Version:
  • +
  • Server Version:
  • +
  • Temporary Directory:
  • +
+
+
+
+
+
\ No newline at end of file diff --git a/admin/partials/_meetings_setup.php b/admin/partials/_meetings_setup.php new file mode 100644 index 0000000..fd8da41 --- /dev/null +++ b/admin/partials/_meetings_setup.php @@ -0,0 +1,392 @@ + +
+
+
+
+
+
+

Customize the Meeting Group Header to your specification.

+

The Meeting Group Header will contain the data from Group By.

+
+
+

Meeting Group [Column] Header(?)

+
+
+ + bread->getOption('suppress_heading') == '1' ? 'checked' : '') ?>> + + + + + + + + + +
Font Size: +
+ +
+
+
+ +
+
+ + bread->getOption('header_uppercase') == '1' ? 'checked' : '') ?>> + + bread->getOption('header_bold') == '1' ? 'checked' : '') ?>> + + bread->getOption('cont_header_shown') == '1' ? 'checked' : '') ?>>
+
+

+

+ + +
+
+ +

+ + + +

+ +
+
+ +

+ + + +

+
+ +
+ +

+ + + +

+ +
+
+ +

+ + + +

+
+
+

+ + + + +

+
+
+

+ + +

+
+
+ + +
+ +
+ + +
+ +
+
+
+
+
+
+

The Meeting Template is a powerful and flexible method for customizing meetings using + HTML markup and BMLT field names. The template is set-up once and never needs to be messed + with again. Note: When changes are made to the Default Font Size or Line Height, the template + may need to be adjusted to reflect those changes.

+

Sample templates can be found in the editor drop down menu Meeting Template.

+

BMLT fields can be found in the editor drop down menu Meeting Template Fields.

+

The Default Font Size and Line Height will be used for the meeting template.

+

Font Size and Line Height can be overridden using HTML mark-up in the meeting text.

+
+
+
+

Meeting Template(?)

+
+

+ Default Font Size:    + Line Height:    + Wheelchair Icon Size:    +

Avoid using tables which will greatly slow down the generation time. Use CSS instead to get table-like effects if need be.
+
+ false, + 'editor_height' => 110, + 'resize' => true, + "media_buttons" => false, + "drag_drop_upload" => true, + "editor_css" => "", + "teeny" => false, + 'quicktags' => true, + 'wpautop' => false, + 'textarea_name' => $editor_id, + 'tinymce' => array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'custom_template_button_1,custom_template_button_2') + ); + wp_editor(stripslashes($this->bread->getOption('meeting_template_content')), $editor_id, $settings); + ?> +
+
+
+
+

Start Time Format(?)

+
+ + + + + + + + + + + + + + + + + +
+
bread->getOption('time_clock') == '12' || $this->bread->getOption('time_clock') == '' ? 'checked' : '') ?>>
+
+
bread->getOption('time_option') == '1' || $this->bread->getOption('time_option') == '' ? 'checked' : '') ?>>
+
+ bread->getOption('remove_space') == '0' || $this->bread->getOption('remove_space') == '' ? 'checked' : ''; ?> +
>
+
+
bread->getOption('time_clock') == '24' ? 'checked' : '') ?>>
+
+
bread->getOption('time_option') == '2' ? 'checked' : '') ?>>
+
+
bread->getOption('remove_space') == '1') ? 'checked' : ''; ?>>
+
+
bread->getOption('time_clock') == '24fr' ? 'checked' : '') ?>>
+
+
bread->getOption('time_option') == '3' ? 'checked' : '') ?>>
+
+
+
+
+
+

Include Only This Meeting Format(?)

+
+ + +
+
+
+

Additional List

+
+

+ This section allows the definition of an additional meeting list, containing meetings that should not be included in the main + list. This is typically virtual meetings, but it can be any group of meetings identified by a format. +

+

+ + +

+

+ + +

+

+ + +

+ bread->getOption('page_fold') == 'half' || $this->bread->getOption('page_fold') == 'full') { + ?> + + +

+ + +

+ +

bread->getOption('include_additional_list') == '1' ? 'checked' : '') ?>>Include meetings with this format in the main list

+ If you wish to define different contents for the additional list, use this template. +
+ false, + 'editor_height' => 110, + 'resize' => true, + "media_buttons" => false, + "drag_drop_upload" => true, + "editor_css" => "", + "teeny" => false, + 'quicktags' => true, + 'wpautop' => false, + 'textarea_name' => $editor_id, + 'tinymce' => array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'custom_template_button_1,custom_template_button_2') + ); + wp_editor(stripslashes($this->bread->getOption('additional_list_template_content')), $editor_id, $settings); + ?> +
+
+
+
+
+
\ No newline at end of file diff --git a/admin/partials/bread-admin-display.php b/admin/partials/bread-admin-display.php new file mode 100644 index 0000000..a851b38 --- /dev/null +++ b/admin/partials/bread-admin-display.php @@ -0,0 +1,322 @@ +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
"; + } elseif ($this->connected) { + $this->server_version = "
Your BMLT Server is running " . $this->connected . "
"; + } + } + } + private 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 = "selected"'; + } ?> + bread->getConfigurationForSettingId($this->bread->getRequestedSetting()); + $this->lang = $this->bread->bmlt()->get_bmlt_server_lang(); + ?> +
    +
    + bread->setOption('bread_version', sanitize_text_field($_POST['bread_version'])); + $this->bread->setOption('front_page_content', wp_kses_post($_POST['front_page_content'])); + $this->bread->setOption('front_page_line_height', $_POST['front_page_line_height']); + $this->bread->setOption('front_page_font_size', floatval($_POST['front_page_font_size'])); + $this->bread->setOption('content_font_size', floatval($_POST['content_font_size'])); + $this->bread->setOption('suppress_heading', floatval($_POST['suppress_heading'])); + $this->bread->setOption('header_font_size', floatval($_POST['header_font_size'])); + $this->bread->setOption('header_text_color', sanitize_hex_color($_POST['header_text_color'])); + $this->bread->setOption('header_background_color', sanitize_hex_color($_POST['header_background_color'])); + $this->bread->setOption('header_uppercase', intval($_POST['header_uppercase'])); + $this->bread->setOption('header_bold', intval($_POST['header_bold'])); + $this->bread->setOption('sub_header_shown', sanitize_text_field($_POST['sub_header_shown'])); + $this->bread->setOption('cont_header_shown', intval($_POST['cont_header_shown'])); + $this->bread->setOption( + 'column_gap', + isset($_POST['column_gap']) ? + intval($_POST['column_gap']) : 5 + ); + $this->bread->setOption('margin_right', intval($_POST['margin_right'])); + $this->bread->setOption('margin_left', intval($_POST['margin_left'])); + $this->bread->setOption('margin_bottom', intval($_POST['margin_bottom'])); + $this->bread->setOption('margin_top', intval($_POST['margin_top'])); + $this->bread->setOption('margin_header', intval($_POST['margin_header'])); + $this->bread->setOption( + 'margin_footer', + isset($_POST['margin_footer']) ? + intval($_POST['margin_footer']) : 5 + ); + $this->bread->setOption('pageheader_fontsize', floatval($_POST['pageheader_fontsize'])); + $this->bread->setOption('pageheader_textcolor', sanitize_hex_color($_POST['pageheader_textcolor'])); + $this->bread->setOption('pageheader_backgroundcolor', sanitize_hex_color($_POST['pageheader_backgroundcolor'])); + $this->bread->setOption('pageheader_content', wp_kses_post($_POST['pageheader_content'])); + $this->bread->setOption('watermark', sanitize_text_field($_POST['watermark'])); + $this->bread->setOption('page_size', sanitize_text_field($_POST['page_size'])); + $this->bread->setOption('page_orientation', sanitize_text_field($_POST['page_orientation'])); + $this->bread->setOption('page_fold', sanitize_text_field($_POST['page_fold'])); + $this->bread->setOption( + 'booklet_pages', + isset($_POST['booklet_pages']) ? + boolval($_POST['booklet_pages']) : false + ); + $this->bread->setOption('meeting_sort', sanitize_text_field($_POST['meeting_sort'])); + $this->bread->setOption('main_grouping', sanitize_text_field($_POST['main_grouping'])); + $this->bread->setOption('subgrouping', sanitize_text_field($_POST['subgrouping'])); + $this->bread->setOption('borough_suffix', sanitize_text_field($_POST['borough_suffix'])); + $this->bread->setOption('county_suffix', sanitize_text_field($_POST['county_suffix'])); + $this->bread->setOption('neighborhood_suffix', sanitize_text_field($_POST['neighborhood_suffix'])); + $this->bread->setOption('city_suffix', sanitize_text_field($_POST['city_suffix'])); + $this->bread->setOption('meeting_template_content', wp_kses_post($_POST['meeting_template_content'])); + $this->bread->setOption('additional_list_template_content', wp_kses_post($_POST['additional_list_template_content'])); + $this->bread->setOption( + 'column_line', + isset($_POST['column_line']) ? + boolval($_POST['column_line']) : 0 + ); + $this->bread->setOption( + 'col_color', + isset($_POST['col_color']) ? + sanitize_hex_color($_POST['col_color']) : '#bfbfbf' + ); + $this->bread->setOption('custom_section_content', wp_kses_post($_POST['custom_section_content'])); + $this->bread->setOption('custom_section_line_height', floatval($_POST['custom_section_line_height'])); + $this->bread->setOption('custom_section_font_size', floatval($_POST['custom_section_font_size'])); + $this->bread->setOption( + 'pagenumbering_font_size', + isset($_POST['pagenumbering_font_size']) ? + floatval($_POST['pagenumbering_font_size']) : '9' + ); + $this->bread->setOption('used_format_1', sanitize_text_field($_POST['used_format_1'])); + $this->bread->setOption('recurse_service_bodies', isset($_POST['recurse_service_bodies']) ? 1 : 0); + $this->bread->setOption('extra_meetings_enabled', isset($_POST['extra_meetings_enabled']) ? intval($_POST['extra_meetings_enabled']) : 0); + $this->bread->setOption('include_protection', boolval($_POST['include_protection'])); + $this->bread->setOption('weekday_language', sanitize_text_field($_POST['weekday_language'])); + $this->bread->setOption('additional_list_language', sanitize_text_field($_POST['additional_list_language'])); + $this->bread->setOption('weekday_start', sanitize_text_field($_POST['weekday_start'])); + $this->bread->setOption( + 'meeting1_footer', + isset($_POST['meeting1_footer']) ? + sanitize_text_field($_POST['meeting1_footer']) : '' + ); + $this->bread->setOption( + 'meeting2_footer', + isset($_POST['meeting2_footer']) ? + sanitize_text_field($_POST['meeting2_footer']) : '' + ); + $this->bread->setOption( + 'nonmeeting_footer', + isset($_POST['nonmeeting_footer']) ? + sanitize_text_field($_POST['nonmeeting_footer']) : '' + ); + $this->bread->setOption('include_additional_list', boolval($_POST['include_additional_list'])); + $this->bread->setOption('additional_list_format_key', sanitize_text_field($_POST['additional_list_format_key'])); + $this->bread->setOption('additional_list_sort_order', sanitize_text_field($_POST['additional_list_sort_order'])); + $this->bread->setOption('base_font', sanitize_text_field($_POST['base_font'])); + $this->bread->setOption('colorspace', sanitize_text_field($_POST['colorspace'])); + $this->bread->setOption('wheelchair_size', sanitize_text_field($_POST['wheelchair_size'])); + $this->bread->setOption('protection_password', sanitize_text_field($_POST['protection_password'])); + $this->bread->setOption('time_clock', sanitize_text_field($_POST['time_clock'])); + $this->bread->setOption('time_option', intval($_POST['time_option'])); + $this->bread->setOption('remove_space', boolval($_POST['remove_space'])); + $this->bread->setOption('content_line_height', floatval($_POST['content_line_height'])); + $this->bread->setOption('root_server', sanitize_url($_POST['root_server'])); + $this->bread->setOption('service_body_1', sanitize_text_field($_POST['service_body_1'])); + $this->bread->setOption('service_body_2', sanitize_text_field($_POST['service_body_2'])); + $this->bread->setOption('service_body_3', sanitize_text_field($_POST['service_body_3'])); + $this->bread->setOption('service_body_4', sanitize_text_field($_POST['service_body_4'])); + $this->bread->setOption('service_body_5', sanitize_text_field($_POST['service_body_5'])); + $this->bread->setOption('cache_time', intval($_POST['cache_time'])); + $this->bread->setOption('custom_query', sanitize_text_field($_POST['custom_query'])); + $this->bread->setOption('additional_list_custom_query', sanitize_text_field($_POST['additional_list_custom_query'])); + $this->bread->setOption('user_agent', isset($_POST['user_agent']) ? sanitize_text_field($_POST['user_agent']) : 'None'); + $this->bread->setOption('sslverify', isset($_POST['sslverify']) ? '1' : '0'); + $this->bread->setOption('extra_meetings', array()); + if (isset($_POST['extra_meetings'])) { + foreach ($_POST['extra_meetings'] as $extra) { + $this->bread->setOption('extra_meetings', wp_kses_post($extra)); + } + } + $authors = isset($_POST['author_chosen']) ? $_POST['author_chosen'] : []; + $this->bread->setOption('authors', array()); + foreach ($authors as $author) { + $this->bread->appendOption('authors', intval($author)); + } + $user = wp_get_current_user(); + if (!is_array($this->bread->getOption('authors'))) { + $this->bread->setOption('authors', array($this->bread->getOption('authors'))); + } + if (!in_array($user->ID, $this->bread->getOption('authors'))) { + $this->bread->setOption('authors', $user->ID); + } + if ($_POST['bmltmeetinglistpreview']) { + session_start(); + $_SESSION['bread_preview_settings'] = $this->bread->getOptions(); + wp_redirect(home_url() . "?preview-meeting-list=1"); + exit(); + } + set_transient('admin_notice', 'Please put down your weapon. You have 20 seconds to comply.'); + if (!$this->admin->current_user_can_modify()) { + echo '

    You do not have permission to save this configuation!

    '; + } else { + $this->admin->save_admin_options(); + echo '

    Your changes were successfully saved!

    '; + $num = delete_transient($this->bread->get_TransientKey($this->bread->getRequestedSetting())); + if ($num > 0) { + echo "

    $num Cache entries deleted

    "; + } + } + echo '
    '; + } elseif (isset($_REQUEST['pwsix_action']) && $_REQUEST['pwsix_action'] == "import_settings") { + echo '

    Your file was successfully imported!

    '; + $num = delete_transient($this->bread->get_TransientKey($this->bread->getRequestedSetting())); + } elseif (isset($_REQUEST['pwsix_action']) && $_REQUEST['pwsix_action'] == "default_settings_success") { + echo '

    Your default settings were successfully updated!

    '; + $num = delete_transient($this->bread->get_TransientKey($this->bread->getRequestedSetting())); + } + $this->bread->fillUnsetOptions(); + ?> +
    +
    + +
    +
    + +
    + +
    +
    + +
    + + + + + refresh_status(); ?> +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + admin->current_user_can_modify()) { ?> + + +

    Generate Meeting List

    +
      Save Changes before Generating Meeting List.
    +
    + +
    +
    +
    + +
    +
    +
    + + + + \ No newline at end of file diff --git a/admin/templates/10/flyer-landscape-largefont.json b/admin/templates/10/flyer-landscape-largefont.json new file mode 100644 index 0000000..4ecfd50 --- /dev/null +++ b/admin/templates/10/flyer-landscape-largefont.json @@ -0,0 +1,81 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "header_font_size": 12, + "front_page_content": "

    \u00a0<\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    [service_body_1]<\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

    [date]<\/strong>\u00a0<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    24 HOUR HELPLINE<\/b><\/span><\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

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

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    https:\/\/na.org<\/b><\/p>\r\n

     <\/p>\r\n

    SUGGESTIONS FOR EVERYONE<\/strong><\/span><\/p>\r\n

    DON\\'T USE no matter what<\/strong><\/p>\r\n

    Ask your Higher Power to keep you clean<\/strong><\/p>\r\n

    Come early and stay late<\/strong><\/p>\r\n

    Get a home group<\/strong><\/p>\r\n

    Go to 90 meetings in 90 days<\/strong><\/p>\r\n

    Read NA literature daily<\/strong><\/p>\r\n

    Get and use a sponsor<\/strong><\/p>\r\n

    Use the PHONE<\/strong><\/p>\r\n

    KEEP COMING BACK. IT WORKS<\/strong><\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    Meetings Weekly:\u00a0[meeting_count]<\/p>", + "front_line_height": "1.0", + "content_font_size": 10, + "content_line_height": 1.2, + "front_page_font_size": 10, + "front_page_line_height": "1.0", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "flyer", + "meeting_sort": "day", + "cache_time": 0, + "page_orientation": "L", + "custom_section_content": "

     <\/p>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    MEETING FORMAT LEGEND<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [format_codes_used_basic]<\/p>\r\n

    [new_column]<\/p>", + "custom_section_line_height": 1, + "custom_section_font_size": 12, + "column_line": false, + "used_format_1": "", + "time_clock": "12", + "column_gap": 6, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n
    time<\/strong><\/td>\r\ngroup, location, info, street,\u00a0city, state,\u00a0zip\u00a0(formats)\u00a0comments<\/em><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>", + "col_color": "#274fae", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "sub_header_shown": "display", + "borough_suffix": "Borough", + "county_suffix": "County", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "include_protection": false, + "base_font": "dejavusanscondensed", + "protection_password": "", + "custom_query": "", + "extra_meetings": [], + "authors": [], + "pageheader_fontsize": 6.5, + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#000000", + "booklet_pages": false, + "additional_list_template_content": "", + "colorspace": "0", + "additional_list_language": "", + "weekday_start": "1", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "meeting_name", + "additional_list_custom_query": "", + "user_agent": "None", + "sslverify": "0", + "wheelchair_size": "20px", + "nonmeeting_footer": "{PAGENO}", + "meeting1_footer": "{PAGENO}", + "meeting2_footer": "", + "bread_version": "2.8.0", + "cont_header_shown": 0, + "margin_header": 0, + "margin_footer": 5, + "pageheader_content": "", + "watermark": "", + "main_grouping": "day", + "subgrouping": "" +} \ No newline at end of file diff --git a/admin/templates/20/trifold-landscape-largefont.json b/admin/templates/20/trifold-landscape-largefont.json new file mode 100644 index 0000000..d9bb6a7 --- /dev/null +++ b/admin/templates/20/trifold-landscape-largefont.json @@ -0,0 +1,85 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 14, + "front_page_content": "

     

    \r\n

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

     

    \r\n

    [month_lower] [year] 
    Wöchentliche Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 12, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "tri", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "meeting_template": "", + "custom_section_font_size": 12.3, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    time
    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "weekday_tinyint,start_time", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/30/trifold-landscape-largefont.json b/admin/templates/30/trifold-landscape-largefont.json new file mode 100644 index 0000000..22a1b85 --- /dev/null +++ b/admin/templates/30/trifold-landscape-largefont.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 12, + "front_page_content": "

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

    [month_lower] [year] 
    Wöchentliche Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 10, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "tri", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "

     

    \r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "custom_section_font_size": 10, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    time
    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "same", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/50/trifold-landscape-medfont.json b/admin/templates/50/trifold-landscape-medfont.json new file mode 100644 index 0000000..49f2b32 --- /dev/null +++ b/admin/templates/50/trifold-landscape-medfont.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 10, + "front_page_content": "

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

    [month_lower] [year] 
    Wöchentliche Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 8, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "tri", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "

     

    \r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "custom_section_font_size": 10, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    time
    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "same", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/70/trifold-landscape-medfont.json b/admin/templates/70/trifold-landscape-medfont.json new file mode 100644 index 0000000..3f086ff --- /dev/null +++ b/admin/templates/70/trifold-landscape-medfont.json @@ -0,0 +1,85 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 10, + "front_page_content": "

     

    \r\n

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

     

    \r\n

    [month_lower] [year] 
    Weekly Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 8, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "tri", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "meeting_template": "", + "custom_section_font_size": 12.3, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    time
    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "meeting_name", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/80/quadfold-portrait-max80.json b/admin/templates/80/quadfold-portrait-max80.json new file mode 100644 index 0000000..24389d5 --- /dev/null +++ b/admin/templates/80/quadfold-portrait-max80.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 10, + "front_page_content": "

     

    \r\n

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

     

    \r\n

    [month_lower] [year] 
    Weekly Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 8, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "quad", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "P", + "custom_section_font_size": 12.3, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    time group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "same", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/90/quadfold-portrait-smallfont.json b/admin/templates/90/quadfold-portrait-smallfont.json new file mode 100644 index 0000000..f760c92 --- /dev/null +++ b/admin/templates/90/quadfold-portrait-smallfont.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 8, + "front_page_content": "

     

    \r\n

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

     

    \r\n

    [month_lower] [year] 
    Weekly Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 7, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "quad", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "

     

    \r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

    [new_column]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "P", + "custom_section_font_size": 8, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    time group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "Narcotics Anonymous Meetings in der Deutschsprachigen Region", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "same", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/99999/book-landscape-medfont.json b/admin/templates/99999/book-landscape-medfont.json new file mode 100644 index 0000000..7c6c69c --- /dev/null +++ b/admin/templates/99999/book-landscape-medfont.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "", + "service_body_2": "", + "service_body_3": "", + "header_font_size": 8, + "front_page_content": "

     

    \r\n

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    [service_body_1]

    \r\n

     

    \r\n

    [month_lower_de] [year] 
    Weekly Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    \r\n

     

    \r\n

    [page_break]

    \r\n

    [start_page_numbers]

    ", + "front_line_height": "1.4", + "content_font_size": 7.2, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "", + "service_body_5": "", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "letter", + "page_fold": "half", + "meeting_sort": "city", + "cache_time": 0, + "custom_section_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_detailed]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "custom_section_font_size": 7.2, + "column_line": false, + "used_format_1": "Not Connected", + "page_height": "258", + "column_gap": 5, + "margin_right": 10, + "margin_left": 10, + "margin_bottom": 10, + "margin_top": 10, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    time group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": false, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": 1, + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "Page {PAGENO}", + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "", + "additional_list_custom_query": "", + "wheelchair_size": "20px", + "margin_footer": 5 +} \ No newline at end of file diff --git a/admin/templates/99999/booklet-landscape-medfont.json b/admin/templates/99999/booklet-landscape-medfont.json new file mode 100644 index 0000000..c8608e2 --- /dev/null +++ b/admin/templates/99999/booklet-landscape-medfont.json @@ -0,0 +1,84 @@ +{ + "root_server": "", + "service_body_1": "Not Used", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 12, + "front_page_content": "

    \"default_nalogo\"

    \r\n

     

    \r\n

    NARCOTICS ANONYMOUS

    \r\n

    [service_body_1]

    \r\n

    [month_lower] [year] 
    Wöchentliche Meetings: [meeting_count]

    \r\n

     

    \r\n

     

    ", + "front_line_height": "1.4", + "content_font_size": 12, + "content_line_height": 1.1, + "front_page_font_size": 10.7, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "front_page_line_height": "1.2", + "time_option": 1, + "remove_space": true, + "page_size": "5inch", + "page_fold": "half", + "meeting_sort": "day", + "cache_time": 0, + "custom_section_content": "\r\n\r\n\r\n\r\n\r\n\r\n
    Meeting Format Legend
    \r\n

    [format_codes_used_basic]

    ", + "custom_section_line_height": 1.1, + "page_orientation": "L", + "custom_section_font_size": 12.3, + "column_line": false, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 5, + "margin_left": 5, + "margin_bottom": 5, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    time
    wheelchair
    group
    location, info
    street, city, state, zip
    (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "col_color": "#bfbfbf", + "time_clock": "12", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "weekday_language": "en", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "sub_header_shown": "display", + "margin_header": 10, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 1, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": true, + "retrieve_all_fields": 1, + "weekday_start": "1", + "bread_version": "2.8.0", + "pageheader_content": "", + "authors": [], + "colorspace": "0", + "cont_header_shown": 1, + "user_agent": "None", + "main_grouping": "day", + "subgrouping": "", + "sslverify": "0", + "nonmeeting_footer": "Page {PAGENO}", + "meeting1_footer": "Page {PAGENO}", + "meeting2_footer": "", + "wheelchair_size": "20px", + "margin_footer": 5, + "additional_list_template_content": "", + "additional_list_language": "", + "include_additional_list": false, + "additional_list_format_key": "", + "additional_list_sort_order": "same", + "additional_list_custom_query": "" +} \ No newline at end of file diff --git a/admin/templates/meeting_data_templates.json b/admin/templates/meeting_data_templates.json new file mode 100644 index 0000000..6b982c7 --- /dev/null +++ b/admin/templates/meeting_data_templates.json @@ -0,0 +1,24 @@ +{ + "One Column Template [Time Meeting Data]": "
    time group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "Two Column Template [Time] [Meeting Data]": "
    time
    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "One Column Template with Day [Day Time Meeting Data]": "
    day_abbr time group, location, info, street,city, state,zip(formats)comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "Two Column Template with Day [Day/Time] [Meeting Data]": "
    day_abbr
    time

    wheelchair
    group, location, info, street, city, state, zip (formats) comments
    virtual_meeting_link
    virtual_meeting_additional_info
    ", + "Online Meeting Two Column - Link in QR-Code": [ + "'", + "", + "", + "", + "", + "", + "", + "
    ", + "day_abbr
    time
    ", + "

    [QRCode code='virtual_meeting_link' size='0.50']

    ", + "
    group", + "

    comments

    ", + "

    formats

    ", + "

    phone_meeting_number

    ", + "

    virtual_meeting_additional_info

    ", + "
    " + ] +} \ No newline at end of file diff --git a/admin/tinymce/code/plugin.js b/admin/tinymce/code/plugin.js new file mode 100644 index 0000000..9df09b3 --- /dev/null +++ b/admin/tinymce/code/plugin.js @@ -0,0 +1,71 @@ +/** + * plugin.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/*global tinymce:true */ + +tinymce.PluginManager.add( + 'code', function (editor) { + function showDialog() + { + var win = editor.windowManager.open( + { + title: "Source code", + body: { + type: 'textbox', + name: 'code', + multiline: true, + minWidth: editor.getParam("code_dialog_width", 600), + minHeight: editor.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), + spellcheck: false, + style: 'direction: ltr; text-align: left' + }, + onSubmit: function (e) { + // We get a lovely "Wrong document" error in IE 11 if we + // don't move the focus to the editor before creating an undo + // transation since it tries to make a bookmark for the current selection + editor.focus(); + + editor.undoManager.transact( + function () { + editor.setContent(e.data.code); + } + ); + + editor.selection.setCursorLocation(); + editor.nodeChanged(); + } + } + ); + + // Gecko has a major performance issue with textarea + // contents so we need to set it when all reflows are done + win.find('#code').value(editor.getContent({source_view: true})); + } + + editor.addCommand("mceCodeEditor", showDialog); + + editor.addButton( + 'code', { + icon: 'code', + tooltip: 'Source code', + onclick: showDialog + } + ); + + editor.addMenuItem( + 'code', { + icon: 'code', + text: 'Source code', + context: 'tools', + onclick: showDialog + } + ); + } +); \ No newline at end of file diff --git a/tinymce/code/plugin.min.js b/admin/tinymce/code/plugin.min.js similarity index 100% rename from tinymce/code/plugin.min.js rename to admin/tinymce/code/plugin.min.js diff --git a/tinymce/contextmenu/plugin.min.js b/admin/tinymce/contextmenu/plugin.min.js similarity index 100% rename from tinymce/contextmenu/plugin.min.js rename to admin/tinymce/contextmenu/plugin.min.js diff --git a/admin/tinymce/front_page_button/plugin.js b/admin/tinymce/front_page_button/plugin.js new file mode 100644 index 0000000..1e3d062 --- /dev/null +++ b/admin/tinymce/front_page_button/plugin.js @@ -0,0 +1,15 @@ +tinymce.PluginManager.add( + 'gavickpro_tc_button', function (editor) { + + editor.addButton( + 'gavickpro_tc_button', { + text: 'My test button', + icon: false, + onclick: function () { + editor.insertContent('Hello World!'); + } + } + ); + + } +); \ No newline at end of file diff --git a/admin/tinymce/front_page_button/plugin.min.js b/admin/tinymce/front_page_button/plugin.min.js new file mode 100644 index 0000000..ae6ee07 --- /dev/null +++ b/admin/tinymce/front_page_button/plugin.min.js @@ -0,0 +1,369 @@ +(function () { + tinymce.PluginManager.add( + 'front_page_button', function ( editor, url ) { + editor.addButton( + 'front_page_button', { + text: 'Meeting List Shortcodes', + icon: false, + type: 'listbox', + menu: [ + { + text: 'INSTRUCTIONS', + menu: [ + { + text: 'Video Instructions', + onclick: function () { + editor.windowManager.open( + { + title: 'Shortcode Instructions', + url: 'https://nameetinglist.org/videos/nameetinglist.mp4', + width: 800, + height: 600, + buttons: [{ + text: 'Close', + onclick: 'close' + }] + } + ); + } + }, + ] + }, + { + text: 'Format Code Legend', + menu: [ + { + text: 'Used Format Codes - Abbreviated - Same language as weekdays text', + onclick: function () { + editor.insertContent('
    Meeting Format Legend
    [format_codes_used_basic]

    '); + } + }, + { + text: 'Used Format Codes - Detailed - Same language as weekdays text', + onclick: function () { + editor.insertContent('
    Meeting Format Legend

    [format_codes_used_detailed]

    '); + } + }, + { + text: 'All Format Codes - Abbreviated - Same language as weekdays text', + onclick: function () { + editor.insertContent('
    Meeting Format Legend

    [format_codes_all_basic]

    '); + } + }, + { + text: 'All Format Codes - Detailed - Same language as weekdays text', + onclick: function () { + editor.insertContent('
    Meeting Format Legend

    [format_codes_all_detailed]

    '); + } + }, + { + text: 'Format Table in other languages', + onclick: function() {alert("To insert a format table in another language, simply add _{code} to the shortcode, where code is the standard 2-letter abbreviation for the language. Eg, use '_es' to get spanish")} + } + ] + }, + { + text: 'Helpline Template', + onclick: function () { + editor.insertContent('
    Helplines

    Big Bend Area - Tallahassee

    877-340-5096

    850-224-2321

    Daytona Beach Area - Volusia County

    800-206-0731

    386-628-0318

    First Coast Area - Duval County

    904-723-5683

    '); + } + }, + { + text: 'Phone List Template', + onclick: function () { + editor.insertContent('
    PHONE NUMBERS
     
     
     
     
     
     
     
     
     
     
     
    '); + } + }, + { + text: 'Header Template', + onclick: function () { + editor.insertContent('
    HEADER TEXT
    '); + } + }, + { + text: 'Additional Meetinglist Template', + onclick: function () { + editor.insertContent('
    Additional Meetings

    [additional_meetinglist]

    '); + } + }, + { + text: 'Service Body', + menu: [ + { + text: 'Service Body 1', + onclick: function () { + editor.insertContent('[service_body_1]'); + } + }, + { + text: 'Service Body 2', + onclick: function () { + editor.insertContent('[service_body_2]'); + } + }, + { + text: 'Service Body 3', + onclick: function () { + editor.insertContent('[service_body_3]'); + } + }, + { + text: 'Service Body 4', + onclick: function () { + editor.insertContent('[service_body_4]'); + } + }, + { + text: 'Service Body 5', + onclick: function () { + editor.insertContent('[service_body_5]'); + } + } + ] + }, + { + text: 'Date', + menu: [ + { text: 'Month (UPPER CASE)', + onclick: function () { + editor.insertContent('[month_upper]'); + } }, { text: 'Month (Lower Case)', onclick: function () { + editor.insertContent('[month_lower]'); } }, { text: 'Month French (UPPER CASE)', onclick: function () { + editor.insertContent('[month_upper_fr]'); } }, { text: 'Month French (Lower Case)', onclick: function () { + editor.insertContent('[month_lower_fr]'); } }, { text: 'Month Spanish (UPPER CASE)', onclick: function () { + editor.insertContent('[month_upper_es]'); } }, { text: 'Month Spanish (Lower Case)', onclick: function () { + editor.insertContent('[month_lower_es]'); } }, { + text: 'Day', + onclick: function () { + editor.insertContent('[day]'); + } + }, + { + text: 'Year', + onclick: function () { + editor.insertContent('[year]'); + } + } + ] + }, + { + text: 'Querystring Overrides', + menu: [ + { + text: 'QueryString Custom Text', + onclick: function () { + editor.insertContent('[querystring_custom_*]'); + } + } + ] + }, + { + text: 'Meeting Count', + onclick: function () { + editor.insertContent('[meeting_count]'); + } + }, + { + text: 'New Page (Booklet Only)', + onclick: function () { + editor.insertContent('

    [page_break]

    '); + } + }, + { + text: 'Start Page Numbers (Booklet Only)', + onclick: function () { + editor.insertContent('

    [start_page_numbers]

    '); + } + }, + { + text: 'New Column (Tri & Quad Fold Only)', + onclick: function () { + editor.insertContent('

    [new_column]

    '); + } + } + ] + } + ); + editor.addButton( + 'custom_template_button_1', { + text: 'Meeting Templates', + icon: false, + type: 'listbox', + menu: Object.keys(meetingDataTemplates).reduce((carry,item) => { + carry.push({ + text: item, + onclick: () => editor.setContent( + Array.isArray(meetingDataTemplates[item]) ? + meetingDataTemplates[item].join('') :meetingDataTemplates[item]) + }) + return carry; + },[]) + } + ); + editor.addButton( + 'custom_template_button_2', { + text: 'Meeting Template Fields', + icon: false, + type: 'listbox', + menu: [ + { + text: 'Area', + menu: [ + { + text: 'Area Name (area)', + onclick: function () { + editor.insertContent('area'); + } + }, + { + text: 'Area Initial (area_i)', + onclick: function () { + editor.insertContent('area_i'); + } + } + ] + }, + { + text: 'Meeting Location', + menu: [ + { + text: 'Address (street)', + onclick: function () { + editor.insertContent('street'); + } + }, + { + text: 'Borough (borough)', + onclick: function () { + editor.insertContent('borough'); + } + }, + { + text: 'Bus Lines (bus_lines)', + onclick: function () { + editor.insertContent('bus_lines'); + } + }, + { + text: 'City (city)', + onclick: function () { + editor.insertContent('city'); + } + }, + { + text: 'County (county)', + onclick: function () { + editor.insertContent('county'); + } + }, + { + text: 'Location Name (location)', + onclick: function () { + editor.insertContent('location'); + } + }, + { + text: 'Location Extra Info (info)', + onclick: function () { + editor.insertContent('info'); + } + }, + { text: 'Neighborhood (neighborhood)', onclick: function () { + editor.insertContent('neighborhood'); } }, { text: 'State (state)', onclick: function () { + editor.insertContent('state'); } }, { + text: 'Zip Code (zip)', + onclick: function () { + editor.insertContent('zip'); + } + } + ] + }, + { + text: 'Meeting Information', + menu: [ + { + text: 'Comments (comments)', + onclick: function () { + editor.insertContent('comments'); + } + }, + { + text: 'Duration Hours (hrs)', + onclick: function () { + editor.insertContent('hrs'); + } + }, + { + text: 'Duration Minutes (mins)', + onclick: function () { + editor.insertContent('mins'); + } + }, + { + text: 'Email Contact (email)', + onclick: function () { + editor.insertContent('email'); + } + }, + { + text: 'Format Codes (formats)', + onclick: function () { + editor.insertContent('formats'); + } + }, + { + text: 'Group Name (group)', + onclick: function () { + editor.insertContent('group'); + } + }, + { + text: 'Start Time (time)', + onclick: function () { + editor.insertContent('time'); + } + }, + { + text: 'Weekday (day)', + onclick: function () { + editor.insertContent('day'); + } + }, + { + text: 'Weekday Abbreviated (day_abbr)', + onclick: function () { + editor.insertContent('day_abbr'); + } + }, + { + text: 'Virtual Meeting Link (virtual_meeting_link)', + onclick: function () { + editor.insertContent('virtual_meeting_link'); + } + }, + { + text: 'Virtual Meeting Info (virtual_meeting_additional_info)', + onclick: function () { + editor.insertContent('virtual_meeting_additional_info'); + } + }, + { + text: 'Phone Meeting Number (phone_meeting_number)', + onclick: function () { + editor.insertContent('phone_meeting_number'); + } + }, + { + text: 'QRCode (virtual_meeting_link)', + onclick: function () { + editor.insertContent('[QRCode code="virtual_meeting_link" size="0.8"]'); + } + }, + ] + } + ] + } + ); + } + ); +})(); \ No newline at end of file diff --git a/admin/tinymce/table/plugin.js b/admin/tinymce/table/plugin.js new file mode 100644 index 0000000..207d221 --- /dev/null +++ b/admin/tinymce/table/plugin.js @@ -0,0 +1,2967 @@ +/** + * Compiled inline version. (Library mode) + */ + +/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ +/*globals $code */ + +(function (exports, undefined) { + "use strict"; + + var modules = {}; + + function require(ids, callback) + { + var module, defs = []; + + for (var i = 0; i < ids.length; ++i) { + module = modules[ids[i]] || resolve(ids[i]); + if (!module) { + throw 'module definition dependecy not found: ' + ids[i]; + } + + defs.push(module); + } + + callback.apply(null, defs); + } + + function define(id, dependencies, definition) + { + if (typeof id !== 'string') { + throw 'invalid module definition, module id must be defined and be a string'; + } + + if (dependencies === undefined) { + throw 'invalid module definition, dependencies must be specified'; + } + + if (definition === undefined) { + throw 'invalid module definition, definition function must be specified'; + } + + require( + dependencies, function () { + modules[id] = definition.apply(null, arguments); + } + ); + } + + function defined(id) + { + return !!modules[id]; + } + + function resolve(id) + { + var target = exports; + var fragments = id.split(/[.\/]/); + + for (var fi = 0; fi < fragments.length; ++fi) { + if (!target[fragments[fi]]) { + return; + } + + target = target[fragments[fi]]; + } + + return target; + } + + function expose(ids) + { + for (var i = 0; i < ids.length; i++) { + var target = exports; + var id = ids[i]; + var fragments = id.split(/[.\/]/); + + for (var fi = 0; fi < fragments.length - 1; ++fi) { + if (target[fragments[fi]] === undefined) { + target[fragments[fi]] = {}; + } + + target = target[fragments[fi]]; + } + + target[fragments[fragments.length - 1]] = modules[id]; + } + } + + // Included from: js/tinymce/plugins/table/classes/TableGrid.js + + /** + * TableGrid.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + + /** + * This class creates a grid out of a table element. This + * makes it a whole lot easier to handle complex tables with + * col/row spans. + * + * @class tinymce.tableplugin.TableGrid + * @private + */ + define( + "tinymce/tableplugin/TableGrid", [ + "tinymce/util/Tools", + "tinymce/Env" + ], function (Tools, Env) { + var each = Tools.each; + + function getSpanVal(td, name) + { + return parseInt(td.getAttribute(name) || 1, 10); + } + + return function (editor, table) { + var grid, gridWidth, startPos, endPos, selectedCell, selection = editor.selection, dom = selection.dom; + + function buildGrid() + { + var startY = 0; + + grid = []; + gridWidth = 0; + + each( + ['thead', 'tbody', 'tfoot'], function (part) { + var rows = dom.select('> ' + part + ' tr', table); + + each( + rows, function (tr, y) { + y += startY; + + each( + dom.select('> td, > th', tr), function (td, x) { + var x2, y2, rowspan, colspan; + + // Skip over existing cells produced by rowspan + if (grid[y]) { + while (grid[y][x]) { + x++; + } + } + + // Get col/rowspan from cell + rowspan = getSpanVal(td, 'rowspan'); + colspan = getSpanVal(td, 'colspan'); + + // Fill out rowspan/colspan right and down + for (y2 = y; y2 < y + rowspan; y2++) { + if (!grid[y2]) { + grid[y2] = []; + } + + for (x2 = x; x2 < x + colspan; x2++) { + grid[y2][x2] = { + part: part, + real: y2 == y && x2 == x, + elm: td, + rowspan: rowspan, + colspan: colspan + }; + } + } + + gridWidth = Math.max(gridWidth, x + 1); + } + ); + } + ); + + startY += rows.length; + } + ); + } + + function cloneNode(node, children) + { + node = node.cloneNode(children); + node.removeAttribute('id'); + + return node; + } + + function getCell(x, y) + { + var row; + + row = grid[y]; + if (row) { + return row[x]; + } + } + + function setSpanVal(td, name, val) + { + if (td) { + val = parseInt(val, 10); + + if (val === 1) { + td.removeAttribute(name, 1); + } else { + td.setAttribute(name, val, 1); + } + } + } + + function isCellSelected(cell) + { + return cell && (dom.hasClass(cell.elm, 'mce-item-selected') || cell == selectedCell); + } + + function getSelectedRows() + { + var rows = []; + + each( + table.rows, function (row) { + each( + row.cells, function (cell) { + if (dom.hasClass(cell, 'mce-item-selected') || (selectedCell && cell == selectedCell.elm)) { + rows.push(row); + return false; + } + } + ); + } + ); + + return rows; + } + + function deleteTable() + { + var rng = dom.createRng(); + + rng.setStartAfter(table); + rng.setEndAfter(table); + + selection.setRng(rng); + + dom.remove(table); + } + + function cloneCell(cell) + { + var formatNode, cloneFormats = {}; + + if (editor.settings.table_clone_elements !== false) { + cloneFormats = Tools.makeMap( + (editor.settings.table_clone_elements || 'strong em b i span font h1 h2 h3 h4 h5 h6 p div').toUpperCase(), + /[ ,]/ + ); + } + + // Clone formats + Tools.walk( + cell, function (node) { + var curNode; + + if (node.nodeType == 3) { + each( + dom.getParents(node.parentNode, null, cell).reverse(), function (node) { + if (!cloneFormats[node.nodeName]) { + return; + } + + node = cloneNode(node, false); + + if (!formatNode) { + formatNode = curNode = node; + } else if (curNode) { + curNode.appendChild(node); + } + + curNode = node; + } + ); + + // Add something to the inner node + if (curNode) { + curNode.innerHTML = Env.ie ? ' ' : '
    '; + } + + return false; + } + }, 'childNodes' + ); + + cell = cloneNode(cell, false); + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + if (formatNode) { + cell.appendChild(formatNode); + } else { + if (!Env.ie || Env.ie > 10) { + cell.innerHTML = '
    '; + } + } + + return cell; + } + + function cleanup() + { + var rng = dom.createRng(), row; + + // Empty rows + each( + dom.select('tr', table), function (tr) { + if (tr.cells.length === 0) { + dom.remove(tr); + } + } + ); + + // Empty table + if (dom.select('tr', table).length === 0) { + rng.setStartBefore(table); + rng.setEndBefore(table); + selection.setRng(rng); + dom.remove(table); + return; + } + + // Empty header/body/footer + each( + dom.select('thead,tbody,tfoot', table), function (part) { + if (part.rows.length === 0) { + dom.remove(part); + } + } + ); + + // Restore selection to start position if it still exists + buildGrid(); + + // If we have a valid startPos object + if (startPos) { + // Restore the selection to the closest table position + row = grid[Math.min(grid.length - 1, startPos.y)]; + if (row) { + selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); + selection.collapse(true); + } + } + } + + function fillLeftDown(x, y, rows, cols) + { + var tr, x2, r, c, cell; + + tr = grid[y][x].elm.parentNode; + for (r = 1; r <= rows; r++) { + tr = dom.getNext(tr, 'tr'); + + if (tr) { + // Loop left to find real cell + for (x2 = x; x2 >= 0; x2--) { + cell = grid[y + r][x2].elm; + + if (cell.parentNode == tr) { + // Append clones after + for (c = 1; c <= cols; c++) { + dom.insertAfter(cloneCell(cell), cell); + } + + break; + } + } + + if (x2 == -1) { + // Insert nodes before first cell + for (c = 1; c <= cols; c++) { + tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); + } + } + } + } + } + + function split() + { + each( + grid, function (row, y) { + each( + row, function (cell, x) { + var colSpan, rowSpan, i; + + if (isCellSelected(cell)) { + cell = cell.elm; + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan > 1 || rowSpan > 1) { + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + // Insert cells right + for (i = 0; i < colSpan - 1; i++) { + dom.insertAfter(cloneCell(cell), cell); + } + + fillLeftDown(x, y, rowSpan - 1, colSpan); + } + } + } + ); + } + ); + } + + function merge(cell, cols, rows) + { + var pos, startX, startY, endX, endY, x, y, startCell, endCell, children, count; + + // Use specified cell and cols/rows + if (cell) { + pos = getPos(cell); + startX = pos.x; + startY = pos.y; + endX = startX + (cols - 1); + endY = startY + (rows - 1); + } else { + startPos = endPos = null; + + // Calculate start/end pos by checking for selected cells in grid works better with context menu + each( + grid, function (row, y) { + each( + row, function (cell, x) { + if (isCellSelected(cell)) { + if (!startPos) { + startPos = {x: x, y: y}; + } + + endPos = {x: x, y: y}; + } + } + ); + } + ); + + // Use selection, but make sure startPos is valid before accessing + if (startPos) { + startX = startPos.x; + startY = startPos.y; + endX = endPos.x; + endY = endPos.y; + } + } + + // Find start/end cells + startCell = getCell(startX, startY); + endCell = getCell(endX, endY); + + // Check if the cells exists and if they are of the same part for example tbody = tbody + if (startCell && endCell && startCell.part == endCell.part) { + // Split and rebuild grid + split(); + buildGrid(); + + // Set row/col span to start cell + startCell = getCell(startX, startY).elm; + setSpanVal(startCell, 'colSpan', (endX - startX) + 1); + setSpanVal(startCell, 'rowSpan', (endY - startY) + 1); + + // Remove other cells and add it's contents to the start cell + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + if (!grid[y] || !grid[y][x]) { + continue; + } + + cell = grid[y][x].elm; + + /*jshint loopfunc:true */ + /*eslint no-loop-func:0 */ + if (cell != startCell) { + // Move children to startCell + children = Tools.grep(cell.childNodes); + each( + children, function (node) { + startCell.appendChild(node); + } + ); + + // Remove bogus nodes if there is children in the target cell + if (children.length) { + children = Tools.grep(startCell.childNodes); + count = 0; + each( + children, function (node) { + if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) { + startCell.removeChild(node); + } + } + ); + } + + dom.remove(cell); + } + } + } + + // Remove empty rows etc and restore caret location + cleanup(); + } + } + + function insertRow(before) + { + var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan; + + // Find first/last row + each( + grid, function (row, y) { + each( + row, function (cell) { + if (isCellSelected(cell)) { + cell = cell.elm; + rowElm = cell.parentNode; + newRow = cloneNode(rowElm, false); + posY = y; + + if (before) { + return false; + } + } + } + ); + + if (before) { + return !posY; + } + } + ); + + // If posY is undefined there is nothing for us to do here...just return to avoid crashing below + if (posY === undefined) { + return; + } + + for (x = 0; x < grid[0].length; x++) { + // Cell not found could be because of an invalid table structure + if (!grid[posY][x]) { + continue; + } + + cell = grid[posY][x].elm; + + if (cell != lastCell) { + if (!before) { + rowSpan = getSpanVal(cell, 'rowspan'); + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan + 1); + continue; + } + } else { + // Check if cell above can be expanded + if (posY > 0 && grid[posY - 1][x]) { + otherCell = grid[posY - 1][x].elm; + rowSpan = getSpanVal(otherCell, 'rowSpan'); + if (rowSpan > 1) { + setSpanVal(otherCell, 'rowSpan', rowSpan + 1); + continue; + } + } + } + + // Insert new cell into new row + newCell = cloneCell(cell); + setSpanVal(newCell, 'colSpan', cell.colSpan); + + newRow.appendChild(newCell); + + lastCell = cell; + } + } + + if (newRow.hasChildNodes()) { + if (!before) { + dom.insertAfter(newRow, rowElm); + } else { + rowElm.parentNode.insertBefore(newRow, rowElm); + } + } + } + + function insertCol(before) + { + var posX, lastCell; + + // Find first/last column + each( + grid, function (row) { + each( + row, function (cell, x) { + if (isCellSelected(cell)) { + posX = x; + + if (before) { + return false; + } + } + } + ); + + if (before) { + return !posX; + } + } + ); + + each( + grid, function (row, y) { + var cell, rowSpan, colSpan; + + if (!row[posX]) { + return; + } + + cell = row[posX].elm; + if (cell != lastCell) { + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan == 1) { + if (!before) { + dom.insertAfter(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } else { + cell.parentNode.insertBefore(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } + } else { + setSpanVal(cell, 'colSpan', cell.colSpan + 1); + } + + lastCell = cell; + } + } + ); + } + + function deleteCols() + { + var cols = []; + + // Get selected column indexes + each( + grid, function (row) { + each( + row, function (cell, x) { + if (isCellSelected(cell) && Tools.inArray(cols, x) === -1) { + each( + grid, function (row) { + var cell = row[x].elm, colSpan; + + colSpan = getSpanVal(cell, 'colSpan'); + + if (colSpan > 1) { + setSpanVal(cell, 'colSpan', colSpan - 1); + } else { + dom.remove(cell); + } + } + ); + + cols.push(x); + } + } + ); + } + ); + + cleanup(); + } + + function deleteRows() + { + var rows; + + function deleteRow(tr) + { + var pos, lastCell; + + // Move down row spanned cells + each( + tr.cells, function (cell) { + var rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan - 1); + pos = getPos(cell); + fillLeftDown(pos.x, pos.y, 1, 1); + } + } + ); + + // Delete cells + pos = getPos(tr.cells[0]); + each( + grid[pos.y], function (cell) { + var rowSpan; + + cell = cell.elm; + + if (cell != lastCell) { + rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan <= 1) { + dom.remove(cell); + } else { + setSpanVal(cell, 'rowSpan', rowSpan - 1); + } + + lastCell = cell; + } + } + ); + } + + // Get selected rows and move selection out of scope + rows = getSelectedRows(); + + // Delete all selected rows + each( + rows.reverse(), function (tr) { + deleteRow(tr); + } + ); + + cleanup(); + } + + function cutRows() + { + var rows = getSelectedRows(); + + dom.remove(rows); + cleanup(); + + return rows; + } + + function copyRows() + { + var rows = getSelectedRows(); + + each( + rows, function (row, i) { + rows[i] = cloneNode(row, true); + } + ); + + return rows; + } + + function pasteRows(rows, before) + { + var selectedRows = getSelectedRows(), + targetRow = selectedRows[before ? 0 : selectedRows.length - 1], + targetCellCount = targetRow.cells.length; + + // Nothing to paste + if (!rows) { + return; + } + + // Calc target cell count + each( + grid, function (row) { + var match; + + targetCellCount = 0; + each( + row, function (cell) { + if (cell.real) { + targetCellCount += cell.colspan; + } + + if (cell.elm.parentNode == targetRow) { + match = 1; + } + } + ); + + if (match) { + return false; + } + } + ); + + if (!before) { + rows.reverse(); + } + + each( + rows, function (row) { + var i, cellCount = row.cells.length, cell; + + // Remove col/rowspans + for (i = 0; i < cellCount; i++) { + cell = row.cells[i]; + setSpanVal(cell, 'colSpan', 1); + setSpanVal(cell, 'rowSpan', 1); + } + + // Needs more cells + for (i = cellCount; i < targetCellCount; i++) { + row.appendChild(cloneCell(row.cells[cellCount - 1])); + } + + // Needs less cells + for (i = targetCellCount; i < cellCount; i++) { + dom.remove(row.cells[i]); + } + + // Add before/after + if (before) { + targetRow.parentNode.insertBefore(row, targetRow); + } else { + dom.insertAfter(row, targetRow); + } + } + ); + + // Remove current selection + dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); + } + + function getPos(target) + { + var pos; + + each( + grid, function (row, y) { + each( + row, function (cell, x) { + if (cell.elm == target) { + pos = {x : x, y : y}; + return false; + } + } + ); + + return !pos; + } + ); + + return pos; + } + + function setStartCell(cell) + { + startPos = getPos(cell); + } + + function findEndPos() + { + var maxX, maxY; + + maxX = maxY = 0; + + each( + grid, function (row, y) { + each( + row, function (cell, x) { + var colSpan, rowSpan; + + if (isCellSelected(cell)) { + cell = grid[y][x]; + + if (x > maxX) { + maxX = x; + } + + if (y > maxY) { + maxY = y; + } + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) { + maxX = x + colSpan; + } + } + + if (rowSpan) { + if (y + rowSpan > maxY) { + maxY = y + rowSpan; + } + } + } + } + } + ); + } + ); + + return {x : maxX, y : maxY}; + } + + function setEndCell(cell) + { + var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan, x, y; + + endPos = getPos(cell); + + if (startPos && endPos) { + // Get start/end positions + startX = Math.min(startPos.x, endPos.x); + startY = Math.min(startPos.y, endPos.y); + endX = Math.max(startPos.x, endPos.x); + endY = Math.max(startPos.y, endPos.y); + + // Expand end positon to include spans + maxX = endX; + maxY = endY; + + // Expand startX + for (y = startY; y <= maxY; y++) { + cell = grid[y][startX]; + + if (!cell.real) { + if (startX - (cell.colspan - 1) < startX) { + startX -= cell.colspan - 1; + } + } + } + + // Expand startY + for (x = startX; x <= maxX; x++) { + cell = grid[startY][x]; + + if (!cell.real) { + if (startY - (cell.rowspan - 1) < startY) { + startY -= cell.rowspan - 1; + } + } + } + + // Find max X, Y + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + cell = grid[y][x]; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) { + maxX = x + colSpan; + } + } + + if (rowSpan) { + if (y + rowSpan > maxY) { + maxY = y + rowSpan; + } + } + } + } + } + + // Remove current selection + dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); + + // Add new selection + for (y = startY; y <= maxY; y++) { + for (x = startX; x <= maxX; x++) { + if (grid[y][x]) { + dom.addClass(grid[y][x].elm, 'mce-item-selected'); + } + } + } + } + } + + function moveRelIdx(cellElm, delta) + { + var pos, index, cell; + + pos = getPos(cellElm); + index = pos.y * gridWidth + pos.x; + + do { + index += delta; + cell = getCell(index % gridWidth, Math.floor(index / gridWidth)); + + if (!cell) { + break; + } + + if (cell.elm != cellElm) { + selection.select(cell.elm, true); + + if (dom.isEmpty(cell.elm)) { + selection.collapse(true); + } + + return true; + } + } while (cell.elm == cellElm); + + return false; + } + + table = table || dom.getParent(selection.getStart(), 'table'); + + buildGrid(); + + selectedCell = dom.getParent(selection.getStart(), 'th,td'); + if (selectedCell) { + startPos = getPos(selectedCell); + endPos = findEndPos(); + selectedCell = getCell(startPos.x, startPos.y); + } + + Tools.extend( + this, { + deleteTable: deleteTable, + split: split, + merge: merge, + insertRow: insertRow, + insertCol: insertCol, + deleteCols: deleteCols, + deleteRows: deleteRows, + cutRows: cutRows, + copyRows: copyRows, + pasteRows: pasteRows, + getPos: getPos, + setStartCell: setStartCell, + setEndCell: setEndCell, + moveRelIdx: moveRelIdx, + refresh: buildGrid + } + ); + }; + } + ); + + // Included from: js/tinymce/plugins/table/classes/Quirks.js + + /** + * Quirks.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + + /** + * This class includes fixes for various browser quirks. + * + * @class tinymce.tableplugin.Quirks + * @private + */ + define( + "tinymce/tableplugin/Quirks", [ + "tinymce/util/VK", + "tinymce/Env", + "tinymce/util/Tools" + ], function (VK, Env, Tools) { + var each = Tools.each; + + function getSpanVal(td, name) + { + return parseInt(td.getAttribute(name) || 1, 10); + } + + return function (editor) { + /** + * Fixed caret movement around tables on WebKit. + */ + function moveWebKitSelection() + { + function eventHandler(e) + { + var key = e.keyCode; + + function handle(upBool, sourceNode) + { + var siblingDirection = upBool ? 'previousSibling' : 'nextSibling'; + var currentRow = editor.dom.getParent(sourceNode, 'tr'); + var siblingRow = currentRow[siblingDirection]; + + if (siblingRow) { + moveCursorToRow(editor, sourceNode, siblingRow, upBool); + e.preventDefault(); + return true; + } else { + var tableNode = editor.dom.getParent(currentRow, 'table'); + var middleNode = currentRow.parentNode; + var parentNodeName = middleNode.nodeName.toLowerCase(); + if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) { + var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody'); + if (targetParent !== null) { + return moveToRowInTarget(upBool, targetParent, sourceNode); + } + } + return escapeTable(upBool, currentRow, siblingDirection, tableNode); + } + } + + function getTargetParent(upBool, topNode, secondNode, nodeName) + { + var tbodies = editor.dom.select('>' + nodeName, topNode); + var position = tbodies.indexOf(secondNode); + if (upBool && position === 0 || !upBool && position === tbodies.length - 1) { + return getFirstHeadOrFoot(upBool, topNode); + } else if (position === -1) { + var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1; + return tbodies[topOrBottom]; + } else { + return tbodies[position + (upBool ? -1 : 1)]; + } + } + + function getFirstHeadOrFoot(upBool, parent) + { + var tagName = upBool ? 'thead' : 'tfoot'; + var headOrFoot = editor.dom.select('>' + tagName, parent); + return headOrFoot.length !== 0 ? headOrFoot[0] : null; + } + + function moveToRowInTarget(upBool, targetParent, sourceNode) + { + var targetRow = getChildForDirection(targetParent, upBool); + + if (targetRow) { + moveCursorToRow(editor, sourceNode, targetRow, upBool); + } + + e.preventDefault(); + return true; + } + + function escapeTable(upBool, currentRow, siblingDirection, table) + { + var tableSibling = table[siblingDirection]; + + if (tableSibling) { + moveCursorToStartOfElement(tableSibling); + return true; + } else { + var parentCell = editor.dom.getParent(table, 'td,th'); + if (parentCell) { + return handle(upBool, parentCell, e); + } else { + var backUpSibling = getChildForDirection(currentRow, !upBool); + moveCursorToStartOfElement(backUpSibling); + e.preventDefault(); + return false; + } + } + } + + function getChildForDirection(parent, up) + { + var child = parent && parent[up ? 'lastChild' : 'firstChild']; + // BR is not a valid table child to return in this case we return the table cell + return child && child.nodeName === 'BR' ? editor.dom.getParent(child, 'td,th') : child; + } + + function moveCursorToStartOfElement(n) + { + editor.selection.setCursorLocation(n, 0); + } + + function isVerticalMovement() + { + return key == VK.UP || key == VK.DOWN; + } + + function isInTable(editor) + { + var node = editor.selection.getNode(); + var currentRow = editor.dom.getParent(node, 'tr'); + return currentRow !== null; + } + + function columnIndex(column) + { + var colIndex = 0; + var c = column; + while (c.previousSibling) { + c = c.previousSibling; + colIndex = colIndex + getSpanVal(c, "colspan"); + } + return colIndex; + } + + function findColumn(rowElement, columnIndex) + { + var c = 0, r = 0; + + each( + rowElement.children, function (cell, i) { + c = c + getSpanVal(cell, "colspan"); + r = i; + if (c > columnIndex) { + return false; + } + } + ); + return r; + } + + function moveCursorToRow(ed, node, row, upBool) + { + var srcColumnIndex = columnIndex(editor.dom.getParent(node, 'td,th')); + var tgtColumnIndex = findColumn(row, srcColumnIndex); + var tgtNode = row.childNodes[tgtColumnIndex]; + var rowCellTarget = getChildForDirection(tgtNode, upBool); + moveCursorToStartOfElement(rowCellTarget || tgtNode); + } + + function shouldFixCaret(preBrowserNode) + { + var newNode = editor.selection.getNode(); + var newParent = editor.dom.getParent(newNode, 'td,th'); + var oldParent = editor.dom.getParent(preBrowserNode, 'td,th'); + + return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent); + } + + function checkSameParentTable(nodeOne, NodeTwo) + { + return editor.dom.getParent(nodeOne, 'TABLE') === editor.dom.getParent(NodeTwo, 'TABLE'); + } + + if (isVerticalMovement() && isInTable(editor)) { + var preBrowserNode = editor.selection.getNode(); + setTimeout( + function () { + if (shouldFixCaret(preBrowserNode)) { + handle(!e.shiftKey && key === VK.UP, preBrowserNode, e); + } + }, 0 + ); + } + } + + editor.on( + 'KeyDown', function (e) { + eventHandler(e); + } + ); + } + + function fixBeforeTableCaretBug() + { + // Checks if the selection/caret is at the start of the specified block element + function isAtStart(rng, par) + { + var doc = par.ownerDocument, rng2 = doc.createRange(), elm; + + rng2.setStartBefore(par); + rng2.setEnd(rng.endContainer, rng.endOffset); + + elm = doc.createElement('body'); + elm.appendChild(rng2.cloneContents()); + + // Check for text characters of other elements that should be treated as content + return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length === 0; + } + + // Fixes an bug where it's impossible to place the caret before a table in Gecko + // this fix solves it by detecting when the caret is at the beginning of such a table + // and then manually moves the caret infront of the table + editor.on( + 'KeyDown', function (e) { + var rng, table, dom = editor.dom; + + // On gecko it's not possible to place the caret before a table + if (e.keyCode == 37 || e.keyCode == 38) { + rng = editor.selection.getRng(); + table = dom.getParent(rng.startContainer, 'table'); + + if (table && editor.getBody().firstChild == table) { + if (isAtStart(rng, table)) { + rng = dom.createRng(); + + rng.setStartBefore(table); + rng.setEndBefore(table); + + editor.selection.setRng(rng); + + e.preventDefault(); + } + } + } + } + ); + } + + // Fixes an issue on Gecko where it's impossible to place the caret behind a table + // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled + function fixTableCaretPos() + { + editor.on( + 'KeyDown SetContent VisualAid', function () { + var last; + + // Skip empty text nodes from the end + for (last = editor.getBody().lastChild; last; last = last.previousSibling) { + if (last.nodeType == 3) { + if (last.nodeValue.length > 0) { + break; + } + } else if (last.nodeType == 1 && (last.tagName == 'BR' || !last.getAttribute('data-mce-bogus'))) { + break; + } + } + + if (last && last.nodeName == 'TABLE') { + if (editor.settings.forced_root_block) { + editor.dom.add( + editor.getBody(), + editor.settings.forced_root_block, + editor.settings.forced_root_block_attrs, + Env.ie && Env.ie < 11 ? ' ' : '
    ' + ); + } else { + editor.dom.add(editor.getBody(), 'br', {'data-mce-bogus': '1'}); + } + } + } + ); + + editor.on( + 'PreProcess', function (o) { + var last = o.node.lastChild; + + if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 + && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) + && last.previousSibling && last.previousSibling.nodeName == "TABLE" + ) { + editor.dom.remove(last); + } + } + ); + } + + // this nasty hack is here to work around some WebKit selection bugs. + function fixTableCellSelection() + { + function tableCellSelected(ed, rng, n, currentCell) + { + // The decision of when a table cell is selected is somewhat involved. The fact that this code is + // required is actually a pointer to the root cause of this bug. A cell is selected when the start + // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases) + // or the parent of the table (in the case of the selection containing the last cell of a table). + var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'); + var tableParent, allOfCellSelected, tableCellSelection; + + if (table) { + tableParent = table.parentNode; + } + + allOfCellSelected = rng.startContainer.nodeType == TEXT_NODE && + rng.startOffset === 0 && + rng.endOffset === 0 && + currentCell && + (n.nodeName == "TR" || n == tableParent); + + tableCellSelection = (n.nodeName == "TD" || n.nodeName == "TH") && !currentCell; + + return allOfCellSelected || tableCellSelection; + } + + function fixSelection() + { + var rng = editor.selection.getRng(); + var n = editor.selection.getNode(); + var currentCell = editor.dom.getParent(rng.startContainer, 'TD,TH'); + + if (!tableCellSelected(editor, rng, n, currentCell)) { + return; + } + + if (!currentCell) { + currentCell = n; + } + + // Get the very last node inside the table cell + var end = currentCell.lastChild; + while (end.lastChild) { + end = end.lastChild; + } + + // Select the entire table cell. Nothing outside of the table cell should be selected. + if (end.nodeType == 3) { + rng.setEnd(end, end.data.length); + editor.selection.setRng(rng); + } + } + + editor.on( + 'KeyDown', function () { + fixSelection(); + } + ); + + editor.on( + 'MouseDown', function (e) { + if (e.button != 2) { + fixSelection(); + } + } + ); + } + + /** + * Delete table if all cells are selected. + */ + function deleteTable() + { + editor.on( + 'keydown', function (e) { + if ((e.keyCode == VK.DELETE || e.keyCode == VK.BACKSPACE) && !e.isDefaultPrevented()) { + var table = editor.dom.getParent(editor.selection.getStart(), 'table'); + + if (table) { + var cells = editor.dom.select('td,th', table), i = cells.length; + while (i--) { + if (!editor.dom.hasClass(cells[i], 'mce-item-selected')) { + return; + } + } + + e.preventDefault(); + editor.execCommand('mceTableDelete'); + } + } + } + ); + } + + deleteTable(); + + if (Env.webkit) { + moveWebKitSelection(); + fixTableCellSelection(); + } + + if (Env.gecko) { + fixBeforeTableCaretBug(); + fixTableCaretPos(); + } + + if (Env.ie > 10) { + fixBeforeTableCaretBug(); + fixTableCaretPos(); + } + }; + } + ); + + // Included from: js/tinymce/plugins/table/classes/CellSelection.js + + /** + * CellSelection.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + + /** + * This class handles table cell selection by faking it using a css class that gets applied + * to cells when dragging the mouse from one cell to another. + * + * @class tinymce.tableplugin.CellSelection + * @private + */ + define( + "tinymce/tableplugin/CellSelection", [ + "tinymce/tableplugin/TableGrid", + "tinymce/dom/TreeWalker", + "tinymce/util/Tools" + ], function (TableGrid, TreeWalker, Tools) { + return function (editor) { + var dom = editor.dom, tableGrid, startCell, startTable, hasCellSelection = true, resizing; + + function clear(force) + { + // Restore selection possibilities + editor.getBody().style.webkitUserSelect = ''; + + if (force || hasCellSelection) { + editor.dom.removeClass( + editor.dom.select('td.mce-item-selected,th.mce-item-selected'), + 'mce-item-selected' + ); + + hasCellSelection = false; + } + } + + function cellSelectionHandler(e) + { + var sel, table, target = e.target; + + if (resizing) { + return; + } + + if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { + table = dom.getParent(target, 'table'); + if (table == startTable) { + if (!tableGrid) { + tableGrid = new TableGrid(editor, table); + tableGrid.setStartCell(startCell); + + editor.getBody().style.webkitUserSelect = 'none'; + } + + tableGrid.setEndCell(target); + hasCellSelection = true; + } + + // Remove current selection + sel = editor.selection.getSel(); + + try { + if (sel.removeAllRanges) { + sel.removeAllRanges(); + } else { + sel.empty(); + } + } catch (ex) { + // IE9 might throw errors here + } + + e.preventDefault(); + } + } + + // Add cell selection logic + editor.on( + 'MouseDown', function (e) { + if (e.button != 2 && !resizing) { + clear(); + + startCell = dom.getParent(e.target, 'td,th'); + startTable = dom.getParent(startCell, 'table'); + } + } + ); + + editor.on('mouseover', cellSelectionHandler); + + editor.on( + 'remove', function () { + dom.unbind(editor.getDoc(), 'mouseover', cellSelectionHandler); + } + ); + + editor.on( + 'MouseUp', function () { + var rng, sel = editor.selection, selectedCells, walker, node, lastNode; + + function setPoint(node, start) + { + var walker = new TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && Tools.trim(node.nodeValue).length !== 0) { + if (start) { + rng.setStart(node, 0); + } else { + rng.setEnd(node, node.nodeValue.length); + } + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) { + rng.setStartBefore(node); + } else { + rng.setEndBefore(node); + } + + return; + } + } while ((node = (start ? walker.next() : walker.prev()))); + } + + // Move selection to startCell + if (startCell) { + if (tableGrid) { + editor.getBody().style.webkitUserSelect = ''; + } + + // Try to expand text selection as much as we can only Gecko supports cell selection + selectedCells = dom.select('td.mce-item-selected,th.mce-item-selected'); + if (selectedCells.length > 0) { + rng = dom.createRng(); + node = selectedCells[0]; + rng.setStartBefore(node); + rng.setEndAfter(node); + + setPoint(node, 1); + walker = new TreeWalker(node, dom.getParent(selectedCells[0], 'table')); + + do { + if (node.nodeName == 'TD' || node.nodeName == 'TH') { + if (!dom.hasClass(node, 'mce-item-selected')) { + break; + } + + lastNode = node; + } + } while ((node = walker.next())); + + setPoint(lastNode); + + sel.setRng(rng); + } + + editor.nodeChanged(); + startCell = tableGrid = startTable = null; + } + } + ); + + editor.on( + 'KeyUp Drop SetContent', function (e) { + clear(e.type == 'setcontent'); + startCell = tableGrid = startTable = null; + resizing = false; + } + ); + + editor.on( + 'ObjectResizeStart ObjectResized', function (e) { + resizing = e.type != 'objectresized'; + } + ); + + return { + clear: clear + }; + }; + } + ); + + // Included from: js/tinymce/plugins/table/classes/Dialogs.js + + /** + * Dialogs.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + + /** + * ... + * + * @class tinymce.tableplugin.Dialogs + * @private + */ + define( + "tinymce/tableplugin/Dialogs", [ + "tinymce/util/Tools", + "tinymce/Env" + ], function (Tools, Env) { + var each = Tools.each; + + return function (editor) { + var self = this; + + function createColorPickAction() + { + var colorPickerCallback = editor.settings.color_picker_callback; + + if (colorPickerCallback) { + return function () { + var self = this; + + colorPickerCallback.call( + editor, + function (value) { + self.value(value).fire('change'); + }, + self.value() + ); + }; + } + } + + function createStyleForm(dom) + { + return { + title: 'Advanced', + type: 'form', + defaults: { + onchange: function () { + updateStyle(dom, this.parents().reverse()[0], this.name() == "style"); + } + }, + items: [ + { + label: 'Style', + name: 'style', + type: 'textbox' + }, + + { + type: 'form', + padding: 0, + formItemDefaults: { + layout: 'grid', + alignH: ['start', 'right'] + }, + defaults: { + size: 7 + }, + items: [ + { + label: 'Border color', + type: 'colorbox', + name: 'borderColor', + onaction: createColorPickAction() + }, + + { + label: 'Background color', + type: 'colorbox', + name: 'backgroundColor', + onaction: createColorPickAction() + } + ] + } + ] + }; + } + + function removePxSuffix(size) + { + return size ? size.replace(/px$/, '') : ""; + } + + function addSizeSuffix(size) + { + if (/^[0-9]+$/.test(size)) { + size += "px"; + } + + return size; + } + + function unApplyAlign(elm) + { + each( + 'left center right'.split(' '), function (name) { + editor.formatter.remove('align' + name, {}, elm); + } + ); + } + + function unApplyVAlign(elm) + { + each( + 'top middle bottom'.split(' '), function (name) { + editor.formatter.remove('valign' + name, {}, elm); + } + ); + } + + function buildListItems(inputList, itemCallback, startItems) + { + function appendItems(values, output) + { + output = output || []; + + Tools.each( + values, function (item) { + var menuItem = {text: item.text || item.title}; + + if (item.menu) { + menuItem.menu = appendItems(item.menu); + } else { + menuItem.value = item.value; + + if (itemCallback) { + itemCallback(menuItem); + } + } + + output.push(menuItem); + } + ); + + return output; + } + + return appendItems(inputList, startItems || []); + } + + function updateStyle(dom, win, isStyleCtrl) + { + var data = win.toJSON(); + var css = dom.parseStyle(data.style); + + if (isStyleCtrl) { + win.find('#borderColor').value(css["border-color"] || '')[0].fire('change'); + win.find('#backgroundColor').value(css["background-color"] || '')[0].fire('change'); + } else { + css["border-color"] = data.borderColor; + css["background-color"] = data.backgroundColor; + } + + win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css)))); + } + + function appendStylesToData(dom, data, elm) + { + var css = dom.parseStyle(dom.getAttrib(elm, 'style')); + + if (css["border-color"]) { + data.borderColor = css["border-color"]; + } + + if (css["background-color"]) { + data.backgroundColor = css["background-color"]; + } + + data.style = dom.serializeStyle(css); + } + + self.tableProps = function () { + self.table(true); + }; + + self.table = function (isProps) { + var dom = editor.dom, tableElm, colsCtrl, rowsCtrl, classListCtrl, data = {}, generalTableForm; + + function onSubmitTableForm() + { + var captionElm; + + updateStyle(dom, this); + data = Tools.extend(data, this.toJSON()); + + Tools.each( + 'backgroundColor borderColor'.split(' '), function (name) { + delete data[name]; + } + ); + + if (data["class"] === false) { + delete data["class"]; + } + + editor.undoManager.transact( + function () { + if (!tableElm) { + tableElm = editor.plugins.table.insertTable(data.cols || 1, data.rows || 1); + } + + editor.dom.setAttribs( + tableElm, { + cellspacing: data.cellspacing, + cellpadding: data.cellpadding, + border: data.border, + style: data.style, + 'class': data['class'] + } + ); + + if (dom.getAttrib(tableElm, 'width')) { + dom.setAttrib(tableElm, 'width', removePxSuffix(data.width)); + } else { + dom.setStyle(tableElm, 'width', addSizeSuffix(data.width)); + } + + dom.setStyle(tableElm, 'height', addSizeSuffix(data.height)); + + // Toggle caption on/off + captionElm = dom.select('caption', tableElm)[0]; + + if (captionElm && !data.caption) { + dom.remove(captionElm); + } + + if (!captionElm && data.caption) { + captionElm = dom.create('caption'); + captionElm.innerHTML = !Env.ie ? '
    ' : '\u00a0'; + tableElm.insertBefore(captionElm, tableElm.firstChild); + } + + unApplyAlign(tableElm); + if (data.align) { + editor.formatter.apply('align' + data.align, {}, tableElm); + } + + editor.focus(); + editor.addVisual(); + } + ); + } + + if (isProps === true) { + tableElm = dom.getParent(editor.selection.getStart(), 'table'); + + if (tableElm) { + data = { + width: removePxSuffix(dom.getStyle(tableElm, 'width') || dom.getAttrib(tableElm, 'width')), + height: removePxSuffix(dom.getStyle(tableElm, 'height') || dom.getAttrib(tableElm, 'height')), + cellspacing: tableElm ? dom.getAttrib(tableElm, 'cellspacing') : '', + cellpadding: tableElm ? dom.getAttrib(tableElm, 'cellpadding') : '', + border: tableElm ? dom.getAttrib(tableElm, 'border') : '', + caption: !!dom.select('caption', tableElm)[0], + 'class': dom.getAttrib(tableElm, 'class') + }; + + each( + 'left center right'.split(' '), function (name) { + if (editor.formatter.matchNode(tableElm, 'align' + name)) { + data.align = name; + } + } + ); + } + } else { + colsCtrl = {label: 'Cols', name: 'cols'}; + rowsCtrl = {label: 'Rows', name: 'rows'}; + } + + if (editor.settings.table_class_list) { + if (data["class"]) { + data["class"] = data["class"].replace(/\s*mce\-item\-table\s*/g, ''); + } + + classListCtrl = { + name: 'class', + type: 'listbox', + label: 'Class', + values: buildListItems( + editor.settings.table_class_list, + function (item) { + if (item.value) { + item.textStyle = function () { + return editor.formatter.getCssText({block: 'table', classes: [item.value]}); + }; + } + } + ) + }; + } + + generalTableForm = { + type: 'form', + layout: 'flex', + direction: 'column', + labelGapCalc: 'children', + padding: 0, + items: [ + { + type: 'form', + labelGapCalc: false, + padding: 0, + layout: 'grid', + columns: 2, + defaults: { + type: 'textbox', + maxWidth: 50 + }, + items: [ + colsCtrl, + rowsCtrl, + {label: 'Width', name: 'width'}, + {label: 'Height', name: 'height'}, + {label: 'Cell spacing', name: 'cellspacing'}, + {label: 'Cell padding', name: 'cellpadding'}, + {label: 'Border', name: 'border'}, + {label: 'Caption', name: 'caption', type: 'checkbox'} + ] + }, + + { + label: 'Alignment', + name: 'align', + type: 'listbox', + text: 'None', + values: [ + {text: 'None', value: ''}, + {text: 'Left', value: 'left'}, + {text: 'Center', value: 'center'}, + {text: 'Right', value: 'right'} + ] + }, + + classListCtrl + ] + }; + + if (editor.settings.table_advtab !== false) { + appendStylesToData(dom, data, tableElm); + + editor.windowManager.open( + { + title: "Table properties", + data: data, + bodyType: 'tabpanel', + body: [ + { + title: 'General', + type: 'form', + items: generalTableForm + }, + createStyleForm(dom) + ], + + onsubmit: onSubmitTableForm + } + ); + } else { + editor.windowManager.open( + { + title: "Table properties", + data: data, + body: generalTableForm, + onsubmit: onSubmitTableForm + } + ); + } + }; + + self.merge = function (grid, cell) { + editor.windowManager.open( + { + title: "Merge cells", + body: [ + {label: 'Cols', name: 'cols', type: 'textbox', value: '1', size: 10}, + {label: 'Rows', name: 'rows', type: 'textbox', value: '1', size: 10} + ], + onsubmit: function () { + var data = this.toJSON(); + + editor.undoManager.transact( + function () { + grid.merge(cell, data.cols, data.rows); + } + ); + } + } + ); + }; + + self.cell = function () { + var dom = editor.dom, cellElm, data, classListCtrl, cells = []; + + function onSubmitCellForm() + { + updateStyle(dom, this); + data = Tools.extend(data, this.toJSON()); + + editor.undoManager.transact( + function () { + each( + cells, function (cellElm) { + editor.dom.setAttribs( + cellElm, { + scope: data.scope, + style: data.style, + 'class': data['class'] + } + ); + + editor.dom.setStyles( + cellElm, { + width: addSizeSuffix(data.width), + height: addSizeSuffix(data.height) + } + ); + + // Switch cell type + if (data.type && cellElm.nodeName.toLowerCase() != data.type) { + cellElm = dom.rename(cellElm, data.type); + } + + // Apply/remove alignment + unApplyAlign(cellElm); + if (data.align) { + editor.formatter.apply('align' + data.align, {}, cellElm); + } + + // Apply/remove vertical alignment + unApplyVAlign(cellElm); + if (data.valign) { + editor.formatter.apply('valign' + data.valign, {}, cellElm); + } + } + ); + + editor.focus(); + } + ); + } + + // Get selected cells or the current cell + cells = editor.dom.select('td.mce-item-selected,th.mce-item-selected'); + cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th'); + if (!cells.length && cellElm) { + cells.push(cellElm); + } + + cellElm = cellElm || cells[0]; + + if (!cellElm) { + // If this element is null, return now to avoid crashing. + return; + } + + data = { + width: removePxSuffix(dom.getStyle(cellElm, 'width') || dom.getAttrib(cellElm, 'width')), + height: removePxSuffix(dom.getStyle(cellElm, 'height') || dom.getAttrib(cellElm, 'height')), + scope: dom.getAttrib(cellElm, 'scope'), + 'class': dom.getAttrib(cellElm, 'class') + }; + + data.type = cellElm.nodeName.toLowerCase(); + + each( + 'left center right'.split(' '), function (name) { + if (editor.formatter.matchNode(cellElm, 'align' + name)) { + data.align = name; + } + } + ); + + each( + 'top middle bottom'.split(' '), function (name) { + if (editor.formatter.matchNode(cellElm, 'valign' + name)) { + data.valign = name; + } + } + ); + + if (editor.settings.table_cell_class_list) { + classListCtrl = { + name: 'class', + type: 'listbox', + label: 'Class', + values: buildListItems( + editor.settings.table_cell_class_list, + function (item) { + if (item.value) { + item.textStyle = function () { + return editor.formatter.getCssText({block: 'td', classes: [item.value]}); + }; + } + } + ) + }; + } + + var generalCellForm = { + type: 'form', + layout: 'flex', + direction: 'column', + labelGapCalc: 'children', + padding: 0, + items: [ + { + type: 'form', + layout: 'grid', + columns: 2, + labelGapCalc: false, + padding: 0, + defaults: { + type: 'textbox', + maxWidth: 50 + }, + items: [ + {label: 'Width', name: 'width'}, + {label: 'Height', name: 'height'}, + { + label: 'Cell type', + name: 'type', + type: 'listbox', + text: 'None', + minWidth: 90, + maxWidth: null, + values: [ + {text: 'Cell', value: 'td'}, + {text: 'Header cell', value: 'th'} + ] + }, + { + label: 'Scope', + name: 'scope', + type: 'listbox', + text: 'None', + minWidth: 90, + maxWidth: null, + values: [ + {text: 'None', value: ''}, + {text: 'Row', value: 'row'}, + {text: 'Column', value: 'col'}, + {text: 'Row group', value: 'rowgroup'}, + {text: 'Column group', value: 'colgroup'} + ] + }, + { + label: 'H Align', + name: 'align', + type: 'listbox', + text: 'None', + minWidth: 90, + maxWidth: null, + values: [ + {text: 'None', value: ''}, + {text: 'Left', value: 'left'}, + {text: 'Center', value: 'center'}, + {text: 'Right', value: 'right'} + ] + }, + { + label: 'V Align', + name: 'valign', + type: 'listbox', + text: 'None', + minWidth: 90, + maxWidth: null, + values: [ + {text: 'None', value: ''}, + {text: 'Top', value: 'top'}, + {text: 'Middle', value: 'middle'}, + {text: 'Bottom', value: 'bottom'} + ] + } + ] + }, + + classListCtrl + ] + }; + + if (editor.settings.table_cell_advtab !== false) { + appendStylesToData(dom, data, cellElm); + + editor.windowManager.open( + { + title: "Cell properties", + bodyType: 'tabpanel', + data: data, + body: [ + { + title: 'General', + type: 'form', + items: generalCellForm + }, + + createStyleForm(dom) + ], + + onsubmit: onSubmitCellForm + } + ); + } else { + editor.windowManager.open( + { + title: "Cell properties", + data: data, + body: generalCellForm, + onsubmit: onSubmitCellForm + } + ); + } + }; + + self.row = function () { + var dom = editor.dom, tableElm, cellElm, rowElm, classListCtrl, data, rows = [], generalRowForm; + + function onSubmitRowForm() + { + var tableElm, oldParentElm, parentElm; + + updateStyle(dom, this); + data = Tools.extend(data, this.toJSON()); + + editor.undoManager.transact( + function () { + var toType = data.type; + + each( + rows, function (rowElm) { + editor.dom.setAttribs( + rowElm, { + scope: data.scope, + style: data.style, + 'class': data['class'] + } + ); + + editor.dom.setStyles( + rowElm, { + height: addSizeSuffix(data.height) + } + ); + + if (toType != rowElm.parentNode.nodeName.toLowerCase()) { + tableElm = dom.getParent(rowElm, 'table'); + + oldParentElm = rowElm.parentNode; + parentElm = dom.select(toType, tableElm)[0]; + if (!parentElm) { + parentElm = dom.create(toType); + if (tableElm.firstChild) { + tableElm.insertBefore(parentElm, tableElm.firstChild); + } else { + tableElm.appendChild(parentElm); + } + } + + parentElm.appendChild(rowElm); + + if (!oldParentElm.hasChildNodes()) { + dom.remove(oldParentElm); + } + } + + // Apply/remove alignment + unApplyAlign(rowElm); + if (data.align) { + editor.formatter.apply('align' + data.align, {}, rowElm); + } + } + ); + + editor.focus(); + } + ); + } + + tableElm = editor.dom.getParent(editor.selection.getStart(), 'table'); + cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th'); + + each( + tableElm.rows, function (row) { + each( + row.cells, function (cell) { + if (dom.hasClass(cell, 'mce-item-selected') || cell == cellElm) { + rows.push(row); + return false; + } + } + ); + } + ); + + rowElm = rows[0]; + if (!rowElm) { + // If this element is null, return now to avoid crashing. + return; + } + + data = { + height: removePxSuffix(dom.getStyle(rowElm, 'height') || dom.getAttrib(rowElm, 'height')), + scope: dom.getAttrib(rowElm, 'scope'), + 'class': dom.getAttrib(rowElm, 'class') + }; + + data.type = rowElm.parentNode.nodeName.toLowerCase(); + + each( + 'left center right'.split(' '), function (name) { + if (editor.formatter.matchNode(rowElm, 'align' + name)) { + data.align = name; + } + } + ); + + if (editor.settings.table_row_class_list) { + classListCtrl = { + name: 'class', + type: 'listbox', + label: 'Class', + values: buildListItems( + editor.settings.table_row_class_list, + function (item) { + if (item.value) { + item.textStyle = function () { + return editor.formatter.getCssText({block: 'tr', classes: [item.value]}); + }; + } + } + ) + }; + } + + generalRowForm = { + type: 'form', + columns: 2, + padding: 0, + defaults: { + type: 'textbox' + }, + items: [ + { + type: 'listbox', + name: 'type', + label: 'Row type', + text: 'None', + maxWidth: null, + values: [ + {text: 'Header', value: 'thead'}, + {text: 'Body', value: 'tbody'}, + {text: 'Footer', value: 'tfoot'} + ] + }, + { + type: 'listbox', + name: 'align', + label: 'Alignment', + text: 'None', + maxWidth: null, + values: [ + {text: 'None', value: ''}, + {text: 'Left', value: 'left'}, + {text: 'Center', value: 'center'}, + {text: 'Right', value: 'right'} + ] + }, + {label: 'Height', name: 'height'}, + classListCtrl + ] + }; + + if (editor.settings.table_row_advtab !== false) { + appendStylesToData(dom, data, rowElm); + + editor.windowManager.open( + { + title: "Row properties", + data: data, + bodyType: 'tabpanel', + body: [ + { + title: 'General', + type: 'form', + items: generalRowForm + }, + createStyleForm(dom) + ], + + onsubmit: onSubmitRowForm + } + ); + } else { + editor.windowManager.open( + { + title: "Row properties", + data: data, + body: generalRowForm, + onsubmit: onSubmitRowForm + } + ); + } + }; + }; + } + ); + + // Included from: js/tinymce/plugins/table/classes/Plugin.js + + /** + * Plugin.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + + /** + * This class contains all core logic for the table plugin. + * + * @class tinymce.tableplugin.Plugin + * @private + */ + define( + "tinymce/tableplugin/Plugin", [ + "tinymce/tableplugin/TableGrid", + "tinymce/tableplugin/Quirks", + "tinymce/tableplugin/CellSelection", + "tinymce/tableplugin/Dialogs", + "tinymce/util/Tools", + "tinymce/dom/TreeWalker", + "tinymce/Env", + "tinymce/PluginManager" + ], function (TableGrid, Quirks, CellSelection, Dialogs, Tools, TreeWalker, Env, PluginManager) { + var each = Tools.each; + + function Plugin(editor) + { + var clipboardRows, self = this, dialogs = new Dialogs(editor); + + function cmd(command) + { + return function () { + editor.execCommand(command); + }; + } + + function insertTable(cols, rows) + { + var y, x, html, tableElm; + + html = ''; + + for (y = 0; y < rows; y++) { + html += ''; + + for (x = 0; x < cols; x++) { + html += ''; + } + + html += ''; + } + + html += '
    ' + (Env.ie ? " " : '
    ') + '
    '; + + editor.undoManager.transact( + function () { + editor.insertContent(html); + + tableElm = editor.dom.get('__mce'); + editor.dom.setAttrib(tableElm, 'id', null); + + editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {}); + editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {}); + } + ); + + return tableElm; + } + + function handleDisabledState(ctrl, selector) + { + function bindStateListener() + { + ctrl.disabled(!editor.dom.getParent(editor.selection.getStart(), selector)); + + editor.selection.selectorChanged( + selector, function (state) { + ctrl.disabled(!state); + } + ); + } + + if (editor.initialized) { + bindStateListener(); + } else { + editor.on('init', bindStateListener); + } + } + + function postRender() + { + /*jshint validthis:true*/ + handleDisabledState(this, 'table'); + } + + function postRenderCell() + { + /*jshint validthis:true*/ + handleDisabledState(this, 'td,th'); + } + + function generateTableGrid() + { + var html = ''; + + html = ''; + + for (var y = 0; y < 10; y++) { + html += ''; + + for (var x = 0; x < 10; x++) { + html += ''; + } + + html += ''; + } + + html += '
    '; + + html += ''; + + return html; + } + + function selectGrid(tx, ty, control) + { + var table = control.getEl().getElementsByTagName('table')[0]; + var x, y, focusCell, cell, active; + var rtl = control.isRtl() || control.parent().rel == 'tl-tr'; + + table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1); + + if (rtl) { + tx = 9 - tx; + } + + for (y = 0; y < 10; y++) { + for (x = 0; x < 10; x++) { + cell = table.rows[y].childNodes[x].firstChild; + active = (rtl ? x >= tx : x <= tx) && y <= ty; + + editor.dom.toggleClass(cell, 'mce-active', active); + + if (active) { + focusCell = cell; + } + } + } + + return focusCell.parentNode; + } + + if (editor.settings.table_grid === false) { + editor.addMenuItem( + 'inserttable', { + text: 'Insert table', + icon: 'table', + context: 'table', + onclick: dialogs.table + } + ); + } else { + editor.addMenuItem( + 'inserttable', { + text: 'Insert table', + icon: 'table', + context: 'table', + ariaHideMenu: true, + onclick: function (e) { + if (e.aria) { + this.parent().hideAll(); + e.stopImmediatePropagation(); + dialogs.table(); + } + }, + onshow: function () { + selectGrid(0, 0, this.menu.items()[0]); + }, + onhide: function () { + var elements = this.menu.items()[0].getEl().getElementsByTagName('a'); + editor.dom.removeClass(elements, 'mce-active'); + editor.dom.addClass(elements[0], 'mce-active'); + }, + menu: [ + { + type: 'container', + html: generateTableGrid(), + + onPostRender: function () { + this.lastX = this.lastY = 0; + }, + + onmousemove: function (e) { + var target = e.target, x, y; + + if (target.tagName.toUpperCase() == 'A') { + x = parseInt(target.getAttribute('data-mce-x'), 10); + y = parseInt(target.getAttribute('data-mce-y'), 10); + + if (this.isRtl() || this.parent().rel == 'tl-tr') { + x = 9 - x; + } + + if (x !== this.lastX || y !== this.lastY) { + selectGrid(x, y, e.control); + + this.lastX = x; + this.lastY = y; + } + } + }, + + onclick: function (e) { + var self = this; + + if (e.target.tagName.toUpperCase() == 'A') { + e.preventDefault(); + e.stopPropagation(); + self.parent().cancel(); + + editor.undoManager.transact( + function () { + insertTable(self.lastX + 1, self.lastY + 1); + } + ); + + editor.addVisual(); + } + } + } + ] + } + ); + } + + editor.addMenuItem( + 'tableprops', { + text: 'Table properties', + context: 'table', + onPostRender: postRender, + onclick: dialogs.tableProps + } + ); + + editor.addMenuItem( + 'deletetable', { + text: 'Delete table', + context: 'table', + onPostRender: postRender, + cmd: 'mceTableDelete' + } + ); + + editor.addMenuItem( + 'cell', { + separator: 'before', + text: 'Cell', + context: 'table', + menu: [ + {text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell}, + {text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderCell}, + {text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell} + ] + } + ); + + editor.addMenuItem( + 'row', { + text: 'Row', + context: 'table', + menu: [ + {text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell}, + {text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell}, + {text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell}, + {text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell}, + {text: '-'}, + {text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell}, + {text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell}, + {text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell}, + {text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell} + ] + } + ); + + editor.addMenuItem( + 'column', { + text: 'Column', + context: 'table', + menu: [ + {text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell}, + {text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell}, + {text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell} + ] + } + ); + + var menuItems = []; + each( + "inserttable tableprops deletetable | cell row column".split(' '), function (name) { + if (name == '|') { + menuItems.push({text: '-'}); + } else { + menuItems.push(editor.menuItems[name]); + } + } + ); + + editor.addButton( + "table", { + type: "menubutton", + title: "Table", + menu: menuItems + } + ); + + // Select whole table is a table border is clicked + if (!Env.isIE) { + editor.on( + 'click', function (e) { + e = e.target; + + if (e.nodeName === 'TABLE') { + editor.selection.select(e); + editor.nodeChanged(); + } + } + ); + } + + self.quirks = new Quirks(editor); + + editor.on( + 'Init', function () { + self.cellSelection = new CellSelection(editor); + } + ); + + // Register action commands + each( + { + mceTableSplitCells: function (grid) { + grid.split(); + }, + + mceTableMergeCells: function (grid) { + var cell; + + cell = editor.dom.getParent(editor.selection.getStart(), 'th,td'); + + if (!editor.dom.select('td.mce-item-selected,th.mce-item-selected').length) { + dialogs.merge(grid, cell); + } else { + grid.merge(); + } + }, + + mceTableInsertRowBefore: function (grid) { + grid.insertRow(true); + }, + + mceTableInsertRowAfter: function (grid) { + grid.insertRow(); + }, + + mceTableInsertColBefore: function (grid) { + grid.insertCol(true); + }, + + mceTableInsertColAfter: function (grid) { + grid.insertCol(); + }, + + mceTableDeleteCol: function (grid) { + grid.deleteCols(); + }, + + mceTableDeleteRow: function (grid) { + grid.deleteRows(); + }, + + mceTableCutRow: function (grid) { + clipboardRows = grid.cutRows(); + }, + + mceTableCopyRow: function (grid) { + clipboardRows = grid.copyRows(); + }, + + mceTablePasteRowBefore: function (grid) { + grid.pasteRows(clipboardRows, true); + }, + + mceTablePasteRowAfter: function (grid) { + grid.pasteRows(clipboardRows); + }, + + mceTableDelete: function (grid) { + grid.deleteTable(); + } + }, function (func, name) { + editor.addCommand( + name, function () { + var grid = new TableGrid(editor); + + if (grid) { + func(grid); + editor.execCommand('mceRepaint'); + self.cellSelection.clear(); + } + } + ); + } + ); + + // Register dialog commands + each( + { + mceInsertTable: dialogs.table, + mceTableProps: function () { + dialogs.table(true); + }, + mceTableRowProps: dialogs.row, + mceTableCellProps: dialogs.cell + }, function (func, name) { + editor.addCommand( + name, function (ui, val) { + func(val); + } + ); + } + ); + + // Enable tab key cell navigation + if (editor.settings.table_tab_navigation !== false) { + editor.on( + 'keydown', function (e) { + var cellElm, grid, delta; + + if (e.keyCode == 9) { + cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td'); + + if (cellElm) { + e.preventDefault(); + + grid = new TableGrid(editor); + delta = e.shiftKey ? -1 : 1; + + editor.undoManager.transact( + function () { + if (!grid.moveRelIdx(cellElm, delta) && delta > 0) { + grid.insertRow(); + grid.refresh(); + grid.moveRelIdx(cellElm, delta); + } + } + ); + } + } + } + ); + } + + self.insertTable = insertTable; + } + + PluginManager.add('table', Plugin); + } + ); +})(this); \ No newline at end of file diff --git a/tinymce/table/plugin.min.js b/admin/tinymce/table/plugin.min.js similarity index 100% rename from tinymce/table/plugin.min.js rename to admin/tinymce/table/plugin.min.js diff --git a/bmlt-meeting-list.php b/bmlt-meeting-list.php index b5f6ec5..bcb53a1 100644 --- a/bmlt-meeting-list.php +++ b/bmlt-meeting-list.php @@ -1,2981 +1,79 @@ 'location_city_subsection', - 'time' => 'start_time', - 'state' => 'location_province', - 'street' => 'location_street', - 'neighborhood' => 'location_neighborhood', - 'city' => 'location_municipality', - 'zip' => 'location_postal_code_1', - 'location' => 'location_text', - 'info' => 'location_info', - 'county' => 'location_sub_province', - 'group' => 'meeting_name', - 'email' => 'email_contact', - 'mins' => 'duration_m', - 'hrs' => 'duration_h', - "area" => 'area_name', - ); - var $section_shortcodes; - var $service_meeting_result = null; - const SETTINGS = 'bmlt_meeting_list_settings'; - const OPTIONS_NAME = 'bmlt_meeting_list_options'; - var $optionsName = Bread::OPTIONS_NAME; - var $options = array(); - var $outside_meeting_result = array(); - var $allSettings = array(); - var $maxSetting = 1; - var $loaded_setting = 1; - var $authors_safe = array(); - var $connection_error = ''; - var $protocol = ''; - var $unique_areas = array(); - - function loadAllSettings() - { - $this->allSettings = get_option(Bread::SETTINGS); - if ($this->allSettings === false) { - $this->allSettings = array(); - $this->allSettings[1] = "Default Setting"; - $this->maxSetting = 1; - } else { - foreach ($this->allSettings as $key => $value) { - if ($key > $this->maxSetting) { - $this->maxSetting = $key; - } - } - } - } - function startsWith($haystack, $needle) - { - $length = strlen($needle); - return (substr($haystack, 0, $length) === $needle); - } - function getCurrentMeetingListHolder() - { - $ret = array(); - if (isset($_REQUEST['current-meeting-list'])) { - $ret['current-meeting-list'] = $_REQUEST['current-meeting-list']; - } else if (isset($_COOKIE['current-meeting-list'])) { - $ret['current-meeting-list'] = $_COOKIE['current-meeting-list']; - } - return $ret; - } - function __construct() - { - // Register hooks - register_activation_hook(__FILE__, array(__CLASS__, 'activation')); - - $this->protocol = (strpos(strtolower(home_url()), "https") !== false ? "https" : "http") . "://"; - - $this->loadAllSettings(); - $holder = $this->getCurrentMeetingListHolder(); - - $current_settings = isset($holder['current-meeting-list']) ? intval($holder['current-meeting-list']) : 1; - $this->load_translations(); - if (isset($holder['current-meeting-list']) && !is_admin()) { - $this->getMLOptions($current_settings); - add_action('plugins_loaded', array(&$this, 'bmlt_meeting_list' )); - } else if (is_admin()) { - $this->requested_setting = $current_settings; - add_action("admin_init", array(&$this, 'my_sideload_image')); - add_action("admin_menu", array(&$this, "admin_menu_link")); - add_filter('tiny_mce_before_init', array(&$this, 'tiny_tweaks')); - add_filter('mce_external_plugins', array(&$this, 'my_custom_plugins')); - add_filter('mce_buttons', array(&$this, 'my_register_mce_button')); - //add_action("admin_notices", array(&$this, "is_root_server_missing")); - add_action("admin_init", array(&$this, "pwsix_process_settings_export")); - add_action("admin_init", array(&$this, "pwsix_process_settings_import")); - add_action("admin_init", array(&$this, "pwsix_process_default_settings")); - add_action("admin_init", array(&$this, "pwsix_process_settings_admin")); - add_action("admin_init", array(&$this, "pwsix_process_rename_settings")); - add_action("admin_init", array(&$this, "my_theme_add_editor_styles")); - add_action("admin_enqueue_scripts", array(&$this, "enqueue_backend_files")); - add_action("wp_default_editor", array(&$this, "ml_default_editor")); - add_filter('tiny_mce_version', array(__CLASS__, 'force_mce_refresh')); - } - - register_deactivation_hook(__FILE__, array(__CLASS__, 'deactivation')); - } - - public static function activation() - { - Bread::add_cap(); - } - - private static function add_cap() - { - $role = $GLOBALS['wp_roles']->role_objects['administrator']; - if (isset($role) && !$role->has_cap('manage_bread')) { - $role->add_cap('manage_bread'); - } - } - - public static function deactivation() - { - Bread::remove_cap(); - } - - private static function remove_cap() - { - $role = $GLOBALS['wp_roles']->role_objects['administrator']; - if (isset($role) && $role->has_cap('manage_bread')) { - $role->remove_cap('manage_bread'); - } - } - - function ml_default_editor() - { - global $my_admin_page; - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - return "tinymce"; - } - } - - function force_mce_refresh($ver) - { - global $my_admin_page; - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - return $ver + 99; - } - } - - function my_sideload_image() - { - global $my_admin_page; - $screen = get_current_screen(); - if (isset($screen) && $screen->id == $my_admin_page) { - if (get_option($this->optionsName) === false) { - $url = plugin_dir_url(__FILE__) . "includes/nalogo.jpg"; - media_sideload_image($url, 0); - } - } - } - - // Register new button in the editor - function my_register_mce_button($buttons) - { - global $my_admin_page; - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - array_push($buttons, 'front_page_button', 'custom_template_button_1', 'custom_template_button_2'); - } - return $buttons; - } - - function my_custom_plugins() - { - global $my_admin_page; - $plugins_array = array(); - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - $plugins = array('table', 'code', 'contextmenu' ); //Add any more plugins you want to load here - //Build the response - the key is the plugin name, value is the URL to the plugin JS - foreach ($plugins as $plugin) { - $plugins_array[ $plugin ] = plugins_url('tinymce/', __FILE__) . $plugin . '/plugin.min.js'; - } - $shortcode_menu = array(); - $shortcode_menu['front_page_button'] = plugins_url('tinymce/', __FILE__) . 'front_page_button/plugin.min.js'; - //let's leave the enhancement mechanism open for now. - //apply_filters is one option, perhaps we will think of something better. - //$shortcode_menu = apply_filters("Bread_Adjust_Menu", $shortcode_menu); - $plugins_array = array_merge($plugins_array, $shortcode_menu); - } - return $plugins_array; - } - - // Enable font size & font family selects in the editor - function tiny_tweaks($initArray) - { - global $my_admin_page; - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - $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 (Default)=arial;'; - $initArray['font_formats'].='Times (Sans-Serif)=times;'; - $initArray['font_formats'].='Courier (Monospace)=courier;'; - $initArray['content_style'] = 'body { font-family: Arial; }'; - } - return $initArray; - } - function get_temp_dir() - { - if (!$this->tmp_dir) { - $dir = get_temp_dir(); - $dir = rtrim($dir, DIRECTORY_SEPARATOR); - if (!is_dir($dir) || !is_writable($dir)) { - return false; - } - $this->brute_force_cleanup($dir); - $attempts = 0; - $path = ''; - do { - $path = sprintf('%s%s%s%s', $dir, DIRECTORY_SEPARATOR, 'bread', mt_rand(100000, mt_getrandmax())); - } while (!mkdir($path) && $attempts++ < 100); - $this->tmp_dir = $path; - } - return $this->tmp_dir; - } - function is_root_server_missing() - { - global $my_admin_page; - $screen = get_current_screen(); - if ($screen->id == $my_admin_page) { - $root_server = $this->options['root_server']; - if ($root_server == '') { - echo '

    Missing BMLT Server in settings for bread.

    '; - $url = admin_url('options-general.php?page=bmlt-meeting-list.php'); - echo "

    Settings

    "; - echo '
    '; - } else if (!$this->get_temp_dir()) { - echo '

    ' . $this->get_temp_dir() . ' temporary directory is not writable.

    '; - $url = admin_url('options-general.php?page=bmlt-meeting-list.php'); - echo "

    Settings

    "; - echo '
    '; - } - } - } - function Bread() - { - $this->__construct(); - } - - /** - * @desc Adds JS/CSS to the header - */ - function enqueue_backend_files($hook) - { - if ($hook == 'toplevel_page_bmlt-meeting-list') { - wp_enqueue_script('common'); - wp_enqueue_script('jquery-ui-tabs'); - wp_enqueue_script('jquery-ui-accordion'); - wp_enqueue_script('jquery-ui-dialog'); - wp_enqueue_style("jquery-ui", plugin_dir_url(__FILE__) . "css/jquery-ui.min.css", false, "1.2", 'all'); - wp_enqueue_style("spectrum", plugin_dir_url(__FILE__) . "css/spectrum.css", false, "1.2", 'all'); - wp_enqueue_style("admin", plugin_dir_url(__FILE__) . "css/admin.css", false, "1.2", 'all'); - wp_enqueue_style("chosen", plugin_dir_url(__FILE__) . "css/chosen.min.css", false, "1.2", 'all'); - wp_enqueue_script("bmlt_meeting_list", plugin_dir_url(__FILE__) . "js/bmlt_meeting_list.js", array('jquery'), "1.2", true); - wp_enqueue_script("tooltipster", plugin_dir_url(__FILE__) . "js/jquery.tooltipster.min.js", array('jquery'), "1.2", true); - wp_enqueue_script("spectrum", plugin_dir_url(__FILE__) . "js/spectrum.js", array('jquery'), "1.2", true); - wp_enqueue_script("chosen", plugin_dir_url(__FILE__) . "js/chosen.jquery.min.js", array('jquery'), "1.2", true); - } - } - - function my_theme_add_editor_styles() - { - global $my_admin_page; - $screen = get_current_screen(); - if (isset($screen) && $screen->id == $my_admin_page) { - add_editor_style(plugin_dir_url(__FILE__) . "css/editor-style.css"); - } - } - function load_translations() - { - $files = scandir(dirname(__FILE__)."/lang"); - foreach ($files as $file) { - if (strpos($file, "translate_")!==0) { - continue; - } - include(dirname(__FILE__)."/lang/".$file); - $key = substr($file, 10, -4); - $this->translate[$key] = $translate; - } - } - function getday($day, $abbreviate = false, $language = 'en') - { - $data = ''; - $key = "WEEKDAYS"; - if ($abbreviate) { - $key = "WKDYS"; - } - return mb_convert_encoding($this->translate[$language][$key][$day], 'UTF-8', mb_list_encodings()); - } - - function authenticate_root_server() - { - $query_string = http_build_query(array( - 'admin_action' => 'login', - 'c_comdef_admin_login' => $this->options['bmlt_login_id'], - 'c_comdef_admin_password' => $this->options['bmlt_login_password'], '&')); - return $this->get($this->options['root_server']."/local_server/server_admin/xml.php?" . $query_string); - } - function requires_authentication() - { - return ($this->options['include_meeting_email'] == 1 || $this->options['include_asm'] == 1); - } - function get_root_server_request($url) - { - $cookies = null; - - if ($this->requires_authentication()) { - $auth_response = $this->authenticate_root_server(); - $cookies = wp_remote_retrieve_cookies($auth_response); - } - - return $this->get($url, $cookies); - } - - function get_configured_root_server_request($url) - { - return $this->get_root_server_request($this->options['root_server']."/".$url); - } - - function get($url, $cookies = array()) - { - $args = array( - 'timeout' => '120', - 'cookies' => $cookies, - ); - if (isset($this->options['user_agent']) && - $this->options['user_agent'] != 'None') { - $args['headers'] = array( - 'User-Agent' => $this->options['user_agent'] - ); - } - if ($this->options['sslverify'] == '1') { - $args['sslverify'] = false; - } - return wp_remote_get($url, $args); - } - function get_all_meetings() - { - $results = $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"); - $result = json_decode(wp_remote_retrieve_body($results), true); - - $this->unique_areas = $this->get_areas(); - $all_meetings = array(); - foreach ($result as $value) { - foreach ($this->unique_areas as $unique_area) { - $area_data = explode(',', $unique_area); - $area_id = $this->arraySafeGet($area_data, 1); - if ($area_id === $value['service_body_bigint']) { - $area_name = $this->arraySafeGet($area_data); - } - } - - $value['start_time'] = date("g:iA", strtotime($value['start_time'])); - $all_meetings[] = $value['meeting_name'].'||| ['.$this->getday($value['weekday_tinyint'], true, $this->lang).'] ['.$value['start_time'].']||| ['.$area_name.']||| ['.$value['id_bigint'].']'; - } - - return $all_meetings; - } - function get_fieldkeys() - { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFieldKeys"); - return json_decode(wp_remote_retrieve_body($results), true); - } - var $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"); - 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; - } - function get_areas() - { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetServiceBodies"); - $result = json_decode(wp_remote_retrieve_body($results), true); - $unique_areas = array(); - - foreach ($result as $value) { - $parent_name = 'Parent ID'; - foreach ($result as $parent) { - if ($value['parent_id'] == $parent['id']) { - $parent_name = $parent['name']; - } - } - if ($value['parent_id'] == '') { - $value['parent_id'] = '0'; - } - $unique_areas[] = $value['name'] . ',' . $value['id'] . ',' . $value['parent_id'] . ',' . $parent_name; - } - - return $unique_areas; - } - - function get_bmlt_server_lang() - { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetServerInfo"); - $result = json_decode(wp_remote_retrieve_body($results), true); - if ($result==null) { - return 'en'; - } - $result = $result["0"]["nativeLang"]; - - return $result; - } - - function testRootServer($override_root_server = null) - { - if ($override_root_server == null) { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetServerInfo"); - } else { - $results = $this->get_root_server_request($override_root_server."/client_interface/json/?switcher=GetServerInfo"); - } - if ($results instanceof WP_Error) { - $this->connection_error = $results->get_error_message(); - return false; - } - $httpcode = wp_remote_retrieve_response_code($results); - if ($httpcode != 200 && $httpcode != 302 && $httpcode != 304) { - $this->connection_error = "HTTP Return Code: ".$httpcode; - return false; - } - - return json_decode(wp_remote_retrieve_body($results), true); - } - // This is used from the AdminUI, not to generate the - // meeting list. - function getFormatsForSelect($all = false) - { - if ($all) { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats"); - $results = json_decode(wp_remote_retrieve_body($results), true); - $this->sortBySubkey($results, 'key_string'); - return $results; - } - if (!isset($this->options['recurse_service_bodies'])) { - $this->options['recurse_service_bodies'] = 1; - } - $area_data = explode(',', $this->options['service_body_1']); - $service_body_id = $this->arraySafeGet($area_data, 1); - if ($this->options['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 = json_decode(wp_remote_retrieve_body($results), true); - $results = empty($service_body_id) ? $results : $results['formats']; - $this->sortBySubkey($results, 'key_string'); - return $results; - } - - function sortBySubkey(&$array, $subkey, $sortType = SORT_ASC) - { - if (empty($array)) { - return; - } - foreach ($array as $subarray) { - $keys[] = $subarray[$subkey]; - } - array_multisort($keys, $sortType, $array); - } - function upgrade_settings() - { - if (!isset($this->options['cont_header_shown']) - && isset($this->options['page_height_fix'])) { - $fix = floatval($this->options['page_height_fix']); - // say, the height of 2 lines - $x = floatval($this->options['content_font_size']) * - floatval($this->options['content_line_height']) * 2.0 * 0.35; // pt to mm - if ($fix < $x) { - $this->options['cont_header_shown'] = true; - } else { - $this->options['cont_header_shown'] = false; - } - unset($this->options['page_height_fix']); - } - if ($this->options['weekday_language'] == 'both') { - $this->options['weekday_language'] = "en_es"; - } - if ($this->options['weekday_language'] == 'both_po') { - $this->options['weekday_language'] = "en_po"; - } - if ($this->options['sub_header_shown'] == '0') { - $this->options['sub_header_shown'] = 'none'; - } - if ($this->options['sub_header_shown'] == '1') { - $this->options['sub_header_shown'] = 'display'; - } - } - function bmlt_meeting_list($atts = null, $content = null) - { - $import_streams = []; - ini_set('max_execution_time', 600); // tomato server can take a long time to generate a schedule, override the server setting - $this->lang = $this->get_bmlt_server_lang(); - // addServiceBody has the side effect that - // the service body option is overridden, so that it contains - // only the name of the service body. - $services = $this->addServiceBody('service_body_1'); - $services .= $this->addServiceBody('service_body_2'); - $services .= $this->addServiceBody('service_body_3'); - $services .= $this->addServiceBody('service_body_4'); - $services .= $this->addServiceBody('service_body_5'); - $area = $this->options['service_body_1']; - - if (isset($_GET['custom_query'])) { - $services = $_GET['custom_query']; - } elseif ($this->options['custom_query'] !== '') { - $services = $this->options['custom_query']; - } - $this->services = $services; - if ($this->options['root_server'] == '') { - 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'] == '' )) { - 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; - } - if (headers_sent()) { - echo '

    Headers already sent before Meeting List generation

    '; - exit; - } - - $num_columns = 0; - if (!isset($this->options['suppress_heading'])) { - $this->options['suppress_heading'] = 0; - } - if (!isset($this->options['header_font_size'])) { - $this->options['header_font_size'] = $this->options['content_font_size']; - } - if (!isset($this->options['header_text_color'])) { - $this->options['header_text_color'] = '#ffffff'; - } - if (!isset($this->options['header_background_color'])) { - $this->options['header_background_color'] = '#000000'; - } - if (!isset($this->options['pageheader_textcolor'])) { - $this->options['pageheader_textcolor'] = '#000000'; - } - if (!isset($this->options['pageheader_fontsize']) || floatval($this->options['pageheader_fontsize'])<4) { - $this->options['pageheader_fontsize'] = '9'; - } - if (!isset($this->options['pageheader_backgroundcolor'])) { - $this->options['pageheader_backgroundcolor'] = '#ffffff'; - } - if (!isset($this->options['margin_left'])) { - $this->options['margin_left'] = 3; - } - if (!isset($this->options['margin_bottom'])) { - $this->options['margin_bottom'] = 3; - } - if (!isset($this->options['margin_top'])) { - $this->options['margin_top'] = 3; - } - if (!isset($this->options['margin_header'])) { - $this->options['margin_header'] = 3; - } - if (!isset($this->options['margin_footer'])) { - $this->options['margin_footer'] = 5; - } - if (!isset($this->options['page_size'])) { - $this->options['page_size'] = 'legal'; - } - if (!isset($this->options['page_orientation'])) { - $this->options['page_orientation'] = 'L'; - } - if (!isset($this->options['booklet_pages'])) { - $this->options['booklet_pages'] = false; - } - if (!isset($this->options['page_fold'])) { - $this->options['page_fold'] = 'quad'; - } - if (!isset($this->options['meeting_sort'])) { - $this->options['meeting_sort'] = 'day'; - } - if (!isset($this->options['borough_suffix'])) { - $this->options['borough_suffix'] = 'Borough'; - } - if (!isset($this->options['county_suffix'])) { - $this->options['county_suffix'] = 'County'; - } - if (!isset($this->options['neighborhood_suffix'])) { - $this->options['neighborhood_suffix'] = 'Neighborhood'; - } - if (!isset($this->options['city_suffix'])) { - $this->options['city_suffix'] = 'City'; - } - if (!isset($this->options['column_line'])) { - $this->options['column_line'] = 0; - } - if (!isset($this->options['col_color'])) { - $this->options['col_color'] = '#bfbfbf'; - } - if (!isset($this->options['custom_section_content'])) { - $this->options['custom_section_content'] = ''; - } - if (!isset($this->options['custom_section_line_height'])) { - $this->options['custom_section_line_height'] = '1'; - } - if (!isset($this->options['custom_section_font_size'])) { - $this->options['custom_section_font_size'] = '9'; - } - if (!isset($this->options['pagenumbering_font_size'])) { - $this->options['pagenumbering_font_size'] = '9'; - } - if (!isset($this->options['include_meeting_email'])) { - $this->options['include_meeting_email'] = 0; - } - if (!isset($this->options['include_protection'])) { - $this->options['include_protection'] = 0; - } - if (!isset($this->options['base_font'])) { - $this->options['base_font'] = 'dejavusanscondensed'; - } - if (!isset($this->options['colorspace'])) { - $this->options['colorspace'] = 0; - } - if (!isset($this->options['weekday_language'])) { - $this->options['weekday_language'] = 'en'; - } - if (!isset($this->options['asm_language'])) { - $this->options['asm_language'] = ''; - } - if (!isset($this->options['weekday_start'])) { - $this->options['weekday_start'] = '1'; - } - if (!isset($this->options['include_asm'])) { - $this->options['include_asm'] = '0'; - } - if (!isset($this->options['asm_format_key'])) { - $this->options['asm_format_key'] = 'ASM'; - } - if (!isset($this->options['asm_sort_order'])) { - $this->options['asm_sort_order'] = 'name'; - } - if (!isset($this->options['header_uppercase'])) { - $this->options['header_uppercase'] = '0'; - } - if (!isset($this->options['header_bold'])) { - $this->options['header_bold'] = '1'; - } - if (!isset($this->options['sub_header_shown'])) { - $this->options['sub_header_shown'] = '0'; - } - if (!isset($this->options['bmlt_login_id'])) { - $this->options['bmlt_login_id'] = ''; - } - if (!isset($this->options['bmlt_login_password'])) { - $this->options['bmlt_login_password'] = ''; - } - if (!isset($this->options['protection_password'])) { - $this->options['protection_password'] = ''; - } - if (!isset($this->options['cache_time'])) { - $this->options['cache_time'] = 0; - } - if (!isset($this->options['extra_meetings'])) { - $this->options['extra_meetings'] = []; - } - if (!isset($this->options['custom_query'])) { - $this->options['custom_query'] = ''; - } - if (!isset($this->options['asm_custom_query'])) { - $this->options['asm_custom_query'] = ''; - } - if (!isset($this->options['user_agent'])) { - $this->options['user_agent'] = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) +bread'; - } - if (!isset($this->options['sslverify'])) { - $this->options['sslverify'] = '0'; - } - if (!isset($this->options['used_format_1'])) { - $this->options['used_format_1'] = ''; - } - if (!isset($this->options['wheelchair_size'])) { - $this->options['wheelchair_size'] = '20px'; - } - if (intval($this->options['cache_time']) > 0 && ! isset($_GET['nocache']) && - ! isset($_GET['custom_query'])) { - if (false !== ( $content = get_transient($this->get_TransientKey()) )) { - $content = pack("H*", $content); - $name = $this->get_FilePath(); - header('Content-Type: application/pdf'); - header('Content-Length: '.strlen($content)); - header('Content-disposition: inline; filename="'.$name.'"'); - header('Cache-Control: public, must-revalidate, max-age=0'); - header('Pragma: public'); - header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); - header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); - echo $content; - exit; - } - } - if (isset($_GET['time_zone'])) { - $this->target_timezone = timezone_open($_GET['time_zone']); - } - // upgrade - if (!isset($this->options['bread_version'])) { - if (!($this->options['meeting_sort'] === 'weekday_area' - || $this->options['meeting_sort'] === 'weekday_city' - || $this->options['meeting_sort'] === 'weekday_county' - || $this->options['meeting_sort'] === 'day')) { - $this->options['weekday_language'] = $this->lang; - } - if ($this->options['page_fold']=='half') { - if ($this->options['page_size']=='A5') { - $this->options['page_size'] = 'A4'; - } - $this->options['page_orientation'] = 'L'; - } - if ($this->options['page_fold']=='tri') { - $this->options['page_orientation'] = 'L'; - } - if (substr($this->options['meeting_sort'], 0, 8) == 'weekday_') { - $this->options['sub_header_shown'] = 'display'; - } - if (isset($this->options['pageheader_text'])) { - $this->options['pageheader_content'] = $this->options['pageheader_text']; - unset($this->options['pageheader_text']); - } - if (substr($this->options['root_server'], -1) == '/') { - $this->options['root_server'] = substr($this->options['root_server'], 0, strlen($this->options['root_server'])-1); - } - if (substr($this->options['root_server'], 0, 4) !== 'http') { - $this->options['root_server'] = 'http://'.$this->options['root_server']; - } - } - $this->upgrade_settings(); - // TODO: The page number is always 5 from botton...this should be adjustable - if ($this->options['page_fold'] == 'half') { - if ($this->options['page_size'] == 'letter') { - $page_type_settings = ['format' => array(139.7,215.9), 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'legal') { - $page_type_settings = ['format' => array(177.8,215.9), 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'ledger') { - $page_type_settings = ['format' => 'letter-P', 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'A4') { - $page_type_settings = ['format' => 'A5-P', 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'A5') { - $page_type_settings = ['format' => 'A6-P', 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == '5inch') { - $page_type_settings = ['format' => array(197.2,279.4), 'margin_footer' => $this->options['margin_footer']]; - } - } elseif ($this->options['page_fold'] == 'flyer') { - if ($this->options['page_size'] == 'letter') { - $page_type_settings = ['format' => array(93.13,215.9), 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'legal') { - $page_type_settings = ['format' => array(118.53,215.9), 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'ledger') { - $page_type_settings = ['format' => array(143.93,279.4), 'margin_footer' => $this->options['margin_footer']]; - } elseif ($this->options['page_size'] == 'A4') { - $page_type_settings = ['format' => array(99.0,210.0), 'margin_footer' => $this->options['margin_footer']]; - } - } elseif ($this->options['page_fold'] == 'full') { - $ps = $this->options['page_size']; - if ($ps=='ledger') { - $ps = 'tabloid'; - } - $page_type_settings = ['format' => $ps."-".$this->options['page_orientation'], 'margin_footer' => $this->options['margin_footer']]; - } else { - $ps = $this->options['page_size']; - if ($ps=='ledger') { - $ps = 'tabloid'; - } - $page_type_settings = ['format' => $ps."-".$this->options['page_orientation'], 'margin_footer' => 0]; - } - $default_font = $this->options['base_font'] == "freesans" ? "dejavusanscondensed" : $this->options['base_font']; - $mode = 's'; - if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { - $mpdf_init_options = [ - 'fontDir' => array( - __DIR__ . '/mpdf/vendor/mpdf/mpdf/ttfonts', - __DIR__ . '/fonts', - ), - 'tempDir' => $this->get_temp_dir(), - 'mode' => $mode, - 'default_font_size' => 7, - 'fontdata' => [ - "arial" => [ - 'R' => "Arial.ttf", - 'B' => "ArialBold.ttf", - 'I' => "ArialItalic.ttf", - 'BI' => "ArialBoldItalic.ttf", - ], - "times" => [ - 'R' => "Times.ttf", - 'B' => "TimesBold.ttf", - 'I' => "TimesItalic.ttf", - 'BI' => "TimesBoldItalic.ttf", - ], - "courier" => [ - 'R' => "CourierNew.ttf", - 'B' => "CourierNewBold.ttf", - 'I' => "CourierNewItalic.ttf", - 'BI' => "CourierNewBoldItalic.ttf", - ] - ], - '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'], - ]; - } else { - $mpdf_init_options = [ - 'mode' => $mode, - 'tempDir' => $this->get_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'], - ]; - } - $mpdf_init_options['restrictColorSpace'] = $this->options['colorspace']; - $mpdf_init_options = array_merge($mpdf_init_options, $page_type_settings); - $mpdf_init_options = apply_filters("Bread_Mpdf_Init_Options", $mpdf_init_options, $this->options); - @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 - // by simply clobbering the other definitions. Since we generate the PDF then - // die, we shouldn't create any conflicts ourselves. - require_once plugin_dir_path(__FILE__).'mpdf/vendor/autoload.php'; - $this->mpdf = new mPDF($mpdf_init_options); - $this->mpdf->setAutoBottomMargin = 'pad'; - $this->mpdf->shrink_tables_to_fit = 1; - // TODO: Adding a page number really could just be an option or tag. - 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->simpleTables = false; - $this->mpdf->useSubstitutions = false; - $this->mpdf->mirrorMargins = false; - $this->mpdf->list_indent_first_level = 1; // 1 or 0 - whether to indent the first level of a list - // LOAD a stylesheet - $header_stylesheet = file_get_contents(plugin_dir_path(__FILE__).'css/mpdfstyletables.css'); - $this->mpdf->WriteHTML($header_stylesheet, 1); // The parameter 1 tells that this is css/style only and no body/html/text - $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['content_line_height']); - $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); - if ($this->options['column_line'] == 1 && - ($this->options['page_fold'] == 'tri' || $this->options['page_fold'] == 'quad')) { - $html = ''; - if ($this->options['page_fold'] == 'tri') { - $html .= ' - - - - - - - -
       
    '; - } - if ($this->options['page_fold'] == 'quad') { - $html .= ' - - - - - - - - -
        
    '; - } - $mpdf_column=new mPDF([ - 'mode' => $mode, - 'tempDir' => $this->get_temp_dir(), - 'format' => $mpdf_init_options['format'], - '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_footer' => 0, - 'orientation' => 'P', - 'restrictColorSpace' => $this->options['colorspace'], - ]); - - $mpdf_column->WriteHTML($html); - $FilePath = $this->get_temp_dir(). DIRECTORY_SEPARATOR . $this->get_FilePath('_column'); - $mpdf_column->Output($FilePath, 'F'); - $h = \fopen($FilePath, 'rb'); - $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); - $import_streams[$FilePath] = $stream; - $pagecount = $this->mpdf->SetSourceFile($stream); - $tplId = $this->mpdf->importPage($pagecount); - $this->mpdf->SetPageTemplate($tplId); - } - - $this->section_shortcodes = array( - '

    ' => '

    ', - '
    [page_break]
    ' => '', - '

    [page_break]

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

    [new_column]

    ' => '', - '[new_column]' => '', - '[page_break no_page_number]' => '', - '[start_page_numbers]' => '', - "[month_lower]" => date("F"), - "[month_upper]" => strtoupper(date("F")), - "[month]" => strtoupper(date("F")), - "[day]" => strtoupper(date("j")), - "[year]" => strtoupper(date("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']), - - ); - $this->unique_areas = $this->get_areas(); - // Extensions - $this->section_shortcodes = apply_filters("Bread_Section_Shortcodes", $this->section_shortcodes, $this->unique_areas, $this->formats_used); - - if (isset($this->options['pageheader_content'])) { - $data = $this->options['pageheader_content']; - $this->standard_shortcode_replacement($data, 'pageheader'); - $header_style = "vertical-align: top; text-align: center; font-weight: bold;margin-top:3px;margin-bottom:3px;"; - $header_style .= "color:".$this->options['pageheader_textcolor'].";"; - $header_style .= "background-color:".$this->options['pageheader_backgroundcolor'].";"; - $header_style .= "font-size:".$this->options['pageheader_fontsize']."pt;"; - $header_style .= "line-height:".$this->options['content_line_height'].";"; - - $this->mpdf->SetHTMLHeader( - '
    '.$data.'
    ', - 'O' - ); - } - if (isset($this->options['watermark'])) { - $this->mpdf->SetWatermarkImage($this->options['watermark'], 0.2, 'F'); - $this->mpdf->showWatermarkImage = true; - } - $sort_keys = 'weekday_tinyint,start_time,meeting_name'; - $get_used_formats = '&get_used_formats'; - $select_language = ''; - if ($this->options['weekday_language'] != $this->lang) { - $select_language = '&lang_enum='.$this->getSingleLanguage($this->options['weekday_language']); - } - if ($this->options['used_format_1'] == '') { - $results = $this->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'] != '') { - $results = $this->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 = json_decode(wp_remote_retrieve_body($results), true); - if (!empty($this->options['extra_meetings'])) { - $extras = ""; - foreach ((array)$this->options['extra_meetings'] as $value) { - $data = array(" [", "]"); - $value = str_replace($data, "", $value); - $extras .= "&meeting_ids[]=".$value; - } - - $extra_results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults&sort_keys=".$sort_keys."".$extras."".$get_used_formats.$select_language); - $extra_result = json_decode(wp_remote_retrieve_body($extra_results), true); - if ($extra_result <> null) { - $result_meetings = array_merge($result['meetings'], $extra_result['meetings']); - foreach ($result_meetings as $key => $row) { - $weekday[$key] = $row['weekday_tinyint']; - $start_time[$key] = $row['start_time']; - } - - array_multisort($weekday, SORT_ASC, $start_time, SORT_ASC, $result_meetings); - $this->formats_used = array_merge($result['formats'], $extra_result['formats']); - } else { - $this->formats_used = $result['formats']; - $result_meetings = $result['meetings']; - } - } else { - $this->formats_used = $result['formats']; - $result_meetings = $result['meetings']; - } - - if ($result_meetings == null) { - echo ""; - echo '

    No Meetings Found

    Or

    Internet or Server Problem

    '.$this->options['root_server'].'

    Please try again or contact your BMLT Administrator

    '; - exit; - } - $this->adjust_timezone($result_meetings, $this->target_timezone); - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats$select_language"); - $this->formats_all = json_decode(wp_remote_retrieve_body($results), true); - if ($this->options['asm_language']=='') { - $this->options['asm_language'] = $this->options['weekday_language']; - } - $this->formats_by_key[$this->options['weekday_language']] = array(); - foreach ($this->formats_all as $thisFormat) { - $this->formats_by_key[$this->options['weekday_language']][$thisFormat['key_string']] = $thisFormat; - if ($thisFormat['world_id'] == 'WCHR') { - $this->wheelchair_format = $thisFormat; - } - } - if (isset($this->options['asm_format_key']) && strlen($this->options['asm_format_key'])>0) { - if ($this->options['weekday_language'] != $this->options['asm_language']) { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetFormats&lang_enum=".$this->getSingleLanguage($this->options['asm_language'])); - $formats_all = json_decode(wp_remote_retrieve_body($results), true); - $this->sortBySubkey($formats_all, 'key_string'); - $this->formats_by_key[$this->options['asm_language']] = array(); - foreach ($formats_all as $thisFormat) { - $this->formats_by_key[$this->options['asm_language']][$thisFormat['key_string']] = $thisFormat; - if ($thisFormat['key_string']==$this->options['asm_format_key']) { - $this->options['asm_format_id'] = $thisFormat['id']; - } - } - } elseif (substr($this->options['asm_format_key'], 0, 1)!='@') { - if (isset($this->formats_by_key[$this->options['weekday_language']][$this->options['asm_format_key']])) { - $this->options['asm_format_id'] = $this->formats_by_key[$this->options['weekday_language']][$this->options['asm_format_key']]['id']; - } - } - } - if (strpos($this->options['custom_section_content'].$this->options['front_page_content'].$this->options['last_page_content'], '[format_codes_used_basic_es') !== false) { - if ($this->options['used_format_1'] == '') { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=time$get_used_formats&lang_enum=es"); - } else { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=time&get_used_formats&lang_enum=es&formats[]=".$this->options['used_format_1']); - } - $result_es = json_decode(wp_remote_retrieve_body($results), true); - $this->formats_spanish = $result_es['formats']; - $this->sortBySubkey($this->formats_spanish, 'key_string'); - } - if (strpos($this->options['custom_section_content'].$this->options['front_page_content'].$this->options['last_page_content'], '[format_codes_used_basic_fr') !== false) { - if ($this->options['used_format_1'] == '') { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=time$get_used_formats&lang_enum=fr"); - } else { - $results = $this->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults$services&sort_keys=time&get_used_formats&lang_enum=fr&formats[]=".$this->options['used_format_1']); - } - $result_fr = json_decode(wp_remote_retrieve_body($results), true); - $this->formats_french = $result_fr['formats']; - $this->sortBySubkey($this->formats_french, 'key_string'); - } - - if ($this->options['include_asm'] === '0') { - $countmax = count($this->formats_used); - for ($count = 0; $count < $countmax; $count++) { - if ($this->formats_used[$count]['key_string'] == $this->options['asm_format_key']) { - unset($this->formats_used[$count]); - } - } - $this->formats_used = array_values($this->formats_used); - } - $this->sortBySubkey($this->formats_used, 'key_string'); - $this->sortBySubkey($this->formats_all, 'key_string'); - - $this->meeting_count = count($result_meetings); - - $result_meetings = $this->orderByWeekdayStart($result_meetings); - if ($this->options['page_fold'] === 'full' || $this->options['page_fold'] === 'half' || $this->options['page_fold'] === 'flyer') { - $num_columns = 0; - } elseif ($this->options['page_fold'] === 'tri') { - $num_columns = 3; - } elseif ($this->options['page_fold'] === 'quad') { - $num_columns = 4; - } elseif ($this->options['page_fold'] === '') { - $this->options['page_fold'] = 'quad'; - $num_columns = 4; - } - - $this->mpdf->SetColumns($num_columns, '', $this->options['column_gap']); - if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - $this->write_front_page(); - } - $this->mpdf->WriteHTML('td{font-size: '.$this->options['content_font_size']."pt;line-height:".$this->options['content_line_height'].';background-color:#ffffff00;}', 1); - $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['content_font_size'] . 'pt'); - $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['content_line_height']); - $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); - $this->upgradeHeaderData(); - if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - $this->WriteHTML(''); - } - $this->writeMeetings($result_meetings, $this->options['meeting_template_content'], $this->options['weekday_language'], $this->options['include_asm']==0 ? -1 : 0, true); - - if ($this->options['page_fold'] !== 'half' && $this->options['page_fold'] !== 'full') { - $this->write_custom_section(); - $this->write_front_page(); - } else { - $this->WriteHTML(''); - if (trim($this->options['last_page_content']) !== '') { - $this->write_last_page(); - } - } - $this->mpdf->SetDisplayMode('fullpage', 'two'); - if ($this->options['page_fold'] == 'half') { - $FilePath = $this->get_temp_dir(). DIRECTORY_SEPARATOR . $this->get_FilePath('_half'); - $this->mpdf->Output($FilePath, 'F'); - $mpdfOptions = [ - 'mode' => $mode, - 'tempDir' => $this->get_temp_dir(), - 'default_font_size' => '', - 'margin_left' => 0, - 'margin_right' => 0, - 'margin_top' => 0, - 'margin_bottom' => 0, - 'margin_footer' => 0, - 'orientation' => 'L', - 'restrictColorSpace' => $this->options['colorspace'], - ]; - $ps = $this->options['page_size']; - if ($ps=='ledger') { - $mpdfOptions['format'] = 'tabloid'; - } elseif ($ps == '5inch') { - $mpdfOptions['format'] = array(197.2,279.4); - } else { - $mpdfOptions['format'] = $ps.'-L'; - } - $mpdfOptions['curlUserAgent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0'; - $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); - $mpdftmp=new mPDF($mpdfOptions); - $this->mpdf->shrink_tables_to_fit = 1; - $ow = $mpdftmp->h; - $oh = $mpdftmp->w; - $pw = $mpdftmp->w / 2; - $ph = $mpdftmp->h; - $h = \fopen($FilePath, 'rb'); - $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); - $import_streams[$FilePath] = $stream; - $pagecount = $mpdftmp->SetSourceFile($stream); - $pp = $this->get_booklet_pages($pagecount); - foreach ($pp as $v) { - $mpdftmp->AddPage(); - if ($v[0]>0 & $v[0]<=$pagecount) { - $tplIdx = $mpdftmp->importPage($v[0]); - $mpdftmp->UseTemplate($tplIdx, 0, 0, $pw, $ph); - } - if ($v[1]>0 & $v[1]<=$pagecount) { - $tplIdx = $mpdftmp->importPage($v[1]); - $mpdftmp->UseTemplate($tplIdx, $pw, 0, $pw, $ph); - } - } - $this->mpdf = $mpdftmp; - } else if ($this->options['page_fold'] == 'full' && $this->options['booklet_pages']) { - $FilePath = $this->get_temp_dir(). DIRECTORY_SEPARATOR . $this->get_FilePath('_full'); - $this->mpdf->Output($FilePath, 'F'); - $mpdfOptions = [ - 'mode' => $mode, - 'tempDir' => $this->get_temp_dir(), - 'default_font_size' => '', - 'margin_left' => 0, - 'margin_right' => 0, - 'margin_top' => 0, - 'margin_bottom' => 0, - 'margin_footer' => 6, - 'orientation' => $this->options['page_orientation'], - 'restrictColorSpace' => $this->options['colorspace'], - ]; - $mpdfOptions['format'] = $this->options['page_size']."-".$this->options['page_orientation']; - /** this is because mPDF has an old UA and SiteGround is complaining - * It will be fixed in the next release of mPDF, but we can't wait that long. - * But, when a new mPDF comes out, remove this line. - */ - $mpdf_config['curlUserAgent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0'; - /* */ - $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); - $mpdftmp=new mPDF($mpdfOptions); - $this->mpdf->shrink_tables_to_fit = 1; - //$mpdftmp->SetImportUse(); - $h = \fopen($FilePath, 'rb'); - $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); - $import_streams[$FilePath] = $stream; - $np = $mpdftmp->SetSourceFile($stream); - $pp = 4*ceil($np/4); - for ($i=1; $i<$np; $i++) { - $mpdftmp->AddPage(); - $tplIdx = $mpdftmp->ImportPage($i); - $mpdftmp->UseTemplate($tplIdx); - } - for ($i=$np; $i<$pp; $i++) { - $mpdftmp->AddPage(); - } - $mpdftmp->AddPage(); - $tplIdx = $mpdftmp->ImportPage($np); - $mpdftmp->UseTemplate($tplIdx); - $this->mpdf = $mpdftmp; - } else if ($this->options['page_fold'] == 'flyer') { - $FilePath = $this->get_temp_dir(). DIRECTORY_SEPARATOR . $this->get_FilePath('_flyer'); - $this->mpdf->Output($FilePath, 'F'); - $mpdfOptions = [ - 'mode' => $mode, - 'tempDir' => $this->get_temp_dir(), - 'default_font_size' => '', - 'margin_left' => 0, - 'margin_right' => 0, - 'margin_top' => 0, - 'margin_bottom' => 0, - 'margin_footer' => 6, - 'format' => $this->options['page_size'].'-L', - 'orientation' => 'L', - 'restrictColorSpace' => $this->options['colorspace'], - ]; - $mpdftmp=new mPDF($mpdfOptions); - $this->mpdf->shrink_tables_to_fit = 1; - //$mpdftmp->SetImportUse(); - $h = \fopen($FilePath, 'rb'); - $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); - $import_streams[$FilePath] = $stream; - $np = $mpdftmp->SetSourceFile($stream); - $ow = $mpdftmp->w; - $oh = $mpdftmp->h; - $fw = $ow / 3; - $mpdftmp->AddPage(); - $tplIdx = $mpdftmp->importPage(1); - $mpdftmp->UseTemplate($tplIdx, 0, 0); - $mpdftmp->UseTemplate($tplIdx, $fw, 0); - $mpdftmp->UseTemplate($tplIdx, $fw+$fw, 0); - $sep = $this->columnSeparators($oh); - if (!empty($sep)) { - $mpdftmp->writeHTML($sep); - } - $mpdftmp->AddPage(); - $tplIdx = $mpdftmp->ImportPage(2); - $mpdftmp->UseTemplate($tplIdx, 0, 0); - $mpdftmp->UseTemplate($tplIdx, $fw, 0); - $mpdftmp->UseTemplate($tplIdx, $fw+$fw, 0); - if (!empty($sep)) { - $mpdftmp->writeHTML($sep); - } - $this->mpdf = $mpdftmp; - } - 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']); - } - if (headers_sent()) { - echo '

    Headers already sent before PDF generation

    '; - } else { - if (intval($this->options['cache_time']) > 0 && ! isset($_GET['nocache']) - && !isset($_GET['custom_query'])) { - $content = $this->mpdf->Output('', 'S'); - $content = bin2hex($content); - $transient_key = $this->get_TransientKey(); - 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->allSettings[$this->loaded_setting]); - $this->mpdf->Output($FilePath, 'I'); - } - foreach ($import_streams as $FilePath => $stream) { - @unlink($FilePath); - } - $this->rrmdir($this->get_temp_dir()); - exit; - } - function rrmdir($dir) - { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - if (is_dir($dir. DIRECTORY_SEPARATOR .$object) && !is_link($dir."/".$object)) { - $this->rrmdir($dir. DIRECTORY_SEPARATOR .$object); - } else { - @unlink($dir. DIRECTORY_SEPARATOR .$object); - } - } - } - @rmdir($dir); - } - } - function brute_force_cleanup($dir) - { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - if (str_starts_with($object, "bread")) { - $filename = $dir . DIRECTORY_SEPARATOR .$object; - if (time()-filemtime($filename) > 24 * 3600) { - $this->rrmdir($filename); - } - } - } - } - } - } - function orderByWeekdayStart(&$result_meetings) - { - $days = array_column($result_meetings, 'weekday_tinyint'); - $today_str = $this->options['weekday_start']; - return array_merge( - array_splice($result_meetings, array_search($today_str, $days)), - array_splice($result_meetings, 0) - ); - } - function get_FilePath($pos = '') - { - $site = ''; - if (is_multisite()) { - $site = get_current_blog_id().'_'; - } - return "meetinglist_".$site.$this->loaded_setting.$pos.'_'.strtolower(date("njYghis")).".pdf"; - } - function adjust_timezone(&$meetings, $target_timezone) - { - if (!$target_timezone) { - return; - } - $target_midnight = new DateTime(); - $target_midnight->setTimezone($target_timezone); - $target_midnight->setTime(23, 59); - $target_yesterday = new DateTime(); - $target_yesterday->setTimezone($target_timezone); - $target_yesterday->setTime(0, 0); - foreach ($meetings as &$meeting) { - if (!empty($meeting['time_zone'])) { - $meeting_time_zone = timezone_open($meeting['time_zone']); - if ($meeting_time_zone) { - $date = date_create($meeting['start_time'], $meeting_time_zone); - date_timezone_set($date, $target_timezone); - $meeting['start_time'] = $date->format('H:i'); - if ($date >= $target_midnight) { - $meeting['weekday_tinyint'] = $meeting['weekday_tinyint']+1; - if ($meeting['weekday_tinyint']==8) { - $meeting['weekday_tinyint'] = 1; - } - } elseif ($date < $target_yesterday) { - $meeting['weekday_tinyint'] = $meeting['weekday_tinyint']-1; - if ($meeting['weekday_tinyint']==0) { - $meeting['weekday_tinyint'] = 7; - } - } - } - } - } - usort($meetings, array($this, "sortDayTime")); - } - function sortDayTime($a, $b) - { - if ($a['weekday_tinyint'] < $b['weekday_tinyint']) { - return -1; - } - if ($a['weekday_tinyint'] > $b['weekday_tinyint']) { - return 1; - } - if ($a['start_time'] < $b['start_time']) { - return -1; - } - if ($a['start_time'] > $b['start_time']) { - return 1; - } - if ($a['duration_time'] < $b['duration_time']) { - return -1; - } - if ($a['duration_time'] > $b['duration_time']) { - return 1; - } - return 0; - } - // include_asm = 0 - let everything through - // 1 - only meetings with asm format - // -1 - only meetings without asm format - function writeMeetings($result_meetings, $template, $lang, $include_asm, $asm_flag) - { - $headerMeetings = $this->getHeaderMeetings($result_meetings, $lang, $include_asm, $asm_flag); - $unique_heading = $this->getUniqueHeadings($headerMeetings); - - $header_style = "color:".$this->options['header_text_color'].";"; - $header_style .= "background-color:".$this->options['header_background_color'].";"; - $header_style .= "font-size:".$this->options['header_font_size']."pt;"; - $header_style .= "line-height:".$this->options['content_line_height'].";"; - $header_style .= "text-align:center;padding-top:2px;padding-bottom:3px;"; - - if ($this->options['header_uppercase'] == 1) { - $header_style .= 'text-transform: uppercase;'; - } - if ($this->options['header_bold'] == 0) { - $header_style .= 'font-weight: normal;'; - } - if ($this->options['header_bold'] == 1) { - $header_style .= 'font-weight: bold;'; - } - $cont = '('.$this->translate[$lang]['CONT'].')'; - - - $template = wpautop(stripslashes($template)); - $template = preg_replace('/[[:^print:]]/', ' ', $template); - - $template = str_replace(" ", " ", $template); - $analysedTemplate = $this->analyseTemplate($template); - $first_meeting = true; - $newMajorHeading = false; - /*** - * You might be wondering why I am not using keep-with-table... - * The problem is, keep with table doesn't work with columns, only pages. - * We want to check that a header and at least one meeting fits, so we write it - * to a test PDF, see how big it is, and check if it will fit. - */ - $test_pages = deep_copy($this->mpdf); - foreach ($unique_heading as $this_heading_raw) { - $newMajorHeading = true; - if ($this->skip_heading($this_heading_raw)) { - continue; - } - $this_heading = $this->remove_sort_key($this_heading_raw); - $unique_subheading = array_keys($headerMeetings[$this_heading_raw]); - asort($unique_subheading, SORT_NATURAL | SORT_FLAG_CASE); - foreach ($unique_subheading as $this_subheading_raw) { - $newSubHeading = true; - $this_subheading = $this->remove_sort_key($this_subheading_raw); - foreach ($headerMeetings[$this_heading_raw][$this_subheading_raw] as $meeting_value) { - $header = ''; - if ($newSubHeading && !empty($this->options['combine_headings'])) { - $header_string = $this->options['combine_headings']; - $header_string = str_replace('main_grouping', $this_heading, $header_string); - $header_string = str_replace('subgrouping', $this_subheading, $header_string); - $header .= "
    ".$header_string."
    "; - } elseif (!empty($this->options['subgrouping'])) { - if ($newMajorHeading === true) { - $xtraMargin = ''; - if (!$first_meeting) { - $xtraMargin = 'margin-top:2pt;'; - } - $header .= '
    '.$this_heading."
    "; - } - if ($newSubHeading && $this->options['sub_header_shown']=='display') { - $header .= "

    ".$this_subheading."

    "; - } - } elseif ($newMajorHeading === true) { - $header .= "
    ".$this_heading."
    "; - } - if ($this->options['suppress_heading']==1) { - $header = ''; - } - $data = $header . $this->write_single_meeting( - $meeting_value, - $template, - $analysedTemplate, - $meeting_value['area_name'] - ); - $this->writeBreak($test_pages); - $y_startpos = $test_pages->y; - @$test_pages->WriteHTML($data); - $y_diff = $test_pages->y - $y_startpos; - if ($y_diff >= $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height) { - $this->writeBreak($this->mpdf); - if (!$newMajorHeading && $this->options['cont_header_shown']) { - $header = "
    ".$this_heading." " . $cont . "
    "; - $data = $header.$data; - } - } - $this->WriteHTML($data); - $first_meeting = false; - $newSubHeading = false; - $newMajorHeading = false; - } - } - } - } - function asm_test($value, $flag = false) - { - if (empty($this->options['asm_format_key'])) { - return false; - } - $format_key = $this->options['asm_format_key']; - if ($format_key == "@Virtual@") { - if ($flag && $this->isHybrid($value)) { - return false; - } - return $this->isVirtual($value) || $this->isHybrid($value); - } - if ($format_key == "@F2F@") { - return !$this->isVirtual($value) || $this->isHybrid($value); - } - $enFormats = explode(",", $value['formats']); - return in_array($format_key, $enFormats); - } - function isHybrid($value) - { - $enFormats = explode(",", $value['formats']); - return in_array('HY', $enFormats); - } - function isVirtual($value) - { - $enFormats = explode(",", $value['formats']); - return in_array('VM', $enFormats); - } - // include_asm = 0 - let everything through - // 1 - only meetings with asm format - // -1 - only meetings without asm format - function getHeaderMeetings(&$result_meetings, $lang, $include_asm, $asm_flag) - { - $levels = $this->getHeaderLevels(); - $headerMeetings = array(); - foreach ($result_meetings as &$value) { - $value = $this->enhance_meeting($value, $lang); - $asm_test = $this->asm_test($value, $asm_flag); - if ((( $include_asm < 0 && $asm_test ) || - ( $include_asm > 0 && !$asm_test ))) { - continue; - } - - $main_grouping = $this->getHeaderItem($value, 'main_grouping'); - if (!isset($headerMeetings[$main_grouping])) { - $headerMeetings[$main_grouping] = array(); - if ($levels == 1) { - $headerMeetings[$main_grouping][0] = array(); - } - } - if ($levels == 2) { - $subgrouping = $this->getHeaderItem($value, 'subgrouping'); - if (!isset($headerMeetings[$main_grouping][$subgrouping])) { - $headerMeetings[$main_grouping][$subgrouping] = array(); - } - $headerMeetings[$main_grouping][$subgrouping][] = $value; - } else { - $headerMeetings[$main_grouping][0][] = $value; - } - } - return $headerMeetings; - } - function getUniqueHeadings($headerMeetings) - { - $unique_heading = array_keys($headerMeetings); - asort($unique_heading, SORT_NATURAL | SORT_FLAG_CASE); - return $unique_heading; - } - function remove_sort_key($this_heading) - { - if (mb_substr($this_heading, 0, 1)=='[') { - $end = strpos($this_heading, ']'); - if ($end>0) { - return trim(substr($this_heading, $end+1)); - } - } - return $this_heading; - } - function skip_heading($this_heading) - { - return (mb_substr($this_heading, 0, 5)=='[XXX]'); - } - function writeBreak($mpdf) - { - if ($this->options['page_fold'] === 'half' || $this->options['page_fold'] === 'full') { - $mpdf->WriteHTML(""); - } else { - $mpdf->WriteHTML(""); - } - } - function getOptionForDisplay($option, $default = '') - { - return empty($this->options[$option])?$default:esc_html($this->options[$option]); - } - function columnSeparators($oh) - { - if ($this->options['column_line'] == 1) { - return ' - - - - - - - - -
       
    '; - } - } - function getHeaderLevels() - { - if (!empty($this->options['subgrouping'])) { - return 2; - } - return 1; - } - function getHeaderItem($value, $name) - { - if (empty($this->options[$name])) { - return ''; - } - $grouping = ''; - if ($this->options[$name]=='service_body_bigint') { - foreach ($this->unique_areas as $unique_area) { - $area_data = explode(',', $unique_area); - $area_name = $this->arraySafeGet($area_data); - $area_id = $this->arraySafeGet($area_data, 1); - if ($area_id === $value['service_body_bigint']) { - return $area_name; - } - } - return 'Area not found'; - } elseif ($this->options[$name]=='day') { - $off = intval($this->options['weekday_start']); - $day = intval($value['weekday_tinyint']); - if ($day < $off) { - $day = $day + 7; - } - return '['.str_pad($day, 2, '0', STR_PAD_LEFT).']'.$value['day']; - } elseif (isset($value[$this->options[$name]])) { - $grouping = $this->parse_field($value[$this->options[$name]]); - } - $alt = ''; - if ($grouping=='' - && !empty($this->options[$name.'_alt']) - && isset($value[$this->options[$name.'_alt']])) { - $grouping = $this->parse_field($value[$this->options[$name.'_alt']]); - $alt = '_alt'; - } - if (strlen(trim($grouping))==0) { - return 'NO DATA'; - } - if (!empty($this->options[$name.$alt.'_suffix'])) { - return $grouping.' '.$this->options[$name.$alt.'_suffix']; - } - return $grouping; - } - function upgradeHeaderData() - { - $this->options['combine_headings'] = ''; - if ($this->options['meeting_sort'] === 'user_defined') { - if ($this->options['sub_header_shown'] == 'combined') { - $this->options['combine_headings'] = 'main_grouping - subgrouping'; - } - return; - } - unset($this->options['subgrouping']); - if ($this->options['meeting_sort'] === 'state') { - $this->options['main_grouping'] = 'location_province'; - $this->options['subgrouping'] = 'location_municipality'; - $this->options['combine_headings'] = 'subgrouping, main_grouping'; - } elseif ($this->options['meeting_sort'] === 'city') { - $this->options['main_grouping'] = 'location_municipality'; - } elseif ($this->options['meeting_sort'] === 'borough') { - $this->options['main_grouping'] = 'location_city_subsection'; - $this->options['main_grouping_suffix'] = $this->options['borough_suffix']; - } elseif ($this->options['meeting_sort'] === 'county') { - $this->options['main_grouping'] = 'location_sub_province'; - $this->options['main_grouping_alt_suffix'] = $this->options['county_suffix']; - } elseif ($this->options['meeting_sort'] === 'borough_county') { - $this->options['main_grouping'] = 'location_city_subsection'; - $this->options['main_grouping_suffix'] = $this->options['borough_suffix']; - $this->options['main_grouping_alt'] = 'location_sub_province'; - $this->options['main_grouping_alt_suffix'] = $this->options['county_suffix']; - } elseif ($this->options['meeting_sort'] === 'neighborhood_city') { - $this->options['main_grouping'] = 'location_neighborhood'; - $this->options['main_grouping_suffix'] = $this->options['neighborhood_suffix']; - $this->options['main_grouping_alt'] = 'location_municipality'; - $this->options['main_grouping_alt_suffix'] = $this->options['city_suffix']; - } elseif ($this->options['meeting_sort'] === 'group') { - $this->options['main_grouping'] = 'meeting_name'; - } elseif ($this->options['meeting_sort'] === 'weekday_area') { - $this->options['main_grouping'] = 'day'; - $this->options['subgrouping'] = 'service_body_bigint'; - } elseif ($this->options['meeting_sort'] === 'weekday_city') { - $this->options['main_grouping'] = 'day'; - $this->options['subgrouping'] = 'location_municipality'; - } elseif ($this->options['meeting_sort'] === 'weekday_county') { - $this->options['main_grouping'] = 'day'; - $this->options['subgrouping'] = 'location_sub_province'; - } else { - $this->options['main_grouping'] = 'day'; - } - } - function get_area_name($meeting_value) - { - foreach ($this->unique_areas as $unique_area) { - $area_data = explode(',', $unique_area); - $area_id = $this->arraySafeGet($area_data, 1); - if ($area_id === $meeting_value['service_body_bigint']) { - return $this->arraySafeGet($area_data); - } - } - return ''; - } - function analyseTemplate($template) - { - $arr = preg_split('/\W+/', $template, 0, PREG_SPLIT_OFFSET_CAPTURE); - $arr = array_reverse($arr, true); - $ret = array(); - foreach ($arr as $item) { - if (strlen($item[0])<3) { - continue; - } - if ($item[0]=='table') { - continue; - } - if ($item[0]=='tbody') { - continue; - } - if ($item[0]=='strong') { - continue; - } - if ($item[0]=='left') { - continue; - } - if ($item[0]=='right') { - continue; - } - if ($item[0]=='top') { - continue; - } - if ($item[0]=='bottom') { - continue; - } - if ($item[0]=='center') { - continue; - } - if ($item[0]=='align') { - continue; - } - if ($item[0]=='font') { - continue; - } - if ($item[0]=='size') { - continue; - } - if ($item[0]=='text') { - continue; - } - if ($item[0]=='style') { - continue; - } - if ($item[0]=='family') { - continue; - } - if ($item[0]=='vertical') { - continue; - } - if ($item[0]=='color') { - continue; - } - if ($item[0]=='QRCode') { - continue; - } - if ($item[1]>0 && $template[$item[1]-1]=='[' - && $template[$item[1]+strlen($item[0])]==']') { - $item[0] = '['.$item[0].']'; - $item[1] = $item[1] - 1; - $item[2] = true; - } else { - $item[2] = false; - } - $ret[] = $item; - } - return $ret; - } - function enhance_meeting(&$meeting_value, $lang) - { - $duration = explode(':', $meeting_value['duration_time']); - $minutes = intval($duration[0])*60 + intval($duration[1]) + intval($duration[2]); - $meeting_value['duration_m'] = $minutes; - $meeting_value['duration_h'] = rtrim(rtrim(number_format($minutes/60, 2), 0), '.'); - $space = ' '; - if ($this->options['remove_space'] == 1) { - $space = ''; - } - if ($this->options['time_clock'] == null || $this->options['time_clock'] == '12' || $this->options['time_option'] == '') { - $time_format = "g:i".$space."A"; - } elseif ($this->options['time_clock'] == '24fr') { - $time_format = "H\hi"; - } else { - $time_format = "H:i"; - } - if ($this->options['time_option'] == 1 || $this->options['time_option'] == '') { - $meeting_value['start_time'] = date($time_format, strtotime($meeting_value['start_time'])); - if ($meeting_value['start_time'] == '12:00PM' || $meeting_value['start_time'] == '12:00 PM') { - $meeting_value['start_time'] = 'NOON'; - } - } elseif ($this->options['time_option'] == '2') { - $addtime = '+ ' . $minutes . ' minutes'; - $end_time = date($time_format, strtotime($meeting_value['start_time'] . ' ' . $addtime)); - $meeting_value['start_time'] = date($time_format, strtotime($meeting_value['start_time'])); - if ($lang=='fa') { - $meeting_value['start_time'] = $this->toPersianNum($end_time).$space.'-'.$space.$this->toPersianNum($meeting_value['start_time']); - } else { - $meeting_value['start_time'] = $meeting_value['start_time'].$space.'-'.$space.$end_time; - } - } elseif ($this->options['time_option'] == '3') { - $time_array = array("1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00", "8:00", "9:00", "10:00", "11:00", "12:00"); - $temp_start_time = date("g:i", strtotime($meeting_value['start_time'])); - $temp_start_time_2 = date("g:iA", strtotime($meeting_value['start_time'])); - if ($temp_start_time_2 == '12:00PM') { - $start_time = 'NOON'; - } elseif (in_array($temp_start_time, $time_array)) { - $start_time = date("g", strtotime($meeting_value['start_time'])); - } else { - $start_time = date("g:i", strtotime($meeting_value['start_time'])); - } - $addtime = '+ ' . $minutes . ' minutes'; - $temp_end_time = date("g:iA", strtotime($meeting_value['start_time'] . ' ' . $addtime)); - $temp_end_time_2 = date("g:i", strtotime($meeting_value['start_time'] . ' ' . $addtime)); - if ($temp_end_time == '12:00PM') { - $end_time = 'NOON'; - } elseif (in_array($temp_end_time_2, $time_array)) { - $end_time = date("g".$space."A", strtotime($temp_end_time)); - } else { - $end_time = date("g:i".$space."A", strtotime($temp_end_time)); - } - $meeting_value['start_time'] = $start_time.$space.'-'.$space.$end_time; - } - - $meeting_value['day_abbr'] = $this->getday($meeting_value['weekday_tinyint'], true, $lang); - $meeting_value['day'] = $this->getday($meeting_value['weekday_tinyint'], false, $lang); - $area_name = $this->get_area_name($meeting_value); - $meeting_value['area_name'] = $area_name; - $meeting_value['area_i'] = substr($area_name, 0, 1); - - $meeting_value['wheelchair'] = ''; - if (!is_null($this->wheelchair_format)) { - $fmts = explode(',', $meeting_value['format_shared_id_list']); - if (in_array($this->wheelchair_format['id'], $fmts)) { - $meeting_value['wheelchair'] = ''; - } - } - // Extensions. - return apply_filters("Bread_Enrich_Meeting_Data", $meeting_value, $this->formats_by_key[$lang]); - } - function write_single_meeting($meeting_value, $template, $analysedTemplate, $area_name) - { - $data = $template; - $namedValues = array(); - foreach ($meeting_value as $field => $notUsed) { - $namedValues[$field] = $this->get_field($meeting_value, $field); - } - foreach ($this->legacy_synonyms as $syn => $field) { - $namedValues[$syn] = $namedValues[$field]; - } - foreach ($analysedTemplate as $item) { - $name = $item[0]; - if ($item[2]) { - $name = substr($name, 1, strlen($name)-2); - } - if (isset($namedValues[$name])) { - $data = substr_replace($data, $namedValues[$name], $item[1], strlen($item[0])); - } - } - $qr_pos = strpos($data, "[QRCode"); - if ($qr_pos) { - $qr_end = strpos($data, ']', $qr_pos); - $data = substr($data, 0, $qr_pos). - ''. - substr($data, $qr_end+1); - } - $search_strings = array(); - $replacements = array(); - $clean_up = array( - '' => '', - ' ' => '', - '' => '', - ' ' => '', - '' => '', - ' ' => '', - ' ' => ' ', - ' ' => ' ', - ' ' => ' ', - '

    ' => '', - '()' => '', - '
    ' => 'line_break', - '
    ' => 'line_break', - 'line_break line_break' => '
    ', - 'line_breakline_break' => '
    ', - 'line_break' => '
    ', - '
    ,' => '
    ', - ',
    ' => '
    ', - ',
    ' => '
    ', - '

    ,' => '

    ', - ", , ," => ",", - ", *," => ",", - ", ," => ",", - " , " => " ", - ", (" => " (", - ', ' ' $value) { - $search_strings[] = $key; - $replacements[] = $value; - } - $data = str_replace($search_strings, $replacements, $data); - return $data; - } - function get_booklet_pages($np, $backcover = true) - { - $lastpage = $np; - $np = 4*ceil($np/4); - $pp = array(); - for ($i=1; $i<=$np/2; $i++) { - $p1 = $np - $i + 1; - if ($backcover) { - if ($i == 1) { - $p1 = $lastpage; - } else if ($p1 >= $lastpage) { - $p1 = 0; - } - } - if ($i % 2 == 1) { - $pp[] = array( $p1, $i ); - } else { - $pp[] = array( $i, $p1 ); - } - } - return $pp; - } - - function write_front_page() - { - - $this->mpdf->WriteHTML('td{font-size: '.$this->options['front_page_font_size']."pt;line-height:".$this->options['front_page_line_height'].';}', 1); - $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['front_page_line_height']); - $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['front_page_font_size'] . 'pt'); - $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); - $this->options['front_page_content'] = wp_unslash($this->options['front_page_content']); - $this->standard_shortcode_replacement($this->options['front_page_content'], 'front_page'); - - - $querystring_custom_items = array(); - preg_match_all('/(\[querystring_custom_\d+\])/', $this->options['front_page_content'], $querystring_custom_items); - foreach ($querystring_custom_items[0] as $querystring_custom_item) { - $mod_qs_ci = str_replace("]", "", str_replace("[", "", $querystring_custom_item)); - $this->options['front_page_content'] = str_replace($querystring_custom_item, (isset($_GET[$mod_qs_ci]) ? $_GET[$mod_qs_ci] : "NOT SET"), $this->options['front_page_content']); - } - $this->writeHTMLwithServiceMeetings($this->options['front_page_content'], 'front_page'); - $this->mpdf->showWatermarkImage = false; - } - - function write_last_page() - { - $this->mpdf->WriteHTML('td{font-size: '.$this->options['last_page_font_size']."pt;line-height:".$this->options['last_page_line_height'].';}', 1); - $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['last_page_font_size'] . 'pt'); - $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['last_page_line_height']); - $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); - $this->standard_shortcode_replacement($this->options['last_page_content'], 'last_page'); - $this->writeHTMLwithServiceMeetings($this->options['last_page_content'], 'last_page'); - } - - function write_custom_section() - { - $this->mpdf->SetHTMLHeader(); - if (isset($this->options['pageheader_content']) && trim($this->options['pageheader_content'])) { - $this->mpdf->SetTopMargin($this->options['margin_header']); - } - $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['custom_section_line_height']); - $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['custom_section_font_size'] . 'pt'); - $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); - $this->standard_shortcode_replacement($this->options['custom_section_content'], 'custom_section'); - $this->mpdf->WriteHTML('td{font-size: '.$this->options['custom_section_font_size']."pt;line-height:".$this->options['custom_section_line_height'].';}', 1); - $this->writeHTMLwithServiceMeetings($this->options['custom_section_content'], 'custom_section'); - } - function locale_month_replacement($data, $case, $sym) - { - $strpos = strpos($data, "[month_$case"."_"); - if ($strpos !== false) { - $locLang = substr($data, $strpos+13, 2); - if (!isset($this->translate[$locLang])) { - $locLang = 'en'; - } - $fmt = new IntlDateFormatter( - $this->translate[$locLang]['LOCALE'], - IntlDateFormatter::FULL, - IntlDateFormatter::FULL - ); - $fmt->setPattern($sym); - $month = ucfirst(mb_convert_encoding($fmt->format(time()), 'UTF-8', 'ISO-8859-1')); - if ($case=='upper') { - $month = mb_strtoupper($month, 'UTF-8'); - } - return substr_replace($data, $month, $strpos, 16); - } - return $data; - } - function standard_shortcode_replacement(&$data, $page) - { - $search_strings = array(); - $replacements = array(); - foreach ($this->section_shortcodes as $key => $value) { - $search_strings[] = $key; - $replacements[] = $value; - } - - $search_strings[] = '[meeting_count]'; - $replacements[] = $this->meeting_count; - $data = $this->options[$page.'_content']; - $data = $this->locale_month_replacement($data, 'lower', "LLLL"); - $data = $this->locale_month_replacement($data, 'upper', "LLLL"); - $data = str_replace($search_strings, $replacements, $data); - $this->replace_format_shortcodes($data, $page); - $data = str_replace("[date]", strtoupper(date("F Y")), $data); - if ($this->target_timezone) { - $data = str_replace('[timezone]', $this->target_timezone->getName(), $data); - } - } - function writeHTML($str) - { - //$str = htmlentities($str); - @$this->mpdf->WriteHTML(wpautop(stripslashes($str))); - } - function writeHTMLwithServiceMeetings($data, $page) - { - $strs = array('

    [service_meetings]

    ','[service_meetings]', - '

    [additional_meetings]

    ','[additional_meetings]'); - - foreach ($strs as $str) { - $pos = strpos($data, $str); - if (!$pos) { - continue; - } - if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - $this->WriteHTML(''); - } - $this->WriteHTML(substr($data, 0, $pos)); - $this->write_service_meetings($this->options[$page.'_font_size'], $this->options[$page.'_line_height']); - if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - $this->WriteHTML(''); - } - $this->WriteHTML(substr($data, $pos+strlen($str))); - return; - } - $this->WriteHTML($data); - } - function replace_format_shortcodes(&$data, $page_name) - { - - $this->shortcode_formats('[format_codes_used_basic]', false, $this->formats_used, $page_name, $data); - $this->shortcode_formats('[format_codes_used_detailed]', true, $this->formats_used, $page_name, $data); - $this->shortcode_formats('[format_codes_used_basic_es]', false, $this->formats_spanish, $page_name, $data); - $this->shortcode_formats('[format_codes_used_detailed_es]', true, $this->formats_spanish, $page_name, $data); - $this->shortcode_formats('[format_codes_used_basic_fr]', false, $this->formats_french, $page_name, $data); - $this->shortcode_formats('[format_codes_all_basic]', false, $this->formats_all, $page_name, $data); - $this->shortcode_formats('[format_codes_all_detailed]', true, $this->formats_all, $page_name, $data); - } - function shortcode_formats($shortcode, $detailed, $formats, $page, &$str) - { - $pos = strpos($str, $shortcode); - if ($pos==false) { - return; - } - $value = ''; - if ($detailed) { - $value = $this->write_detailed_formats($formats, $page); - } else { - $value = $this->write_formats($formats, $page); - } - $str = substr($str, 0, $pos).$value.substr($str, $pos+strlen($shortcode)); - } - function write_formats($formats, $page) - { - if ($formats == null) { - return ''; - } - $this->mpdf->WriteHTML('td{font-size: '.$this->options[$page.'_font_size']."pt;line-height:".$this->options[$page.'_line_height'].';}', 1); - $data = "options[$page.'_line_height'].";'>"; - for ($count = 0; $count < count($formats); $count++) { - $data .= ''; - $data .= ""; - $data .= ""; - $count++; - if ($count >= count($formats)) { - $data .= ""; - $data .= ""; - } else { - $data .= ""; - $data .= ""; - } - $data .= ""; - } - $data .= "
    ".$formats[$count]['key_string']."".$formats[$count]['name_string']."".$formats[$count]['key_string']."".$formats[$count]['name_string']."
    "; - return $data; - } - function asm_required($data) - { - return strpos($data, '[service_meetings]') || strpos($data, '[additional_meetings]'); - } - function write_detailed_formats($formats, $page) - { - if ($formats == null) { - return ''; - } - $this->mpdf->WriteHTML('td{font-size: '.$this->options[$page.'_font_size']."pt;line-height:".$this->options[$page.'_line_height'].';}', 1); - $data = "options[$page.'_line_height'].";'>"; - for ($count = 0; $count < count($formats); $count++) { - if (isset($this->options[$page.'_font_size']) && isset($this->options[$page . '_line_height'])) { - $data .= ""; - $data .= ""; - } - } - $data .= "
    options[$page . '_line_height'] . ";font-weight:bold;'>" . $formats[$count]['key_string'] . "options[$page . '_line_height'] . ";'>(" . $formats[$count]['name_string'] . ") " . $formats[$count]['description_string'] . "
    "; - return $data; - } - private function parse_field($text) - { - if ($text!='') { - $exploded = explode("#@-@#", $text); - $knt = count($exploded); - if ($knt > 1) { - $text = $exploded[$knt-1]; - } - } - return $text; - } - public function arraySafeGet($arr, $i = 0) - { - return is_array($arr) ? $arr[$i] ?? '': ''; - } - function get_field($obj, $field) - { - $value = ''; - if (isset($obj[$field])) { - $value = $this->parse_field($obj[$field]); - } - return $value; - } - function write_service_meetings($font_size, $line_height) - { - if ($this->service_meeting_result == null) { - $sort_order = $this->options['asm_sort_order']; - if ($sort_order=='same') { - $sort_order = 'weekday_tinyint,start_time'; - } - $asm_id = ""; - if (isset($this->options['asm_format_id'])) { - $asm_id = '&formats[]='.$this->options['asm_format_id']; - } - $services = $this->services; - if (!empty($this->options['asm_custom_query'])) { - $services = $this->options['asm_custom_query']; - } - $asm_query = "client_interface/json/?switcher=GetSearchResults$services$asm_id&sort_keys=$sort_order"; - // I'm not sure we need this, but for now we need to emulate the old behavior - if ($this->options['asm_format_key']==='ASM') { - $asm_query .= "&advanced_published=0"; - } - $results = $this->get_configured_root_server_request($asm_query); - $this->service_meeting_result = json_decode(wp_remote_retrieve_body($results), true); - if ($sort_order == 'weekday_tinyint,start_time') { - $this->adjust_timezone($this->service_meeting_result, $this->target_timezone); - $this->service_meeting_result = $this->orderByWeekdayStart($this->service_meeting_result); - } - } - if ($this->options['asm_sort_order']=='same') { - if (isset($this->options['asm_template_content']) && trim($this->options['asm_template_content'])) { - $template = $this->options['asm_template_content']; - } else { - $template = $this->options['meeting_template_content']; - } - $this->writeMeetings($this->service_meeting_result, $template, $this->options['asm_language'], 1, false); - return; - } - $temp = array(); - foreach ($this->service_meeting_result as $value) { - $value = $this->enhance_meeting($value, $this->options['asm_language']); - if ($this->asm_test($value, false)) { - $temp[] = $value; - } - } - $this->service_meeting_result = $temp; - if (empty($temp)) { - return; - } - $data = ''; - $template = ''; - if (isset($this->options['asm_template_content']) && trim($this->options['asm_template_content'])) { - $template = $this->options['asm_template_content']; - } else { - $data .= ""; - } - foreach ($this->service_meeting_result as $value) { - $area_name = $this->get_area_name($value); - if ($template != '') { - $template = str_replace(" ", " ", $template); - $data .= $this->write_single_meeting( - $value, - $template, - $this->analyseTemplate($template), - $area_name - ); - continue; - } - $display_string = ''.$value['meeting_name'].''; - if (!strstr($value['comments'], 'Open Position')) { - $display_string .= ' - ' . $value['start_time'] . ''; - } - - if (trim($value['location_text'])) { - $display_string .= ' - '.trim($value['location_text']); - } - if (trim($value['location_street'])) { - $display_string .= ' - ' . trim($value['location_street']); - } - if (trim($value['location_city_subsection'])) { - $display_string .= ' ' . trim($value['location_city_subsection']); - } - if (trim($value['location_neighborhood'])) { - $display_string .= ' ' . trim($value['location_neighborhood']); - } - if (trim($value['location_municipality'])) { - $display_string .= ' '.trim($value['location_municipality']); - } - if (trim($value['location_province'])) { - //$display_string .= ' '.trim ( $value['location_province'] ); - } - if (trim($value['location_postal_code_1'])) { - $display_string .= ' ' . trim($value['location_postal_code_1']); - } - if (trim($value['location_info'])) { - $display_string .= " (".trim($value['location_info']).")"; - } - - if (isset($value['email_contact']) && $value['email_contact'] != '' && $this->options['include_meeting_email'] == 1) { - $str = $this->parse_field($value['email_contact']); - $value['email_contact'] = $str; - $value['email_contact'] = ' ('.$value['email_contact'].')'; - } else { - $value['email_contact'] = ''; - } - $display_string .= $value['email_contact']; - $data .= ""; - } - if ($template == '') { - $data .= "
    ".$display_string."
    "; - } - $this->writeHTML($data); - } - - /** - * @desc Adds the options sub-panel - */ - function admin_menu_link() - { - global $my_admin_page; - Bread::add_cap(); - $my_admin_page = add_menu_page('Meeting List', 'Meeting List', 'manage_bread', basename(__FILE__), array(&$this, 'admin_options_page'), 'dashicons-admin-page'); - } - - function bmltrootserverurl_meta_box() - { - global $connect; - ?> - - -

    BMLT Server Implementations

    - getMLOptions($this->requested_setting); - $this->lang = $this->get_bmlt_server_lang(); - ?> -
    -
    -
    -
    - - - -
    -

    Your current meeting list settings will be replaced and lost forever.

    -

    Consider backing up your settings by using the Configuration Tab.

    -
    -
    -
    - - - -
    -

    Your current meeting list settings will be replaced and lost forever.

    -

    Consider backing up your settings by using the Configuration Tab.

    -
    -
    -
    - - - -
    -

    Your current meeting list settings will be replaced and lost forever.

    -

    Consider backing up your settings by using the Configuration Tab.

    -
    -
    -
    - options['bread_version'] = sanitize_text_field($_POST['bread_version']); - $this->options['front_page_content'] = wp_kses_post($_POST['front_page_content']); - $this->options['last_page_content'] = wp_kses_post($_POST['last_page_content']); - $this->options['front_page_line_height'] = $_POST['front_page_line_height']; - $this->options['front_page_font_size'] = floatval($_POST['front_page_font_size']); - $this->options['last_page_font_size'] = floatval($_POST['last_page_font_size']); - $this->options['last_page_line_height'] = floatval($_POST['last_page_line_height']); - $this->options['content_font_size'] = floatval($_POST['content_font_size']); - $this->options['suppress_heading'] = floatval($_POST['suppress_heading']); - $this->options['header_font_size'] = floatval($_POST['header_font_size']); - $this->options['header_text_color'] = validate_hex_color($_POST['header_text_color']); - $this->options['header_background_color'] = validate_hex_color($_POST['header_background_color']); - $this->options['header_uppercase'] = intval($_POST['header_uppercase']); - $this->options['header_bold'] = intval($_POST['header_bold']); - $this->options['sub_header_shown'] = sanitize_text_field($_POST['sub_header_shown']); - $this->options['cont_header_shown'] = intval($_POST['cont_header_shown']); - $this->options['column_gap'] = isset($_POST['column_gap']) ? - intval($_POST['column_gap']) : 5; - $this->options['margin_right'] = intval($_POST['margin_right']); - $this->options['margin_left'] = intval($_POST['margin_left']); - $this->options['margin_bottom'] = intval($_POST['margin_bottom']); - $this->options['margin_top'] = intval($_POST['margin_top']); - $this->options['margin_header'] = intval($_POST['margin_header']); - $this->options['margin_footer'] = isset($_POST['margin_footer']) ? - intval($_POST['margin_footer']): 5; - $this->options['pageheader_fontsize'] = floatval($_POST['pageheader_fontsize']); - $this->options['pageheader_textcolor'] = validate_hex_color($_POST['pageheader_textcolor']); - $this->options['pageheader_backgroundcolor'] = validate_hex_color($_POST['pageheader_backgroundcolor']); - $this->options['pageheader_content'] = wp_kses_post($_POST['pageheader_content']); - $this->options['watermark'] = sanitize_text_field($_POST['watermark']); - $this->options['page_size'] = sanitize_text_field($_POST['page_size']); - $this->options['page_orientation'] = validate_page_orientation($_POST['page_orientation']); - $this->options['page_fold'] = sanitize_text_field($_POST['page_fold']); - $this->options['booklet_pages'] = isset($_POST['booklet_pages']) ? - boolval($_POST['booklet_pages']): false; - $this->options['meeting_sort'] = sanitize_text_field($_POST['meeting_sort']); - $this->options['main_grouping'] = sanitize_text_field($_POST['main_grouping']); - $this->options['subgrouping'] = sanitize_text_field($_POST['subgrouping']); - $this->options['borough_suffix'] = sanitize_text_field($_POST['borough_suffix']); - $this->options['county_suffix'] = sanitize_text_field($_POST['county_suffix']); - $this->options['neighborhood_suffix'] = sanitize_text_field($_POST['neighborhood_suffix']); - $this->options['city_suffix'] = sanitize_text_field($_POST['city_suffix']); - $this->options['meeting_template_content'] = wp_kses_post($_POST['meeting_template_content']); - $this->options['asm_template_content'] = wp_kses_post($_POST['asm_template_content']); - $this->options['column_line'] = isset($_POST['column_line']) ? - boolval($_POST['column_line']) : 0; - $this->options['col_color'] = isset($_POST['col_color']) ? - validate_hex_color($_POST['col_color']) : '#bfbfbf'; - $this->options['custom_section_content'] = wp_kses_post($_POST['custom_section_content']); - $this->options['custom_section_line_height'] = floatval($_POST['custom_section_line_height']); - $this->options['custom_section_font_size'] = floatval($_POST['custom_section_font_size']); - $this->options['pagenumbering_font_size'] = isset($_POST['pagenumbering_font_size']) ? - floatval($_POST['pagenumbering_font_size']) : '9'; - $this->options['used_format_1'] = sanitize_text_field($_POST['used_format_1']); - $this->options['include_meeting_email'] = isset($_POST['include_meeting_email']) ? boolval($_POST['include_meeting_email']) : false; - $this->options['recurse_service_bodies'] = isset($_POST['recurse_service_bodies']) ? 1 : 0; - $this->options['extra_meetings_enabled'] = isset($_POST['extra_meetings_enabled']) ? intval($_POST['extra_meetings_enabled']) : 0; - $this->options['include_protection'] = boolval($_POST['include_protection']); - $this->options['weekday_language'] = sanitize_text_field($_POST['weekday_language']); - $this->options['asm_language'] = sanitize_text_field($_POST['asm_language']); - $this->options['weekday_start'] = sanitize_text_field($_POST['weekday_start']); - $this->options['meeting1_footer'] = isset($_POST['meeting1_footer']) ? - sanitize_text_field($_POST['meeting1_footer']) : ''; - $this->options['meeting2_footer'] = isset($_POST['meeting2_footer']) ? - sanitize_text_field($_POST['meeting2_footer']) :''; - $this->options['nonmeeting_footer'] = isset($_POST['nonmeeting_footer']) ? - sanitize_text_field($_POST['nonmeeting_footer']):''; - $this->options['include_asm'] = boolval($_POST['include_asm']); - $this->options['asm_format_key'] = sanitize_text_field($_POST['asm_format_key']); - $this->options['asm_sort_order'] = sanitize_text_field($_POST['asm_sort_order']); - $this->options['bmlt_login_id'] = sanitize_text_field($_POST['bmlt_login_id']); - $this->options['bmlt_login_password'] = sanitize_text_field($_POST['bmlt_login_password']); - $this->options['base_font'] = sanitize_text_field($_POST['base_font']); - $this->options['colorspace'] = sanitize_text_field($_POST['colorspace']); - $this->options['wheelchair_size'] = sanitize_text_field($_POST['wheelchair_size']); - $this->options['protection_password'] = sanitize_text_field($_POST['protection_password']); - $this->options['time_clock'] = sanitize_text_field($_POST['time_clock']); - $this->options['time_option'] = intval($_POST['time_option']); - $this->options['remove_space'] = boolval($_POST['remove_space']); - $this->options['content_line_height'] = floatval($_POST['content_line_height']); - $this->options['root_server'] = validate_url($_POST['root_server']); - $this->options['service_body_1'] = sanitize_text_field($_POST['service_body_1']); - $this->options['service_body_2'] = sanitize_text_field($_POST['service_body_2']); - $this->options['service_body_3'] = sanitize_text_field($_POST['service_body_3']); - $this->options['service_body_4'] = sanitize_text_field($_POST['service_body_4']); - $this->options['service_body_5'] = sanitize_text_field($_POST['service_body_5']); - $this->options['cache_time'] = intval($_POST['cache_time']); - $this->options['custom_query'] = sanitize_text_field($_POST['custom_query']); - $this->options['asm_custom_query'] = sanitize_text_field($_POST['asm_custom_query']); - $this->options['user_agent'] = isset($_POST['user_agent']) ? sanitize_text_field($_POST['user_agent']) : 'None'; - $this->options['sslverify'] = isset($_POST['sslverify']) ? '1' : '0'; - $this->options['extra_meetings'] = array(); - if (isset($_POST['extra_meetings'])) { - foreach ($_POST['extra_meetings'] as $extra) { - $this->options['extra_meetings'][] = wp_kses_post($extra); - } - } - $authors = $_POST['authors_select']; - $this->options['authors'] = array(); - foreach ($authors as $author) { - $this->options['authors'][] = intval($author); - } - $user = wp_get_current_user(); - if (!in_array($user->ID, $this->options['authors'])) { - $this->options['authors'][] = $user->ID; - } - set_transient('admin_notice', 'Please put down your weapon. You have 20 seconds to comply.'); - if (!$this->current_user_can_modify()) { - echo '

    You do not have permission to save this configuation!

    '; - } else { - $this->save_admin_options(); - echo '

    Your changes were successfully saved!

    '; - $num = delete_transient($this->get_TransientKey()); - if ($num > 0) { - echo "

    $num Cache entries deleted

    "; - } - } - echo '
    '; - } elseif (isset($_REQUEST['pwsix_action']) && $_REQUEST['pwsix_action'] == "import_settings") { - echo '

    Your file was successfully imported!

    '; - $num = delete_transient($this->get_TransientKey()); - } elseif (isset($_REQUEST['pwsix_action']) && $_REQUEST['pwsix_action'] == "default_settings_success") { - echo '

    Your default settings were successfully updated!

    '; - $num = delete_transient($this->get_TransientKey()); - } - global $wpdb; - $query = "SELECT COUNT(*) FROM {$wpdb->posts} WHERE guid LIKE '%default_nalogo.jpg%'"; - if ($wpdb->get_var($query) == 0) { - $url = plugin_dir_url(__FILE__) . "includes/default_nalogo.jpg"; - media_sideload_image($url, 0); - } - $this->fillUnsetOptions(); - - $this->authors_safe = $this->options['authors']; - ?> - -
    -
    - -
    -
    - -
    - - - - testRootServer(); - $this_connected = is_array($serverInfo) && array_key_exists("version", $serverInfo[0]) ? $serverInfo[0]["version"] : ''; - $bmlt_version = $this_connected; - if ($serverInfo[0]["aggregator_mode_enabled"] ?? false) { - $ThisVersion = "
    Using Tomato Server
    "; - } else { - $this_version = intval(str_replace(".", "", $this_connected)); - $connect = "

    Connection to BMLT Server Failed. Check spelling or try again. If you are certain spelling is correct, BMLT Server could be down.

    "; - if ($this_connected) { - $ThisVersion = "
    Your BMLT Server is running ".$bmlt_version."
    "; - } - } - ?> -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    -
    - - options[$option]) || strlen(trim($this->options[$option])) == 0) { - $this->options[$option] = $default; - } - } - function fillUnsetStringOption($option, $default) - { - if (!isset($this->options[$option])) { - $this->options[$option] = $default; - } - } - function fillUnsetArrayOption($option, $default) - { - if (!isset($this->options[$option])) { - $this->options[$option] = $default; - } else if (!is_array($this->options[$option])) { - if (is_string($this->options[$option]) && strlen(trim($this->options[$option])) > 0) { - $this->options[$option] = [ trim($this->options[$option]) ]; - } else { - $this->options[$option] = $default; - } - } - } - function fillUnsetOptions() - { - $this->fillUnsetOption('front_page_line_height', '1.0'); - $this->fillUnsetOption('front_page_font_size', '10'); - $this->fillUnsetOption('last_page_font_size', '10'); - $this->fillUnsetOption('content_font_size', '9'); - $this->fillUnsetOption('header_font_size', $this->options['content_font_size']); - $this->fillUnsetOption('pageheader_fontsize', $this->options['header_font_size']); - if (floatval($this->options['pageheader_fontsize']) < 4) { - $this->options['pageheader_fontsize'] = 6; - } - $this->fillUnsetOption('suppress_heading', 0); - $this->fillUnsetOption('header_text_color', '#ffffff'); - $this->fillUnsetOption('header_background_color', '#000000'); - $this->fillUnsetOption('pageheader_textcolor', '#000000'); - $this->fillUnsetOption('pageheader_backgroundcolor', '#ffffff'); - $this->fillUnsetOption('header_uppercase', '0'); - $this->fillUnsetOption('header_bold', '1'); - $this->fillUnsetOption('sub_header_shown', 'none'); - $this->fillUnsetOption('margin_top', 3); - $this->fillUnsetOption('margin_bottom', 3); - $this->fillUnsetOption('margin_left', 3); - $this->fillUnsetOption('margin_right', 3); - $this->fillUnsetOption('column_gap', "5"); - $this->fillUnsetOption('content_line_height', '1.0'); - $this->fillUnsetOption('last_page_line_height', '1.0'); - $this->fillUnsetOption('page_size', 'legal'); - $this->fillUnsetOption('page_orientation', 'L'); - $this->fillUnsetOption('page_fold', 'quad'); - $this->fillUnsetOption('meeting_sort', 'day'); - $this->fillUnsetStringOption('booklet_pages', false); - $this->fillUnsetStringOption('borough_suffix', 'Borough'); - $this->fillUnsetStringOption('county_suffix', 'County'); - $this->fillUnsetStringOption('neighborhood_suffix', 'Neighborhood'); - $this->fillUnsetStringOption('city_suffix', 'City'); - $this->fillUnsetStringOption('meeting_template_content', ''); - $this->fillUnsetStringOption('asm_template_content', ''); - $this->fillUnsetOption('column_line', 0); - $this->fillUnsetOption('col_color', '#bfbfbf'); - $this->fillUnsetStringOption('custom_section_content', ''); - $this->fillUnsetOption('custom_section_line_height', '1'); - $this->fillUnsetOption('custom_section_font_size', '9'); - $this->fillUnsetOption('pagenumbering_font_size', '9'); - $this->fillUnsetStringOption('used_format_1', ''); - $this->fillUnsetOption('include_meeting_email', 0); - $this->fillUnsetOption('base_font', 'dejavusanscondensed'); - $this->fillUnsetOption('colorspace', 0); - $this->fillUnsetOption('recurse_service_bodies', 1); - $this->fillUnsetOption('extra_meetings_enabled', 0); - $this->fillUnsetOption('include_protection', 0); - $this->fillUnsetOption('weekday_language', 'en'); - $this->fillUnsetStringOption('asm_language', ''); // same as main language - $this->fillUnsetOption('weekday_start', '1'); - $this->fillUnsetOption('include_asm', '0'); - $this->fillUnsetOption('asm_format_key', ''); - $this->fillUnsetOption('asm_sort_order', 'name'); - $this->fillUnsetStringOption('bmlt_login_id', ''); - $this->fillUnsetStringOption('bmlt_login_password', ''); - $this->fillUnsetStringOption('protection_password', ''); - $this->fillUnsetStringOption('custom_query', ''); - $this->fillUnsetStringOption('asm_custom_query', ''); - $this->fillUnsetStringOption('user_agent', 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) +bread'); - $this->fillUnsetOption('sslverify', '0'); - $this->fillUnsetOption('cache_time', 0); - $this->fillUnsetOption('wheelchair_size', "20px"); - $this->fillUnsetArrayOption('extra_meetings', []); - if (!isset($this->options['extra_meetings'])) { - if (count($this->options['extra_meetings'])>0) { - $this->options['extra_meetings_enabled'] = 1; - } else { - $this->options['extra_meetings_enabled'] = 0; - } - } - $this->fillUnsetArrayOption('authors', []); - $my_footer = isset($this->translate[$this->options['weekday_language']]) ? - $this->translate[$this->options['weekday_language']]['PAGE'].' {PAGENO}' : '{PAGENO}'; - $this->fillUnsetStringOption('nonmeeting_footer', $my_footer); - $this->fillUnsetStringOption('meeting1_footer', $this->options['nonmeeting_footer']); - $this->fillUnsetStringOption('meeting2_footer', $this->options['nonmeeting_footer']); - } - function get_TransientPrefix() - { - return '_bread'; - } - function get_TransientKey() - { - return $this->get_TransientPrefix().'__'.$this->loaded_setting; - } - function pwsix_process_settings_admin() - { - $this->getMLOptions($this->requested_setting); - if (isset($_POST['bmltmeetinglistsave']) && $_POST['bmltmeetinglistsave'] == 'Save Changes') { - return; - } - if (empty($_POST['pwsix_action']) || 'settings_admin' != $_POST['pwsix_action']) { - return; - } - if (! wp_verify_nonce($_POST['pwsix_settings_admin_nonce'], 'pwsix_settings_admin_nonce')) { - return; - } - if (isset($_POST['delete'])) { - if (!$this->current_user_can_modify()) { - return; - } - if ($this->loaded_setting == 1) { - return; - } - unset($this->allSettings[$this->loaded_setting]); - update_option(Bread::SETTINGS, $this->allSettings); - $this->getMLOptions(1); - $this->loaded_setting = 1; - $this->requested_setting = 1; - } elseif (isset($_POST['duplicate'])) { - if (!$this->current_user_can_create()) { - return; - } - $id = $this->maxSetting + 1; - $this->optionsName = $this->generateOptionName($id); - $this->authors_safe = array(); - $this->options['authors'] = array(); - $this->save_admin_options(); - $this->allSettings[$id] = 'Setting '.$id; - update_option(Bread::SETTINGS, $this->allSettings); - $this->maxSetting = $id; - $this->getMLOptions($id); - } - } - function pwsix_process_rename_settings() - { - $this->getMLOptions($this->requested_setting); - if (isset($_POST['bmltmeetinglistsave']) && $_POST['bmltmeetinglistsave'] == 'Save Changes') { - return; - } - if (empty($_POST['pwsix_action']) || 'rename_setting' != $_POST['pwsix_action']) { - return; - } - if (! wp_verify_nonce($_POST['pwsix_rename_nonce'], 'pwsix_rename_nonce')) { - return; - } - if (! $this->current_user_can_modify()) { - return; - } - - $this->allSettings[$this->loaded_setting] = sanitize_text_field($_POST['setting_descr']); - update_option(Bread::SETTINGS, $this->allSettings); - } - /** - * Process a settings export that generates a .json file of the shop settings - */ - function pwsix_process_settings_export() - { - $this->getMLOptions($this->requested_setting); - if (isset($_POST['bmltmeetinglistsave']) && $_POST['bmltmeetinglistsave'] == 'Save Changes') { - return; - } - if (empty($_REQUEST['pwsix_action']) || 'export_settings' != $_REQUEST['pwsix_action']) { - return; - } - if (! wp_verify_nonce($_POST['pwsix_export_nonce'], 'pwsix_export_nonce')) { - return; - } - if (! current_user_can('manage_bread')) { // TODO: Is this necessary? Why not let the user make a copy - return; - } - - $blogname = str_replace(" - ", " ", get_option('blogname').'-'.$this->allSettings[$this->loaded_setting]); - $blogname = str_replace(" ", "-", $blogname); - $date = date("m-d-Y"); - $blogname = trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($blogname)), '-'); - $json_name = $blogname.$date.".json"; // Naming the filename will be generated. - $settings = get_option($this->optionsName); - foreach ($settings as $key => $value) { - $value = maybe_unserialize($value); - $need_options[$key] = $value; - } - $json_file = json_encode($need_options); // Encode data into json data - ignore_user_abort(true); - header('Content-Type: application/json; charset=utf-8'); - header("Content-Disposition: attachment; filename=$json_name"); - header("Expires: 0"); - echo json_encode($settings); - exit; - } - function current_user_can_modify() - { - if (! current_user_can('manage_bread')) { - return false; - } - $user = wp_get_current_user(); - if (in_array('administrator', $user->roles)) { - return true; - } - if (!is_array($this->authors_safe) || empty($this->authors_safe)) { - return true; - } - if (in_array($user->ID, $this->authors_safe)) { - return true; - } - return false; - } - function current_user_can_create() - { - if (! current_user_can('manage_bread')) { - return false; - } - return true; - } - /** - * Process a settings import from a json file - */ - function pwsix_process_settings_import() - { - $this->getMLOptions($this->requested_setting); - if (isset($_POST['bmltmeetinglistsave']) && $_POST['bmltmeetinglistsave'] == 'Save Changes') { - return; - } - if (empty($_REQUEST['pwsix_action']) || 'import_settings' != $_REQUEST['pwsix_action']) { - return; - } - if (empty($_REQUEST['pwsix_import_nonce']) || !wp_verify_nonce($_REQUEST['pwsix_import_nonce'], 'pwsix_import_nonce')) { - return; - } - if (! current_user_can('manage_bread')) { - return; - } - $file_name = $_FILES['import_file']['name']; - $tmp = explode('.', $file_name); - $extension = end($tmp); - if ($extension != 'json') { - wp_die(__('Please upload a valid .json file')); - } - $import_file = $_FILES['import_file']['tmp_name']; - if (empty($import_file)) { - wp_die(__('Please upload a file to import')); - } - $file_size = $_FILES['import_file']['size']; - if ($file_size > 500000) { - wp_die(__('File size greater than 500k')); - } - $encode_options = file_get_contents($import_file); - while (0 === strpos(bin2hex($encode_options), 'efbbbf')) { - $encode_options = substr($encode_options, 3); - } - $settings = json_decode($encode_options, true); - $settings['authors'] = $this->authors_safe; - update_option($this->optionsName, $settings); - setcookie('pwsix_action', "import_settings", time()+10); - setcookie('current-meeting-list', $this->loaded_setting, time()+10); - wp_safe_redirect(admin_url('?page=bmlt-meeting-list.php')); - } +/** + * Currently plugin version. + * 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.8.0'); - /** - * Process a default settings - */ - function pwsix_process_default_settings() - { - $this->getMLOptions($this->requested_setting); - if (! current_user_can('manage_bread') || - (isset($_POST['bmltmeetinglistsave']) && $_POST['bmltmeetinglistsave'] == 'Save Changes' )) { - return; - } elseif (isset($_REQUEST['pwsix_action']) && 'three_column_default_settings' == $_REQUEST['pwsix_action']) { - if (! wp_verify_nonce($_POST['pwsix_submit_three_column'], 'pwsix_submit_three_column')) { - die('Whoops! There was a problem with the data you posted. Please go back and try again.'); - } - $import_file = plugin_dir_path(__FILE__) . "includes/three_column_settings.json"; - } elseif (isset($_REQUEST['pwsix_action']) && 'four_column_default_settings' == $_REQUEST['pwsix_action']) { - if (! wp_verify_nonce($_POST['pwsix_submit_four_column'], 'pwsix_submit_four_column')) { - die('Whoops! There was a problem with the data you posted. Please go back and try again.'); - } - $import_file = plugin_dir_path(__FILE__) . "includes/four_column_settings.json"; - } elseif (isset($_REQUEST['pwsix_action']) && 'booklet_default_settings' == $_REQUEST['pwsix_action']) { - if (! wp_verify_nonce($_POST['pwsix_submit_booklet'], 'pwsix_submit_booklet')) { - die('Whoops! There was a problem with the data you posted. Please go back and try again.'); - } - $import_file = plugin_dir_path(__FILE__) . "includes/booklet_settings.json"; - } else { - return; - } - if (empty($import_file)) { - wp_die(__('Error importing default settings file')); - } - $encode_options = file_get_contents($import_file); - $settings = json_decode($encode_options, true); - $settings['authors'] = $this->authors_safe; - update_option($this->optionsName, $settings); - setcookie('pwsix_action', "default_settings_success", time()+10); - setcookie('current-meeting-list', $this->loaded_setting, time()+10); - wp_safe_redirect(admin_url('?page=bmlt-meeting-list.php')); - } +/** + * The code that runs during plugin activation. + * This action is documented in includes/class-bread-activator.php + */ +function activate_bread() +{ + include_once plugin_dir_path(__FILE__) . 'includes/class-bread-activator.php'; + Bread_Activator::activate(); +} - /** - * @desc Adds the Settings link to the plugin activate/deactivate page - */ - function filter_plugin_actions($links, $file) - { - $settings_link = '' . __('Settings') . ''; - array_unshift($links, $settings_link); // before other links - return $links; - } +/** + * The code that runs during plugin deactivation. + * This action is documented in includes/class-bread-deactivator.php + */ +function deactivate_bread() +{ + include_once plugin_dir_path(__FILE__) . 'includes/class-bread-deactivator.php'; + Bread_Deactivator::deactivate(); +} - /** - * Retrieves the plugin options from the database. - * @return array - */ - function getMLOptions($current_setting) - { - if ($current_setting < 1 and is_admin()) { - $current_setting = 1; - } - if ($current_setting != 1) { - $this->optionsName = $this->generateOptionName($current_setting); - } else { - $this->optionsName = Bread::OPTIONS_NAME; - } - //Don't forget to set up the default options - if (!$theOptions = get_option($this->optionsName)) { - if ($current_setting != 1) { - unset($this->allSettings[$current_setting]); - update_option(Bread::SETTINGS, $this->allSettings); - die('Undefined setting: '. $current_setting); - } - $import_file = plugin_dir_path(__FILE__) . "includes/three_column_settings.json"; - $encode_options = file_get_contents($import_file); - $theOptions = json_decode($encode_options, true); - update_option($this->optionsName, $theOptions); - } - $this->options = $theOptions; - $this->fillUnsetOptions(); - $this->upgrade_settings(); - $this->authors_safe = isset($theOptions['authors']) ? $theOptions['authors'] : array(); - $this->loaded_setting = $current_setting; - } +register_activation_hook(__FILE__, 'activate_bread'); +register_deactivation_hook(__FILE__, 'deactivate_bread'); - private function generateOptionName($current_setting) - { - return Bread::OPTIONS_NAME . '_' . $current_setting; - } - private function addServiceBody($service_body_name) - { - if (false === ( $this->options[$service_body_name] == 'Not Used' )) { - $area_data = explode(',', $this->options[$service_body_name]); - $area = $this->arraySafeGet($area_data); - $this->options[$service_body_name] = ($area == 'NOT USED' ? '' : $area); - $service_body_id = $this->arraySafeGet($area_data, 1); - if ($this->options['recurse_service_bodies'] == 1) { - return '&recursive=1&services[]=' . $service_body_id; - } else { - return '&services[]='.$service_body_id; - } - } - } - /** - * Saves the admin options to the database. - */ - function save_admin_options() - { - update_option($this->optionsName, $this->options); - return; - } - 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']; - } - } //End Class bread -} // end if -//instantiate the class -if (class_exists("Bread")) { - $BMLTMeetinglist_instance = new Bread(); +/** + * The core plugin class that is used to define internationalization, + * admin-specific hooks, and public-facing site hooks. + */ +require plugin_dir_path(__FILE__) . 'includes/class-bread.php'; +require plugin_dir_path(__FILE__) . 'includes/class-bread-bmlt.php'; +/** + * Begins execution of the plugin. + * + * Since everything within the plugin is registered via hooks, + * then kicking off the plugin from this point in the file does + * not affect the page life cycle. + * + * @since 2.8.0 + */ +function run_bread() +{ + + $plugin = new Bread(); + $plugin->run(); } +run_bread(); diff --git a/bread.php b/bread.php index 299ca7b..6fef63f 100644 --- a/bread.php +++ b/bread.php @@ -1 +1 @@ - \ No newline at end of file +=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.3.1", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.4.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T06:21:38+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "1dc0fedac703199e8704de085e47dd46bac0dde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1dc0fedac703199e8704de085e47dd46bac0dde4", + "reference": "1dc0fedac703199e8704de085e47dd46bac0dde4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-invoker": "^5.0", + "phpunit/php-text-template": "^4.0", + "phpunit/php-timer": "^7.0", + "sebastian/cli-parser": "^3.0", + "sebastian/code-unit": "^3.0", + "sebastian/comparator": "^6.0", + "sebastian/diff": "^6.0", + "sebastian/environment": "^7.0", + "sebastian/exporter": "^6.1.2", + "sebastian/global-state": "^7.0", + "sebastian/object-enumerator": "^6.0", + "sebastian/type": "^5.0", + "sebastian/version": "^5.0" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.2-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.6" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-07-03T05:51:49+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:44:28+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-31T05:30:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:54:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:56:19+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-17T13:12:04+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/aac1f6f347a5c5ac6bc98ad395007df00990f480", - "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -611,7 +2133,57 @@ "type": "open_collective" } ], - "time": "2024-04-23T20:25:34+00:00" + "time": "2024-09-18T10:38:58+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], diff --git a/includes/booklet_settings.json b/includes/booklet_settings.json deleted file mode 100644 index f21adfb..0000000 --- a/includes/booklet_settings.json +++ /dev/null @@ -1 +0,0 @@ -{"root_server":"","service_body_1":"","service_body_2":"Not Used","service_body_3":"Not Used","header_font_size":9.5,"area_service_meetings":null,"helplines":null,"helplines_line_height":null,"front_page_content":"

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

     <\/p>\r\n

     <\/p>\r\n

    [service_body_1]<\/span><\/strong><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    \"NALogo\"<\/a>\u00a0<\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    \u00a0<\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    [date]<\/strong><\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    To The Newcomer\u00a0<\/strong><\/span><\/p>\r\n

    This meeting list is dedicated to you and is produced to help you find meetings throughout the\u00a0<\/span><\/span>[service_body_1]. The information in this book was as accurate as possible at the time of printing. Sometimes groups have to move or change their time. If you go to a listed meeting and it is not there, go to another meeting. If you can, please call our office at: (555) 555-5555, so that we can correct our meeting list for future printings. At Narcotics Anonymous, YOU are the most important person.<\/span>\u00a0<\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    We Do Recover\u00a0<\/strong><\/span><\/p>\r\n

    \u00a0\u201cWhen at the end of the road we find that we can no longer function as a human being, either with or without drugs, we all face the same dilemma. What is there to do? There seems to be this alternative: Either to go on as best we can to the bitter ends - Jails, Institution, or Death - or find a new way to live. In years gone by, very few addicts had this last choice. Those who are addicted today are more fortunate. For the first time in man\\'s entire history, a simple way has been proving itself in the lives of many addicts. It is a simple spiritual, (not religious) program known as NARCOTICS ANONYMOUS.\u201d<\/span><\/p>\r\n

    [page_break][start_page_numbers]<\/p>\r\n\r\n\r\n\r\n
    THE TWELVE STEPS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \r\n

    1.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We admitted that we were powerless over our addiction, that our lives had become unmanageable.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    2.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We came to believe that a Power greater than ourselves could restore us to sanity.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    3.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We made a decision to turn our will and our lives over to the care of God as we understood Him.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    4.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We made a searching and fearless moral inventory of ourselves.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    5.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We admitted to God, to ourselves, and to another human being the exact nature of our wrongs.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    6.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We were entirely ready to have God remove all these defects of character.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    7.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We humbly asked Him to remove our shortcomings.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    8.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We made a list of all persons we had harmed and became willing to make amends to them all.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    9.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We made direct amends to such people wherever possible, except when to do so would injure them or others.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    10.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We continued to take personal inventory and when we were wrong promptly admitted it.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    11.\u00a0<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    We sought through prayer and meditation to improve our conscious contact with God as we understood Him, praying only for knowledge of His will for us and the power to carry that out.\u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    12.\u00a0<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Having had a spiritual awakening as a result of these steps, we tried to carry this message to addicts, and to practice these principles in all our affairs. \u00a0<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    Meeting Format Legend<\/span><\/strong><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [format_codes_used_basic]<\/p>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    Helplines<\/span><\/strong><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \r\n

    Service Body 1<\/p>\r\n<\/td>\r\n

    Number 1<\/td>\r\n<\/tr>\r\n
    \r\n

    Service Body 2<\/p>\r\n<\/td>\r\n

    Number 2<\/td>\r\n<\/tr>\r\n
    \r\n

     <\/p>\r\n<\/td>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n<\/tr>\r\n

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

    [page_break]<\/p>","front_line_height":"1.4","area_service_meetings_line_height":null,"content_font_size":9,"content_line_height":1.2,"front_page_font_size":11.4,"service_body_4":"Not Used","service_body_5":"Not Used","page_layout":null,"last_page_content":"

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    THE TWELVE TRADITIONS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \r\n

    1.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Our common welfare should come first; personal recovery depends on NA unity.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    2.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    For our group purpose there is but one ultimate authority\u2014 a loving God as He may express Himself in our group conscience. Our leaders are but trusted servants; they do not govern.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    3.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    The only requirement for membership is a desire to stop using.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    4.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Each group should be autonomous except in matters affecting other groups or NA as a whole.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    5.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Each group has but one primary purpose\u2014to carry the message to the addict who still suffers.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    6.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    An NA group ought never endorse, finance, or lend the NA name to any related facility or outside enterprise, lest problems of money, property, or prestige divert us from our primary purpose.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    7.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Every NA group ought to be fully self-supporting, declining outside contributions.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    8.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Narcotics Anonymous should remain forever nonprofessional, but our service centers may employ special workers.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    9.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    NA, as such, ought never be organized, but we may create service boards or committees directly responsible to those they serve.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    10.<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Narcotics Anonymous has no opinion on outside issues; hence the NA name ought never be drawn into public controversy.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    11.\u00a0<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Our public relations policy is based on attraction rather than promotion; we need always maintain personal anonymity at the level of press, radio, and films.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    12.\u00a0<\/span><\/p>\r\n<\/td>\r\n

    \r\n

    Anonymity is the spiritual foundation of all our Traditions, ever reminding us to place principles before personalities.<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [page_break]<\/p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Name<\/span><\/strong><\/td>\r\nPhone Number<\/span><\/strong><\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n\u00a0<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>","front_page_line_height":"1.1","last_page_line_height":1.2,"time_option":1,"remove_space":true,"helplines_font_size":null,"format_codes":null,"format_codes_font_size":null,"format_codes_line_height":null,"page_size":"5inch","page_fold":"half","meeting_sort":"city","cache_time":0,"show_status":null,"use_custom_section":null,"custom_section_content":"","custom_section_line_height":1,"page_orientation":"L","area_service_meetings_font_size":null,"which_format_codes":null,"custom_section_font_size":9,"include_meeting_email":false,"bmlt_login_id":"","bmlt_login_password":"","last_page_font_size":12,"column_line":false,"used_format_1":"","column_gap":5,"margin_right":5,"margin_left":5,"margin_bottom":5,"margin_top":5,"meeting_template_content":"
    day_abbr<\/strong><\/span><\/span> time<\/strong><\/span><\/span> group<\/strong>, location, info, street, city, state, zip (formats)\u00A0comments<\/em><\/span><\/div>","col_color":"#bfbfbf","time_clock":"12","header_text_color":"#ffffff","header_background_color":"#000000","header_uppercase":1,"header_bold":1,"logo_copied":"done","weekday_language":"en","include_asm":false,"sub_header_shown":1,"borough_suffix":"Borough","county_suffix":"County","neighborhood_suffix":"Neighborhood","city_suffix":"City","pagenumbering_font_size":9,"recurse_service_bodies":1,"extra_meetings_enabled":0,"include_protection":false,"base_font":"dejavusanscondensed","protection_password":"","custom_query":"","extra_meetings":[]} \ No newline at end of file diff --git a/includes/class-bread-activator.php b/includes/class-bread-activator.php new file mode 100644 index 0000000..dc11ee6 --- /dev/null +++ b/includes/class-bread-activator.php @@ -0,0 +1,40 @@ + + */ +class Bread_Activator +{ + + /** + * Short Description. (use period) + * + * Long Description. + * + * @since 2.8.0 + */ + public static function activate() + { + $role = $GLOBALS['wp_roles']->role_objects['administrator']; + if (isset($role) && !$role->has_cap('manage_bread')) { + $role->add_cap('manage_bread'); + } + } +} diff --git a/includes/class-bread-bmlt.php b/includes/class-bread-bmlt.php new file mode 100644 index 0000000..b5f518f --- /dev/null +++ b/includes/class-bread-bmlt.php @@ -0,0 +1,277 @@ +bread = $bread; + } + + public function get_configured_root_server_request($url, $raw = false) + { + $results = $this->bread->bmlt()->get($this->bread->getOption('root_server') . "/" . $url); + if ($raw) { + return $results; + } + return json_decode(wp_remote_retrieve_body($results), true); + } + 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"); + } + /** + * Undocumented function + * + * @param string $url The BMLT call. + * @return WP_Error | array The result of the call. + */ + private function get(string $url): WP_Error | array + { + $args = array( + 'timeout' => '120', + ); + if ($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); + } + /** + * 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->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"); + + $unique_areas = $this->bread->bmlt()->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'] = date("g:iA", strtotime($value['start_time'])); + $all_meetings[$value['id_bigint']] = strip_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->bread->bmlt()->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->bread->bmlt()->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. + * + * @return array the service bodies. + */ + 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"); + $this->unique_areas = array(); + + foreach ($result as $value) { + $parent_name = 'Parent ID'; + foreach ($result as $parent) { + if ($value['parent_id'] == $parent['id']) { + $parent_name = $parent['name']; + } + } + if ($value['parent_id'] == '') { + $value['parent_id'] = '0'; + } + $this->unique_areas[] = $value['name'] . ',' . $value['id'] . ',' . $value['parent_id'] . ',' . $parent_name; + } + + return $this->unique_areas; + } + /** + * 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 + { + if ($this->bmlt_server_lang == '') { + $result = $this->bread->bmlt()->testRootServer(); + if (!($result && is_array($result) && is_array($result[0]))) { + return 'en'; + } + $this->bmlt_server_lang = ($result == null) ? 'en' : $result["0"]["nativeLang"]; + } + return $this->bmlt_server_lang; + } + /** + * Check if this is a valid BMLT server. + * + * @param $override_root_server + * @return array the results of GetServerInfo + */ + 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); + } else { + $results = $this->bread->bmlt()->get($override_root_server . "/client_interface/json/?switcher=GetServerInfo"); + } + if ($results instanceof WP_Error) { + $this->connection_error = $results->get_error_message(); + return false; + } + $httpcode = wp_remote_retrieve_response_code($results); + if ($httpcode != 200 && $httpcode != 302 && $httpcode != 304) { + $this->connection_error = "HTTP Return Code: " . $httpcode; + return false; + } + + 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->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetFormats"); + $this->bread->bmlt()->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->bread->bmlt()->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. + * + * @param array $array The array to be sorted. + * @param string $subkey The key to be sorted by. + * @param [type] $sortType SORT_ASC (default) or SORT_DESC + * @return void + */ + public function sortBySubkey(array &$array, string $subkey, int $sortType = SORT_ASC): void + { + if (empty($array)) { + return; + } + foreach ($array as $subarray) { + $keys[] = $subarray[$subkey]; + } + array_multisort($keys, $sortType, $array); + } + /** + * Generate that part of the BMLT query-string that reflects the service bodies being queried. + * + * @return string Something to paste into the URL + */ + public function generateDefaultQuery(): string + { + // addServiceBody has the side effect that + // the service body option is overridden, so that it contains + // only the name of the service body. + $services = $this->addServiceBody('service_body_1'); + $services .= $this->addServiceBody('service_body_2'); + $services .= $this->addServiceBody('service_body_3'); + $services .= $this->addServiceBody('service_body_4'); + $services .= $this->addServiceBody('service_body_5'); + return $services; + } + 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; + } + } + } + public function parse_field($text) + { + if ($text != '') { + $exploded = explode("#@-@#", $text); + $knt = count($exploded); + if ($knt > 1) { + $text = $exploded[$knt - 1]; + } + } + return $text; + } +} diff --git a/includes/class-bread-deactivator.php b/includes/class-bread-deactivator.php new file mode 100644 index 0000000..0941f5d --- /dev/null +++ b/includes/class-bread-deactivator.php @@ -0,0 +1,40 @@ + + */ +class Bread_Deactivator +{ + + /** + * Short Description. (use period) + * + * Long Description. + * + * @since 2.8.0 + */ + public static function deactivate() + { + $role = $GLOBALS['wp_roles']->role_objects['administrator']; + if (isset($role) && $role->has_cap('manage_bread')) { + $role->remove_cap('manage_bread'); + } + } +} diff --git a/includes/class-bread-i18n.php b/includes/class-bread-i18n.php new file mode 100644 index 0000000..514f96d --- /dev/null +++ b/includes/class-bread-i18n.php @@ -0,0 +1,45 @@ + + */ +class Bread_i18n +{ + + + /** + * Load the plugin text domain for translation. + * + * @since 2.8.0 + */ + public function load_plugin_textdomain() + { + + load_plugin_textdomain( + 'bread', + false, + dirname(dirname(plugin_basename(__FILE__))) . '/languages/' + ); + } +} diff --git a/includes/class-bread-loader.php b/includes/class-bread-loader.php new file mode 100644 index 0000000..e0f5f2a --- /dev/null +++ b/includes/class-bread-loader.php @@ -0,0 +1,132 @@ + + */ +class Bread_Loader +{ + + /** + * The array of actions registered with WordPress. + * + * @since 2.8.0 + * @access protected + * @var array $actions The actions registered with WordPress to fire when the plugin loads. + */ + protected $actions; + + /** + * The array of filters registered with WordPress. + * + * @since 2.8.0 + * @access protected + * @var array $filters The filters registered with WordPress to fire when the plugin loads. + */ + protected $filters; + + /** + * Initialize the collections used to maintain the actions and filters. + * + * @since 2.8.0 + */ + public function __construct() + { + + $this->actions = array(); + $this->filters = array(); + } + + /** + * Add a new action to the collection to be registered with WordPress. + * + * @since 2.8.0 + * @param string $hook The name of the WordPress action that is being registered. + * @param object $component A reference to the instance of the object on which the action is defined. + * @param string $callback The name of the function definition on the $component. + * @param int $priority Optional. The priority at which the function should be fired. Default is 10. + * @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1. + */ + public function add_action($hook, $component, $callback, $priority = 10, $accepted_args = 1) + { + $this->actions = $this->add($this->actions, $hook, $component, $callback, $priority, $accepted_args); + } + + /** + * Add a new filter to the collection to be registered with WordPress. + * + * @since 2.8.0 + * @param string $hook The name of the WordPress filter that is being registered. + * @param object $component A reference to the instance of the object on which the filter is defined. + * @param string $callback The name of the function definition on the $component. + * @param int $priority Optional. The priority at which the function should be fired. Default is 10. + * @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1 + */ + public function add_filter($hook, $component, $callback, $priority = 10, $accepted_args = 1) + { + $this->filters = $this->add($this->filters, $hook, $component, $callback, $priority, $accepted_args); + } + + /** + * A utility function that is used to register the actions and hooks into a single + * collection. + * + * @since 2.8.0 + * @access private + * @param array $hooks The collection of hooks that is being registered (that is, actions or filters). + * @param string $hook The name of the WordPress filter that is being registered. + * @param object $component A reference to the instance of the object on which the filter is defined. + * @param string $callback The name of the function definition on the $component. + * @param int $priority The priority at which the function should be fired. + * @param int $accepted_args The number of arguments that should be passed to the $callback. + * @return array The collection of actions and filters registered with WordPress. + */ + private function add($hooks, $hook, $component, $callback, $priority, $accepted_args) + { + + $hooks[] = array( + 'hook' => $hook, + 'component' => $component, + 'callback' => $callback, + 'priority' => $priority, + 'accepted_args' => $accepted_args + ); + + return $hooks; + } + + /** + * Register the filters and actions with WordPress. + * + * @since 2.8.0 + */ + public function run() + { + include_once plugin_dir_path(__FILE__).'class-bread-bmlt.php'; + + foreach ($this->filters as $hook) { + add_filter($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']); + } + + foreach ($this->actions as $hook) { + add_action($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']); + } + } +} diff --git a/includes/class-bread.php b/includes/class-bread.php new file mode 100644 index 0000000..fd6c499 --- /dev/null +++ b/includes/class-bread.php @@ -0,0 +1,785 @@ + + */ +class Bread +{ + + /** + * The loader that's responsible for maintaining and registering all hooks that power + * the plugin. + * + * @since 2.8.0 + * @access protected + * @var Bread_Loader $loader Maintains and registers all hooks for the plugin. + */ + protected $loader; + + /** + * The unique identifier of this plugin. + * + * @since 2.8.0 + * @access protected + * @var string $plugin_name The string used to uniquely identify this plugin. + */ + protected $plugin_name; + + /** + * The current version of the plugin. + * + * @since 2.8.0 + * @access protected + * @var string $version The current version of the plugin. + */ + protected $version; + public const SETTINGS = 'bmlt_meeting_list_settings'; + public const OPTIONS_NAME = 'bmlt_meeting_list_options'; + private $optionsName; + private $allSettings = array(); + private $maxSetting = 1; + /** + * The setting we are editing, generating or otherwise working with. Generally set with query string "?current-meeting-list". + * + * @var integer + */ + private int $requested_setting = 1; + private $protocol; + private string $tmp_dir; + /** + * The settings that detemine how the meeting list should be generated. + * + * @var array + */ + private array $options = array(); + /** + * Translations for things like weekday names. Two dimensions, first is language code, second is key value for text. + * + * @var array + */ + private array $translate = array(); + private bool $generating_meeting_list = false; + private bool $exporting_meeting_list = false; + /** + * The wizard wants to know if we are generating the first meeting list for this site. + * + * @var boolean + */ + private bool $initial_setting = false; + /** + * The helper class for accessing the BMLT root server. + * + * @var Bread_Bmlt + */ + private Bread_Bmlt $bread_bmlt; + public function bmlt(): Bread_Bmlt + { + return $this->bread_bmlt; + } + public function temp_dir(): string + { + return $this->tmp_dir; + } + public function getOption($name): mixed + { + if (!isset($this->options[$name])) { + return ''; + } + return $this->options[$name]; + } + public function emptyOption($name): bool + { + return empty($this->options[$name]); + } + public function getOptionForDisplay($option, $default = '') + { + return empty($this->options[$option]) ? $default : esc_html($this->options[$option]); + } + public function setOption($name, $value) + { + return $this->options[$name] = $value; + } + public function appendOption($name, $value) + { + return $this->options[$name][] = $value; + } + /** + * mPDF needs to have a temporary directory available. This is a problem for some hosting providers, so we need some + * logic to make sure it is available, and also to clean up after ourselves, to save disk space. + * + * @return string + */ + private static function setup_temp_dir(): string + { + $dir = get_temp_dir(); + $dir = rtrim($dir, DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { + return false; + } + Bread::brute_force_cleanup($dir); + $attempts = 0; + $path = ''; + do { + $path = sprintf('%s%s%s%s', $dir, DIRECTORY_SEPARATOR, 'bread', mt_rand(100000, mt_getrandmax())); + } while (!mkdir($path) && $attempts++ < 100); + return $path; + } + private static function brute_force_cleanup($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + if (str_starts_with($object, "bread")) { + $filename = $dir . DIRECTORY_SEPARATOR . $object; + if (time() - filemtime($filename . (is_dir($filename) ? '/.' : '')) > 24 * 3600) { + Bread::rrmdir($filename); + } + } + } + } + } + } + public function removeTempDir() + { + Bread::rrmdir($this->temp_dir()); + } + private static function rrmdir($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . "/" . $object)) { + Bread::rrmdir($dir . DIRECTORY_SEPARATOR . $object); + } else { + @unlink($dir . DIRECTORY_SEPARATOR . $object); + } + } + } + @rmdir($dir); + } + } + private function loadAllSettings($holder): int + { + if (isset($holder['bread_preview_settings'])) { + $this->allSettings = array(); + $this->allSettings[1] = $holder['bread_preview_settings']; + } else { + $this->allSettings = get_option(Bread::SETTINGS); + } + if ($this->allSettings === false) { + $this->allSettings = array(); + $this->allSettings[1] = array(); + $this->maxSetting = 1; + } else { + foreach ($this->allSettings as $key => $value) { + if ($key > $this->maxSetting) { + $this->maxSetting = $key; + } + } + } + return isset($holder['current-meeting-list']) ? intval($holder['current-meeting-list']) : 1; + } + /** + * Undocumented function + * + * @param [type] $id + * @param [type] $name + * @return void + */ + public function setAndSaveSetting($id, $name): void + { + $this->allSettings[$id] = $name; + update_option(Bread::SETTINGS, $this->allSettings); + } + public function getSettingName($id) + { + return $this->allSettings[$id]; + } + public function getSettingNames() + { + return $this->allSettings; + } + public function deleteSetting($id) + { + unset($this->allSettings[$id]); + update_option(Bread::SETTINGS, $this->allSettings); + } + /** + * Example the superglobals $_REQUEST, $_SESSION and $_COOKIE and try to determine the settings-id, putting the + * results in a container. + * + * @return array + */ + private function getCurrentMeetingListHolder(): array + { + $ret = array(); + if (isset(($_REQUEST['preview-meeting-list'])) && !is_admin()) { + session_start(); + $ret['bread_preview_settings'] = $_SESSION['bread_preview_settings']; + $ret['current-meeting-list'] = 1; + // TODO check that the value is equal to some security option we've set, otherwise reject. + } + if (isset($_REQUEST['current-meeting-list'])) { + $ret['current-meeting-list'] = $_REQUEST['current-meeting-list']; + } elseif (isset($_REQUEST['export-meeting-list'])) { + $ret['current-meeting-list'] = $_REQUEST['export-meeting-list']; + $this->exporting_meeting_list = true; + } elseif (isset($_COOKIE['current-meeting-list'])) { + $ret['current-meeting-list'] = $_COOKIE['current-meeting-list']; + } + $this->generating_meeting_list = !empty($ret) && !is_admin() && !$this->exporting_meeting_list; + return $ret; + } + /** + * Undocumented function + * + * @return void + */ + public function generatingMeetingList(): bool + { + return $this->generating_meeting_list; + } + public function exportingMeetingList(): bool + { + return $this->exporting_meeting_list; + } + public function generateOptionName($current_setting) + { + return Bread::OPTIONS_NAME . '_' . $current_setting; + } + /** + * Retrieves the plugin options from the database. + * + * @return array + */ + public function &getConfigurationForSettingId($current_setting) + { + if ($current_setting < 1) { + $current_setting = is_admin() ? 1 : $this->requested_setting; + } + if (is_array($this->allSettings[$current_setting])) { + $this->options = $this->allSettings[$current_setting]; + } else { + if ($current_setting != 1) { + $this->optionsName = $this->generateOptionName($current_setting); + } else { + $this->optionsName = Bread::OPTIONS_NAME; + } + //Don't forget to set up the default options + if (!$theOptions = get_option($this->optionsName)) { + if ($current_setting != 1) { + unset($this->allSettings[$current_setting]); + update_option(Bread::SETTINGS, $this->allSettings); + die('Undefined setting: ' . $current_setting); + } + $this->initial_setting = true; + $import_file = plugin_dir_path(__FILE__) . "../admin/templates/30/trifold-landscape-largefont.json"; + $encode_options = file_get_contents($import_file); + $theOptions = json_decode($encode_options, true); + update_option($this->optionsName, $theOptions); + } + $this->setOptions($theOptions); + } + + $this->requested_setting = $current_setting; + return $this->options; + } + public function getOptions() + { + return $this->options; + } + public function setOptions(array $theOptions) + { + $this->options = $theOptions; + $this->fillUnsetOptions(); + $this->upgrade_settings(); + } + public function getOptionsName() + { + return $this->optionsName; + } + public function setOptionsName($name) + { + return $this->optionsName = $name; + } + public function getRequestedSetting() + { + return $this->requested_setting; + } + public function setRequestedSetting($id) + { + $this->requested_setting = $id; + } + /** + * Define the core functionality of the plugin. + * + * Set the plugin name and the plugin version that can be used throughout the plugin. + * Load the dependencies, define the locale, and set the hooks for the admin area and + * the public-facing side of the site. + * + * @since 2.8.0 + */ + public function __construct() + { + if (defined('BREAD_VERSION')) { + $this->version = BREAD_VERSION; + } else { + $this->version = '2.8.0'; + } + $this->plugin_name = 'bread'; + $this->tmp_dir = $this->setup_temp_dir(); + $this->protocol = (strpos(strtolower(home_url()), "https") !== false ? "https" : "http") . "://"; + + $holder = $this->getCurrentMeetingListHolder(); + $this->requested_setting = $this->loadAllSettings($holder); + $this->bread_bmlt = new Bread_Bmlt($this); + + $this->load_dependencies(); + $this->set_locale(); + $this->load_translations(); + $this->define_admin_hooks(); + $this->define_public_hooks(); + } + function load_translations() + { + $files = scandir(dirname(__FILE__) . "/lang"); + foreach ($files as $file) { + if (strpos($file, "translate_") !== 0) { + continue; + } + include dirname(__FILE__) . "/lang/" . $file; + $key = substr($file, 10, -4); + $this->translate[$key] = $translate; + } + } + public function getTranslateTable() + { + return $this->translate; + } + public function getProtocol() + { + return $this->protocol; + } + /** + * Did any settings exist before? + * + * @return bool true when no setting existed previously, ie, if we should create setting 1. + */ + public function getInitialSetting() + { + return $this->initial_setting; + } + /** + * Load the required dependencies for this plugin. + * + * Include the following files that make up the plugin: + * + * - Bread_Loader. Orchestrates the hooks of the plugin. + * - Bread_i18n. Defines internationalization functionality. + * - Bread_Admin. Defines all hooks for the admin area. + * - Bread_Public. Defines all hooks for the public side of the site. + * + * Create an instance of the loader which will be used to register the hooks + * with WordPress. + * + * @since 2.8.0 + * @access private + */ + private function load_dependencies() + { + + /** + * The class responsible for orchestrating the actions and filters of the + * core plugin. + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-bread-loader.php'; + + /** + * The class responsible for defining internationalization functionality + * of the plugin. + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-bread-i18n.php'; + + /** + * The class responsible for defining all actions that occur in the admin area. + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-bread-admin.php'; + + /** + * The class that creates the main menu item in the admin area, shared between bread and crouton. + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-bmltenabled-admin.php'; + + /** + * The class responsible for defining all actions that occur in the public-facing + * side of the site. + */ + include_once plugin_dir_path(dirname(__FILE__)) . 'public/class-bread-public.php'; + + $this->loader = new Bread_Loader(); + } + + /** + * Define the locale for this plugin for internationalization. + * + * Uses the Bread_i18n class in order to set the domain and to register the hook + * with WordPress. + * + * @since 2.8.0 + * @access private + */ + private function set_locale() + { + + $plugin_i18n = new Bread_i18n(); + + $this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain'); + } + /** + * Register all of the hooks related to the admin area functionality + * of the plugin. + * + * @since 2.8.0 + * @access private + */ + private function define_admin_hooks() + { + $bmltenabled_admin = new BmltEnabled_Admin(); + $plugin_admin = new Bread_Admin($this->get_plugin_name(), $this->get_version(), $bmltenabled_admin, $this); + + $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles'); + $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts'); + + $this->loader->add_action("admin_menu", $bmltenabled_admin, "admin_menu_link"); + $this->loader->add_action("BmltEnabled_Submenu", $plugin_admin, "admin_submenu_link"); + $this->loader->add_filter("BmltEnabled_Slugs", $plugin_admin, "submenu_slug"); + $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'); + + // This is a public function. nameetinglists.org wants to let people see each others settings. + // It's OK now, since there is no more login stuff. + $this->loader->add_action('plugins_loaded', $plugin_admin, 'download_settings'); + } + + /** + * Register all of the hooks related to the public-facing functionality + * of the plugin. + * + * @since 2.8.0 + * @access private + */ + private function define_public_hooks() + { + + $plugin_public = new Bread_Public($this->get_plugin_name(), $this->get_version(), $this); + + $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'); + } + + /** + * Run the loader to execute all of the hooks with WordPress. + * + * @since 2.8.0 + */ + public function run() + { + $this->loader->run(); + } + + /** + * The name of the plugin used to uniquely identify it within the context of + * WordPress and to define internationalization functionality. + * + * @since 2.8.0 + * @return string The name of the plugin. + */ + public function get_plugin_name() + { + return $this->plugin_name; + } + + /** + * The reference to the class that orchestrates the hooks with the plugin. + * + * @since 2.8.0 + * @return Bread_Loader Orchestrates the hooks of the plugin. + */ + public function get_loader() + { + return $this->loader; + } + + /** + * Retrieve the version number of the plugin. + * + * @since 2.8.0 + * @return string The version number of the plugin. + */ + public function get_version() + { + return $this->version; + } + /** + * Return a blank if the key is unset + * + * @param array $arr + * @param string $key + * @return string + */ + public static function arraySafeGet(array $arr, string $key = "0"): string + { + return $arr[$key] ?? ''; + } + public function getday($day, $abbreviate = false, $language = 'en') + { + $key = "WEEKDAYS"; + if ($abbreviate) { + $key = "WKDYS"; + } + return $this->translate[$language][$key][$day]; + } + private function fillUnsetOption($option, $default) + { + if (!isset($this->options[$option]) || strlen(trim($this->options[$option])) == 0) { + $this->options[$option] = $default; + } + } + private function fillUnsetStringOption($option, $default) + { + if (!isset($this->options[$option])) { + $this->options[$option] = $default; + } + } + private function fillUnsetArrayOption($option, $default) + { + if (!isset($this->options[$option])) { + $this->options[$option] = $default; + } else if (!is_array($this->options[$option])) { + if (is_string($this->options[$option]) && strlen(trim($this->options[$option])) > 0) { + $this->options[$option] = [trim($this->options[$option])]; + } else { + $this->options[$option] = $default; + } + } + } + /** + * Make sure the data from frontend or from DB is usuable. This might involve upgrading data from earlier versions of Bread. + * + * @return void + */ + public function fillUnsetOptions() + { + $this->fillUnsetOptionsInner(); + $this->removeDeprecated(); + } + private function removeDeprecated() + { + unset($this->options['include_meeting_email']); + unset($this->options['bmlt_login_id']); + unset($this->options['bmlt_login_password']); + } + private function fillUnsetOptionsInner() + { + $this->fillUnsetOption('front_page_line_height', '1.0'); + $this->fillUnsetOption('front_page_font_size', '10'); + $this->fillUnsetOption('content_font_size', '9'); + $this->fillUnsetOption('header_font_size', $this->options['content_font_size']); + $this->fillUnsetOption('pageheader_fontsize', $this->options['header_font_size']); + if (floatval($this->options['pageheader_fontsize']) < 4) { + $this->options['pageheader_fontsize'] = 6; + } + $this->fillUnsetOption('suppress_heading', 0); + $this->fillUnsetOption('header_text_color', '#ffffff'); + $this->fillUnsetOption('header_background_color', '#000000'); + $this->fillUnsetOption('pageheader_textcolor', '#000000'); + $this->fillUnsetOption('pageheader_backgroundcolor', '#ffffff'); + $this->fillUnsetOption('header_uppercase', '0'); + $this->fillUnsetOption('header_bold', '1'); + $this->fillUnsetOption('sub_header_shown', 'none'); + $this->fillUnsetOption('margin_top', 3); + $this->fillUnsetOption('margin_bottom', 3); + $this->fillUnsetOption('margin_left', 3); + $this->fillUnsetOption('margin_right', 3); + $this->fillUnsetOption('column_gap', "5"); + $this->fillUnsetOption('content_line_height', '1.0'); + $this->fillUnsetOption('page_size', 'legal'); + $this->fillUnsetOption('page_orientation', 'L'); + $this->fillUnsetOption('page_fold', 'quad'); + $this->fillUnsetOption('meeting_sort', 'day'); + $this->fillUnsetStringOption('booklet_pages', false); + $this->fillUnsetStringOption('borough_suffix', 'Borough'); + $this->fillUnsetStringOption('county_suffix', 'County'); + $this->fillUnsetStringOption('neighborhood_suffix', 'Neighborhood'); + $this->fillUnsetStringOption('city_suffix', 'City'); + $this->fillUnsetStringOption('meeting_template_content', ''); + $this->fillUnsetStringOption('additional_list_template_content', ''); + $this->fillUnsetOption('column_line', 0); + $this->fillUnsetOption('col_color', '#bfbfbf'); + $this->fillUnsetStringOption('custom_section_content', ''); + $this->fillUnsetOption('custom_section_line_height', '1'); + $this->fillUnsetOption('custom_section_font_size', '9'); + $this->fillUnsetOption('pagenumbering_font_size', '9'); + $this->fillUnsetStringOption('used_format_1', ''); + $this->fillUnsetOption('base_font', 'dejavusanscondensed'); + $this->fillUnsetOption('colorspace', 0); + $this->fillUnsetOption('recurse_service_bodies', 1); + $this->fillUnsetOption('extra_meetings_enabled', 0); + $this->fillUnsetOption('include_protection', 0); + $this->fillUnsetOption('weekday_language', 'en'); + $this->fillUnsetStringOption('additional_list_language', ''); // same as main language + $this->fillUnsetOption('weekday_start', '1'); + $this->fillUnsetOption('include_additional_list', '0'); + $this->fillUnsetOption('additional_list_format_key', ''); + $this->fillUnsetOption('additional_list_sort_order', 'name'); + $this->fillUnsetStringOption('protection_password', ''); + $this->fillUnsetStringOption('custom_query', ''); + $this->fillUnsetStringOption('additional_list_custom_query', ''); + $this->fillUnsetStringOption('user_agent', 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) +bread'); + $this->fillUnsetOption('sslverify', '0'); + $this->fillUnsetOption('cache_time', 0); + $this->fillUnsetOption('wheelchair_size', "20px"); + $this->fillUnsetArrayOption('extra_meetings', []); + if (!isset($this->options['extra_meetings'])) { + if (count($this->options['extra_meetings']) > 0) { + $this->options['extra_meetings_enabled'] = 1; + } else { + $this->options['extra_meetings_enabled'] = 0; + } + } + $this->fillUnsetArrayOption('authors', []); + $my_footer = isset($this->translate[$this->options['weekday_language']]) ? + $this->translate[$this->options['weekday_language']]['PAGE'] . ' {PAGENO}' : '{PAGENO}'; + $this->fillUnsetStringOption('nonmeeting_footer', $my_footer); + $this->fillUnsetStringOption('meeting1_footer', $this->options['nonmeeting_footer']); + $this->fillUnsetStringOption('meeting2_footer', $this->options['nonmeeting_footer']); + } + /** + * Does the work of upgrading from earlier versions. + */ + private function upgrade_settings(): void + { + if (!isset($this->options['bread_version'])) { + if (!($this->options['meeting_sort'] === 'weekday_area' + || $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(); + } + if ($this->options['page_fold'] == 'half') { + if ($this->options['page_size'] == 'A5') { + $this->options['page_size'] = 'A4'; + } + $this->options['page_orientation'] = 'L'; + } + if ($this->options['page_fold'] == 'tri') { + $this->options['page_orientation'] = 'L'; + } + if (substr($this->options['meeting_sort'], 0, 8) == 'weekday_') { + $this->options['sub_header_shown'] = 'display'; + } + if (isset($this->options['pageheader_text'])) { + $this->options['pageheader_content'] = $this->options['pageheader_text']; + unset($this->options['pageheader_text']); + } + if (substr($this->options['root_server'], -1) == '/') { + $this->options['root_server'] = substr($this->options['root_server'], 0, strlen($this->options['root_server']) - 1); + } + if (substr($this->options['root_server'], 0, 4) !== 'http') { + $this->options['root_server'] = 'http://' . $this->options['root_server']; + } + } + if (!isset($this->options['cont_header_shown']) + && isset($this->options['page_height_fix']) + ) { + $fix = floatval($this->options['page_height_fix']); + // say, the height of 2 lines + $x = floatval($this->options['content_font_size']) * + floatval($this->options['content_line_height']) * 2.0 * 0.35; // pt to mm + if ($fix < $x) { + $this->options['cont_header_shown'] = true; + } else { + $this->options['cont_header_shown'] = false; + } + unset($this->options['page_height_fix']); + } + if ($this->options['weekday_language'] == 'both') { + $this->options['weekday_language'] = "en_es"; + } + if ($this->options['weekday_language'] == 'both_po') { + $this->options['weekday_language'] = "en_po"; + } + if ($this->options['sub_header_shown'] == '0') { + $this->options['sub_header_shown'] = 'none'; + } + if ($this->options['sub_header_shown'] == '1') { + $this->options['sub_header_shown'] = 'display'; + } + $this->renamed_option('asm_format_key', 'additional_list_format_key'); + $this->renamed_option('asm_sort_order', 'additional_list_sort_order'); + $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->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']; + $this->options['custom_section_line_height'] = $this->options['last_page_line_height']; + unset($this->options['last_page_content']); + unset($this->options['last_page_line_height']); + unset($this->options['last_page_font_size']); + } + } + $this->options['bread_version'] = BREAD_VERSION; + } + private function renamed_option(string $old, string $new) + { + if (!empty($this->options[$old])) { + if (empty($this->options[$new])) { + $this->options[$new] = $this->options[$old]; + unset($this->options[$old]); + } + } + } + /** + * Stores the current settings in the Wordpress Options DB. + * + * @return void + */ + public function updateOptions() + { + update_option(Bread::getOptionsName(), $this->options); + } + public static function get_TransientKey($setting): string + { + return '_bread__' . $setting; + } + public function getMaxSetting(): int + { + return $this->maxSetting; + } +} diff --git a/includes/default_nalogo.jpg b/includes/default_nalogo.jpg deleted file mode 100644 index 8302613..0000000 Binary files a/includes/default_nalogo.jpg and /dev/null differ diff --git a/includes/four_column_settings.json b/includes/four_column_settings.json deleted file mode 100644 index 9c3874a..0000000 --- a/includes/four_column_settings.json +++ /dev/null @@ -1 +0,0 @@ -{"root_server":"","service_body_1":"","service_body_2":"Not Used","service_body_3":"Not Used","service_body_4":"Not Used","service_body_5":"Not Used","area_service_meetings":null,"helplines":null,"helplines_line_height":null,"helplines_font_size":null,"format_codes":null,"format_codes_font_size":"9","format_codes_line_height":"1.0","front_page_content":"

    \u00a0\"NALogo-1\"<\/p>\r\n

     <\/p>\r\n

    [area]<\/b><\/span><\/p>\r\n

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

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

    \u00a0<\/p>\r\n

    [date]<\/b><\/span><\/p>\r\n

     <\/p>\r\n

    24 HOUR HELPLINE<\/b><\/span><\/p>\r\n

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

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

    \u00a0<\/p>\r\n

    www.na.org<\/b><\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    SUGGESTIONS FOR EVERYONE<\/span><\/strong><\/p>\r\n

    DON\\'T USE no matter what<\/span><\/strong><\/p>\r\n

    Ask your Higher Power to keep you clean<\/span><\/strong><\/p>\r\n

    Come early and stay late<\/span><\/strong><\/p>\r\n

    Get a home group<\/span><\/strong><\/p>\r\n

    Go to 90 meetings in 90 days<\/span><\/strong><\/p>\r\n

    Read NA literature daily<\/span><\/strong><\/p>\r\n

    Get and use a sponsor<\/span><\/strong><\/p>\r\n

    Use the PHONE<\/span><\/strong><\/p>\r\n

    KEEP COMING BACK. IT WORKS<\/span><\/strong><\/p>\r\n

     <\/p>\r\n

    Meetings Weekly:\u00a0[meeting_count]<\/p>","front_page_line_height":"1.4","last_page_content":"","last_page_line_height":1.2,"area_service_meetings_line_height":"1.0","content_font_size":7,"page_layout":null,"time_option":1,"remove_space":true,"content_line_height":1.2,"page_size":"legal","page_fold":"quad","meeting_sort":"day","cache_time":0,"page_orientation":"L","show_status":null,"use_custom_section":null,"custom_section_content":"

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    MEETING FORMAT LEGEND<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [format_codes_used_basic]<\/p>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]<\/p>\r\n

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    NARCOTICS ANONYMOUS HELPLINES<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \r\n

    Service Body 1<\/p>\r\n<\/td>\r\n

    \r\n

    Number 1<\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

    Service Body 2<\/p>\r\n<\/td>\r\n

    \r\n

    Number 2<\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n<\/tr>\r\n

    \r\n

     <\/p>\r\n<\/td>\r\n

    \r\n

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

    [new_column]<\/div>\r\n\r\n\r\n\r\n
    PHONE NUMBERS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    \u00a0<\/p>\r\n

    The Narcotics Anonymous Message:<\/strong><\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    \u201cThat an addict, any addict, can stop using drugs, lose the desire to use\u00a0and find a new way to live.\\\"<\/span><\/p>","custom_section_line_height":1,"which_format_codes":"used","area_service_meetings_font_size":"10","custom_section_font_size":8,"front_page_font_size":10,"include_meeting_email":false,"bmlt_login_id":"","bmlt_login_password":"","last_page_font_size":10,"column_line":false,"used_format_1":"","column_gap":5,"margin_right":2,"margin_left":2,"margin_bottom":2,"margin_top":2,"meeting_template_content":"

    time<\/strong><\/span> group<\/strong>, location, info, street,\u00A0city, state,\u00A0zip\u00A0(formats)\u00A0comments<\/em><\/div>","col_color":"#bfbfbf","time_clock":"12","header_font_size":7.5,"header_text_color":"#ffffff","header_background_color":"#000000","header_uppercase":1,"header_bold":1,"weekday_language":"en","include_asm":false,"logo_copied":"done","sub_header_shown":1,"borough_suffix":"Borough","county_suffix":"County","neighborhood_suffix":"Neighborhood","city_suffix":"City","pagenumbering_font_size":9,"recurse_service_bodies":1,"extra_meetings_enabled":0,"include_protection":false,"base_font":"dejavusanscondensed","protection_password":"","custom_query":"","extra_meetings":[]} \ No newline at end of file diff --git a/includes/index.php b/includes/index.php new file mode 100644 index 0000000..8142269 --- /dev/null +++ b/includes/index.php @@ -0,0 +1 @@ + "Russian", + "LOCALE" => "ru-RU", + "WEEKDAYS" => array( 'ERROR', "Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота" ), + "WKDYS" => array( 'ERR', 'ВСК', 'ПНД', 'ВТР', 'СРД', 'ЧТВ', 'ПТН', 'СБТ' ), + "PAGE" => "Страница", + "CONT" => "продолжать" +); diff --git a/lang/translate_se.php b/includes/lang/translate_se.php similarity index 100% rename from lang/translate_se.php rename to includes/lang/translate_se.php diff --git a/includes/three_column_settings.json b/includes/three_column_settings.json deleted file mode 100644 index 95ff3f9..0000000 --- a/includes/three_column_settings.json +++ /dev/null @@ -1 +0,0 @@ -{"root_server":"","service_body_1":"","service_body_2":"Not Used","service_body_3":"Not Used","header_font_size":6.5,"area_service_meetings":null,"front_page_content":"

    \u00a0<\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    [service_body_1]<\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

    [date]<\/strong>\u00a0<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    24 HOUR HELPLINE<\/b><\/span><\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

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

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    https:\/\/na.org<\/b><\/p>\r\n

     <\/p>\r\n

    SUGGESTIONS FOR EVERYONE<\/strong><\/span><\/p>\r\n

    DON\\'T USE no matter what<\/strong><\/p>\r\n

    Ask your Higher Power to keep you clean<\/strong><\/p>\r\n

    Come early and stay late<\/strong><\/p>\r\n

    Get a home group<\/strong><\/p>\r\n

    Go to 90 meetings in 90 days<\/strong><\/p>\r\n

    Read NA literature daily<\/strong><\/p>\r\n

    Get and use a sponsor<\/strong><\/p>\r\n

    Use the PHONE<\/strong><\/p>\r\n

    KEEP COMING BACK. IT WORKS<\/strong><\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    Meetings Weekly:\u00a0[meeting_count]<\/p>","front_line_height":"1.0","area_service_meetings_line_height":null,"content_font_size":6,"content_line_height":1.2,"front_page_font_size":10,"last_page_content":"","front_page_line_height":"1.0","last_page_line_height":1,"page_layout":null,"service_body_4":"Not Used","service_body_5":"Not Used","time_option":1,"remove_space":true,"helplines_font_size":null,"format_codes":null,"format_codes_font_size":null,"format_codes_line_height":null,"page_size":"letter","page_fold":"tri","meeting_sort":"day","cache_time":0,"page_orientation":"L","show_status":null,"use_custom_section":null,"custom_section_content":"

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    MEETING FORMAT LEGEND<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [format_codes_used_basic]<\/p>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    HELPLINES<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Area 1<\/td>\r\nNumber 1<\/td>\r\n<\/tr>\r\n
    Area 2<\/td>\r\nNumber 2<\/td>\r\n<\/tr>\r\n
    <\/td>\r\n<\/td>\r\n<\/tr>\r\n
    <\/td>\r\n<\/td>\r\n<\/tr>\r\n
    <\/td>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]<\/p>\r\n

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    PHONE NUMBERS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    What is our message?<\/span><\/span><\/strong><\/p>\r\n

     <\/p>\r\n

    The message is that an addict, <\/span><\/strong><\/p>\r\n

    any addict,\u00a0<\/span><\/strong>can stop using drugs,<\/span><\/strong><\/p>\r\n

    \u00a0<\/span><\/strong>lose the desire to use,<\/span><\/strong><\/p>\r\n

    and find a new way to live.<\/span><\/strong><\/p>\r\n

    Our message is hope<\/span><\/strong><\/p>\r\n

    and the promise of freedom.\u00a0<\/span><\/strong><\/p>\r\n

    \u00a0<\/p>\r\n

    Basic Text<\/span>, page 65<\/span><\/em><\/p>\r\n

    [new_column]<\/p>","custom_section_line_height":1,"area_service_meetings_font_size":null,"which_format_codes":null,"custom_section_font_size":6.5,"include_meeting_email":false,"bmlt_login_id":"","bmlt_login_password":"","last_page_font_size":10,"column_line":false,"used_format_1":"","time_clock":"12","column_gap":6,"margin_right":2,"margin_left":2,"margin_bottom":2,"margin_top":2,"meeting_template_content":"

    time<\/strong><\/span><\/span> \u00A0<\/span> [hrs]HR<\/strong><\/span><\/span> \u00A0<\/span>group<\/strong>, location,\u00A0info,\u00A0street, city, state, zip (formats)<\/strong>\u00A0comments<\/em><\/div>","col_color":"#274fae","header_text_color":"#ffffff","header_background_color":"#000000","header_uppercase":1,"header_bold":1,"include_asm":false,"weekday_language":"both","sub_header_shown":1,"borough_suffix":"Borough","county_suffix":"County","neighborhood_suffix":"Neighborhood","city_suffix":"City","pagenumbering_font_size":9,"recurse_service_bodies":1,"extra_meetings_enabled":0,"include_protection":false,"base_font":"dejavusanscondensed","protection_password":"","custom_query":"","extra_meetings":[]} diff --git a/js/bmlt_meeting_list.js b/js/bmlt_meeting_list.js deleted file mode 100644 index 48b9fa4..0000000 --- a/js/bmlt_meeting_list.js +++ /dev/null @@ -1,744 +0,0 @@ -var s = document.getElementsByTagName('SELECT')[5].options, - l = [], - d = ''; -for(i = 0; i < s.length; i++){ -column = s[i].text.split(';'); -for(j = 0; j < column.length; j++){ - if(!l[j]) l[j] = 0; - if(column[j].length > l[j]){ - l[j] = column[j].length; - } -} -} -for(i = 0; i < s.length; i++){ -column = s[i].text.split(';'); -temp_line = ''; -for(j = 0; j < column.length; j++){ - t = (l[j] - column[j].length); - d = '\u00a0'; - for(k = 0; k < t; k++){ - d += '\u00a0'; - } - temp_line += column[j] + d; -} -s[i].text = temp_line; -} - function root_server_video() { - jQuery('.tooltip').tooltipster('hide'); - jQuery('#root-server-video').bPopup({ - transition: 'slideIn', - closeClass: 'b-close', - onClose: function() { - for (var player in mejs.players) { - mejs.players[player].media.stop(); - } - } - }); -}; -function current_meeting_list_video() { - jQuery('.tooltip').tooltipster('hide'); - jQuery('#current-meeting-list-video').bPopup({ - transition: 'slideIn', - closeClass: 'b-close', - onClose: function() { - for (var player in mejs.players) { - mejs.players[player].media.stop(); - } - } - }); -}; -function numbersonly(myfield, e, dec) { - var key; - var keychar; - if (window.event) - key = window.event.keyCode; - else if (e) - key = e.which; - else - return true; - keychar = String.fromCharCode(key); - // control keys - if ((key == null) || (key == 0) || (key == 8) || - (key == 9) || (key == 13) || (key == 27)) - return true; - // numbers - else if ((("0123456789").indexOf(keychar) > -1)) - return true; - // decimal point jump - else if (dec && (keychar == ".")) { - myfield.form.elements[dec].focus(); - return false; - } else - return false; -} -var $ml = jQuery.noConflict - jQuery(document).ready(function($ml) { - $ml(".connecting").hide(); - $ml(".saving").hide(); - $ml("#accordion").accordion({ - heightStyle: "content", - active: false, - collapsible: true - }); - $ml("#accordion2").accordion({ - heightStyle: "content", - active: false, - collapsible: true - }); - $ml("#accordion3").accordion({ - heightStyle: "content", - active: false, - collapsible: true - }); - $ml("#accordion_asm").accordion({ - heightStyle: "content", - active: false, - collapsible: true - }); - $ml("#col_color").spectrum({ - preferredFormat: "hex", - showInput: true, - showInitial: true, - theme: "sp-light" - }); - $ml("#col_color").click(function() { - $ml("#triggerSet").spectrum("set", $("#col_color").val()); - }); - $ml("#header_text_color").spectrum({ - preferredFormat: "hex", - showInput: true, - showInitial: true, - theme: "sp-light" - }); - $ml("#header_text_color").click(function() { - $ml("#triggerSet").spectrum("set", $("#header_text_color").val()); - }); - $ml("#header_background_color").spectrum({ - preferredFormat: "hex", - showInput: true, - showInitial: true, - theme: "sp-light" - }); - $ml("#header_background_color").click(function() { - $ml("#triggerSet").spectrum("set", $("#header_background_color").val()); - }); - $ml("#pageheader_textcolor").spectrum({ - preferredFormat: "hex", - showInput: true, - showInitial: true, - theme: "sp-light" - }); - $ml("#pageheader_textcolor").click(function() { - $ml("#triggerSet").spectrum("set", $("#pageheader_textcolor").val()); - }); - $ml("#pageheader_backgroundcolor").spectrum({ - preferredFormat: "hex", - showInput: true, - showInitial: true, - theme: "sp-light" - }); - $ml("#pageheader_backgroundcolor").click(function() { - $ml("#triggerSet").spectrum("set", $("#pageheader_backgroundcolor").val()); - }); - $ml("#bmlt_meeting_list_options").on("keypress", function(event) { - if (event.which == 13 && !event.shiftKey) { - event.preventDefault(); - return false; - } - }); - $ml("#bmltmeetinglistsave1").click(function(e) { - $ml(".saving").show(); - }); - $ml("#bmltmeetinglistsave2").click(function(e) { - $ml(".saving").show(); - }); - $ml("#bmltmeetinglistsave3").click(function(e) { - $ml(".saving").show(); - }); - $ml("#bmltmeetinglistsave4").click(function(e) { - $ml(".saving").show(); - }); - $ml("#bmltmeetinglistsave5").click(function(e) { - $ml(".saving").show(); - }); - $ml("#bmltmeetinglistsave6").click(function(e) { - $ml(".saving").show(); - }); - $ml("#submit_booklet").click(function(e) { - e.preventDefault(); - $ml('#basicModal3').dialog('open'); - }); - $ml('#basicModal3').dialog({ - autoOpen: false, - width: 'auto', - title: "Are you sure?", - modal: true, - buttons: { - "Confirm": function(e) { - $ml(this).dialog('close'); - $ml(".saving").show(); - $ml("#booklet_default_settings").submit(); - }, - "Cancel": function() { - $ml(this).dialog('close'); - } - } - }); - $ml("#submit_four_column").click(function(e) { - e.preventDefault(); - $ml('#basicModal2').dialog('open'); - }); - $ml('#basicModal2').dialog({ - autoOpen: false, - width: 'auto', - title: "Are you sure?", - modal: true, - buttons: { - "Confirm": function(e) { - $ml(this).dialog('close'); - $ml(".saving").show(); - $ml("#four_column_default_settings").submit(); - }, - "Cancel": function() { - $ml(this).dialog('close'); - } - } - }); - $ml("#submit_three_column").click(function(e) { - e.preventDefault(); - $ml('#basicModal1').dialog('open'); - }); - $ml('#basicModal1').dialog({ - autoOpen: false, - width: 'auto', - title: "Are you sure?", - modal: true, - buttons: { - "Confirm": function(e) { - $ml(this).dialog('close'); - $ml(".saving").show(); - $ml("#three_column_default_settings").submit(); - }, - "Cancel": function() { - $ml(this).dialog('close'); - } - } - }); - $ml('input[name="submit_import_file"]').on('click', function(e) { - e.preventDefault(); - var import_file_val = $ml('input[name=import_file]').val(); - if (import_file_val == false) { - $ml('#nofileModal').dialog('open'); - } else { - $ml('#basicModal').dialog('open'); - } - }); - $ml("#nofileModal").dialog({ - autoOpen: false, - modal: true, - buttons: { - Ok: function() { - $ml(this).dialog("close"); - } - } - }); - $ml('#basicModal').dialog({ - autoOpen: false, - width: 'auto', - title: "Are you sure?", - modal: true, - buttons: { - "Confirm": function(e) { - $ml(this).dialog('close'); - $ml(".saving").show(); - $ml('#form_import_file').submit(); - }, - "Cancel": function() { - $ml(this).dialog('close'); - } - } - }); - $ml('#root-server-button').bind('click', function(e) { - e.preventDefault(); - $ml('#root-server-video').bPopup({ - transition: 'slideIn', - closeClass: 'b-close', - onClose: function() { - for (var player in mejs.players) { - mejs.players[player].media.stop(); - } - } - }); - }); - $ml('#service-body-button').bind('click', function(e) { - e.preventDefault(); - $ml('#service-body-video').bPopup({ - transition: 'slideIn', - closeClass: 'b-close', - onClose: function() { - for (var player in mejs.players) { - mejs.players[player].media.stop(); - } - } - }); - }); - $ml('.my-tooltip').tooltipster({ - animation: 'grow', - delay: 200, - theme: 'tooltipster-noir', - contentAsHTML: true, - //positionTracker: false, - touchDevices: false, - hideOnClick: true, - icon: '(?)', - iconCloning: true, - iconDesktop: true, - iconTouch: false, - iconTheme: 'tooltipster-icon', - interactive: true, - arrow: false, - position: 'right', - //maxWidth: 900, - //offsetX: 150, - offsetY: -200, - trigger: 'click' - }); - $ml('.tooltip').tooltipster({ - animation: 'grow', - delay: 200, - theme: 'tooltipster-noir', - hideOnClick: true, - contentAsHTML: true, - positionTracker: false, - icon: '(?)', - iconCloning: true, - iconDesktop: true, - iconTouch: false, - iconTheme: 'tooltipster-icon', - interactive: true, - arrow: true, - position: 'right', - trigger: 'click' - }); - $ml('.bottom-tooltip').tooltipster({ - animation: 'grow', - delay: 200, - theme: 'tooltipster-noir', - hideOnClick: true, - contentAsHTML: true, - positionTracker: false, - icon: '(?)', - iconCloning: true, - iconDesktop: true, - iconTouch: false, - iconTheme: 'tooltipster-icon', - interactive: true, - arrow: true, - position: 'bottom-left', - offsetX: -10, - trigger: 'click' - }); - $ml('.top-tooltip').tooltipster({ - animation: 'grow', - delay: 200, - theme: 'tooltipster-noir', - hideOnClick: true, - contentAsHTML: true, - positionTracker: false, - icon: '(?)', - iconCloning: true, - iconDesktop: true, - iconTouch: false, - iconTheme: 'tooltipster-icon', - interactive: true, - arrow: true, - position: 'top-left', - offsetX: -10, - trigger: 'click' - }); - $ml('.top-middle-tooltip').tooltipster({ - animation: 'grow', - delay: 200, - theme: 'tooltipster-noir', - hideOnClick: true, - contentAsHTML: true, - positionTracker: false, - icon: '(?)', - iconCloning: true, - iconDesktop: true, - iconTouch: false, - iconTheme: 'tooltipster-icon', - interactive: true, - arrow: true, - position: 'top', - offsetX: -10, - trigger: 'click' - }); - $ml("#meeting-list-tabs").tabs({ - active: 0 - }); - $ml('#meeting-list-tabs').tabs().addClass('ui-tabs-vertical ui-helper-clearfix'); - $ml("#container").removeClass('hide'); - var meeting_sort_val = $ml("#meeting_sort").val(); - $ml('.borough_by_suffix').hide(); - $ml('.county_by_suffix').hide(); - if (meeting_sort_val === 'borough_county') { - $ml('.borough_by_suffix').show(); - $ml('.county_by_suffix').show(); - } else if (meeting_sort_val === 'borough') { - $ml('.borough_by_suffix').show(); - } else if (meeting_sort_val === 'county') { - $ml('.county_by_suffix').show(); - } - $ml('.neighborhood_by_suffix').hide(); - $ml('.city_by_suffix').hide(); - if (meeting_sort_val === 'neighborhood_city') { - $ml('.neighborhood_by_suffix').show(); - $ml('.city_by_suffix').show(); - } - var user_defined_sub = false; - $ml('.user_defined_headings').hide(); - if (meeting_sort_val === 'user_defined') { - $ml('.user_defined_headings').show(); - if ($ml("#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) { - $ml('.show_subheader').show(); - } else { - $ml('.show_subheader').hide(); - } - $ml("#suppress_heading").click(function() { - var val = $ml("#suppress_heading:checked").val(); - if (val == 1) { - $ml("#header_options_div").hide(); - } else { - $ml("#header_options_div").show(); - } - }); - $ml("#meeting_sort").change(function() { - var meeting_sort_val = $ml("#meeting_sort").val(); - $ml('.borough_by_suffix').hide(); - $ml('.county_by_suffix').hide(); - $ml('.neighborhood_by_suffix').hide(); - $ml('.city_by_suffix').hide(); - if (meeting_sort_val === 'borough_county') { - $ml('.borough_by_suffix').show(); - $ml('.county_by_suffix').show(); - } else if (meeting_sort_val === 'borough') { - $ml('.borough_by_suffix').show(); - } else if (meeting_sort_val === 'county') { - $ml('.county_by_suffix').show(); - } - if (meeting_sort_val === 'neighborhood_city') { - $ml('.neighborhood_by_suffix').show(); - $ml('.city_by_suffix').show(); - } - var user_defined_sub = false; - $ml('.user_defined_headings').hide(); - if (meeting_sort_val === 'user_defined') { - $ml('.user_defined_headings').show(); - if ($ml("#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) { - $ml('.show_subheader').show(); - } else { - $ml('.show_subheader').hide(); - } - }); - $ml("#subgrouping").click(function() { - var user_defined_sub = false; - $ml('.user_defined_headings').hide(); - if ($ml("#meeting_sort").val() === 'user_defined') { - $ml('.user_defined_headings').show(); - if ($ml("#subgrouping").val()!='') { - $ml('.show_subheader').show(); - } else { - $ml('.show_subheader').hide(); - } - } - }); - var time_clock_val = $ml('input[name=time_clock]:checked').val(); - if (time_clock_val == '24') { - $ml('#option3').hide(); - $ml('label[for=option3]').hide(); - } else if (time_clock_val == '24fr') { - $ml('#option3').hide(); - $ml('label[for=option3]').hide(); - } else { - $ml('#option3').show(); - $ml('label[for=option3]').show(); - } - $ml("#two").click(function() { - var time_clock_val = $ml('input[name=time_clock]:checked').val(); - if (time_clock_val == '24') { - $ml('label[for=option1]').html('20:00'); - $ml('label[for=option2]').html('20:00 - 21:00'); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - } else if (time_clock_val == '24fr') { - $ml('label[for=option1]').html('20h00'); - $ml('label[for=option2]').html('20h00 - 21h00'); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - } else { - $ml('#option3').show(); - $ml('label[for=option1]').html('8:00 PM'); - $ml('label[for=option2]').html('8:00 PM - 9:00 PM'); - $ml('label[for=option3]').html('8 - 9 PM'); - } - }); - $ml("#four").click(function() { - var time_clock_val = $ml('input[name=time_clock]:checked').val(); - if (time_clock_val == '24') { - $ml('label[for=option1]').html('20:00'); - $ml('label[for=option2]').html('20:00-21:00'); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - } else if (time_clock_val == '24fr') { - $ml('#option3').hide(); - $ml('label[for=option1]').html('20h00'); - $ml('label[for=option2]').html('20h00-21h00'); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - } else { - $ml('#option3').show(); - $ml('label[for=option1]').html('8:00PM'); - $ml('label[for=option2]').html('8:00PM-9:00PM'); - $ml('label[for=option3]').html('8-9PM'); - } - }); - $ml("#time_clock12").click(function() { - var remove_space_val = $ml('input[name=remove_space]:checked').val(); - $ml('#option3').show(); - $ml('label[for=option3]').show(); - if (remove_space_val == '1') { - $ml('label[for=option1]').html('8:00PM'); - $ml('label[for=option2]').html('8:00PM-9:00PM'); - $ml('label[for=option3]').html('8-9PM'); - } else { - $ml('label[for=option1]').html('8:00 PM'); - $ml('label[for=option2]').html('8:00 PM - 9:00 PM'); - $ml('label[for=option3]').html('8 - 9 PM'); - } - }); - $ml("#time_clock24").click(function() { - var remove_space_val = $ml('input[name=remove_space]:checked').val(); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - if (remove_space_val == '1') { - $ml('label[for=option1]').html('20:00'); - $ml('label[for=option2]').html('20:00-21:00'); - } else { - $ml('label[for=option1]').html('20:00'); - $ml('label[for=option2]').html('20:00 - 21:00'); - } - }); - $ml("#time_clock24fr").click(function() { - var remove_space_val = $ml('input[name=remove_space]:checked').val(); - $ml('#option3').hide(); - $ml('label[for=option3]').html(''); - if (remove_space_val == '1') { - $ml('label[for=option1]').html('20h00'); - $ml('label[for=option2]').html('20h00-21h00'); - } else { - $ml('label[for=option1]').html('20h00'); - $ml('label[for=option2]').html('20h00 - 21h00'); - } - }); - var page_fold_val = $ml('input[name=page_fold]:checked').val(); - function bookletControlsShowHide() { - $ml('#pagenodiv').show(); - $ml('#columngapdiv').hide(); - $ml('#columnseparatordiv').hide(); - $ml("#portrait, label[for=portrait]").hide(); - $ml('#landscape').prop("checked",true); - $ml("#5inch, label[for=5inch]").show(); - $ml("#letter, label[for=letter]").show(); - $ml("#legal, label[for=legal]").show(); - $ml("#ledger, label[for=ledger]").show(); - $ml("#booklet_pages, label[for=booklet_pages]").hide(); - $ml("#A4, label[for=A4]").show(); - $ml("#A5, label[for=A5]").show(); - $ml("#A6, label[for=A6]").hide(); - $ml("#portrait, label[for=portrait]").hide(); - $ml("#meeting-list-tabs ul li:eq(5)").hide(); - $ml("#meeting-list-tabs ul li:eq(6)").show(); - $ml('#meeting-list-tabs').tabs('disable', 5); - $ml('#meeting-list-tabs').tabs('enable', 6); - $ml("#half-fold").css({ - "display": "inline-block" - }); - $ml("#tri-fold").css({ - "display": "none" - }); - } - function fullPageControlsShowHide() { - $ml('#pagenodiv').hide(); - $ml("#A4, label[for=A4]").show(); - $ml("#A5, label[for=A5]").show(); - $ml("#A6, label[for=A6]").show(); - $ml("#5inch, label[for=5inch]").hide(); - $ml("#portrait, label[for=portrait]").show(); - $ml("#booklet_pages, label[for=booklet_pages]").show(); - $ml("#meeting-list-tabs ul li:eq(5)").hide(); - $ml("#meeting-list-tabs ul li:eq(6)").show(); - $ml('#meeting-list-tabs').tabs('disable', 5); - $ml('#meeting-list-tabs').tabs('enable', 6); - $ml('#columngapdiv').hide(); - $ml('#columnseparatordiv').hide(); - $ml("#half-fold").css({ - "display": "inline-block" - }); - $ml("#tri-fold").css({ - "display": "none" - }); - } - function foldControlsShowHide(fold) { - $ml("#letter, label[for=letter]").show(); - $ml("#legal, label[for=legal]").show(); - $ml("#ledger, label[for=ledger]").show(); - $ml("#booklet_pages, label[for=booklet_pages]").hide(); - $ml("#A4, label[for=A4]").show(); - $ml("#A5, label[for=A5]").hide(); - $ml("#A6, label[for=A6]").hide(); - $ml("#5inch, label[for=5inch]").hide(); - if (fold=='quad') - $ml("#portrait, label[for=portrait]").show(); - else { - $ml("#portrait, label[for=portrait]").hide(); - $ml('#landscape').prop("checked",true); - } - $ml("#meeting-list-tabs ul li:eq(5)").hide(); - $ml("#booklet_pages, label[for=booklet_pages]").hide(); - $ml("#meeting-list-tabs ul li:eq(5)").show(); - $ml("#meeting-list-tabs ul li:eq(6)").hide(); - $ml('#meeting-list-tabs').tabs('enable', 5); - $ml('#meeting-list-tabs').tabs('disable', 6); - } - if (page_fold_val == 'half') { - bookletControlsShowHide(); - } else if (page_fold_val == 'full') { - fullPageControlsShowHide(); - } else { - foldControlsShowHide(page_fold_val); - } - $ml('input[name=page_fold]:radio').click(function() { - var page_fold_val = $ml('input[name=page_fold]:checked').val(); - var page_orientation_val = $ml('input[name=page_orientation]:checked').val(); - var page_size_val = $ml('input[name=page_size]:checked').val(); - if (page_fold_val == 'half') { - bookletControlsShowHide(); - } - else if (page_fold_val == 'full') { - fullPageControlsShowHide(); - } - else { - foldControlsShowHide(page_fold_val); - }; - if (page_fold_val === 'tri') { - $ml('input[name=page_size]').val(['letter']); - }; - }); - $ml(".service_body_select").chosen({ - inherit_select_classes: true, - width: "62%" - }); - $ml("#extra_meetings").chosen({ - no_results_text: "Oops, nothing found!", - width: "100%", - placeholder_text_multiple: "Select Extra Meetings", - search_contains: true - }); - $ml('#extra_meetings').on('chosen:showing_dropdown', function(evt, params) { - $ml(".ctrl_key").show(); - }); - $ml('#extra_meetings').on('chosen:hiding_dropdown', function(evt, params) { - $ml(".ctrl_key").hide(); - }); - $ml("#author_chosen").chosen({ - no_results_text: "Oops, nothing found!", - width: "100%", - placeholder_text_multiple: "Select authors", - search_contains: true - }); - $ml('#author_chosen').on('chosen:showing_dropdown', function(evt, params) { - $ml(".ctrl_key").show(); - }); - $ml('#author_chosen').on('chosen:hiding_dropdown', function(evt, params) { - $ml(".ctrl_key").hide(); - }); -/* - $ml("#extra_meetings").select2({ - //tags: "true", - placeholder: "Select Meetings", - containerCssClass: 'tpx-select2-container select2-container-lg', - dropdownCssClass: 'tpx-select2-drop', - dropdownAutoWidth: true, - allowClear: true, - width: "100%", - /* dropdownParent: $ml('.exactCenter'), */ - /* minimumResultsForSearch: 1, */ - /* closeOnSelect: false, */ -/* escapeMarkup: function (markup) { return markup; } - }).maximizeSelect2Height({cushion: 100}); - $ml('.select2-choices').css('background-image','none').css('background-color','#111111 !important'); - */ - $ml("#meeting-list-tabs").tabs({ - active: 0 - }); - $ml("#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; - } - $ml('#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"; - $ml(window).on("load", function () { - if($ml('#use_aggregator').is(':checked')) { - $ml("#root_server").prop("readonly", true); - } - }); - $ml('#use_aggregator').click(function() { - if($ml(this).is(':checked')) { - $ml("#root_server").val(aggregator); - $ml("#root_server").prop("readonly", true); - } else { - $ml("#root_server").val(""); - $ml("#root_server").prop("readonly", false); - } - }); - var rootServerValue = $ml('#root_server').val(); - if(~rootServerValue.indexOf(aggregator)) { - $ml("#use_aggregator").prop("checked", true); - } -}); diff --git a/languages/bread.pot b/languages/bread.pot new file mode 100644 index 0000000..e69de29 diff --git a/partials/_bmlt_server_setup.php b/partials/_bmlt_server_setup.php deleted file mode 100644 index faf6158..0000000 --- a/partials/_bmlt_server_setup.php +++ /dev/null @@ -1,269 +0,0 @@ -has_cap('manage_bread')) { - $specific_users[] = $user; - } -} -?> -
    -
    -
    -
    -

    BMLT Server

    -
    -

    - - -

    - options['user_agent'].'" />'; - if ($this->options['sslverify']=='1') {?> -

    - - - options['root_server'])) { - echo "

    ERROR: Please enter a BMLT Server"; - echo ''; - if ($this->options['sslverify']=='1') {?> -

    - - -

    ERROR: Problem Connecting to BMLT Server
    connection_error; ?> -

    - - -

    - options['sslverify']?'checked':''; ?>/> - -

    - -

    - - - -

    - - get_areas(); ?> - - -
      -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • -
    -
    - options['recurse_service_bodies'] == 1 ? 'checked' : '') ?> /> Recurse Service Bodies -
    -
    -
    -
    -

    Custom Query

    -
    - - -
    -
    -
    -

    Include Extra Meetings

    -
    - - -

    Hint: Type a group name, weekday or area to narrow down your choices.

    -
    - options['extra_meetings_enabled']) && $this->options['extra_meetings_enabled'] == 1 ? 'checked' : '') ?> /> Extra Meetings Enabled -
    -
    - -
    -
    -

    Current Meeting List Link

    - -
    -
    -

    Meeting List Author(s)

    -
    - -
    -
    -
    -

    Meeting List Cache

    -
    - - -

    This site is using an external object cache.

    - -
      -
    • - -   0 - 999 Hours (0 = disable cache)   -
    • -
    -

    CACHE is DELETED when you Save Changes.

    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    -
    -
    diff --git a/partials/_custom_section_setup.php b/partials/_custom_section_setup.php deleted file mode 100644 index aaf9a72..0000000 --- a/partials/_custom_section_setup.php +++ /dev/null @@ -1,51 +0,0 @@ - -
    -
    -
    -
    - The Custom Content can be customized with text, graphics, tables, shortcodes, ect.

    -

    Default Font Size can be changed for specific text in the editor.

    -

    Add Media button - upload and add graphics.

    -

    Meeting List Shortcodes dropdown - insert variable data.

    -

    The Custom Content will print immediately after the meetings in the meeting list.

    - '; - ?> -

    Custom Content

    -
    -

    Default Font Size:    - Line Height:

    -
    - false, - 'editor_height' => 500, - 'resize' => true, - "media_buttons" => true, - "drag_drop_upload" => true, - "editor_css" => "", - "teeny" => false, - 'quicktags' => true, - 'wpautop' => false, - 'textarea_name' => $editor_id, - 'tinymce'=> array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'front_page_button') - ); - wp_editor(stripslashes(str_replace("http://", $this->protocol, $this->options['custom_section_content'])), $editor_id, $settings); - ?> -
    -
    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    diff --git a/partials/_front_page_setup.php b/partials/_front_page_setup.php deleted file mode 100644 index 030542e..0000000 --- a/partials/_front_page_setup.php +++ /dev/null @@ -1,51 +0,0 @@ - -
    -
    -
    -
    - The Front Page can be customized with text, graphics, tables, shortcodes, ect.

    -

    Add Media button - upload and add graphics.

    -

    Meeting List Shortcodes dropdown - insert custom data.

    -

    Default Font Size can be changed for specific text.

    - '; - ?> -

    Front Page Content

    -
    -

    Default Font Size:    - Line Height:

    -
    - false, - 'editor_height' => 500, - 'resize' => true, - "media_buttons" => true, - "drag_drop_upload" => true, - "editor_css" => "", - "teeny" => false, - 'quicktags' => true, - 'wpautop' => false, - 'textarea_name' => $editor_id, - 'tinymce'=> array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'front_page_button') - ); - wp_editor(stripslashes(str_replace("http://", $this->protocol, $this->options['front_page_content'])), $editor_id, $settings); - ?> -
    -
    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    -
    diff --git a/partials/_help_videos.php b/partials/_help_videos.php deleted file mode 100644 index 4cf6666..0000000 --- a/partials/_help_videos.php +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/partials/_last_page_setup.php b/partials/_last_page_setup.php deleted file mode 100644 index edfc2bf..0000000 --- a/partials/_last_page_setup.php +++ /dev/null @@ -1,49 +0,0 @@ - -
    -
    -
    -
    - Last Page Content

    -

    Any text or graphics can be entered into this section. - '; - ?> -

    Last Page Content

    -
    -

    Default Font Size:    - Line Height:

    -
    - false, - 'editor_height' => 500, - 'resize' => true, - "media_buttons" => true, - "drag_drop_upload" => true, - "editor_css" => "", - "teeny" => false, - 'quicktags' => true, - 'wpautop' => false, - 'textarea_name' => $editor_id, - 'tinymce'=> array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'front_page_button') - ); - wp_editor(stripslashes($this->options['last_page_content']), $editor_id, $settings); - ?> -
    -
    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    -
    diff --git a/partials/_layout_setup.php b/partials/_layout_setup.php deleted file mode 100644 index 51b74e1..0000000 --- a/partials/_layout_setup.php +++ /dev/null @@ -1,201 +0,0 @@ - -
    -
    -
    -
    - Page Layout Defaults

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Meeting List SizePage LayoutOrientationPaper SizePage Height
    Smaller AreasTri-FoldLandscapeLetter, A4195, 180
    Medium AreaQuad-FoldLandscapeLegal, A4195, 180
    Large Area, Region, MetroHalf-FoldLandscapeBooklet, A5250, 260
    AnythingFull PagePortrait, LandscapeLetter, Legal, A4None
    -

    When a layout is clicked defaults are reset for orientation, paper size and page height.

    - '; - ?> -

    Page Layout

    -
    -

    - - options['page_fold'] == 'flyer' ? 'checked' : '') ?>> - options['page_fold'] == 'tri' ? 'checked' : '') ?>> - options['page_fold'] == 'quad' ? 'checked' : '') ?>> - options['page_fold'] == 'half' ? 'checked' : '') ?>> - options['page_fold'] == 'full' ? 'checked' : '') ?>> -

    -

    - options['page_orientation'] == 'P' ? 'checked' : '') ?>> - options['page_orientation'] == 'L' ? 'checked' : '') ?>> - options['booklet_pages'] == '1' ? 'checked' : '') ?> /> -

    - options['page_size'] == '5inch' ? 'checked' : '') ?>> - options['page_size'] == 'letter' ? 'checked' : '') ?>> - options['page_size'] == 'legal' ? 'checked' : '') ?>> - options['page_size'] == 'ledger' ? 'checked' : '') ?>> - options['page_size'] == 'A4' ? 'checked' : '') ?>> - options['page_size'] == 'A5' ? 'checked' : '') ?>> - options['page_size'] == 'A6' ? 'checked' : '') ?>> -

    -

    -
    -

    - Page Margin Top:     - Bottom:     - Left:     - Right:     -

    -
    -
    -

    - The page header is a title that goes across the entire page above the meetings. -

    - - - -

    - Header Margin Top:     -
    Header Text:     -
    Watermark:     -

    -
    - options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - ?> -
    - - -
    - - - -
    -
    -

    - Page Numbers Font Size: -

    -
    - -
    -

    - Column Gap Width: -

    -
    -
    - -

    - - - - -
    Separator: options['column_line'] == '1' ? 'checked' : '') ?> /> -
    - -
    -
    - -

    -
    - -
    -

    - - - - - - - - - -
    - - -
    - - -
    -

    -
    -
    - Enable PDF Protection.

    -

    Encrypts and sets the PDF document permissions for the PDF file.

    - -

    PDF can be opened and printed. - -

    Optional Password to allow editing in a PDF editor. -

    Note: PDF is encrypted and cannot be opened in MS Word at all.

    - '; - ?> - -

    options['include_protection'] == '1' ? 'checked' : '') ?>>Enable PDF Protection

    -

    -

    - - -
    - - -

    -
    -
    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    -
    diff --git a/partials/_meeting_list_setup.php b/partials/_meeting_list_setup.php deleted file mode 100644 index 0901be1..0000000 --- a/partials/_meeting_list_setup.php +++ /dev/null @@ -1,85 +0,0 @@ - -
    -
    -
    -

    Read This Section First

    -
    -

    Getting Started

    -

    bread is first activated using a "Tri Fold - Landscape - Letter Size" layout. This is a "starter" meeting list that uses an Area with about 100 meetings. The starter meeting list will contain standard content for a basic meeting list that can be printed on a home computer. A basic NA logo will be added to your media libray. The starter meeting list uses a logo being hosted on https://nameetinglist.org.

    -

    Step 1.

    -

    Click on the BMLT Server tab to the left. Change the BMLT Server and click the Save Changes button.

    -

    To find your BMLT Server click on the red question (?) mark.

    -

    Step 2.

    -

    From the Service Body 1 dropdown select your Area or Region. Then click Save Changes.

    -

    Step 3.

    -

    Click Generate Meeting List. Your meeting list will open in a new tab or window.

    -

    Step 4.

    -

    See the "Meeting List Setup" section below for additional defaults.

    -

    Repeat steps 1, 2 and 3 after changing to new Default Settings.

    -

    What Now?

    -

    From here you will move forward with setting up your meeting list by exploring the Page Layout, Front Page, Custom Section, Meetings, etc tabs. There are countless ways to setup a meeting list.

    -

    Please allow yourself to experiment with mixing and matching different settings and content. There is a good chance you can find a way to match or at least come very close to your current meeting list.

    -

    When setting up the meeting list it is helpful to have some knowledge of HTML when using the editors. Very little or no knowledge of HTML is required to maintain the meeting list after the setup. If you get stuck or would like some help with the setup, read the Support section below.

    -
    -

    Meeting List Setup

    -
    -

    Default Settings and Content

    -

    Changing the Default Settings and Content should only be considered when first using the Meeting List Generator or when you decide to completely start over with setting up your meeting list.

    -

    The buttons below will completely reset your meeting list settings (and content) to whichever layout you choose. There is no Undo.

    -

    Consider backing up settings by using the Configuration Tab before changing your Meeting List Settings.

    - - - -

    Small or Medium Size Areas

    -

    Areas with up to about 100 meetings would benefit from using the tri-fold layout on letter sized paper. Areas larger than 100 meetings would typically use a quad fold meeting list on legal sized paper. These are just basic guidelines and are by no means set in stone. For example, an Area with over 100 meetings could use the tri-fold on letter sized paper using smaller fonts to allow the content to fit. The meeting list configuration is extremely flexible.

    -

    The Custom Content section is used to add information like helplines, service meetings, meeting format legend, etc.

    -

    Large Areas, Metro Areas or Regions

    -

    Larger service bodies would benefit from using a booklet meeting list.

    -

    The booklet uses the Front and Last pages for custom content. There is no Custom Content section on a booklet meeting list.

    -

    Support

    -

    For support file an issue at https://github.com/bmlt-enabled/bread/issues

    -
    -

    Multiple Meeting Lists

    -
    -

    This tool supports multiple meeting lists per site.

    -

    This feature is configured from the Configuration Tab. There, each concurrent meeting list can be given a - name, and the system gives the meeting list a numberic identifier. The meeting list can then be generated using

    - a link of the form http://[host]?current-meeting-list=[id]

    -

    After switching to another concurrent meeting list, any changes in the admin UI impact the currently selected meeting list

    -

    If you want to give another user access to bread you can give that use the "manage_bread" capability using a custom role editor.

    -
    -

    Reusable Templating

    -
    -

    You can dynamically set some of the options to create a reusable template.

    -

    In order to change the meeting information you can pass a dynamic custom query using &custom_query=, ensure you are using URL encoding.

    -

    You can also use any combinations of [querystring_custom_*], where * is any digit. You can then override that specific value using it in querystring as &querystring_custom_1= (for instance).

    -

    You can use any HTML characters, including line breaks.

    -

    Here is a video of it in action: https://bmlt.app/reusable-templates-with-bread-1-6-x/

    -
    -

    Support and Help

    -
    -

    File an issue https://github.com/bmlt-enabled/bread/issues

    - Debug Information -
      -
    • Bread Version:
    • -
    • Wordpress Version:
    • -
    • Protocol: protocol; ?>
    • -
    • PHP Version:
    • -
    • Server Version:
    • -
    • Temporary Directory:
    • -
    -
    -
    -
    -
    -
    diff --git a/partials/_meetings_setup.php b/partials/_meetings_setup.php deleted file mode 100644 index 7eb436b..0000000 --- a/partials/_meetings_setup.php +++ /dev/null @@ -1,489 +0,0 @@ - -
    -
    -
    -
    - Customize the Meeting Group Header to your specification.

    -

    The Meeting Group Header will contain the data from Group By.

    - '; - ?> -

    Meeting Group [Column] Header

    -
    -
    -

    Instructions

    -
    - -
    -
    -
    - - options['suppress_heading'] == '1' ? 'checked' : '') ?>> - - - - - - - -
    Font Size: -
    - -
    -
    -
    - -
    -
    - - options['header_uppercase'] == '1' ? 'checked' : '') ?>> - - options['header_bold'] == '1' ? 'checked' : '') ?>> - - options['cont_header_shown'] == '1' ? 'checked' : '') ?>>
    -

    -

    - - -
    -
    - -

    - - - -

    - -
    -
    - -

    - - - -

    -
    - -
    - -

    - - - -

    - -
    -
    - -

    - - - -

    -
    -
    -

    - - - - -

    -
    -
    -

    - - -

    -
    -
    - - -
    - -
    - - -
    - options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - ?> - -

    -

    -
    -
    - -

    The Meeting Template is a powerful and flexible method for customizing meetings using - HTML markup and BMLT field names. The template is set-up once and never needs to be messed - with again. Note: When changes are made to the Default Font Size or Line Height, the template - may need to be adjusted to reflect those changes.

    -

    Sample templates can be found in the editor drop down menu Meeting Template.

    -

    BMLT fields can be found in the editor drop down menu Meeting Template Fields.

    -

    The Default Font Size and Line Height will be used for the meeting template.

    -

    Font Size and Line Height can be overridden using HTML mark-up in the meeting text.

    -
    - '; - ?> -

    Meeting Template

    -
    -
    -

    Instructions

    -
    - -
    -
    -

    - Default Font Size:    - Line Height:    - Wheelchair Icon Size:    -

    Avoid using tables which will greatly slow down the generation time. Use CSS instead to get table-like effects if need be.
    -
    - false, - 'editor_height' => 110, - 'resize' => true, - "media_buttons" => false, - "drag_drop_upload" => true, - "editor_css" => "", - "teeny" => false, - 'quicktags' => true, - 'wpautop' => false, - 'textarea_name' => $editor_id, - 'tinymce'=> array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'custom_template_button_1,custom_template_button_2') - ); - wp_editor(stripslashes($this->options['meeting_template_content']), $editor_id, $settings); - ?> -
    -
    -
    -
    - Format the Start Time (start_time) field in the Meeting Template.

    - '; - ?> -

    Start Time Format

    -
    - - options['remove_space'] == '1') { ?> - - - options['time_clock'] == '12') { ?> - - - - - options['time_clock'] == '24fr') { ?> - - - - - - - - - - - - - - - - - - - - - - - -
    -
    options['time_clock'] == '12' || $this->options['time_clock'] == '' ? 'checked' : '') ?>>
    -
    -
    options['time_option'] == '1' || $this->options['time_option'] == '' ? 'checked' : '') ?>>
    -
    - options['remove_space'] == '0' || $this->options['remove_space'] == '') { ?> -
    - -
    - -
    -
    options['time_clock'] == '24' ? 'checked' : '') ?>>
    -
    -
    options['time_option'] == '2' ? 'checked' : '') ?>>
    -
    - options['remove_space'] == '1') { ?> -
    - -
    - -
    -
    options['time_clock'] == '24fr' ? 'checked' : '') ?>>
    -
    -
    options['time_option'] == '3' ? 'checked' : '') ?>>
    -
    -
    -
    -
    -
    - Create a special interest meeting list.

    - '; - ?> -

    Include Only This Meeting Format

    -
    - - getFormatsForSelect(false); ?> - - - -
    -
    - authenticate_root_server()); ?> -
    Login ID or Password Incorrect

    "; ?> - -
    Login OK

    "; ?> - -
    -

    Additional List

    -
    -

    - This section allows the definition of an additional meeting list, containing meetings that should not be included in the main - list. This is typically service meetings, but it can be any group of meetings identified by a format. -

    - - -

    - - -

    - - -

    - options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { - ?> - - -

    - - -

    - The additional list may include fields that might be used for say "service meetings". To access these fields - you must login with an service body administrator account. -
    - -      - - -
    - -

    - Enable the Meeting Email Contact (email_contact) field in the Meeting Template.

    -

    This feature requires a login ID and password for the service body.

    -

    This can be Service Body Administrator or Observer.

    -

    Visit the BMLT Roles page for more details.

    - '; - ?> - Meeting Email Contact -

    options['include_meeting_email'] == '1' ? 'checked' : '') ?>>Enable

    -
    - - -

    options['include_asm'] == '1' ? 'checked' : '') ?>>Include meetings with this format in the main list

    - The default format for the additional list is ASM. If you wish to define a different format for the additional list, use this template. -
    - false, - 'editor_height' => 110, - 'resize' => true, - "media_buttons" => false, - "drag_drop_upload" => true, - "editor_css" => "", - "teeny" => false, - 'quicktags' => true, - 'wpautop' => false, - 'textarea_name' => $editor_id, - 'tinymce'=> array('toolbar1' => 'bold,italic,underline,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,alignjustify,link,unlink,table,undo,redo,fullscreen', 'toolbar2' => 'formatselect,fontsizeselect,fontselect,forecolor,backcolor,indent,outdent,pastetext,removeformat,charmap,code', 'toolbar3' => 'custom_template_button_1,custom_template_button_2') - ); - wp_editor(stripslashes($this->options['asm_template_content']), $editor_id, $settings); - ?> -
    -
    -
    -

    Instructions

    -
    - -
    -
    -
    -
    -
    -
    -
    - current_user_can_modify()) { - echo ' - - '; - }?> - Generate Meeting List

    '; ?> -
      Save Changes before Generate Meeting List.
    -
    -
    diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e48232f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + + \ No newline at end of file diff --git a/public/class-bread-content-generator.php b/public/class-bread-content-generator.php new file mode 100644 index 0000000..38c8692 --- /dev/null +++ b/public/class-bread-content-generator.php @@ -0,0 +1,626 @@ + + */ +class Bread_ContentGenerator +{ + /** + * Converts HTML to PDF. + * + * @var Mpdf + */ + private Mpdf $mpdf; + /** + * The configuration of the meeting list. + * + * @var array + */ + private array $options; + private Bread $bread; + /** + * The meetings in the meeting list. + * + * @var array + */ + private array $result_meetings; + /** + * The standard shortcodes, not including fields in the array representing the meeting. Used on front and custom sections. + * + * @var array + */ + private array $shortcodes; + private int $meeting_count; + private $target_timezone; + /** + * Convenient API for dealing with formats. + * + * @var Bread_FormatsManager + */ + private Bread_FormatsManager $formatsManager; + /** + * Helper class that enriches the meeting with calculated values. + * + * @var Bread_Meeting_Enhancer + */ + private Bread_Meeting_Enhancer $meetingEnhancer; + /** + * Usually, the key fieled in the array representing the meeting is used to insert a value into a template. But we also have these convenience names. + * + * @var array + */ + private array $legacy_synonyms = array( + 'borough' => 'location_city_subsection', + 'time' => 'start_time', + 'state' => 'location_province', + 'street' => 'location_street', + 'neighborhood' => 'location_neighborhood', + 'city' => 'location_municipality', + 'zip' => 'location_postal_code_1', + 'location' => 'location_text', + 'info' => 'location_info', + 'county' => 'location_sub_province', + 'group' => 'meeting_name', + 'email' => 'email_contact', + 'mins' => 'duration_m', + 'hrs' => 'duration_h', + "area" => 'area_name', + ); + /** + * The constuctor sets things up so that we are ready to generate. + * + * @param Mpdf $mpdf The object that converts HTML to PDF. + * @param Bread $bread The configuration of the meeting list. + * @param array $result_meetings The meetings to be included in the list. + * @param Bread_FormatsManager $formatsManager + */ + function __construct(Mpdf $mpdf, Bread $bread, array $result_meetings, Bread_FormatsManager $formatsManager) + { + $this->mpdf = $mpdf; + $this->bread = $bread; + $this->options = $bread->getOptions(); + $this->result_meetings = $this->orderByWeekdayStart($result_meetings); + $this->formatsManager = $formatsManager; + if (isset($_GET['time_zone'])) { + $this->target_timezone = timezone_open($_GET['time_zone']); + } + $this->meeting_count = count($this->result_meetings); + $this->shortcodes = array( + '

    ' => '

    ', + '
    [page_break]
    ' => '', + '

    [page_break]

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

    [new_column]

    ' => '', + '[new_column]' => '', + '[page_break no_page_number]' => '', + '[start_page_numbers]' => '', + "[month_lower]" => date("F"), + "[month_upper]" => strtoupper(date("F")), + "[month]" => strtoupper(date("F")), + "[day]" => strtoupper(date("j")), + "[year]" => strtoupper(date("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']), + + ); + $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'] . '
    '); + } + if (!empty($this->options['pageheader_content'])) { + $data = $this->standard_shortcode_replacement('pageheader'); + $header_style = "vertical-align: top; text-align: center; font-weight: bold;margin-top:3px;margin-bottom:3px;"; + $header_style .= "color:" . $this->options['pageheader_textcolor'] . ";"; + $header_style .= "background-color:" . $this->options['pageheader_backgroundcolor'] . ";"; + $header_style .= "font-size:" . $this->options['pageheader_fontsize'] . "pt;"; + $header_style .= "line-height:" . $this->options['content_line_height'] . ";"; + + $this->mpdf->SetHTMLHeader( + '
    ' . $data . '
    ', + 'O' + ); + } + if (!empty($this->options['watermark'])) { + $this->mpdf->SetWatermarkImage($this->options['watermark'], 0.2, 'F'); + $this->mpdf->showWatermarkImage = true; + } + } + /** + * Generates the contents of the meeting list. + * + * @param int $num_columns The number of columns in the meeting list. + * @return void + */ + public function generate(int $num_columns): void + { + require_once __DIR__ . '/class-bread-meetingslist-structure.php'; + require_once __DIR__ . '/class-bread-meeting-enhancer.php'; + $this->mpdf->SetColumns($num_columns, '', $this->options['column_gap']); + if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { + $this->write_front_page(); + } + $this->mpdf->WriteHTML('td{font-size: ' . $this->options['content_font_size'] . "pt;line-height:" . $this->options['content_line_height'] . ';background-color:#ffffff00;}', 1); + $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['content_font_size'] . 'pt'); + $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['content_line_height']); + $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); + if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { + $this->WriteHTML(''); + } + $lang = $this->options['weekday_language']; + $this->meetingEnhancer = new Bread_Meeting_Enhancer($this->bread, $this->bread->bmlt()->get_areas()); + foreach ($this->result_meetings as &$value) { + $value = $this->meetingEnhancer->enhance_meeting($value, $lang, $this->formatsManager); + } + $meetingslistStructure = new Bread_Meetingslist_Structure($this->bread, $this->result_meetings, $lang, $this->options['include_additional_list'] == 0 ? -1 : 0); + $this->writeMeetings($this->options['meeting_template_content'], $meetingslistStructure); + + if ($this->options['page_fold'] !== 'half' && $this->options['page_fold'] !== 'full') { + $this->write_custom_section(); + $this->write_front_page(); + } else { + $this->mpdf->WriteHTML(''); + $this->write_custom_section(); + } + } + /** + * Writes a HTML string to th PDF. + * + * @param string $str The string to be written. + * @return void + */ + private function writeHTML(string $str): void + { + //$str = htmlentities($str); + @$this->mpdf->WriteHTML(wpautop(stripslashes($str))); + } + /** + * Replace the shortcodes with the standard values (not meeting dependent). + * + * @param string $page The text containing the shortcodes. + * @return string + */ + private function standard_shortcode_replacement(string $page): string + { + $search_strings = array(); + $replacements = array(); + foreach ($this->shortcodes as $key => $value) { + $search_strings[] = $key; + $replacements[] = $value; + } + + $search_strings[] = '[meeting_count]'; + $replacements[] = $this->meeting_count; + $data = $this->options[$page . '_content']; + $data = $this->locale_month_replacement($data, 'lower'); + $data = $this->locale_month_replacement($data, 'upper'); + $data = str_replace($search_strings, $replacements, $data); + $this->replace_format_shortcodes($data, $page); + $data = str_replace("[date]", strtoupper(date("F Y")), $data); + if ($this->target_timezone) { + $data = str_replace('[timezone]', $this->target_timezone->getName(), $data); + } + return $data; + } + /** + * Replace [month] shortcodes with the locale-specific name of the month. + * + * @param string $data The text containing the shortcode. + * @param string $case 'upper' or 'lower' + * @return string The text with the shortcode replaced by the value. + */ + private function locale_month_replacement(string $data, string $case): string + { + $strpos = strpos($data, "[month_$case" . "_"); + if ($strpos !== false) { + $locLang = substr($data, $strpos + 13, 2); + if (!isset($this->translate[$locLang])) { + $locLang = 'en'; + } + $fmt = new IntlDateFormatter( + $this->bread->getTranslateTable()[$locLang]['LOCALE'], + IntlDateFormatter::FULL, + IntlDateFormatter::FULL + ); + $fmt->setPattern('LLLL'); + $month = ucfirst(mb_convert_encoding($fmt->format(time()), 'UTF-8', 'ISO-8859-1')); + if ($case == 'upper') { + $month = mb_strtoupper($month, 'UTF-8'); + } + return substr_replace($data, $month, $strpos, 16); + } + return $data; + } + /** + * Order the meetings so that the list starts on the day of the week selected in the configuation. + * + * @param array $result_meetings The meetings + * @return array The meetings. + */ + private function orderByWeekdayStart(array &$result_meetings): array + { + $days = array_column($result_meetings, 'weekday_tinyint'); + $today_str = $this->options['weekday_start']; + return array_merge( + array_splice($result_meetings, array_search($today_str, $days)), + array_splice($result_meetings, 0) + ); + } + /** + * Generate the front page. + * + * @return void + */ + private function write_front_page() + { + $this->mpdf->WriteHTML('td{font-size: ' . $this->options['front_page_font_size'] . "pt;line-height:" . $this->options['front_page_line_height'] . ';}', 1); + $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['front_page_line_height']); + $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['front_page_font_size'] . 'pt'); + $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); + $this->options['front_page_content'] = wp_unslash($this->options['front_page_content']); + $data = $this->standard_shortcode_replacement('front_page'); + + $querystring_custom_items = array(); + preg_match_all('/(\[querystring_custom_\d+\])/', $this->options['front_page_content'], $querystring_custom_items); + foreach ($querystring_custom_items[0] as $querystring_custom_item) { + $mod_qs_ci = str_replace("]", "", str_replace("[", "", $querystring_custom_item)); + $data = str_replace($querystring_custom_item, (isset($_GET[$mod_qs_ci]) ? $_GET[$mod_qs_ci] : "NOT SET"), $data); + } + $this->writeHTMLwithAdditionalMeetinglist($data); + $this->mpdf->showWatermarkImage = false; + } + /** + * Generate the custom section. + * + * @return void + */ + private function write_custom_section() + { + $this->mpdf->SetHTMLHeader(); + if (isset($this->options['pageheader_content']) && trim($this->options['pageheader_content'])) { + $this->mpdf->SetTopMargin($this->options['margin_header']); + } + $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['custom_section_line_height']); + $this->mpdf->SetDefaultBodyCSS('font-size', $this->options['custom_section_font_size'] . 'pt'); + $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); + $data = $this->standard_shortcode_replacement('custom_section'); + $this->mpdf->WriteHTML('td{font-size: ' . $this->options['custom_section_font_size'] . "pt;line-height:" . $this->options['custom_section_line_height'] . ';}', 1); + $this->writeHTMLwithAdditionalMeetinglist($data); + } + /** + * Generate the meeting list itself, using the specified template and the meetings as structed by the heading manager. + * + * @param string $template + * @param Bread_Meetingslist_Structure $meetingslistStructure + * @return void + */ + private function writeMeetings(string $template, Bread_Meetingslist_Structure $meetingslistStructure): void + { + $template = wpautop(stripslashes($template)); + $template = preg_replace('/[[:^print:]]/', ' ', $template); + + $template = str_replace(" ", " ", $template); + $analysedTemplate = $this->analyseTemplate($template); + + /*** + * You might be wondering why I am not using keep-with-table... + * The problem is, keep with table doesn't work with columns, only pages. + * We want to check that a header and at least one meeting fits, so we write it + * to a test PDF, see how big it is, and check if it will fit. + */ + $test_pages = deep_copy($this->mpdf); + while ($subheadings = $meetingslistStructure->iterateMainHeading()) { + while ($meetings = $meetingslistStructure->iterateSubHeading($subheadings)) { + while ($meeting_value = $meetingslistStructure->iterateMeetings($meetings)) { + $header = $meetingslistStructure->calculateHeading(); + $data = $this->write_single_meeting( + $meeting_value, + $template, + $analysedTemplate + ); + $this->writeBreak($test_pages); + $y_startpos = $test_pages->y; + @$test_pages->WriteHTML($header . $data); + $y_diff = $test_pages->y - $y_startpos; + if ($y_diff >= $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height) { + $this->writeBreak($this->mpdf); + if (empty($header)) { + $header = $meetingslistStructure->calculateContHeader(); + } + } + $this->WriteHTML($header . $data); + } + } + } + } + /** + * Write a break between meetings, so that the meeting does not get broken up in the printout. + * In booklets, a page break. In fliers, a column break. + * + * @param Mpdf $mpdf + * @return void + */ + private function writeBreak(Mpdf $mpdf) + { + if ($this->options['page_fold'] === 'half' || $this->options['page_fold'] === 'full') { + $mpdf->WriteHTML(""); + } else { + $mpdf->WriteHTML(""); + } + } + /** + * Break up the template into chunks, so that it can be efficiently processed/ filled with values from the meeting. + * + * @param string $template + * @return array chunks to be used by the write_single_meeting method. + */ + private function analyseTemplate(string $template): array + { + $arr = preg_split('/\W+/', $template, 0, PREG_SPLIT_OFFSET_CAPTURE); + $arr = array_reverse($arr, true); + $ret = array(); + foreach ($arr as $item) { + if (strlen($item[0]) < 3) { + continue; + } + $htmlTags = array('table', 'tbody', 'strong', 'left', 'right', 'top', 'bottom', 'center', 'align', 'font', 'size', 'text', 'style', 'family', 'vertical', 'color', 'QRCode'); + if (in_array($item[0], $htmlTags)) { + continue; + } + if ($item[1] > 0 && $template[$item[1] - 1] == '[' + && $template[$item[1] + strlen($item[0])] == ']' + ) { + $item[0] = '[' . $item[0] . ']'; + $item[1] = $item[1] - 1; + $item[2] = true; + } else { + $item[2] = false; + } + $ret[] = $item; + } + return $ret; + } + + /** + * Write a single meeting to the PDF + * + * @param array $meeting_value The meeting data. + * @param string $template + * @param array $analysedTemplate + * @return void + */ + private function write_single_meeting(array $meeting_value, string $template, array $analysedTemplate) + { + $data = $template; + $namedValues = array(); + foreach ($meeting_value as $field => $notUsed) { + $namedValues[$field] = $this->get_field($meeting_value, $field); + } + foreach ($this->legacy_synonyms as $syn => $field) { + $namedValues[$syn] = $namedValues[$field]; + } + foreach ($analysedTemplate as $item) { + $name = $item[0]; + if ($item[2]) { + $name = substr($name, 1, strlen($name) - 2); + } + if (isset($namedValues[$name])) { + $data = substr_replace($data, $namedValues[$name], $item[1], strlen($item[0])); + } + } + $qr_pos = strpos($data, "[QRCode"); + if ($qr_pos) { + $qr_end = strpos($data, ']', $qr_pos); + $data = substr($data, 0, $qr_pos) . + '' . + substr($data, $qr_end + 1); + } + $search_strings = array(); + $replacements = array(); + $clean_up = array( + '' => '', + ' ' => '', + '' => '', + ' ' => '', + '' => '', + ' ' => '', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '

    ' => '', + '()' => '', + '
    ' => 'line_break', + '
    ' => 'line_break', + 'line_break line_break' => '
    ', + 'line_breakline_break' => '
    ', + 'line_break' => '
    ', + '
    ,' => '
    ', + ',
    ' => '
    ', + ',
    ' => '
    ', + '

    ,' => '

    ', + ", , ," => ",", + ", *," => ",", + ", ," => ",", + " , " => " ", + ", (" => " (", + ', ' ' $value) { + $search_strings[] = $key; + $replacements[] = $value; + } + $data = str_replace($search_strings, $replacements, $data); + return $data; + } + /** + * Generate the PDF of templates that also contain "additional lists" and format tables. + * + * @param string $data The template. + * @return void + */ + private function writeHTMLwithAdditionalMeetinglist(string $data) + { + $strs = array( + '

    [additional_meetinglist]

    ', + '[additional_meetinglist]', + '

    [service_meetings]

    ', + '[service_meetings]' + ); + + foreach ($strs as $str) { + $pos = strpos($data, $str); + if (!$pos) { + continue; + } + if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { + $this->WriteHTML(''); + } + $this->WriteHTML(substr($data, 0, $pos)); + $this->write_additional_meetinglist(); + if ($this->options['page_fold'] == 'half' || $this->options['page_fold'] == 'full') { + $this->WriteHTML(''); + } + $this->WriteHTML(substr($data, $pos + strlen($str))); + return; + } + $this->WriteHTML($data); + } + + private function replace_format_shortcodes(&$data, $page_name) + { + $lang = $this->options['weekday_language']; + $this->shortcode_formats('[format_codes_used_basic]', false, $lang, false, $page_name, $data); + $this->shortcode_formats('[format_codes_used_detailed]', true, $lang, false, $page_name, $data); + $this->shortcode_formats('[format_codes_used_basic_es]', false, 'es', true, $page_name, $data); + $this->shortcode_formats('[format_codes_used_detailed_es]', true, 'es', true, $page_name, $data); + $this->shortcode_formats('[format_codes_used_basic_fr]', false, 'fr', true, $page_name, $data); + $this->shortcode_formats('[format_codes_all_basic]', false, $lang, true, $page_name, $data); + $this->shortcode_formats('[format_codes_all_detailed]', true, $lang, true, $page_name, $data); + } + private function shortcode_formats($shortcode, $detailed, $lang, $isAll, $page, &$str) + { + $pos = strpos($str, $shortcode); + if ($pos == false) { + return; + } + $value = ''; + if ($detailed) { + $value = $this->formatsManager->write_detailed_formats($lang, $isAll, $this->options[$page . '_line_height'], $this->options[$page . '_font_size'] . "pt"); + } else { + $value = $this->formatsManager->write_formats($lang, $isAll, $this->options[$page . '_line_height'], $this->options[$page . '_font_size'] . "pt"); + } + $str = substr($str, 0, $pos) . $value . substr($str, $pos + strlen($shortcode)); + } + private function get_field(array $obj, string $field): mixed + { + $value = ''; + if (isset($obj[$field])) { + $value = $this->bread->bmlt()->parse_field($obj[$field]); + } + return $value; + } + function write_additional_meetinglist() + { + if (isset($this->options['additional_list_template_content']) && trim($this->options['additional_list_template_content'])) { + $template = $this->options['additional_list_template_content']; + } else { + $template = $this->options['meeting_template_content']; + } + if (empty($this->options['additional_list_language'])) { + $this->options['additional_list_language'] = $this->options['weekday_language']; + } + $additional_list_query = false; + $additional_meetinglist_result = $this->result_meetings; + /** + * If we are selecting the meetings in the second list based on some format, we don't need another BMLT query. + * Except if its an service meeting (format code ASM). Then we need a second query because of the protected fields. + */ + if (empty($this->options['additional_list_format_key']) || $this->options['additional_list_format_key'] == 'ASM') { + $additional_list_query = true; + $sort_order = $this->options['additional_list_sort_order']; + if ($sort_order == 'same') { + $sort_order = 'weekday_tinyint,start_time'; + } + $additional_list_id = ""; + if ($this->options['additional_list_format_key'] === 'ASM') { + $additional_list_id = '&formats[]=' . $this->formatsManager->getFormatByKey($this->options['weekday_language'], 'ASM'); + } + $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$additional_list_id&sort_keys=$sort_order"; + // additional_list can contain E-Mail and phone numbers that require logins. + if ($this->options['additional_list_format_key'] === 'ASM') { + $additional_list_query .= "&advanced_published=0"; + } + $additional_meetinglist_result = $this->bread->bmlt()->get_configured_root_server_request($additional_list_query); + $this->adjust_timezone($additional_meetinglist_result, $this->target_timezone); + } + if ($additional_list_query || $this->options['weekday_language'] != $this->options['additional_list_language']) { + foreach ($additional_meetinglist_result as &$value) { + $value = $this->meetingEnhancer->enhance_meeting($value, $this->options['additional_list_language'], $this->formatsManager); + } + } + $meetingslistStructure = new Bread_Meetingslist_Structure($this->bread, $additional_meetinglist_result, $this->options['additional_list_language'], 1); + $this->writeMeetings($template, $meetingslistStructure); + return; + } + + private function adjust_timezone(&$meetings, $target_timezone) + { + if (!$target_timezone) { + return; + } + $target_midnight = new DateTime(); + $target_midnight->setTimezone($target_timezone); + $target_midnight->setTime(23, 59); + $target_yesterday = new DateTime(); + $target_yesterday->setTimezone($target_timezone); + $target_yesterday->setTime(0, 0); + foreach ($meetings as &$meeting) { + if (!empty($meeting['time_zone'])) { + $meeting_time_zone = timezone_open($meeting['time_zone']); + if ($meeting_time_zone) { + $date = date_create($meeting['start_time'], $meeting_time_zone); + date_timezone_set($date, $target_timezone); + $meeting['start_time'] = $date->format('H:i'); + if ($date >= $target_midnight) { + $meeting['weekday_tinyint'] = $meeting['weekday_tinyint'] + 1; + if ($meeting['weekday_tinyint'] == 8) { + $meeting['weekday_tinyint'] = 1; + } + } elseif ($date < $target_yesterday) { + $meeting['weekday_tinyint'] = $meeting['weekday_tinyint'] - 1; + if ($meeting['weekday_tinyint'] == 0) { + $meeting['weekday_tinyint'] = 7; + } + } + } + } + } + usort($meetings, array($this, "sortDayTime")); + } +} diff --git a/public/class-bread-format-manager.php b/public/class-bread-format-manager.php new file mode 100644 index 0000000..0283eea --- /dev/null +++ b/public/class-bread-format-manager.php @@ -0,0 +1,233 @@ +usedFormats[$lang] = $usedFormats; + $this->hashedFormats[$lang] = $this->hashFormats($usedFormats); + $this->defaultLang = $lang; + $this->bmlt = $bmlt; + } + /** + * Helper functtion to create a key=>value array of formats for convenient lookup + * + * @param array $formats the list of formats + * @return array The key=>value pairs of formats (key==key_string) + */ + private function hashFormats(array $formats): array + { + $ret = array(); + foreach ($formats as $format) { + $ret[$format['key_string']] = $format; + } + return $ret; + } + /** + * Retrieves the full list of formats for a particular language + * + * @param string $lang The language. + * @return void + */ + private function loadFormats(string $lang): void + { + if (isset($this->allFormats[$lang])) { + return; + } + $this->allFormats[$lang] = $this->bmlt->get_formats_by_language($lang); + $this->bmlt->sortBySubkey($this->allFormats[$lang], 'key_string'); + $this->hashedFormats[$lang] = $this->hashFormats($this->allFormats[$lang]); + } + /** + * ULookup the format having a particular field having a particular value. Null if none found. + * + * @param string $lang the language of the formats being searched. + * @param string $field + * @param string $id + * @return array|null + */ + private function getFormatFromField(string $lang, string $field, string $id): array|null + { + if (!isset($this->allFormats[$lang])) { + if (isset($this->usedFormats[$lang])) { + $found = $this->searchField($this->usedFormats[$lang], $id, $field); + if ($found != null) { + return $found; + } + } + $this->loadFormats($lang); + } + return $this->searchField($this->allFormats[$lang], $id, $field); + } + /** + * Do the actual search for a loaded set of formats. + * + * @param array $formats + * @param string $id + * @param string $field + * @return array|null + */ + private function searchField(array $formats, string $id, string $field): array|null + { + foreach ($formats as $format) { + if ($format[$field] == $id) { + return $format; + } + } + return null; + } + /** + * Lookup a format in the hashed list. + * + * @param string $lang + * @param string $key + * @return array|null + */ + public function getFormatByKey(string $lang, string $key): array|null + { + if (!isset($this->hashedFormats[$lang])) { + $this->loadFormats($lang); + } + if (isset($this->hashedFormats[$lang][$key])) { + return $this->hashedFormats[$lang][$key]; + } + if (isset($this->allFormats[$lang])) { + return null; + } + $this->loadFormats($lang); + if (isset($this->hashedFormats[$lang][$key])) { + return $this->hashedFormats[$lang][$key]; + } + return null; + } + /** + * Get the list of formats that were actually used, translated into the specified language. + * + * @param string $lang + * @return array + */ + public function getFormatsUsed(string $lang = ''): array + { + $lang = ($lang == '') ? $this->defaultLang : $lang; + if (!isset($this->usedFormats[$lang])) { + $this->loadFormats($lang); + $this->usedFormats[$lang] = array(); + foreach ($this->usedFormats[$this->defaultLang] as $usedFormat) { + $this->usedFormats[$lang][] = $this->getFormatFromField($lang, 'id', $usedFormat['id']); + } + } + return $this->usedFormats[$lang]; + } + public function getHashedFormats(string $lang): array + { + if (!isset($this->hashedFormats[$lang])) { + $this->loadFormats($lang); + } + return $this->hashedFormats[$lang]; + } + /** + * Generate the HTML table of formats. + * + * @param string $lang + * @param boolean $isAll All formats or only used. + * @param string $lineHeight + * @param string $fontSize + * @return void + */ + public function write_detailed_formats(string $lang, bool $isAll, string $lineHeight, string $fontSize) + { + $formats = $isAll ? $this->allFormats[$lang] : $this->getFormatsUsed($lang); + if (empty($formats)) { + return ''; + } + $data = ""; + foreach ($formats as $format) { + $data .= ""; + $data .= ""; + } + $data .= "
    " . $format['key_string'] . "(" . $format['name_string'] . ") " . $format['description_string'] . "
    "; + return $data; + } + /** + * Generate the HTML table of formats. + * + * @param string $lang + * @param boolean $isAll All formats or only used. + * @param string $lineHeight + * @param string $fontSize + * @return void + */ + public function write_formats(string $lang, bool $isAll, string $lineHeight, string $fontSize) + { + $formats = $isAll ? $this->allFormats[$lang] : $this->getFormatsUsed($lang); + if (empty($formats)) { + return ''; + } + $data = ""; + for ($count = 0; $count < count($formats); $count++) { + $data .= ''; + $data .= ""; + $data .= ""; + $count++; + if ($count >= count($formats)) { + $data .= ""; + $data .= ""; + } else { + $data .= ""; + $data .= ""; + } + $data .= ""; + } + $data .= "
    " . $formats[$count]['key_string'] . "" . $formats[$count]['name_string'] . "" . $formats[$count]['key_string'] . "" . $formats[$count]['name_string'] . "
    "; + return $data; + } + public function getWheelchairFormat($lang) + { + if (is_array($this->wheelchairFormat) && empty($this->wheelchairFormat)) { + $this->wheelchairFormat = $this->getFormatFromField($lang, 'world_id', 'WCHR'); + } + return $this->wheelchairFormat; + } +} diff --git a/public/class-bread-meeting-enhancer.php b/public/class-bread-meeting-enhancer.php new file mode 100644 index 0000000..6df918d --- /dev/null +++ b/public/class-bread-meeting-enhancer.php @@ -0,0 +1,118 @@ +bread = $bread; + $this->options = $bread->getOptions(); + $this->areas = $areas; + } + /** + * Enhance the meeting fields (in place) with calculated values. + * + * @param array $meeting_value the raw meeting values, as returned from the BMLT root server. + * @param string $lang The language used when generating format descriptions, etc. + * @return void + */ + public function enhance_meeting(&$meeting_value, $lang, $formatsManager) + { + $duration = explode(':', $meeting_value['duration_time']); + $minutes = intval($duration[0]) * 60 + intval($duration[1]) + intval($duration[2]); + $meeting_value['duration_m'] = $minutes; + $meeting_value['duration_h'] = rtrim(rtrim(number_format($minutes / 60, 2), 0), '.'); + $space = ' '; + if ($this->options['remove_space'] == 1) { + $space = ''; + } + if ($this->options['time_clock'] == null || $this->options['time_clock'] == '12' || $this->options['time_option'] == '') { + $time_format = "g:i" . $space . "A"; + } elseif ($this->options['time_clock'] == '24fr') { + $time_format = "H\hi"; + } else { + $time_format = "H:i"; + } + if ($this->options['time_option'] == 1 || $this->options['time_option'] == '') { + $meeting_value['start_time'] = date($time_format, strtotime($meeting_value['start_time'])); + if ($meeting_value['start_time'] == '12:00PM' || $meeting_value['start_time'] == '12:00 PM') { + $meeting_value['start_time'] = 'NOON'; + } + } elseif ($this->options['time_option'] == '2') { + $addtime = '+ ' . $minutes . ' minutes'; + $end_time = date($time_format, strtotime($meeting_value['start_time'] . ' ' . $addtime)); + $meeting_value['start_time'] = date($time_format, strtotime($meeting_value['start_time'])); + if ($lang == 'fa') { + $meeting_value['start_time'] = $this->toPersianNum($end_time) . $space . '-' . $space . $this->toPersianNum($meeting_value['start_time']); + } else { + $meeting_value['start_time'] = $meeting_value['start_time'] . $space . '-' . $space . $end_time; + } + } elseif ($this->options['time_option'] == '3') { + $time_array = array("1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00", "8:00", "9:00", "10:00", "11:00", "12:00"); + $temp_start_time = date("g:i", strtotime($meeting_value['start_time'])); + $temp_start_time_2 = date("g:iA", strtotime($meeting_value['start_time'])); + if ($temp_start_time_2 == '12:00PM') { + $start_time = 'NOON'; + } elseif (in_array($temp_start_time, $time_array)) { + $start_time = date("g", strtotime($meeting_value['start_time'])); + } else { + $start_time = date("g:i", strtotime($meeting_value['start_time'])); + } + $addtime = '+ ' . $minutes . ' minutes'; + $temp_end_time = date("g:iA", strtotime($meeting_value['start_time'] . ' ' . $addtime)); + $temp_end_time_2 = date("g:i", strtotime($meeting_value['start_time'] . ' ' . $addtime)); + if ($temp_end_time == '12:00PM') { + $end_time = 'NOON'; + } elseif (in_array($temp_end_time_2, $time_array)) { + $end_time = date("g" . $space . "A", strtotime($temp_end_time)); + } else { + $end_time = date("g:i" . $space . "A", strtotime($temp_end_time)); + } + $meeting_value['start_time'] = $start_time . $space . '-' . $space . $end_time; + } + + $meeting_value['day_abbr'] = $this->bread->getday($meeting_value['weekday_tinyint'], true, $lang); + $meeting_value['day'] = $this->bread->getday($meeting_value['weekday_tinyint'], false, $lang); + $area_name = $this->get_area_name($meeting_value); + $meeting_value['area_name'] = $area_name; + $meeting_value['area_i'] = substr($area_name, 0, 1); + + $meeting_value['wheelchair'] = ''; + $wheelchair_format = $formatsManager->getWheelchairFormat($this->options['weekday_language']); + if (!is_null($wheelchair_format)) { + $fmts = explode(',', $meeting_value['format_shared_id_list']); + if (in_array($wheelchair_format['id'], $fmts)) { + $meeting_value['wheelchair'] = ''; + } + } + // Extensions. + return apply_filters("Bread_Enrich_Meeting_Data", $meeting_value, $formatsManager->getHashedFormats($lang)); + } + private function get_area_name(array $meeting_value): string + { + foreach ($this->areas as $unique_area) { + $area_data = explode(',', $unique_area); + $area_id = $this->bread->arraySafeGet($area_data, 1); + if ($area_id === $meeting_value['service_body_bigint']) { + return $this->bread->arraySafeGet($area_data); + } + } + return ''; + } + + private function toPersianNum($number) + { + $number = str_replace("1", "۱", $number); + $number = str_replace("2", "۲", $number); + $number = str_replace("3", "۳", $number); + $number = str_replace("4", "۴", $number); + $number = str_replace("5", "۵", $number); + $number = str_replace("6", "۶", $number); + $number = str_replace("7", "۷", $number); + $number = str_replace("8", "۸", $number); + $number = str_replace("9", "۹", $number); + $number = str_replace("0", "۰", $number); + return $number; + } +} diff --git a/public/class-bread-meetingslist-structure.php b/public/class-bread-meetingslist-structure.php new file mode 100644 index 0000000..871c5e9 --- /dev/null +++ b/public/class-bread-meetingslist-structure.php @@ -0,0 +1,490 @@ +options['combine_headings'] = ''; + if ($meeting_sort === 'user_defined') { + if ($this->options['sub_header_shown'] == 'combined') { + $this->options['combine_headings'] = 'main_grouping - subgrouping'; + } + return; + } + unset($this->options['subgrouping']); + if ($meeting_sort === 'state') { + $this->options['main_grouping'] = 'location_province'; + $this->options['subgrouping'] = 'location_municipality'; + $this->options['combine_headings'] = 'subgrouping, main_grouping'; + } elseif ($meeting_sort === 'city') { + $this->options['main_grouping'] = 'location_municipality'; + } elseif ($meeting_sort === 'borough') { + $this->options['main_grouping'] = 'location_city_subsection'; + $this->options['main_grouping_suffix'] = $this->options['borough_suffix']; + } elseif ($meeting_sort === 'county') { + $this->options['main_grouping'] = 'location_sub_province'; + $this->options['main_grouping_alt_suffix'] = $this->options['county_suffix']; + } elseif ($meeting_sort === 'borough_county') { + $this->options['main_grouping'] = 'location_city_subsection'; + $this->options['main_grouping_suffix'] = $this->options['borough_suffix']; + $this->options['main_grouping_alt'] = 'location_sub_province'; + $this->options['main_grouping_alt_suffix'] = $this->options['county_suffix']; + } elseif ($meeting_sort === 'neighborhood_city') { + $this->options['main_grouping'] = 'location_neighborhood'; + $this->options['main_grouping_suffix'] = $this->options['neighborhood_suffix']; + $this->options['main_grouping_alt'] = 'location_municipality'; + $this->options['main_grouping_alt_suffix'] = $this->options['city_suffix']; + } elseif ($meeting_sort === 'group') { + $this->options['main_grouping'] = 'meeting_name'; + } elseif ($meeting_sort === 'weekday_area') { + $this->options['main_grouping'] = 'day'; + $this->options['subgrouping'] = 'service_body_bigint'; + } elseif ($meeting_sort === 'weekday_city') { + $this->options['main_grouping'] = 'day'; + $this->options['subgrouping'] = 'location_municipality'; + } elseif ($meeting_sort === 'weekday_county') { + $this->options['main_grouping'] = 'day'; + $this->options['subgrouping'] = 'location_sub_province'; + } else { + $this->options['main_grouping'] = 'day'; + } + } + /** + * Setup for structuring the meeting list + * + * @param Bread $bread The configuration of the meeting list. + * @param array $result_meetings The meetings in the meeting list. + * @param string $lang The language of the meeting list + * @param integer $include_additional_list Whether or not to include meetings that match the requirements of the additional list. Where + * 0 - let everything through + * 1 - only meetings with additional_list format + * -1 - only meetings without additional_list format + */ + function __construct(Bread $bread, array $result_meetings, string $lang, int $include_additional_list) + { + $this->bread = $bread; + $this->options = $bread->getOptions(); + $this->suppress_heading = $this->options['suppress_heading'] == 1; + + $meeting_sort = $this->options['meeting_sort']; + if ($include_additional_list > 0) { + $this->options['suppess_heading'] = 1; + switch ($this->options['additional_list_sort_order']) { + case 'meeting_name': + $meeting_sort = 'meeting_name'; + $this->suppress_heading = true; + break; + case 'weekday_tinyint,start_time': + $meeting_sort = 'day'; + $this->suppress_heading = true; + break; + default: + break; + } + } + $this->upgradeHeaderData($meeting_sort); + + $header_style = "color:" . $this->options['header_text_color'] . ";"; + $header_style .= "background-color:" . $this->options['header_background_color'] . ";"; + $header_style .= "font-size:" . $this->options['header_font_size'] . "pt;"; + $header_style .= "line-height:" . $this->options['content_line_height'] . ";"; + $header_style .= "text-align:center;padding-top:2px;padding-bottom:3px;"; + + if ($this->options['header_uppercase'] == 1) { + $header_style .= 'text-transform: uppercase;'; + } + if ($this->options['header_bold'] == 0) { + $header_style .= 'font-weight: normal;'; + } + if ($this->options['header_bold'] == 1) { + $header_style .= 'font-weight: bold;'; + } + $this->header_style = $header_style; + $this->cont = '(' . $bread->getTranslateTable()[$lang]['CONT'] . ')'; + + $this->headerMeetings = $this->getHeaderMeetings($result_meetings, $include_additional_list); + $this->unique_heading = $this->getUniqueHeadings($this->headerMeetings); + } + /** + * Iterates over the main headings in the meeting list + * + * @return array the list of sub-headings under this heading. If there are no sub-headings, an array with a single element is returned. At the end of the list, false is returned. + */ + public function iterateMainHeading(): array|bool + { + if ($this->main_index >= count($this->unique_heading)) { + return false; + } + $this->main_heading_raw = $this->unique_heading[$this->main_index++]; + if ($this->skip_heading($this->main_heading_raw)) { + return $this->iterateMainHeading(); + } + $unique_subheading = array_keys($this->headerMeetings[$this->main_heading_raw]); + asort($unique_subheading, SORT_NATURAL | SORT_FLAG_CASE); + $this->sub_index = 0; + return array_values($unique_subheading); + } + /** + * Iterates over the sub-headings in the current main heading. + * + * @param array $unique_subheading The list over which we are iterating. + * @param boolean $still_new a flag to indicate we are looking for the first subheading (we may skip some sub headings) + * @return array an array of the meetings in this subheading. At the end of the list, false is returned. + */ + public function iterateSubHeading(array $unique_subheading, bool $still_new = false): array|bool + { + if ($this->sub_index >= count($unique_subheading)) { + return false; + } + $this->newMainHeading = ($this->sub_index == 0) || $still_new; + $this->sub_heading_raw = $unique_subheading[$this->sub_index++]; + if ($this->skip_heading($this->sub_heading_raw)) { + return $this->iterateSubHeading($unique_subheading, $this->newMainHeading); + } + $this->meeting_index = 0; + return $this->headerMeetings[$this->main_heading_raw][$this->sub_heading_raw]; + } + /** + * Iterates over the meetings in the current sub-heading. + * + * @param array $meetings + * @return array The next meeting. At the end of the list, false is returned + */ + public function iterateMeetings(array $meetings): array|bool + { + if ($this->meeting_index >= count($meetings)) { + return false; + } + $this->newMainHeading = $this->newMainHeading && $this->meeting_index == 0; + return $meetings[$this->meeting_index++]; + } + /** + * Does the work of structuring the meeting list into heading, subheadings and meetings. + * + * @param array $result_meetings The meetings returned from the BMLT root server query. + * @param integer $include_additional_list Whether or not to include meetings that match the requirements of the additional list. Where + * 0 - let everything through + * 1 - only meetings with additional_list format + * -1 - only meetings without additional_list format + * @return array rray, with header text as key and array of subheadings as values. Each subheading is itself an array with the heading as key, and the meetings as values. + */ + private function getHeaderMeetings(array &$result_meetings, int $include_additional_list): array + { + $levels = $this->getHeaderLevels(); + $headerMeetings = array(); + foreach ($result_meetings as &$value) { + $additional_list_test = $this->additional_list_test($value, $include_additional_list == 1); + if ((($include_additional_list < 0 && $additional_list_test) || + ($include_additional_list > 0 && !$additional_list_test))) { + continue; + } + $main_grouping = $this->getHeaderItem($value, $this->setupDefaultHeading('main_')); + if (!isset($headerMeetings[$main_grouping])) { + $headerMeetings[$main_grouping] = array(); + if ($levels == 1) { + $headerMeetings[$main_grouping][0] = array(); + } + } + if ($levels == 2) { + $subgrouping = $this->getHeaderItem($value, $this->setupDefaultHeading('sub')); + if (!isset($headerMeetings[$main_grouping][$subgrouping])) { + $headerMeetings[$main_grouping][$subgrouping] = array(); + } + $headerMeetings[$main_grouping][$subgrouping][] = $value; + } else { + $headerMeetings[$main_grouping][0][] = $value; + } + } + return $headerMeetings; + } + /** + * Sort the headings alphabetically. + * + * @param array $headerMeetings Array, with header text as key and array of subheadings as values. Each subheading is itself an array with the heading as key, and the meetings as values. + * @return array The sorted list. + */ + public function getUniqueHeadings(array $headerMeetings): array + { + $unique_heading = array_keys($headerMeetings); + asort($unique_heading, SORT_NATURAL | SORT_FLAG_CASE); + return array_values($unique_heading); + } + /** + * Main headings may contain [numbers] in the beginning, in case you don't want to sort alphebetically. These number can be added to a "filter" + * extension to enhance_meeting. This was added for New Zealand, who wants their cities sorted north-south. This gets the heading that we want to print. + * + * @param string $this_heading The raw heading + * @return string The heading with the [number] removed. + */ + private function remove_sort_key(string $this_heading): string + { + if (mb_substr($this_heading, 0, 1) == '[') { + $end = strpos($this_heading, ']'); + if ($end > 0) { + return trim(substr($this_heading, $end + 1)); + } + } + return $this_heading; + } + /** + * If you want a heading to be skipped, the extension can add the sort key "[XXX]" to it. + * + * @param string $this_heading The raw heading + * @return bool true if the heading should be skipped. + */ + private function skip_heading(string $this_heading): bool + { + return (mb_substr($this_heading, 0, 5) == '[XXX]'); + } + private function getHeaderLevels(): int + { + if (!empty($this->options['subgrouping'])) { + return 2; + } + return 1; + } + private function setupDefaultHeading(string $level): array + { + return array( + 'name' => $level . 'grouping', + 'name_alt' => $level . 'grouping_alt', + 'name_suffix' => $level . 'grouping_suffix', + 'name_alt_suffix' => $level . 'grouping_alt_suffix', + ); + } + private function getHeaderItem(array $value, array $names): string + { + if (!$this->options[$names['name']]) { + return ''; + } + $grouping = ''; + $name = $this->options[$names['name']]; + if ($name == 'service_body_bigint') { + foreach ($this->bread->bmlt()->get_areas() as $unique_area) { + $area_data = explode(',', $unique_area); + $area_name = Bread::arraySafeGet($area_data); + $area_id = Bread::arraySafeGet($area_data, 1); + if ($area_id === $value['service_body_bigint']) { + return $area_name; + } + } + return 'Area not found'; + } elseif ($name == 'day') { + $off = intval($this->options['weekday_start']); + $day = intval($value['weekday_tinyint']); + if ($day < $off) { + $day = $day + 7; + } + return '[' . str_pad($day, 2, '0', STR_PAD_LEFT) . ']' . $value['day']; + } elseif (isset($value[$name])) { + $grouping = $this->bread->bmlt()->parse_field($value[$name]); + } + $suffix = $this->options[$names['name_suffix']] ?? ''; + if ($grouping == '' + && !empty($name_alt) + && isset($value[$name_alt]) + ) { + $grouping = $this->bread->bmlt()->parse_field($value[$name_alt]); + $suffix = $this->options[$names['name_alt_suffix']] ?? ''; + } + if (strlen(trim($grouping)) == 0) { + return 'NO DATA'; + } + if (!empty($suffix)) { + return $grouping . ' ' . $suffix; + } + return $grouping; + } + /** + * Does the meeting belong in the additional list. + * + * @param array $value the meeting. + * @param boolean $flag true if we are generating the additonal list. Hybrid meetings don't belong if virtual meetings are being selected. + * @return boolean true if the meeting belongs in the addional list. + */ + private function additional_list_test(array $value, $flag = false): bool + { + if (empty($this->options['additional_list_format_key'])) { + return false; + } + $format_key = $this->options['additional_list_format_key']; + if ($format_key == "@Virtual@") { + //TODO: Is this correct? For now, I'm just refactoring, so leaving it in. + if ($flag && $this->isHybrid($value)) { + return false; + } + return $this->isVirtual($value) || $this->isHybrid($value); + } + if ($format_key == "@F2F@") { + return !$this->isVirtual($value) || $this->isHybrid($value); + } + $enFormats = explode(",", $value['formats']); + return in_array($format_key, $enFormats); + } + private function isHybrid(array $value): bool + { + if (empty($value['formats'])) { + return false; + } + $enFormats = explode(",", $value['formats']); + return in_array('HY', $enFormats); + } + private function isVirtual(array $value): bool + { + if (empty($value['formats'])) { + return false; + } + $enFormats = explode(",", $value['formats']); + return in_array('VM', $enFormats); + } + /** + * Gets the heading for the current meeting, based on where we've iterated to in the Heading array + * + * @return string the current heading, if needed, as HTML/CSS. Or an empty string, if not. + */ + public function calculateHeading(): string + { + $header = ''; + if ($this->suppress_heading) { + return $header; + } + $this_heading = $this->remove_sort_key($this->main_heading_raw); + $this_subheading = $this->remove_sort_key($this->sub_heading_raw); + if (($this->meeting_index == 1) && !empty($options['combine_headings'])) { + $header_string = $this->options['combine_headings']; + $header_string = str_replace('main_grouping', $this_heading, $header_string); + $header_string = str_replace('subgrouping', $this_subheading, $header_string); + $header .= "
    " . $header_string . "
    "; + } elseif (!empty($this->options['subgrouping'])) { + if ($this->newMainHeading) { + $xtraMargin = ''; + if (!$this->main_index > 1 or $this->meeting_index > 1) { + $xtraMargin = 'margin-top:2pt;'; + } + $header .= '
    ' . $this_heading . "
    "; + } + if (($this->meeting_index == 1) && $this->options['sub_header_shown'] == 'display') { + $header .= "

    " . $this_subheading . "

    "; + } + } elseif ($this->newMainHeading) { + $header .= '
    ' . $this_heading . "
    "; + } + return $header; + } + /** + * When moving between pages/columns, get an appropriate "Continued" HTML. + * + * @return string the header HTML. + */ + public function calculateContHeader(): string + { + $header = ''; + $cont = ''; + if ($this->suppress_heading == 1) { + return $header; + } + if (!$this->options['cont_header_shown']) { + return $header; + } + if (!empty($this->options['combine_headings'])) { + if (!$this->newMainHeading && $this->meeting_index == 1) { + $cont = $this->cont; + } + $header_string = $this->options['combine_headings']; + $header_string = str_replace('main_grouping', $this->remove_sort_key($this->main_heading_raw), $header_string); + $header_string = str_replace('subgrouping', $this->remove_sort_key($this->main_heading_raw), $header_string); + $header .= "
    " . $header_string . $cont . "
    "; + } else if (!$this->newMainHeading) { + $cont = $this->cont; + } + $header = "
    " . $this->remove_sort_key($this->main_heading_raw) . " " . $cont . "
    "; + + return $header; + } +} diff --git a/public/class-bread-public.php b/public/class-bread-public.php new file mode 100644 index 0000000..d000026 --- /dev/null +++ b/public/class-bread-public.php @@ -0,0 +1,591 @@ + + */ +class Bread_Public +{ + + /** + * The ID of this plugin. + * + * @since 2.8.0 + * @access private + * @var string $plugin_name The ID of this plugin. + */ + private string $plugin_name; + + /** + * The version of this plugin. + * + * @since 2.8.0 + * @access private + * @var string $version The current version of this plugin. + */ + private string $version; + /** + * The settings/ meeting list configuration. Everything that was filled in in the WP Backend. + * + * @since 2.8.0 + * @access private + * @var array The settings/ meeting list configuration. Everything that was filled in in the WP Backend. + */ + private array $options; + /** + * Does the work of translating the HTML to PDF. + * + * @since 2.8.0 + * @access private + * @var object Does the work of translating the HTML to PDF. + */ + private Mpdf $mpdf; + private Bread $bread; + /** + * Initialize the class and set its properties. + * + * @since 2.8.0 + * @param string $plugin_name The name of the plugin. + * @param string $version The version of this plugin. + */ + public function __construct($plugin_name, $version, $bread) + { + $this->plugin_name = $plugin_name; + $this->version = $version; + $this->bread = $bread; + $this->options = $bread->getOptions(); + } + + /** + * Register the stylesheets for the public-facing side of the site. + * + * @since 2.8.0 + */ + public function enqueue_styles() + { + wp_enqueue_style($this->plugin_name, plugin_dir_url(__FILE__) . 'css/bread-public.css', array(), $this->version, 'all'); + } + + /** + * Register the JavaScript for the public-facing side of the site. + * + * @since 2.8.0 + */ + public function enqueue_scripts() + { + wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/bread-public.js', array('jquery'), $this->version, false); + } + + public function bmlt_meeting_list($atts = null, $content = null) + { + if (!$this->bread->generatingMeetingList()) { + return; + } + $this->options = $this->bread->getConfigurationForSettingId($this->bread->getRequestedSetting()); + $import_streams = []; + ini_set('max_execution_time', 600); // tomato server can take a long time to generate a schedule, override the server setting + + if ($this->options['root_server'] == '') { + 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'] == '')) { + 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; + } + if (headers_sent()) { + echo '

    Headers already sent before Meeting List generation

    '; + exit; + } + + if (intval($this->options['cache_time']) > 0 && ! isset($_GET['nocache']) + && ! isset($_GET['custom_query']) + ) { + if (false !== ($content = get_transient($this->bread->get_TransientKey($this->bread->getRequestedSetting())))) { + $content = pack("H*", $content); + $name = $this->get_FilePath(); + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($content)); + header('Content-disposition: inline; filename="' . $name . '"'); + header('Cache-Control: public, must-revalidate, max-age=0'); + header('Pragma: public'); + header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + echo $content; + 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); + @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 + // by simply clobbering the other definitions. Since we generate the PDF then + // die, we shouldn't create any conflicts ourselves. + 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'; + $this->mpdf = new mPDF($mpdf_init_options); + $this->mpdf->setAutoBottomMargin = 'pad'; + $this->mpdf->shrink_tables_to_fit = 1; + + $this->mpdf->simpleTables = false; + $this->mpdf->useSubstitutions = false; + $this->mpdf->mirrorMargins = false; + $this->mpdf->list_indent_first_level = 1; // 1 or 0 - whether to indent the first level of a list + // LOAD a stylesheet + $header_stylesheet = file_get_contents(plugin_dir_path(__FILE__) . 'css/mpdfstyletables.css'); + $this->mpdf->WriteHTML($header_stylesheet, 1); // The parameter 1 tells that this is css/style only and no body/html/text + $this->mpdf->SetDefaultBodyCSS('line-height', $this->options['content_line_height']); + $this->mpdf->SetDefaultBodyCSS('background-color', '#ffffff00'); + 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); + } + $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); + } + + if ($result == null) { + echo ""; + echo '

    No Meetings Found

    Or

    Internet or Server Problem

    ' . $this->options['root_server'] . '

    Please try again or contact your BMLT Administrator

    '; + exit; + } + if (!empty($this->options['extra_meetings'])) { + $extras = ""; + foreach ((array)$this->options['extra_meetings'] as $value) { + $data = array(" [", "]"); + $value = str_replace($data, "", $value); + $extras .= "&meeting_ids[]=" . $value; + } + + $extra_result = $this->bread->bmlt()->get_configured_root_server_request("client_interface/json/?switcher=GetSearchResults&sort_keys=" . $sort_keys . "" . $extras . "" . $get_used_formats . $select_language); + $formatsManager = null; + if ($extra_result <> null) { + $result_meetings = array_merge($result['meetings'], $extra_result['meetings']); + foreach ($result_meetings as $key => $row) { + $weekday[$key] = $row['weekday_tinyint']; + $start_time[$key] = $row['start_time']; + } + + array_multisort($weekday, SORT_ASC, $start_time, SORT_ASC, $result_meetings); + $formatsManager = new Bread_FormatsManager(array_merge($result['formats'], $extra_result['formats']), $this->options['weekday_language'], $this->bread->bmlt()); + } else { + $formatsManager = new Bread_FormatsManager($result['formats'], $this->options['weekday_language'], $this->bread->bmlt()); + $result_meetings = $result['meetings']; + } + } else { + $formatsManager = new Bread_FormatsManager($result['formats'], $this->options['weekday_language'], $this->bread->bmlt()); + $result_meetings = $result['meetings']; + } + + if ($this->options['additional_list_language'] == '') { + $this->options['additional_list_language'] = $this->options['weekday_language']; + } + $num_columns = 0; + if ($this->options['page_fold'] === 'full' || $this->options['page_fold'] === 'half' || $this->options['page_fold'] === 'flyer') { + $num_columns = 0; + } elseif ($this->options['page_fold'] === 'tri') { + $num_columns = 3; + } elseif ($this->options['page_fold'] === 'quad') { + $num_columns = 4; + } elseif ($this->options['page_fold'] === '') { + $this->options['page_fold'] = 'quad'; + $num_columns = 4; + } + $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); + 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']); + } + if (headers_sent()) { + echo '

    Headers already sent before PDF generation

    '; + } else { + if (intval($this->options['cache_time']) > 0 && ! isset($_GET['nocache']) + && !isset($_GET['custom_query']) + ) { + $content = $this->mpdf->Output('', 'S'); + $content = bin2hex($content); + $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())); + $this->mpdf->Output($FilePath, 'I'); + } + foreach ($import_streams as $FilePath => $stream) { + @unlink($FilePath); + } + $this->bread->removeTempDir(); + exit; + } + private function constuct_page_type_settings() + { + $page_type_settings = array(); + // TODO: The page number is always 5 from botton...this should be adjustable + if ($this->options['page_fold'] == 'half') { + if ($this->options['page_size'] == 'letter') { + $page_type_settings = ['format' => array(139.7, 215.9), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'legal') { + $page_type_settings = ['format' => array(177.8, 215.9), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'ledger') { + $page_type_settings = ['format' => 'letter-P', 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'A4') { + $page_type_settings = ['format' => 'A5-P', 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'A5') { + $page_type_settings = ['format' => 'A6-P', 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == '5inch') { + $page_type_settings = ['format' => array(197.2, 279.4), 'margin_footer' => $this->options['margin_footer']]; + } + } elseif ($this->options['page_fold'] == 'flyer') { + if ($this->options['page_size'] == 'letter') { + $page_type_settings = ['format' => array(93.13, 215.9), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'legal') { + $page_type_settings = ['format' => array(118.53, 215.9), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'ledger') { + $page_type_settings = ['format' => array(143.93, 279.4), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == 'A4') { + $page_type_settings = ['format' => array(99.0, 210.0), 'margin_footer' => $this->options['margin_footer']]; + } + } elseif ($this->options['page_size'] == '5inch') { + $this->options['page_fold'] = 'full'; + $page_type_settings = ['format' => array(197.2, 279.4), 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_fold'] == 'full') { + $ps = $this->options['page_size']; + if ($ps == 'ledger') { + $ps = 'tabloid'; + } + $page_type_settings = ['format' => $ps . "-" . $this->options['page_orientation'], 'margin_footer' => $this->options['margin_footer']]; + } elseif ($this->options['page_size'] == '5inch') { + $page_type_settings = ['format' => array(197.2, 279.4), 'margin_footer' => $this->options['margin_footer']]; + } else { + $ps = $this->options['page_size']; + if ($ps == 'ledger') { + $ps = 'tabloid'; + } + $page_type_settings = ['format' => $ps . "-" . $this->options['page_orientation'], 'margin_footer' => 0]; + } + return $page_type_settings; + } + private function construct_init_options($default_font, $mode, $page_type_settings): array + { + if ($default_font == 'arial' || $default_font == 'times' || $default_font == 'courier') { + $mpdf_init_options = [ + 'fontDir' => array( + __DIR__ . '/mpdf/vendor/mpdf/mpdf/ttfonts', + __DIR__ . '/fonts', + ), + 'tempDir' => $this->bread->temp_dir(), + 'mode' => $mode, + 'default_font_size' => 7, + 'fontdata' => [ + "arial" => [ + 'R' => "Arial.ttf", + 'B' => "ArialBold.ttf", + 'I' => "ArialItalic.ttf", + 'BI' => "ArialBoldItalic.ttf", + ], + "times" => [ + 'R' => "Times.ttf", + 'B' => "TimesBold.ttf", + 'I' => "TimesItalic.ttf", + 'BI' => "TimesBoldItalic.ttf", + ], + "courier" => [ + 'R' => "CourierNew.ttf", + 'B' => "CourierNewBold.ttf", + 'I' => "CourierNewItalic.ttf", + 'BI' => "CourierNewBoldItalic.ttf", + ] + ], + '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'], + ]; + } 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'], + ]; + } + $mpdf_init_options['restrictColorSpace'] = $this->options['colorspace']; + $mpdf_init_options = array_merge($mpdf_init_options, $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) + { + $html = ''; + if ($this->options['page_fold'] == 'tri') { + $html .= ' + + + + + + + +
       
    '; + } + if ($this->options['page_fold'] == 'quad') { + $html .= ' + + + + + + + + +
        
    '; + } + $mpdf_column = new mPDF( + [ + 'mode' => $mode, + 'tempDir' => $this->bread->temp_dir(), + 'format' => $format, + '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_footer' => 0, + 'orientation' => 'P', + 'restrictColorSpace' => $this->options['colorspace'], + ] + ); + + $mpdf_column->WriteHTML($html); + $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_column'); + $mpdf_column->Output($FilePath, 'F'); + $h = \fopen($FilePath, 'rb'); + $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); + $import_streams[$FilePath] = $stream; + $pagecount = $this->mpdf->SetSourceFile($stream); + $tplId = $this->mpdf->importPage($pagecount); + $this->mpdf->SetPageTemplate($tplId); + } + private function reorder_booklet_pages($mode) + { + 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, + 'tempDir' => $this->bread->temp_dir(), + 'default_font_size' => '', + 'margin_left' => 0, + 'margin_right' => 0, + 'margin_top' => 0, + 'margin_bottom' => 0, + 'margin_footer' => 0, + 'orientation' => 'L', + 'restrictColorSpace' => $this->options['colorspace'], + ]; + $ps = $this->options['page_size']; + if ($ps == 'ledger') { + $mpdfOptions['format'] = 'tabloid'; + } elseif ($ps == '5inch') { + $mpdfOptions['format'] = array(197.2, 279.4); + } else { + $mpdfOptions['format'] = $ps . '-L'; + } + $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); + $mpdftmp = new mPDF($mpdfOptions); + $this->mpdf->shrink_tables_to_fit = 1; + $ow = $mpdftmp->h; + $oh = $mpdftmp->w; + $pw = $mpdftmp->w / 2; + $ph = $mpdftmp->h; + $h = \fopen($FilePath, 'rb'); + $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); + $import_streams[$FilePath] = $stream; + $pagecount = $mpdftmp->SetSourceFile($stream); + $pp = $this->get_booklet_pages($pagecount); + foreach ($pp as $v) { + $mpdftmp->AddPage(); + if ($v[0] > 0 & $v[0] <= $pagecount) { + $tplIdx = $mpdftmp->importPage($v[0]); + $mpdftmp->UseTemplate($tplIdx, 0, 0, $pw, $ph); + } + if ($v[1] > 0 & $v[1] <= $pagecount) { + $tplIdx = $mpdftmp->importPage($v[1]); + $mpdftmp->UseTemplate($tplIdx, $pw, 0, $pw, $ph); + } + } + $this->mpdf = $mpdftmp; + } 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'); + $mpdfOptions = [ + 'mode' => $mode, + 'tempDir' => $this->bread->temp_dir(), + 'default_font_size' => '', + 'margin_left' => 0, + 'margin_right' => 0, + 'margin_top' => 0, + 'margin_bottom' => 0, + 'margin_footer' => 6, + 'orientation' => $this->options['page_orientation'], + 'restrictColorSpace' => $this->options['colorspace'], + ]; + $mpdfOptions['format'] = $this->options['page_size'] . "-" . $this->options['page_orientation']; + $mpdfOptions = apply_filters("Bread_Mpdf_Init_Options", $mpdfOptions, $this->options); + $mpdftmp = new mPDF($mpdfOptions); + $this->mpdf->shrink_tables_to_fit = 1; + //$mpdftmp->SetImportUse(); + $h = \fopen($FilePath, 'rb'); + $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); + $import_streams[$FilePath] = $stream; + $np = $mpdftmp->SetSourceFile($stream); + $pp = 4 * ceil($np / 4); + for ($i = 1; $i < $np; $i++) { + $mpdftmp->AddPage(); + $tplIdx = $mpdftmp->ImportPage($i); + $mpdftmp->UseTemplate($tplIdx); + } + for ($i = $np; $i < $pp; $i++) { + $mpdftmp->AddPage(); + } + $mpdftmp->AddPage(); + $tplIdx = $mpdftmp->ImportPage($np); + $mpdftmp->UseTemplate($tplIdx); + $this->mpdf = $mpdftmp; + } else if ($this->options['page_fold'] == 'flyer') { + $FilePath = $this->bread->temp_dir() . DIRECTORY_SEPARATOR . $this->get_FilePath('_flyer'); + $this->mpdf->Output($FilePath, 'F'); + $mpdfOptions = [ + 'mode' => $mode, + 'tempDir' => $this->bread->temp_dir(), + 'default_font_size' => '', + 'margin_left' => 0, + 'margin_right' => 0, + 'margin_top' => 0, + 'margin_bottom' => 0, + 'margin_footer' => 6, + 'format' => $this->options['page_size'] . '-L', + 'orientation' => 'L', + 'restrictColorSpace' => $this->options['colorspace'], + ]; + $mpdftmp = new mPDF($mpdfOptions); + $this->mpdf->shrink_tables_to_fit = 1; + //$mpdftmp->SetImportUse(); + $h = \fopen($FilePath, 'rb'); + $stream = new \setasign\Fpdi\PdfParser\StreamReader($h, false); + $import_streams[$FilePath] = $stream; + $np = $mpdftmp->SetSourceFile($stream); + $ow = $mpdftmp->w; + $oh = $mpdftmp->h; + $fw = $ow / 3; + $mpdftmp->AddPage(); + $tplIdx = $mpdftmp->importPage(1); + $mpdftmp->UseTemplate($tplIdx, 0, 0); + $mpdftmp->UseTemplate($tplIdx, $fw, 0); + $mpdftmp->UseTemplate($tplIdx, $fw + $fw, 0); + $sep = $this->columnSeparators($oh); + if (!empty($sep)) { + $mpdftmp->writeHTML($sep); + } + $mpdftmp->AddPage(); + $tplIdx = $mpdftmp->ImportPage(2); + $mpdftmp->UseTemplate($tplIdx, 0, 0); + $mpdftmp->UseTemplate($tplIdx, $fw, 0); + $mpdftmp->UseTemplate($tplIdx, $fw + $fw, 0); + if (!empty($sep)) { + $mpdftmp->writeHTML($sep); + } + $this->mpdf = $mpdftmp; + } + } + function get_booklet_pages($np, $backcover = true) + { + $lastpage = $np; + $np = 4 * ceil($np / 4); + $pp = array(); + for ($i = 1; $i <= $np / 2; $i++) { + $p1 = $np - $i + 1; + if ($backcover) { + if ($i == 1) { + $p1 = $lastpage; + } else if ($p1 >= $lastpage) { + $p1 = 0; + } + } + if ($i % 2 == 1) { + $pp[] = array($p1, $i); + } else { + $pp[] = array($i, $p1); + } + } + return $pp; + } + function get_FilePath($pos = '') + { + $site = ''; + if (is_multisite()) { + $site = get_current_blog_id() . '_'; + } + return "meetinglist_" . $site . $this->bread->getRequestedSetting() . $pos . '_' . strtolower(date("njYghis")) . ".pdf"; + } + + function getSingleLanguage($lang) + { + return substr($lang, 0, 2); + } + function columnSeparators($oh) + { + if ($this->options['column_line'] == 1) { + return ' + + + + + + + + +
       
    '; + } + } +} diff --git a/public/css/bread-public.css b/public/css/bread-public.css new file mode 100644 index 0000000..65bbf96 --- /dev/null +++ b/public/css/bread-public.css @@ -0,0 +1,4 @@ +/** + * All of the CSS for your public-facing functionality should be + * included in this file. + */ \ No newline at end of file diff --git a/css/mpdfstyletables.css b/public/css/mpdfstyletables.css similarity index 100% rename from css/mpdfstyletables.css rename to public/css/mpdfstyletables.css diff --git a/includes/wheelchair.png b/public/css/wheelchair.png similarity index 100% rename from includes/wheelchair.png rename to public/css/wheelchair.png diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..8142269 --- /dev/null +++ b/public/index.php @@ -0,0 +1 @@ + + + \ No newline at end of file diff --git a/readme.txt b/readme.txt index 15b8496..116ebe3 100644 --- a/readme.txt +++ b/readme.txt @@ -4,8 +4,8 @@ Contributors: odathp, radius314, pjaudiomv, klgrimley, jbraswell, otrok7, alanb2 Tags: meeting list, bmlt, narcotics anonymous, na Requires PHP: 8.1 Requires at least: 6.2 -Tested up to: 6.6.1 -Stable tag: 2.7.14 +Tested up to: 6.7.1 +Stable tag: 2.8.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -55,6 +55,14 @@ Follow all these steps, keep in mind that once you start using bread, it's not g == Changelog == += 2.8.0 = +* Wizard to help getting started on Bread +* Preview meeting lists without saving +* UI Improvements including combining with crouton in WP Admin-Menu +* Major refactoring of code structure +* Removed ASM table as alternative to additional meeting template +* Removed BMLT Login during printing of additional meetings. + = 2.7.13 = * Multilingual [month_upper]. @@ -172,7 +180,7 @@ Follow all these steps, keep in mind that once you start using bread, it's not g * Access to configure is now controlled with a custom capability called `manage_bread`. This is automatically added to the `Administrator` role. * Include additional (secondary) meeting list. This can be used to provide a seperate list of special interest or foreign language meetings, or to list meetings that for some - reason are not included in the main list. This is a generalized version of Bread 1's "ASM" functionality, + reason are not included in the main list. This is a generalized version of Bread 1's "additional_list" functionality, which was used to list area service meetings. * Italian, German and Farsi support. * New layout options @@ -301,7 +309,7 @@ Follow all these steps, keep in mind that once you start using bread, it's not g = 1.3.0 = * Bumped up the HTTP GET timeout from 30 seconds to 2 mins. -* Allows for specifying an unpublished ASM (must use credentials. +* Allows for specifying an unpublished additional_list (must use credentials. * Restructured "Special Features" section. * Removed hardcodings inherited from legacy code base. * Cleaned up some dead code. diff --git a/simplify-mpdf.sh b/simplify-mpdf.sh index 47ffdfc..7eceb55 100755 --- a/simplify-mpdf.sh +++ b/simplify-mpdf.sh @@ -1 +1 @@ -find mpdf/vendor/mpdf/mpdf/ttfonts -type f ! -name 'DejaVu*.ttf' -delete +find vendor/mpdf/mpdf/ttfonts -type f ! -name 'DejaVu*.ttf' -delete diff --git a/tests/BreadFormatsManagerTest.php b/tests/BreadFormatsManagerTest.php new file mode 100644 index 0000000..2cf17ca --- /dev/null +++ b/tests/BreadFormatsManagerTest.php @@ -0,0 +1,33 @@ +getFormats($usedFormat), $lang, $bmlt); + } + public function testGetFormatsUsed() + { + $bread = new Bread([]); + $bread->bmlt()->setFormatBase('german-formats'); + $mgr = $this->getFormatMgr('berlin-formats-de', 'de', $bread->bmlt()); + $used = $mgr->getFormatsUsed(); + assertEquals(50, count($used)); + $o1 = $mgr->getFormatByKey('de', 'O1'); + assertNotNull($o1); + $o1e = $mgr->getFormatByKey('en', 'O1'); + assertNotNull($o1e); + $used = $mgr->getFormatsUsed('en'); + assertEquals(50, count($used)); + } +} diff --git a/tests/BreadMeetingEnhancerTest.php b/tests/BreadMeetingEnhancerTest.php new file mode 100644 index 0000000..e89e2e9 --- /dev/null +++ b/tests/BreadMeetingEnhancerTest.php @@ -0,0 +1,33 @@ +getFormats($usedFormat), $lang, $bmlt); + } + public function testMeetingEnhancer() + { + $bread = new Bread([]); + $bread->bmlt()->setFormatBase('german-formats'); + $mgr = $this->getFormatMgr('berlin-formats-de', 'de', $bread->bmlt()); + $used = $mgr->getFormatsUsed(); + assertEquals(50, count($used)); + $o1 = $mgr->getFormatByKey('de', 'O1'); + assertNotNull($o1); + $o1e = $mgr->getFormatByKey('en', 'O1'); + assertNotNull($o1e); + $used = $mgr->getFormatsUsed('en'); + assertEquals(50, count($used)); + } +} diff --git a/tests/BreadMeetinglistStructureTest.php b/tests/BreadMeetinglistStructureTest.php new file mode 100644 index 0000000..ccd24b7 --- /dev/null +++ b/tests/BreadMeetinglistStructureTest.php @@ -0,0 +1,195 @@ +getFormats($usedFormat), $lang, $bread->bmlt()); + } + private function enhanceMeetings(&$meetings, Bread $bread, $formatMgr) + { + $enhancer = new Bread_Meeting_Enhancer($bread, array()); + foreach ($meetings as &$meeting) { + $meeting = $enhancer->enhance_meeting($meeting, 'de', $formatMgr); + } + } + public function calculateExpectedHeadingStyle($options): string + { + $header_style = "color:" . $options['header_text_color'] . ";"; + $header_style .= "background-color:" . $options['header_background_color'] . ";"; + $header_style .= "font-size:" . $options['header_font_size'] . "pt;"; + $header_style .= "line-height:" . $options['content_line_height'] . ";"; + $header_style .= "text-align:center;padding-top:2px;padding-bottom:3px;"; + if ($options['header_uppercase'] == 1) { + $header_style .= 'text-transform: uppercase;'; + } + if ($options['header_bold'] == 0) { + $header_style .= 'font-weight: normal;'; + } + if ($options['header_bold'] == 1) { + $header_style .= 'font-weight: bold;'; + } + return $header_style; + } + public function testBerlinByDayMain() + { + $this->doTest( + 'berlin-booklet', + [], + 'berlin', + 'berlin-formats-de', + 'german-formats', + -1, + ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'], + [[0], [0], [0], [0], [0], [0], [0]], + 'de' + ); + } + public function testBerlinByDayAdditional() + { + $this->doTest( + 'berlin-booklet', + [ + 'additional_list_sort_order' => 'weekday_tinyint,start_time' + ], + 'berlin', + 'berlin-formats-de', + 'german-formats', + 1, + ['', '', '', '', '', '', ''], + [[0], [0], [0], [0], [0], [0], [0]], + 'de' + ); + } + public function testBerlinByDayAdditionalSortSame() + { + $this->doTest( + 'berlin-booklet', + [], + 'berlin', + 'berlin-formats-de', + 'german-formats', + 1, + ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'], + [[0], [0], [0], [0], [0], [0], [0]], + 'de' + ); + } + public function testBerlinByCityPlusDayMain() + { + $this->doTest( + 'berlin-by-city-plus-day', + [], + 'berlin', + 'berlin-formats-de', + 'german-formats', + -1, + ['Berlin', 'Dallgow-Döberitz', 'Eberswalde', 'Potsdam', 'Rathenow'], + [[0], [0], [0], [0], [0]], + 'de' + ); + } + public function testBerlinByCityPlusDayAdditional() + { + $this->doTest( + 'berlin-booklet', + [ + 'additional_list_sort_order' => 'weekday_tinyint,start_time' + ], + 'berlin', + 'berlin-formats-de', + 'german-formats', + 1, + ['', '', '', '', '', '', ''], + [[0], [0], [0], [0], [0], [0], [0]], + 'de' + ); + } + public function testBerlinByDayThenCityPlusDayAdditionalMain() + { + $this->doTest( + 'berlin-by-day-then-city-plus-day', + [], + 'berlin', + 'berlin-formats-de', + 'german-formats', + -1, + ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'], + [['Berlin',], ['Berlin', 'Potsdam', 'Rathenow'], ['Berlin', 'Dallgow-Döberitz', 'Eberswalde'], ['Berlin', 'Potsdam'], ['Berlin',], ['Berlin', 'Potsdam'], ['Berlin', 'Potsdam']], + 'de' + ); + } + public function testBerlinByDayThenCityPlusDayAdditional() + { + $this->doTest( + 'berlin-by-day-then-city-plus-day', + [], + 'berlin', + 'berlin-formats-de', + 'german-formats', + 1, + ['', '', '', '', '', '', ''], + [[0], [0], [0], [0], [0], [0], [0]], + 'de' + ); + } + public function doTest($config, $changes, $meetingJson, $usedFormats, $formatBase, $include, $expectedHeading, $expectedSubHeading, $lang): void + { + $options = $this->getConfiguration($config); + foreach ($changes as $key => $value) { + $options[$key] = $value; + } + $bread = new Bread($options); + $meetings = $this->getMeetings($meetingJson); + $bread->bmlt()->setFormatBase($formatBase); + $formatMgr = $this->getFormatMgr($usedFormats, $lang, $bread); + $this->enhanceMeetings($meetings, $bread, $formatMgr); + + $bms = new Bread_Meetingslist_Structure($bread, $meetings, $lang, $include); + $knt = 0; + $expectedHeaderStyle = $this->calculateExpectedHeadingStyle($options); + while ($subs = $bms->iterateMainHeading()) { + assertEquals(count($expectedSubHeading[$knt]), count($subs)); + $knt++; + $knt2 = 0; + while ($meetings = $bms->iterateSubHeading($subs)) { + $expected = ''; + if ($knt2++ == 0 && !empty($expectedHeading[$knt - 1])) { + $expected = '
    ' . $expectedHeading[$knt - 1] . "
    "; + } + $knt3 = 0; + while ($meeting = $bms->iterateMeetings($meetings)) { + $expectedSub = ''; + if ($knt3++ == 0) { + if (!empty($subs[$knt2 - 1])) { + $expectedSub = "

    " . $subs[$knt2 - 1] . "

    "; + } + } else { + $expected = ''; + } + assertEquals($expected . $expectedSub, $bms->calculateHeading()); + } + } + } + assertEquals(count($expectedHeading), $knt); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..cd2758b --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ +\"\"<\/p>\r\n


    [month_lower] [year]

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

     <\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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

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

    NA - Eine Gemeinschaft genesender S\u00fcchtiger

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

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

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

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

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

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

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

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

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

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

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

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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


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

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

    [page_break]<\/p>", + "front_line_height": "1.4", + "area_service_meetings_line_height": "1.0", + "content_font_size": 8, + "content_line_height": 1.100000000000000088817841970012523233890533447265625, + "front_page_font_size": 8, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "page_layout": null, + "last_page_content": "

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    ONLINE-MEETINGS<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]
    [page_break]<\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    GSK (Gebiets-Service-Konferenz)<\/b><\/span><\/p>\r\n

    F\u00fcr den aktuellen Termin und Ort der GSK bitte www.na-berlin.de\/gsk<\/strong> besuchen. Oder scanne einfach den QR-Code<\/span>
    Kontakt: gsk-chair@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nInfo-Komitee Berlin (K&E und \u00d6A Berlin)
    <\/span><\/strong><\/span>17:30 - 19:30\u00a0 <\/strong>Jeden 3. Mittwoch im Monat
    <\/strong><\/span>NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: info@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    \u00a0<\/span><\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nBerliner Convention-Vorbereitungs-Komitee
    <\/b>F\u00fcr aktuellen Termin & Ort bitte QR-Code scannen.<\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: bcvk@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nWebteam
    17:30 - 19:00 Jeden 1. Donnerstag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: webteam@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    Anti-Sexismus-Komitee
    19:30-21:00 Jeden 4. Montag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: ask@na-berlin.de, hilfeag@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\n\r\n

    NA-Basis-Komitee
    17:00-18:30 Jeden 1. Dienstag im Monat<\/b><\/span>
    NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: basis@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    KONTAKT \/ INFORMATION<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

    INTERNET<\/strong><\/span>
    www.na-berlin.de<\/span>
    info@na-berlin.de<\/span><\/p>\r\n


    ANSCHRIFT<\/strong><\/span>
    NASKB e.V.<\/span>
    Franz-Mehring-Platz 1
    10243 Berlin<\/span><\/p>\r\n


    Bankverbindung<\/strong><\/span>
    NASKB e.V.<\/span>
    IBAN: DE 71 100 100 100 470 988 109<\/span>
    BIC (Swift): PBNKDEFF<\/span><\/p>\r\n

     <\/p>\r\n


    \u00c4nderungen: meetingsliste@na-berlin.de
    <\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    Die aktuellsten Informationen zu den Meetings sind unter www.na-berlin.de\/meetings<\/strong> zu finden. Oder scanne einfach den QR-Code.<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    \\\"Bild: Freepik.com\\\". Die Grafiken dieser Meetingsliste wurden mit Ressourcen von Freepik.com erstellt.<\/span><\/p>\r\n

    [page_break no_page_number]<\/p>\r\n\r\n\r\n\r\n
    TELEFONNUMMERN<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

    \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span><\/p>", + "front_page_line_height": "1.2", + "last_page_line_height": 1.100000000000000088817841970012523233890533447265625, + "time_option": 2, + "remove_space": true, + "page_size": "A6", + "page_fold": "full", + "meeting_sort": "day", + "cache_time": 0, + "show_status": null, + "use_custom_section": null, + "custom_section_content": "", + "custom_section_line_height": 1, + "page_orientation": "P", + "meeting_template": "", + "area_service_meetings_font_size": "9", + "which_format_codes": "used", + "custom_section_font_size": 7.70000000000000017763568394002504646778106689453125, + "include_meeting_email": false, + "last_page_font_size": 8, + "column_line": 0, + "used_format_1": "", + "page_height": "258", + "column_gap": 5, + "margin_right": 6, + "margin_left": 6, + "margin_bottom": 6, + "margin_top": 5, + "meeting_template_content": "\r\n\r\n\r\n
    \r\n

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

    comments<\/em><\/span><\/p>\r\nwheelchair<\/td>\r\n

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

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

    location, info, street, zip city (public_transport) info_names<\/p>\r\n

    fmt_names<\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>", + "col_color": "#bfbfbf", + "time_clock": "24", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "include_additional_list": false, + "weekday_language": "de", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "789-789+", + "extra_meetings": [], + "sub_header_shown": "none", + "margin_header": 0, + "pageheader_fontsize": 10, + "pageheader_text": "", + "neighborhood_suffix": "Neighborhood", + "city_suffix": "City", + "pagenumbering_font_size": 9, + "recurse_service_bodies": 0, + "extra_meetings_enabled": 0, + "base_font": "dejavusanscondensed", + "custom_query": "", + "watermark": "", + "suppress_heading": 0, + "pageheader_textcolor": "#000000", + "pageheader_backgroundcolor": "#ffffff", + "booklet_pages": true, + "additional_list_template_content": "\r\n\r\n\r\n
    \r\n

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

    [QRCode code=\\\"virtual_meeting_link\\\" size=\\\"0.70\\\"]
    <\/strong><\/span><\/p>\r\n<\/td>\r\n

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

    comments\u00a0<\/em><\/span><\/p>\r\n

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

    Telefoneinwahl: +49 6950 500 952<\/span><\/p>\r\n

    Zoom Code: zoom_code<\/span><\/p>\r\n

    virtual_meeting_additional_info<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>", + "retrieve_all_fields": 1, + "weekday_start": "2", + "additional_list_format_key": "@Virtual@", + "additional_list_sort_order": "same", + "additional_list_logged_in": false, + "bread_version": "2.0", + "pageheader_content": "", + "authors": [ + 122, + 13, + 2 + ], + "additional_list_language": "", + "cont_header_shown": 1, + "colorspace": "3", + "main_grouping": "link_header", + "subgrouping": "day", + "user_agent": "None", + "additional_list_custom_query": "", + "sslverify": "0", + "nonmeeting_footer": "", + "meeting1_footer": "Pr\u00e4senz-Meetings - Seite {PAGENO}", + "meeting2_footer": "Online-Meetings - Seite {PAGENO}", + "margin_footer": 5, + "wheelchair_size": "20px" +} \ No newline at end of file diff --git a/tests/configurations/berlin-by-city-plus-day.json b/tests/configurations/berlin-by-city-plus-day.json new file mode 100644 index 0000000..fd9624e --- /dev/null +++ b/tests/configurations/berlin-by-city-plus-day.json @@ -0,0 +1 @@ +{"root_server":"http:\/\/narcotics-anonymous.de\/bmlt","service_body_1":"Gebiet Berlin,3,1,Deutschsprachige Region","service_body_2":"Not Used","service_body_3":"Not Used","header_font_size":8.2,"area_service_meetings":null,"helplines":null,"helplines_line_height":null,"front_page_content":"

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


    [month_lower] [year]

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

     <\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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

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

    NA - Eine Gemeinschaft genesender S\u00fcchtiger

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

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

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

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

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

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

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

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

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

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

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

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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


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

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

    [page_break]<\/p>","front_line_height":"1.4","area_service_meetings_line_height":"1.0","content_font_size":8,"content_line_height":1.1,"front_page_font_size":10,"service_body_4":"Not Used","service_body_5":"Not Used","page_layout":null,"last_page_content":"

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    ONLINE-MEETINGS<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]
    [page_break]<\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    GSK (Gebiets-Service-Konferenz)<\/b><\/span><\/p>\r\n

    F\u00fcr den aktuellen Termin und Ort der GSK bitte www.na-berlin.de\/gsk<\/strong> besuchen. Oder scanne einfach den QR-Code<\/span>
    Kontakt: gsk-chair@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nInfo-Komitee Berlin (K&E und \u00d6A Berlin)
    <\/span><\/strong><\/span>17:30 - 19:30\u00a0 <\/strong>Jeden 3. Mittwoch im Monat
    <\/strong><\/span>NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: info@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    \u00a0<\/span><\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nBerliner Convention-Vorbereitungs-Komitee
    <\/b>F\u00fcr aktuellen Termin & Ort bitte QR-Code scannen.<\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: bcvk@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nWebteam
    17:30 - 19:00 Jeden 1. Donnerstag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: webteam@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    Anti-Sexismus-Komitee
    19:30-21:00 Jeden 4. Montag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: ask@na-berlin.de, hilfeag@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\n\r\n

    NA-Basis-Komitee
    17:00-18:30 Jeden 1. Dienstag im Monat<\/b><\/span>
    NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: basis@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    KONTAKT \/ INFORMATION<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

    INTERNET<\/strong><\/span>
    www.na-berlin.de<\/span>
    info@na-berlin.de<\/span><\/p>\r\n


    ANSCHRIFT<\/strong><\/span>
    NASKB e.V.<\/span>
    Franz-Mehring-Platz 1
    10243 Berlin<\/span><\/p>\r\n


    Bankverbindung<\/strong><\/span>
    NASKB e.V.<\/span>
    IBAN: DE 71 100 100 100 470 988 109<\/span>
    BIC (Swift): PBNKDEFF<\/span><\/p>\r\n

     <\/p>\r\n


    \u00c4nderungen: meetingsliste@na-berlin.de
    <\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    Die aktuellsten Informationen zu den Meetings sind unter www.na-berlin.de\/meetings<\/strong> zu finden. Oder scanne einfach den QR-Code.<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    \\\"Bild: Freepik.com\\\". Die Grafiken dieser Meetingsliste wurden mit Ressourcen von Freepik.com erstellt.<\/span><\/p>\r\n

    [page_break no_page_number]<\/p>\r\n\r\n\r\n\r\n
    TELEFONNUMMERN<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

    \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span><\/p>","front_page_line_height":"1.0","last_page_line_height":1.1,"time_option":1,"remove_space":true,"helplines_font_size":null,"format_codes":null,"format_codes_font_size":"9","format_codes_line_height":"1.0","page_size":"A6","page_fold":"full","meeting_sort":"city","cache_time":0,"include_zip":false,"show_status":null,"use_custom_section":null,"custom_section_content":"","custom_section_line_height":1,"page_orientation":"P","meeting_template":"","area_service_meetings_font_size":"9","which_format_codes":"used","custom_section_font_size":9,"include_meeting_email":false,"last_page_font_size":8,"column_line":0,"used_format_1":"","page_height":"258","column_gap":5,"margin_right":3,"margin_left":3,"margin_bottom":3,"margin_top":3,"meeting_template_content":"\r\n\r\n\r\n
    \r\n

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

    comments<\/em><\/span><\/p>\r\nwheelchair<\/td>\r\n

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

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

    location, info, street, zip city (public_transport) info_names<\/p>\r\n

    fmt_names<\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>","col_color":"#bfbfbf","time_clock":"24","header_text_color":"#ffffff","header_background_color":"#000000","header_uppercase":1,"header_bold":1,"include_asm":false,"weekday_language":"de","borough_suffix":"Borough","county_suffix":"County","include_protection":false,"protection_password":"","extra_meetings":[],"sub_header_shown":"none","margin_header":0,"pageheader_fontsize":10,"pageheader_text":"","neighborhood_suffix":"Neighborhood","city_suffix":"City","pagenumbering_font_size":9,"recurse_service_bodies":0,"extra_meetings_enabled":0,"base_font":"dejavusanscondensed","custom_query":"","outside_meeting_template_content":"\r\n\r\n\r\n
    day
    time
    borough<\/strong><\/td>\r\n
    original_name<\/strong>\r\n

    location,\u00a0info,\u00a0street, zip state\u00a0(public_transport)\u00a0comments <\/em>info_names<\/p>\r\n

    fmt_names<\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>","watermark":"","outside_meeting_query_1":"&services=3&formats=105","outside_meeting_query_2":"","suppress_heading":0,"pageheader_textcolor":"#000000","pageheader_backgroundcolor":"#ffffff","booklet_pages":true,"asm_template_content":"","used_format_2":"","retrieve_all_fields":1,"weekday_start":"2","asm_sort_order":"same","asm_logged_in":false,"bread_version":"2.0","pageheader_content":"","authors":[1],"asm_language":"","cont_header_shown":1,"colorspace":"3","main_grouping":"day","subgrouping":"day","user_agent":"None","asm_custom_query":"","sslverify":"0","nonmeeting_footer":"","meeting1_footer":"Pr\u00e4senz-Meetings - Seite {PAGENO}","meeting2_footer":"Online-Meetings - Seite {PAGENO}","margin_footer":5,"wheelchair_size":"20px","additional_list_template_content":"","additional_list_language":"","include_additional_list":false,"additional_list_format_key":"@Virtual@","additional_list_sort_order":"weekday_tinyint,start_time","additional_list_custom_query":""} \ No newline at end of file diff --git a/tests/configurations/berlin-by-day-then-city-plus-day.json b/tests/configurations/berlin-by-day-then-city-plus-day.json new file mode 100644 index 0000000..90a1cdb --- /dev/null +++ b/tests/configurations/berlin-by-day-then-city-plus-day.json @@ -0,0 +1 @@ +{"root_server":"http:\/\/narcotics-anonymous.de\/bmlt","service_body_1":"Gebiet Berlin,3,1,Deutschsprachige Region","service_body_2":"Not Used","service_body_3":"Not Used","header_font_size":8.2,"area_service_meetings":null,"helplines":null,"helplines_line_height":null,"front_page_content":"

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


    [month_lower] [year]

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

     <\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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

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

    NA - Eine Gemeinschaft genesender S\u00fcchtiger

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

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

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

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

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

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

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

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

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

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

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

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

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

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


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

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

    [page_break]<\/p>","front_line_height":"1.4","area_service_meetings_line_height":"1.0","content_font_size":8,"content_line_height":1.1,"front_page_font_size":10,"service_body_4":"Not Used","service_body_5":"Not Used","page_layout":null,"last_page_content":"

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    ONLINE-MEETINGS<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]
    [page_break]<\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    GSK (Gebiets-Service-Konferenz)<\/b><\/span><\/p>\r\n

    F\u00fcr den aktuellen Termin und Ort der GSK bitte www.na-berlin.de\/gsk<\/strong> besuchen. Oder scanne einfach den QR-Code<\/span>
    Kontakt: gsk-chair@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nInfo-Komitee Berlin (K&E und \u00d6A Berlin)
    <\/span><\/strong><\/span>17:30 - 19:30\u00a0 <\/strong>Jeden 3. Mittwoch im Monat
    <\/strong><\/span>NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: info@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    \u00a0<\/span><\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nBerliner Convention-Vorbereitungs-Komitee
    <\/b>F\u00fcr aktuellen Termin & Ort bitte QR-Code scannen.<\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: bcvk@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\nWebteam
    17:30 - 19:00 Jeden 1. Donnerstag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: webteam@na-berlin.de<\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    Anti-Sexismus-Komitee
    19:30-21:00 Jeden 4. Montag im Monat online<\/b><\/span>
    Meeting-ID: 819 3182 4492
    Kennwort: 12
    Telefon-Einwahl: 069 505 00 952<\/span>
    Kontakt: ask@na-berlin.de, hilfeag@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n
    \"\"<\/td>\r\n\r\n

    NA-Basis-Komitee
    17:00-18:30 Jeden 1. Dienstag im Monat<\/b><\/span>
    NA Basis Berlin
    Franz-Mehring-Platz 1, Raum 217, 10243 Berlin<\/span>
    Kontakt: basis@na-berlin.de<\/span><\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [page_break]<\/p>\r\n\r\n\r\n\r\n
    KONTAKT \/ INFORMATION<\/b><\/span><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

    INTERNET<\/strong><\/span>
    www.na-berlin.de<\/span>
    info@na-berlin.de<\/span><\/p>\r\n


    ANSCHRIFT<\/strong><\/span>
    NASKB e.V.<\/span>
    Franz-Mehring-Platz 1
    10243 Berlin<\/span><\/p>\r\n


    Bankverbindung<\/strong><\/span>
    NASKB e.V.<\/span>
    IBAN: DE 71 100 100 100 470 988 109<\/span>
    BIC (Swift): PBNKDEFF<\/span><\/p>\r\n

     <\/p>\r\n


    \u00c4nderungen: meetingsliste@na-berlin.de
    <\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    Die aktuellsten Informationen zu den Meetings sind unter www.na-berlin.de\/meetings<\/strong> zu finden. Oder scanne einfach den QR-Code.<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    \\\"Bild: Freepik.com\\\". Die Grafiken dieser Meetingsliste wurden mit Ressourcen von Freepik.com erstellt.<\/span><\/p>\r\n

    [page_break no_page_number]<\/p>\r\n\r\n\r\n\r\n
    TELEFONNUMMERN<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

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

     <\/p>\r\n

    \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/span><\/p>","front_page_line_height":"1.0","last_page_line_height":1.1,"time_option":1,"remove_space":true,"helplines_font_size":null,"format_codes":null,"format_codes_font_size":"9","format_codes_line_height":"1.0","page_size":"A6","page_fold":"full","meeting_sort":"weekday_city","cache_time":0,"include_zip":false,"show_status":null,"use_custom_section":null,"custom_section_content":"","custom_section_line_height":1,"page_orientation":"P","meeting_template":"","area_service_meetings_font_size":"9","which_format_codes":"used","custom_section_font_size":9,"include_meeting_email":false,"last_page_font_size":8,"column_line":0,"used_format_1":"","page_height":"258","column_gap":5,"margin_right":3,"margin_left":3,"margin_bottom":3,"margin_top":3,"meeting_template_content":"\r\n\r\n\r\n
    \r\n

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

    comments<\/em><\/span><\/p>\r\nwheelchair<\/td>\r\n

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

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

    location, info, street, zip city (public_transport) info_names<\/p>\r\n

    fmt_names<\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>","col_color":"#bfbfbf","time_clock":"24","header_text_color":"#ffffff","header_background_color":"#000000","header_uppercase":1,"header_bold":1,"include_asm":false,"weekday_language":"de","borough_suffix":"Borough","county_suffix":"County","include_protection":false,"protection_password":"","extra_meetings":[],"sub_header_shown":"display","margin_header":0,"pageheader_fontsize":10,"pageheader_text":"","neighborhood_suffix":"Neighborhood","city_suffix":"City","pagenumbering_font_size":9,"recurse_service_bodies":0,"extra_meetings_enabled":0,"base_font":"dejavusanscondensed","custom_query":"","outside_meeting_template_content":"\r\n\r\n\r\n
    day
    time
    borough<\/strong><\/td>\r\n
    original_name<\/strong>\r\n

    location,\u00a0info,\u00a0street, zip state\u00a0(public_transport)\u00a0comments <\/em>info_names<\/p>\r\n

    fmt_names<\/p>\r\n<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>","watermark":"","outside_meeting_query_1":"&services=3&formats=105","outside_meeting_query_2":"","suppress_heading":0,"pageheader_textcolor":"#000000","pageheader_backgroundcolor":"#ffffff","booklet_pages":true,"asm_template_content":"","used_format_2":"","retrieve_all_fields":1,"weekday_start":"2","asm_sort_order":"same","asm_logged_in":false,"bread_version":"2.0","pageheader_content":"","authors":[1],"asm_language":"","cont_header_shown":1,"colorspace":"3","main_grouping":"day","subgrouping":"day","user_agent":"None","asm_custom_query":"","sslverify":"0","nonmeeting_footer":"","meeting1_footer":"Pr\u00e4senz-Meetings - Seite {PAGENO}","meeting2_footer":"Online-Meetings - Seite {PAGENO}","margin_footer":5,"wheelchair_size":"20px","additional_list_template_content":"","additional_list_language":"","include_additional_list":false,"additional_list_format_key":"@Virtual@","additional_list_sort_order":"weekday_tinyint,start_time","additional_list_custom_query":""} \ No newline at end of file diff --git a/tests/configurations/texas.json b/tests/configurations/texas.json new file mode 100644 index 0000000..b1c3820 --- /dev/null +++ b/tests/configurations/texas.json @@ -0,0 +1,107 @@ +{ + "root_server": "https:\/\/texasoklahomana.org\/main_server", + "service_body_1": "Texas Tri-County Area,1028,1018,Tejas Bluebonnet Region", + "service_body_2": "Not Used", + "service_body_3": "Not Used", + "header_font_size": 6.5, + "area_service_meetings": null, + "front_page_content": "

    \u00a0<\/p>\r\n

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

     <\/p>\r\n

     <\/p>\r\n

    [service_body_1]<\/strong><\/span><\/p>\r\n

     <\/p>\r\n

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

     <\/p>\r\n

    [date]<\/strong>\u00a0<\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    24 HOUR HELPLINE<\/b><\/span><\/p>\r\n

    <\/b>866-579-8333<\/b><\/span><\/p>\r\n

     <\/p>\r\n

     <\/p>\r\n

    Florida Relay for the Deaf<\/b><\/p>\r\n

    800-955-8771<\/b><\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    Greater Orlando Area Service Committee<\/b><\/p>\r\n

    PO Box 532095<\/b><\/p>\r\n

    Orlando, FL\u00a032853-2095<\/strong><\/p>\r\n

    \u00a0<\/p>\r\n

    \u00a0<\/p>\r\n

    http:\/\/orlandona.org<\/b><\/p>\r\n

     <\/p>\r\n

    SUGGESTIONS FOR EVERYONE<\/strong><\/span><\/p>\r\n

    DON\\'T USE no matter what<\/strong><\/p>\r\n

    Ask your Higher Power to keep you clean<\/strong><\/p>\r\n

    Come early and stay late<\/strong><\/p>\r\n

    Get a home group<\/strong><\/p>\r\n

    Go to 90 meetings in 90 days<\/strong><\/p>\r\n

    Read NA literature daily<\/strong><\/p>\r\n

    Get and use a sponsor<\/strong><\/p>\r\n

    Use the PHONE<\/strong><\/p>\r\n

    KEEP COMING BACK. IT WORKS<\/strong><\/span><\/p>\r\n

    \u00a0<\/p>\r\n

    Meetings Weekly:\u00a0[meeting_count]<\/p>", + "front_line_height": "1.0", + "area_service_meetings_line_height": null, + "content_font_size": 6, + "content_line_height": 1.1999999999999999555910790149937383830547332763671875, + "front_page_font_size": 10, + "helpines": "Bahamas, 242-426-5245\r\nBay Area\/Pinellas (Pinellas County), 727-547-0444\r\nBoca Raton\/Delray Beach, 561-393-0303\r\nBradenton, 941-957-7910\r\nDaytona Beach (Volusia County), 800-206-0731\r\nFirst Coast Area (Duval County), 904-723-5683\r\nForest\/Ocala (Marion City), 352-368-6061\r\nGainesville (Uncoast Area), 352-376-8008\r\nJacksonville, 800-576-4357\r\nHeartland Area (Lakeland\/Polk County), 863-683-0630\r\nMelbourne\/Titusville, 321-631-4357\r\nNature Coast, 352-508-1604\r\nNew Port Richey\/Zephrhills, 800-691-5551\r\nNorth-West Florida, 800-467-7314\r\nPalm Coast Area, 561-848-6262\r\nRecover Coast Area (Pasco County), 727-842-2433\r\nRiver Coast Area (Hernando County), 352-754-7200\r\nSuncoast Area (Sarasota), 941-257-5055\r\nSouth Florida Region, 866-288-6262\r\nSpace Coast Area (Brevard County), 321-631-4357\r\nSt- Petersburg\/Clearwater\/Pinellas County, 727-547-0444\r\nTallahassee\/Big Bend, 850-224-2321\r\nTampa (Hillsborough County), 813-879-4357\r\nTreasure Coast (St- Lucie County), 888-624-6822\r\nUnity Springs (West Volusia County), 888-385-3121", + "helpines_line_height": null, + "helpline_line_height": "1.5", + "helplines_line_height": null, + "helplines": null, + "last_page_content": "", + "front_page_line_height": "1.0", + "last_page_line_height": 1, + "page_layout": null, + "service_body_4": "Not Used", + "service_body_5": "Not Used", + "time_option": 1, + "remove_space": true, + "helplines_font_size": null, + "format_codes": null, + "format_codes_font_size": null, + "format_codes_line_height": null, + "page_size": "letter", + "page_fold": "tri", + "meeting_sort": "day", + "cache_time": 0, + "include_zip": null, + "page_orientation": "L", + "show_status": null, + "use_custom_section": null, + "custom_section_content": "

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    MEETING FORMAT LEGEND<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [format_codes_used_basic]<\/p>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    HELPLINES<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    Bahamas<\/td>\r\n242-426-5245<\/td>\r\n<\/tr>\r\n
    Bay Area\/Pinellas (Pinellas County)<\/td>\r\n727-547-0444<\/td>\r\n<\/tr>\r\n
    Boca Raton\/Delray Beach\u00a0<\/td>\r\n561-393-0303<\/td>\r\n<\/tr>\r\n
    Bradenton<\/td>\r\n941-957-7910<\/td>\r\n<\/tr>\r\n
    Daytona Beach (Volusia County)\u00a0<\/td>\r\n800-206-0731<\/td>\r\n<\/tr>\r\n
    First Coast Area (Duval County)<\/td>\r\n904-723-5683<\/td>\r\n<\/tr>\r\n
    Forest\/Ocala (Marion City)<\/td>\r\n352-368-6061<\/td>\r\n<\/tr>\r\n
    Gainesville (Uncoast Area)<\/td>\r\n352-376-8008<\/td>\r\n<\/tr>\r\n
    Jacksonville<\/td>\r\n800-576-4357<\/td>\r\n<\/tr>\r\n
    Heartland Area (Lakeland\/Polk County)<\/td>\r\n863-683-0630<\/td>\r\n<\/tr>\r\n
    Melbourne\/Titusville\u00a0<\/td>\r\n321-631-4357<\/td>\r\n<\/tr>\r\n
    Nature Coast\u00a0<\/td>\r\n352-508-1604<\/td>\r\n<\/tr>\r\n
    New Port Richey\/Zephrhills<\/td>\r\n800-691-5551<\/td>\r\n<\/tr>\r\n
    North-West Florida<\/td>\r\n800-467-7314<\/td>\r\n<\/tr>\r\n
    Palm Coast Area\u00a0<\/td>\r\n561-848-6262<\/td>\r\n<\/tr>\r\n
    Recover Coast Area (Pasco County)<\/td>\r\n727-842-2433<\/td>\r\n<\/tr>\r\n
    River Coast Area (Hernando County)\u00a0<\/td>\r\n352-754-7200<\/td>\r\n<\/tr>\r\n
    Suncoast Area (Sarasota)\u00a0<\/td>\r\n941-257-5055<\/td>\r\n<\/tr>\r\n
    South Florida Region<\/td>\r\n866-288-6262<\/td>\r\n<\/tr>\r\n
    Space Coast Area (Brevard County)<\/td>\r\n321-631-4357<\/td>\r\n<\/tr>\r\n
    St- Petersburg\/Clearwater\/Pinellas County<\/td>\r\n727-547-0444<\/td>\r\n<\/tr>\r\n
    Tallahassee\/Big Bend\u00a0<\/td>\r\n850-224-2321<\/td>\r\n<\/tr>\r\n
    Tampa (Hillsborough County)\u00a0<\/td>\r\n813-879-4357<\/td>\r\n<\/tr>\r\n
    Treasure Coast (St- Lucie County)<\/td>\r\n888-624-6822<\/td>\r\n<\/tr>\r\n
    Unity Springs (West Volusia County)\u00a0<\/td>\r\n888-385-3121<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

     <\/p>\r\n\r\n\r\n\r\n
    SERVICE MEETINGS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

    [service_meetings]<\/p>\r\n

    [new_column]<\/p>\r\n\r\n\r\n\r\n
    PHONE NUMBERS<\/span><\/strong><\/span><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n
    \u00a0<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\n

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

    What is our message?<\/span><\/span><\/strong><\/p>\r\n

     <\/p>\r\n

    The message is that an addict, <\/span><\/strong><\/p>\r\n

    any addict,\u00a0<\/span><\/strong>can stop using drugs,<\/span><\/strong><\/p>\r\n

    \u00a0<\/span><\/strong>lose the desire to use,<\/span><\/strong><\/p>\r\n

    and find a new way to live.<\/span><\/strong><\/p>\r\n

    Our message is hope<\/span><\/strong><\/p>\r\n

    and the promise of freedom.\u00a0<\/span><\/strong><\/p>\r\n

    \u00a0<\/p>\r\n

    Basic Text<\/span>, page 65<\/span><\/em><\/p>\r\n

    [new_column]<\/p>", + "custom_section_line_height": 1.1999999999999999555910790149937383830547332763671875, + "area_service_meetings_font_size": null, + "which_format_codes": null, + "meeting_template": null, + "custom_section_font_size": 6.5, + "include_meeting_email": true, + "last_page_font_size": 10, + "column_line": true, + "used_format_1": "", + "used_format_2": null, + "time_clock": "12", + "page_height": "200", + "column_gap": 6, + "margin_right": 2, + "margin_left": 2, + "margin_bottom": 2, + "margin_top": 2, + "meeting_template_content": "\r\n\r\n\r\n
    time<\/strong><\/span><\/td>\r\n\u00a0<\/td>\r\nhrsHR<\/strong><\/span><\/td>\r\n\u00a0<\/td>\r\ngroup<\/strong>, location,\u00a0info,\u00a0street, city, state, zip (formats)<\/strong>\u00a0comments<\/em><\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>", + "col_color": "#274fae", + "header_text_color": "#ffffff", + "header_background_color": "#000000", + "header_uppercase": 1, + "header_bold": 1, + "include_additional_list": false, + "weekday_language": "both", + "borough_suffix": "Borough", + "county_suffix": "County", + "include_protection": false, + "protection_password": "", + "extra_meetings": [], + "pageheader_fontsize": 0, + "suppress_heading": 0, + "pageheader_textcolor": "", + "pageheader_backgroundcolor": "", + "sub_header_shown": "", + "booklet_pages": false, + "neighborhood_suffix": "", + "city_suffix": "", + "additional_list_template_content": "", + "pagenumbering_font_size": "9", + "base_font": "", + "colorspace": "", + "recurse_service_bodies": 0, + "extra_meetings_enabled": 0, + "additional_list_language": "", + "weekday_start": "", + "additional_list_format_key": "", + "additional_list_sort_order": "", + "custom_query": "", + "additional_list_custom_query": "", + "user_agent": "None", + "sslverify": "0", + "authors": [ + 3 + ], + "nonmeeting_footer": "", + "meeting1_footer": "", + "meeting2_footer": "", + "cont_header_shown": 0, + "bread_version": "", + "margin_header": 0, + "margin_footer": 5, + "pageheader_content": "", + "watermark": "", + "main_grouping": "", + "subgrouping": "", + "page_height_fix": "0" +} \ No newline at end of file diff --git a/tests/formats/berlin-formats-de.json b/tests/formats/berlin-formats-de.json new file mode 100644 index 0000000..e77b60e --- /dev/null +++ b/tests/formats/berlin-formats-de.json @@ -0,0 +1 @@ +{"formats":[{"key_string":"ru","name_string":"Russisch","description_string":"In russischer Sprache","lang":"de","id":"109","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Med","name_string":"Meditation","description_string":"Gemeinsame, freie Meditation ist Bestandteil des Meetings","lang":"de","id":"119","world_id":"MED","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"BT","name_string":"BasicText","description_string":"Lesen aus dem Basic Text","lang":"de","id":"118","world_id":"BT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Lit","name_string":"Literatur","description_string":"Lesen aus NA-Literatur","lang":"de","id":"124","world_id":"LIT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"S","name_string":"Schritte","description_string":"Meetingthema bezieht sich auf die zw\u00f6lf Schritte","lang":"de","id":"113","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"kf","name_string":"Kinderfreundlich","description_string":"Kinderfreundlich","lang":"de","id":"91","world_id":"CW","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp","name_string":"Sprecher","description_string":"Ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"115","world_id":"SPK","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KK","name_string":"Keine Kinder","description_string":"Bitte keine Kinder mitbringen","lang":"de","id":"204","world_id":"NC","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"EF","name_string":"Es funktioniert","description_string":"Lesen aus dem Buch 'Es funktioniert - wie und warum'","lang":"de","id":"121","world_id":"IW","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"CL","name_string":"Clean Leben","description_string":"Lesen aus dem Buch 'Clean Leben - die Reise geht weiter'","lang":"de","id":"120","world_id":"LC","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Essf","name_string":"Essfrei","description_string":"Essfrei; bitte keine Nahrungsmittel mitbringen","lang":"de","id":"203","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"p","name_string":"Pause","description_string":"Meeting mit Pause","lang":"de","id":"106","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"nfh","name_string":"Nur f\u00fcr heute","description_string":"Lesen der Tagesmeditation 'Nur f\u00fcr Heute'","lang":"de","id":"111","world_id":"JFT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"glbtiiq","name_string":"LGBTQ*","description_string":"Gay, Lesbian, Transgender, Intersexual, Queer etc.","lang":"de","id":"10","world_id":"GL","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"T","name_string":"Themenmeeting","description_string":"Gemeinsam bestimmtes Thema","lang":"de","id":"116","world_id":"TOP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"de","name_string":"Deutsch","description_string":"In deutscher Sprache","lang":"de","id":"125","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"sch10","name_string":"Schritt 10","description_string":"Meeting konzentriert sich auf Schritt 10","lang":"de","id":"205","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo1","name_string":"Findet nur in der 1. Woche des Monats statt","description_string":"Findet nur in der 1. Woche des Monats statt","lang":"de","id":"100","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo2","name_string":"Findet nur in der 2. Woche des Monats statt","description_string":"Findet nur in der 2. Woche des Monats statt","lang":"de","id":"101","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo3","name_string":"Findet nur in der 3. Woche des Monats statt","description_string":"Findet nur in der 3. Woche des Monats statt","lang":"de","id":"102","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo4","name_string":"Findet nur in der 4. Woche des Monats statt","description_string":"Findet nur in der 4. Woche des Monats statt","lang":"de","id":"103","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp1","name_string":"Woche 1: Sprecher","description_string":"1. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"126","world_id":"","format_type_enum":"FC1-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"FuAL","name_string":"Letzte Woche: Frage und Antwort","description_string":"Letzte Woche des Monats: Frage und Antwort","lang":"de","id":"122","world_id":"QA","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Tr","name_string":"Traditionen","description_string":"Meetingsthema bezieht sich auf die 'Zw\u00f6lf Traditionen'","lang":"de","id":"117","world_id":"TRAD","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"pl","name_string":"Polnisch","description_string":"In polnischer Sprache","lang":"de","id":"208","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"en","name_string":"Englisch","description_string":"In englischer Sprache","lang":"de","id":"107","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fa","name_string":"Farsi","description_string":"In Farsi","lang":"de","id":"108","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O","name_string":"auch Nichts\u00fcchtige willkommen","description_string":"offen f\u00fcr Nicht-S\u00fcchtige","lang":"de","id":"2","world_id":"OPEN","format_type_enum":"O-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O2","name_string":"2. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 2. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"4","world_id":"","format_type_enum":"O-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O4","name_string":"4. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 4. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"6","world_id":"","format_type_enum":"O-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O5","name_string":"5. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 5. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"7","world_id":"","format_type_enum":"O-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O1","name_string":"1. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 1. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"3","world_id":"","format_type_enum":"O-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fr","name_string":"Franz\u00f6sisch","description_string":"In franz\u00f6sischer Sprache","lang":"de","id":"211","world_id":"","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"VM","name_string":"Virtual Meeting","description_string":"Meets Virtually","lang":"de","id":"213","world_id":"VM","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SPAD","name_string":"Ein spirituelles Prinzip pro Tag","description_string":"Lesen aus dem Buch Ein spirituelles Prinzip pro Tag","lang":"de","id":"228","world_id":"SPAD","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"F","name_string":"Frauenmeeting","description_string":"Meeting f\u00fcr Frauen","lang":"de","id":"11","world_id":"W","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"M","name_string":"M\u00e4nnermeeting","description_string":"Meeting f\u00fcr M\u00e4nner","lang":"de","id":"15","world_id":"M","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"flinta","name_string":"FLINTA*","description_string":"Meeting (auch) f\u00fcr Female Lesbian Intersexual Nonbinary Trans Agender Personen","lang":"de","id":"229","world_id":"","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp2","name_string":"Woche 2: Sprecher","description_string":"2. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"127","world_id":"","format_type_enum":"FC1-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp3","name_string":"Woche 3: Sprecher","description_string":"3. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"128","world_id":"","format_type_enum":"FC1-3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp4","name_string":"Woche 4: Sprecher","description_string":"4. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"129","world_id":"","format_type_enum":"FC1-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp5","name_string":"Woche 5: Sprecher","description_string":"5. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"131","world_id":"","format_type_enum":"FC1-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SpL","name_string":"Letzte Woche: Sprecher","description_string":"Letzte Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"130","world_id":"","format_type_enum":"FC1-L","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"inst","name_string":"Institutsmeeting","description_string":"Institutionsmeeting, CLEAN-Anspruch durch Einrichtung","lang":"de","id":"105","world_id":"RA","format_type_enum":"Alert","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"wf","name_string":"Wechselndes Format","description_string":"Das Meetings-Format wechselt w\u00f6chentlich","lang":"de","id":"110","world_id":"VAR","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"uk","name_string":"Ukrainisch","description_string":"In ukrainischer Sprache","lang":"de","id":"230","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Beh","name_string":"barrierefrei","description_string":"Zugang zum Meeting ist rollstuhlgerecht","lang":"de","id":"33","world_id":"WCHR","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"FuA","name_string":"Frage und Antwort","description_string":"Frage und Antwort","lang":"de","id":"231","world_id":"QA","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KT","name_string":"Tiereverbot","description_string":"Tiere sind nicht zugelassen","lang":"de","id":"94","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"es","name_string":"Spanisch","description_string":"In spanischer Sprache","lang":"de","id":"232","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"}]} \ No newline at end of file diff --git a/tests/formats/german-formats-de.json b/tests/formats/german-formats-de.json new file mode 100644 index 0000000..d98bad4 --- /dev/null +++ b/tests/formats/german-formats-de.json @@ -0,0 +1 @@ +[{"key_string":"ru","name_string":"Russisch","description_string":"In russischer Sprache","lang":"de","id":"109","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Med","name_string":"Meditation","description_string":"Gemeinsame, freie Meditation ist Bestandteil des Meetings","lang":"de","id":"119","world_id":"MED","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"naf","name_string":"Nicht an Feiertagen","description_string":"Findet nicht an Feiertagen statt","lang":"de","id":"99","world_id":"","format_type_enum":"When","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"BT","name_string":"BasicText","description_string":"Lesen aus dem Basic Text","lang":"de","id":"118","world_id":"BT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Lit","name_string":"Literatur","description_string":"Lesen aus NA-Literatur","lang":"de","id":"124","world_id":"LIT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"S","name_string":"Schritte","description_string":"Meetingthema bezieht sich auf die zw\u00f6lf Schritte","lang":"de","id":"113","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"kf","name_string":"Kinderfreundlich","description_string":"Kinderfreundlich","lang":"de","id":"91","world_id":"CW","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp","name_string":"Sprecher","description_string":"Ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"115","world_id":"SPK","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KK","name_string":"Keine Kinder","description_string":"Bitte keine Kinder mitbringen","lang":"de","id":"204","world_id":"NC","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"EF","name_string":"Es funktioniert","description_string":"Lesen aus dem Buch 'Es funktioniert - wie und warum'","lang":"de","id":"121","world_id":"IW","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"CL","name_string":"Clean Leben","description_string":"Lesen aus dem Buch 'Clean Leben - die Reise geht weiter'","lang":"de","id":"120","world_id":"LC","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Essf","name_string":"Essfrei","description_string":"Essfrei; bitte keine Nahrungsmittel mitbringen","lang":"de","id":"203","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"p","name_string":"Pause","description_string":"Meeting mit Pause","lang":"de","id":"106","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"nfh","name_string":"Nur f\u00fcr heute","description_string":"Lesen der Tagesmeditation 'Nur f\u00fcr Heute'","lang":"de","id":"111","world_id":"JFT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"glbtiiq","name_string":"LGBTQ*","description_string":"Gay, Lesbian, Transgender, Intersexual, Queer etc.","lang":"de","id":"10","world_id":"GL","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"T","name_string":"Themenmeeting","description_string":"Gemeinsam bestimmtes Thema","lang":"de","id":"116","world_id":"TOP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"schf","name_string":"Schrittef\u00fchrer","description_string":"Meetingsthema aus dem 'Leitfaden f\u00fcr die Schritte-Arbeit'","lang":"de","id":"114","world_id":"SWG","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"de","name_string":"Deutsch","description_string":"In deutscher Sprache","lang":"de","id":"125","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"GT","name_string":"Geburtstagsmeeting","description_string":"Gemeinsames feiern von Clean-Geburtstagen","lang":"de","id":"207","world_id":"","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"sch10","name_string":"Schritt 10","description_string":"Meeting konzentriert sich auf Schritt 10","lang":"de","id":"205","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"RE","name_string":"Rauchen erlaubt","description_string":"Rauchen ist erlaubt.","lang":"de","id":"25","world_id":"SMOK","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo1","name_string":"Findet nur in der 1. Woche des Monats statt","description_string":"Findet nur in der 1. Woche des Monats statt","lang":"de","id":"100","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo2","name_string":"Findet nur in der 2. Woche des Monats statt","description_string":"Findet nur in der 2. Woche des Monats statt","lang":"de","id":"101","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo3","name_string":"Findet nur in der 3. Woche des Monats statt","description_string":"Findet nur in der 3. Woche des Monats statt","lang":"de","id":"102","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Wo4","name_string":"Findet nur in der 4. Woche des Monats statt","description_string":"Findet nur in der 4. Woche des Monats statt","lang":"de","id":"103","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"WoL","name_string":"Findet nur in der letzten Woche des Monats statt","description_string":"Findet nur in der letzten Woche des Monats statt","lang":"de","id":"104","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp1","name_string":"Woche 1: Sprecher","description_string":"1. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"126","world_id":"","format_type_enum":"FC1-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"FuAL","name_string":"Letzte Woche: Frage und Antwort","description_string":"Letzte Woche des Monats: Frage und Antwort","lang":"de","id":"122","world_id":"QA","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"new","name_string":"Newcomer","description_string":"Wendet sich besonders an Neuank\u00f6mmlinge/Newcomer","lang":"de","id":"112","world_id":"BEG","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Tr","name_string":"Traditionen","description_string":"Meetingsthema bezieht sich auf die 'Zw\u00f6lf Traditionen'","lang":"de","id":"117","world_id":"TRAD","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"pl","name_string":"Polnisch","description_string":"In polnischer Sprache","lang":"de","id":"208","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O3","name_string":"3. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 3. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"5","world_id":"","format_type_enum":"O-3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"en","name_string":"Englisch","description_string":"In englischer Sprache","lang":"de","id":"107","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fa","name_string":"Farsi","description_string":"In Farsi","lang":"de","id":"108","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O","name_string":"auch Nichts\u00fcchtige willkommen","description_string":"offen f\u00fcr Nicht-S\u00fcchtige","lang":"de","id":"2","world_id":"OPEN","format_type_enum":"O-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O2","name_string":"2. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 2. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"4","world_id":"","format_type_enum":"O-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O4","name_string":"4. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 4. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"6","world_id":"","format_type_enum":"O-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O5","name_string":"5. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 5. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"7","world_id":"","format_type_enum":"O-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"OL","name_string":"Letzte Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"In der letzten Woche des Monats offen f\u00fcr Nicht-S\u00fcchtige","lang":"de","id":"8","world_id":"","format_type_enum":"O-L","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O1","name_string":"1. Woche d. Monats: auch Nichts\u00fcchtige willkommen","description_string":"Offen in der 1. Woche des Monats f\u00fcr Nicht-S\u00fcchtige.","lang":"de","id":"3","world_id":"","format_type_enum":"O-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"ar","name_string":"Arabisch","description_string":"In Arabisch","lang":"de","id":"209","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"hbs","name_string":"Serbo-Croatisch","description_string":"In serbo-croatisch Sprache","lang":"de","id":"210","world_id":"","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fr","name_string":"Franz\u00f6sisch","description_string":"In franz\u00f6sischer Sprache","lang":"de","id":"211","world_id":"","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"HY","name_string":"Simultan Online und Pr\u00e4senz","description_string":"Meeting findet statt Online und vorort","lang":"de","id":"212","world_id":"HYBR","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"VM","name_string":"Virtual Meeting","description_string":"Meets Virtually","lang":"de","id":"213","world_id":"VM","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-BT","name_string":"Begrenzte Teilnehmerzahl","description_string":"Begrenzte Teilnehmerzahl","lang":"de","id":"220","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Pu","name_string":"Pop-Up","description_string":"Meeting findet nur f\u00fcr eine begrenzte Zeit statt","lang":"de","id":"216","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Mask","name_string":"Maskenpflicht","description_string":"Pflicht zum Tragen einer medizinischen Gesichtsmaske","lang":"de","id":"217","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-List","name_string":"Teilnehmerliste","description_string":"Dokumentation von Teilnehmer Kontaktdaten","lang":"de","id":"219","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Aussen","name_string":"im Freien","description_string":"Meeting findet im Freien statt.","lang":"de","id":"218","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Abstand","name_string":"Mindestabstand","description_string":"Mindestabstand muss eingehalten werden","lang":"de","id":"221","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-3G","name_string":"3G Regeln","description_string":"Besuchern_innen m\u00fcssen getestet, geimpft oder genesen sein. Der Vermietende Organization fordert, dass wir die Nachweise kontrollieren.","lang":"de","id":"224","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-2G","name_string":"2G Regeln","description_string":"Besuchern_innen m\u00fcssen geimpft oder genesen sein. Der Vermietende Organization fordert, dass wir die Nachweise kontrollieren.","lang":"de","id":"225","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"hu","name_string":"ungarische","description_string":"In ungarische Sprache","lang":"de","id":"227","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SPAD","name_string":"Ein spirituelles Prinzip pro Tag","description_string":"Lesen aus dem Buch Ein spirituelles Prinzip pro Tag","lang":"de","id":"228","world_id":"SPAD","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"F","name_string":"Frauenmeeting","description_string":"Meeting f\u00fcr Frauen","lang":"de","id":"11","world_id":"W","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"M","name_string":"M\u00e4nnermeeting","description_string":"Meeting f\u00fcr M\u00e4nner","lang":"de","id":"15","world_id":"M","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"flinta","name_string":"FLINTA*","description_string":"Meeting (auch) f\u00fcr Female Lesbian Intersexual Nonbinary Trans Agender Personen","lang":"de","id":"229","world_id":"","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp2","name_string":"Woche 2: Sprecher","description_string":"2. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"127","world_id":"","format_type_enum":"FC1-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp3","name_string":"Woche 3: Sprecher","description_string":"3. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"128","world_id":"","format_type_enum":"FC1-3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp4","name_string":"Woche 4: Sprecher","description_string":"4. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"129","world_id":"","format_type_enum":"FC1-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp5","name_string":"Woche 5: Sprecher","description_string":"5. Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"131","world_id":"","format_type_enum":"FC1-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SpL","name_string":"Letzte Woche: Sprecher","description_string":"Letzte Woche des Monats: ein eingeladenes Mitglied berichtet von seiner Genesung","lang":"de","id":"130","world_id":"","format_type_enum":"FC1-L","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"VG","name_string":"Vor\u00fcbergehend Geschlossen","description_string":"Meeting findet vor\u00fcbergehend nicht statt.","lang":"de","id":"215","world_id":"","format_type_enum":"Alert","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Test","name_string":"Negativer Coronatest","description_string":"Teilnehmer m\u00fcssen einen aktuellen negativen Corona-Test vorlegen k\u00f6nnen","lang":"de","id":"222","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"inst","name_string":"Institutsmeeting","description_string":"Institutionsmeeting, CLEAN-Anspruch durch Einrichtung","lang":"de","id":"105","world_id":"RA","format_type_enum":"Alert","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"wf","name_string":"Wechselndes Format","description_string":"Das Meetings-Format wechselt w\u00f6chentlich","lang":"de","id":"110","world_id":"VAR","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"TC","name_string":"Temporarily Closed Facility","description_string":"Das Meeting findet zur Zeit Online statt.","lang":"de","id":"223","world_id":"TC","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"uk","name_string":"Ukrainisch","description_string":"In ukrainischer Sprache","lang":"de","id":"230","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Beh","name_string":"barrierefrei","description_string":"Zugang zum Meeting ist rollstuhlgerecht","lang":"de","id":"33","world_id":"WCHR","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"FuA","name_string":"Frage und Antwort","description_string":"Frage und Antwort","lang":"de","id":"231","world_id":"QA","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KT","name_string":"Tiereverbot","description_string":"Tiere sind nicht zugelassen","lang":"de","id":"94","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"es","name_string":"Spanisch","description_string":"In spanischer Sprache","lang":"de","id":"232","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"}] \ No newline at end of file diff --git a/tests/formats/german-formats-en.json b/tests/formats/german-formats-en.json new file mode 100644 index 0000000..e2e3069 --- /dev/null +++ b/tests/formats/german-formats-en.json @@ -0,0 +1 @@ +[{"key_string":"ru","name_string":"Russian","description_string":"Iin Russian language","lang":"en","id":"109","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O5","name_string":"Week 5: open","description_string":"open to non addicts in the last week of the month","lang":"en","id":"7","world_id":"","format_type_enum":"O-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Med","name_string":"Meditation","description_string":"Part of meeting time is free meditation.","lang":"en","id":"119","world_id":"MED","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"naf","name_string":"Not on holidays","description_string":"Not on holidays","lang":"en","id":"99","world_id":"","format_type_enum":"When","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"BT","name_string":"BasicText","description_string":"Reading from the Basic Text","lang":"en","id":"118","world_id":"BT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Lit","name_string":"Literature","description_string":"Reading from the NA literature","lang":"en","id":"124","world_id":"LIT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Tr","name_string":"Traditions","description_string":"Tradition study meeting","lang":"en","id":"117","world_id":"TRAD","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"S","name_string":"Step","description_string":"Step study meeting","lang":"en","id":"113","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"kf","name_string":"kids welcome","description_string":"children are welcome","lang":"en","id":"91","world_id":"CW","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp","name_string":"Speaker","description_string":"Speaker Meeting","lang":"en","id":"115","world_id":"SPK","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KK","name_string":"No kids","description_string":"Please do not bring children","lang":"en","id":"204","world_id":"NC","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"new","name_string":"Newcomer","description_string":"Esp. addressing newcomers","lang":"en","id":"112","world_id":"BEG","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"EF","name_string":"It works","description_string":"Reading from book 'It works, how and why'","lang":"en","id":"121","world_id":"IW","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"CL","name_string":"Living Clean","description_string":"Reading from the book 'Living Clean'","lang":"en","id":"120","world_id":"LC","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Essf","name_string":"No food","description_string":"Please do not bring food","lang":"en","id":"203","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"p","name_string":"Break","description_string":"With break","lang":"en","id":"106","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"nfh","name_string":"Just for Today","description_string":"Reading the days text from 'Just for Today'","lang":"en","id":"111","world_id":"JFT","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"glbtiiq","name_string":"LGBTQ*","description_string":"Gay, Lesbian, Transgender, Intersexual, Queer etc.","lang":"en","id":"10","world_id":"GL","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"T","name_string":"Topic","description_string":"Suggested topic for sharing","lang":"en","id":"116","world_id":"TOP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"schf","name_string":"Step WrG","description_string":"Step Working Guide","lang":"en","id":"114","world_id":"SWG","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"de","name_string":"German","description_string":"In German language","lang":"en","id":"125","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"GT","name_string":"Birthdays","description_string":"Meeting celebrates clean birthdays","lang":"en","id":"207","world_id":"","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"sch10","name_string":"step 10","description_string":"Meeting focuses on step 10","lang":"en","id":"205","world_id":"STEP","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp1","name_string":"Week 1: Speaker","description_string":"First week of the month: Speaker Meeting","lang":"en","id":"126","world_id":"","format_type_enum":"FC1-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"FuAL","name_string":"Q&A","description_string":"Last week of the month: Question and Answer","lang":"en","id":"122","world_id":"QA","format_type_enum":"FC1-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"pl","name_string":"Polish","description_string":"In Polish","lang":"en","id":"208","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O1","name_string":"Week 1: open","description_string":"open to non addicts in the first week of the month","lang":"en","id":"3","world_id":"","format_type_enum":"O-1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O2","name_string":"Week 2: open","description_string":"open to non addicts in the second week of the month","lang":"en","id":"4","world_id":"","format_type_enum":"O-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O3","name_string":"Week 3: open","description_string":"open to non addicts in the third week of the month","lang":"en","id":"5","world_id":"","format_type_enum":"O-3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O4","name_string":"Week 4: open","description_string":"open to non addicts in the fourth week of the month","lang":"en","id":"6","world_id":"","format_type_enum":"O-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"OL","name_string":"Last Week: open","description_string":"in the last week of a month open to non addicts","lang":"en","id":"8","world_id":"","format_type_enum":"O-L","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"en","name_string":"English","description_string":"In English language","lang":"en","id":"107","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fa","name_string":"Farsi","description_string":"In Farsi language","lang":"en","id":"108","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"O","name_string":"non-addicts welcome","description_string":"open to non addicts.","lang":"en","id":"2","world_id":"OPEN","format_type_enum":"O-*","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"ar","name_string":"Arabic","description_string":"In Arabic","lang":"en","id":"209","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"hbs","name_string":"Serbo-Croatian","description_string":"In Serbo-Croatian Language","lang":"en","id":"210","world_id":"","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"fr","name_string":"French","description_string":"In French language","lang":"en","id":"211","world_id":"","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"HY","name_string":"Simultaneously Online and Physical","description_string":"Meeting takes place online and in physical room","lang":"en","id":"212","world_id":"HYBR","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"VM","name_string":"Virtual Meeting","description_string":"Meets Virtually","lang":"en","id":"213","world_id":"VM","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-BT","name_string":"Limited number of attendees","description_string":"Limited number of attendees","lang":"en","id":"220","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"PopUp","name_string":"Pop-Up","description_string":"Meeting will take place for a limited time","lang":"en","id":"216","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Mask","name_string":"Mask required","description_string":"Particpants must wear medical masks","lang":"en","id":"217","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Aussen","name_string":"Outdoors","description_string":"Meeting takes place outdoors","lang":"en","id":"218","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-List","name_string":"Attendence List","description_string":"Contact Infomation will be collected","lang":"en","id":"219","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Abstand","name_string":"Social Distancing","description_string":"Social distancing must be maintained","lang":"en","id":"221","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-3G","name_string":"Attendees must be tested, vaccinated or be recovered from Covid-19","description_string":"Attendees must be tested, vaccinated or be recovered from Covid-19. The owners of the space require that we check and document that these rules are enforced.","lang":"en","id":"224","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-2G","name_string":"Attendees must be tested, vaccinated or be recovered from Covid-19","description_string":"Attendees must be vaccinated or be recovered from Covid-19. The owners of the space require that we check and document that these rules are enforced.","lang":"en","id":"225","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"hu","name_string":"Hungarian","description_string":"In Hungarian Language","lang":"en","id":"227","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SPAD","name_string":"A Spiritual Principle a Day","description_string":"This meeting is focused on discussion of the book A Spiritual Principle a Day.","lang":"en","id":"228","world_id":"SPAD","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"F","name_string":"Women","description_string":"Womens meeting","lang":"en","id":"11","world_id":"W","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"M","name_string":"Mens Meeting","description_string":"Mens Meeting","lang":"en","id":"15","world_id":"M","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"flinta","name_string":"FLINTA*","description_string":"Meeting (also) open for Female Lesbian Intersexual Nonbinary Trans Agender People","lang":"en","id":"229","world_id":"","format_type_enum":"FC3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp2","name_string":"Week 2: Speaker","description_string":"Second week of the month: Speaker Meeting","lang":"en","id":"127","world_id":"","format_type_enum":"FC1-2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp3","name_string":"Week 3: Speaker","description_string":"Third week of the month: Speaker Meeting","lang":"en","id":"128","world_id":"","format_type_enum":"FC1-3","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp4","name_string":"Week 4: Speaker","description_string":"Fourth week of the month; Speaker Meeting","lang":"en","id":"129","world_id":"","format_type_enum":"FC1-4","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Sp5","name_string":"Week 5: Speaker","description_string":"Fifth week of the month: Speaker Meeting","lang":"en","id":"131","world_id":"","format_type_enum":"FC1-5","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"SpL","name_string":"Last Week: Speaker","description_string":"Last week of the month: Speaker Meeting","lang":"en","id":"130","world_id":"","format_type_enum":"FC1-L","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"VG","name_string":"Temporarily Closed","description_string":"Meeting is temporarily closed.","lang":"en","id":"215","world_id":"","format_type_enum":"Alert","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Co-Test","name_string":"Negative Corona-Test","description_string":"Participants must have proof of a current negative corona test","lang":"en","id":"222","world_id":"","format_type_enum":"Covid","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"inst","name_string":"Institute","description_string":"Meeting in an institution that requires visitors be clean","lang":"en","id":"105","world_id":"RA","format_type_enum":"Alert","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"rf","name_string":"Rotating Format","description_string":"Format of the meeting changes weekly","lang":"en","id":"110","world_id":"VAR","format_type_enum":"","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"TC","name_string":"Temporarily Closed Facility","description_string":"The meeting temporarily takes place online.","lang":"en","id":"223","world_id":"TC","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"uk","name_string":"Ukrainian","description_string":"In Ukrainian language","lang":"en","id":"230","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Beh","name_string":"wheelchair","description_string":"accessible by wheelchair","lang":"en","id":"33","world_id":"WCHR","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"Q&A","name_string":"Question and Answer","description_string":"Question and Answer","lang":"en","id":"231","world_id":"QA","format_type_enum":"FC1","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"KT","name_string":"no dogs","description_string":"animals are not permitted","lang":"en","id":"94","world_id":"","format_type_enum":"FC2","root_server_uri":"https://narcotics-anonymous.de/bmlt"},{"key_string":"es","name_string":"Spanish","description_string":"In Spanish","lang":"en","id":"232","world_id":"LANG","format_type_enum":"LANG","root_server_uri":"https://narcotics-anonymous.de/bmlt"}] \ No newline at end of file diff --git a/tests/mock/class-bread-bmlt.php b/tests/mock/class-bread-bmlt.php new file mode 100644 index 0000000..69feaa8 --- /dev/null +++ b/tests/mock/class-bread-bmlt.php @@ -0,0 +1,47 @@ +format_base = $format_base; + } + public function set_areas(array $areas) + { + $this->areas = $areas; + } + public function get_areas() + { + return $this->areas; + } + public static function parse_field($text) + { + if ($text != '') { + $exploded = explode("#@-@#", $text); + $knt = count($exploded); + if ($knt > 1) { + $text = $exploded[$knt - 1]; + } + } + return $text; + } + public function get_formats_by_language(string $lang) + { + $json = file_get_contents('tests/formats/' . $this->format_base . '-' . $lang . ".json"); + return json_decode($json, true); + } + public static function sortBySubkey(array &$array, string $subkey, int $sortType = SORT_ASC): void + { + if (empty($array)) { + return; + } + foreach ($array as $subarray) { + $keys[] = $subarray[$subkey]; + } + array_multisort($keys, $sortType, $array); + } +} diff --git a/tests/mock/class-bread.php b/tests/mock/class-bread.php new file mode 100644 index 0000000..d99d3f4 --- /dev/null +++ b/tests/mock/class-bread.php @@ -0,0 +1,45 @@ +load_translations(); + $this->options = $options; + $this->bmlt1 = new Bread_Bmlt($this); + } + function bmlt() + { + return $this->bmlt1; + } + function getOptions() + { + return $this->options; + } + function load_translations() + { + $files = scandir("includes/lang"); + foreach ($files as $file) { + if (strpos($file, "translate_") !== 0) { + continue; + } + include 'includes/lang/' . $file; + $key = substr($file, 10, -4); + $this->translate[$key] = $translate; + } + } + public function getTranslateTable() + { + return $this->translate; + } + public function getday($day, $abbreviate = false, $language = 'en') + { + $key = "WEEKDAYS"; + if ($abbreviate) { + $key = "WKDYS"; + } + return $this->translate[$language][$key][$day]; + } +} diff --git a/tests/serviceBodies/berlin.json b/tests/serviceBodies/berlin.json new file mode 100644 index 0000000..5d32e66 --- /dev/null +++ b/tests/serviceBodies/berlin.json @@ -0,0 +1 @@ +[{"id_bigint":"10818","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"11005","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"10:30:00","duration_time":"01:00:00","time_zone":"","formats":"en","lang_enum":"de","longitude":"13.4216428","latitude":"52.5253744","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107","meeting_name":"Steps on a Sunday","location_text":"Volkssolidarit\u00e4t: Stadtteilzentrum Friedrichshain","location_info":"","location_street":"Pauline-Staegemann-Stra\u00dfe 6","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10249","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"M5/M6/M8 Mollstr./Otto-Braun-Stra\u00dfe","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10778","worldid_mixed":"G00013960","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"11:00:00","duration_time":"01:30:00","time_zone":"","formats":"KT,Sp","lang_enum":"de","longitude":"13.41894","latitude":"52.50339","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,115","meeting_name":"HEILE HAUS Meeting","location_text":"HeileHaus","location_info":"3. OG","location_street":"Waldemarstr. 36","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3, U8 Kottbusser Tor","format_comments":"Erweiterung zum Format#@-@#Sprechermeeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10883","worldid_mixed":"G00391179","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"11:00:00","duration_time":"01:30:00","time_zone":"","formats":"KT","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94","meeting_name":"Schieb keinen Film","location_text":"NA Basis Berlin","location_info":"","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S5, S3, S9, S7 Berlin Ostbahnhof, U5 Weberwiese, Bus 240, 347","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10873","worldid_mixed":"G00386544","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"12:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,ru,Sp4","lang_enum":"de","longitude":"13.4501483","latitude":"52.4741101","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,109,129","meeting_name":"Berlinka - Russisches NA-Meeting","location_text":"","location_info":"","location_street":"Schudomastra\u00dfe 45","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12055","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10042","worldid_mixed":"G00053158","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"2","start_time":"13:00:00","duration_time":"01:30:00","time_zone":"","formats":"O,kf,KT,CL,VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,91,94,120,213","meeting_name":"CLEAN LEBEN - Die Reise geht auch online weiter","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10838","worldid_mixed":"G00385407","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"13:00:00","duration_time":"01:45:00","time_zone":"","formats":"O1,Beh,kf,KT,CL,Lit","lang_enum":"de","longitude":"13.385534","latitude":"52.5381969","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,33,91,94,120,124","meeting_name":"Clean leben - die Reise geht weiter","location_text":"Waschk\u00fcche","location_info":"","location_street":"Feldstr. 10","location_city_subsection":"Gesundbrunnen","location_neighborhood":"Gesundbrunnen","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"13355","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U6 Schwartzkopffstr. / U8 Voltastr. / Bus 247 Gartenplatz / S Nordbahnhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10897","worldid_mixed":"G00391953","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"16:00:00","duration_time":"01:30:00","time_zone":"","formats":"en","lang_enum":"de","longitude":"13.4445071","latitude":"52.4407709","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107","meeting_name":"SWIR - Sexworkers in Recovery","location_text":"","location_info":"Aus Sicherheitsgr\u00fcnden wird die Adresse nicht ver\u00f6ffentlicht. Bei Interesse bitte an diese Mail-Adresse wenden/ To protect the authenticity of this meeting, please contact: swirberlin@protonmail.com","location_street":"","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10833","worldid_mixed":"G00338436","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"fa","lang_enum":"de","longitude":"13.30253","latitude":"52.51658","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"108","meeting_name":"Farsi Meeting \u062c\u0644\u0633\u0647 \u0641\u0627\u0631\u0633\u06cc \u0622\u0631\u0627\u0645\u0634","location_text":"Landesstelle Berlin f. Suchtfragen","location_info":"","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10585","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10929","worldid_mixed":"G00205455","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"O2,O4,O5,Beh,kf,KT,Sp1,Sp3","lang_enum":"de","longitude":"13.5468553","latitude":"52.4377325","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,6,7,33,91,94,126,128","meeting_name":"Carpe Diem Today","location_text":"Selbsthilfekontaktstelle Treptow-K\u00f6penick Eigeninitiative","location_info":"","location_street":"Genossenschaftsstr. 70, 1. Etage","location_city_subsection":"Treptow","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12489","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Tram 61, 62, 63 Marktplatz Adlershof, S8 Adlershof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10869","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"18:00:00","duration_time":"01:00:00","time_zone":"","formats":"M,S,Sp1","lang_enum":"de","longitude":"13.0496803","latitude":"52.4002434","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"15,113,126","meeting_name":"M\u00e4nnermeeting Potsdam City","location_text":"SEKIZ","location_info":"","location_street":"Hermann-Elflein-Str. 11","location_city_subsection":"","location_neighborhood":"","location_municipality":"Potsdam","location_sub_province":"","location_province":"","location_postal_code_1":"14467","location_nation":"","comments":"Sprechermeeting bis 19:15 Uhr","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"Tram 91, Bus 606, 631, 605, 695 Luisenplatz S\u00fcd, Park Sanssouci","format_comments":"Schrittemeeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10773","worldid_mixed":"G00386548","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"O4,KT,EF,Sp2","lang_enum":"de","longitude":"13.3344441","latitude":"52.466359","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"6,94,121,127","meeting_name":"Es funktioniert","location_text":"Nachbarschaftshaus Friedenau","location_info":"","location_street":"Holsteinischen Stra\u00dfe 30","location_city_subsection":"Friedenau","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12161","location_nation":"","comments":"Lesen aus dem Es funktioniert - Wie und Warum der Schritte 1-12.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S1 Feuerbachstr., U9 Walter-Schreiber-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10993","worldid_mixed":"G00318059","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"18:30:00","duration_time":"01:30:00","time_zone":"","formats":"KT,en,Med,de","lang_enum":"de","longitude":"13.4444052","latitude":"52.4681932","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,107,119,125","meeting_name":"Meditationsmeeting","location_text":"Confamilia","location_info":"","location_street":"Lahnstra\u00dfe 84","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12055","location_nation":"Deutschland","comments":"Die Meditationszeit betr\u00e4gt zwanzig Minuten in Stille und ist nicht angeleitet","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S+U Neuk\u00f6lln (U7, S41, S42, S45, S46), H Lahnstr./U Neuk\u00f6lln (171, 246, 377)","format_comments":"Erweiterung zum Format#@-@#every language welcome","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10366","worldid_mixed":"G00161544","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"O,BT,EF,Lit","lang_enum":"de","longitude":"13.37879","latitude":"52.4923","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,118,121,124","meeting_name":"Wie es funktioniert","location_text":"Yorckshare","location_info":"","location_street":"Yorckstrasse 26","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10965","location_nation":"Deutschland","comments":"Sprecher-Schritte-Meeting","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10892","worldid_mixed":"G00391952","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"F,en,Sp1,Sp3,flinta","lang_enum":"de","longitude":"13.4191845","latitude":"52.5035082","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"11,107,126,128,229","meeting_name":"International FLINTA Meeting","location_text":"Heile Haus","location_info":"'Welcoming all FLINTA (female, lesbian, intersex, trans and agender)","location_street":"Waldemarstr. 36","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U8, U3 Kottbusser Tor","format_comments":"Erweiterung zum Format#@-@#1st Sunday of the month there is a speaker \u2022 Otherwise we read from It Works How and Why","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10046","worldid_mixed":"G00053160","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"KT,BT","lang_enum":"de","longitude":"13.42079913","latitude":"52.54072759","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,118","meeting_name":"Kerzenlicht-Meeting","location_text":"Ev. Eliasgemeinde","location_info":"","location_street":"G\u00f6hrener Str. 11","location_city_subsection":"Prenzlauer Berg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10437","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S41, S42, S8, S85, S9 Prenzlauer Allee, U2 Eberswalder Str., Tram M2, M10","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10050","worldid_mixed":"G00019787","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"1","start_time":"19:00:00","duration_time":"01:45:00","time_zone":"","formats":"KT,inst,p,nfh,Essf","lang_enum":"de","longitude":"13.32086","latitude":"52.49008","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,105,106,111,203","meeting_name":"Die Pfalzburger","location_text":"Die Pfalzburger","location_info":"","location_street":"Pfalzburger Str. 35-38","location_city_subsection":"Wilmersdorf","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10717","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Blissstr./U9 G\u00fcntzelstr., Bus 101,104,249","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10769","worldid_mixed":"OLM804","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"1","venue_type":"2","start_time":"21:00:00","duration_time":"01:45:00","time_zone":"","formats":"Sp4,VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"129,213","meeting_name":"easy does it","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":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10762","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10405","worldid_mixed":"G00378664","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"10:30:00","duration_time":"01:00:00","time_zone":"","formats":"O,Beh,en","lang_enum":"de","longitude":"13.3915904","latitude":"52.50293633","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,33,107","meeting_name":"Spiritual Principles","location_text":"Bauh\u00fctte Kreuzberg","location_info":"","location_street":"Friedrichstr. 18/19","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10969","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U6 Kochstra\u00dfe, U1/U3 Hallesches Tor","format_comments":"Erweiterung zum Format#@-@#ausgew\u00e4hlte Kurztexte zu spirituellen Prinzipien aus der NA-Literatur
    (selected short texts about spritual principles from NA-literature); SPAD","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10001","worldid_mixed":"G00010896","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"2","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,KT,wf,Essf,VM","lang_enum":"de","longitude":"-75.5276699","latitude":"38.9108325","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,94,110,203,213","meeting_name":"NA-Meeting","location_text":"DND (Drogennotdienst)","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"","location_sub_province":"","location_province":"","location_postal_code_1":"","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"Woche 1: Sprecher; Woche 2: Literatur; 3: Nur f\u00fcr heute; 4: Thema; 5: Literatur","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"10955","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,F,Lit,Sp4","lang_enum":"de","longitude":"13.53105","latitude":"52.4607","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,11,124,129","meeting_name":"Frauen*-Meeting: Sisterhood","location_text":"Frauentreff an der Wuhlheide","location_info":"EG, Alle Menschen, die sich unter der Bezeichnung Frau definieren.","location_street":"Rathenaustra\u00dfe 40","location_city_subsection":"Sch\u00f6neweide","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12459","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S Sch\u00f6neweide, Tramhaltestelle Rathenaustra\u00dfe/Ecke HTW Tram: 27, 67, 60 und 61","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10005","worldid_mixed":"G00013952","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"18:00:00","duration_time":"01:45:00","time_zone":"","formats":"KT,p","lang_enum":"de","longitude":"13.37997","latitude":"52.44834","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,106","meeting_name":"NA-Meeting","location_text":"Tageszentrum Tempelhof","location_info":"","location_street":"Kurf\u00fcrstenstr. 43","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12105","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U6 Westphalweg","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10974","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"18:30:00","duration_time":"01:00:00","time_zone":"","formats":"uk","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"230","meeting_name":"NA-Meeting","location_text":"NA Basis Berlin","location_info":"","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"Berlin Ostbahnhof, U5 Weberwiese","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10812","worldid_mixed":"G00297240","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"wf,Sp1","lang_enum":"de","longitude":"13.2022659","latitude":"52.5355225","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"110,126","meeting_name":"Magic Monday","location_text":"Mauerritze","location_info":"","location_street":"Mauerstra\u00dfe 6","location_city_subsection":"Spandau","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"13597","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10002","worldid_mixed":"G00338427","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"O4,KT,wf","lang_enum":"de","longitude":"13.33436","latitude":"52.46103","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"6,94,110","meeting_name":"Spirituelle Prinzipien","location_text":"JeverNeun Jugend- und Familienzentrum","location_info":"","location_street":"Jeverstr. 9","location_city_subsection":"Steglitz","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12157","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S1 / Bus M76 Feuerbachstr, U9 Walter-Schreiber-Platz","format_comments":"Erweiterung zum Format#@-@#1. & 3. Woche Sprecher, sonst wechselndes Format;","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10003","worldid_mixed":"G00161549","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"19:00:00","duration_time":"01:45:00","time_zone":"","formats":"O2,Beh,KT,p,nfh,Sp2","lang_enum":"de","longitude":"13.36997","latitude":"52.55525","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,33,94,106,111,127","meeting_name":"NA-Meeting","location_text":"J\u00fcdisches Krankenhaus","location_info":"Haus D, Raum 5","location_street":"Heinz-Galinski-Str. 1","location_city_subsection":"Wedding","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"13347","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"50/M13,U8/U9 Osloer Str.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10718","worldid_mixed":"G00385396","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"19:00:00","duration_time":"01:45:00","time_zone":"","formats":"M,p,wf,T,Lit,Sp1","lang_enum":"de","longitude":"13.4151686","latitude":"52.5645161","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"15,106,110,116,124,126","meeting_name":"M\u00e4nnermeeting","location_text":"Suchthilfe Pankow (STAB/SPI)","location_info":"","location_street":"Arkonastr. 45-49","location_city_subsection":"Pankow","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"13189","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S+U Pankow","format_comments":"1. Sprechermeeting 2. Literaturmeeting 3. Themenmeeting 4. Literaturmeeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10930","worldid_mixed":"G00386544","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"19:30:00","duration_time":"01:00:00","time_zone":"","formats":"en,Sp1,SPAD","lang_enum":"de","longitude":"13.450143","latitude":"52.4740595","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107,126,228","meeting_name":"Spiritual Contact","location_text":"ADV","location_info":"","location_street":"Schudomastr. 45","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12055","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Bus M41 Hertzbergplatz, U7 Karl-Marx-Str., S45/46 Sonnenallee","format_comments":"Erweiterung zum Format#@-@#SPAD","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11044","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"20:00:00","duration_time":"01:30:00","time_zone":"","formats":"es","lang_enum":"de","longitude":"13.4081908","latitude":"52.536322","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"232","meeting_name":"sigue viniendo - junta en espa\u00f1ol","location_text":"AK Kraak","location_info":"2. Hinterhof, 1. OG","location_street":"Kastanienallee 77","location_city_subsection":"Prenzlauer Berg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10435","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Tram M1, M12 Schwedter Str., und U-Bahn U2 Eberswalder Str.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11016","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"1","start_time":"20:30:00","duration_time":"01:30:00","time_zone":"","formats":"Beh,KT,fa,T","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"33,94,108,116","meeting_name":"NA Meeting","location_text":"NA Basis","location_info":"Raum 217","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"Berlin Ostbahnhof, U5 Weberwiese","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10896","worldid_mixed":"G00391953","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"2","start_time":"21:00:00","duration_time":"01:30:00","time_zone":"","formats":"en,VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107,213","meeting_name":"SWIR - Sexworkers in Recovery","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":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10719","worldid_mixed":"OLM805","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"2","venue_type":"2","start_time":"21:00:00","duration_time":"01:30:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Es funktioniert","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"Erweiterung zum Format#@-@#Schrittemeeting, lesen aus 'Es funktioniert'","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10865","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10007","worldid_mixed":"G00019784","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"11:00:00","duration_time":"01:30:00","time_zone":"","formats":"O4,KT,Essf,SPAD","lang_enum":"de","longitude":"13.4269287","latitude":"52.4949326","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"6,94,203,228","meeting_name":"Clean in Berlin","location_text":"\u00d6lberggemeinde","location_info":"im Souterrain/Keller \u00fcber Parkplatz","location_street":"Lausitzer Str. 29","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3 G\u00f6rlitzer Bahnhof, U8 Sch\u00f6nleinstr., M29,","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10871","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"16:45:00","duration_time":"02:00:00","time_zone":"","formats":"pl","lang_enum":"de","longitude":"13.3024301","latitude":"52.5164955","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"208","meeting_name":"Przy-ja\u017ani","location_text":"","location_info":"","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"10585","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10008","worldid_mixed":"G00338436","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"fa,CL","lang_enum":"de","longitude":"13.30253","latitude":"52.51658","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"108,120","meeting_name":"Farsi Meeting \u062c\u0644\u0633\u0647 \u0641\u0627\u0631\u0633\u06cc \u0622\u0631\u0627\u0645\u0634","location_text":"Landesstelle Berlin f. Suchtfragen","location_info":"","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10585","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10310","worldid_mixed":"G00355702","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"nfh,Sp1","lang_enum":"de","longitude":"13.0496703","latitude":"52.4002369","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"111,126","meeting_name":"Nur F\u00fcr Heute","location_text":"SEKIZ","location_info":"","location_street":"Hermann-Elflein-Str.11","location_city_subsection":"","location_neighborhood":"","location_municipality":"Potsdam","location_sub_province":"","location_province":"Brandenburg","location_postal_code_1":"14467","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Tram 91, Bus 606, 631, 605, 695 Potsdam Luisenplatz S\u00fcd, Park Sanssouci","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10365","worldid_mixed":"G00161450","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,KT,S,Sp3","lang_enum":"de","longitude":"13.3344441","latitude":"52.466359","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,94,113,128","meeting_name":"Schritt f\u00fcr Schritt","location_text":"Nachbarschaftshaus Friedenau","location_info":"","location_street":"Holsteinische Stra\u00dfe 30","location_city_subsection":"Friedenau","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12161","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S1 Feuerbachstr., U9 Walter-Schreiber-Platz","format_comments":"Arbeitsmeeting in der 3. Woche des Monats","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10987","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"18:00:00","duration_time":"02:00:00","time_zone":"","formats":"","lang_enum":"de","longitude":"12.3329758","latitude":"52.6055818","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"","meeting_name":"NA Meeting Rathenow","location_text":"Torhaus","location_info":"","location_street":"Bergstrasse 1","location_city_subsection":"","location_neighborhood":"","location_municipality":"Rathenow","location_sub_province":"","location_province":"","location_postal_code_1":"14712","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10742","worldid_mixed":"G00277762","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:00:00","duration_time":"01:15:00","time_zone":"","formats":"ru","lang_enum":"de","longitude":"13.38573","latitude":"52.46772","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"109","meeting_name":"Russisches NA-Meeting","location_text":"Lichtblicke (Selbsthilfezentrum)","location_info":"","location_street":"Tempelhofer Damm 133","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12099","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S41, S42, S45, S46 Tempelhof, U6 Tempelhof/Alt-Tempelhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10933","worldid_mixed":"G00338437","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,Beh,KT,nfh,FuAL,Sp2","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,33,94,111,122,127","meeting_name":"Coming Home","location_text":"NA Basis Berlin","location_info":"Raum 217","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U5 Weberwiese","format_comments":"Erweiterung zum Format#@-@#Woche 3: Tradition des Monats","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10926","worldid_mixed":"G00393048","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:00:00","duration_time":"01:45:00","time_zone":"","formats":"O,KT,p,nfh,Sp3","lang_enum":"de","longitude":"13.4531175","latitude":"52.4450537","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,94,106,111,128","meeting_name":"Never Alone","location_text":"Stadtmission Britz","location_info":"","location_street":"Malchiner Str. 73","location_city_subsection":"Britz","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12359","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U7 Parchimer Allee","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10804","worldid_mixed":"G00013955","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:15:00","duration_time":"01:30:00","time_zone":"","formats":"nfh","lang_enum":"de","longitude":"13.3024187","latitude":"52.5165256","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"111","meeting_name":"Gierkezeile","location_text":"","location_info":"","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10585","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10794","worldid_mixed":"G00387434","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:30:00","duration_time":"01:00:00","time_zone":"","formats":"O4,KT,en,fr","lang_enum":"de","longitude":"13.4191845","latitude":"52.5035082","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"6,94,107,211","meeting_name":"Plus Jamais Seul (bilingual)","location_text":"Heile Haus","location_info":"F\u00fcr den Code h\u00e4ngt ein Schild an der T\u00fcr.
    This meeting is bilingual: Everyone is invited to share either in french or in english.","location_street":"Waldemarstr. 36","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10999","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U8, U3 Kottbusser Tor","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10761","worldid_mixed":"G00385403","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"3","venue_type":"1","start_time":"19:30:00","duration_time":"01:30:00","time_zone":"","formats":"O,Beh,en,CL,Sp1,Sp3","lang_enum":"de","longitude":"13.39278","latitude":"52.52806","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,33,107,120,126,128","meeting_name":"No Matter What","location_text":"Begegnungsst\u00e4tte Mehr Mitte - Volkssolidarit\u00e4t Berlin","location_info":"","location_street":"Torstra\u00dfe 190","location_city_subsection":"Mitte","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"Berlin","location_postal_code_1":"10115","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U8 Rosenthaler Platz, S Oranienburger Str.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10978","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"07:00:00","duration_time":"01:00:00","time_zone":"","formats":"en,de,SPAD","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107,125,228","meeting_name":"spiritual espresso","location_text":"NA Basis Berlin","location_info":"Room 217","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"Nur f\u00fcr S\u00fcchtige oder die, die glauben, ein Problem mit Drogen zu haben.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Berlin Ostbahnhof, U5 Weberwiese","format_comments":"Das SPAD wird in englisch und deutsch gelesen.","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10866","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10013","worldid_mixed":"G00338447","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"10:30:00","duration_time":"01:00:00","time_zone":"","formats":"O,kf,en,sch10","lang_enum":"de","longitude":"13.46451","latitude":"52.5098","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,91,107,205","meeting_name":"Rise and Shine in Friedrichshain","location_text":"Selbsthilfe-Treffpunkt Friedrichshain-Kreuzberg","location_info":"","location_street":"Boxhagener Str. 89","location_city_subsection":"Friedrichshain","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10245","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"Bus 240/Tram 21 Wismarplatz, Tram 13 Holteistr., U5 Samariterstr.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10923","worldid_mixed":"OLM1397","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"2","start_time":"11:00:00","duration_time":"01:00:00","time_zone":"","formats":"nfh,VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"111,213","meeting_name":"Mittwochsgruppe: Sucht hat keine Feiertage","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"10893","worldid_mixed":"G00355705","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"12:00:00","duration_time":"01:45:00","time_zone":"","formats":"O,kf,KT,CL","lang_enum":"de","longitude":"13.4099341","latitude":"52.5303581","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,91,94,120","meeting_name":"Mittwoch-Mittags-Meeting","location_text":"Herz-Jesu-Gemeinde","location_info":"","location_street":"Sch\u00f6nhauser Allee 182","location_city_subsection":"Prenzlauer Berg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10119","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U2 Senefelder Platz, U2 Rosa-Luxemburg-Platz, U8 Rosenthaler Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11033","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"13:30:00","duration_time":"01:30:00","time_zone":"","formats":"Beh,KT,Lit","lang_enum":"de","longitude":"13.4466865","latitude":"52.4772809","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"33,94,124","meeting_name":"Queer und Allies","location_text":"Selbsthilfezentrum Neuk\u00f6lln-Nord","location_info":"
    Bitte an der T\u00fcr klingeln!","location_street":"Wilhelm-Busch-Str. 12","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12043","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Karl-Marx-Stra\u00dfe, S41/S42 Sonnenallee, Bus M41/171 Hertzbergplatz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10015","worldid_mixed":"G00268640","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"fa,EF","lang_enum":"de","longitude":"13.38573","latitude":"52.46772","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"108,121","meeting_name":"Farsi Meeting \u062c\u0644\u0633\u0647 \u0641\u0627\u0631\u0633\u06cc","location_text":"Lichtblicke (Selbsthilfezentrum)","location_info":"","location_street":"Tempelhofer Damm 133","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12099","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S41, S42, S45, S46 Tempelhof, U6 Tempelhof/Alt-Tempelhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11045","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"17:15:00","duration_time":"01:15:00","time_zone":"","formats":"","lang_enum":"de","longitude":"13.80209","latitude":"52.83382","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"","meeting_name":"Go Clean Eberswalde","location_text":"Cafe Br\u00fccke","location_info":"","location_street":"Eisenbahnstra\u00dfe 52","location_city_subsection":"","location_neighborhood":"","location_municipality":"Eberswalde","location_sub_province":"","location_province":"","location_postal_code_1":"16225","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"Bus 861 Grabowstr., RB24 Eberswalde Hbf","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10420","worldid_mixed":"OLM796","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"2","start_time":"18:30:00","duration_time":"01:00:00","time_zone":"","formats":"glbtiiq,en,nfh,Sp1,VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"10,107,111,126,213","meeting_name":"Trans, Nonbinary and Womxn Speaker Meeting","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"Those who identify as one of the above or are questioning their gender identity are welcome.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"10625","worldid_mixed":"G00385407","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"18:30:00","duration_time":"01:45:00","time_zone":"","formats":"O4,p,CL,Sp2","lang_enum":"de","longitude":"13.3802656","latitude":"52.4480942","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"6,106,120,127","meeting_name":"Clean Leben, die Reise geht weiter \u2661","location_text":"Tageszentrum Tempelhof","location_info":"","location_street":"Kurf\u00fcrstenstr. 43","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12105","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U6 Westphalweg","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11048","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"O,glbtiiq,en","lang_enum":"de","longitude":"13.4501483","latitude":"52.4741101","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,10,107","meeting_name":"Queer Global Vibes","location_text":"ADV","location_info":"","location_street":"Schudomastr. 45","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12055","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Bus M41 Hertzbergplatz, U7 Karl-Marx-Str., S45/46 Sonnenallee","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10544","worldid_mixed":"G00318062","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"KT,Sp,CL,Sp1","lang_enum":"de","longitude":"13.052954","latitude":"52.529864","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,115,120,126","meeting_name":"Brandenburg Eins","location_text":"Lichtblicke","location_info":"Sprechermeeting bei Bedarf 15 Min l\u00e4nger.","location_street":"Wilhelmstr. 4, 1. OG","location_city_subsection":"Dallgow-D\u00f6beritz","location_neighborhood":"","location_municipality":"Dallgow-D\u00f6beritz","location_sub_province":"Deutschland","location_province":"Brandenburg","location_postal_code_1":"14624","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#100 Meter vom Bahnhof Dallgow-D\u00f6beritz entfernt. RB21, RE2, RE4, RE6, Bus 653, 655, 663","format_comments":"Erweiterung zum Format#@-@#2. Woche: Nur f\u00fcr Heute","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10951","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:00:00","duration_time":"01:15:00","time_zone":"","formats":"glbtiiq,ru,nfh,Sp1","lang_enum":"de","longitude":"13.4108278","latitude":"52.5377046","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"10,109,111,126","meeting_name":"Russian-speaking Rainbow Meeting","location_text":"Quarteera e.V.","location_info":"Ring Quarteera. Raum im Hinterhaus, durch die rechte T\u00fcr, im 1.UG.","location_street":"Oderberger Str. 60","location_city_subsection":"Prenzlauer Berg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10435","location_nation":"","comments":"Tiere sind erlaubt","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U2 Eberswalder Str.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10016","worldid_mixed":"G00013960","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"O2,KT,wf","lang_enum":"de","longitude":"13.41894","latitude":"52.50339","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,94,110","meeting_name":"Komm wieder!!","location_text":"HeileHaus","location_info":"","location_street":"Waldemarstr. 36","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U1, U3, U8 Kottbusser Tor","format_comments":"Woche 1. Sprecher*in ; 2. Nur f\u00fcr heute; 3. Themen Meeting; 4. Schritt des laufenden Monats - Basic Text; 5. Es funktioniert - Wie und Warum","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10017","worldid_mixed":"deleted","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"Beh,KT,nfh,Sp1","lang_enum":"de","longitude":"13.36997","latitude":"52.55525","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"33,94,111,126","meeting_name":"NA-Meeting","location_text":"J\u00fcdisches Krankenhaus","location_info":"Haus D, Raum 5","location_street":"Heinz-Galinski-Str. 1","location_city_subsection":"Wedding","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"13347","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#M13/50, U8/U9 U Osloer Stra\u00dfe","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10018","worldid_mixed":"G00053152","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"2","start_time":"19:30:00","duration_time":"01:30:00","time_zone":"","formats":"O,KT,Sp,VM","lang_enum":"de","longitude":"13.4272009","latitude":"52.4956904","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,94,115,213","meeting_name":"Go Clean Online-Meeting","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10867","worldid_mixed":"G00053152","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"1","start_time":"19:30:00","duration_time":"01:30:00","time_zone":"","formats":"O,KT,Sp","lang_enum":"de","longitude":"13.42863","latitude":"52.49739","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,94,115","meeting_name":"Go Clean","location_text":"Stadtteilzentrum","location_info":"","location_street":"Lausitzer Str. 8","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U1, U3 G\u00f6rlitzer Bhf","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11012","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"4","venue_type":"2","start_time":"19:45:00","duration_time":"01:00:00","time_zone":"","formats":"en,VM,SPAD,flinta","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107,213,228,229","meeting_name":"NA Meeting","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"","comments":"Video Optional, Open to all on request","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"Erweiterung zum Format#@-@#Spiritual Principle A Day reading then raised hand sharing","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10765","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10350","worldid_mixed":"G00363274","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"2","start_time":"11:30:00","duration_time":"01:30:00","time_zone":"","formats":"CL,VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"120,213","meeting_name":"Clean leben - Online-Literaturmeeting","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10572","worldid_mixed":"G00378666","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"18:00:00","duration_time":"01:00:00","time_zone":"","formats":"","lang_enum":"de","longitude":"13.0770165","latitude":"52.3892423","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"","meeting_name":"M\u00e4nnermeeting Potsdam","location_text":"freiLand Potsdam","location_info":"freiLand Gel\u00e4nde- Haus 1 - Raum 3","location_street":"Friedrich-Engels-Stra\u00dfe 22","location_city_subsection":"","location_neighborhood":"","location_municipality":"Potsdam","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"14473","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Potsdam Hbf S7, RE1, RE7","format_comments":"1. und 3. Woche Themenmeeting, 2. und 4. Woche Literatur","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10956","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"18:00:00","duration_time":"01:00:00","time_zone":"","formats":"Tr","lang_enum":"de","longitude":"13.3342806","latitude":"52.4611839","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"117","meeting_name":"Old School Recovery","location_text":"JeverNeun Jugend- und Familienzentrum","location_info":"Wir bitten Personen mit Grippe- oder Erk\u00e4ltungssymptomen, nicht am Meeting teilzunehmen.

    Das Meeting findet am 24.10. und 31.10.24 nicht statt!.","location_street":"Jeverstr. 9","location_city_subsection":"Steglitz","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12157","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S Feuerbachstra\u00dfe","format_comments":"Wir lesen die Traditionen und teilen dazu.","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10828","worldid_mixed":"G00262787","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"2","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Gelassen in den Abend","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10337","worldid_mixed":"G00361495","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"18:30:00","duration_time":"01:00:00","time_zone":"","formats":"O1,F,Sp4,Essf","lang_enum":"de","longitude":"13.0496803","latitude":"52.4002434","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,11,129,203","meeting_name":"Frauenmeeting","location_text":"SEKIZ","location_info":"","location_street":"Hermann-Elflein-Str. 11","location_city_subsection":"","location_neighborhood":"","location_municipality":"Potsdam","location_sub_province":"","location_province":"Brandenburg","location_postal_code_1":"14467","location_nation":"Deutschland","comments":"Sprecherinnenmeetings gehen bis 19:45 Uhr.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Tram 91,98; Bus 605 Dortusstr.; Tram 92 Brandenburger Str.; Bus 606, 631 Luisenplatz S\u00fcd/Park Sanssouci","format_comments":"Woche 3: Literatur/Schritte; Woche 4: Sprecherin/Audiostick; Woche 5: Literatur/Schritte","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10343","worldid_mixed":"G00363275;","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"18:30:00","duration_time":"01:30:00","time_zone":"","formats":"Lit","lang_enum":"de","longitude":"13.5793573","latitude":"52.4546461","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"124","meeting_name":"NA-Meeting","location_text":"Rabenhaus e.V.","location_info":"","location_street":"Puchanstr. 9","location_city_subsection":"K\u00f6penick","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12555","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S3 K\u00f6penick","format_comments":"Erweiterung zum Format#@-@#Nur f\u00fcr heute","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10904","worldid_mixed":"G00338435","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"O2,KT,wf,Sp3","lang_enum":"de","longitude":"13.37839","latitude":"52.4600049","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,94,110,128","meeting_name":"Wie, Was, Warum","location_text":"Evangelische Kirchengemeinde Alt-Tempelhof","location_info":"2. OG Hermann-Ehlers-Zimmer","location_street":"Kaiserin-Augusta-Str. 23","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12103","location_nation":"Deutschland","comments":"Tiere sind leider nicht gestattet.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Bus 246, 184 Albrechtstr./Manteufelstr.; U6 Kaiserin-Augusta-Str.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10859","worldid_mixed":"G00390155-dup","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"19:00:00","duration_time":"02:00:00","time_zone":"","formats":"Wo1,Wo3,pl","lang_enum":"de","longitude":"13.3024301","latitude":"52.5164955","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"100,102,208","meeting_name":"Przy-ja\u017ani","location_text":"","location_info":"Raum 1","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"Charlottenburg","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"10585","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10860","worldid_mixed":"G00390155","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"19:00:00","duration_time":"02:00:00","time_zone":"","formats":"F,Wo2,Wo4,pl","lang_enum":"de","longitude":"13.3024301","latitude":"52.5164955","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"11,101,103,208","meeting_name":"Kobieta NA","location_text":"","location_info":"Raum 1","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"Charlottenburg","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"10585","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10823","worldid_mixed":"G00386544","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"19:15:00","duration_time":"01:30:00","time_zone":"","formats":"O1,ru,Sp4","lang_enum":"de","longitude":"13.4501483","latitude":"52.4741101","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,109,129","meeting_name":"Berlinka - Russisches NA-Meeting","location_text":"","location_info":"","location_street":"Schudomastra\u00dfe 45","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"12055","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10906","worldid_mixed":"G00184883","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"19:30:00","duration_time":"01:30:00","time_zone":"","formats":"O1,nfh,Sp4","lang_enum":"de","longitude":"13.384284","latitude":"52.5669634","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,111,129","meeting_name":"Kamin-Meeting","location_text":"Haus Ph\u00f6nix","location_info":"","location_street":"Koloniestr. 76","location_city_subsection":"Mitte","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"13359","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S1, S25, S26, Bus 255, M27 S Wollankstr, Bus 250 H Verl\u00e4ngerte Koloniestr.","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10538","worldid_mixed":"G00013957","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"20:00:00","duration_time":"01:00:00","time_zone":"","formats":"M,KT,T,Sp1","lang_enum":"de","longitude":"13.36041","latitude":"52.48767","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"15,94,116,126","meeting_name":"M\u00e4nnermeeting","location_text":"Halk K\u00f6sesi Stadtteilladen","location_info":"","location_street":"Crellestr. 38, Ecke Helmstr.","location_city_subsection":"Sch\u00f6neberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10827","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S1 Julius-Leber-Br\u00fccke, U7 Kleistpark","format_comments":"Erweiterung zum Format#@-@#(Thema als Shooting)","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10868","worldid_mixed":"G00318062","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"2","start_time":"20:00:00","duration_time":"01:00:00","time_zone":"","formats":"Sp3,VM","lang_enum":"de","longitude":"13.0751666","latitude":"52.5696551","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"128,213","meeting_name":"Brandenburg Zwei","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Falkensee","location_sub_province":"Deutschland","location_province":"Brandenburg","location_postal_code_1":"14612","location_nation":"Deutschland","comments":"Das Sprechermeeting geht bis 21:15 Uhr.","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"11000","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"20:00:00","duration_time":"01:00:00","time_zone":"","formats":"Beh,FuA","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"33,231","meeting_name":"Es funktioniert...Wieso, Weshalb, Warum","location_text":"NA-Basis","location_info":"","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"Hunde sind erlaubt","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S5, S3, S9, S7 Berlin Ostbahnhof, U5 Weberwiese, Bus 240, 347","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10921","worldid_mixed":"G00392896","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"1","start_time":"20:30:00","duration_time":"01:00:00","time_zone":"","formats":"en","lang_enum":"de","longitude":"13.4076108","latitude":"52.4929224","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"107","meeting_name":"NA Meeting","location_text":"Nachbarschaftshaus Urbanstra\u00dfe","location_info":"","location_street":"Urbanstr. 21","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10961","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U7 S\u00fcdstern, U1/U3 Prinzenstr., Bus M41","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10615","worldid_mixed":"OLM1048","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"5","venue_type":"2","start_time":"22:00:00","duration_time":"01:30:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Donnerstag NAchtmeeting","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9319670031?pwd=M3ByWFREcSt2SHVnM21OMWxRR29Sdz09","virtual_meeting_additional_info":"Passwort: 125940","phone_meeting_number":""},{"id_bigint":"10586","worldid_mixed":"G00205446","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"08:00:00","duration_time":"01:00:00","time_zone":"","formats":"O,KT,nfh","lang_enum":"de","longitude":"13.40802","latitude":"52.53124","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,94,111","meeting_name":"Morning Has Broken","location_text":"K.I.S","location_info":"","location_street":"Fehrbelliner Str. 92","location_city_subsection":"Prenzlauer Berg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10119","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U2 Senefelderplatz, U8 Rosenthaler Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10766","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10924","worldid_mixed":"OLM1397","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"2","start_time":"11:00:00","duration_time":"01:15:00","time_zone":"","formats":"nfh,VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"111,213","meeting_name":"Sucht hat keine Feiertage","location_text":"","location_info":"","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"10026","worldid_mixed":"G00007770","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"12:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,Beh,KT,BT,Sp5,Essf","lang_enum":"de","longitude":"13.3128581","latitude":"52.5117696","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,33,94,118,131,203","meeting_name":"NA-Meeting bei SEKIS Charlottenburg","location_text":"SEKIS","location_info":"","location_street":"Bismarckstr. 101, 5. OG., Eingang Weimarer Stra\u00dfe","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10625","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U2 Deutsche Oper","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11030","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"15:00:00","duration_time":"01:30:00","time_zone":"","formats":"O,Beh,kf,KT,T,Lit,Essf","lang_enum":"de","longitude":"13.428532","latitude":"52.4977813","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,33,91,94,116,124,203","meeting_name":"Clean Friday","location_text":"Kreuzberger Stadtteilzentrum","location_info":"Deutschsprachiges Meeting","location_street":"Lausitzer Str.8","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3 G\u00f6rlitzer Bahnhof","format_comments":"Erweiterung zum Format#@-@#Themen Meeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10030","worldid_mixed":"G00161480","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,fa,T","lang_enum":"de","longitude":"13.30253","latitude":"52.51658","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,108,116","meeting_name":"Farsi Meeting \u062c\u0644\u0633\u0647 \u0641\u0627\u0631\u0633\u06cc \u0622\u0631\u0627\u0645\u0634","location_text":"Landesstelle Berlin f. Suchtfragen","location_info":"","location_street":"Gierkezeile 39","location_city_subsection":"Charlottenburg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10585","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U7 Richard-Wagner-Platz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10593","worldid_mixed":"G00126257","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"17:00:00","duration_time":"01:45:00","time_zone":"","formats":"O2,p,wf,T,Lit,Sp1","lang_enum":"de","longitude":"13.4151686","latitude":"52.5645161","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,106,110,116,124,126","meeting_name":"Just for Friday","location_text":"Suchthilfe Pankow (STAB/SPI)","location_info":"","location_street":"Arkonastr. 45-49","location_city_subsection":"Pankow","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"13189","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S+U Pankow","format_comments":"1. Sprechermeeting 2. Literaturmeeting 3. Themenmeeting 4. Literaturmeeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10743","worldid_mixed":"G00277762","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:00:00","duration_time":"01:15:00","time_zone":"","formats":"ru","lang_enum":"de","longitude":"13.38573","latitude":"52.46772","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"109","meeting_name":"Russisches NA Meeting","location_text":"Lichtblicke (Selbsthilfezentrum)","location_info":"","location_street":"Tempelhofer Damm 133","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12099","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S41, S42, S45, S46 Tempelhof, U6 Tempelhof/Alt-Tempelhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"11022","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"F","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"11","meeting_name":"Frauen in der Basis","location_text":"NA Basis Berlin","location_info":"","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain-Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S5, S3, S9, S7 Berlin Ostbahnhof, U5 Weberwiese, Bus 240, 347","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10905","worldid_mixed":"G00392481","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"","lang_enum":"de","longitude":"13.3550646","latitude":"52.4996974","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"","meeting_name":"NA-Meeting","location_text":"DND (Drogennotdienst)","location_info":"","location_street":"B\u00fclowstr. 106","location_city_subsection":"Sch\u00f6neberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10783","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U2 Nollendorfplatz","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10910","worldid_mixed":"G00392480","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:00:00","duration_time":"01:30:00","time_zone":"","formats":"wf,nfh,S,Tr,Lit,Sp5","lang_enum":"de","longitude":"13.4790365","latitude":"52.5082676","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"110,111,113,117,124,131","meeting_name":"NA Meeting","location_text":"Kiezspinne Lichtenberg","location_info":"","location_street":"Schulze-Boysen-Str. 38","location_city_subsection":"Lichtenberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10365","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S-Bahn, U5, M13, M16 Frankfurter Allee","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10540","worldid_mixed":"G00303415","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:30:00","duration_time":"01:30:00","time_zone":"","formats":"O2,KT,CL,Sp4","lang_enum":"de","longitude":"13.25417","latitude":"52.43366","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"4,94,120,129","meeting_name":"NA-Meeting","location_text":"Selbsthilfekontaktstelle Villa Mittelhof","location_info":"Im Nebengeb\u00e4ude rechts neben der Villa","location_street":"K\u00f6nigstr. 42-43","location_city_subsection":"Zehlendorf","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"Berlin","location_postal_code_1":"14163","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S Zehlendorf, Bus M48, X10, X11, 101, 112, 115, 118, 285, 624,; Zehlendorf Eiche","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10032","worldid_mixed":"G00289773","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"18:45:00","duration_time":"01:15:00","time_zone":"","formats":"O,glbtiiq,Beh,wf,nfh,T","lang_enum":"de","longitude":"13.35417","latitude":"52.50002","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,10,33,110,111,116","meeting_name":"LGBTIQ+ / Rainbow Meeting","location_text":"Mann-o-Meter, Eingang MANEO","location_info":"
    Bitte ausschlie\u00dflich die untere Klingel (Gruppenraum) benutzen!","location_street":"B\u00fclowstr. 106","location_city_subsection":"Sch\u00f6neberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10783","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U2, U3, U4 Nollendorfplatz","format_comments":"Erweiterung zum Format#@-@#Format: abwechselnd Themenmeeting und Lesen einer zuf\u00e4llig ausgew\u00e4hlten Seite aus dem NFH","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10815","worldid_mixed":"G00388156","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"19:00:00","duration_time":"01:00:00","time_zone":"","formats":"Sp2","lang_enum":"de","longitude":"13.1943868","latitude":"52.5208573","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"127","meeting_name":"NA-Meeting","location_text":"","location_info":"","location_street":"Adamstra\u00dfe 39","location_city_subsection":"Spandau","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"13595","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10525","worldid_mixed":"G00338426","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"1","start_time":"20:00:00","duration_time":"01:00:00","time_zone":"","formats":"KT,en,nfh,T,KK","lang_enum":"de","longitude":"13.41894","latitude":"52.50339","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"94,107,111,116,204","meeting_name":"Loud & Proud","location_text":"HeileHaus","location_info":"Hinterhaus, 3. OG/backyard 3rd floor","location_street":"Waldemarstr. 36","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3, U8 Kottbusser Tor","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10532","worldid_mixed":"OLM798","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"2","start_time":"22:00:00","duration_time":"01:30:00","time_zone":"","formats":"T,VM","lang_enum":"de","longitude":"13.3602743","latitude":"52.5372897","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"116,213","meeting_name":"Gemeinsam ins Wochenende","location_text":"","location_info":"","location_street":"","location_city_subsection":"Mitte","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/5525364553?pwd=VStiamNzVWFYb3J2M09yK1QrQW1xUT09","virtual_meeting_additional_info":"Passwort: 938186","phone_meeting_number":""},{"id_bigint":"10962","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"6","venue_type":"2","start_time":"23:55:00","duration_time":"01:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"13.404954","latitude":"52.5200066","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"AKTIVES face 2 face Meeting auf Zoom","location_text":"","location_info":"ZEIG DICH! (durchgehend Kamerapflicht)","location_street":"","location_city_subsection":"","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10819","worldid_mixed":"OLM818","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"2","start_time":"10:00:00","duration_time":"00:35:00","time_zone":"","formats":"VM","lang_enum":"de","longitude":"-118.563659","latitude":"34.235918","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"213","meeting_name":"Schritte schreib Meeting","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":"Schreiben in Stille/Write in silence","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"","format_comments":"","seat_reservation":"","virtual_meeting_link":"https://us02web.zoom.us/j/9079883918?pwd=LzZodFhaS0ZuT0h4RFdKOThKTE1mZz09","virtual_meeting_additional_info":"Passwort: 647529","phone_meeting_number":""},{"id_bigint":"10746","worldid_mixed":"G00385395","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"11:00:00","duration_time":"01:00:00","time_zone":"","formats":"O,nfh,Aussen","lang_enum":"de","longitude":"13.47458432","latitude":"52.4839463","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,111,218","meeting_name":"Park-Meeting am Karpfenteich","location_text":"Am Karpfenteich","location_info":"Bei Sturm, Gewitter bzw. Starkregen, entf\u00e4llt das Meeting kurzfristig. / Bitte eine wetterfeste Sitzunterlage und ggf. Schirm mitbringen!","location_street":"","location_city_subsection":"Treptow","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"12435","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S41, S42, S8, S85, S9 Treptower Park; Bus 165, 166, 265 Alt-Treptow","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10961","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"12:30:00","duration_time":"01:30:00","time_zone":"","formats":"O,en,EF","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,107,121","meeting_name":"Saturday Meeting","location_text":"NA Basis Berlin","location_info":"Room 217","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschlang","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"S Ostbahnhof, U5 Weberwiese","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10876","worldid_mixed":"G00053085","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"14:00:00","duration_time":"01:30:00","time_zone":"","formats":"O,Beh,T","lang_enum":"de","longitude":"13.428532","latitude":"52.4977813","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,33,116","meeting_name":"NA-Meeting","location_text":"Stadtteilzentrum","location_info":"","location_street":"Lausitzer Str. 8","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"10999","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3 G\u00f6rlitzer Bahnhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10051","worldid_mixed":"G00338449","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"16:00:00","duration_time":"01:30:00","time_zone":"","formats":"O1,Lit,Sp3,Sp4","lang_enum":"de","longitude":"13.0496703","latitude":"52.4002369","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,124,128,129","meeting_name":"Sanssouci","location_text":"SEKIZ","location_info":"","location_street":"Hermann-Elflein-Str. 11","location_city_subsection":"","location_neighborhood":"","location_municipality":"Potsdam","location_sub_province":"","location_province":"Brandenburg","location_postal_code_1":"14467","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Tram 91,98, Bus 605 Dortusstr.; Tram 92 Brandenburger Str. ; Bus 606,631 Luisenpl. S\u00fcd/Park Sanssouci","format_comments":"Erweiterung zum Format#@-@#Woche 3: Geburtstagssprechermeeting/Stick","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10825","worldid_mixed":"G00388157","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"16:00:00","duration_time":"01:30:00","time_zone":"","formats":"O,KT,Sp2,Sp4","lang_enum":"de","longitude":"13.428532","latitude":"52.4977813","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"2,94,127,129","meeting_name":"Start Up Meeting","location_text":"Stadtteilzentrum","location_info":"Im Kiez-Cafe","location_street":"Lausitzer Str. 8","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"","location_postal_code_1":"10999","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#U1, U3 G\u00f6rlitzer Bahnhof","format_comments":"Erweiterung zum Format#@-@#1. Woche: Themenmeeting, 3. Woche: Literaturmeeting","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10037","worldid_mixed":"G00268640","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"17:00:00","duration_time":"01:30:00","time_zone":"","formats":"kf,KT,fa,T,Sp1","lang_enum":"de","longitude":"13.38573","latitude":"52.46772","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"91,94,108,116,126","meeting_name":"Farsi Meeting \u062c\u0644\u0633\u0647 \u0641\u0627\u0631\u0633\u06cc","location_text":"Lichtblicke (Selbsthilfezentrum)","location_info":"","location_street":"Tempelhofer Damm 133","location_city_subsection":"Tempelhof","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12099","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S41,42,45,46 Tempelhof, U6 Tempelhof/Alt-Tempelhof","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10744","worldid_mixed":"G00385399","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"18:30:00","duration_time":"01:30:00","time_zone":"","formats":"O1,F,kf,flinta","lang_enum":"de","longitude":"13.4367708","latitude":"52.4924867","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"3,11,91,229","meeting_name":"Frauen/FLINTA-Meeting","location_text":"Ev. Martha-Gemeinde","location_info":"","location_street":"Glogauer Stra\u00dfe 22","location_city_subsection":"Kreuzberg","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"Deutschland","location_province":"Berlin","location_postal_code_1":"10999","location_nation":"","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"U1, U3 G\u00f6rlitzer Bahnhof; U7, U8 Hermannplatz; Bus M29 Glogauer Stra\u00dfe + 171, 194 Pfl\u00fcger Str.","format_comments":"1. Woche: Clean Leben - Die Reise geht weiter, 2. Woche: Spirituelles Prinzip, 3. Woche: Nur f\u00fcr heute - Tagesmeditation, 4. Woche: Sponsorschaft, 5. Woche:\u00a0Sprecherin*","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10958","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"19:00:00","duration_time":"01:15:00","time_zone":"","formats":"ru","lang_enum":"de","longitude":"13.450143","latitude":"52.4740595","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"109","meeting_name":"Don Quixote Meeting","location_text":"","location_info":"","location_street":"Schudomastra\u00dfe 45","location_city_subsection":"Neuk\u00f6lln","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"Berlin","location_postal_code_1":"12055","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#S Sonnenallee, H Mareschstra\u00dfe","format_comments":"","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""},{"id_bigint":"10969","worldid_mixed":"","shared_group_id_bigint":"","service_body_bigint":"3","weekday_tinyint":"7","venue_type":"1","start_time":"19:00:00","duration_time":"01:30:00","time_zone":"","formats":"SpL,SPAD","lang_enum":"de","longitude":"13.4401668","latitude":"52.5127354","distance_in_km":"","distance_in_miles":"","email_contact":"","published":"1","root_server_uri":"https://narcotics-anonymous.de/bmlt","format_shared_id_list":"130,228","meeting_name":"12SODMF","location_text":"NA Basis Berlin","location_info":"Raum 217","location_street":"Franz-Mehring-Platz 1","location_city_subsection":"Friedrichshain","location_neighborhood":"","location_municipality":"Berlin","location_sub_province":"","location_province":"","location_postal_code_1":"10243","location_nation":"Deutschland","comments":"","zone":"","contact_name2":"","contact_name1":"","contact_email1":"","contact_email2":"","public_transport":"\u00d6PNV#@-@#Berlin Ostbahnhof","format_comments":"Erweiterung zum Format#@-@#Spirituelles Prinzip des Tages","seat_reservation":"","virtual_meeting_link":"","virtual_meeting_additional_info":"","phone_meeting_number":""}] \ No newline at end of file diff --git a/tinymce/code/plugin.js b/tinymce/code/plugin.js deleted file mode 100644 index 79ade57..0000000 --- a/tinymce/code/plugin.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*global tinymce:true */ - -tinymce.PluginManager.add('code', function(editor) { - function showDialog() { - var win = editor.windowManager.open({ - title: "Source code", - body: { - type: 'textbox', - name: 'code', - multiline: true, - minWidth: editor.getParam("code_dialog_width", 600), - minHeight: editor.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), - spellcheck: false, - style: 'direction: ltr; text-align: left' - }, - onSubmit: function(e) { - // We get a lovely "Wrong document" error in IE 11 if we - // don't move the focus to the editor before creating an undo - // transation since it tries to make a bookmark for the current selection - editor.focus(); - - editor.undoManager.transact(function() { - editor.setContent(e.data.code); - }); - - editor.selection.setCursorLocation(); - editor.nodeChanged(); - } - }); - - // Gecko has a major performance issue with textarea - // contents so we need to set it when all reflows are done - win.find('#code').value(editor.getContent({source_view: true})); - } - - editor.addCommand("mceCodeEditor", showDialog); - - editor.addButton('code', { - icon: 'code', - tooltip: 'Source code', - onclick: showDialog - }); - - editor.addMenuItem('code', { - icon: 'code', - text: 'Source code', - context: 'tools', - onclick: showDialog - }); -}); \ No newline at end of file diff --git a/tinymce/front_page_button/plugin.js b/tinymce/front_page_button/plugin.js deleted file mode 100644 index 0438e22..0000000 --- a/tinymce/front_page_button/plugin.js +++ /dev/null @@ -1,11 +0,0 @@ -tinymce.PluginManager.add('gavickpro_tc_button', function(editor) { - - editor.addButton('gavickpro_tc_button', { - text: 'My test button', - icon: false, - onclick: function() { - editor.insertContent('Hello World!'); - } - }); - -}); \ No newline at end of file diff --git a/tinymce/front_page_button/plugin.min.js b/tinymce/front_page_button/plugin.min.js deleted file mode 100644 index 2efb563..0000000 --- a/tinymce/front_page_button/plugin.min.js +++ /dev/null @@ -1,389 +0,0 @@ -(function() { - tinymce.PluginManager.add('front_page_button', function( editor, url ) { - editor.addButton( 'front_page_button', { - text: 'Meeting List Shortcodes', - icon: false, - type: 'listbox', - menu: [ - { - text: 'INSTRUCTIONS', - menu: [ - { - text: 'Video Instructions', - onclick: function() { - editor.windowManager.open({ - title: 'Shortcode Instructions', - url: 'https://nameetinglist.org/videos/nameetinglist.mp4', - width: 800, - height: 600, - buttons: [{ - text: 'Close', - onclick: 'close' - }] - }); - } - }, - { - text: 'Cheatsheet', - onclick: function() { - editor.windowManager.open({ - title: 'Shortcode Instructions', - url: 'https://nameetinglist.org/videos/table.html', - width: 650, - height: 530, - buttons: [{ - text: 'Close', - onclick: 'close' - }] - }); - } - } - ] - }, - { - text: 'Format Code Legend', - menu: [ - { - text: 'Used Format Codes - Abbreviated - English', - onclick: function() { - editor.insertContent('
    Meeting Format Legend
    [format_codes_used_basic]

    '); - } - }, - { - text: 'Used Format Codes - Abbreviated - Spanish', - onclick: function() { - editor.insertContent('
    Reunión Formato Leyenda

    [format_codes_used_basic_es]

    '); - } - }, - { - text: 'Used Format Codes - Abbreviated - French', - onclick: function() { - editor.insertContent('
    Légende format de réunion

    [format_codes_used_basic_fr]

    '); - } - }, - { - text: 'Used Format Codes - Detailed - English', - onclick: function() { - editor.insertContent('
    Meeting Format Legend

    [format_codes_used_detailed]

    '); - } - }, - { - text: 'Used Format Codes - Detailed - Spanish', - onclick: function() { - editor.insertContent('
    Reunión Formato Leyenda

    [format_codes_used_detailed_es]

    '); - } - }, - { - text: 'All Format Codes - Abbreviated - English', - onclick: function() { - editor.insertContent('
    Meeting Format Legend

    [format_codes_all_basic]

    '); - } - }, - { - text: 'All Format Codes - Detailed - English', - onclick: function() { - editor.insertContent('
    Meeting Format Legend

    [format_codes_all_detailed]

    '); - } - } - ] - }, - { - text: 'Helpline Template', - onclick: function() { - editor.insertContent('
    Helplines

    Big Bend Area - Tallahassee

    877-340-5096

    850-224-2321

    Daytona Beach Area - Volusia County

    800-206-0731

    386-628-0318

    First Coast Area - Duval County

    904-723-5683

    '); - } - }, - { - text: 'Phone List Template', - onclick: function() { - editor.insertContent('
    PHONE NUMBERS
     
     
     
     
     
     
     
     
     
     
     
    '); - } - }, - { - text: 'Header Template', - onclick: function() { - editor.insertContent('
    HEADER TEXT
    '); - } - }, - { - text: 'Service Meetings Template', - onclick: function() { - editor.insertContent('
    Service Meetings

    [service_meetings]

    '); - } - }, - { - text: 'Service Body', - menu: [ - { - text: 'Service Body 1', - onclick: function() { - editor.insertContent('[service_body_1]'); - } - }, - { - text: 'Service Body 2', - onclick: function() { - editor.insertContent('[service_body_2]'); - } - }, - { - text: 'Service Body 3', - onclick: function() { - editor.insertContent('[service_body_3]'); - } - }, - { - text: 'Service Body 4', - onclick: function() { - editor.insertContent('[service_body_4]'); - } - }, - { - text: 'Service Body 5', - onclick: function() { - editor.insertContent('[service_body_5]'); - } - } - ] - }, - { - text: 'Date', - menu: [ - { text: 'Month (UPPER CASE)', onclick: function() { editor.insertContent('[month_upper]'); } }, { text: 'Month (Lower Case)', onclick: function() { editor.insertContent('[month_lower]'); } }, { text: 'Month French (UPPER CASE)', onclick: function() { editor.insertContent('[month_upper_fr]'); } }, { text: 'Month French (Lower Case)', onclick: function() { editor.insertContent('[month_lower_fr]'); } }, { text: 'Month Spanish (UPPER CASE)', onclick: function() { editor.insertContent('[month_upper_es]'); } }, { text: 'Month Spanish (Lower Case)', onclick: function() { editor.insertContent('[month_lower_es]'); } }, { - text: 'Day', - onclick: function() { - editor.insertContent('[day]'); - } - }, - { - text: 'Year', - onclick: function() { - editor.insertContent('[year]'); - } - } - ] - }, - { - text: 'Querystring Overrides', - menu: [ - { - text: 'QueryString Custom Text', - onclick: function() { - editor.insertContent('[querystring_custom_*]'); - } - } - ] - }, - { - text: 'Meeting Count', - onclick: function() { - editor.insertContent('[meeting_count]'); - } - }, - { - text: 'New Page (Booklet Only)', - onclick: function() { - editor.insertContent('

    [page_break]

    '); - } - }, - { - text: 'Start Page Numbers (Booklet Only)', - onclick: function() { - editor.insertContent('

    [start_page_numbers]

    '); - } - }, - { - text: 'New Column (Tri & Quad Fold Only)', - onclick: function() { - editor.insertContent('

    [new_column]

    '); - } - } - ] - }); - editor.addButton( 'custom_template_button_1', { - text: 'Meeting Templates', - icon: false, - type: 'listbox', - menu: [ - { - text: 'One Column Template [Meeting Data]', - onclick: function() { - editor.insertContent('
    time group, location, info, street, city, state, zip (formats) comments
    '); - } - }, - { - text: 'Two Column Template [Time] [Meeting Data]', - onclick: function() { - editor.insertContent('
    timegroup, location, info, street, city, state, zip (formats) comments
    '); - } - }, - { - text: 'Three Column Template [Day] [Time] [Meeting Data]', - onclick: function() { - editor.insertContent('
    day_abbrtimegroup, location, info, street, city, state, zip (formats) comments
    '); - } - } - ] - }); - editor.addButton( 'custom_template_button_2', { - text: 'Meeting Template Fields', - icon: false, - type: 'listbox', - menu: [ - { - text: 'Area', - menu: [ - { - text: 'Area Name (area)', - onclick: function() { - editor.insertContent('area'); - } - }, - { - text: 'Area Initial (area_i)', - onclick: function() { - editor.insertContent('area_i'); - } - } - ] - }, - { - text: 'Meeting Location', - menu: [ - { - text: 'Address (street)', - onclick: function() { - editor.insertContent('street'); - } - }, - { - text: 'Borough (borough)', - onclick: function() { - editor.insertContent('borough'); - } - }, - { - text: 'Bus Lines (bus_lines)', - onclick: function() { - editor.insertContent('bus_lines'); - } - }, - { - text: 'City (city)', - onclick: function() { - editor.insertContent('city'); - } - }, - { - text: 'County (county)', - onclick: function() { - editor.insertContent('county'); - } - }, - { - text: 'Location Name (location)', - onclick: function() { - editor.insertContent('location'); - } - }, - { - text: 'Location Extra Info (info)', - onclick: function() { - editor.insertContent('info'); - } - }, - { text: 'Neighborhood (neighborhood)', onclick: function() { editor.insertContent('neighborhood'); } }, { text: 'State (state)', onclick: function() { editor.insertContent('state'); } }, { - text: 'Zip Code (zip)', - onclick: function() { - editor.insertContent('zip'); - } - } - ] - }, - { - text: 'Meeting Information', - menu: [ - { - text: 'Comments (comments)', - onclick: function() { - editor.insertContent('comments'); - } - }, - { - text: 'Duration Hours (hrs)', - onclick: function() { - editor.insertContent('hrs'); - } - }, - { - text: 'Duration Minutes (mins)', - onclick: function() { - editor.insertContent('mins'); - } - }, - { - text: 'Email Contact (email)', - onclick: function() { - editor.insertContent('email'); - } - }, - { - text: 'Format Codes (formats)', - onclick: function() { - editor.insertContent('formats'); - } - }, - { - text: 'Group Name (group)', - onclick: function() { - editor.insertContent('group'); - } - }, - { - text: 'Start Time (time)', - onclick: function() { - editor.insertContent('time'); - } - }, - { - text: 'Weekday (day)', - onclick: function() { - editor.insertContent('day'); - } - }, - { - text: 'Weekday Abbreviated (day_abbr)', - onclick: function() { - editor.insertContent('day_abbr'); - } - }, - { - text: 'Virtual Meeting Link (virtual_meeting_link)', - onclick: function() { - editor.insertContent('virtual_meeting_link'); - } - }, - { - text: 'Virtual Meeting Info (virtual_meeting_additional_info)', - onclick: function() { - editor.insertContent('virtual_meeting_additional_info'); - } - }, - { - text: 'Phone Meeting Number (phone_meeting_number)', - onclick: function() { - editor.insertContent('phone_meeting_number'); - } - }, - { - text: 'QRCode (virtual_meeting_link)', - onclick: function() { - editor.insertContent('[QRCode code="virtual_meeting_link" size="0.8"]'); - } - }, - ] - } - ] - }); - }); -})(); \ No newline at end of file diff --git a/tinymce/table/plugin.js b/tinymce/table/plugin.js deleted file mode 100644 index 5026889..0000000 --- a/tinymce/table/plugin.js +++ /dev/null @@ -1,2680 +0,0 @@ -/** - * Compiled inline version. (Library mode) - */ - -/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ -/*globals $code */ - -(function(exports, undefined) { - "use strict"; - - var modules = {}; - - function require(ids, callback) { - var module, defs = []; - - for (var i = 0; i < ids.length; ++i) { - module = modules[ids[i]] || resolve(ids[i]); - if (!module) { - throw 'module definition dependecy not found: ' + ids[i]; - } - - defs.push(module); - } - - callback.apply(null, defs); - } - - function define(id, dependencies, definition) { - if (typeof id !== 'string') { - throw 'invalid module definition, module id must be defined and be a string'; - } - - if (dependencies === undefined) { - throw 'invalid module definition, dependencies must be specified'; - } - - if (definition === undefined) { - throw 'invalid module definition, definition function must be specified'; - } - - require(dependencies, function() { - modules[id] = definition.apply(null, arguments); - }); - } - - function defined(id) { - return !!modules[id]; - } - - function resolve(id) { - var target = exports; - var fragments = id.split(/[.\/]/); - - for (var fi = 0; fi < fragments.length; ++fi) { - if (!target[fragments[fi]]) { - return; - } - - target = target[fragments[fi]]; - } - - return target; - } - - function expose(ids) { - for (var i = 0; i < ids.length; i++) { - var target = exports; - var id = ids[i]; - var fragments = id.split(/[.\/]/); - - for (var fi = 0; fi < fragments.length - 1; ++fi) { - if (target[fragments[fi]] === undefined) { - target[fragments[fi]] = {}; - } - - target = target[fragments[fi]]; - } - - target[fragments[fragments.length - 1]] = modules[id]; - } - } - -// Included from: js/tinymce/plugins/table/classes/TableGrid.js - -/** - * TableGrid.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class creates a grid out of a table element. This - * makes it a whole lot easier to handle complex tables with - * col/row spans. - * - * @class tinymce.tableplugin.TableGrid - * @private - */ -define("tinymce/tableplugin/TableGrid", [ - "tinymce/util/Tools", - "tinymce/Env" -], function(Tools, Env) { - var each = Tools.each; - - function getSpanVal(td, name) { - return parseInt(td.getAttribute(name) || 1, 10); - } - - return function(editor, table) { - var grid, gridWidth, startPos, endPos, selectedCell, selection = editor.selection, dom = selection.dom; - - function buildGrid() { - var startY = 0; - - grid = []; - gridWidth = 0; - - each(['thead', 'tbody', 'tfoot'], function(part) { - var rows = dom.select('> ' + part + ' tr', table); - - each(rows, function(tr, y) { - y += startY; - - each(dom.select('> td, > th', tr), function(td, x) { - var x2, y2, rowspan, colspan; - - // Skip over existing cells produced by rowspan - if (grid[y]) { - while (grid[y][x]) { - x++; - } - } - - // Get col/rowspan from cell - rowspan = getSpanVal(td, 'rowspan'); - colspan = getSpanVal(td, 'colspan'); - - // Fill out rowspan/colspan right and down - for (y2 = y; y2 < y + rowspan; y2++) { - if (!grid[y2]) { - grid[y2] = []; - } - - for (x2 = x; x2 < x + colspan; x2++) { - grid[y2][x2] = { - part: part, - real: y2 == y && x2 == x, - elm: td, - rowspan: rowspan, - colspan: colspan - }; - } - } - - gridWidth = Math.max(gridWidth, x + 1); - }); - }); - - startY += rows.length; - }); - } - - function cloneNode(node, children) { - node = node.cloneNode(children); - node.removeAttribute('id'); - - return node; - } - - function getCell(x, y) { - var row; - - row = grid[y]; - if (row) { - return row[x]; - } - } - - function setSpanVal(td, name, val) { - if (td) { - val = parseInt(val, 10); - - if (val === 1) { - td.removeAttribute(name, 1); - } else { - td.setAttribute(name, val, 1); - } - } - } - - function isCellSelected(cell) { - return cell && (dom.hasClass(cell.elm, 'mce-item-selected') || cell == selectedCell); - } - - function getSelectedRows() { - var rows = []; - - each(table.rows, function(row) { - each(row.cells, function(cell) { - if (dom.hasClass(cell, 'mce-item-selected') || (selectedCell && cell == selectedCell.elm)) { - rows.push(row); - return false; - } - }); - }); - - return rows; - } - - function deleteTable() { - var rng = dom.createRng(); - - rng.setStartAfter(table); - rng.setEndAfter(table); - - selection.setRng(rng); - - dom.remove(table); - } - - function cloneCell(cell) { - var formatNode, cloneFormats = {}; - - if (editor.settings.table_clone_elements !== false) { - cloneFormats = Tools.makeMap( - (editor.settings.table_clone_elements || 'strong em b i span font h1 h2 h3 h4 h5 h6 p div').toUpperCase(), - /[ ,]/ - ); - } - - // Clone formats - Tools.walk(cell, function(node) { - var curNode; - - if (node.nodeType == 3) { - each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { - if (!cloneFormats[node.nodeName]) { - return; - } - - node = cloneNode(node, false); - - if (!formatNode) { - formatNode = curNode = node; - } else if (curNode) { - curNode.appendChild(node); - } - - curNode = node; - }); - - // Add something to the inner node - if (curNode) { - curNode.innerHTML = Env.ie ? ' ' : '
    '; - } - - return false; - } - }, 'childNodes'); - - cell = cloneNode(cell, false); - setSpanVal(cell, 'rowSpan', 1); - setSpanVal(cell, 'colSpan', 1); - - if (formatNode) { - cell.appendChild(formatNode); - } else { - if (!Env.ie || Env.ie > 10) { - cell.innerHTML = '
    '; - } - } - - return cell; - } - - function cleanup() { - var rng = dom.createRng(), row; - - // Empty rows - each(dom.select('tr', table), function(tr) { - if (tr.cells.length === 0) { - dom.remove(tr); - } - }); - - // Empty table - if (dom.select('tr', table).length === 0) { - rng.setStartBefore(table); - rng.setEndBefore(table); - selection.setRng(rng); - dom.remove(table); - return; - } - - // Empty header/body/footer - each(dom.select('thead,tbody,tfoot', table), function(part) { - if (part.rows.length === 0) { - dom.remove(part); - } - }); - - // Restore selection to start position if it still exists - buildGrid(); - - // If we have a valid startPos object - if (startPos) { - // Restore the selection to the closest table position - row = grid[Math.min(grid.length - 1, startPos.y)]; - if (row) { - selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); - selection.collapse(true); - } - } - } - - function fillLeftDown(x, y, rows, cols) { - var tr, x2, r, c, cell; - - tr = grid[y][x].elm.parentNode; - for (r = 1; r <= rows; r++) { - tr = dom.getNext(tr, 'tr'); - - if (tr) { - // Loop left to find real cell - for (x2 = x; x2 >= 0; x2--) { - cell = grid[y + r][x2].elm; - - if (cell.parentNode == tr) { - // Append clones after - for (c = 1; c <= cols; c++) { - dom.insertAfter(cloneCell(cell), cell); - } - - break; - } - } - - if (x2 == -1) { - // Insert nodes before first cell - for (c = 1; c <= cols; c++) { - tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); - } - } - } - } - } - - function split() { - each(grid, function(row, y) { - each(row, function(cell, x) { - var colSpan, rowSpan, i; - - if (isCellSelected(cell)) { - cell = cell.elm; - colSpan = getSpanVal(cell, 'colspan'); - rowSpan = getSpanVal(cell, 'rowspan'); - - if (colSpan > 1 || rowSpan > 1) { - setSpanVal(cell, 'rowSpan', 1); - setSpanVal(cell, 'colSpan', 1); - - // Insert cells right - for (i = 0; i < colSpan - 1; i++) { - dom.insertAfter(cloneCell(cell), cell); - } - - fillLeftDown(x, y, rowSpan - 1, colSpan); - } - } - }); - }); - } - - function merge(cell, cols, rows) { - var pos, startX, startY, endX, endY, x, y, startCell, endCell, children, count; - - // Use specified cell and cols/rows - if (cell) { - pos = getPos(cell); - startX = pos.x; - startY = pos.y; - endX = startX + (cols - 1); - endY = startY + (rows - 1); - } else { - startPos = endPos = null; - - // Calculate start/end pos by checking for selected cells in grid works better with context menu - each(grid, function(row, y) { - each(row, function(cell, x) { - if (isCellSelected(cell)) { - if (!startPos) { - startPos = {x: x, y: y}; - } - - endPos = {x: x, y: y}; - } - }); - }); - - // Use selection, but make sure startPos is valid before accessing - if (startPos) { - startX = startPos.x; - startY = startPos.y; - endX = endPos.x; - endY = endPos.y; - } - } - - // Find start/end cells - startCell = getCell(startX, startY); - endCell = getCell(endX, endY); - - // Check if the cells exists and if they are of the same part for example tbody = tbody - if (startCell && endCell && startCell.part == endCell.part) { - // Split and rebuild grid - split(); - buildGrid(); - - // Set row/col span to start cell - startCell = getCell(startX, startY).elm; - setSpanVal(startCell, 'colSpan', (endX - startX) + 1); - setSpanVal(startCell, 'rowSpan', (endY - startY) + 1); - - // Remove other cells and add it's contents to the start cell - for (y = startY; y <= endY; y++) { - for (x = startX; x <= endX; x++) { - if (!grid[y] || !grid[y][x]) { - continue; - } - - cell = grid[y][x].elm; - - /*jshint loopfunc:true */ - /*eslint no-loop-func:0 */ - if (cell != startCell) { - // Move children to startCell - children = Tools.grep(cell.childNodes); - each(children, function(node) { - startCell.appendChild(node); - }); - - // Remove bogus nodes if there is children in the target cell - if (children.length) { - children = Tools.grep(startCell.childNodes); - count = 0; - each(children, function(node) { - if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) { - startCell.removeChild(node); - } - }); - } - - dom.remove(cell); - } - } - } - - // Remove empty rows etc and restore caret location - cleanup(); - } - } - - function insertRow(before) { - var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan; - - // Find first/last row - each(grid, function(row, y) { - each(row, function(cell) { - if (isCellSelected(cell)) { - cell = cell.elm; - rowElm = cell.parentNode; - newRow = cloneNode(rowElm, false); - posY = y; - - if (before) { - return false; - } - } - }); - - if (before) { - return !posY; - } - }); - - // If posY is undefined there is nothing for us to do here...just return to avoid crashing below - if (posY === undefined) { - return; - } - - for (x = 0; x < grid[0].length; x++) { - // Cell not found could be because of an invalid table structure - if (!grid[posY][x]) { - continue; - } - - cell = grid[posY][x].elm; - - if (cell != lastCell) { - if (!before) { - rowSpan = getSpanVal(cell, 'rowspan'); - if (rowSpan > 1) { - setSpanVal(cell, 'rowSpan', rowSpan + 1); - continue; - } - } else { - // Check if cell above can be expanded - if (posY > 0 && grid[posY - 1][x]) { - otherCell = grid[posY - 1][x].elm; - rowSpan = getSpanVal(otherCell, 'rowSpan'); - if (rowSpan > 1) { - setSpanVal(otherCell, 'rowSpan', rowSpan + 1); - continue; - } - } - } - - // Insert new cell into new row - newCell = cloneCell(cell); - setSpanVal(newCell, 'colSpan', cell.colSpan); - - newRow.appendChild(newCell); - - lastCell = cell; - } - } - - if (newRow.hasChildNodes()) { - if (!before) { - dom.insertAfter(newRow, rowElm); - } else { - rowElm.parentNode.insertBefore(newRow, rowElm); - } - } - } - - function insertCol(before) { - var posX, lastCell; - - // Find first/last column - each(grid, function(row) { - each(row, function(cell, x) { - if (isCellSelected(cell)) { - posX = x; - - if (before) { - return false; - } - } - }); - - if (before) { - return !posX; - } - }); - - each(grid, function(row, y) { - var cell, rowSpan, colSpan; - - if (!row[posX]) { - return; - } - - cell = row[posX].elm; - if (cell != lastCell) { - colSpan = getSpanVal(cell, 'colspan'); - rowSpan = getSpanVal(cell, 'rowspan'); - - if (colSpan == 1) { - if (!before) { - dom.insertAfter(cloneCell(cell), cell); - fillLeftDown(posX, y, rowSpan - 1, colSpan); - } else { - cell.parentNode.insertBefore(cloneCell(cell), cell); - fillLeftDown(posX, y, rowSpan - 1, colSpan); - } - } else { - setSpanVal(cell, 'colSpan', cell.colSpan + 1); - } - - lastCell = cell; - } - }); - } - - function deleteCols() { - var cols = []; - - // Get selected column indexes - each(grid, function(row) { - each(row, function(cell, x) { - if (isCellSelected(cell) && Tools.inArray(cols, x) === -1) { - each(grid, function(row) { - var cell = row[x].elm, colSpan; - - colSpan = getSpanVal(cell, 'colSpan'); - - if (colSpan > 1) { - setSpanVal(cell, 'colSpan', colSpan - 1); - } else { - dom.remove(cell); - } - }); - - cols.push(x); - } - }); - }); - - cleanup(); - } - - function deleteRows() { - var rows; - - function deleteRow(tr) { - var pos, lastCell; - - // Move down row spanned cells - each(tr.cells, function(cell) { - var rowSpan = getSpanVal(cell, 'rowSpan'); - - if (rowSpan > 1) { - setSpanVal(cell, 'rowSpan', rowSpan - 1); - pos = getPos(cell); - fillLeftDown(pos.x, pos.y, 1, 1); - } - }); - - // Delete cells - pos = getPos(tr.cells[0]); - each(grid[pos.y], function(cell) { - var rowSpan; - - cell = cell.elm; - - if (cell != lastCell) { - rowSpan = getSpanVal(cell, 'rowSpan'); - - if (rowSpan <= 1) { - dom.remove(cell); - } else { - setSpanVal(cell, 'rowSpan', rowSpan - 1); - } - - lastCell = cell; - } - }); - } - - // Get selected rows and move selection out of scope - rows = getSelectedRows(); - - // Delete all selected rows - each(rows.reverse(), function(tr) { - deleteRow(tr); - }); - - cleanup(); - } - - function cutRows() { - var rows = getSelectedRows(); - - dom.remove(rows); - cleanup(); - - return rows; - } - - function copyRows() { - var rows = getSelectedRows(); - - each(rows, function(row, i) { - rows[i] = cloneNode(row, true); - }); - - return rows; - } - - function pasteRows(rows, before) { - var selectedRows = getSelectedRows(), - targetRow = selectedRows[before ? 0 : selectedRows.length - 1], - targetCellCount = targetRow.cells.length; - - // Nothing to paste - if (!rows) { - return; - } - - // Calc target cell count - each(grid, function(row) { - var match; - - targetCellCount = 0; - each(row, function(cell) { - if (cell.real) { - targetCellCount += cell.colspan; - } - - if (cell.elm.parentNode == targetRow) { - match = 1; - } - }); - - if (match) { - return false; - } - }); - - if (!before) { - rows.reverse(); - } - - each(rows, function(row) { - var i, cellCount = row.cells.length, cell; - - // Remove col/rowspans - for (i = 0; i < cellCount; i++) { - cell = row.cells[i]; - setSpanVal(cell, 'colSpan', 1); - setSpanVal(cell, 'rowSpan', 1); - } - - // Needs more cells - for (i = cellCount; i < targetCellCount; i++) { - row.appendChild(cloneCell(row.cells[cellCount - 1])); - } - - // Needs less cells - for (i = targetCellCount; i < cellCount; i++) { - dom.remove(row.cells[i]); - } - - // Add before/after - if (before) { - targetRow.parentNode.insertBefore(row, targetRow); - } else { - dom.insertAfter(row, targetRow); - } - }); - - // Remove current selection - dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); - } - - function getPos(target) { - var pos; - - each(grid, function(row, y) { - each(row, function(cell, x) { - if (cell.elm == target) { - pos = {x : x, y : y}; - return false; - } - }); - - return !pos; - }); - - return pos; - } - - function setStartCell(cell) { - startPos = getPos(cell); - } - - function findEndPos() { - var maxX, maxY; - - maxX = maxY = 0; - - each(grid, function(row, y) { - each(row, function(cell, x) { - var colSpan, rowSpan; - - if (isCellSelected(cell)) { - cell = grid[y][x]; - - if (x > maxX) { - maxX = x; - } - - if (y > maxY) { - maxY = y; - } - - if (cell.real) { - colSpan = cell.colspan - 1; - rowSpan = cell.rowspan - 1; - - if (colSpan) { - if (x + colSpan > maxX) { - maxX = x + colSpan; - } - } - - if (rowSpan) { - if (y + rowSpan > maxY) { - maxY = y + rowSpan; - } - } - } - } - }); - }); - - return {x : maxX, y : maxY}; - } - - function setEndCell(cell) { - var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan, x, y; - - endPos = getPos(cell); - - if (startPos && endPos) { - // Get start/end positions - startX = Math.min(startPos.x, endPos.x); - startY = Math.min(startPos.y, endPos.y); - endX = Math.max(startPos.x, endPos.x); - endY = Math.max(startPos.y, endPos.y); - - // Expand end positon to include spans - maxX = endX; - maxY = endY; - - // Expand startX - for (y = startY; y <= maxY; y++) { - cell = grid[y][startX]; - - if (!cell.real) { - if (startX - (cell.colspan - 1) < startX) { - startX -= cell.colspan - 1; - } - } - } - - // Expand startY - for (x = startX; x <= maxX; x++) { - cell = grid[startY][x]; - - if (!cell.real) { - if (startY - (cell.rowspan - 1) < startY) { - startY -= cell.rowspan - 1; - } - } - } - - // Find max X, Y - for (y = startY; y <= endY; y++) { - for (x = startX; x <= endX; x++) { - cell = grid[y][x]; - - if (cell.real) { - colSpan = cell.colspan - 1; - rowSpan = cell.rowspan - 1; - - if (colSpan) { - if (x + colSpan > maxX) { - maxX = x + colSpan; - } - } - - if (rowSpan) { - if (y + rowSpan > maxY) { - maxY = y + rowSpan; - } - } - } - } - } - - // Remove current selection - dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); - - // Add new selection - for (y = startY; y <= maxY; y++) { - for (x = startX; x <= maxX; x++) { - if (grid[y][x]) { - dom.addClass(grid[y][x].elm, 'mce-item-selected'); - } - } - } - } - } - - function moveRelIdx(cellElm, delta) { - var pos, index, cell; - - pos = getPos(cellElm); - index = pos.y * gridWidth + pos.x; - - do { - index += delta; - cell = getCell(index % gridWidth, Math.floor(index / gridWidth)); - - if (!cell) { - break; - } - - if (cell.elm != cellElm) { - selection.select(cell.elm, true); - - if (dom.isEmpty(cell.elm)) { - selection.collapse(true); - } - - return true; - } - } while (cell.elm == cellElm); - - return false; - } - - table = table || dom.getParent(selection.getStart(), 'table'); - - buildGrid(); - - selectedCell = dom.getParent(selection.getStart(), 'th,td'); - if (selectedCell) { - startPos = getPos(selectedCell); - endPos = findEndPos(); - selectedCell = getCell(startPos.x, startPos.y); - } - - Tools.extend(this, { - deleteTable: deleteTable, - split: split, - merge: merge, - insertRow: insertRow, - insertCol: insertCol, - deleteCols: deleteCols, - deleteRows: deleteRows, - cutRows: cutRows, - copyRows: copyRows, - pasteRows: pasteRows, - getPos: getPos, - setStartCell: setStartCell, - setEndCell: setEndCell, - moveRelIdx: moveRelIdx, - refresh: buildGrid - }); - }; -}); - -// Included from: js/tinymce/plugins/table/classes/Quirks.js - -/** - * Quirks.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class includes fixes for various browser quirks. - * - * @class tinymce.tableplugin.Quirks - * @private - */ -define("tinymce/tableplugin/Quirks", [ - "tinymce/util/VK", - "tinymce/Env", - "tinymce/util/Tools" -], function(VK, Env, Tools) { - var each = Tools.each; - - function getSpanVal(td, name) { - return parseInt(td.getAttribute(name) || 1, 10); - } - - return function(editor) { - /** - * Fixed caret movement around tables on WebKit. - */ - function moveWebKitSelection() { - function eventHandler(e) { - var key = e.keyCode; - - function handle(upBool, sourceNode) { - var siblingDirection = upBool ? 'previousSibling' : 'nextSibling'; - var currentRow = editor.dom.getParent(sourceNode, 'tr'); - var siblingRow = currentRow[siblingDirection]; - - if (siblingRow) { - moveCursorToRow(editor, sourceNode, siblingRow, upBool); - e.preventDefault(); - return true; - } else { - var tableNode = editor.dom.getParent(currentRow, 'table'); - var middleNode = currentRow.parentNode; - var parentNodeName = middleNode.nodeName.toLowerCase(); - if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) { - var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody'); - if (targetParent !== null) { - return moveToRowInTarget(upBool, targetParent, sourceNode); - } - } - return escapeTable(upBool, currentRow, siblingDirection, tableNode); - } - } - - function getTargetParent(upBool, topNode, secondNode, nodeName) { - var tbodies = editor.dom.select('>' + nodeName, topNode); - var position = tbodies.indexOf(secondNode); - if (upBool && position === 0 || !upBool && position === tbodies.length - 1) { - return getFirstHeadOrFoot(upBool, topNode); - } else if (position === -1) { - var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1; - return tbodies[topOrBottom]; - } else { - return tbodies[position + (upBool ? -1 : 1)]; - } - } - - function getFirstHeadOrFoot(upBool, parent) { - var tagName = upBool ? 'thead' : 'tfoot'; - var headOrFoot = editor.dom.select('>' + tagName, parent); - return headOrFoot.length !== 0 ? headOrFoot[0] : null; - } - - function moveToRowInTarget(upBool, targetParent, sourceNode) { - var targetRow = getChildForDirection(targetParent, upBool); - - if (targetRow) { - moveCursorToRow(editor, sourceNode, targetRow, upBool); - } - - e.preventDefault(); - return true; - } - - function escapeTable(upBool, currentRow, siblingDirection, table) { - var tableSibling = table[siblingDirection]; - - if (tableSibling) { - moveCursorToStartOfElement(tableSibling); - return true; - } else { - var parentCell = editor.dom.getParent(table, 'td,th'); - if (parentCell) { - return handle(upBool, parentCell, e); - } else { - var backUpSibling = getChildForDirection(currentRow, !upBool); - moveCursorToStartOfElement(backUpSibling); - e.preventDefault(); - return false; - } - } - } - - function getChildForDirection(parent, up) { - var child = parent && parent[up ? 'lastChild' : 'firstChild']; - // BR is not a valid table child to return in this case we return the table cell - return child && child.nodeName === 'BR' ? editor.dom.getParent(child, 'td,th') : child; - } - - function moveCursorToStartOfElement(n) { - editor.selection.setCursorLocation(n, 0); - } - - function isVerticalMovement() { - return key == VK.UP || key == VK.DOWN; - } - - function isInTable(editor) { - var node = editor.selection.getNode(); - var currentRow = editor.dom.getParent(node, 'tr'); - return currentRow !== null; - } - - function columnIndex(column) { - var colIndex = 0; - var c = column; - while (c.previousSibling) { - c = c.previousSibling; - colIndex = colIndex + getSpanVal(c, "colspan"); - } - return colIndex; - } - - function findColumn(rowElement, columnIndex) { - var c = 0, r = 0; - - each(rowElement.children, function(cell, i) { - c = c + getSpanVal(cell, "colspan"); - r = i; - if (c > columnIndex) { - return false; - } - }); - return r; - } - - function moveCursorToRow(ed, node, row, upBool) { - var srcColumnIndex = columnIndex(editor.dom.getParent(node, 'td,th')); - var tgtColumnIndex = findColumn(row, srcColumnIndex); - var tgtNode = row.childNodes[tgtColumnIndex]; - var rowCellTarget = getChildForDirection(tgtNode, upBool); - moveCursorToStartOfElement(rowCellTarget || tgtNode); - } - - function shouldFixCaret(preBrowserNode) { - var newNode = editor.selection.getNode(); - var newParent = editor.dom.getParent(newNode, 'td,th'); - var oldParent = editor.dom.getParent(preBrowserNode, 'td,th'); - - return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent); - } - - function checkSameParentTable(nodeOne, NodeTwo) { - return editor.dom.getParent(nodeOne, 'TABLE') === editor.dom.getParent(NodeTwo, 'TABLE'); - } - - if (isVerticalMovement() && isInTable(editor)) { - var preBrowserNode = editor.selection.getNode(); - setTimeout(function() { - if (shouldFixCaret(preBrowserNode)) { - handle(!e.shiftKey && key === VK.UP, preBrowserNode, e); - } - }, 0); - } - } - - editor.on('KeyDown', function(e) { - eventHandler(e); - }); - } - - function fixBeforeTableCaretBug() { - // Checks if the selection/caret is at the start of the specified block element - function isAtStart(rng, par) { - var doc = par.ownerDocument, rng2 = doc.createRange(), elm; - - rng2.setStartBefore(par); - rng2.setEnd(rng.endContainer, rng.endOffset); - - elm = doc.createElement('body'); - elm.appendChild(rng2.cloneContents()); - - // Check for text characters of other elements that should be treated as content - return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length === 0; - } - - // Fixes an bug where it's impossible to place the caret before a table in Gecko - // this fix solves it by detecting when the caret is at the beginning of such a table - // and then manually moves the caret infront of the table - editor.on('KeyDown', function(e) { - var rng, table, dom = editor.dom; - - // On gecko it's not possible to place the caret before a table - if (e.keyCode == 37 || e.keyCode == 38) { - rng = editor.selection.getRng(); - table = dom.getParent(rng.startContainer, 'table'); - - if (table && editor.getBody().firstChild == table) { - if (isAtStart(rng, table)) { - rng = dom.createRng(); - - rng.setStartBefore(table); - rng.setEndBefore(table); - - editor.selection.setRng(rng); - - e.preventDefault(); - } - } - } - }); - } - - // Fixes an issue on Gecko where it's impossible to place the caret behind a table - // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled - function fixTableCaretPos() { - editor.on('KeyDown SetContent VisualAid', function() { - var last; - - // Skip empty text nodes from the end - for (last = editor.getBody().lastChild; last; last = last.previousSibling) { - if (last.nodeType == 3) { - if (last.nodeValue.length > 0) { - break; - } - } else if (last.nodeType == 1 && (last.tagName == 'BR' || !last.getAttribute('data-mce-bogus'))) { - break; - } - } - - if (last && last.nodeName == 'TABLE') { - if (editor.settings.forced_root_block) { - editor.dom.add( - editor.getBody(), - editor.settings.forced_root_block, - editor.settings.forced_root_block_attrs, - Env.ie && Env.ie < 11 ? ' ' : '
    ' - ); - } else { - editor.dom.add(editor.getBody(), 'br', {'data-mce-bogus': '1'}); - } - } - }); - - editor.on('PreProcess', function(o) { - var last = o.node.lastChild; - - if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 && - (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && - last.previousSibling && last.previousSibling.nodeName == "TABLE") { - editor.dom.remove(last); - } - }); - } - - // this nasty hack is here to work around some WebKit selection bugs. - function fixTableCellSelection() { - function tableCellSelected(ed, rng, n, currentCell) { - // The decision of when a table cell is selected is somewhat involved. The fact that this code is - // required is actually a pointer to the root cause of this bug. A cell is selected when the start - // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases) - // or the parent of the table (in the case of the selection containing the last cell of a table). - var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'); - var tableParent, allOfCellSelected, tableCellSelection; - - if (table) { - tableParent = table.parentNode; - } - - allOfCellSelected = rng.startContainer.nodeType == TEXT_NODE && - rng.startOffset === 0 && - rng.endOffset === 0 && - currentCell && - (n.nodeName == "TR" || n == tableParent); - - tableCellSelection = (n.nodeName == "TD" || n.nodeName == "TH") && !currentCell; - - return allOfCellSelected || tableCellSelection; - } - - function fixSelection() { - var rng = editor.selection.getRng(); - var n = editor.selection.getNode(); - var currentCell = editor.dom.getParent(rng.startContainer, 'TD,TH'); - - if (!tableCellSelected(editor, rng, n, currentCell)) { - return; - } - - if (!currentCell) { - currentCell = n; - } - - // Get the very last node inside the table cell - var end = currentCell.lastChild; - while (end.lastChild) { - end = end.lastChild; - } - - // Select the entire table cell. Nothing outside of the table cell should be selected. - if (end.nodeType == 3) { - rng.setEnd(end, end.data.length); - editor.selection.setRng(rng); - } - } - - editor.on('KeyDown', function() { - fixSelection(); - }); - - editor.on('MouseDown', function(e) { - if (e.button != 2) { - fixSelection(); - } - }); - } - - /** - * Delete table if all cells are selected. - */ - function deleteTable() { - editor.on('keydown', function(e) { - if ((e.keyCode == VK.DELETE || e.keyCode == VK.BACKSPACE) && !e.isDefaultPrevented()) { - var table = editor.dom.getParent(editor.selection.getStart(), 'table'); - - if (table) { - var cells = editor.dom.select('td,th', table), i = cells.length; - while (i--) { - if (!editor.dom.hasClass(cells[i], 'mce-item-selected')) { - return; - } - } - - e.preventDefault(); - editor.execCommand('mceTableDelete'); - } - } - }); - } - - deleteTable(); - - if (Env.webkit) { - moveWebKitSelection(); - fixTableCellSelection(); - } - - if (Env.gecko) { - fixBeforeTableCaretBug(); - fixTableCaretPos(); - } - - if (Env.ie > 10) { - fixBeforeTableCaretBug(); - fixTableCaretPos(); - } - }; -}); - -// Included from: js/tinymce/plugins/table/classes/CellSelection.js - -/** - * CellSelection.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles table cell selection by faking it using a css class that gets applied - * to cells when dragging the mouse from one cell to another. - * - * @class tinymce.tableplugin.CellSelection - * @private - */ -define("tinymce/tableplugin/CellSelection", [ - "tinymce/tableplugin/TableGrid", - "tinymce/dom/TreeWalker", - "tinymce/util/Tools" -], function(TableGrid, TreeWalker, Tools) { - return function(editor) { - var dom = editor.dom, tableGrid, startCell, startTable, hasCellSelection = true, resizing; - - function clear(force) { - // Restore selection possibilities - editor.getBody().style.webkitUserSelect = ''; - - if (force || hasCellSelection) { - editor.dom.removeClass( - editor.dom.select('td.mce-item-selected,th.mce-item-selected'), - 'mce-item-selected' - ); - - hasCellSelection = false; - } - } - - function cellSelectionHandler(e) { - var sel, table, target = e.target; - - if (resizing) { - return; - } - - if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { - table = dom.getParent(target, 'table'); - if (table == startTable) { - if (!tableGrid) { - tableGrid = new TableGrid(editor, table); - tableGrid.setStartCell(startCell); - - editor.getBody().style.webkitUserSelect = 'none'; - } - - tableGrid.setEndCell(target); - hasCellSelection = true; - } - - // Remove current selection - sel = editor.selection.getSel(); - - try { - if (sel.removeAllRanges) { - sel.removeAllRanges(); - } else { - sel.empty(); - } - } catch (ex) { - // IE9 might throw errors here - } - - e.preventDefault(); - } - } - - // Add cell selection logic - editor.on('MouseDown', function(e) { - if (e.button != 2 && !resizing) { - clear(); - - startCell = dom.getParent(e.target, 'td,th'); - startTable = dom.getParent(startCell, 'table'); - } - }); - - editor.on('mouseover', cellSelectionHandler); - - editor.on('remove', function() { - dom.unbind(editor.getDoc(), 'mouseover', cellSelectionHandler); - }); - - editor.on('MouseUp', function() { - var rng, sel = editor.selection, selectedCells, walker, node, lastNode; - - function setPoint(node, start) { - var walker = new TreeWalker(node, node); - - do { - // Text node - if (node.nodeType == 3 && Tools.trim(node.nodeValue).length !== 0) { - if (start) { - rng.setStart(node, 0); - } else { - rng.setEnd(node, node.nodeValue.length); - } - - return; - } - - // BR element - if (node.nodeName == 'BR') { - if (start) { - rng.setStartBefore(node); - } else { - rng.setEndBefore(node); - } - - return; - } - } while ((node = (start ? walker.next() : walker.prev()))); - } - - // Move selection to startCell - if (startCell) { - if (tableGrid) { - editor.getBody().style.webkitUserSelect = ''; - } - - // Try to expand text selection as much as we can only Gecko supports cell selection - selectedCells = dom.select('td.mce-item-selected,th.mce-item-selected'); - if (selectedCells.length > 0) { - rng = dom.createRng(); - node = selectedCells[0]; - rng.setStartBefore(node); - rng.setEndAfter(node); - - setPoint(node, 1); - walker = new TreeWalker(node, dom.getParent(selectedCells[0], 'table')); - - do { - if (node.nodeName == 'TD' || node.nodeName == 'TH') { - if (!dom.hasClass(node, 'mce-item-selected')) { - break; - } - - lastNode = node; - } - } while ((node = walker.next())); - - setPoint(lastNode); - - sel.setRng(rng); - } - - editor.nodeChanged(); - startCell = tableGrid = startTable = null; - } - }); - - editor.on('KeyUp Drop SetContent', function(e) { - clear(e.type == 'setcontent'); - startCell = tableGrid = startTable = null; - resizing = false; - }); - - editor.on('ObjectResizeStart ObjectResized', function(e) { - resizing = e.type != 'objectresized'; - }); - - return { - clear: clear - }; - }; -}); - -// Included from: js/tinymce/plugins/table/classes/Dialogs.js - -/** - * Dialogs.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * ... - * - * @class tinymce.tableplugin.Dialogs - * @private - */ -define("tinymce/tableplugin/Dialogs", [ - "tinymce/util/Tools", - "tinymce/Env" -], function(Tools, Env) { - var each = Tools.each; - - return function(editor) { - var self = this; - - function createColorPickAction() { - var colorPickerCallback = editor.settings.color_picker_callback; - - if (colorPickerCallback) { - return function() { - var self = this; - - colorPickerCallback.call( - editor, - function(value) { - self.value(value).fire('change'); - }, - self.value() - ); - }; - } - } - - function createStyleForm(dom) { - return { - title: 'Advanced', - type: 'form', - defaults: { - onchange: function() { - updateStyle(dom, this.parents().reverse()[0], this.name() == "style"); - } - }, - items: [ - { - label: 'Style', - name: 'style', - type: 'textbox' - }, - - { - type: 'form', - padding: 0, - formItemDefaults: { - layout: 'grid', - alignH: ['start', 'right'] - }, - defaults: { - size: 7 - }, - items: [ - { - label: 'Border color', - type: 'colorbox', - name: 'borderColor', - onaction: createColorPickAction() - }, - - { - label: 'Background color', - type: 'colorbox', - name: 'backgroundColor', - onaction: createColorPickAction() - } - ] - } - ] - }; - } - - function removePxSuffix(size) { - return size ? size.replace(/px$/, '') : ""; - } - - function addSizeSuffix(size) { - if (/^[0-9]+$/.test(size)) { - size += "px"; - } - - return size; - } - - function unApplyAlign(elm) { - each('left center right'.split(' '), function(name) { - editor.formatter.remove('align' + name, {}, elm); - }); - } - - function unApplyVAlign(elm) { - each('top middle bottom'.split(' '), function(name) { - editor.formatter.remove('valign' + name, {}, elm); - }); - } - - function buildListItems(inputList, itemCallback, startItems) { - function appendItems(values, output) { - output = output || []; - - Tools.each(values, function(item) { - var menuItem = {text: item.text || item.title}; - - if (item.menu) { - menuItem.menu = appendItems(item.menu); - } else { - menuItem.value = item.value; - - if (itemCallback) { - itemCallback(menuItem); - } - } - - output.push(menuItem); - }); - - return output; - } - - return appendItems(inputList, startItems || []); - } - - function updateStyle(dom, win, isStyleCtrl) { - var data = win.toJSON(); - var css = dom.parseStyle(data.style); - - if (isStyleCtrl) { - win.find('#borderColor').value(css["border-color"] || '')[0].fire('change'); - win.find('#backgroundColor').value(css["background-color"] || '')[0].fire('change'); - } else { - css["border-color"] = data.borderColor; - css["background-color"] = data.backgroundColor; - } - - win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css)))); - } - - function appendStylesToData(dom, data, elm) { - var css = dom.parseStyle(dom.getAttrib(elm, 'style')); - - if (css["border-color"]) { - data.borderColor = css["border-color"]; - } - - if (css["background-color"]) { - data.backgroundColor = css["background-color"]; - } - - data.style = dom.serializeStyle(css); - } - - self.tableProps = function() { - self.table(true); - }; - - self.table = function(isProps) { - var dom = editor.dom, tableElm, colsCtrl, rowsCtrl, classListCtrl, data = {}, generalTableForm; - - function onSubmitTableForm() { - var captionElm; - - updateStyle(dom, this); - data = Tools.extend(data, this.toJSON()); - - Tools.each('backgroundColor borderColor'.split(' '), function(name) { - delete data[name]; - }); - - if (data["class"] === false) { - delete data["class"]; - } - - editor.undoManager.transact(function() { - if (!tableElm) { - tableElm = editor.plugins.table.insertTable(data.cols || 1, data.rows || 1); - } - - editor.dom.setAttribs(tableElm, { - cellspacing: data.cellspacing, - cellpadding: data.cellpadding, - border: data.border, - style: data.style, - 'class': data['class'] - }); - - if (dom.getAttrib(tableElm, 'width')) { - dom.setAttrib(tableElm, 'width', removePxSuffix(data.width)); - } else { - dom.setStyle(tableElm, 'width', addSizeSuffix(data.width)); - } - - dom.setStyle(tableElm, 'height', addSizeSuffix(data.height)); - - // Toggle caption on/off - captionElm = dom.select('caption', tableElm)[0]; - - if (captionElm && !data.caption) { - dom.remove(captionElm); - } - - if (!captionElm && data.caption) { - captionElm = dom.create('caption'); - captionElm.innerHTML = !Env.ie ? '
    ' : '\u00a0'; - tableElm.insertBefore(captionElm, tableElm.firstChild); - } - - unApplyAlign(tableElm); - if (data.align) { - editor.formatter.apply('align' + data.align, {}, tableElm); - } - - editor.focus(); - editor.addVisual(); - }); - } - - if (isProps === true) { - tableElm = dom.getParent(editor.selection.getStart(), 'table'); - - if (tableElm) { - data = { - width: removePxSuffix(dom.getStyle(tableElm, 'width') || dom.getAttrib(tableElm, 'width')), - height: removePxSuffix(dom.getStyle(tableElm, 'height') || dom.getAttrib(tableElm, 'height')), - cellspacing: tableElm ? dom.getAttrib(tableElm, 'cellspacing') : '', - cellpadding: tableElm ? dom.getAttrib(tableElm, 'cellpadding') : '', - border: tableElm ? dom.getAttrib(tableElm, 'border') : '', - caption: !!dom.select('caption', tableElm)[0], - 'class': dom.getAttrib(tableElm, 'class') - }; - - each('left center right'.split(' '), function(name) { - if (editor.formatter.matchNode(tableElm, 'align' + name)) { - data.align = name; - } - }); - } - } else { - colsCtrl = {label: 'Cols', name: 'cols'}; - rowsCtrl = {label: 'Rows', name: 'rows'}; - } - - if (editor.settings.table_class_list) { - if (data["class"]) { - data["class"] = data["class"].replace(/\s*mce\-item\-table\s*/g, ''); - } - - classListCtrl = { - name: 'class', - type: 'listbox', - label: 'Class', - values: buildListItems( - editor.settings.table_class_list, - function(item) { - if (item.value) { - item.textStyle = function() { - return editor.formatter.getCssText({block: 'table', classes: [item.value]}); - }; - } - } - ) - }; - } - - generalTableForm = { - type: 'form', - layout: 'flex', - direction: 'column', - labelGapCalc: 'children', - padding: 0, - items: [ - { - type: 'form', - labelGapCalc: false, - padding: 0, - layout: 'grid', - columns: 2, - defaults: { - type: 'textbox', - maxWidth: 50 - }, - items: [ - colsCtrl, - rowsCtrl, - {label: 'Width', name: 'width'}, - {label: 'Height', name: 'height'}, - {label: 'Cell spacing', name: 'cellspacing'}, - {label: 'Cell padding', name: 'cellpadding'}, - {label: 'Border', name: 'border'}, - {label: 'Caption', name: 'caption', type: 'checkbox'} - ] - }, - - { - label: 'Alignment', - name: 'align', - type: 'listbox', - text: 'None', - values: [ - {text: 'None', value: ''}, - {text: 'Left', value: 'left'}, - {text: 'Center', value: 'center'}, - {text: 'Right', value: 'right'} - ] - }, - - classListCtrl - ] - }; - - if (editor.settings.table_advtab !== false) { - appendStylesToData(dom, data, tableElm); - - editor.windowManager.open({ - title: "Table properties", - data: data, - bodyType: 'tabpanel', - body: [ - { - title: 'General', - type: 'form', - items: generalTableForm - }, - createStyleForm(dom) - ], - - onsubmit: onSubmitTableForm - }); - } else { - editor.windowManager.open({ - title: "Table properties", - data: data, - body: generalTableForm, - onsubmit: onSubmitTableForm - }); - } - }; - - self.merge = function(grid, cell) { - editor.windowManager.open({ - title: "Merge cells", - body: [ - {label: 'Cols', name: 'cols', type: 'textbox', value: '1', size: 10}, - {label: 'Rows', name: 'rows', type: 'textbox', value: '1', size: 10} - ], - onsubmit: function() { - var data = this.toJSON(); - - editor.undoManager.transact(function() { - grid.merge(cell, data.cols, data.rows); - }); - } - }); - }; - - self.cell = function() { - var dom = editor.dom, cellElm, data, classListCtrl, cells = []; - - function onSubmitCellForm() { - updateStyle(dom, this); - data = Tools.extend(data, this.toJSON()); - - editor.undoManager.transact(function() { - each(cells, function(cellElm) { - editor.dom.setAttribs(cellElm, { - scope: data.scope, - style: data.style, - 'class': data['class'] - }); - - editor.dom.setStyles(cellElm, { - width: addSizeSuffix(data.width), - height: addSizeSuffix(data.height) - }); - - // Switch cell type - if (data.type && cellElm.nodeName.toLowerCase() != data.type) { - cellElm = dom.rename(cellElm, data.type); - } - - // Apply/remove alignment - unApplyAlign(cellElm); - if (data.align) { - editor.formatter.apply('align' + data.align, {}, cellElm); - } - - // Apply/remove vertical alignment - unApplyVAlign(cellElm); - if (data.valign) { - editor.formatter.apply('valign' + data.valign, {}, cellElm); - } - }); - - editor.focus(); - }); - } - - // Get selected cells or the current cell - cells = editor.dom.select('td.mce-item-selected,th.mce-item-selected'); - cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th'); - if (!cells.length && cellElm) { - cells.push(cellElm); - } - - cellElm = cellElm || cells[0]; - - if (!cellElm) { - // If this element is null, return now to avoid crashing. - return; - } - - data = { - width: removePxSuffix(dom.getStyle(cellElm, 'width') || dom.getAttrib(cellElm, 'width')), - height: removePxSuffix(dom.getStyle(cellElm, 'height') || dom.getAttrib(cellElm, 'height')), - scope: dom.getAttrib(cellElm, 'scope'), - 'class': dom.getAttrib(cellElm, 'class') - }; - - data.type = cellElm.nodeName.toLowerCase(); - - each('left center right'.split(' '), function(name) { - if (editor.formatter.matchNode(cellElm, 'align' + name)) { - data.align = name; - } - }); - - each('top middle bottom'.split(' '), function(name) { - if (editor.formatter.matchNode(cellElm, 'valign' + name)) { - data.valign = name; - } - }); - - if (editor.settings.table_cell_class_list) { - classListCtrl = { - name: 'class', - type: 'listbox', - label: 'Class', - values: buildListItems( - editor.settings.table_cell_class_list, - function(item) { - if (item.value) { - item.textStyle = function() { - return editor.formatter.getCssText({block: 'td', classes: [item.value]}); - }; - } - } - ) - }; - } - - var generalCellForm = { - type: 'form', - layout: 'flex', - direction: 'column', - labelGapCalc: 'children', - padding: 0, - items: [ - { - type: 'form', - layout: 'grid', - columns: 2, - labelGapCalc: false, - padding: 0, - defaults: { - type: 'textbox', - maxWidth: 50 - }, - items: [ - {label: 'Width', name: 'width'}, - {label: 'Height', name: 'height'}, - { - label: 'Cell type', - name: 'type', - type: 'listbox', - text: 'None', - minWidth: 90, - maxWidth: null, - values: [ - {text: 'Cell', value: 'td'}, - {text: 'Header cell', value: 'th'} - ] - }, - { - label: 'Scope', - name: 'scope', - type: 'listbox', - text: 'None', - minWidth: 90, - maxWidth: null, - values: [ - {text: 'None', value: ''}, - {text: 'Row', value: 'row'}, - {text: 'Column', value: 'col'}, - {text: 'Row group', value: 'rowgroup'}, - {text: 'Column group', value: 'colgroup'} - ] - }, - { - label: 'H Align', - name: 'align', - type: 'listbox', - text: 'None', - minWidth: 90, - maxWidth: null, - values: [ - {text: 'None', value: ''}, - {text: 'Left', value: 'left'}, - {text: 'Center', value: 'center'}, - {text: 'Right', value: 'right'} - ] - }, - { - label: 'V Align', - name: 'valign', - type: 'listbox', - text: 'None', - minWidth: 90, - maxWidth: null, - values: [ - {text: 'None', value: ''}, - {text: 'Top', value: 'top'}, - {text: 'Middle', value: 'middle'}, - {text: 'Bottom', value: 'bottom'} - ] - } - ] - }, - - classListCtrl - ] - }; - - if (editor.settings.table_cell_advtab !== false) { - appendStylesToData(dom, data, cellElm); - - editor.windowManager.open({ - title: "Cell properties", - bodyType: 'tabpanel', - data: data, - body: [ - { - title: 'General', - type: 'form', - items: generalCellForm - }, - - createStyleForm(dom) - ], - - onsubmit: onSubmitCellForm - }); - } else { - editor.windowManager.open({ - title: "Cell properties", - data: data, - body: generalCellForm, - onsubmit: onSubmitCellForm - }); - } - }; - - self.row = function() { - var dom = editor.dom, tableElm, cellElm, rowElm, classListCtrl, data, rows = [], generalRowForm; - - function onSubmitRowForm() { - var tableElm, oldParentElm, parentElm; - - updateStyle(dom, this); - data = Tools.extend(data, this.toJSON()); - - editor.undoManager.transact(function() { - var toType = data.type; - - each(rows, function(rowElm) { - editor.dom.setAttribs(rowElm, { - scope: data.scope, - style: data.style, - 'class': data['class'] - }); - - editor.dom.setStyles(rowElm, { - height: addSizeSuffix(data.height) - }); - - if (toType != rowElm.parentNode.nodeName.toLowerCase()) { - tableElm = dom.getParent(rowElm, 'table'); - - oldParentElm = rowElm.parentNode; - parentElm = dom.select(toType, tableElm)[0]; - if (!parentElm) { - parentElm = dom.create(toType); - if (tableElm.firstChild) { - tableElm.insertBefore(parentElm, tableElm.firstChild); - } else { - tableElm.appendChild(parentElm); - } - } - - parentElm.appendChild(rowElm); - - if (!oldParentElm.hasChildNodes()) { - dom.remove(oldParentElm); - } - } - - // Apply/remove alignment - unApplyAlign(rowElm); - if (data.align) { - editor.formatter.apply('align' + data.align, {}, rowElm); - } - }); - - editor.focus(); - }); - } - - tableElm = editor.dom.getParent(editor.selection.getStart(), 'table'); - cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th'); - - each(tableElm.rows, function(row) { - each(row.cells, function(cell) { - if (dom.hasClass(cell, 'mce-item-selected') || cell == cellElm) { - rows.push(row); - return false; - } - }); - }); - - rowElm = rows[0]; - if (!rowElm) { - // If this element is null, return now to avoid crashing. - return; - } - - data = { - height: removePxSuffix(dom.getStyle(rowElm, 'height') || dom.getAttrib(rowElm, 'height')), - scope: dom.getAttrib(rowElm, 'scope'), - 'class': dom.getAttrib(rowElm, 'class') - }; - - data.type = rowElm.parentNode.nodeName.toLowerCase(); - - each('left center right'.split(' '), function(name) { - if (editor.formatter.matchNode(rowElm, 'align' + name)) { - data.align = name; - } - }); - - if (editor.settings.table_row_class_list) { - classListCtrl = { - name: 'class', - type: 'listbox', - label: 'Class', - values: buildListItems( - editor.settings.table_row_class_list, - function(item) { - if (item.value) { - item.textStyle = function() { - return editor.formatter.getCssText({block: 'tr', classes: [item.value]}); - }; - } - } - ) - }; - } - - generalRowForm = { - type: 'form', - columns: 2, - padding: 0, - defaults: { - type: 'textbox' - }, - items: [ - { - type: 'listbox', - name: 'type', - label: 'Row type', - text: 'None', - maxWidth: null, - values: [ - {text: 'Header', value: 'thead'}, - {text: 'Body', value: 'tbody'}, - {text: 'Footer', value: 'tfoot'} - ] - }, - { - type: 'listbox', - name: 'align', - label: 'Alignment', - text: 'None', - maxWidth: null, - values: [ - {text: 'None', value: ''}, - {text: 'Left', value: 'left'}, - {text: 'Center', value: 'center'}, - {text: 'Right', value: 'right'} - ] - }, - {label: 'Height', name: 'height'}, - classListCtrl - ] - }; - - if (editor.settings.table_row_advtab !== false) { - appendStylesToData(dom, data, rowElm); - - editor.windowManager.open({ - title: "Row properties", - data: data, - bodyType: 'tabpanel', - body: [ - { - title: 'General', - type: 'form', - items: generalRowForm - }, - createStyleForm(dom) - ], - - onsubmit: onSubmitRowForm - }); - } else { - editor.windowManager.open({ - title: "Row properties", - data: data, - body: generalRowForm, - onsubmit: onSubmitRowForm - }); - } - }; - }; -}); - -// Included from: js/tinymce/plugins/table/classes/Plugin.js - -/** - * Plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class contains all core logic for the table plugin. - * - * @class tinymce.tableplugin.Plugin - * @private - */ -define("tinymce/tableplugin/Plugin", [ - "tinymce/tableplugin/TableGrid", - "tinymce/tableplugin/Quirks", - "tinymce/tableplugin/CellSelection", - "tinymce/tableplugin/Dialogs", - "tinymce/util/Tools", - "tinymce/dom/TreeWalker", - "tinymce/Env", - "tinymce/PluginManager" -], function(TableGrid, Quirks, CellSelection, Dialogs, Tools, TreeWalker, Env, PluginManager) { - var each = Tools.each; - - function Plugin(editor) { - var clipboardRows, self = this, dialogs = new Dialogs(editor); - - function cmd(command) { - return function() { - editor.execCommand(command); - }; - } - - function insertTable(cols, rows) { - var y, x, html, tableElm; - - html = ''; - - for (y = 0; y < rows; y++) { - html += ''; - - for (x = 0; x < cols; x++) { - html += ''; - } - - html += ''; - } - - html += '
    ' + (Env.ie ? " " : '
    ') + '
    '; - - editor.undoManager.transact(function() { - editor.insertContent(html); - - tableElm = editor.dom.get('__mce'); - editor.dom.setAttrib(tableElm, 'id', null); - - editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {}); - editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {}); - }); - - return tableElm; - } - - function handleDisabledState(ctrl, selector) { - function bindStateListener() { - ctrl.disabled(!editor.dom.getParent(editor.selection.getStart(), selector)); - - editor.selection.selectorChanged(selector, function(state) { - ctrl.disabled(!state); - }); - } - - if (editor.initialized) { - bindStateListener(); - } else { - editor.on('init', bindStateListener); - } - } - - function postRender() { - /*jshint validthis:true*/ - handleDisabledState(this, 'table'); - } - - function postRenderCell() { - /*jshint validthis:true*/ - handleDisabledState(this, 'td,th'); - } - - function generateTableGrid() { - var html = ''; - - html = ''; - - for (var y = 0; y < 10; y++) { - html += ''; - - for (var x = 0; x < 10; x++) { - html += ''; - } - - html += ''; - } - - html += '
    '; - - html += ''; - - return html; - } - - function selectGrid(tx, ty, control) { - var table = control.getEl().getElementsByTagName('table')[0]; - var x, y, focusCell, cell, active; - var rtl = control.isRtl() || control.parent().rel == 'tl-tr'; - - table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1); - - if (rtl) { - tx = 9 - tx; - } - - for (y = 0; y < 10; y++) { - for (x = 0; x < 10; x++) { - cell = table.rows[y].childNodes[x].firstChild; - active = (rtl ? x >= tx : x <= tx) && y <= ty; - - editor.dom.toggleClass(cell, 'mce-active', active); - - if (active) { - focusCell = cell; - } - } - } - - return focusCell.parentNode; - } - - if (editor.settings.table_grid === false) { - editor.addMenuItem('inserttable', { - text: 'Insert table', - icon: 'table', - context: 'table', - onclick: dialogs.table - }); - } else { - editor.addMenuItem('inserttable', { - text: 'Insert table', - icon: 'table', - context: 'table', - ariaHideMenu: true, - onclick: function(e) { - if (e.aria) { - this.parent().hideAll(); - e.stopImmediatePropagation(); - dialogs.table(); - } - }, - onshow: function() { - selectGrid(0, 0, this.menu.items()[0]); - }, - onhide: function() { - var elements = this.menu.items()[0].getEl().getElementsByTagName('a'); - editor.dom.removeClass(elements, 'mce-active'); - editor.dom.addClass(elements[0], 'mce-active'); - }, - menu: [ - { - type: 'container', - html: generateTableGrid(), - - onPostRender: function() { - this.lastX = this.lastY = 0; - }, - - onmousemove: function(e) { - var target = e.target, x, y; - - if (target.tagName.toUpperCase() == 'A') { - x = parseInt(target.getAttribute('data-mce-x'), 10); - y = parseInt(target.getAttribute('data-mce-y'), 10); - - if (this.isRtl() || this.parent().rel == 'tl-tr') { - x = 9 - x; - } - - if (x !== this.lastX || y !== this.lastY) { - selectGrid(x, y, e.control); - - this.lastX = x; - this.lastY = y; - } - } - }, - - onclick: function(e) { - var self = this; - - if (e.target.tagName.toUpperCase() == 'A') { - e.preventDefault(); - e.stopPropagation(); - self.parent().cancel(); - - editor.undoManager.transact(function() { - insertTable(self.lastX + 1, self.lastY + 1); - }); - - editor.addVisual(); - } - } - } - ] - }); - } - - editor.addMenuItem('tableprops', { - text: 'Table properties', - context: 'table', - onPostRender: postRender, - onclick: dialogs.tableProps - }); - - editor.addMenuItem('deletetable', { - text: 'Delete table', - context: 'table', - onPostRender: postRender, - cmd: 'mceTableDelete' - }); - - editor.addMenuItem('cell', { - separator: 'before', - text: 'Cell', - context: 'table', - menu: [ - {text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell}, - {text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderCell}, - {text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell} - ] - }); - - editor.addMenuItem('row', { - text: 'Row', - context: 'table', - menu: [ - {text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell}, - {text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell}, - {text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell}, - {text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell}, - {text: '-'}, - {text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell}, - {text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell}, - {text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell}, - {text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell} - ] - }); - - editor.addMenuItem('column', { - text: 'Column', - context: 'table', - menu: [ - {text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell}, - {text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell}, - {text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell} - ] - }); - - var menuItems = []; - each("inserttable tableprops deletetable | cell row column".split(' '), function(name) { - if (name == '|') { - menuItems.push({text: '-'}); - } else { - menuItems.push(editor.menuItems[name]); - } - }); - - editor.addButton("table", { - type: "menubutton", - title: "Table", - menu: menuItems - }); - - // Select whole table is a table border is clicked - if (!Env.isIE) { - editor.on('click', function(e) { - e = e.target; - - if (e.nodeName === 'TABLE') { - editor.selection.select(e); - editor.nodeChanged(); - } - }); - } - - self.quirks = new Quirks(editor); - - editor.on('Init', function() { - self.cellSelection = new CellSelection(editor); - }); - - // Register action commands - each({ - mceTableSplitCells: function(grid) { - grid.split(); - }, - - mceTableMergeCells: function(grid) { - var cell; - - cell = editor.dom.getParent(editor.selection.getStart(), 'th,td'); - - if (!editor.dom.select('td.mce-item-selected,th.mce-item-selected').length) { - dialogs.merge(grid, cell); - } else { - grid.merge(); - } - }, - - mceTableInsertRowBefore: function(grid) { - grid.insertRow(true); - }, - - mceTableInsertRowAfter: function(grid) { - grid.insertRow(); - }, - - mceTableInsertColBefore: function(grid) { - grid.insertCol(true); - }, - - mceTableInsertColAfter: function(grid) { - grid.insertCol(); - }, - - mceTableDeleteCol: function(grid) { - grid.deleteCols(); - }, - - mceTableDeleteRow: function(grid) { - grid.deleteRows(); - }, - - mceTableCutRow: function(grid) { - clipboardRows = grid.cutRows(); - }, - - mceTableCopyRow: function(grid) { - clipboardRows = grid.copyRows(); - }, - - mceTablePasteRowBefore: function(grid) { - grid.pasteRows(clipboardRows, true); - }, - - mceTablePasteRowAfter: function(grid) { - grid.pasteRows(clipboardRows); - }, - - mceTableDelete: function(grid) { - grid.deleteTable(); - } - }, function(func, name) { - editor.addCommand(name, function() { - var grid = new TableGrid(editor); - - if (grid) { - func(grid); - editor.execCommand('mceRepaint'); - self.cellSelection.clear(); - } - }); - }); - - // Register dialog commands - each({ - mceInsertTable: dialogs.table, - mceTableProps: function() { - dialogs.table(true); - }, - mceTableRowProps: dialogs.row, - mceTableCellProps: dialogs.cell - }, function(func, name) { - editor.addCommand(name, function(ui, val) { - func(val); - }); - }); - - // Enable tab key cell navigation - if (editor.settings.table_tab_navigation !== false) { - editor.on('keydown', function(e) { - var cellElm, grid, delta; - - if (e.keyCode == 9) { - cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td'); - - if (cellElm) { - e.preventDefault(); - - grid = new TableGrid(editor); - delta = e.shiftKey ? -1 : 1; - - editor.undoManager.transact(function() { - if (!grid.moveRelIdx(cellElm, delta) && delta > 0) { - grid.insertRow(); - grid.refresh(); - grid.moveRelIdx(cellElm, delta); - } - }); - } - } - }); - } - - self.insertTable = insertTable; - } - - PluginManager.add('table', Plugin); -}); -})(this); \ No newline at end of file