<?php
/**
 * Collection of default system-wide fields
 * @package Simple Lightbox
 * @subpackage Fields
 * @author Archetyped
 *
 */
class SLB_Fields extends SLB_Field_Collection {

	var $item_type = 'SLB_Field_Type';

	/**
	 * Placeholder handlers
	 * @var array
	 */
	var $placholders = null;

	/* Constructor */

	function __construct() {
		parent::__construct('fields');
	}

	protected function _hooks() {
		parent::_hooks();
		// Init fields
		add_action('init', $this->m('register_types'));
		// Init placeholders
		add_action('init', $this->m('register_placeholders'));
	}

	/* Field Types */

	/**
	 * Initialize fields
	 */
	function register_types() {
		/* Field Types */

		// Base
		$base = new SLB_Field_Type('base');
		$base->set_description(__('Default Element', 'simple-lightbox'));
		$base->set_property('tag', 'span');
		$base->set_property('class', '', 'attr');
		$base->set_layout('form_attr', '{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"}');
		$base->set_layout('form', '<{form_attr ref_base="layout"} />');
		$base->set_layout('label', '<label for="{field_id}">{label}</label>');
		$base->set_layout('display', '{data context="display"}');
		$this->add($base);

		// Base closed
		$base_closed = new SLB_Field_Type('base_closed');
		$base_closed->set_parent('base');
		$base_closed->set_description(__('Default Element (Closed Tag)', 'simple-lightbox'));
		$base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>');
		$base_closed->set_layout('form_end', '</{tag}>');
		$base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}');
		$this->add($base_closed);

		// Input
		$input = new SLB_Field_Type('input', 'base');
		$input->set_description(__('Default Input Element', 'simple-lightbox'));
		$input->set_property('tag', 'input');
		$input->set_property('type', 'text', 'attr');
		$input->set_property('value', '{data}', 'attr');
		$this->add($input);

		// Text input
		$text = new SLB_Field_Type('text', 'input');
		$text->set_description(__('Text Box', 'simple-lightbox'));
		$text->set_property('size', 15, 'attr');
		$text->set_property('label');
		$text->set_layout('form', '{label ref_base="layout"} {inherit}');
		$this->add($text);

		// Checkbox
		$cb = new SLB_Field_Type('checkbox', 'input');
		$cb->set_property('type', 'checkbox');
		$cb->set_property('value', null);
		$cb->set_layout('form_attr', '{inherit} {checked}');
		$cb->set_layout('form', '{label ref_base="layout"} <{form_attr ref_base="layout"} />');
		$this->add($cb);

		// Textarea
		$ta = new SLB_Field_Type('textarea', 'base_closed');
		$ta->set_property('tag', 'textarea');
		$ta->set_property('cols', 40, 'attr');
		$ta->set_property('rows', 3, 'attr');
		$this->add($ta);

		// Rich Text
		$rt = new SLB_Field_Type('richtext', 'textarea');
		$rt->set_property('class', 'theEditor {inherit}');
		$rt->set_layout('form', '<div class="rt_container">{inherit}</div>');
		$rt->add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
		$this->add($rt);

		// Hidden
		$hidden = new SLB_Field_Type('hidden');
		$hidden->set_parent('input');
		$hidden->set_description(__('Hidden Field', 'simple-lightbox'));
		$hidden->set_property('type', 'hidden');
		$this->add($hidden);

		// Select
		$select = new SLB_Field_Type('select', 'base_closed');
		$select->set_description(__('Select tag', 'simple-lightbox'));
		$select->set_property('tag', 'select');
		$select->set_property('tag_option', 'option');
		$select->set_property('options', array());
		$select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{option_loop ref_base="layout"}{form_end ref_base="layout"}');
		$select->set_layout('option_loop', '{loop data="properties.options" layout="option" layout_data="option_data"}');
		$select->set_layout('option', '<{tag_option} value="{data_ext id="option_value" context="attr"}">{data_ext id="option_text" context="text"}</{tag_option}>');
		$select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value" context="attr"}" selected="selected">{data_ext id="option_text" context="text"}</{tag_option}>');
		$this->add($select);

