<?php
// $Id$
/**
 * Callback function to supply a list of content types.
 */

/*
 * There are seven types of panels plugins at present: context, relationships,
 * arguments, content_types, styles, layouts, cache; Panels provides an API that
 * allows any module to implement these, but there's a starter set that comes
 * with Panels itself. They're found in the various subdirectories that live under
 * the Panels directory.
 * 
 * All of them (but for our purposes more importantly, content types) start with
 * a function that returns a 'plugin declaration array'. The declaration array is
 * an associative array containing a bunch of settings and callbacks that the Panels
 * API uses to determine when and how it utilizes the plugin. The Panels API looks
 * for certain keys in that array - exactly which keys depends on the plugin type -
 * and treats the value stored in that key as a directive governing how the plugin
 * should behave.
 * 
 * Remember - although grouping the callbacks defined in these directives into the 
 * same .inc file can be handy, you're not bound to doing so. If you have a 
 * visibility checker that you want to share across a lot of content types, for 
 * example, consider defining the visibility checker in the main .module file while
 * keeping the rest of the plugin in its own .inc file.
 * 
 * Yes, some unnecessary duplication with things like the add/edit directives; they
 * exist as they are largely for legacy compatibility reasons.
 * 
 * Plugin declaration functions must adhere to a particular naming convention; see
 * panels_get_directories() for details.
 * 
 * Note that the plugin declaration array given in this sample does NOT define a
 * coherent, working content type; displaying the full range of the plugin's
 * flexibility makes it impossible to do so. For working examples, see the various
 * plugins defined in the panels/content_types directory.
 */
