PluginProbe
FareHarbor for WordPress / trunk
FareHarbor for WordPress vtrunk
3.6.15 3.6.14 trunk 1.1 1.2 2.0 2.1 3.0 3.0.1 3.1 3.2 3.3 3.3.1 3.4 3.5 3.6 3.6.1 3.6.10 3.6.11 3.6.12 3.6.13 3.6.2 3.6.3 3.6.4 3.6.5 All 29 releases
fareharbor / fareharbor.php

fareharbor.php in FareHarbor for WordPress trunk, at fareharbor.php

1,262 lines 37.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: FareHarbor for WordPress
4 Plugin URI: https://fareharbor.com/help/setup/wordpress-plugin/
5 Description: Easily add FareHarbor reservation calendars and buttons to your site
6 Version: 3.6.15
7 Author: FareHarbor
8 Author URI: https://fareharbor.com
9 */
10
11 // Make sure this file isn't loaded on its own
12 // -----------------------------------------------
13
14 defined( 'ABSPATH' ) or die( 'Lost? <a href="/">Return home.</a>' );
15
16 // Add init hook
17 // -----------------------------------------------
18
19 add_action( 'init', array( 'fareharbor', 'init' ) );
20
21
22 // Global functions for potential backwards compatability
23 // -----------------------------------------------
24
25 function fh_shortcode( $attrs ) {
26 return fareharbor::fareharbor_shortcode( $attrs );
27 }
28
29 function lightframe_shortcode( $attrs, $content = '' ) {
30 return fareharbor::lightframe_shortcode( $attrs, $content );
31 }
32
33 function partners_shortcode( $attrs ) {
34 return fareharbor::partners_shortcode( $attrs );
35 }
36
37
38 // The important stuff
39 // -----------------------------------------------
40
41 final class fareharbor {
42
43 // This class is just a namespace for static methods & variables
44 private function __construct() {}
45
46 public static $version = '3.6.15';
47
48 // Update the saved version number in the wp_options table
49 // ===============================================
50
51 public static function init() {
52
53 // In a multisite environment some themes may wish to disable
54 // this plugin.
55 if ( apply_filters( 'fareharbor/disabled', false ) )
56 return;
57
58 // Register the shortcodes
59 // -----------------------------------------------
60
61 add_shortcode( 'fareharbor', array( 'fareharbor', 'fareharbor_shortcode' ) );
62 add_shortcode( 'lightframe', array( 'fareharbor', 'lightframe_shortcode' ) );
63 add_shortcode( 'partners', array( 'fareharbor', 'partners_shortcode' ) );
64 add_shortcode( 'itemgrid', array( 'fareharbor', 'itemgrid_shortcode' ) );
65 // add_shortcode( 'bookembed', array( 'fareharbor', 'bookembed_shortcode' ) );
66
67 // Register our Settings Page & Settings
68 // -----------------------------------------------
69
70 add_action( 'admin_menu', array( 'fareharbor', 'register_options_page' ) );
71 add_action( 'admin_init', array( 'fareharbor', 'register_settings' ) );
72
73
74 // Include the script in the footer
75 // -----------------------------------------------
76
77 add_action( 'wp_footer', array( 'fareharbor', 'lightframe_api_footer' ) );
78
79
80 // Maybe include fh-kit styles in header
81 // -----------------------------------------------
82 // Depends on option being set in the admin dashboard
83 add_action( 'wp_enqueue_scripts', array( 'fareharbor', 'maybe_enqueue_fh_kit_styles' ) );
84
85 $saved_version = get_option( 'fareharbor_version' );
86 if ( $saved_version !== self::$version )
87 self::upgrade( $saved_version );
88
89 }
90
91 // Upgrade
92 // ===============================================
93
94 private static function upgrade( $from ) {
95
96 if ( !$from ) {
97 // New install or upgrade from version before 3.0, before we saved
98 // version numbers in the database. No choice but to treat those
99 // as a new install.
100 // No settings should be saved but let's be careful not to override
101 // just in case
102 $saved_settings = get_option( 'fareharbor_settings', array() );
103 if ( !is_array( $saved_settings ) )
104 // Even less likely, but again just in case
105 $saved_settings = array();
106
107 $defaults = array(
108 'fh_responsive_calendars' => 'on',
109 'fh_default_fallback' => 'simple',
110 'fh_auto_lightframe_enabled' => 'on',
111 );
112 update_option( 'fareharbor_settings', $saved_settings + $defaults );
113
114 // All new installs from v3.4 on should use v2 of the stylesheet
115 update_option( 'fareharbor_kit_version', 'v2' );
116 }
117 elseif ( version_compare( $from, '3.4', '<' ) ) {
118 // Can't swap version of old sites that might already be using v1
119 // of the stylesheet!
120 update_option( 'fareharbor_kit_version', 'v1' );
121 }
122
123 update_option( 'fareharbor_version', self::$version, true );
124
125 }
126
127 // Script source
128 // ===============================================
129
130 public static function lightframe_api_footer() {
131
132 $src = 'https://' . self::url() . '/embeds/api/v1/';
133
134 if ( self::get_option( 'fh_auto_lightframe_enabled' ) )
135 $src .= '?autolightframe=yes';
136
137 echo "<!-- FareHarbor plugin activated --><script src=\"$src\"></script>";
138
139 }
140
141
142 // FH-Kit Styles
143 // ===============================================
144
145 public static function maybe_enqueue_fh_kit_styles() {
146
147 if ( self::is_lite() )
148 return;
149
150 if( !self::get_option( 'fh_buttons_active' ) )
151 return;
152
153 $query_string = self::get_option( 'fh_buttons_query' );
154
155 $query_string = strip_tags( $query_string );
156 $query_string = trim( $query_string );
157 $query_string = ltrim( $query_string, '?' );
158
159 // Default to v1 for sites that don't have this in the options table in
160 // case upgrade procedure didn't run. All new installs should get
161 // `fareharbor_kit_version` saved as v2 upon install.
162 $version = get_option( 'fareharbor_kit_version', 'v1' );
163 // Make the version filterable
164 $version = apply_filters( 'fareharbor/fh_kit_version', $version );
165 // Validate. If we got something funky (as opposed to nothing at all) then
166 // we'll default to version 2.
167 if ( !in_array( $version, array( 'v1', 'v2', ), true ) )
168 $version = 'v2';
169
170 $fh_kit_src = "https://fh-kit.com/buttons/$version/";
171
172 if ( $query_string )
173 $fh_kit_src .= '?' . $query_string;
174
175 $fh_kit_src = apply_filters( 'fareharbor/buttons_style_src', $fh_kit_src );
176
177 // the 4th parameter is $version. Leaving it out causes WP to
178 // add ver={current-wp-version} to the url. passing null stops this.
179 wp_enqueue_style( 'fh-buttons', $fh_kit_src, array(), null );
180
181 }
182
183
184 // Default Arguments
185 // ===============================================
186
187 private static $shared_defaults,
188 $lightframe_defaults,
189 $calendar_defaults,
190 $partners_defaults,
191 $itemgrid_defaults,
192 $bookembed_defaults;
193
194 public static function _clear_options() {
195 // This method exists for testing only.
196 self::$options = null;
197 self::$shared_defaults = null;
198 self::$lightframe_defaults = null;
199 self::$calendar_defaults = null;
200 self::$partners_defaults = null;
201 self::$itemgrid_defaults = null;
202 self::$bookembed_defaults = null;
203 }
204
205 private static function init_defaults() {
206
207 if ( isset( self::$shared_defaults ) )
208 return;
209
210 self::$shared_defaults = array(
211
212 'shortname' => self::get_option( 'fh_default_shortname', '' ),
213 'items' => '',
214 'asn' => self::get_option( 'fh_default_asn', '' ),
215 'asn_ref' => self::get_option( 'fh_default_asn_ref', '' ),
216 'ref' => self::get_option( 'fh_default_ref', '' ),
217 'lightframe' => self::get_option( 'fh_default_lightframe', 'yes' ),
218 'full_items' => self::get_option( 'fh_default_full_items', 'no' ),
219 'sheet' => self::get_option( 'fh_default_sheet', '' ),
220 'sheet_uuid' => '',
221 'schedule' => self::get_option( 'fh_default_schedule', '' ),
222 'schedule_uuid' => '',
223 'fallback' => 'simple',
224 'flow' => '',
225 'language' => self::get_option( 'fh_default_language', '' ),
226 'branding' => '',
227 'bookable_only' => '',
228 'package' => '',
229
230 );
231
232 self::$bookembed_defaults =
233 self::$lightframe_defaults = array(
234
235 // 'class' => '',
236 // 'style' => '',
237 // 'id' => '',
238 'view' => 'items',
239 'view_item' => '',
240 'view_availability' => '',
241
242 );
243 self::$bookembed_defaults[ 'fallback' ] = '';
244
245 self::$calendar_defaults = array();
246
247 if ( self::get_option( 'fh_responsive_calendars', false ) )
248 self::$calendar_defaults[ 'type' ] = 'calendar';
249 else
250 self::$calendar_defaults[ 'type' ] = 'calendar-small';
251
252 self::$partners_defaults = array(
253
254 'include' => '',
255
256 );
257
258 self::$itemgrid_defaults = array();
259
260 }
261
262
263 // [lightframe][/lightframe] shortcode
264 // ===============================================
265
266 public static function lightframe_shortcode( $attrs, $content = '' ) {
267
268 $link_attrs = self::lightframe_link_attrs( $attrs );
269
270 if ( $error = self::maybe_handle_shortcode_error( $link_attrs ) )
271 return $error;
272
273 if ( !empty( $attrs[ 'class' ] ) )
274 $link_attrs[ 'class' ] = $attrs[ 'class' ];
275 elseif ( $default_class = self::get_option( 'fh_default_class' ) )
276 $link_attrs[ 'class' ] = $default_class;
277
278 if ( !empty( $attrs[ 'id' ] ) )
279 $link_attrs[ 'id' ] = $attrs[ 'id' ];
280
281 if ( !empty( $attrs[ 'style' ] ) )
282 $link_attrs[ 'style' ] = $attrs[ 'style' ];
283
284 $link_attrs = array_map( 'strip_tags', $link_attrs );
285
286 $link_attrs_string = '';
287 foreach ( $link_attrs as $name => $value ) {
288 $value = esc_attr($value);
289 $link_attrs_string .= " $name=\"$value\"";
290 }
291
292 if ( trim( $content ) )
293 $content = do_shortcode( $content );
294
295 return "<a$link_attrs_string>$content</a>";
296
297 }
298
299 public static function lightframe_link_attrs( $args ) {
300
301 if ( !isset( self::$lightframe_defaults ) )
302 self::init_defaults();
303
304 $args = self::process_args( $args, self::$lightframe_defaults, 'lightframe' );
305
306 if ( isset( $args[ 'error' ] ) )
307 return $args;
308
309 if ( $args[ 'view_availability' ] && !$args[ 'view_item' ] )
310 return array( 'error' => 'view_availability but no view_item' );
311
312 $fallback_url = self::lightframe_fallback_url( $args );
313
314 $api_options = self::lightframe_api_options( $args );
315 $json = strtr( json_encode( $api_options ), '"', "'" );
316
317 return array(
318 'href' => $fallback_url,
319 'target' => '_blank',
320 'onclick' => "FH.open($json); return false;",
321 );
322
323 }
324
325 private static function lightframe_fallback_url( $args, $is_bookembed = false ) {
326
327 // The bookembed url structure is very similar to the simple fallback url structure
328 $is_simple_fallback = ( $args[ 'fallback' ] === '' ) || ( $args[ 'fallback' ] === 'simple' ) || $is_bookembed;
329
330 $url = 'https://' . self::url() . '/';
331
332 if ( $is_bookembed )
333 $url .= 'embeds/script/book/';
334 elseif ( $is_simple_fallback )
335 $url .= 'embeds/book/';
336
337 $url .= $args[ 'shortname' ] . '/';
338
339 if ( !$is_simple_fallback
340 || $args[ 'view' ] === 'all-availability'
341 || $args[ 'view_item' ]
342 ) {
343 $url .= 'items/';
344 }
345
346 if ( $args[ 'view_item' ] ) {
347
348 $url .= $args[ 'view_item' ] . '/';
349
350 if ( $args[ 'view_availability' ] )
351 $url .= 'availability/' . $args[ 'view_availability' ] . '/book/';
352 elseif ( $args[ 'full_items' ] === 'no' )
353 $url .= 'calendar/';
354
355 } elseif ( $args[ 'view' ] === 'all-availability' ) {
356
357 $url .= 'calendar/';
358
359 }
360
361 $query = self::get_shared_args_array( $args, 'dash', $is_simple_fallback, false, true );
362
363 if ( $is_simple_fallback && $args[ 'items' ] )
364 $query[ 'selected-items' ] = $args[ 'items' ];
365
366 if ( $query )
367 $url .= '?' . http_build_query( $query );
368
369 return $url;
370
371 }
372
373 private static function lightframe_api_options( $args ) {
374
375 $lo = array( 'shortname' => $args[ 'shortname' ] );
376
377 // Filter the visible items
378 if ( $args[ 'items' ] )
379 // putting it in an array so it gets brackets in json_encode
380 $lo[ 'items' ] = explode( ',', $args[ 'items' ] );
381
382 // Set the view
383 if ( $args[ 'view_item' ] ) {
384
385 $lo[ 'view' ] = array( 'item' => $args[ 'view_item' ] );
386
387 if ( $args[ 'view_availability' ] )
388 $lo[ 'view' ][ 'availability' ] = $args[ 'view_availability' ];
389
390 } elseif ( in_array( $args[ 'view' ], array( 'items', 'all-availability' ), true ) ) {
391
392 $lo[ 'view' ] = $args[ 'view' ];
393
394 } else {
395
396 $lo[ 'view' ] = 'items';
397
398 }
399
400 // Modifiers: shared args like fallback, asn, asn_ref, etc.
401 $lo += self::get_shared_args_array( $args, 'camel', true, true, true );
402
403 return $lo;
404
405 }
406
407
408 // [bookembed] shortcode
409 // ===============================================
410
411 public static function bookembed_shortcode( $attrs ) {
412
413 $script_src = self::bookembed_script_src( $attrs );
414
415 $maybe_error = self::maybe_handle_shortcode_error( $script_src );
416 return $maybe_error ? $maybe_error : "<script src=\"$script_src\"></script>";
417
418 }
419
420 public static function bookembed_script_src( $args ) {
421
422 if ( !isset( self::$bookembed_defaults ) )
423 self::init_defaults();
424
425 $args = self::process_args( $args, self::$bookembed_defaults, 'bookembed' );
426
427 if ( isset( $args[ 'error' ] ) )
428 return $args;
429
430 if ( $args[ 'view_availability' ] && !$args[ 'view_item' ] )
431 return array( 'error' => 'view_availability but no view_item' );
432
433 return self::lightframe_fallback_url( $args, true );
434
435 }
436
437
438 // [fareharbor] shortcode (calendar)
439 // ===============================================
440
441 public static function fareharbor_shortcode( $attrs ) {
442
443 $script_src = self::calendar_script_src( $attrs );
444
445 $maybe_error = self::maybe_handle_shortcode_error( $script_src );
446 return $maybe_error ? $maybe_error : self::calendar_shortcode_script_tag( $script_src );
447
448 }
449
450 private static function calendar_shortcode_script_tag( $script_src ) {
451
452 if ( self::calendar_script_src_has_language( $script_src ) ) {
453 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- FareHarbor embed scripts must load at parser time.
454 return "<script src=\"$script_src\"></script>";
455 }
456
457 // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript -- FareHarbor embed scripts depend on parser-time loading.
458 $script = '<script>(function() {
459 var scriptSrc = ' . wp_json_encode( $script_src ) . ';
460 var currentScript = document.currentScript;
461 var languages = navigator.languages;
462 var supportedLocales = {
463 "en-gb": "en-gb"
464 };
465
466 if ( !languages || !languages.length )
467 languages = [ navigator.language || navigator.userLanguage || "" ];
468
469 for ( var i = 0; i < languages.length; i++ ) {
470 var language = ( languages[i] || "" ).toLowerCase().replace( "_", "-" );
471 if ( !language )
472 continue;
473
474 var parts = language.split( "-" );
475 var locale = parts.length > 1 ? parts[0] + "-" + parts[1] : language;
476
477 if ( supportedLocales[ locale ] ) {
478 scriptSrc += ( scriptSrc.indexOf( "?" ) === -1 ? "?" : "&" ) + "language=" + encodeURIComponent( supportedLocales[ locale ] );
479 break;
480 }
481 }
482
483 if ( document.readyState === "loading" ) {
484 document.write( "<script src=\"" + scriptSrc.replace( /"/g, "&quot;" ) + "\"><\/script>" );
485 return;
486 }
487
488 var script = document.createElement( "script" );
489 script.src = scriptSrc;
490
491 if ( currentScript && currentScript.parentNode )
492 currentScript.parentNode.insertBefore( script, currentScript.nextSibling );
493 else
494 document.head.appendChild( script );
495 }());</script>';
496 // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
497
498 /*
499 * A shortcode may be expanded by a wrapper after WordPress runs wpautop().
500 * Do not allow a later formatting pass to turn the loader's blank lines into
501 * paragraph tags inside its script element.
502 */
503 return str_replace( array( "\r", "\n" ), '', $script );
504
505 }
506
507 private static function calendar_script_src_has_language( $script_src ) {
508
509 return (bool) preg_match( '/(?:\?|&)language=/', $script_src );
510
511 }
512
513 public static function calendar_script_src( $args ) {
514
515 if ( !isset( self::$calendar_defaults ) )
516 self::init_defaults();
517
518 $args = self::process_args( $args, self::$calendar_defaults, 'fareharbor' );
519
520 if ( isset( $args[ 'error' ] ) )
521 return $args;
522
523 $type = $args[ 'type' ];
524 if ( $type === 'small' )
525 $type = 'calendar-small';
526 elseif ( $type === 'large' || $type === 'responsive' )
527 $type = 'calendar';
528
529 $url_suffix = '';
530 if ( $args[ 'items' ] )
531 $url_suffix = 'items/' . $args[ 'items' ] . '/';
532
533 return self::embed_script_src( $type, $args, $url_suffix, array(), false, true );
534
535 }
536
537
538 // [partners] shortcode
539 // ===============================================
540
541 public static function partners_shortcode( $attrs ) {
542
543 $script_src = self::partners_script_src( $attrs );
544
545 $maybe_error = self::maybe_handle_shortcode_error( $script_src );
546 return $maybe_error ? $maybe_error : "<script src=\"$script_src\"></script>";
547
548 }
549
550 public static function partners_script_src( $args ) {
551
552 if ( !isset( self::$partners_defaults ) )
553 self::init_defaults();
554
555 $args = self::process_args( $args, self::$partners_defaults, 'partners' );
556
557 if ( isset( $args[ 'error' ] ) )
558 return $args;
559
560 $query = array();
561 if ( $args[ 'include' ] )
562 $query[ 'include' ] = $args[ 'include' ];
563
564 return self::embed_script_src( 'partners', $args, '', $query, true, false );
565
566 }
567
568
569 // [items] shortcode
570 // ===============================================
571
572 public static function itemgrid_shortcode( $attrs ) {
573
574 $script_src = self::itemgrid_script_src( $attrs );
575
576 $maybe_error = self::maybe_handle_shortcode_error( $script_src );
577 return $maybe_error ? $maybe_error : "<script src=\"$script_src\"></script>";
578
579 }
580
581
582 public static function itemgrid_script_src( $args ) {
583
584 if ( !isset( self::$itemgrid_defaults ) )
585 self::init_defaults();
586
587 $args = self::process_args( $args, self::$itemgrid_defaults, 'itemgrid' );
588
589 if ( isset( $args[ 'error' ] ) )
590 return $args;
591
592 $query = array();
593 if ( $args[ 'items' ] )
594 $query[ 'selected-items' ] = $args[ 'items' ];
595
596 return self::embed_script_src( 'items', $args, '', $query, true, true );
597
598 }
599
600
601 // Shared Shortcode Functionality
602 // ===============================================
603
604 // Handling Argument Input
605 // -----------------------------------------------
606
607 private static function process_args( $args, $defaults, $shortcode_name ) {
608
609 if ( !isset( self::$shared_defaults ) )
610 self::init_defaults();
611
612 $defaults += self::$shared_defaults;
613
614 if ( !is_array( $args ) ) // Just in case.
615 $args = $defaults;
616
617 // Trim whitespace && Strip Tags
618 $args = array_map( array( __CLASS__, 'sanitize' ), $args );
619
620 // Strip smart quotes, because WordPress returns them as part of the value if the shortcode was set up using them
621 $args = str_replace(
622 array( "\xe2\x80\x98", "\xe2\x80\x99", "\xe2\x80\x9c", "\xe2\x80\x9d", chr(145), chr(146), chr(147), chr(148) ),
623 array( '', '', '', '', '', '', '', '' ),
624 $args
625 );
626
627 // Merge in defaults
628 $args = shortcode_atts( $defaults, $args, $shortcode_name );
629
630 // Confirm there's a shortname. All of our shortcodes require this
631 if ( !$args[ 'shortname' ] )
632 return array( 'error' => 'no shortname' );
633
634 // Shortnames only work if lowercase
635 $args[ 'shortname' ] = strtolower( $args[ 'shortname' ] );
636
637 // Clean up item IDs and included companies because users can't be trusted
638 $csv_keys = array( 'items', 'view_item', 'include' );
639 foreach ( $csv_keys as $key )
640 if ( isset( $args[ $key ] ) )
641 $args[ $key ] = self::sanitize_csv( $args[ $key ] );
642
643 if ( $shortcode_name === 'lightframe' || $shortcode_name === 'bookembed' ) {
644
645 if ( !$args[ 'view_item' ] ) {
646 // See if we're filtering to just one item
647 // in that case we treat it as if it were
648 // passed to view_item instead
649 $items_array = explode( ',', $args[ 'items' ] );
650 if ( count( $items_array ) === 1 ) {
651 $args[ 'view_item' ] = $items_array[0];
652 $args[ 'items' ] = '';
653 }
654 }
655
656 }
657
658 return $args;
659
660 }
661
662 public static function sanitize($value)
663 {
664 $sanitized_value = preg_replace('/[^a-zA-Z0-9-_\s,:\/\.=&]/', '', $value);
665
666 return trim(strip_tags($sanitized_value));
667
668 }
669
670 private static function sanitize_csv( $value ) {
671
672 return rtrim( str_replace( ' ', '', $value ), ',' );
673
674 }
675
676
677 // Handling Errors
678 // -----------------------------------------------
679
680 private static $error_messages = array(
681
682 'no shortname'
683 => '<p>Please provide a FareHarbor shortname. (Format: <code>shortname="yourshortname"</code>)</p>',
684 'view_availability but no view_item'
685 => '<p>Please provide <code>view_item</code> if using <code>view_availability</code>.</p>',
686
687 );
688
689 private static function maybe_handle_shortcode_error( $output ) {
690
691 if ( is_array( $output ) && !empty( $output[ 'error' ] ) ) {
692
693 return isset( self::$error_messages[ $output[ 'error' ] ] )
694 ? self::$error_messages[ $output[ 'error' ] ]
695 : '<p>' . $output[ 'error' ] . '</p>';
696
697 }
698
699 return false;
700
701 }
702
703
704 // Scripts for embeds (in use by [partners] and [fareharbor])
705 // -----------------------------------------------
706
707 private static function embed_script_src( $type, $args, $url_suffix, $query, $include_full_items, $include_flow ) {
708
709 $script_src = 'https://' . self::url() . '/embeds/script/'
710 . $type
711 . '/'
712 . $args[ 'shortname' ]
713 . '/'
714 . $url_suffix;
715
716 $args[ 'lightframe' ] = strtolower( trim( $args[ 'lightframe' ] ) );
717 if ( $args[ 'lightframe' ] === 'no' )
718 $query += array( 'lightframe' => $args[ 'lightframe' ] );
719
720 $query += self::get_shared_args_array( $args, 'dash', $include_full_items, true, $include_flow );
721
722 $query_string = http_build_query( $query );
723
724 return $script_src . ( $query_string ? "?$query_string" : '' );
725
726 }
727
728
729 // Shared arguments
730 // -----------------------------------------------
731
732 private static function get_shared_args_array( $args, $casing, $include_full_items, $include_fallback, $include_flow ) {
733
734 $out = array();
735
736 if ( $include_fallback && in_array( $args[ 'fallback' ], array( 'simple', 'classic' ) ) )
737 $out[ 'fallback' ] = $args[ 'fallback' ];
738
739 if ( $include_full_items && $args[ 'full_items' ] !== 'no' )
740 $out[ 'full_items' ] = $args[ 'full_items' ];
741
742 if ( $args[ 'asn' ] ) {
743
744 $out[ 'asn' ] = $args[ 'asn' ];
745
746 if ( $args[ 'asn_ref' ] )
747 $out[ 'asn_ref' ] = $args[ 'asn_ref' ];
748
749 }
750
751 if ( $args[ 'ref' ] )
752 $out[ 'ref' ] = $args[ 'ref' ];
753
754 if ( $args[ 'sheet' ] )
755 $out[ 'sheet' ] = $args[ 'sheet' ];
756
757 if ( $args[ 'sheet_uuid' ] )
758 $out[ 'sheet_uuid' ] = $args[ 'sheet_uuid' ];
759
760 if ( $args[ 'schedule' ] )
761 $out[ 'schedule' ] = $args[ 'schedule' ];
762
763 if ( $args[ 'schedule_uuid' ] )
764 $out[ 'schedule_uuid' ] = $args[ 'schedule_uuid' ];
765
766 if ( $args[ 'language' ] )
767 $out[ 'language' ] = $args[ 'language' ];
768
769 if ( $args[ 'branding' ])
770 $out[ 'branding' ] = $args[ 'branding' ];
771
772 if ( $args[ 'bookable_only' ])
773 $out[ 'bookable_only' ] = $args[ 'bookable_only' ];
774
775 if ( $args[ 'package' ])
776 $out[ 'package' ] = $args[ 'package' ];
777
778 if ( $include_flow && $args[ 'flow' ] )
779 $out[ 'flow' ] = $args[ 'flow' ];
780
781 return self::fix_array_key_casing( $out, $casing );
782
783 }
784
785 private static function fix_array_key_casing( $in, $casing ) {
786
787 $out = array();
788 foreach ( $in as $key => $value )
789 $out[ self::fix_casing( $key, $casing ) ] = $value;
790
791 return $out;
792
793 }
794
795 private static function fix_casing( $text, $casing ) {
796 // Assumes $text is already in snake_case
797 switch ( $casing ) {
798
799 case 'camel':
800
801 // ucwords() doesn't support a $delimeters param
802 // until php 5.4.32/5.5.16
803 $text = strtr( $text, '_', ' ' );
804 $text = ucwords( $text );
805 $text = str_replace( ' ', '', $text );
806 // lcfirst() doesn't exist until php 5.3.0
807 $text[0] = strtolower( $text[0] );
808 return $text;
809
810 case 'dash':
811
812 return strtr( $text, '_', '-' );
813
814 }
815
816 return $text;
817
818 }
819
820
821 // FareHarbor URL with optional FH_ENVIRONMENT
822 // -----------------------------------------------
823
824 private static function url() {
825
826 $environment = ( defined( 'FH_ENVIRONMENT' ) && FH_ENVIRONMENT ) ? FH_ENVIRONMENT : '';
827 $environment = apply_filters( 'fareharbor/environment', $environment );
828
829 return trim( $environment )
830 ? trim( $environment ) . '.fareharbor.com'
831 : 'fareharbor.com';
832
833 }
834
835
836 // Settings & Settings Pages
837 // ===============================================
838
839 // Settings
840 // -----------------------------------------------
841
842 private static function is_lite() {
843 // This filter allows fh-kit and the admin page
844 // to be turned off with:
845 // add_filter( 'fareharbor/lite', '__return_true' );
846 return apply_filters( 'fareharbor/lite', false );
847
848 }
849
850 private static $options;
851
852 private static function get_option( $option_name, $default = '', $filtered = true ) {
853
854 $filter_name = str_replace( 'fh_', '', $option_name );
855 $filter_name = str_replace( 'default_', 'defaults/', $filter_name );
856 $filter_name = str_replace( 'buttons_', 'buttons/', $filter_name );
857 $filter_name = "fareharbor/$filter_name";
858
859 if ( self::is_lite() )
860 return $filtered ? apply_filters( $filter_name, $default ) : $default;
861
862 if ( !isset( self::$options ) )
863 self::$options = get_option( 'fareharbor_settings' );
864
865 $value = isset( self::$options[ $option_name ] )
866 ? self::$options[ $option_name ]
867 : $default;
868
869 if ( $filtered )
870 $value = apply_filters( $filter_name, $value );
871
872 return self::sanitize($value);
873
874 }
875
876
877 // The settings page
878 // -----------------------------------------------
879
880 public static function register_options_page() {
881
882 if ( self::is_lite() )
883 return;
884
885 add_options_page(
886 'FareHarbor Plugin Settings',
887 'FareHarbor',
888 'manage_options',
889 __FILE__,
890 array( __CLASS__, 'render_options_page' )
891 );
892
893 }
894
895 public static function register_settings() {
896
897 if ( self::is_lite() )
898 return;
899
900 register_setting(
901 'fareharbor_settings',
902 'fareharbor_settings',
903 array( __CLASS__, 'validate_settings' )
904 );
905
906 add_settings_section(
907 'fh_about_section',
908 'About',
909 array( __CLASS__, 'render_fh_about_section_text' ),
910 __FILE__
911 );
912
913 add_settings_section(
914 'fh_shortcode_defaults_section',
915 'Shortcode Defaults',
916 array( __CLASS__, 'render_fh_shortcode_defaults_section_text' ),
917 __FILE__
918 );
919
920 add_settings_field(
921 'fh_default_shortname',
922 'shortname',
923 array( __CLASS__, 'render_input_field' ),
924 __FILE__,
925 'fh_shortcode_defaults_section',
926 array(
927 'option_name' => 'fh_default_shortname',
928 'description' => 'Your company\'s FareHarbor shortname.',
929 )
930 );
931
932 add_settings_field(
933 'fh_responsive_calendars',
934 'Responsive Calendars',
935 array( __CLASS__, 'render_input_field' ),
936 __FILE__,
937 'fh_shortcode_defaults_section',
938 array(
939 'option_name' => 'fh_responsive_calendars',
940 'type' => 'checkbox',
941 'label' => 'Use responsive calendars by default.',
942 'description' => 'This is the same as [fareharbor type="responsive"].',
943 )
944 );
945
946 add_settings_field(
947 'fh_default_asn',
948 'asn',
949 array( __CLASS__, 'render_input_field' ),
950 __FILE__,
951 'fh_shortcode_defaults_section',
952 array(
953 'option_name' => 'fh_default_asn',
954 'description' => 'The shortname of your partner company. Include if using the ASN network.',
955 )
956 );
957
958 add_settings_field(
959 'fh_default_asn_ref',
960 'asn_ref',
961 array( __CLASS__, 'render_input_field' ),
962 __FILE__,
963 'fh_shortcode_defaults_section',
964 array(
965 'option_name' => 'fh_default_asn_ref',
966 'description' => 'The voucher number that should be set for ASN bookings.',
967 )
968 );
969
970 add_settings_field(
971 'fh_default_ref',
972 'ref',
973 array( __CLASS__, 'render_input_field' ),
974 __FILE__,
975 'fh_shortcode_defaults_section',
976 array(
977 'option_name' => 'fh_default_ref',
978 'description' => 'The online booking reference that should be set for bookings',
979 )
980 );
981
982 add_settings_field(
983 'fh_default_sheet',
984 'sheet',
985 array( __CLASS__, 'render_input_field' ),
986 __FILE__,
987 'fh_shortcode_defaults_section',
988 array(
989 'option_name' => 'fh_default_sheet',
990 'description' => 'The price sheet that should be used while creating bookings.',
991 )
992 );
993
994 add_settings_field(
995 'fh_default_schedule',
996 'schedule',
997 array( __CLASS__, 'render_input_field' ),
998 __FILE__,
999 'fh_shortcode_defaults_section',
1000 array(
1001 'option_name' => 'fh_default_schedule',
1002 'description' => 'The price schedule that should be used while creating bookings.',
1003 )
1004 );
1005
1006 add_settings_field(
1007 'fh_default_language',
1008 'language',
1009 array( __CLASS__, 'render_input_field' ),
1010 __FILE__,
1011 'fh_shortcode_defaults_section',
1012 array(
1013 'option_name' => 'fh_default_language',
1014 'description' => 'The two letter code for the language that FareHarbor pages should be overridden to, instead of detecting the user\'s language. (Not recommended.)',
1015 )
1016 );
1017
1018 add_settings_field(
1019 'fh_default_full_items',
1020 'full_items',
1021 array( __CLASS__, 'render_select_field' ),
1022 __FILE__,
1023 'fh_shortcode_defaults_section',
1024 array(
1025 'option_name' => 'fh_default_full_items',
1026 'choices' => array(
1027 'yes',
1028 'no',
1029 ),
1030 'description' => 'Should the Lightframe that is opened include item descriptions and photos? Defaults to "no".',
1031 )
1032 );
1033
1034 add_settings_field(
1035 'fh_default_lightframe',
1036 'lightframe',
1037 array( __CLASS__, 'render_select_field' ),
1038 __FILE__,
1039 'fh_shortcode_defaults_section',
1040 array(
1041 'option_name' => 'fh_default_lightframe',
1042 'choices' => array(
1043 'yes',
1044 'no',
1045 ),
1046 'description' => 'Choose "yes" to open new bookings inside a Lightframe. Choose "no" to open them in an external page instead. Defaults to "yes".'
1047 )
1048 );
1049
1050 add_settings_field(
1051 'fh_default_class',
1052 'class',
1053 array( __CLASS__, 'render_input_field' ),
1054 __FILE__,
1055 'fh_shortcode_defaults_section',
1056 array(
1057 'option_name' => 'fh_default_class',
1058 'description' => 'The class option only applies to the [lightframe] shortcode.',
1059 )
1060 );
1061
1062 add_settings_section(
1063 'fh_auto_lightframe_section',
1064 'Automatic Lightframing',
1065 array( __CLASS__, 'render_fh_auto_lightframe_section_text' ),
1066 __FILE__
1067 );
1068
1069 add_settings_field(
1070 'fh_auto_lightframe_enabled',
1071 'Auto Lightframing Activation',
1072 array( __CLASS__, 'render_input_field' ),
1073 __FILE__,
1074 'fh_auto_lightframe_section',
1075 array(
1076 'option_name' => 'fh_auto_lightframe_enabled',
1077 'type' => 'checkbox',
1078 'label' => 'Enable auto Lightframing.',
1079 )
1080 );
1081
1082 add_settings_section(
1083 'fh_buttons_section',
1084 'FareHarbor Buttons',
1085 array( __CLASS__, 'render_fh_buttons_section_text' ),
1086 __FILE__
1087 );
1088
1089 add_settings_field(
1090 'fh_buttons_active',
1091 'FH Buttons Activation',
1092 array( __CLASS__, 'render_input_field' ),
1093 __FILE__,
1094 'fh_buttons_section',
1095 array(
1096 'option_name' => 'fh_buttons_active',
1097 'type' => 'checkbox',
1098 'label' => 'Load the FH Buttons stylesheet on all pages.',
1099 )
1100 );
1101
1102 add_settings_field(
1103 'fh_buttons_query',
1104 'Button Colors',
1105 array( __CLASS__, 'render_input_field' ),
1106 __FILE__,
1107 'fh_buttons_section',
1108 array(
1109 'option_name' => 'fh_buttons_query',
1110 'description' => 'Provide colors in the format <code>red=ff0000&green=00ff00</code>',
1111 )
1112 );
1113
1114 }
1115
1116 public static function validate_settings( $input ) {
1117
1118 // Strip tags and trim whitespace
1119 $input = array_map( array( __CLASS__, 'sanitize' ), $input );
1120
1121 // Let's not pollute the DB with empty settings
1122 $input = array_filter( $input );
1123
1124 // Shortnames only work when lowercase
1125 if ( isset( $input[ 'fh_default_shortname' ] ) )
1126 $input[ 'fh_default_shortname' ] = strtolower( $input[ 'fh_default_shortname' ] );
1127
1128 return $input;
1129
1130 }
1131
1132 public static function render_options_page() {
1133
1134 ?>
1135
1136 <div class="wrap">
1137
1138 <h2>FareHarbor Plugin Settings</h2>
1139
1140 <form action="options.php" method="post">
1141
1142 <?php settings_fields( 'fareharbor_settings' ); ?>
1143
1144 <?php do_settings_sections( __FILE__ ); ?>
1145
1146 <p class="submit">
1147 <input name="Submit" type="submit" class="button-primary" value="<?php esc_attr_e( 'Save Changes' ); ?>" />
1148 </p>
1149
1150 </form>
1151
1152 </div>
1153
1154 <?php
1155
1156 }
1157
1158 public static function render_fh_about_section_text() {
1159
1160 echo '<p><b>This plugin requires a FareHarbor account to work.</b> FareHarbor provides powerful, intuitive, and highly customizable reservation software for activity and tourism businesses. We can help you enable online booking on your website using this plugin and a whole range of other features. Don\'t have an account? <a href="https://fareharbor.com/join/" target="_blank">Get in touch at fareharbor.com.</a></p><p>If you\'re already with FareHarbor, check out our <a href="https://fareharbor.com/help/setup/wordpress-plugin/" target="_blank">help center article</a> on how to get started with this plugin.</p>';
1161
1162 }
1163
1164 public static function render_fh_shortcode_defaults_section_text() {
1165
1166 echo '<p>Set default options for the [lightframe], [fareharbor], [itemgrid], and [partners] shortcodes here. The options written in when including a shortcode on a page will always take precedence over the default values entered below. <a href="https://fareharbor.com/help/setup/wordpress-plugin/#setting-defaults" target="_blank">Learn how default options work.</a></p>';
1167
1168 }
1169
1170 public static function render_fh_auto_lightframe_section_text() {
1171
1172 echo '<p>Open normal links to FareHarbor as Lightframe overlays, even if the [lightframe] shortcode isn&apos;t used.</p>';
1173
1174 }
1175
1176 public static function render_fh_buttons_section_text() {
1177
1178 echo '<p>FareHarbor can automatically load in a CSS stylesheet which includes classes to make your Lightframe links look like beautifully designed buttons.</p>';
1179
1180 }
1181
1182 public static function render_input_field( $args ) {
1183 // We use this function to render all of our options fields
1184 // $args is supplied as the last parameter to each
1185 // add_settings_field() call in fareharbor::register_settings()
1186 // This function is only run on our settings page in the admin
1187
1188 $type = isset( $args[ 'type' ] ) ? $args[ 'type' ] : 'text';
1189
1190 $option_name = $args[ 'option_name' ];
1191 $value = self::get_option( $option_name, '', false );
1192
1193 // Attributes all input tags need
1194 $attrs = array(
1195 'name' => "fareharbor_settings[$option_name]",
1196 'id' => $option_name,
1197 'type' => $type,
1198 );
1199
1200 // Type-specific attributes
1201 switch ( $type ) {
1202
1203 case 'text':
1204 $attrs[ 'size' ] = '40';
1205 $attrs[ 'value' ] = (string) $value;
1206 break;
1207
1208 case 'checkbox':
1209 if ( $value )
1210 $attrs[ 'checked' ] = 'checked';
1211 break;
1212
1213 }
1214
1215 // Merge in attributes supplied in the arguments
1216 if ( isset( $args[ 'attrs' ] ) )
1217 // attributes from the arguments will override the defaults
1218 $attrs = $args[ 'attrs' ] + $attrs;
1219
1220 // Actually generate the input tag
1221 $out = '<input';
1222 foreach ( $attrs as $name => $value )
1223 $out .= " $name=\"$value\"";
1224 $out .= ' />';
1225
1226 if ( !empty( $args[ 'label' ] ) )
1227 $out .= "<label for=\"$option_name\">{$args[ 'label' ]}</label>";
1228
1229 if ( !empty( $args[ 'description' ] ) )
1230 $out .= "<p class=\"description\">{$args[ 'description' ]}</p>";
1231
1232 // And print it onto the page
1233 echo $out;
1234
1235 }
1236
1237 public static function render_select_field( $args ) {
1238
1239 $option_name = $args[ 'option_name' ];
1240 $value = self::get_option( $option_name, '', false );
1241
1242 ?>
1243
1244 <select name="<?php echo "fareharbor_settings[$option_name]" ?>" id="<?php echo $option_name ?>">
1245
1246 <option value="" <?php selected( '', $value ); ?>></option>
1247
1248 <?php foreach ( $args[ 'choices' ] as $choice ) { ?>
1249 <option value="<?php echo $choice ?>" <?php selected( $choice, $value ) ?>><?php echo $choice ?></option>
1250 <?php } ?>
1251
1252 </select>
1253
1254 <?php
1255
1256 if ( !empty( $args[ 'description' ] ) )
1257 echo "<p class=\"description\">{$args[ 'description' ]}</p>";
1258
1259 }
1260
1261 }
1262