You are here

panels_ajax_tab.module in Panels Ajax Tabs 7

Allows users to create and manage Panels Ajax Tabs.

File

panels_ajax_tab.module
View source
<?php

/**
 * @file
 * Allows users to create and manage Panels Ajax Tabs.
 */

/**
 * Implements hook_ctools_plugin_api().
 */
function panels_ajax_tab_ctools_plugin_api() {
  return array(
    "version" => "1",
  );
}

/**
 * Implements hook_ctools_plugin_dierctory().
 */
function panels_ajax_tab_ctools_plugin_directory($module, $plugin) {
  if ($plugin == 'content_types') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implements hook_menu().
 */
function panels_ajax_tab_menu() {
  $items = array();
  $items['panels_ajax_tab/%/%/%'] = array(
    'title' => 'panels-ajax tab AJAX callback',
    'description' => 'AHAJ callback for panels tabs',
    'page callback' => 'panels_ajax_tab_ajax',
    'page arguments' => array(
      1,
      2,
      3,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/user-interface/panels-ajax-tab'] = array(
    'title' => 'Panels AJAX Tabs Visibility',
    'description' => "Configure visibility rule for panels ajax tab.",
    'page callback' => 'panels_ajax_tab_admin_overview',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'panels_ajax_tab.admin.inc',
  );
  $items['admin/config/user-interface/panels-ajax-tab/%panels_ajax_tab_container/edit'] = array(
    'description' => 'Visibility settings callback for panels tabs',
    'title' => 'panels-ajax tab visibility settings',
    'title callback' => '_panels_ajax_tab_settings_title',
    'title arguments' => array(
      4,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'panels_ajax_tab_settings',
      4,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'panels_ajax_tab.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Title callback: Returns a title for Panel AJAX tab container.
 *
 * @param array $panels_ajax_tab
 *   An panels ajax tab configuration.
 */
function _panels_ajax_tab_settings_title(array $panels_ajax_tab) {
  return t('@container_id settings', array(
    '@container_id' => $panels_ajax_tab['container_id'],
  ));
}

/**
 * Loads a panels ajax tab configuration.
 *
 * @param string $container_id
 *   The container id.
 */
function panels_ajax_tab_container_load($container_id) {
  if (!empty($container_id)) {
    $panels_ajax_tabs = panels_ajax_tab_config_cache();
    foreach ($panels_ajax_tabs as $panels_ajax_tab) {
      if ($panels_ajax_tab['container_id'] == $container_id) {
        return $panels_ajax_tab;
      }
    }
  }
  return FALSE;
}

/**
 * Implements hook_theme().
 */
function panels_ajax_tab_theme($existing, $type, $theme, $path) {
  return array(
    'panels_ajax_tab_tabs' => array(
      'variables' => array(
        'tabs' => array(),
        'tab_container_id' => NULL,
        'context_string' => NULL,
        'clean_url' => FALSE,
        'clean_url_delim' => '/',
      ),
    ),
    'panels_ajax_tab_container' => array(
      'variables' => array(
        'tab_container_id' => NULL,
        'preloaded' => '',
        'content' => '',
      ),
    ),
    'panels_ajax_tab_ajax' => array(
      'variables' => array(
        'minipanel' => NULL,
      ),
    ),
    'panels_ajax_tab_tabs_edit_form' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_boot().
 *
 * IF Clean-URLs are enabled for any ajax-tab pane, we strip the URL identifier
 * off the end of the URL and place it in $_GET['panels_ajax_tab_trigger'].
 * We additionally put the mini-panel this corresponds to in
 * $_GET['panels_ajax_tab_tab']. Once this is done drupal can handle the URL
 * normally (it won't get confused by the identifier on the end of the URL) and
 * panels-ajax-tabs will look for what tab to load by inspecting
 * $_GET['panels_ajax_tab_tab']. In Essense, it transforms a url like
 * http://example.com/path/to/drupal/page.mytab to a url like
 * http://example.com/path/to/drupal/page?panels_ajax_tab_trigger=mytab&panels_ajax_tab_tab=mypanel.
 */
function panels_ajax_tab_boot() {
  $config_cache = cache_get('panels_ajax_tab_config_cache');

  // If there are no url-cache settings, ensure that the url-cache is rebuilt
  // for next-time and skip.
  if (empty($config_cache)) {
    register_shutdown_function('panels_ajax_tab_config_cache');
    return;
  }
  foreach ($config_cache->data as $config) {
    if ($config['clean_url']) {
      foreach ($config['mini_panels'] as $panel_id => $panel_conf) {
        $delim = $config['clean_url_delim'];
        $trigger = $delim . $panel_conf['url_id'];
        $pathlen = strlen($_GET['q']);
        $triggerlen = strlen($trigger);
        if ($triggerlen > $pathlen) {
          continue;
        }
        if (substr_compare($_GET['q'], $trigger, -$triggerlen) === 0) {
          $_GET['q'] = substr($_GET['q'], 0, $pathlen - $triggerlen);
          $_GET['panels_ajax_tab_tab'] = $panel_id;
          $_GET['panels_ajax_tab_trigger'] = $panel_conf['url_id'];
          return;
        }
      }
    }
  }
}

/**
 * Implements hook_element_info_alter().
 *
 * Form actions should default to the panels-ajax-tab request URL, not the
 * actual request URI.
 */
function panels_ajax_tab_element_info_alter(&$element_info) {
  if (arg(0) == 'panels_ajax_tab') {
    $headers = getallheaders();
    if (!empty($headers['X-Request-Path'])) {
      $element_info['form']['#action'] = $headers['X-Request-Path'];
    }
  }
}

/**
 * AJAX callback for rendering tab.
 */
function panels_ajax_tab_ajax($panel_name, $context_string, $url_enabled) {

  // Modify the request so downstream modules properly process the URL
  // and menu object.
  $headers = getallheaders();
  if (!empty($headers['X-Request-Path'])) {
    $_GET['original_q'] = $_GET['q'];
    $_GET['q'] = ltrim($headers['X-Request-Path'], '/');
    panels_ajax_tab_boot();
    $_GET['q'] = drupal_get_normal_path($_GET['q']);
  }

  // Load the mini panel.
  $mini = panels_ajax_tab_prepare_mini($panel_name, $context_string);

  // Render the output.
  $output = theme('panels_ajax_tab_ajax', array(
    'minipanel' => $mini,
    'url_enabled' => $url_enabled,
  ));
  $response = array();
  $response['markup'] = $output;

  // Allow other modules to alter the response.
  $trigger = !empty($_GET['panels_ajax_tab_trigger']) ? $_GET['panels_ajax_tab_trigger'] : '';
  drupal_alter('panels_ajax_tab_response', $response, $panel_name, $context_string, $trigger);
  drupal_json_output($response);
  exit;
}

/**
 * Prepares the mini-panel.
 */
function panels_ajax_tab_prepare_mini($mini, $context_string) {
  ctools_include('context');
  ctools_include('plugins');
  ctools_include('plugins', 'panels');
  if (is_string($mini)) {
    $mini = panels_mini_load($mini);
  }
  if ($context_string != 'none') {
    $entity = panels_ajax_tab_get_context($context_string);
    if (!$entity) {
      drupal_not_found();
      exit;
    }

    // If it's a node, check node-view permissions.
    if ($entity->entity_type == 'node') {
      if (!node_access('view', $entity)) {
        drupal_access_denied();
        exit;
      }
    }
    $context_plugin = ctools_get_plugins('ctools', 'contexts', 'entity');
    $context_plugin['keyword'] = $entity->entity_type;
    $context = ctools_context_create_entity(FALSE, $entity, FALSE, $context_plugin);

    // Load up any contexts we might be using.
    $context = ctools_context_match_required_contexts($mini->requiredcontexts, array(
      $context,
    ));
    $mini->context = $mini->display->context = ctools_context_load_contexts($mini, FALSE, $context);
  }
  if (empty($mini) || !empty($mini->disabled)) {
    return;
  }
  $mini->display->owner = $mini;

  // Unique ID of this mini.
  $mini->display->owner->id = $mini->name;
  return $mini;
}

/**
 * Get a context from a context_string.
 */
function panels_ajax_tab_get_context($context_string) {
  if (empty($context_string) || $context_string == 'none') {
    return FALSE;
  }
  $parts = explode(':', $context_string);
  $entity_type = strtolower($parts[0]);
  $entity_ids = array(
    (int) $parts[1],
  );
  $entities = entity_load($entity_type, $entity_ids);
  $entity = array_pop($entities);
  $entity->entity_type = $entity_type;
  return $entity;
}

/**
 * Theme function that renders the panels ajax tabs.
 */
function theme_panels_ajax_tab_tabs($vars) {
  foreach ($vars['tabs'] as $tab) {
    $url_enabled = isset($tab['url_enabled']) ? $tab['url_enabled'] : 1;
    $tabhtml = '<a href="' . $tab['href'] . '" class="panels-ajax-tab-tab" data-panel-name="' . $tab['mini_panel']->name . '" data-target-id="' . $vars['tab_container_id'] . '" data-entity-context="' . $vars['context_string'] . '" data-trigger="' . $tab['url_id'] . '" data-url-enabled="' . $url_enabled . '">' . $tab['title'] . '</a>';

    // Add a hidden link for javascript enabled crawlers (but not normal web
    // crawlers).
    $tabhtml .= '<a href="/panels_ajax_tab/' . $tab['mini_panel']->name . '/' . $vars['context_string'] . '/' . $url_enabled . '" rel="nofollow" style="display:none" class="js-crawler-link"></a>';
    $tabs[] = $tabhtml;
  }
  return theme('item_list', array(
    'items' => $tabs,
    'attributes' => array(
      'class' => array(
        'tabs',
        'inline',
        'panels-ajax-tab',
      ),
    ),
  ));
}

/**
 * Theme function that renders the container.
 */
function theme_panels_ajax_tab_container($vars) {
  return '<div data-panels-ajax-tab-preloaded="' . $vars['preloaded'] . '" id="panels-ajax-tab-container-' . $vars['tab_container_id'] . '" class="panels-ajax-tab-container">' . $vars['content'] . '</div>';
}

/**
 * Theme function that renders the AHAH markup for a mini-panel.
 */
function theme_panels_ajax_tab_ajax($vars) {

  // Grab all CSS for later comparison before clearing it.
  $previous_css = drupal_add_css();

  // Clear the JavaScript & CSS not related to the pane being rendered.
  drupal_static_reset('drupal_add_js');
  drupal_static_reset('drupal_add_css');

  // Render the pane.
  $mini = $vars['minipanel'];
  $layout = panels_get_layout($mini->display->layout);
  if (empty($mini->display->cache_key)) {
    $mini->display->cache_key = $mini->name;
  }
  $panels_output = panels_render_display($mini->display);

  // Grab new CSS files related to this pane.
  $css = drupal_add_css();

  // Assign a unique basename to new stylesheets.
  foreach ($css as $key => $item) {
    if ($item['type'] == 'file') {

      // If not defined, force a unique basename for this file.
      $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
      $item['basename'] = $basename;
      $css[$key] = $item;
    }
  }

  // Iterate through previous CSS stylesheets, and add any with matching
  // basenames to allow, for proper overrides when calling drupal_get_css($css).
  foreach ($previous_css as $key => $item) {
    if ($item['type'] == 'file') {

      // If not defined, force a unique basename for this file.
      $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
      $item['basename'] = $basename;
      $previous_css[$key] = $item;
      foreach ($css as $key2 => $item2) {

        // Make sure to append previous stylesheets with matching basenames
        // to the new CSS array.
        if ($item['basename'] == $item2['basename']) {
          $css[$key] = $previous_css[$key];
        }
      }
    }
  }
  $output = '';
  $output .= '<?xml version="1.0" encoding="UTF-8" ?>';
  $output .= '
    <html version="HTML+RDFa+MathML 1.1"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:dc="http://purl.org/dc/terms/"
    xmlns:foaf="http://xmlns.com/foaf/0.1/"
    xmlns:og="http://ogp.me/ns#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns:sioc="http://rdfs.org/sioc/ns#"
    xmlns:sioct="http://rdfs.org/sioc/types#"
    xmlns:skos="http://www.w3.org/2004/02/skos/core#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:mml="http://www.w3.org/1998/Math/MathML">
  ';
  $output .= '<head>';

  // Need to call page alter so that modules like google analytics can add js
  // We only invoke the page alter if url_enabled is true.
  if ($vars['url_enabled']) {
    $page = array();
    drupal_alter('page', $page);
  }

  // Get the javascript and filter out the default misc/drupal.js -
  // it's already been added by the calling page.
  $javascript = drupal_add_js();
  unset($javascript['misc/drupal.js']);

  // Add the javascript.
  $output .= drupal_get_js('header', $javascript);

  // Add CSS.
  $output .= drupal_get_css($css);
  if (isset($layout['css'])) {
    $output .= "<link rel='stylesheet' type='text/css' href='" . base_path() . $layout['path'] . '/' . $layout['css'] . "' />";
  }
  $output .= '</head>';

  // Print the output of the panel.
  $output .= '<body>';
  $output .= theme('status_messages');
  $output .= '<div class="panels-ajax-tab-panel panels-ajax-tab-panel-' . str_replace('_', '-', $mini->name) . '">';
  $output .= $panels_output;
  $output .= '</div>';
  $output .= drupal_get_js('footer', $javascript);
  $output .= '</body></html>';
  return $output;
}

/**
 * Theme function that renders the pane configuration form.
 */
function theme_panels_ajax_tab_tabs_edit_form($variables) {
  $form = $variables['form'];
  $output = '';

  // Render elements that are supposed to be on top.
  foreach (element_children($form) as $element) {
    if ($element == 'mini_panels') {
      break;
    }
    else {
      $output .= drupal_render($form[$element]);
    }
  }
  $rows = array();
  foreach (element_children($form['mini_panels']) as $mini_panel) {
    $form['mini_panels'][$mini_panel]['weight']['#attributes']['class'] = array(
      'panel-weight',
    );
    $rows[] = array(
      'data' => array(
        drupal_render($form['mini_panels'][$mini_panel]['selected']),
        drupal_render($form['mini_panels'][$mini_panel]['name']),
        drupal_render($form['mini_panels'][$mini_panel]['tab_title']),
        drupal_render($form['mini_panels'][$mini_panel]['url_id']),
        drupal_render($form['mini_panels'][$mini_panel]['weight']),
      ),
      'class' => array(
        'draggable',
      ),
    );
  }
  $header = array(
    t('Select'),
    t('Mini panel'),
    t('Tab title'),
    t('URL identifier'),
    t('Weight'),
  );
  $output .= theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'id' => 'panels-ajax-tab-admin-table',
    ),
  ));
  $output .= drupal_render_children($form);
  drupal_add_tabledrag('panels-ajax-tab-admin-table', 'order', 'sibling', 'panel-weight');
  return $output;
}

/**
 * Implements hook_flush_caches().
 */
function panels_ajax_tab_flush_caches() {

  // After caches are cleared we will run panels_ajax_tab_config_cache
  // to immediately rebuild the url-cache.
  register_shutdown_function('panels_ajax_tab_config_cache');

  // We don't want to add any custom cache-tables, just return an empty array.
  return array();
}

/**
 * Implements hook_cron().
 */
function panels_ajax_tab_cron() {
  panels_ajax_tab_config_cache();
}

/**
 * Rebuild the URL cache.
 *
 * URL suffix information is needed on boot, but it cannot be generated at boot
 * time, so we generate it separately and store
 * it in a cache item.
 */
function panels_ajax_tab_config_cache() {
  $configs = panels_ajax_tab_get_config();
  cache_set('panels_ajax_tab_config_cache', $configs);
  return $configs;
}

/**
 * Get a list of all panels-ajax-tab configs.
 *
 * This is used to build a list url suffixes to process at boot if so enabled.
 */
function panels_ajax_tab_get_config() {
  $config = array();

  // Mini panels.
  if (module_exists('panels_mini')) {
    $panels = panels_mini_load_all();
    foreach ($panels as $panel) {
      if (!empty($panel->display->content)) {
        foreach ($panel->display->content as $pid => $pane) {
          if (in_array($pane->type, array(
            'panels_ajax_tab_tabs',
            'highwire_panel_tabs',
          ))) {
            $pane->configuration['plugin_type'] = $pane->type;
            $container_id = $pane->configuration['container_id'];
            $config[$container_id] = $pane->configuration;
          }
        }
      }
    }
  }

  // Page-manager pages.
  if (module_exists('page_manager')) {

    // Now check all panel pages and ignore all mini panels.
    ctools_include('page', 'page_manager', 'plugins/tasks');
    ctools_include('page_manager.admin', 'page_manager', '');
    ctools_include('export');
    $tasks = page_manager_get_tasks_by_type('page');
    $pages = array();
    foreach ($tasks as $task) {
      if ($task_pages = page_manager_load_task_handlers($task)) {
        $pages = array_merge($pages, $task_pages);
      }
      if ($subtasks = page_manager_get_task_subtasks($task)) {
        foreach ($subtasks as $subtask) {
          $task_pages = page_manager_load_task_handlers($subtask);
          $pages = array_merge($pages, $task_pages);
        }
      }
    }

    // Not all display objects are loaded, make sure to load them.
    foreach ($pages as $page) {
      if (empty($page->conf['display']) && !empty($page->conf['did'])) {
        $page->conf['display'] = panels_load_display($page->conf['did']);
      }
    }
    foreach ($pages as $page) {
      if (empty($page->disabled) && !empty($page->conf['display']->content)) {
        foreach ($page->conf['display']->content as $pid => $pane) {
          if (in_array($pane->type, array(
            'panels_ajax_tab_tabs',
            'highwire_panel_tabs',
          ))) {
            $pane->configuration['plugin_type'] = $pane->type;
            $container_id = $pane->configuration['container_id'];
            $config[$container_id] = $pane->configuration;
          }
        }
      }
    }
  }

  // Post-process the configurations.
  $old_settings = variable_get('panels_ajax_tab', array(
    'clean_url' => FALSE,
    'clean_url_delim' => '/',
    'panes' => array(),
  ));

  // Get the visibility settings, if available.
  $visibility_rules = variable_get('panels_ajax_tab_visibility_rules', array());
  foreach ($config as &$conf) {

    // Remove any unselected mini-panels from the config.
    foreach ($conf['mini_panels'] as $panel_id => $panel_conf) {
      if (!$panel_conf['selected']) {
        unset($conf['mini_panels'][$panel_id]);
      }
    }

    // Sort remaining panels.
    uasort($conf['mini_panels'], 'drupal_sort_weight');

    // Mix in any missing settings. Some settings could be missing due to
    // upgrade path.
    if (!isset($conf['clean_url'])) {
      $conf['clean_url'] = $old_settings['clean_url'];
    }
    if (!isset($conf['clean_url_delim'])) {
      $conf['clean_url_delim'] = $old_settings['clean_url_delim'];
    }

    // Add up visibility.
    $visibility_settings = !empty($visibility_rules[$conf['container_id']]) ? $visibility_rules[$conf['container_id']] : array();
    $conf['visibility_settings'] = !empty($conf['visibility_settings']) ? $conf['visibility_settings'] : $visibility_settings;
  }

  // This goes in an alter hook.
  $old_settings = variable_get('panels_ajax_tab', array(
    'clean_url' => FALSE,
    'clean_url_delim' => '/',
    'panes' => array(),
  ));
  foreach ($config as &$conf) {
    if (!isset($conf['highwire_panel_tab']) && !empty($old_settings['highwire_panel_tab'])) {
      $conf['highwire_panel_tab'] = $old_settings['highwire_panel_tab'];
    }
  }
  return $config;
}

/**
 * Helper function to get the visibility per container, if any.
 */
function _panels_ajax_tab_get_container_visibility_settings($container_id) {
  $tab_visibility_config =& drupal_static(__FUNCTION__);
  if (!isset($tab_visibility_config[$container_id])) {
    $tab_visibility_config[$container_id] = array();

    // Find the panel_tabs configuration associated with this container.
    $config_cache = cache_get('panels_ajax_tab_config_cache');
    if (!empty($config_cache)) {
      $configs = $config_cache->data;
    }
    else {
      $configs = panels_ajax_tab_config_cache();
    }
    foreach ($configs as $config) {
      if ($config['container_id'] == $container_id) {
        $tab_visibility_config[$container_id] = !empty($config['visibility_settings']) ? $config['visibility_settings'] : array();
        break;
      }
    }
  }
  return $tab_visibility_config[$container_id];
}

/**
 * Determine if the current user has access via  plugin.
 */
function panels_ajax_tab_ctools_access($mini_panel, $visibility_settings, $contexts) {
  if (empty($contexts) || empty($visibility_settings)) {
    return TRUE;
  }
  $panels_ajax_tab_access =& drupal_static(__FUNCTION__);
  $cache_arg = array(
    $mini_panel,
    $visibility_settings,
    $contexts,
  );
  $cache_key = __FUNCTION__ . '_' . md5(serialize($cache_arg));
  if (!isset($panels_ajax_tab_access[$cache_key])) {
    $panels_ajax_tab_access[$cache_key] = TRUE;
    $contexts = !is_array($contexts) ? array(
      $contexts,
    ) : $contexts;
    $new_contexts = array();
    foreach ($contexts as $key => $context) {
      $new_contexts[$context->id] = $context;
    }
    if (isset($visibility_settings[$mini_panel])) {
      $visibility_setting = $visibility_settings[$mini_panel];
      if (!empty($visibility_setting['visibility_rule']) && $visibility_setting['visibility_rule'] != 'none') {
        $setting = array();
        $setting['name'] = $visibility_setting['visibility_rule'];
        $setting['settings'] = $visibility_setting['visibility_settings'];
        $setting['not'] = $visibility_setting['visibility_settings']['not'];
        $setting['context'] = $context->id;
        $settings['plugins'] = array(
          $setting,
        );
        ctools_include('context');
        $panels_ajax_tab_access[$cache_key] = ctools_access($settings, $new_contexts);
      }
    }
  }
  return $panels_ajax_tab_access[$cache_key];
}

/**
 * If we are running nginx we need to implement getallheaders our self.
 *
 * Code is taken from http://php.net/manual/en/function.getallheaders.php.
 */
if (!function_exists('getallheaders')) {

  /**
   * Fetch all HTTP request headers.
   */
  function getallheaders() {
    foreach ($_SERVER as $name => $value) {
      if (substr($name, 0, 5) == 'HTTP_') {
        $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
      }
    }
    return $headers;
  }
}

Functions

Namesort descending Description
panels_ajax_tab_ajax AJAX callback for rendering tab.
panels_ajax_tab_boot Implements hook_boot().
panels_ajax_tab_config_cache Rebuild the URL cache.
panels_ajax_tab_container_load Loads a panels ajax tab configuration.
panels_ajax_tab_cron Implements hook_cron().
panels_ajax_tab_ctools_access Determine if the current user has access via plugin.
panels_ajax_tab_ctools_plugin_api Implements hook_ctools_plugin_api().
panels_ajax_tab_ctools_plugin_directory Implements hook_ctools_plugin_dierctory().
panels_ajax_tab_element_info_alter Implements hook_element_info_alter().
panels_ajax_tab_flush_caches Implements hook_flush_caches().
panels_ajax_tab_get_config Get a list of all panels-ajax-tab configs.
panels_ajax_tab_get_context Get a context from a context_string.
panels_ajax_tab_menu Implements hook_menu().
panels_ajax_tab_prepare_mini Prepares the mini-panel.
panels_ajax_tab_theme Implements hook_theme().
theme_panels_ajax_tab_ajax Theme function that renders the AHAH markup for a mini-panel.
theme_panels_ajax_tab_container Theme function that renders the container.
theme_panels_ajax_tab_tabs Theme function that renders the panels ajax tabs.
theme_panels_ajax_tab_tabs_edit_form Theme function that renders the pane configuration form.
_panels_ajax_tab_get_container_visibility_settings Helper function to get the visibility per container, if any.
_panels_ajax_tab_settings_title Title callback: Returns a title for Panel AJAX tab container.