function panels_SAMPLE_CT_panels_content_types() {
  // The string used for the array key IS significant: it will determine the pane's
  // "type". Namespace collisions are destructive, wherein plugins using a given namespace
  // that are read later by the Panels API will override plugins read earlier. That order is
  // generally determined by the weight of the module (in the system table) defining the
  // plugin. 
  // All the plugins that come packaged with Panels obey a strict naming convention out
  // of necessity: this array key is the same as the name of the file itself, as 
  // well as being the same as the string prefixed by the module name (here, 
  // 'panels'), and affixed by a string that indicates the type of plugin (here, 
  // 'panels_content_types'). Your modules need not follow the same conventions, 
  // although it is recommended that you do if possible; again, refer to
  // panels_get_directories() for the easiest method of doing so. 
  $items['SAMPLE_CT'] = array(
    // title (string): SETTING. The title that will be used for this pane on the 'Add Content' modal
    // form, and in the display content editor in general. This is a purely internal setting;
    // normal users will never see it.
    'title' => t('Sample Content Type'),
    // weight (int): SETTING. Standard drupal weighting concept at work here; all it determines is the
    // position of this content type's icon relative to the other content type icons on the
    // general Panels configuration forms. See panels_common_settings().
    'weight' => -10,
    // single (bool): SETTING. Indicates that this content type plugin provides only a single content 
    // type. Currently, this setting is ONLY used in figuring out how to group the content type
    // on the general Panels configuration forms; see panels_common_settings(). 
    // Check out panels_admin_content_types_block() for an example of how one plugin
    // can used define multiple content types (technically, multiple subtypes).
    // Defaults to FALSE.
    'single' => TRUE,
    // content_types (string): CALLBACK. The Panels API calls this function to get
    // basic information about the content types this plugin provides. See the callback
    // for details.
    'content_types' => 'panels_admin_content_types_SAMPLE_CT',
    // render callback (string): CALLBACK. The Panels API calls this function when it's
    // trying to render the pane for viewing. See the callback for details.
    'render callback' => 'panels_content_SAMPLE_CT',
    // add callback (string): CALLBACK. This function gets called when the user
    // clicks an icon to add a new pane (from the 'Add Content' modal form).
    // See the callback for details; note that it is often possible to use the
    // same, or nearly the same, callback for this as for the 'edit callback.'   
    'add callback' => 'panels_admin_add_SAMPLE_CT',
    // edit callback (string): CALLBACK. This function gets called when the user
    // clicks the 'Configure' button; it partially governs what appears on the
    // configuration modal form that pops up.
    'edit callback' => 'panels_admin_edit_SAMPLE_CT',
    // (add|edit) validate callback (string): CALLBACK. Defines a callback to be
    // used as a FAPI validator, but only for the $form_values set by form items
    // defined in the 'add/edit callback'.
    'add validate callback' => 'panels_admin_validate_SAMPLE_CT',
    'edit validate callback' => 'panels_admin_validate_SAMPLE_CT',
    // (add|edit) submit callback (string): CALLBACK. Defines a callback to be
    // used as a FAPI submit handler, but only for the $form_values set by form
    // items defined in the 'add/edit callback'.
    'add submit callback' => 'panels_admin_submit_SAMPLE_CT',
    'edit submit callback' => 'panels_admin_submit_SAMPLE_CT',  
    // title callback (string): CALLBACK. This function determines the title that
    // the pane will use, both in the admin interface and for general viewing. Note
    // that this can almost always be overridden.
    'title callback' => 'panels_admin_title_SAMPLE_CT',
    // render last (bool): SETTING. If set to TRUE, this pane will be pushed to the
    // back of the line during the render routine. See panels_render_panes().
    'render last' => TRUE,
    // visibility control (string): CALLBACK. This function gets called at the same
    // time as the add/edit callbacks. Use it to create a form widget that allowing the
    // user to select values that will make sense when passed to your 'visibility check'
    // callback. If your plugin declares this option, you'll need to be cognizant
    // of your declarations for several other options: 'visibility submit', 'visibility
    // check' and 'visibility serialize'. 'roles and visibility' is also relevant,
    // but won't be useful to the vast majority of content type plugins.
    'visibility control' => 'panels_admin_visibility_control_SAMPLE_CT',
    // visibility submit (string): CALLBACK. The custom submit handler you define
    // for your content type's visibility settings. This function is passed
    // whatever selection the user makes on the form widget defined by the
    // 'visibility control' callback. Implement this if you need to wrangle that
    // data before the Panels API's data storage routines kick in, or if the API's
    // built-in routines are inadequate and you need to build a custom storage
    // mechanism. See panels_content_config_form() and panels_content_config_form_submit()
    // to grok the logic behind if/when/how this callback is triggered. 
    'visibility submit' => 'panels_admin_visibility_submit_SAMPLE_CT',
    // visibility check (string): CALLBACK. The Panels API calls this function during 
    // the pane accessibility checking routine - that's done in panels_pane_access().
    // Define the logic governing your content type's visibility here.
    'visibility check' => 'panels_content_visibility_check_SAMPLE_CT',
    // visibility serialize (bool): SETTING. Set this to 'true' if the contents of 
    // $pane->visibility need to be serialized before being written to the database. 
    // Set this to TRUE if, for example, your visibility form widget uses checkboxes 
    // (and therefore generates an array), as opposed to if your widget uses radios 
    // (and therefore generates an integer that can be stored directly). See 
    // panels_content_config_form_submit() and panels_save_display() to better
    // understand how this works.
    // Defaults to FALSE.
    'visibility serialize' => TRUE,
    // role-based access (bool): SETTING. Boolean setting to indicate whether you
    // want the your content type to utilize the Panels API's built-in access
    // system, which is based on drupal user roles. Set this to FALSE to disable
    // role-based access. Note that this will automatically be set to FALSE if
    // a 'visibility control' callback is defined.
    // Defaults to TRUE.
    'role-based access' => FALSE,
    // roles and visibility (bool): SETTING. If you want your content type to use
    // both your custom visibility logic and Panels' built-in roles-based access
    // system, then set this to TRUE. Setting 'role-based access' to TRUE is not
    // sufficient; see panels_ajax_ct_preconfigure() to understand how this works.
    // If you use both systems, panels_pane_access() will AND the results together
    // when determining pane visibility.
    'roles and visibility' => TRUE,
    // form control (string): CALLBACK. If the other callbacks governing the add/edit
    // form ('add/edit callback', 'visibility control') aren't enough for your needs,
    // then implement this callback. The callback will be passed a fully-built $form object
    // by reference, and you can alter it as you see fit. Use with caution.
    'form control' => 'panels_admin_form_control_SAMPLE_CT',
  );
  return $items;
}

/**
 * Callback function set by the 'content_types' option. Returns an array of
 * data used by the Panels API to determine:
 *    - Whether or not the content type can be added to this display, based on
 *      what context(s) are available. 
 *    - If context requirements are met, the remainder of the array's data
 *      defines the icon, title, and description that the content type will
 *      be rendered with on the the Add Content modal.
 */
