/**
  * @param array  $atts
  * @param string $content
  * @param string $tag
  *
  * @return string
  */
 public static function shortcode($atts, $content = '', $tag = 'connections')
 {
     // Grab an instance of the Connections object.
     $instance = Connections_Directory();
     $html = '';
     if (is_customize_preview()) {
         /**
          * Hook to allow the active template to be overridden and set to the current template being customized.
          *
          * @since 8.4
          *
          * @param array $atts {
          *     @type string $template The template slug of the template being customized.
          * }
          */
         $atts = apply_filters('cn_template_customizer_template', $atts);
     }
     /** @var cnTemplate $template */
     $template = cnTemplateFactory::loadTemplate($atts);
     if ($template === FALSE) {
         return cnTemplatePart::loadTemplateError($atts);
     }
     /*
      * This filter adds the current template paths to cnLocate so when template
      * part file overrides are being searched for, it'll also search in template
      * specific paths. This filter is then removed at the end of the shortcode.
      */
     add_filter('cn_locate_file_paths', array($template, 'templatePaths'));
     self::addFilterRegistry('cn_locate_file_paths');
     do_action('cn_template_include_once-' . $template->getSlug());
     do_action('cn_template_enqueue_js-' . $template->getSlug());
     /*
      * Now that the template has been loaded, Validate the user supplied shortcode atts.
      */
     $defaults = array('id' => NULL, 'slug' => NULL, 'category' => NULL, 'category_in' => NULL, 'exclude_category' => NULL, 'category_name' => NULL, 'category_slug' => NULL, 'wp_current_category' => FALSE, 'allow_public_override' => FALSE, 'private_override' => FALSE, 'show_alphaindex' => cnSettingsAPI::get('connections', 'display_results', 'index'), 'repeat_alphaindex' => cnSettingsAPI::get('connections', 'display_results', 'index_repeat'), 'show_alphahead' => cnSettingsAPI::get('connections', 'display_results', 'show_current_character'), 'list_type' => NULL, 'order_by' => NULL, 'limit' => NULL, 'offset' => NULL, 'family_name' => NULL, 'last_name' => NULL, 'title' => NULL, 'organization' => NULL, 'department' => NULL, 'district' => NULL, 'county' => NULL, 'city' => NULL, 'state' => NULL, 'zip_code' => NULL, 'country' => NULL, 'meta_query' => '', 'content' => '', 'near_addr' => NULL, 'latitude' => NULL, 'longitude' => NULL, 'radius' => 10, 'unit' => 'mi', 'template' => NULL, 'width' => NULL, 'lock' => FALSE, 'force_home' => FALSE, 'home_id' => in_the_loop() && is_page() ? get_the_ID() : cnSettingsAPI::get('connections', 'home_page', 'page_id'));
     $defaults = apply_filters('cn_list_atts_permitted', $defaults);
     $defaults = apply_filters('cn_list_atts_permitted-' . $template->getSlug(), $defaults);
     $atts = shortcode_atts($defaults, $atts, $tag);
     $atts = apply_filters('cn_list_atts', $atts);
     $atts = apply_filters('cn_list_atts-' . $template->getSlug(), $atts);
     /*
      * Convert some of the $atts values in the array to boolean.
      */
     cnFormatting::toBoolean($atts['allow_public_override']);
     cnFormatting::toBoolean($atts['private_override']);
     cnFormatting::toBoolean($atts['show_alphaindex']);
     cnFormatting::toBoolean($atts['repeat_alphaindex']);
     cnFormatting::toBoolean($atts['show_alphahead']);
     cnFormatting::toBoolean($atts['wp_current_category']);
     cnFormatting::toBoolean($atts['lock']);
     cnFormatting::toBoolean($atts['force_home']);
     // var_dump( $atts );
     /*
      * The post editor entity encodes the post text we have to decode it
      * so a match can be made when the query is run.
      */
     $atts['family_name'] = html_entity_decode($atts['family_name']);
     $atts['last_name'] = html_entity_decode($atts['last_name']);
     $atts['title'] = html_entity_decode($atts['title']);
     $atts['organization'] = html_entity_decode($atts['organization']);
     $atts['department'] = html_entity_decode($atts['department']);
     $atts['city'] = html_entity_decode($atts['city']);
     $atts['state'] = html_entity_decode($atts['state']);
     $atts['zip_code'] = html_entity_decode($atts['zip_code']);
     $atts['country'] = html_entity_decode($atts['country']);
     $atts['category_name'] = html_entity_decode($atts['category_name']);
     if (0 < strlen($atts['meta_query'])) {
         // The meta query syntax follows the JSON standard, except, the WordPress Shortcode API does not allow
         // brackets within shortcode options, so parenthesis have to be used instead, so, lets swap them
         // that was json_decode can be ran and the resulting array used in cnRetrieve::entries().
         $atts['meta_query'] = str_replace(array('(', ')'), array('[', ']'), $atts['meta_query']);
         $metaQuery = cnFormatting::maybeJSONdecode($atts['meta_query']);
         $atts['meta_query'] = is_array($metaQuery) ? $metaQuery : array();
     }
     $atts = apply_filters('cn_list_retrieve_atts', $atts);
     $atts = apply_filters('cn_list_retrieve_atts-' . $template->getSlug(), $atts);
     $results = $instance->retrieve->entries($atts);
     // $html .= print_r( $instance->lastQuery, TRUE );
     // Apply any registered filters to the results.
     if (!empty($results)) {
         $results = apply_filters('cn_list_results', $results);
         $results = apply_filters('cn_list_results-' . $template->getSlug(), $results);
         self::addFilterRegistry('cn_list_results-' . $template->getSlug());
     }
     ob_start();
     // Prints the template's CSS file.
     // NOTE: This is primarily to support legacy templates which included a CSS
     // file which was not enqueued in the page header.
     do_action('cn_template_inline_css-' . $template->getSlug(), $atts);
     // The return to top anchor
     do_action('cn_list_return_to_target', $atts);
     $html .= ob_get_clean();
     $html .= sprintf('<div class="cn-list" id="cn-list" data-connections-version="%1$s-%2$s"%3$s>', $instance->options->getVersion(), $instance->options->getDBVersion(), empty($atts['width']) ? '' : ' style="width: ' . $atts['width'] . 'px;"');
     $html .= sprintf('<div class="cn-template cn-%1$s" id="cn-%1$s" data-template-version="%2$s">', $template->getSlug(), $template->getVersion());
     // The filter should check $content that content is not empty before processing $content.
     // And if it is empty the filter should return (bool) FALSE, so the core template parts can be executed.
     $content = apply_filters("cn_shortcode_content-{$tag}", FALSE, $content, $atts, $results, $template);
     if ($content === FALSE) {
         ob_start();
         // Render the core result list header.
         cnTemplatePart::header($atts, $results, $template);
         // Render the core result list body.
         cnTemplatePart::body($atts, $results, $template);
         // Render the core result list footer.
         cnTemplatePart::footer($atts, $results, $template);
         $html .= ob_get_clean();
     } else {
         $html .= $content;
     }
     $html .= PHP_EOL . '</div>' . (WP_DEBUG ? '<!-- END #cn-' . $template->getSlug() . ' -->' : '') . PHP_EOL;
     $html .= PHP_EOL . '</div>' . (WP_DEBUG ? '<!-- END #cn-list -->' : '') . PHP_EOL;
     // Clear any filters that have been added.
     // This allows support using the shortcode multiple times on the same page.
     cnShortcode::clearFilterRegistry();
     // @todo This should be run via a filter.
     return self::removeEOL($html);
 }