		// Span
		$span = new SLB_Field_Type('span', 'base_closed');
		$span->set_description(__('Inline wrapper', 'simple-lightbox'));
		$span->set_property('tag', 'span');
		$span->set_property('value', 'Hello there!');
		$this->add($span);

		// Enable plugins to modify (add, remove, etc.) field types
		$this->util->do_action_ref_array('register_fields', array($this), false);

		// Signal completion of field registration
		$this->util->do_action_ref_array('fields_registered', array($this), false);
	}

	/* Placeholder handlers */

	function register_placeholders() {
		// Default placeholder handlers
		$this->register_placeholder('all', $this->m('process_placeholder_default'), 11);
		$this->register_placeholder('field_id', $this->m('process_placeholder_id'));
		$this->register_placeholder('field_name', $this->m('process_placeholder_name'));
		$this->register_placeholder('data', $this->m('process_placeholder_data'));
		$this->register_placeholder('data_ext',$this->m('process_placeholder_data_ext'));
		$this->register_placeholder('loop', $this->m('process_placeholder_loop'));
		$this->register_placeholder('label', $this->m('process_placeholder_label'));
		$this->register_placeholder('checked', $this->m('process_placeholder_checked'));

		// Allow other code to register placeholders
		$this->util->do_action_ref_array('register_field_placeholders', array($this), false);

		// Signal completion of field placeholder registration
		$this->util->do_action_ref_array('field_placeholders_registered', array($this), false);
	}

	/**
	 * Register a function to handle a placeholder
	 * Multiple handlers may be registered for a single placeholder
	 * Adds filter hook to WP for handling specified placeholder
	 * Placeholders are in layouts and are replaced with data at runtime
	 * @uses add_filter()
	 * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
	 * @param callback $callback Function to set as a handler
	 * @param int $priority (optional) Priority of handler
	 * @return void
	 */
	function register_placeholder($placeholder, $callback, $priority = 10) {
		if ( 'all' == $placeholder )
			$placeholder = '';
		else
			$placeholder = '_' . $placeholder;
		$hook = $this->add_prefix('process_placeholder' . $placeholder);
		add_filter($hook, $callback, $priority, 5);
	}

	/**
	 * Default placeholder processing
	 * To be executed when current placeholder has not been handled by another handler
	 * @param string $output Value to be used in place of placeholder
	 * @param SLB_Field $item Field containing placeholder
	 * @param array $placeholder Current placeholder
	 * @see SLB_Field::parse_layout for structure of $placeholder array
	 * @param string $layout Layout to build
	 * @param array $data Extended data for item
	 * @return string Value to use in place of current placeholder
	 */
	function process_placeholder_default($output, $item, $placeholder, $layout, $data) {
		// Validate parameters before processing
		if ( empty($output) && ($item instanceof SLB_Field_Type) && is_array($placeholder) ) {
			// Build path to replacement data
			$output = $item->get_member_value($placeholder);

			// Check if value is group (properties, etc.)
			// All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
			if (is_array($output)
				&& !empty($placeholder['attributes'])
				&& is_array($placeholder['attributes'])
				&& ($ph = $item->get_placeholder_defaults())
				&& $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved))
			) {
				/* Targeted property is an array, but the placeholder contains additional options on how property is to be used */

				// Find items matching criteria in $output
				// Check for group criteria
				if ( 'properties' == $placeholder['tag'] && ($prop_group = $item->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) {
					/* Process group */
					$group_out = array();
					// Iterate through properties in group and build string.
					foreach ( array_keys( $prop_group ) as $prop_key ) {
						$prop_val = $item->get_property( $prop_key );
						if ( !is_null( $prop_val ) ) {
							// Process placeholders.
							$prop_val = $item->process_placeholders( $prop_val, $layout, $data );
							// Add property to attribute string output.
							$group_out[] = esc_attr( $prop_key ) . '="' . esc_attr( $prop_val ) . '"';
						}
					}
					$output = implode(' ', $group_out);
				}
			} elseif ( is_object($output) && ($output instanceof $item->base_class) ) {
				/* Targeted property is actually a nested item */
				// Set caller to current item
				$output->set_caller($item);
				// Build layout for nested element
				$output = $output->build_layout($layout);
			}
		}

		return $output;
	}

	/**
	 * Renders field ID formatted for a form field's `id` attribute.
	 *
	 * ID is formatted to be unique identifier for form field.
	 * Example: `options_field_id`.
	 * Registered as handler for `{field_id}` placeholder.
	 *
	 * @param string $output Placeholder's rendered value.
	 * @param SLB_Field $item Field containing placeholder.
	 * @param array &$placeholder Placeholder being processed.
	 * @param string $layout Name of layout being built.
	 * @param array $data Additional data for current field.
	 * @return string Field's ID (formatted for a form field's `id` attribute).
	 */
	function process_placeholder_id( $output, $item, &$placeholder, $layout, $data ) {
		// Get attributes
		$args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id'));
		$output = $item->get_id($args);
		// Set default placeholder context.
		if ( ! isset( $placeholder['attributes']['context'] ) ) {
			$placeholder['attributes']['context'] = 'attr';
		}
		return $output;
	}

	/**
	 * Renders field ID formatted for a form field's `name` attribute.
	 *
	 * ID is formatted to be part of an associative array for processing form submission.
	 * Example: `options[field_id]`.
	 * Registered as handler for `{field_name}` placeholder.
	 *
	 * @param string $output Placeholder's rendered value.
	 * @param SLB_Field $item Field containing placeholder.
	 * @param array &$placeholder Placeholder being processed.
	 * @param string $layout Name of layout being built.
	 * @param array $data Additional data for current field.
	 * @return string Field's ID (formatted for a form field's `name` attribute).
	 */
	function process_placeholder_name($output, $item, &$placeholder, $layout, $data) {
		// Get attributes
		$args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_name'));
		$output = $item->get_id($args);
		// Set default placeholder context.
		if ( ! isset( $placeholder['attributes']['context'] ) ) {
			$placeholder['attributes']['context'] = 'attr';
		}
		return $output;
	}

	/**
	 * Build item label
	 * @see SLB_Fields::process_placeholder_default for parameter descriptions
	 * @return string Field label
	 */
	function process_placeholder_label($output, $item, $placeholder, $layout, $data) {
		// Check if item has label property (e.g. sub-elements)
		$out = $item->get_property('label');
		// If property not set, use item title
		if ( empty($out) )
			$out = $item->get_title();
		return $out;
	}

	/**
	 * Retrieve data for item
	 * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
	 * @return string Placeholder output
	 */
	function process_placeholder_data($output, $item, $placeholder, $layout) {
		$opts = $placeholder['attributes'];
		// Strip context from data retrieval options (Formatting handled upstream).
		if ( is_array( $opts ) ) {
			unset( $opts['context'] );
		}
		// Get data
		$out = $item->get_data($opts);
		if ( ! is_null($out) ) {
			// Get specific member in value (e.g. value from a specific item element)
			if ( isset($opts['element']) && is_array($out) && ( $el = $opts['element'] ) && isset($out[$el]) )
				$out = $out[$el];
		}

		// Return data
		return $out;
	}

	/**
	 * Set checked attribute on item
	 * Evaluates item's data to see if item should be checked or not
	 * @see SLB_Fields::process_placeholder_default for parameter descriptions
	 * @return string Appropriate checkbox attribute
	 */
	function process_placeholder_checked($output, $item, $placeholder, $layout, $data) {
		$out = '';
		$c = $item->get_container();
		$d = ( isset($c->data[$item->get_id()]) ) ? $c->data[$item->get_id()] : null;
		$item->set_property('d', true);
		if ( $item->get_data() )
			$out = 'checked="checked"';
		$item->set_property('d', false);
		return $out;
	}

	/**
	 * Loops over data to build item output
	 * Options:
	 *  data		- Dot-delimited path in item that contains data to loop through
	 *  layout		- Name of layout to use for each data item in loop
	 *  layout_data	- Name of layout to use for data item that matches previously-saved item data
	 * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
	 * @return string Placeholder output
	 */
	function process_placeholder_loop($output, $item, $placeholder, $layout, $data) {
		// Setup loop options
		$attr_defaults = array (
								'layout'		=> '',
								'layout_data'	=> null,
								'data'			=> ''
								);
		$attr = wp_parse_args($placeholder['attributes'], $attr_defaults);
		if ( is_null($attr['layout_data']) )
			$attr['layout_data'] =& $attr['layout'];
		// Get data for loop
		$path = explode('.', $attr['data']);
		$loop_data = $item->get_member_value($path);

		// Check if data is callback
		if ( is_callable($loop_data) )
			$loop_data = call_user_func($loop_data);

		// Get item data
		$data = $item->get_data();

		// Iterate over data and build output
		$out = array();
		if ( is_array($loop_data) && !empty($loop_data) ) {
			foreach ( $loop_data as $value => $label ) {
				// Load appropriate layout based on item value
				$layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout'];
				// Stop processing if no valid layout is returned
				if ( empty($layout) )
					continue;
				// Prep extended item data
				$data_ext = array('option_value' => $value, 'option_text' => $label);
				$out[] = $item->build_layout($layout, $data_ext);
			}
		}

		// Return output
		return implode($out);
	}

	/**
	 * Returns specified value from extended data array for item.
	 *
	 * @param string $output Value to be used in place of placeholder.
	 * @param SLB_Field $item Field containing placeholder.
	 * @param array $placeholder Current placeholder.
	 * @see SLB_Field::parse_layout for structure of `$placeholder` array.
	 * @param string $layout Name of layout being built.
	 * @param array $data Extended data for item.
	 *
	 * @return string Processed value.
	 */
	function process_placeholder_data_ext( $output, SLB_Field $item, array $placeholder, $layout, array $data ) {
		$key = ( isset( $placeholder['attributes']['id'] ) ) ? $placeholder['attributes']['id'] : false;
		if ( !! $key && isset( $data[ $key ] ) && is_scalar( $data[ $key ] ) ) {
			$output = strval( $data[ $key ] );
		}

		return $output;
	}

	/* Build */

	/**
	 * Output items in a group
	 * @param string $group ID of Group to output
	 * @return string Group output
	 * TODO Make compatible with parent::build_group()
	 */
	function build_group($group) {
		$out = array();
		$classnames = (object) array(
			'multi'		=> 'multi_field',
			'single'	=> 'single_field',
			'elements'	=> 'has_elements'
		);

		// Stop execution if group does not exist
		if ( $this->group_exists($group) && $group =& $this->get_group($group) ) {
			$group_items = ( count($group->items) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->items) ) && ( $f =& $group->items[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' );
			$classname = array($this->add_prefix('attributes_wrap'), $group_items);
			$out[] = '<div class="' . implode(' ', $classname) . '">'; // Wrap all items in group

			// Build layout for each item in group
			foreach ( array_keys($group->items) as $item_id ) {
				$item =& $group->items[$item_id];
				$item->set_caller($this);
				// Start item output
				$id = $this->add_prefix('field_' . $item->get_id());
				$out[] = '<div id="' . $id . '_wrap" class=' . $this->add_prefix('attribute_wrap') . '>';
				// Build item layout
				$out[] = $item->build_layout();
				// end item output
				$out[] = '</div>';
				$item->clear_caller();
			}
			$out[] = '</div>'; // Close items container
			// Add description if exists
			if ( !empty($group->description) )
				$out[] = '<p class=' . $this->add_prefix('group_description') . '>' . $group->description . '</p>';
		}

		// Return group output
		return implode($out);
	}
}