function panels_admin_content_types_SAMPLE_CT() {
  return array(
    // As with the plugin declaration, the value of this array key is significant:
    // it will become the pane's subtype, stored in $pane->subtype.
    'content' => array(
      // The name used for this subtype on the Add Content modal - this is what
      // appears right below the icon.
      'title' => t('SAMPLE CONTENT TYPE'),
      // The name of the icon file to be used for this subtype.
      'icon' => 'icon_node.png',
      // The server path to the directory where the above icon is located.
      'path' => panels_get_path('content_types/node'),
      // The 'description' appears as a tooltip when the user hovers their
      // mouse pointer over the icon.
      'description' => t('Descriptive text for the SAMPLE CONTENT TYPE, to be used in the tooltip.'),
      // This option indicates which contexts are prerequisites for the content
      // type to be used. If a display lacks a context required by this content
      // type, then it simply will not be displayed. Multiple required contexts 
      // can be declared by placing each context into an indexed array. 
      'required context' => new panels_required_context(t('Sample Required Context'), 'sample_context_required'),
      // This option has the same syntax as 'required context', but if optional
      // context requirements are not met, the content type will still be usable,
      // simply in a reduced form. It's up to the plugin author to define just how
      // different that functionality by writing varying the behavior of this plugin's
      // other callbacks according to the presence/absence of the context.
      'optional context' => new panels_optional_context(t('Sample Optional Context'), 'sample_context_optional'),
      // Category is the group this subtype's icon will be placed in. The first
      // item in the array is the category name, and the second is the subtype's
      // weight in that category (used for ordering the subtypes in the category
      // relative to one another). Omitting a value for weight will cause it to
      // default to 0; if you do omit the weight, you can simply return the
      // t()-wrapped string title of the content type - no need to put it in an array.
      'category' => array(t('Node context'), -9),
    ),
  );
}

/**
 * Callback function set by the 'render callback' option. This callback constructs 
 * and returns an object for display.
 * 
 * The sample function below is a direct copy of the node_content plugin's render
 * callback; abstract example cases are of little use from here on out. Note that
 * this case only implements three parameters, but there is also a fourth. Your
 * content type can use as few/many of these parameters as you want, although
 * you won't be able to much if you don't implement the first parameter, $conf.
 *
 * @param array $conf
 *  The contents of $pane->configuration. This will be an array with the following
 *  keys, by default:
 *    - override_title (int/bool): 0 or 1, reflecting whether the user checked the
 *      'override title' checkbox on the pane configuration form.
 *    - override_title_text (string): a string containing the title override, as
 *      written by the user on the pane configuration form.
 *    - css_id (string): the special css id entered by the user on the pane config
 *      form, if any.
 *    - css_class (string): same idea as the css id.
 *    - module (string): a string containing the name of the module implementing
 *      this content type (or, in some cases, owning/generating the content).\n
 * The above keys reflect the standard set of form items that the Panels API
 * provides to every pane type by default. Any additional configuration items that
 * you add (via the add/edit callbacks) will also appear in $conf by default.
 * @param array $panel_args
 *  An indexed array of all arguments, if any, that have been passed to the display.
 * @param mixed $context
 *  The contents of $context can vary widely. If only one context is being passed
 *  to the pane, $context will simply be that context object. If multiple contexts
 *  are passed, however, then $context will be an indexed array of those contexts.
 *  The sort of data contained in the context is completely dependent on the how
 *  that context has been defined.
 * @param $incoming_content
 * 
 * @return object $block
 *  An object, ready to be passed through the styling & theming layers. At minimum,
 *  the object should contain a 'content' element, as well as 'title' and/or
 *  'subject' elements. If a 'title' element is not included, then the 'subject'
 *  is copied into $block->title later on in the render process. You are free to
 *  define as many elements as you want, but those elements will only be used
 *  if you write a panels style plugin specifically designed to take advantage of
 *  of them. Note that the '$block' variable name used here is arbitrary.
 */
function panels_content_SAMPLE_CT($conf, $panel_args, $context) {
  // The node_content content_type plugin has a required context of 'node.'
  // This simply double-checks to make sure that the necessary context is present;
  // in particular, it excludes 'empty' contexts, which are used primarily during
  // the edit process.
  if (!empty($context) && empty($context->data)) {
    return;
  }

  // The node context plugin stores an entire, fully-loaded $node object into
  // its $context->data element; this pulls that node data out (via cloning, to
  // ensure the original context data itself remains unchanged) and stores it in
  // a correspondingly-named variable, $node.
  $node = isset($context->data) ? drupal_clone($context->data) : NULL;
  $block->module = 'node';
  // Stores the nid from the context, to ensure it is acecssible later.
  $block->delta = $node->nid;

  // Just in case the context didn't load, but managed to get past the initial
  // checks, this adds filler content to the $block.
  if (empty($node)) {
    $block->delta = 'placeholder';
    $block->subject = t('Node title.');
    $block->content = t('Node content goes here.');
  }
  else {
    if (!empty($conf['identifier'])) {
      $node->panel_identifier = $conf['identifier'];
    }

    $block->subject = $node->title;

    unset($node->title);
    // The pane's content is a complex enough operation that we delegate creating
    // it to a helper function.
    $block->content = panels_admin_SAMPLE_CT($node, $conf);
  }

  // If the user has the necessary permissions, an 'admin link' is generated.
  // Admin links are the special links that appear above the pane's title when 
  // you mouse over the pane.
  if (node_access('update', $node)) {
    $block->admin_links['update'] = array(
      'title' => t('Edit node'),
      'alt' => t("Edit this node"),
      'href' => "node/$node->nid/edit",
      'query' => drupal_get_destination(),
    );
  }

  if (!empty($conf['link']) && $node) {
    $block->title_link = "node/$node->nid";
  }

  return $block;
}

/**
 * Probably the most important lesson to be noted about this helper function is
 * just how similar it is to node.module's routine for node rendering. 
 * In fact, helper function is little more than a minor rewrite of
 * node_view(); the first lines are lifted directly from node_build_content(),
 * and the latter half from node_view().
 */
function panels_admin_SAMPLE_CT($node, $conf) {
  // Remove the delimiter (if any) that separates the teaser from the body.
  $node->body = str_replace('<!--break-->', '', $node->body);

  // The 'view' hook can be implemented to overwrite the default function
  // to display nodes.
  if (node_hook($node, 'view')) {
    $node = node_invoke($node, 'view', $conf['teaser'], $conf['page']);
  }
  else {
    $node = node_prepare($node, $conf['teaser']);
  }

  if (empty($conf['no_extras'])) {
  // Allow modules to make their own additions to the node.
    node_invoke_nodeapi($node, 'view', $conf['teaser'], $conf['page']);
  }

  if ($conf['links']) {
    $node->links = module_invoke_all('link', 'node', $node, $conf['teaser']);

    foreach (module_implements('link_alter') AS $module) {
      $function = $module .'_link_alter';
      $function($node, $node->links);
    }
  }

  // Set the proper node part, then unset unused $node part so that a bad
  // theme can not open a security hole.
  $content = drupal_render($node->content);
  if ($conf['teaser']) {
    $node->teaser = $content;
    unset($node->body);
  }
  else {
    $node->body = $content;
    unset($node->teaser);
  }

  // Allow modules to modify the fully-built node.
  node_invoke_nodeapi($node, 'alter', $conf['teaser'], $conf['page']);

  return theme('node', $node, $conf['teaser'], $conf['page']);
}

/**
 * Callback function set by the 'add callback' option. This callback constructs
 * the pane configuration form for newly-added panes. This sample is lifted from
 * the block content type plugin (block.inc); it is the only built-in Panels content
 * type that implements an add callback that is different from the edit callback.
 * 
 * Clearly there's relatively little need to differentiate between the add and edit
 * callbacks; the only thing this one does is make sure that $conf has some of the
 * right values before heading into the edit form. You still need to define both the
 * 'add callback' and 'edit callback' options in the plugin declaration array, but you
 * can just make them point to the same function.
 * 
 * See the edit callback for more detailed discussion.
 *
 */
function panels_admin_add_SAMPLE_CT($id, $parents, $conf = array()) {
  list($conf['module'], $conf['delta']) = explode('-', $id, 2);
  return panels_admin_edit_SAMPLE_CT($id, $parents, $conf);
}

/**
 * Callback function set by the 'edit callback' option in the plugin declaration array.
 * This callback constructs the configuration form for panes that have already been added;
 * the callback is fired when the 'Configure' button is clicked.
 * 
 * This function essentially operates like a limited and targeted implementation of
 * hook_form_alter(); the Panels API wrangles FAPI as needed, so all you need to do
 * is add the widgets you want for your content type/subtype.
 * 
 * NOTE - in future versions, the 'Block visibility' options are likely to be moved into
 * the appropriate visibility callbacks. They're here now because the block content type
 * plugin was written long before the visibility system was introduced.
 * 
 * Some of the techniques used in this edit callback are pretty advanced. For a more basic
 * but quite thorough implementation of this callback, see panels_admin_edit_node_content(). 
 *
 * @param string $id
 *  The subtype of the pane being edited. The block panels content type plugin calls this 
 *  variable '$id' for legacy reasons; we recommend you call this variable $subtype if you
 *  want your variable names to be optimally descriptive of their values.
 * @param array $parents
 *  This parameter is largely deprecated, and is included for legacy API compatibility. Its
 *  intention was to provide information to form widgets about where they live on the $form.
 *  It is likely to disappear in Panels3. For all add/edit callbacks:
 *  @code $parents = array('configuration'); @endcode
 *  This corresponds to the fact that the $form returned from this callback will not be added
 *  to the root of the overall $form array, but to the $form['configuration'] sub-array.
 *  See panels_content_config_form().
 * @param array $conf
 *  The contents of $pane->configuration, if any.
 * @return array $form
 *  A standard FAPI form array.
 */
function panels_admin_edit_SAMPLE_CT($id, $parents, $conf) {
  $form['module'] = array(
    '#type' => 'value',
    '#value' => $conf['module'],
  );
  $form['delta'] = array(
    '#type' => 'value',
    '#value' => $conf['delta'],
  );

  if (user_access('administer advanced pane settings')) {
    $form['block_visibility'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use block visibility settings (see block config)'),
      '#default_value' => $conf['block_visibility'],
      '#description' => t('If checked, the block visibility settings for this block will apply to this block.'),
    );
    // Module-specific block configurations.
    if ($settings = module_invoke($conf['module'], 'block', 'configure', $conf['delta'])) {
      // Specifically modify a couple of core block forms.
      if ($conf['module'] == 'block') {
        unset($settings['submit']);
        $settings['info']['#type'] = 'value';
        $settings['info']['#value'] = $settings['info']['#default_value'];
      }
      panels_admin_fix_block_tree($settings);
      $form['block_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('Block settings'),
        '#description' => t('Settings in this section are global and are for all blocks of this type, anywhere in the system.'),
        '#tree' => FALSE,
      );


      $form['block_settings'] += $settings;
    }
  }

  return $form;
}

/**
 * Callback function set by the '[add|edit] submit callback' option in the plugin
 * declaration array. In simpler plugins, you won't need to implement this callback;
 * any values set by the widgets you added to the config form will automatically
 * have their data added to the $pane->configuration array, which is serialized
 * and stored in the panels_pane table.
 * 
 * However, in cases where the settings being changed on this form need to be
 * reflected in some other data structure, this callback can be used to ensure
 * that the necessary changes are made. In this example (again from the block
 * content type plugin), hook_block() is invoked with $op = 'save' for the module
 * that owns the block, thereby allowing the normal block saving routine to
 * do its thing.
 */
function panels_admin_submit_SAMPLE_CT(&$form_values) {
  if (!empty($form_values['block_settings'])) {
    module_invoke($form_values['module'], 'block', 'save', $form_values['delta'], $form_values['block_settings']);
  }
}

function panels_admin_title_SAMPLE_CT($conf, $context) {
  return t('"@s" content', array('@s' => $context->identifier));
}

/**
 * Callback function set by the 'visibility control' option in the plugin declaration array.
 * 
 * Operates quite similarly to the add and edit callbacks, with a few exceptions:
 *    - The FAPI parent is $form['visibility'] instead of $form['configuration']
 *    - Instead of creating separate callbacks for add and edit, the $add parameter
 *      indicates which operation is taking place.
 *    - Whereas a submit handler is often unnecessary for the add/edit callbacks,
 *      implementing 'visibility control' means you need to be cognizant of several
 *      other options in the declaration array.
 * The visibility widget defined in this function is a reworked version of one originally
 * created for og_panels (but was not/has not yet been committed); the goal is to control
 * pane visibility according to the status of the current user relative to the group.
 * 
 * Remember, this is NOT where you define the logic behind your visibility handling -
 * all you're doing here is providing a form widget that to get some data. It's up to
 * your visibility checker, defined in the 'visibility check' callback, to create the
 * logic that can take the data from here and make the right decision about pane
 * visibility.
 *
 * @param mixed $contexts
 *  As in the render callback, this is either a context object, or an array of context
 *  objects. It's unnecessary and probably unwise to include this context
 *  data directly in the values that get saved in your form visibility function; that
 *  very same data will be available via the $display variable that's passed to the
 *  checker. Rather, $context is provided in the event that your visibility widget
 *  needs to vary depending on some information in $context.
 * @param string $subtype
 *  The contents of $pane->subtype for the pane currently being edited.
 * @param array $conf
 *  The contents of $pane->configuration, if any.
 * @param bool $add
 *  If TRUE, then a new pane is being added. If FALSE, then an existing pane is being edited.
 * @return array $visibility_widget
 *  A standard FAPI widget, to be added to the form.
 */
function panels_admin_visibility_control_SAMPLE_CT($contexts, $subtype, $conf, $add) {
  return $visibility_widget = array(
    '#type' => 'radios',
    '#title' => t('Pane Visibility'),
    '#description' => t('Who should this pane be visible to?'),
    '#options' => array(
      'all' => t('Everyone'),
      'member' => t('Only group members'),
      'nonmember' => t('Only group non-members'),
      'admin' => t('Group administrators'),
      ),
    '#default_value' => isset($conf['visibility']) ? $conf['visibility'] : 0,
  );
}

/**
 * Callback function set by the 'visibility check' option in the plugin declaration array.
 * 
 * This function takes advantage of cached static variables to increase performance. On any
 * given page request, we know that only ONE group is going to be accessed, and only ONE user
 * is going to be doing the accessing. Since the static keyword only lasts through a single page
 * request, and nid and uid are the two variables that visibility depends upon in this case,
 * we only have to query the database and build the $visibility array once, no matter how many 
 * panes fire this callback to determine visibility during this page request.
 *
 * @param object $pane
 *  The fully-loaded $pane object that we're running the visibility check against. The 
 *  value set by the widget defined in the 'visibility control' callback is contained 
 *  in $pane->visibility.
 * @param object $display
 *  The fully-loaded display object that's currently being rendered. If you need $context
 *  to figure out what action to take, you'll find it/them in $display->context.
 * @param object $user
 *  The current $user - the same as what you'd get from @code global $user @endcode. 
 *  Passed for convenience, since visibility is often dependent on the $user.
 * @return bool
 *  A boolean indicating if the pane should (TRUE) or should not (FALSE) be visible.
 */
function panels_content_visibility_check_SAMPLE_CT($pane, $display, $user) {
  // use static variable to somewhat reduce queries for complex og_panels pages
  static $visible;
  if (!is_array($visible)) {
    $visible = array();
    $visible['all'] = TRUE;
  }
  if (!isset($visible[$pane->visibility])) {
    $members = array();
    $sql = "SELECT u.uid AS uid, ogu.is_admin AS admin FROM {og_uid} ogu INNER JOIN {users} u ON ogu.uid = u.uid WHERE ogu.nid = %d AND ogu.is_active = 1 AND u.status = 1 ORDER BY ogu.created DESC";
    // $display holds the context; the $data element of $context holds a node object,
    // and we want the nid of that node.
    $result = db_query($sql, $display->context->data->nid);
    while ($account = db_fetch_array($result)) {
      $members[$account['uid']] = $account['admin'];
    }
    $visible['member'] = in_array($user->uid, array_keys($members));
    $visible['nonmember'] = !$visible['member'];
    $visible['admin'] = $visible['member'] ? $members[$user->uid] : FALSE;
  }
  return $visible[$pane->visibility];
}  

/**
 * Callback function set by the 'visibility submit' option in the plugin declaration array.
 * 
 * Note that in this particular sample implementation, the 'visibility serialize'
 * directive should be set to FALSE, as the value produced by the widget in the 
 * sample 'visibility control' function returns a simple string.
 * 
 * Note also that the visibility field in the panels_pane table is the standard
 * 'text' field. The save routines in panels_save_display() will always 
 * convert the value to a string, so even if you send an integer in, you'll get
 * a string back out on the other end.
 * 
 * Note finally that this function is COMPLETELY superfluous, and is only included
 * for the purposes of showing 
 * 
 * See panels_content_config_form_submit() for the context in which this function is called.
 *
 * @param string $visibility_value
 * @return string $visibility_value
 */
function panels_admin_visibility_submit_SAMPLE_CT($visibility_value) {
  return $visibility_value;
}


function panels_admin_form_control_SAMPLE_CT(&$form) {
  $form['shmear'] = array('#type' => 'value', '#value' => 'SHMEAR ME!');
}
